mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-01 06:28:38 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 257158321c | |||
| ddf0a41e29 | |||
| 357e1e8e96 | |||
| 7a89f9869a | |||
| 30fbd22efe | |||
| 29df73ba72 | |||
| f8676483c1 | |||
| c79a6f2fba | |||
| c38e67ea61 | |||
| 97a5f8805e | |||
| 421fef6ebf | |||
| 33406c3082 | |||
| c3851889b0 | |||
| 4a5de2da6b | |||
| 1afb23cada | |||
| a3b0f3f868 | |||
| 0aa32916d6 | |||
| ebe845d46c | |||
| edee8d2fc5 | |||
| 5f3de0876f | |||
| 2ceab04d01 | |||
| de37de44e8 | |||
| ccb87d5a13 | |||
| e2e0ca3f05 | |||
| b33a79704b | |||
| 6def174b84 | |||
| 346a18b444 | |||
| b32685c698 | |||
| 09b3618da8 | |||
| 901328f247 | |||
| b47ebaa9d9 | |||
| 465e8d6c0a | |||
| ecd86ec877 | |||
| 5d7864154c | |||
| 7b915428f7 |
+12
@@ -2,6 +2,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## 3.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
|
## 3.0.2
|
||||||
- Fix crash for anchor links
|
- Fix crash for anchor links
|
||||||
|
|
||||||
|
|||||||
+21
-2
@@ -71,11 +71,18 @@ Server.prototype.start = function(dir, port) {
|
|||||||
|
|
||||||
// Redirect to directory's index.html
|
// Redirect to directory's index.html
|
||||||
function redirect() {
|
function redirect() {
|
||||||
|
var resultURL = urlTransform(req.url, function(parsed) {
|
||||||
|
parsed.pathname += '/';
|
||||||
|
return parsed;
|
||||||
|
});
|
||||||
|
|
||||||
res.statusCode = 301;
|
res.statusCode = 301;
|
||||||
res.setHeader('Location', req.url + '/');
|
res.setHeader('Location', resultURL);
|
||||||
res.end('Redirecting to ' + req.url + '/');
|
res.end('Redirecting to ' + resultURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.setHeader('X-Current-Location', req.url);
|
||||||
|
|
||||||
// Send file
|
// Send file
|
||||||
send(req, url.parse(req.url).pathname, {
|
send(req, url.parse(req.url).pathname, {
|
||||||
root: dir
|
root: dir
|
||||||
@@ -106,4 +113,16 @@ Server.prototype.start = function(dir, port) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
urlTransform is a helper function that allows a function to transform
|
||||||
|
a url string in it's parsed form and returns the new url as a string
|
||||||
|
|
||||||
|
@param {String} uri
|
||||||
|
@param {Function} fn
|
||||||
|
@return {String}
|
||||||
|
*/
|
||||||
|
function urlTransform(uri, fn) {
|
||||||
|
return url.format(fn(url.parse(uri)));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Server;
|
module.exports = Server;
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ describe('configSchema', function() {
|
|||||||
expect(result.errors.length).toBe(0);
|
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() {
|
it('should not accept filepath', function() {
|
||||||
var result = validate({
|
var result = validate({
|
||||||
structure: {
|
structure: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var FILENAME_REGEX = '^[a-z-._\d,\s]+$';
|
var FILENAME_REGEX = '^[a-zA-Z-._\d,\s]+$';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'$schema': 'http://json-schema.org/schema#',
|
'$schema': 'http://json-schema.org/schema#',
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
All GitBook themes plugins name start with this prefix once shorted.
|
||||||
|
*/
|
||||||
|
module.exports = 'theme-';
|
||||||
@@ -42,21 +42,39 @@ describe('PluginDependency', function() {
|
|||||||
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('listToArray', function() {
|
describe('listToArray', function() {
|
||||||
var list = PluginDependency.listToArray(Immutable.List([
|
it('must create an array from a list of plugin dependencies', function() {
|
||||||
PluginDependency.createFromString('hello@1.0.0'),
|
var list = PluginDependency.listToArray(Immutable.List([
|
||||||
PluginDependency.createFromString('noversion'),
|
PluginDependency.createFromString('hello@1.0.0'),
|
||||||
PluginDependency.createFromString('-disabled')
|
PluginDependency.createFromString('noversion'),
|
||||||
]));
|
PluginDependency.createFromString('-disabled')
|
||||||
|
]));
|
||||||
|
|
||||||
expect(list).toEqual([
|
expect(list).toEqual([
|
||||||
'hello@1.0.0',
|
'hello@1.0.0',
|
||||||
'noversion',
|
'noversion',
|
||||||
'-disabled'
|
'-disabled'
|
||||||
]);
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('listFromArray', function() {
|
||||||
|
it('must create an array from a list of plugin dependencies', function() {
|
||||||
|
var arr = Immutable.fromJS([
|
||||||
|
'hello@1.0.0',
|
||||||
|
{
|
||||||
|
'name': 'plugin-ga',
|
||||||
|
'version': 'git+ssh://samy@github.com/GitbookIO/plugin-ga.git'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
var list = PluginDependency.listFromArray(arr);
|
||||||
|
|
||||||
|
expect(list.first().getName()).toBe('hello');
|
||||||
|
expect(list.first().getVersion()).toBe('1.0.0');
|
||||||
|
expect(list.last().getName()).toBe('plugin-ga');
|
||||||
|
expect(list.last().getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+27
-6
@@ -293,26 +293,47 @@ Book.prototype.getDefaultExt = function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Infer the default path for a Readme.
|
Infer the default path for a Readme.
|
||||||
|
@param {Boolean} [absolute=false] False for a path relative to
|
||||||
|
this book's content root
|
||||||
@return {String}
|
@return {String}
|
||||||
*/
|
*/
|
||||||
Book.prototype.getDefaultReadmePath = function() {
|
Book.prototype.getDefaultReadmePath = function(absolute) {
|
||||||
return this.getContentRoot()+'README'+this.getDefaultExt();
|
var defaultPath = 'README'+this.getDefaultExt();
|
||||||
|
if (absolute) {
|
||||||
|
return path.join(this.getContentRoot(), defaultPath);
|
||||||
|
} else {
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Infer the default path for a Summary.
|
Infer the default path for a Summary.
|
||||||
|
@param {Boolean} [absolute=false] False for a path relative to
|
||||||
|
this book's content root
|
||||||
@return {String}
|
@return {String}
|
||||||
*/
|
*/
|
||||||
Book.prototype.getDefaultSummaryPath = function() {
|
Book.prototype.getDefaultSummaryPath = function(absolute) {
|
||||||
return this.getContentRoot()+'SUMMARY'+this.getDefaultExt();
|
var defaultPath = 'SUMMARY'+this.getDefaultExt();
|
||||||
|
if (absolute) {
|
||||||
|
return path.join(this.getContentRoot(), defaultPath);
|
||||||
|
} else {
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Infer the default path for a Glossary.
|
Infer the default path for a Glossary.
|
||||||
|
@param {Boolean} [absolute=false] False for a path relative to
|
||||||
|
this book's content root
|
||||||
@return {String}
|
@return {String}
|
||||||
*/
|
*/
|
||||||
Book.prototype.getDefaultGlossaryPath = function() {
|
Book.prototype.getDefaultGlossaryPath = function(absolute) {
|
||||||
return this.getContentRoot()+'GLOSSARY'+this.getDefaultExt();
|
var defaultPath = 'GLOSSARY'+this.getDefaultExt();
|
||||||
|
if (absolute) {
|
||||||
|
return path.join(this.getContentRoot(), defaultPath);
|
||||||
|
} else {
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+53
-56
@@ -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) {
|
Config.prototype.setFile = function(file) {
|
||||||
return this.set('file', file);
|
return this.set('file', file);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a configuration value by its key path
|
* Return a configuration value by its key path
|
||||||
|
* @param {String} key
|
||||||
@param {String} key
|
* @return {Mixed}
|
||||||
@return {Mixed}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.getValue = function(keyPath, def) {
|
Config.prototype.getValue = function(keyPath, def) {
|
||||||
var values = this.getValues();
|
var values = this.getValues();
|
||||||
keyPath = Config.keyToKeyPath(keyPath);
|
keyPath = Config.keyToKeyPath(keyPath);
|
||||||
@@ -46,12 +52,11 @@ Config.prototype.getValue = function(keyPath, def) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update a configuration value
|
* Update a configuration value
|
||||||
|
* @param {String} key
|
||||||
@param {String} key
|
* @param {Mixed} value
|
||||||
@param {Mixed} value
|
* @return {Config}
|
||||||
@return {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.setValue = function(keyPath, value) {
|
Config.prototype.setValue = function(keyPath, value) {
|
||||||
keyPath = Config.keyToKeyPath(keyPath);
|
keyPath = Config.keyToKeyPath(keyPath);
|
||||||
|
|
||||||
@@ -64,10 +69,9 @@ Config.prototype.setValue = function(keyPath, value) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a list of plugin dependencies
|
* Return a list of plugin dependencies
|
||||||
|
* @return {List<PluginDependency>}
|
||||||
@return {List<PluginDependency>}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.getPluginDependencies = function() {
|
Config.prototype.getPluginDependencies = function() {
|
||||||
var plugins = this.getValue('plugins');
|
var plugins = this.getValue('plugins');
|
||||||
|
|
||||||
@@ -79,11 +83,10 @@ Config.prototype.getPluginDependencies = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a plugin dependency by its name
|
* Return a plugin dependency by its name
|
||||||
|
* @param {String} name
|
||||||
@param {String} name
|
* @return {PluginDependency}
|
||||||
@return {PluginDependency}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.getPluginDependency = function(name) {
|
Config.prototype.getPluginDependency = function(name) {
|
||||||
var plugins = this.getPluginDependencies();
|
var plugins = this.getPluginDependencies();
|
||||||
|
|
||||||
@@ -93,11 +96,10 @@ Config.prototype.getPluginDependency = function(name) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update the list of plugins dependencies
|
* Update the list of plugins dependencies
|
||||||
|
* @param {List<PluginDependency>}
|
||||||
@param {List<PluginDependency>}
|
* @return {Config}
|
||||||
@return {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.setPluginDependencies = function(deps) {
|
Config.prototype.setPluginDependencies = function(deps) {
|
||||||
var plugins = PluginDependency.listToArray(deps);
|
var plugins = PluginDependency.listToArray(deps);
|
||||||
|
|
||||||
@@ -106,11 +108,10 @@ Config.prototype.setPluginDependencies = function(deps) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update values for an existing configuration
|
* Update values for an existing configuration
|
||||||
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.updateValues = function(values) {
|
Config.prototype.updateValues = function(values) {
|
||||||
values = Immutable.fromJS(values);
|
values = Immutable.fromJS(values);
|
||||||
|
|
||||||
@@ -118,12 +119,11 @@ Config.prototype.updateValues = function(values) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update values for an existing configuration
|
* Update values for an existing configuration
|
||||||
|
* @param {Config} config
|
||||||
@param {Config} config
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.mergeValues = function(values) {
|
Config.prototype.mergeValues = function(values) {
|
||||||
var currentValues = this.getValues();
|
var currentValues = this.getValues();
|
||||||
values = Immutable.fromJS(values);
|
values = Immutable.fromJS(values);
|
||||||
@@ -134,12 +134,11 @@ Config.prototype.mergeValues = function(values) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new config for a file
|
* Create a new config for a file
|
||||||
|
* @param {File} file
|
||||||
@param {File} file
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.create = function(file, values) {
|
Config.create = function(file, values) {
|
||||||
return new Config({
|
return new Config({
|
||||||
file: file,
|
file: file,
|
||||||
@@ -148,11 +147,10 @@ Config.create = function(file, values) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new config
|
* Create a new config
|
||||||
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.createWithValues = function(values) {
|
Config.createWithValues = function(values) {
|
||||||
return new Config({
|
return new Config({
|
||||||
values: Immutable.fromJS(values)
|
values: Immutable.fromJS(values)
|
||||||
@@ -161,11 +159,10 @@ Config.createWithValues = function(values) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert a keyPath to an array of keys
|
* Convert a keyPath to an array of keys
|
||||||
|
* @param {String|Array}
|
||||||
@param {String|Array}
|
* @return {Array}
|
||||||
@return {Array}
|
*/
|
||||||
*/
|
|
||||||
Config.keyToKeyPath = function(keyPath) {
|
Config.keyToKeyPath = function(keyPath) {
|
||||||
if (is.string(keyPath)) keyPath = keyPath.split('.');
|
if (is.string(keyPath)) keyPath = keyPath.split('.');
|
||||||
return keyPath;
|
return keyPath;
|
||||||
|
|||||||
+4
-11
@@ -1,9 +1,10 @@
|
|||||||
var Immutable = require('immutable');
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
var TemplateBlock = require('./templateBlock');
|
var TemplateBlock = require('./templateBlock');
|
||||||
var PREFIX = require('../constants/pluginPrefix');
|
|
||||||
var DEFAULT_VERSION = '*';
|
var DEFAULT_VERSION = '*';
|
||||||
|
|
||||||
|
var PluginDependency = require('./pluginDependency');
|
||||||
|
|
||||||
var Plugin = Immutable.Record({
|
var Plugin = Immutable.Record({
|
||||||
name: String(),
|
name: String(),
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ Plugin.prototype.getDepth = function() {
|
|||||||
@return {String}
|
@return {String}
|
||||||
*/
|
*/
|
||||||
Plugin.prototype.getNpmID = function() {
|
Plugin.prototype.getNpmID = function() {
|
||||||
return Plugin.nameToNpmID(this.getName());
|
return PluginDependency.nameToNpmID(this.getName());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,14 +153,6 @@ Plugin.createFromDep = function(dep) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
Plugin.nameToNpmID = PluginDependency.nameToNpmID;
|
||||||
Return NPM id for a plugin name
|
|
||||||
|
|
||||||
@param {String}
|
|
||||||
@return {String}
|
|
||||||
*/
|
|
||||||
Plugin.nameToNpmID = function(s) {
|
|
||||||
return PREFIX + s;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Plugin;
|
module.exports = Plugin;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
var is = require('is');
|
var is = require('is');
|
||||||
|
var semver = require('semver');
|
||||||
var Immutable = require('immutable');
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
|
var PREFIX = require('../constants/pluginPrefix');
|
||||||
var DEFAULT_VERSION = '*';
|
var DEFAULT_VERSION = '*';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
PluginDependency represents the informations about a plugin
|
* PluginDependency represents the informations about a plugin
|
||||||
stored in config.plugins
|
* stored in config.plugins
|
||||||
*/
|
*/
|
||||||
var PluginDependency = Immutable.Record({
|
var PluginDependency = Immutable.Record({
|
||||||
name: String(),
|
name: String(),
|
||||||
|
|
||||||
@@ -30,11 +32,39 @@ PluginDependency.prototype.isEnabled = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a plugin with a name and a plugin
|
* Toggle this plugin state
|
||||||
|
* @param {Boolean}
|
||||||
|
* @return {PluginDependency}
|
||||||
|
*/
|
||||||
|
PluginDependency.prototype.toggle = function(state) {
|
||||||
|
if (is.undef(state)) {
|
||||||
|
state = !this.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
@param {String}
|
return this.set('enabled', state);
|
||||||
@return {Plugin|undefined}
|
};
|
||||||
*/
|
|
||||||
|
/**
|
||||||
|
* Return NPM ID for the dependency
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
PluginDependency.prototype.getNpmID = function() {
|
||||||
|
return PluginDependency.nameToNpmID(this.getName());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the plugin using a git dependency
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
PluginDependency.prototype.isGitDependency = function() {
|
||||||
|
return !semver.validRange(this.getVersion());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a plugin with a name and a plugin
|
||||||
|
* @param {String}
|
||||||
|
* @return {Plugin|undefined}
|
||||||
|
*/
|
||||||
PluginDependency.create = function(name, version, enabled) {
|
PluginDependency.create = function(name, version, enabled) {
|
||||||
if (is.undefined(enabled)) {
|
if (is.undefined(enabled)) {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
@@ -48,11 +78,10 @@ PluginDependency.create = function(name, version, enabled) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a plugin from a string
|
* Create a plugin from a string
|
||||||
|
* @param {String}
|
||||||
@param {String}
|
* @return {Plugin|undefined}
|
||||||
@return {Plugin|undefined}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.createFromString = function(s) {
|
PluginDependency.createFromString = function(s) {
|
||||||
var parts = s.split('@');
|
var parts = s.split('@');
|
||||||
var name = parts[0];
|
var name = parts[0];
|
||||||
@@ -72,22 +101,20 @@ PluginDependency.createFromString = function(s) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a PluginDependency from a string
|
* Create a PluginDependency from a string
|
||||||
|
* @param {String}
|
||||||
@param {String}
|
* @return {List<PluginDependency>}
|
||||||
@return {List<PluginDependency>}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.listFromString = function(s) {
|
PluginDependency.listFromString = function(s) {
|
||||||
var parts = s.split(',');
|
var parts = s.split(',');
|
||||||
return PluginDependency.listFromArray(parts);
|
return PluginDependency.listFromArray(parts);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a PluginDependency from an array
|
* Create a PluginDependency from an array
|
||||||
|
* @param {Array}
|
||||||
@param {Array}
|
* @return {List<PluginDependency>}
|
||||||
@return {List<PluginDependency>}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.listFromArray = function(arr) {
|
PluginDependency.listFromArray = function(arr) {
|
||||||
return Immutable.List(arr)
|
return Immutable.List(arr)
|
||||||
.map(function(entry) {
|
.map(function(entry) {
|
||||||
@@ -95,8 +122,8 @@ PluginDependency.listFromArray = function(arr) {
|
|||||||
return PluginDependency.createFromString(entry);
|
return PluginDependency.createFromString(entry);
|
||||||
} else {
|
} else {
|
||||||
return PluginDependency({
|
return PluginDependency({
|
||||||
name: entry.name,
|
name: entry.get('name'),
|
||||||
version: entry.version
|
version: entry.get('version')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -106,11 +133,10 @@ PluginDependency.listFromArray = function(arr) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Export plugin dependencies as an array
|
* Export plugin dependencies as an array
|
||||||
|
* @param {List<PluginDependency>} list
|
||||||
@param {List<PluginDependency>} list
|
* @return {Array<String>}
|
||||||
@return {Array<String>}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.listToArray = function(list) {
|
PluginDependency.listToArray = function(list) {
|
||||||
return list
|
return list
|
||||||
.map(function(dep) {
|
.map(function(dep) {
|
||||||
@@ -130,4 +156,13 @@ PluginDependency.listToArray = function(list) {
|
|||||||
.toJS();
|
.toJS();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return NPM id for a plugin name
|
||||||
|
* @param {String}
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
PluginDependency.nameToNpmID = function(s) {
|
||||||
|
return PREFIX + s;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = PluginDependency;
|
module.exports = PluginDependency;
|
||||||
|
|||||||
+32
-39
@@ -43,10 +43,9 @@ TemplateBlock.prototype.getBlocks = function() {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return shortcuts associated with this block or undefined
|
* Return shortcuts associated with this block or undefined
|
||||||
|
* @return {TemplateShortcut|undefined}
|
||||||
@return {TemplateShortcut|undefined}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.getShortcuts = function() {
|
TemplateBlock.prototype.getShortcuts = function() {
|
||||||
var shortcuts = this.get('shortcuts');
|
var shortcuts = this.get('shortcuts');
|
||||||
if (shortcuts.size === 0) {
|
if (shortcuts.size === 0) {
|
||||||
@@ -57,19 +56,17 @@ TemplateBlock.prototype.getShortcuts = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return name for the nunjucks extension
|
* Return name for the nunjucks extension
|
||||||
|
* @return {String}
|
||||||
@return {String}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.getExtensionName = function() {
|
TemplateBlock.prototype.getExtensionName = function() {
|
||||||
return 'Block' + this.getName() + 'Extension';
|
return 'Block' + this.getName() + 'Extension';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a nunjucks extension to represents this block
|
* Return a nunjucks extension to represents this block
|
||||||
|
* @return {Nunjucks.Extension}
|
||||||
@return {Nunjucks.Extension}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
||||||
blocksOutput = blocksOutput || {};
|
blocksOutput = blocksOutput || {};
|
||||||
|
|
||||||
@@ -192,11 +189,11 @@ TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Apply a block to a content
|
* Apply a block to a content
|
||||||
@param {Object} inner
|
* @param {Object} inner
|
||||||
@param {Object} context
|
* @param {Object} context
|
||||||
@return {Promise<String>|String}
|
* @return {Promise<String>|String}
|
||||||
*/
|
*/
|
||||||
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
||||||
var processFn = this.getProcess();
|
var processFn = this.getProcess();
|
||||||
|
|
||||||
@@ -215,11 +212,10 @@ TemplateBlock.prototype.applyBlock = function(inner, context) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Normalize result from a block process function
|
* Normalize result from a block process function
|
||||||
|
* @param {Object|String} result
|
||||||
@param {Object|String} result
|
* @return {Object}
|
||||||
@return {Object}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
||||||
if (is.string(result)) {
|
if (is.string(result)) {
|
||||||
result = { body: result };
|
result = { body: result };
|
||||||
@@ -230,12 +226,11 @@ TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert a block result to HTML
|
* Convert a block result to HTML
|
||||||
|
* @param {Object} result
|
||||||
@param {Object} result
|
* @param {Object} blocksOutput: stored post processing blocks in this object
|
||||||
@param {Object} blocksOutput: stored post processing blocks in this object
|
* @return {String}
|
||||||
@return {String}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
||||||
var indexedKey;
|
var indexedKey;
|
||||||
var toIndex = (!result.parse) || (result.post !== undefined);
|
var toIndex = (!result.parse) || (result.post !== undefined);
|
||||||
@@ -256,12 +251,11 @@ TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a template block from a function or an object
|
* Create a template block from a function or an object
|
||||||
|
* @param {String} blockName
|
||||||
@param {String} blockName
|
* @param {Object} block
|
||||||
@param {Object} block
|
* @return {TemplateBlock}
|
||||||
@return {TemplateBlock}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.create = function(blockName, block) {
|
TemplateBlock.create = function(blockName, block) {
|
||||||
if (is.fn(block)) {
|
if (is.fn(block)) {
|
||||||
block = new Immutable.Map({
|
block = new Immutable.Map({
|
||||||
@@ -275,11 +269,10 @@ TemplateBlock.create = function(blockName, block) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Extract kwargs from an arguments array
|
* Extract kwargs from an arguments array
|
||||||
|
* @param {Array} args
|
||||||
@param {Array} args
|
* @return {Object}
|
||||||
@return {Object}
|
*/
|
||||||
*/
|
|
||||||
function extractKwargs(args) {
|
function extractKwargs(args) {
|
||||||
var last = args[args.length - 1];
|
var last = args[args.length - 1];
|
||||||
return (is.object(last) && last.__keywords)? args.pop() : {};
|
return (is.object(last) && last.__keywords)? args.pop() : {};
|
||||||
|
|||||||
@@ -17,23 +17,21 @@ TemplateOutput.prototype.getBlocks = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update content of this output
|
* Update content of this output
|
||||||
|
* @param {String} content
|
||||||
@param {String} content
|
* @return {TemplateContent}
|
||||||
@return {TemplateContent}
|
*/
|
||||||
*/
|
|
||||||
TemplateOutput.prototype.setContent = function(content) {
|
TemplateOutput.prototype.setContent = function(content) {
|
||||||
return this.set('content', content);
|
return this.set('content', content);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a TemplateOutput from a text content
|
* Create a TemplateOutput from a text content
|
||||||
and an object containing block definition
|
* and an object containing block definition
|
||||||
|
* @param {String} content
|
||||||
@param {String} content
|
* @param {Object} blocks
|
||||||
@param {Object} blocks
|
* @return {TemplateOutput}
|
||||||
@return {TemplateOutput}
|
*/
|
||||||
*/
|
|
||||||
TemplateOutput.create = function(content, blocks) {
|
TemplateOutput.create = function(content, blocks) {
|
||||||
return new TemplateOutput({
|
return new TemplateOutput({
|
||||||
content: content,
|
content: content,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
var PluginDependency = require('../../models/pluginDependency');
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
/**
|
|
||||||
Add a plugin to a book's configuration
|
|
||||||
|
|
||||||
@param {Book} book
|
/**
|
||||||
@param {String} plugin
|
* Add a plugin to a book's configuration
|
||||||
@param {String} version (optional)
|
* @param {Book} book
|
||||||
@return {Book}
|
* @param {String} plugin
|
||||||
*/
|
* @param {String} version (optional)
|
||||||
|
* @return {Book}
|
||||||
|
*/
|
||||||
function addPlugin(book, plugin, version) {
|
function addPlugin(book, plugin, version) {
|
||||||
var config = book.getConfig();
|
var config = book.getConfig();
|
||||||
var deps = config.getPluginDependencies();
|
var deps = config.getPluginDependencies();
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
addPlugin: require('./addPlugin'),
|
addPlugin: require('./addPlugin'),
|
||||||
removePlugin: require('./removePlugin')
|
removePlugin: require('./removePlugin'),
|
||||||
|
togglePlugin: require('./togglePlugin'),
|
||||||
|
editPlugin: require('./editPlugin')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Remove a plugin from a book's configuration
|
* Remove a plugin from a book's configuration
|
||||||
|
* @param {Book} book
|
||||||
@param {Book} book
|
* @param {String} plugin
|
||||||
@param {String} plugin
|
* @return {Book}
|
||||||
@return {Book}
|
*/
|
||||||
*/
|
|
||||||
function removePlugin(book, pluginName) {
|
function removePlugin(book, pluginName) {
|
||||||
var config = book.getConfig();
|
var config = book.getConfig();
|
||||||
var deps = config.getPluginDependencies();
|
var deps = config.getPluginDependencies();
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
var fs = require('fs');
|
||||||
var generateMock = require('../generateMock');
|
var generateMock = require('../generateMock');
|
||||||
var WebsiteGenerator = require('../website');
|
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() {
|
it('should copy asset files', function() {
|
||||||
return generateMock(WebsiteGenerator, {
|
return generateMock(WebsiteGenerator, {
|
||||||
'README.md': 'Hello World',
|
'README.md': 'Hello World',
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ var generateBook = require('./generateBook');
|
|||||||
function generateMock(Generator, files) {
|
function generateMock(Generator, files) {
|
||||||
var fs = createMockFS(files);
|
var fs = createMockFS(files);
|
||||||
var book = Book.createForFS(fs);
|
var book = Book.createForFS(fs);
|
||||||
var dir = tmp.dirSync();
|
var dir;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dir = tmp.dirSync();
|
||||||
|
} catch(err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
book = book.setLogLevel('disabled');
|
book = book.setLogLevel('disabled');
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ var Api = require('../api');
|
|||||||
var Plugins = require('../plugins');
|
var Plugins = require('../plugins');
|
||||||
var Promise = require('../utils/promise');
|
var Promise = require('../utils/promise');
|
||||||
var defaultBlocks = require('../constants/defaultBlocks');
|
var defaultBlocks = require('../constants/defaultBlocks');
|
||||||
|
var fileToOutput = require('./helper/fileToOutput');
|
||||||
|
|
||||||
var CODEBLOCK = 'code';
|
var CODEBLOCK = 'code';
|
||||||
|
|
||||||
@@ -17,9 +18,13 @@ function getModifiers(output, page) {
|
|||||||
var book = output.getBook();
|
var book = output.getBook();
|
||||||
var plugins = output.getPlugins();
|
var plugins = output.getPlugins();
|
||||||
var glossary = book.getGlossary();
|
var glossary = book.getGlossary();
|
||||||
var entries = glossary.getEntries();
|
|
||||||
var file = page.getFile();
|
var file = page.getFile();
|
||||||
|
|
||||||
|
// Glossary entries
|
||||||
|
var entries = glossary.getEntries();
|
||||||
|
var glossaryFile = glossary.getFile();
|
||||||
|
var glossaryFilename = fileToOutput(output, glossaryFile.getPath());
|
||||||
|
|
||||||
// Current file path
|
// Current file path
|
||||||
var currentFilePath = file.getPath();
|
var currentFilePath = file.getPath();
|
||||||
|
|
||||||
@@ -34,18 +39,18 @@ function getModifiers(output, page) {
|
|||||||
// Normalize IDs on headings
|
// Normalize IDs on headings
|
||||||
Modifiers.addHeadingId,
|
Modifiers.addHeadingId,
|
||||||
|
|
||||||
|
// Annotate text with glossary entries
|
||||||
|
Modifiers.annotateText.bind(null, entries, glossaryFilename),
|
||||||
|
|
||||||
|
// Resolve images
|
||||||
|
Modifiers.resolveImages.bind(null, currentFilePath),
|
||||||
|
|
||||||
// Resolve links (.md -> .html)
|
// Resolve links (.md -> .html)
|
||||||
Modifiers.resolveLinks.bind(null,
|
Modifiers.resolveLinks.bind(null,
|
||||||
currentFilePath,
|
currentFilePath,
|
||||||
resolveFileToURL.bind(null, output)
|
resolveFileToURL.bind(null, output)
|
||||||
),
|
),
|
||||||
|
|
||||||
// Resolve images
|
|
||||||
Modifiers.resolveImages.bind(null, currentFilePath),
|
|
||||||
|
|
||||||
// Annotate text with glossary entries
|
|
||||||
Modifiers.annotateText.bind(null, entries),
|
|
||||||
|
|
||||||
// Highlight code blocks using "code" block
|
// Highlight code blocks using "code" block
|
||||||
Modifiers.highlightCode.bind(null, function(lang, source) {
|
Modifiers.highlightCode.bind(null, function(lang, source) {
|
||||||
return Promise(code.applyBlock({
|
return Promise(code.applyBlock({
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('annotateText', function() {
|
|||||||
it('should annotate text', function() {
|
it('should annotate text', function() {
|
||||||
var $ = cheerio.load('<p>This is a word, and multiple words</p>');
|
var $ = cheerio.load('<p>This is a word, and multiple words</p>');
|
||||||
|
|
||||||
annotateText(entries, $);
|
annotateText(entries, 'GLOSSARY.md', $);
|
||||||
|
|
||||||
var links = $('a');
|
var links = $('a');
|
||||||
expect(links.length).toBe(2);
|
expect(links.length).toBe(2);
|
||||||
@@ -31,14 +31,14 @@ describe('annotateText', function() {
|
|||||||
it('should not annotate scripts', function() {
|
it('should not annotate scripts', function() {
|
||||||
var $ = cheerio.load('<script>This is a word, and multiple words</script>');
|
var $ = cheerio.load('<script>This is a word, and multiple words</script>');
|
||||||
|
|
||||||
annotateText(entries, $);
|
annotateText(entries, 'GLOSSARY.md', $);
|
||||||
expect($('a').length).toBe(0);
|
expect($('a').length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not annotate when has class "no-glossary"', function() {
|
it('should not annotate when has class "no-glossary"', function() {
|
||||||
var $ = cheerio.load('<p class="no-glossary">This is a word, and multiple words</p>');
|
var $ = cheerio.load('<p class="no-glossary">This is a word, and multiple words</p>');
|
||||||
|
|
||||||
annotateText(entries, $);
|
annotateText(entries, 'GLOSSARY.md', $);
|
||||||
expect($('a').length).toBe(0);
|
expect($('a').length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,9 +62,10 @@ function replaceText($, el, search, replace, text_only ) {
|
|||||||
Annotate text using a list of GlossaryEntry
|
Annotate text using a list of GlossaryEntry
|
||||||
|
|
||||||
@param {List<GlossaryEntry>}
|
@param {List<GlossaryEntry>}
|
||||||
|
@param {String} glossaryFilePath
|
||||||
@param {HTMLDom} $
|
@param {HTMLDom} $
|
||||||
*/
|
*/
|
||||||
function annotateText(entries, $) {
|
function annotateText(entries, glossaryFilePath, $) {
|
||||||
entries.forEach(function(entry) {
|
entries.forEach(function(entry) {
|
||||||
var entryId = entry.getID();
|
var entryId = entry.getID();
|
||||||
var name = entry.getName();
|
var name = entry.getName();
|
||||||
@@ -81,7 +82,7 @@ function annotateText(entries, $) {
|
|||||||
) return;
|
) return;
|
||||||
|
|
||||||
replaceText($, this, searchRegex, function(match) {
|
replaceText($, this, searchRegex, function(match) {
|
||||||
return '<a href="/GLOSSARY.md#' + entryId + '" '
|
return '<a href="/' + glossaryFilePath + '#' + entryId + '" '
|
||||||
+ 'class="glossary-term" title="' + escape(description) + '">'
|
+ 'class="glossary-term" title="' + escape(description) + '">'
|
||||||
+ match
|
+ match
|
||||||
+ '</a>';
|
+ '</a>';
|
||||||
|
|||||||
+41
-10
@@ -5,6 +5,25 @@ var Page = require('../models/page');
|
|||||||
var walkSummary = require('./walkSummary');
|
var walkSummary = require('./walkSummary');
|
||||||
var parsePage = require('./parsePage');
|
var parsePage = require('./parsePage');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Parse a page from a path
|
||||||
|
|
||||||
|
@param {Book} book
|
||||||
|
@param {String} filePath
|
||||||
|
@return {Page}
|
||||||
|
*/
|
||||||
|
function parseFilePage(book, filePath) {
|
||||||
|
var fs = book.getContentFS();
|
||||||
|
|
||||||
|
return fs.statFile(filePath)
|
||||||
|
.then(function(file) {
|
||||||
|
var page = Page.createForFile(file);
|
||||||
|
return parsePage(book, page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parse all pages from a book as an OrderedMap
|
Parse all pages from a book as an OrderedMap
|
||||||
|
|
||||||
@@ -12,10 +31,11 @@ var parsePage = require('./parsePage');
|
|||||||
@return {Promise<OrderedMap<Page>>}
|
@return {Promise<OrderedMap<Page>>}
|
||||||
*/
|
*/
|
||||||
function parsePagesList(book) {
|
function parsePagesList(book) {
|
||||||
var fs = book.getContentFS();
|
|
||||||
var summary = book.getSummary();
|
var summary = book.getSummary();
|
||||||
|
var glossary = book.getGlossary();
|
||||||
var map = Immutable.OrderedMap();
|
var map = Immutable.OrderedMap();
|
||||||
|
|
||||||
|
// Parse pages from summary
|
||||||
return timing.measure(
|
return timing.measure(
|
||||||
'parse.listPages',
|
'parse.listPages',
|
||||||
walkSummary(summary, function(article) {
|
walkSummary(summary, function(article) {
|
||||||
@@ -26,21 +46,32 @@ function parsePagesList(book) {
|
|||||||
// Is the page ignored?
|
// Is the page ignored?
|
||||||
if (book.isContentFileIgnored(filepath)) return;
|
if (book.isContentFileIgnored(filepath)) return;
|
||||||
|
|
||||||
return fs.statFile(filepath)
|
return parseFilePage(book, filepath)
|
||||||
.then(function(file) {
|
|
||||||
var page = Page.createForFile(file);
|
|
||||||
return parsePage(book, page);
|
|
||||||
})
|
|
||||||
.then(function(page) {
|
.then(function(page) {
|
||||||
map = map.set(filepath, page);
|
map = map.set(filepath, page);
|
||||||
}, function() {
|
}, function() {
|
||||||
// file doesn't exist
|
// file doesn't exist
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(function() {
|
)
|
||||||
return map;
|
|
||||||
})
|
// Parse glossary
|
||||||
);
|
.then(function() {
|
||||||
|
var file = glossary.getFile();
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFilePage(book, file.getPath())
|
||||||
|
.then(function(page) {
|
||||||
|
map = map.set(file.getPath(), page);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
.then(function() {
|
||||||
|
return map;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
var parseStructureFile = require('./parseStructureFile');
|
var parseStructureFile = require('./parseStructureFile');
|
||||||
var Summary = require('../models/summary');
|
var Summary = require('../models/summary');
|
||||||
var SummaryModifier = require('../modifiers').Summary;
|
var SummaryModifier = require('../modifiers').Summary;
|
||||||
var location = require('../utils/location');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parse summary in a book, the summary can only be parsed
|
Parse summary in a book, the summary can only be parsed
|
||||||
@@ -27,11 +26,10 @@ function parseSummary(book) {
|
|||||||
summary = Summary.createFromParts(file, result.parts);
|
summary = Summary.createFromParts(file, result.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert readme as first entry
|
// Insert readme as first entry if not in SUMMARY.md
|
||||||
var firstArticle = summary.getFirstArticle();
|
var readmeArticle = summary.getByPath(readmeFile.getPath());
|
||||||
|
|
||||||
if (readmeFile.exists() &&
|
if (readmeFile.exists() && !readmeArticle) {
|
||||||
(!firstArticle || !location.areIdenticalPaths(firstArticle.getRef(), readmeFile.getPath()))) {
|
|
||||||
summary = SummaryModifier.unshiftArticle(summary, {
|
summary = SummaryModifier.unshiftArticle(summary, {
|
||||||
title: 'Introduction',
|
title: 'Introduction',
|
||||||
ref: readmeFile.getPath()
|
ref: readmeFile.getPath()
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var Book = require('../../models/book');
|
||||||
|
var NodeFS = require('../../fs/node');
|
||||||
|
var installPlugin = require('../installPlugin');
|
||||||
|
|
||||||
|
var Parse = require('../../parse');
|
||||||
|
|
||||||
|
describe('installPlugin', function() {
|
||||||
|
var book;
|
||||||
|
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
var fs = NodeFS(path.resolve(__dirname, '../../../'));
|
||||||
|
var baseBook = Book.createForFS(fs);
|
||||||
|
|
||||||
|
return Parse.parseConfig(baseBook)
|
||||||
|
.then(function(_book) {
|
||||||
|
book = _book;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must install a plugin from NPM', function() {
|
||||||
|
var dep = PluginDependency.createFromString('ga');
|
||||||
|
return installPlugin(book, dep);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var Book = require('../../models/book');
|
||||||
|
var NodeFS = require('../../fs/node');
|
||||||
|
var installPlugins = require('../installPlugins');
|
||||||
|
|
||||||
|
var Parse = require('../../parse');
|
||||||
|
|
||||||
|
describe('installPlugins', function() {
|
||||||
|
var book;
|
||||||
|
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
var fs = NodeFS(path.resolve(__dirname, '../../../'));
|
||||||
|
var baseBook = Book.createForFS(fs);
|
||||||
|
|
||||||
|
return Parse.parseConfig(baseBook)
|
||||||
|
.then(function(_book) {
|
||||||
|
book = _book;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must install all plugins from NPM', function() {
|
||||||
|
return installPlugins(book)
|
||||||
|
.then(function(n) {
|
||||||
|
expect(n).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var resolveVersion = require('../resolveVersion');
|
||||||
|
|
||||||
|
describe('resolveVersion', function() {
|
||||||
|
it('must skip resolving and return non-semver versions', function() {
|
||||||
|
var plugin = PluginDependency.createFromString('ga@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
|
|
||||||
|
return resolveVersion(plugin)
|
||||||
|
.then(function(version) {
|
||||||
|
expect(version).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must resolve a normal plugin dependency', function() {
|
||||||
|
var plugin = PluginDependency.createFromString('ga@>0.9.0 < 1.0.1');
|
||||||
|
|
||||||
|
return resolveVersion(plugin)
|
||||||
|
.then(function(version) {
|
||||||
|
expect(version).toBe('1.0.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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
@@ -1,134 +1,32 @@
|
|||||||
var npm = require('npm');
|
|
||||||
var npmi = require('npmi');
|
var npmi = require('npmi');
|
||||||
var semver = require('semver');
|
|
||||||
var Immutable = require('immutable');
|
|
||||||
|
|
||||||
var pkg = require('../../package.json');
|
var pkg = require('../../package.json');
|
||||||
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
||||||
var Promise = require('../utils/promise');
|
var Promise = require('../utils/promise');
|
||||||
var Plugin = require('../models/plugin');
|
var installPlugin = require('./installPlugin');
|
||||||
var gitbook = require('../gitbook');
|
|
||||||
var listForBook = require('./listForBook');
|
|
||||||
|
|
||||||
var npmIsReady;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Initialize and prepare NPM
|
|
||||||
|
|
||||||
@return {Promise}
|
|
||||||
*/
|
|
||||||
function initNPM() {
|
|
||||||
if (npmIsReady) return npmIsReady;
|
|
||||||
|
|
||||||
npmIsReady = Promise.nfcall(npm.load, {
|
|
||||||
silent: true,
|
|
||||||
loglevel: 'silent'
|
|
||||||
});
|
|
||||||
|
|
||||||
return npmIsReady;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Resolve a plugin to a version
|
|
||||||
|
|
||||||
@param {Plugin}
|
|
||||||
@return {Promise<String>}
|
|
||||||
*/
|
|
||||||
function resolveVersion(plugin) {
|
|
||||||
var npmId = Plugin.nameToNpmID(plugin.getName());
|
|
||||||
var requiredVersion = plugin.getVersion();
|
|
||||||
|
|
||||||
return initNPM()
|
|
||||||
.then(function() {
|
|
||||||
return Promise.nfcall(npm.commands.view, [npmId + '@' + requiredVersion, 'engines'], true);
|
|
||||||
})
|
|
||||||
.then(function(versions) {
|
|
||||||
versions = Immutable.Map(versions).entrySeq();
|
|
||||||
|
|
||||||
var result = versions
|
|
||||||
.map(function(entry) {
|
|
||||||
return {
|
|
||||||
version: entry[0],
|
|
||||||
gitbook: (entry[1].engines || {}).gitbook
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(function(v) {
|
|
||||||
return v.gitbook && gitbook.satisfies(v.gitbook);
|
|
||||||
})
|
|
||||||
.sort(function(v1, v2) {
|
|
||||||
return semver.lt(v1.version, v2.version)? 1 : -1;
|
|
||||||
})
|
|
||||||
.get(0);
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
return result.version;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Install a plugin for a book
|
|
||||||
|
|
||||||
@param {Book}
|
|
||||||
@param {Plugin}
|
|
||||||
@return {Promise}
|
|
||||||
*/
|
|
||||||
function installPlugin(book, plugin) {
|
|
||||||
var logger = book.getLogger();
|
|
||||||
|
|
||||||
var installFolder = book.getRoot();
|
|
||||||
var name = plugin.getName();
|
|
||||||
var requirement = plugin.getVersion();
|
|
||||||
|
|
||||||
logger.info.ln('');
|
|
||||||
logger.info.ln('installing plugin "' + name + '"');
|
|
||||||
|
|
||||||
// Find a version to install
|
|
||||||
return resolveVersion(plugin)
|
|
||||||
.then(function(version) {
|
|
||||||
if (!version) {
|
|
||||||
throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info.ln('install plugin "' + name +'" (' + requirement + ') from NPM with version', version);
|
|
||||||
return Promise.nfcall(npmi, {
|
|
||||||
'name': plugin.getNpmID(),
|
|
||||||
'version': version,
|
|
||||||
'path': installFolder,
|
|
||||||
'npmLoad': {
|
|
||||||
'loglevel': 'silent',
|
|
||||||
'loaded': true,
|
|
||||||
'prefix': installFolder
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
logger.info.ok('plugin "' + name + '" installed with success');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Install plugin requirements for a book
|
Install plugin requirements for a book
|
||||||
|
|
||||||
@param {Book}
|
@param {Book}
|
||||||
@return {Promise}
|
@return {Promise<Number>}
|
||||||
*/
|
*/
|
||||||
function installPlugins(book) {
|
function installPlugins(book) {
|
||||||
var logger = book.getLogger();
|
var logger = book.getLogger();
|
||||||
var plugins = listForBook(book);
|
var config = book.getConfig();
|
||||||
|
var plugins = config.getPluginDependencies();
|
||||||
|
|
||||||
// Remove default plugins
|
// Remove default plugins
|
||||||
// (only if version is same as installed)
|
// (only if version is same as installed)
|
||||||
plugins = plugins.filterNot(function(plugin) {
|
plugins = plugins.filterNot(function(plugin) {
|
||||||
return (
|
return (
|
||||||
DEFAULT_PLUGINS.includes(plugin.getName()) &&
|
// Disabled plugin
|
||||||
plugin.getVersion() === pkg.dependencies[plugin.getNpmID()]
|
!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 Promise.forEach(plugins, function(plugin) {
|
||||||
return installPlugin(book, plugin);
|
return installPlugin(book, plugin);
|
||||||
});
|
})
|
||||||
|
.thenResolve(plugins.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = installPlugins;
|
module.exports = installPlugins;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ var Promise = require('../utils/promise');
|
|||||||
var listForBook = require('./listForBook');
|
var listForBook = require('./listForBook');
|
||||||
var findForBook = require('./findForBook');
|
var findForBook = require('./findForBook');
|
||||||
var loadPlugin = require('./loadPlugin');
|
var loadPlugin = require('./loadPlugin');
|
||||||
|
var sortPlugins = require('./sortPlugins');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,11 +28,9 @@ function loadForBook(book) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort plugins to match list in book.json
|
return sortPlugins(installed, requirementsKeys);
|
||||||
installed = installed.sort(function(a, b){
|
})
|
||||||
return requirementsKeys.indexOf(a.getName()) < requirementsKeys.indexOf(b.getName()) ? -1 : 1;
|
.then(function(installed) {
|
||||||
});
|
|
||||||
|
|
||||||
// Log state
|
// Log state
|
||||||
logger.info.ln(installed.size + ' plugins are installed');
|
logger.info.ln(installed.size + ' plugins are installed');
|
||||||
if (requirements.size != installed.size) {
|
if (requirements.size != installed.size) {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
var npm = require('npm');
|
||||||
|
var semver = require('semver');
|
||||||
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
|
var Promise = require('../utils/promise');
|
||||||
|
var Plugin = require('../models/plugin');
|
||||||
|
var gitbook = require('../gitbook');
|
||||||
|
|
||||||
|
var npmIsReady;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initialize and prepare NPM
|
||||||
|
|
||||||
|
@return {Promise}
|
||||||
|
*/
|
||||||
|
function initNPM() {
|
||||||
|
if (npmIsReady) return npmIsReady;
|
||||||
|
|
||||||
|
npmIsReady = Promise.nfcall(npm.load, {
|
||||||
|
silent: true,
|
||||||
|
loglevel: 'silent'
|
||||||
|
});
|
||||||
|
|
||||||
|
return npmIsReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resolve a plugin dependency to a version
|
||||||
|
|
||||||
|
@param {PluginDependency} plugin
|
||||||
|
@return {Promise<String>}
|
||||||
|
*/
|
||||||
|
function resolveVersion(plugin) {
|
||||||
|
var npmId = Plugin.nameToNpmID(plugin.getName());
|
||||||
|
var requiredVersion = plugin.getVersion();
|
||||||
|
|
||||||
|
if (plugin.isGitDependency()) {
|
||||||
|
return Promise.resolve(requiredVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return initNPM()
|
||||||
|
.then(function() {
|
||||||
|
return Promise.nfcall(npm.commands.view, [npmId + '@' + requiredVersion, 'engines'], true);
|
||||||
|
})
|
||||||
|
.then(function(versions) {
|
||||||
|
versions = Immutable.Map(versions).entrySeq();
|
||||||
|
|
||||||
|
var result = versions
|
||||||
|
.map(function(entry) {
|
||||||
|
return {
|
||||||
|
version: entry[0],
|
||||||
|
gitbook: (entry[1].engines || {}).gitbook
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(function(v) {
|
||||||
|
return v.gitbook && gitbook.satisfies(v.gitbook);
|
||||||
|
})
|
||||||
|
.sort(function(v1, v2) {
|
||||||
|
return semver.lt(v1.version, v2.version)? 1 : -1;
|
||||||
|
})
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
return result.version;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolveVersion;
|
||||||
@@ -0,0 +1,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;
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -2,12 +2,12 @@ var Promise = require('../utils/promise');
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Replace position markers of blocks by body after processing
|
* Replace position markers of blocks by body after processing
|
||||||
This is done to avoid that markdown/asciidoc processer parse the block content
|
* This is done to avoid that markdown/asciidoc processer parse the block content
|
||||||
|
*
|
||||||
@param {String} content
|
* @param {String} content
|
||||||
@return {Object} {blocks: Set, content: String}
|
* @return {Object} {blocks: Set, content: String}
|
||||||
*/
|
*/
|
||||||
function replaceBlocks(content, blocks) {
|
function replaceBlocks(content, blocks) {
|
||||||
var newContent = content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
|
var newContent = content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
|
||||||
var replacedWith = match;
|
var replacedWith = match;
|
||||||
@@ -24,14 +24,14 @@ function replaceBlocks(content, blocks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Post render a template:
|
* Post render a template:
|
||||||
- Execute "post" for blocks
|
* - Execute "post" for blocks
|
||||||
- Replace block content
|
* - Replace block content
|
||||||
|
*
|
||||||
@param {TemplateEngine} engine
|
* @param {TemplateEngine} engine
|
||||||
@param {TemplateOutput} content
|
* @param {TemplateOutput} content
|
||||||
@return {Promise<String>}
|
* @return {Promise<String>}
|
||||||
*/
|
*/
|
||||||
function postRender(engine, output) {
|
function postRender(engine, output) {
|
||||||
var content = output.getContent();
|
var content = output.getContent();
|
||||||
var blocks = output.getBlocks();
|
var blocks = output.getBlocks();
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ var TemplateOutput = require('../models/templateOutput');
|
|||||||
var replaceShortcuts = require('./replaceShortcuts');
|
var replaceShortcuts = require('./replaceShortcuts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Render a template
|
* Render a template
|
||||||
|
*
|
||||||
@param {TemplateEngine} engine
|
* @param {TemplateEngine} engine
|
||||||
@param {String} filePath: absolute path for the loader
|
* @param {String} filePath: absolute path for the loader
|
||||||
@param {String} content
|
* @param {String} content
|
||||||
@param {Object} context
|
* @param {Object} context (optional)
|
||||||
@return {Promise<TemplateOutput>}
|
* @return {Promise<TemplateOutput>}
|
||||||
*/
|
*/
|
||||||
function renderTemplate(engine, filePath, content, context) {
|
function renderTemplate(engine, filePath, content, context) {
|
||||||
context = context || {};
|
context = context || {};
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitbook",
|
"name": "gitbook",
|
||||||
"version": "3.0.2",
|
"version": "3.1.0",
|
||||||
"homepage": "https://www.gitbook.com",
|
"homepage": "https://www.gitbook.com",
|
||||||
"description": "Library and cmd utility to generate GitBooks",
|
"description": "Library and cmd utility to generate GitBooks",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
@@ -21,15 +21,15 @@
|
|||||||
"extend": "^3.0.0",
|
"extend": "^3.0.0",
|
||||||
"fresh-require": "1.0.3",
|
"fresh-require": "1.0.3",
|
||||||
"front-matter": "2.0.8",
|
"front-matter": "2.0.8",
|
||||||
"gitbook-asciidoc": "1.1.4",
|
"gitbook-asciidoc": "1.2.0",
|
||||||
"gitbook-markdown": "1.2.3",
|
"gitbook-markdown": "1.3.0",
|
||||||
"gitbook-plugin-fontsettings": "1.0.3",
|
"gitbook-plugin-fontsettings": "2.0.0",
|
||||||
"gitbook-plugin-highlight": "2.0.2",
|
"gitbook-plugin-highlight": "2.0.2",
|
||||||
"gitbook-plugin-livereload": "0.0.1",
|
"gitbook-plugin-livereload": "0.0.1",
|
||||||
"gitbook-plugin-lunr": "1.1.0",
|
"gitbook-plugin-lunr": "1.1.0",
|
||||||
"gitbook-plugin-search": "2.2.1",
|
"gitbook-plugin-search": "2.2.1",
|
||||||
"gitbook-plugin-sharing": "1.0.2",
|
"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",
|
"github-slugid": "1.0.1",
|
||||||
"graceful-fs": "4.1.4",
|
"graceful-fs": "4.1.4",
|
||||||
"i18n-t": "1.0.0",
|
"i18n-t": "1.0.0",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ var is = require('is');
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var expect = require('expect');
|
var expect = require('expect');
|
||||||
|
var cheerio = require('cheerio');
|
||||||
|
|
||||||
expect.extend({
|
expect.extend({
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +44,18 @@ expect.extend({
|
|||||||
'expected to be defined'
|
'expected to be defined'
|
||||||
);
|
);
|
||||||
return this;
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user