Compare commits

...

47 Commits

Author SHA1 Message Date
Samy Pessé cc9274c9af Bump version to 3.0.0-pre.12 2016-05-04 13:50:52 +02:00
Johan Preynat bbdf7efc34 Fix location.relative(dir, file) trailing slash if file is a directory 2016-05-04 11:51:39 +02:00
Samy Pessé 38a986995a Merge pull request #1264 from GitbookIO/fix/plugin-resources
Fix Loading plugin resources
2016-05-04 10:33:29 +02:00
Johan Preynat ee8d35df7a Update Plugins.listResources() to use the prepared resources list 2016-05-04 10:30:11 +02:00
Johan Preynat be411cd5ff Update copyPluginAssets to use prepared resources 2016-05-04 10:27:21 +02:00
Johan Preynat 2104019171 List resources per plugin in website generator onInit() 2016-05-04 10:26:57 +02:00
Johan Preynat 4e564707df Add resources to website generator state 2016-05-04 10:26:06 +02:00
Johan Preynat da8611e0c3 Add global method resolve(fileName) 2016-05-04 10:25:37 +02:00
Samy Pessé 5d11641a5f Merge pull request #1260 from GitbookIO/move-article
Adds move and remove article modifier.
2016-05-04 09:47:50 +02:00
Samy Pessé 24d38e4fcf Fix #1263: Fix page generation for AsciiDoc 2016-05-04 09:30:09 +02:00
Samy Pessé efb65a8413 Remove filter for pdf/epub/mobi files when outputting 2016-05-03 17:18:13 +02:00
Soreine 3fc90554f5 Add tests, and fixes. 2016-05-03 16:02:58 +02:00
Soreine 4ebe28faae Update insert specification 2016-05-03 13:11:32 +02:00
Soreine 693171cb05 Adds move and remove article modifier. 2016-05-03 12:37:26 +02:00
Samy Pessé 42a2d090aa Bump version to 3.0.0-pre.11 2016-05-03 12:15:30 +02:00
Samy Pessé c84eaa83dd Merge pull request #1259 from GitbookIO/png-data-uri
Adding data-uri support for PNG Images in Books
2016-05-03 12:10:09 +02:00
Johan Preynat 74c3ff80cb lib/models/config.js: Fix wrong call to PluginDependency.listToArray() to PluginDependency.listFromArray() 2016-05-03 12:04:52 +02:00
Johan Preynat 5893d3ee64 lib/parse/parseConfig.js: Remove unused require 2016-05-03 12:04:04 +02:00
Johan Preynat 7f435c56a0 lib/output/modifiers/inlineAssets.js: Add inlinePng() modifier 2016-05-03 11:30:02 +02:00
Johan Preynat 59b7100cf6 lib/output/modifiers/__tests__/inlinePng.js: Add test file for inlinePng modifier 2016-05-03 11:29:40 +02:00
Johan Preynat 0923d1aa24 lib/output/modifiers/inlinePng.js: Add modifier to save inline PNG images 2016-05-03 11:29:17 +02:00
Johan Preynat ffe6c36f2f lib/output/modifiers/resolveImages.js: Don't resolve images when src is a data URI 2016-05-03 11:28:39 +02:00
Johan Preynat 8c4752c5d8 lib/utils/__tests__/location.js: Add location.isDataURI() test file 2016-05-03 11:27:36 +02:00
Johan Preynat d6202e522b lib/utils/images.js: Add convertInlinePNG() method 2016-05-03 11:27:05 +02:00
Johan Preynat 50a24c62c5 lib/utils/location.js: Add isDataURI() method 2016-05-03 11:26:38 +02:00
Soreine 6b17d08892 Adds default file naming utilities in Book model 2016-05-03 11:17:27 +02:00
Samy Pesse 79c91fd8c5 Log npm version being used when running gitbook install 2016-05-02 22:18:19 +02:00
Samy Pesse 95764258ab Update dependencies 2016-05-02 22:16:51 +02:00
Samy Pesse 41e6872557 Merge branch 'fix/plugins_deps' 2016-05-02 22:02:45 +02:00
Samy Pesse 30c96c37c2 Adapt Plugin.listAll to use a list of PluginDependency 2016-05-02 22:02:24 +02:00
Samy Pesse 20f1cf3dff Enable promise long stack trace when testing or debugging 2016-05-02 22:01:05 +02:00
Samy Pesse 8de0d6186e Fix mock fs for windows testing 2016-05-02 19:16:06 +02:00
Samy Pesse edd7d8aefe Update gitbook-asciidoc@1.1.4 2016-05-02 18:45:17 +02:00
Samy Pesse a8da4903f4 Update gitbook-asciidoc@1.1.3 to fix error on linux 2016-05-02 17:27:18 +02:00
Samy Pesse c8df5033f7 Update gitbook-asciidoc@1.1.2 2016-05-02 17:25:41 +02:00
Samy Pesse 412f418da3 Update gitbook-markdown@1.2.3 2016-05-02 17:15:54 +02:00
Johan Preynat 3782d21069 lib/output/ebook/onPage.js: Fix missing page's filepath at Modifiers.inlineAssets() call 2016-05-02 16:54:14 +02:00
Johan Preynat 82a29f4470 lib/models/languages.js: Use lang.ref 2016-05-02 16:37:47 +02:00
Johan Preynat 4a65326b24 Update jest@12.0.2 2016-05-02 16:37:24 +02:00
Johan Preynat d75d8729be lib/output/ebook/onFinish.js: Use lowercase summary.html filename from themes 2016-05-02 16:26:00 +02:00
Samy Pessé c87060454e Add model for plugin dependencies in the book.json 2016-05-02 16:24:20 +02:00
Samy Pessé bbba52c7ff Update gitbook-markdown and gitbook-asciidoc 2016-05-02 16:21:44 +02:00
Samy Pessé 33418bdb33 Document method in utils/command.js 2016-05-02 15:21:08 +02:00
Samy Pessé de47db2b66 Fix escaping of ebook-convert options 2016-05-02 15:09:10 +02:00
Johan Preynat fa6fcffbe1 Improve lib/plugins/__tests__/findInstalled.js 2016-05-02 15:06:02 +02:00
Samy Pessé 93d3f11891 Fix js error when using node < 4.x 2016-05-02 15:03:29 +02:00
Johan Preynat 59479f10fc Fix lib/plugins/__tests__/findInstalled.js 2016-05-02 14:46:54 +02:00
51 changed files with 914 additions and 209 deletions
+1
View File
@@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix `uk` translation
- Fix heading ID including dashes
- Fix error in code highlighting for unknown languages
- Fix data-uri images being handled as external images
- Accept SSH url as plugin version
- Add templating blocks `markdown`, `asciidoc` and `markup`
- Better search experience
+11
View File
@@ -1,3 +1,4 @@
var path = require('path');
var Promise = require('../utils/promise');
var PathUtils = require('../utils/path');
var fs = require('../utils/fs');
@@ -67,6 +68,16 @@ function encodeGlobal(output) {
return bookFS.readAsString(fileName);
},
/**
Resolve a file from the book root
@param {String} fileName
@return {String}
*/
resolve: function(fileName) {
return path.resolve(book.getContentRoot(), fileName);
},
template: {
/**
Apply a templating block and returns its result
+1 -1
View File
@@ -16,7 +16,7 @@ function createMockFS(files) {
var mtime = new Date();
function getFile(filePath) {
var parts = path.normalize(filePath).split('/');
var parts = path.normalize(filePath).split(path.sep);
return parts.reduce(function(list, part, i) {
if (!list) return null;
-2
View File
@@ -1,5 +1,3 @@
jest.autoMockOff();
describe('Plugin', function() {
var Plugin = require('../plugin');
+47
View File
@@ -0,0 +1,47 @@
describe('PluginDependency', function() {
var PluginDependency = require('../pluginDependency');
describe('createFromString', function() {
it('must parse name', function() {
var plugin = PluginDependency.createFromString('hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('*');
});
it('must parse state', function() {
var plugin = PluginDependency.createFromString('-hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.isEnabled()).toBe(false);
});
describe('Version', function() {
it('must parse version', function() {
var plugin = PluginDependency.createFromString('hello@1.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('1.0.0');
});
it('must parse semver', function() {
var plugin = PluginDependency.createFromString('hello@>=4.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('>=4.0.0');
});
});
describe('GIT Version', function() {
it('must handle HTTPS urls', function() {
var plugin = PluginDependency.createFromString('hello@git+https://github.com/GitbookIO/plugin-ga.git');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('git+https://github.com/GitbookIO/plugin-ga.git');
});
it('must handle SSH urls', function() {
var plugin = PluginDependency.createFromString('hello@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
});
});
});
+52
View File
@@ -244,6 +244,58 @@ Book.createForFS = function createForFS(fs) {
});
};
/**
Infers the default extension for files
@return {String}
*/
Book.prototype.getDefaultExt = function() {
// Inferring sources
var clues = [
this.getReadme(),
this.getSummary(),
this.getGlossary()
];
// List their extensions
var exts = clues.map(function (clue) {
var file = clue.getFile();
if (file.exists()) {
return file.getParser().getExtensions().first();
} else {
return null;
}
});
// Adds the general default extension
exts.push('.md');
// Choose the first non null
return exts.find(function (e) { return e !== null; });
};
/**
Infer the default path for a Readme.
@return {String}
*/
Book.prototype.getDefaultReadmePath = function() {
return this.getContentRoot()+'README'+this.getDefaultExt();
};
/**
Infer the default path for a Summary.
@return {String}
*/
Book.prototype.getDefaultSummaryPath = function() {
return this.getContentRoot()+'SUMMARY'+this.getDefaultExt();
};
/**
Infer the default path for a Glossary.
@return {String}
*/
Book.prototype.getDefaultGlossaryPath = function() {
return this.getContentRoot()+'GLOSSARY'+this.getDefaultExt();
};
/**
Create a language book from a parent
+28
View File
@@ -2,6 +2,7 @@ var is = require('is');
var Immutable = require('immutable');
var File = require('./file');
var PluginDependency = require('./pluginDependency');
var configDefault = require('../constants/configDefault');
var Config = Immutable.Record({
@@ -52,6 +53,33 @@ Config.prototype.setValue = function(keyPath, value) {
return this.set('values', values);
};
/**
Return a list of plugin dependencies
@return {List<PluginDependency>}
*/
Config.prototype.getPluginDependencies = function() {
var plugins = this.getValue('plugins');
if (is.string(plugins)) {
return PluginDependency.listFromString(plugins);
} else {
return PluginDependency.listFromArray(plugins);
}
};
/**
Update the list of plugins dependencies
@param {List<PluginDependency>}
@return {Config}
*/
Config.prototype.setPluginDependencies = function(deps) {
var plugins = PluginDependency.listFromArray(deps);
return this.setValue('plugins', plugins);
};
/**
Create a new config for a file
+1 -1
View File
@@ -57,7 +57,7 @@ Languages.createFromList = function(file, langs) {
langs.forEach(function(lang) {
lang = Language({
title: lang.title,
path: lang.path
path: lang.ref
});
list = list.set(lang.getID(), lang);
});
+4
View File
@@ -43,6 +43,10 @@ Parser.prototype.parseGlossary = function(content) {
Parser.prototype.preparePage = function(content) {
var page = this.get('page');
if (!page.prepare) {
return Promise(content);
}
return Promise(page.prepare(content));
};
+13
View File
@@ -139,6 +139,19 @@ Plugin.createFromString = function(s) {
});
};
/**
Create a plugin from a dependency
@param {PluginDependency}
@return {Plugin}
*/
Plugin.createFromDep = function(dep) {
return new Plugin({
name: dep.getName(),
version: dep.getVersion()
});
};
/**
Return NPM id for a plugin name
+115
View File
@@ -0,0 +1,115 @@
var is = require('is');
var Immutable = require('immutable');
var DEFAULT_VERSION = '*';
/*
PluginDependency represents the informations about a plugin
stored in config.plugins
*/
var PluginDependency = Immutable.Record({
name: String(),
// Requirement version (ex: ">1.0.0")
version: String(DEFAULT_VERSION),
// Is this plugin enabled or disabled?
enabled: Boolean(true)
}, 'PluginDependency');
PluginDependency.prototype.getName = function() {
return this.get('name');
};
PluginDependency.prototype.getVersion = function() {
return this.get('version');
};
PluginDependency.prototype.isEnabled = function() {
return this.get('enabled');
};
/**
Create a plugin from a string
@param {String}
@return {Plugin|undefined}
*/
PluginDependency.createFromString = function(s) {
var parts = s.split('@');
var name = parts[0];
var version = parts.slice(1).join('@');
var enabled = true;
if (name[0] === '-') {
enabled = false;
name = name.slice(1);
}
return new PluginDependency({
name: name,
version: version || DEFAULT_VERSION,
enabled: enabled
});
};
/**
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>}
*/
PluginDependency.listFromArray = function(arr) {
return Immutable.List(arr)
.map(function(entry) {
if (is.string(entry)) {
return PluginDependency.createFromString(entry);
} else {
return PluginDependency({
name: entry.name,
version: entry.version
});
}
})
.filter(function(dep) {
return Boolean(dep.getName());
});
};
/**
Export plugin dependencies as an array
@param {List<PluginDependency>}
@return {Array<String>}
*/
PluginDependency.listToArray = function(arr) {
return arr
.map(function(dep) {
var result;
if (dep.isEnabled()) {
result += '-';
}
result += dep.getName();
if (dep.getVersion() !== DEFAULT_VERSION) {
result += '@' + dep.getVersion();
}
return result;
})
.toJS();
};
module.exports = PluginDependency;
+33 -1
View File
@@ -56,7 +56,7 @@ Summary.prototype.getArticle = function(iter, partIter) {
Return a part/article by its level
@param {String} level
@return {Article}
@return {Article|Part}
*/
Summary.prototype.getByLevel = function(level) {
function iterByLevel(article) {
@@ -129,6 +129,27 @@ Summary.prototype.getPrevArticle = function(current) {
return prev;
};
/**
Return the parent article, or parent part of an article
@param {String|Article} current
@return {Article|Part|Null}
*/
Summary.prototype.getParent = function (level) {
// Coerce to level
level = is.string(level)? level : level.getLevel();
// Get parent level
var parentLevel = getParentLevel(level);
if (!parentLevel) {
return null;
}
// Get parent of the position
var parentArticle = this.getByLevel(parentLevel);
return parentArticle || null;
};
/**
Render summary as text
@@ -188,4 +209,15 @@ Summary.createFromParts = function createFromParts(file, parts) {
});
};
/**
Returns parent level of a level
@param {String} level
@return {String}
*/
function getParentLevel(level) {
var parts = level.split('.');
return parts.slice(0, -1).join('.');
}
module.exports = Summary;
+9 -4
View File
@@ -1,4 +1,4 @@
var PluginDependency = require('../../models/pluginDependency');
/**
Add a plugin to a book's configuration
@@ -9,10 +9,15 @@
*/
function addPlugin(book, plugin, version) {
var config = book.getConfig();
var plugins = config.getValue('plugins', []);
var deps = config.getPluginDependencies();
plugins = plugins.push('livereload');
config = config.setValue('plugins', plugins);
var dep = PluginDependency({
name: plugin,
version: version
});
deps = deps.push(dep);
config = config.setPluginDependencies(deps);
return book.setConfig(config);
}
+5 -7
View File
@@ -8,15 +8,13 @@
*/
function removePlugin(book, pluginName) {
var config = book.getConfig();
var plugins = config.getValue('plugins', []);
var deps = config.getPluginDependencies();
// Find index of this plugin
var index = plugins.findIndex(function(plugin) {
return plugin === pluginName;
deps = deps.filter(function(dep) {
return dep.getName() === pluginName;
});
plugins = plugins.delete(index);
config = config.setValue('plugins', plugins);
config = config.setPluginDependencies(deps);
return book.setConfig(config);
}
@@ -0,0 +1,45 @@
var Immutable = require('immutable');
var Summary = require('../../../models/summary');
var File = require('../../../models/file');
describe('mergeAtLevel', function() {
var mergeAtLevel = require('../mergeAtLevel');
var summary = Summary.createFromParts(File(), [
{
articles: [
{
title: '1.1',
path: '1.1'
},
{
title: '1.2',
path: '1.2'
}
]
},
{
title: 'Part I',
articles: []
}
]);
it('should edit a part', function() {
var beforeChildren = summary.getByLevel('1').getArticles();
var newSummary = mergeAtLevel(summary, '1', {title: 'Part O'});
var edited = newSummary.getByLevel('1');
expect(edited.getTitle()).toBe('Part O');
// Same children
expect(Immutable.is(beforeChildren, edited.getArticles())).toBe(true);
});
it('should edit a part', function() {
var beforePath = summary.getByLevel('1.2').getPath();
var newSummary = mergeAtLevel(summary, '1.2', {title: 'Renamed article'});
var edited = newSummary.getByLevel('1.2');
expect(edited.getTitle()).toBe('Renamed article');
// Same children
expect(Immutable.is(beforePath, edited.getPath())).toBe(true);
});
});
@@ -0,0 +1,68 @@
var Immutable = require('immutable');
var Summary = require('../../../models/summary');
var File = require('../../../models/file');
describe('moveArticle', function() {
var moveArticle = require('../moveArticle');
var summary = Summary.createFromParts(File(), [
{
articles: [
{
title: '1.1',
path: '1.1'
},
{
title: '1.2',
path: '1.2'
}
]
},
{
title: 'Part I',
articles: [
{
title: '2.1',
path: '2.1',
articles: [
{
title: '2.1.1',
path: '2.1.1'
},
{
title: '2.1.2',
path: '2.1.2'
}
]
},
{
title: '2.2',
path: '2.2'
}
]
}
]);
it('should move an article at in place', function() {
var newSummary = moveArticle(summary, '2.1', '2.1');
expect(Immutable.is(summary, newSummary)).toBe(true);
});
it('should move an article to an previous level', function() {
var newSummary = moveArticle(summary, '2.2', '2.1');
var moved = newSummary.getByLevel('2.1');
var other = newSummary.getByLevel('2.2');
expect(moved.getTitle()).toBe('2.2');
expect(other.getTitle()).toBe('2.1');
});
it('should move an article to a next level', function() {
var newSummary = moveArticle(summary, '2.1', '2.2');
var moved = newSummary.getByLevel('2.1');
var other = newSummary.getByLevel('2.2');
expect(moved.getTitle()).toBe('2.2');
expect(other.getTitle()).toBe('2.1');
});
});
+2 -2
View File
@@ -1,4 +1,4 @@
var editArticle = require('./editArticle');
var mergeAtLevel = require('./mergeAtLevel');
/**
Edit title of an article
@@ -9,7 +9,7 @@ var editArticle = require('./editArticle');
@return {Summary}
*/
function editArticleTitle(summary, level, newTitle) {
return editArticle(summary, level, {
return mergeAtLevel(summary, level, {
title: newTitle
});
}
-1
View File
@@ -1,4 +1,3 @@
/**
Edit title of a part in the summary
+2 -1
View File
@@ -1,6 +1,7 @@
module.exports = {
insertArticle: require('./insertArticle'),
moveArticle: require('./moveArticle'),
removeArticle: require('./removeArticle'),
unshiftArticle: require('./unshiftArticle'),
editPartTitle: require('./editPartTitle'),
+21 -35
View File
@@ -1,25 +1,14 @@
var is = require('is');
var SummaryArticle = require('../../models/summaryArticle');
var editArticle = require('./editArticle');
var mergeAtLevel = require('./mergeAtLevel');
var indexArticleLevels = require('./indexArticleLevels');
/**
Get level of parent of an article
@param {String} level
@return {String}
*/
function getParentLevel(level) {
var parts = level.split('.');
return parts.slice(0, -1).join('.');
}
/**
Insert an article in a summary at a specific position
Returns a new Summary with the article at the given level, with
subsequent article shifted.
@param {Summary} summary
@param {String|Article} level: level to insert after
@param {String|Article} level: level to insert at
@param {Article} article
@return {Summary}
*/
@@ -27,37 +16,34 @@ function insertArticle(summary, level, article) {
article = SummaryArticle(article);
level = is.string(level)? level : level.getLevel();
var parentLevel = getParentLevel(level);
if (!parentLevel) {
// todo: insert new part
return summary;
}
// Get parent of the position
var parentArticle = summary.getByLevel(parentLevel);
if (!parentLevel) {
var parent = summary.getParent(level);
if (!parent) {
return summary;
}
// Find the index to insert at
var articles = parentArticle.getArticles();
var index = articles.findIndex(function(art) {
return art.getLevel() === level;
});
if (!index) {
return summary;
}
var articles = parent.getArticles();
var index = getLeafIndex(level);
// Insert the article at the right index
articles = articles.insert(index, article);
// Reindex the level from here
parentArticle = parentArticle.set('articles', articles);
parentArticle = indexArticleLevels(parentArticle);
parent = parent.set('articles', articles);
parent = indexArticleLevels(parent);
return editArticle(summary, parentLevel, parentArticle);
return mergeAtLevel(summary, parent.getLevel(), parent);
}
/**
@param {String}
@return {Number} The index of this level within its parent's children
*/
function getLeafIndex(level) {
var arr = level.split('.').map(function (char) {
return parseInt(char, 10);
});
return arr[arr.length - 1] - 1;
}
module.exports = insertArticle;
@@ -11,16 +11,17 @@ function editArticleInList(articles, level, newArticle) {
return articles.map(function(article) {
var articleLevel = article.getLevel();
if (articleLevel == level) {
if (articleLevel === level) {
// it is the article to edit
return article.merge(newArticle);
}
if (level.indexOf(articleLevel) === 0) {
} else if (level.indexOf(articleLevel) === 0) {
// it is a parent
var articles = editArticleInList(article.getArticles(), level, newArticle);
return article.set('articles', articles);
} else {
// This is not the article you are looking for
return article;
}
return article;
});
}
@@ -35,36 +36,40 @@ function editArticleInList(articles, level, newArticle) {
*/
function editArticleInPart(part, level, newArticle) {
var articles = part.getArticles();
articles = editArticleInList(articles);
articles = editArticleInList(articles, level, newArticle);
return part.set('articles', articles);
}
/**
Edit an article in a summary
Edit an article, or a part, in a summary. Does a shallow merge.
@param {Summary} summary
@param {String} level
@param {Article} newArticle
@param {Article|Part} newValue
@return {Summary}
*/
function editArticle(summary, level, newArticle) {
var parts = summary.getParts();
function mergeAtLevel(summary, level, newValue) {
var levelParts = level.split('.');
var partIndex = Number(levelParts[0]);
var partIndex = Number(levelParts[0]) -1;
var parts = summary.getParts();
var part = parts.get(partIndex);
if (!part) {
return summary;
}
part = editArticleInPart(part, level, newArticle);
parts = parts.set(partIndex, part);
var isEditingPart = levelParts.length < 2;
if (isEditingPart) {
part = part.merge(newValue);
} else {
part = editArticleInPart(part, level, newValue);
}
parts = parts.set(partIndex, part);
return summary.set('parts', parts);
}
module.exports = editArticle;
module.exports = mergeAtLevel;
+82
View File
@@ -0,0 +1,82 @@
var is = require('is');
var removeArticle = require('./removeArticle');
var insertArticle = require('./insertArticle');
/**
Returns a new summary, with the given article removed from its
origin level, and placed at the given target level.
@param {Summary} summary
@param {String|SummaryArticle} origin: level to remove
@param {String|SummaryArticle} target: the level where the article will be found
@return {Summary}
*/
function moveArticle(summary, origin, target) {
// Coerce to level
var originLevel = is.string(origin)? origin : origin.getLevel();
var targetLevel = is.string(target)? target : target.getLevel();
var article = summary.getByLevel(originLevel);
// Remove
var removed = removeArticle(summary, origin);
// Adjust targetLevel if removing impacted it
targetLevel = arrayToLevel(
shiftLevel(levelToArray(originLevel),
levelToArray(targetLevel)));
// Re-insert
return insertArticle(removed, target, article);
}
/**
@param {Array<Number>} removedLevel
@param {Array<Number>} level The level to udpate
@return {Array<Number>}
*/
function shiftLevel(removedLevel, level) {
if (level.length === 0) {
// `removedLevel` is under level, so no effect
return level;
} else if (removedLevel.length === 0) {
// Either `level` is a child of `removedLevel`... or they are equal
// This is undefined behavior.
return level;
}
var removedRoot = removedLevel[0];
var root = level[0];
var removedRest = removedLevel.slice(1);
var rest = level.slice(1);
if (removedRoot < root) {
// It will shift levels at this point. The rest is unchanged.
return Array.prototype.concat(root - 1, rest);
} else if (removedRoot === root) {
// Look deeper
return Array.prototype.concat(root, shiftLevel(removedRest, rest));
} else {
// No impact
return level;
}
}
/**
@param {String}
@return {Array<Number>}
*/
function levelToArray(l) {
return l.split('.').map(function (char) {
return parseInt(char, 10);
});
}
/**
@param {Array<Number>}
@return {String}
*/
function arrayToLevel(a) {
return a.join('.');
}
module.exports = moveArticle;
+37
View File
@@ -0,0 +1,37 @@
var is = require('is');
var mergeAtLevel = require('./mergeAtLevel');
var indexArticleLevels = require('./indexArticleLevels');
/**
Remove an article from a level.
@param {Summary} summary
@param {String|SummaryArticle} level: level to remove
@return {Summary}
*/
function removeArticle(summary, level) {
// Coerce to level
level = is.string(level)? level : level.getLevel();
var parent = summary.getParent(level);
var articles = parent.getArticles();
// Find the index to remove
var index = articles.findIndex(function(art) {
return art.getLevel() === level;
});
if (index === -1) {
return summary;
}
// Remove from children
articles = articles.remove(index);
parent = parent.set('articles', articles);
// Reindex the level from here
parent = indexArticleLevels(parent);
return mergeAtLevel(summary, parent.getLevel(), parent);
}
module.exports = removeArticle;
+1 -1
View File
@@ -4,7 +4,7 @@ var SummaryPart = require('../../models/summaryPart');
var indexLevels = require('./indexLevels');
/**
Insert an article at the
Insert an article at the beginning of summary
@param {Summary} summary
@param {Article} article
+9
View File
@@ -12,6 +12,15 @@ describe('WebsiteGenerator', function() {
});
});
pit('should generate an index.html for AsciiDoc', function() {
return generateMock(WebsiteGenerator, {
'README.adoc': 'Hello World'
})
.then(function(folder) {
expect(folder).toHaveFile('index.html');
});
});
pit('should generate an HTML file for each articles', function() {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
+4 -3
View File
@@ -9,6 +9,7 @@ var command = require('../../utils/command');
var writeFile = require('../helper/writeFile');
var getConvertOptions = require('./getConvertOptions');
var SUMMARY_FILE = 'SUMMARY.html';
/**
Write the SUMMARY.html
@@ -20,12 +21,12 @@ function writeSummary(output) {
var options = output.getOptions();
var prefix = options.get('prefix');
var filePath = 'SUMMARY.html';
var filePath = SUMMARY_FILE;
var engine = WebsiteGenerator.createTemplateEngine(output, filePath);
var context = JSONUtils.encodeOutput(output);
// Render the theme
return Templating.renderFile(engine, prefix + '/SUMMARY.html', context)
return Templating.renderFile(engine, prefix + '/summary.html', context)
// Write it to the disk
.then(function(html) {
@@ -53,7 +54,7 @@ function runEbookConvert(output) {
.then(function(options) {
var cmd = [
'ebook-convert',
path.resolve(outputFolder, 'SUMMARY.html'),
path.resolve(outputFolder, SUMMARY_FILE),
path.resolve(outputFolder, 'index.' + format),
command.optionsToShellArgs(options)
].join(' ');
+1 -1
View File
@@ -12,7 +12,7 @@ function onPage(output, page) {
// Inline assets
return Modifiers.modifyHTML(page, [
Modifiers.inlineAssets(options.get('root'))
Modifiers.inlineAssets(options.get('root'), page.getFile().getPath())
])
// Write page using website generator
@@ -0,0 +1,25 @@
var cheerio = require('cheerio');
var tmp = require('tmp');
var inlinePng = require('../inlinePng');
describe('inlinePng', function() {
var dir;
beforeEach(function() {
dir = tmp.dirSync();
});
pit('should write an inline PNG using data URI as a file', function() {
var $ = cheerio.load('<img alt="GitBook Logo 20x20" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUEAYAAADdGcFOAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAF+klEQVRIDY3Wf5CVVR3H8c9z791fyI9dQwdQ4TTI7wEWnQZZAa/mJE4Z0OaKUuN1KoaykZxUGGHay+iIVFMoEYrUPhDCKEKW2ChT8dA0RCSxWi6EW3sYYpcfxq5C+4O9957O+7m7O/qHQ9/XzH1+nHuec57z8wkWTsKw0y6N/LxXN6KzTnEUHi8eP/l3YStSU/MdsYvBbGh8six2YXcbcgc++QkfTQkWz/81KtqDA0hlUoWnsX+5uxe5X365BB9my2bjrHNHccLk16BpS9CExjcmXMDbD6wehdyEjxbjz1uK1zn9qga6dcfnMLXeXY/qjuQqTF4W1MKke8ZgeNhjMCxMPIWSd4OF78C55CFI/1kF6WwXpMqjkAZ/CKniNDrCsmU4lE1YbPlgR2x7R39FF23D4mq3A1+Z35PGTNs1E1XhxcGQOh6HNPwXkK56BVJhOaRg/pvoHXNxHFw410B25EYE2RMvI0i/twFJvXcrFObykEa+DmnQGLwYqR0l2a6JqItaj8C/4E2QxtZCofkC8tF1t8HZc/fAZaLnIF2xEsoEtW1w7vBSSFtfhDTnCki9cSi81Ain1uko2Ld+Dmf2rkUq0/5t+PYbFtPQdkjzNiAXTWtDEF49FgkzJInAVPwNyhzcDOmrdZCm/Rn+ebWtcPs+/U24hmg2XL0rRkPPELh9R8fDtXR2oC/VuZbGaci79Ajkb6lZgfyYtyzy/X9s6T/pO/ZfN/RdNxxIwTWM2wbX8KVmuIaEqmKm6zEondwGpd0SyOy5DrJ//TFkX9kMhd3XQHbEVCSsm4OECV5HIv2p15CwfWPSntoHRbv2Q1HzSvSlSqZwATIuBxk/zZBOBbdB+u9hSKU3Q7pwAjInZkFm6U8hu7MSMqe/Dqn8fUj5GVCmpxK+4N/F1LMa0p5eSOPqIPP7NGSunAI/+R6GnzQzIBt8A1LC/QZ+6HwLst1rITv0n5CtXgSZ78yFTNkR+FdeDZneJkip3fAtsQ5Scilkek7CH9dAmjIWvkK7IXXOh6/IzZDNPQdZXR1TQmdjKv0ZfEu0YKDpNflpyG5aDtnRv8VAuu3dBV+huyBbvgdS97tQNLQc0mfugKy5Cb4BipPIXvsUpK5N8Mvao/Bd3QDZRH9Rrtj3Cl6FHwPFMLmNkKrj8BnHoT+XX6f2wl+XxFS4Ab7C72Dgf7bi+5DpTkNm8kQMpCs/BzIlz8LfPxnzLdh3EjwMX4GX4Ju4GNb9A1L7k/D3J8b6kv2LFCtmCmcgUzoJsr2z4MfwFsh87xikZefg188fYaAhpPUxm3ge/vFnYkoED0HqeQiyJYcwkNGWnoNv6s9C1p1Bf/389VYoCjohW7UfMms3wXdpBv7+FEiPLIHs4DIMNERUNhbSpY3wk6QOsqlCDVx2xCrInMpBmfNPQOnzKxBkkrugdOl9GKigSZZCUWIm/GqwDtLUI5D+WAOlb9wKP0YvQLbjZSjsaYaL/n0/FA3fDtnCGihK5UYjCK+ZDr+TDIKLdm2Fs1UOzo76F5wO74XSZj0S6d7RCMLkCshcXALZxaWQRjXDZQ62oRAdCeG/Ju5HELX2QFH3C0hkRy6GovyfwF58AoVbguOxyB2H7/I34Gf11yANnQSp7Vr4MbQH0vg7kbNNp5AM3UrIVDchnz56B1Jm573wW9gZSFVPwO/hefg5FsIvN09CchtQCIOFw/F5U8ii3CZn4cqo7C8YlXEPYkx9cacZl00+iwnprrtwVdj1Q/gXmAs/pu6LZc9XQOGgSvh19n2cDZN341g2EcfxTEGwH/RewqlMsUfbbWIGLjUG+j/j9nokD1beiOvLS5dhjr30Gu6ZnivgdtM/6VJvY1+6pBHbH+h9CX84vfMxNJtisYVFlys+WNCIZJNmIsjohlhNSQC3f8R55H+y/hjkN8GPR9ndCLJxT4/3n0Px51ay8XQnNrYfDJHf//Fc0oMrEZSeeQGJ7+Z+gKCgLbHNWgXnB9FlYt5JaN38JIINC95EakjtAqQeuUx21c5B6tEFf0fSfbEFQf28Z6D6y+X/H0jf40QQJhYwAAAAAElFTkSuQmCC"/>');
return inlinePng(dir.name, 'index.html', $)
.then(function() {
var $img = $('img');
var src = $img.attr('src');
expect(dir.name).toHaveFile(src);
});
});
});
+3 -1
View File
@@ -1,4 +1,6 @@
var is = require('is');
var Immutable = require('immutable');
var Promise = require('../../utils/promise');
var editHTMLElement = require('./editHTMLElement');
@@ -9,7 +11,7 @@ var editHTMLElement = require('./editHTMLElement');
@return {String}
*/
function getLanguageForClass(classNames) {
return classNames
return Immutable.List(classNames)
.map(function(cl) {
// Markdown
if (cl.search('lang-') === 0) {
+3 -1
View File
@@ -1,5 +1,6 @@
var svgToImg = require('./svgToImg');
var svgToPng = require('./svgToPng');
var inlinePng = require('./inlinePng');
var resolveImages = require('./resolveImages');
var fetchRemoteImages = require('./fetchRemoteImages');
@@ -20,7 +21,8 @@ function inlineAssets(rootFolder, currentFile) {
.then(fetchRemoteImages.bind(null, rootFolder, currentFile, $))
.then(svgToImg.bind(null, rootFolder, currentFile, $))
.then(svgToPng.bind(null, rootFolder, currentFile, $));
.then(svgToPng.bind(null, rootFolder, currentFile, $))
.then(inlinePng.bind(null, rootFolder, currentFile, $));
};
}
+47
View File
@@ -0,0 +1,47 @@
var crc = require('crc');
var path = require('path');
var imagesUtil = require('../../utils/images');
var fs = require('../../utils/fs');
var LocationUtils = require('../../utils/location');
var editHTMLElement = require('./editHTMLElement');
/**
Convert all inline PNG images to PNG file
@param {String} rootFolder
@param {HTMLDom} $
@return {Promise}
*/
function inlinePng(rootFolder, currentFile, $) {
var currentDirectory = path.dirname(currentFile);
return editHTMLElement($, 'img', function($img) {
var src = $img.attr('src');
if (!LocationUtils.isDataURI(src)) {
return;
}
// We avoid generating twice the same PNG
var hash = crc.crc32(src).toString(16);
var fileName = hash + '.png';
// Result file path
var filePath = path.join(rootFolder, fileName);
return fs.assertFile(filePath, function() {
return imagesUtil.convertInlinePNG(src, filePath);
})
.then(function() {
// Convert filename to a relative filename
fileName = LocationUtils.relative(currentDirectory, fileName);
// Replace src
$img.attr('src', fileName);
});
});
}
module.exports = inlinePng;
+1 -1
View File
@@ -16,7 +16,7 @@ function resolveImages(currentFile, $) {
return editHTMLElement($, 'img', function($img) {
var src = $img.attr('src');
if (LocationUtils.isExternal(src)) {
if (LocationUtils.isExternal(src) || LocationUtils.isDataURI(src)) {
return;
}
+7 -5
View File
@@ -78,14 +78,16 @@ function copyAssets(output, plugin) {
function copyResources(output, plugin) {
var logger = output.getLogger();
var options = output.getOptions();
var prefix = options.get('prefix');
var options = output.getOptions();
var outputRoot = options.get('root');
var pluginRoot = plugin.getPath();
var resources = plugin.getResources(prefix);
var state = output.getState();
var resources = state.getResources();
var assetsFolder = resources.get('assets');
var pluginRoot = plugin.getPath();
var pluginResources = resources.get(plugin.getName());
var assetsFolder = pluginResources.get('assets');
var assetOutputFolder = path.join(outputRoot, 'gitbook', plugin.getNpmID());
if (!assetsFolder) {
+2
View File
@@ -2,6 +2,7 @@ var Promise = require('../../utils/promise');
var copyPluginAssets = require('./copyPluginAssets');
var prepareI18n = require('./prepareI18n');
var prepareResources = require('./prepareResources');
/**
Initialize the generator
@@ -12,6 +13,7 @@ var prepareI18n = require('./prepareI18n');
function onInit(output) {
return Promise(output)
.then(prepareI18n)
.then(prepareResources)
.then(copyPluginAssets);
}
+10 -6
View File
@@ -18,11 +18,15 @@ var fileToOutput = require('../helper/fileToOutput');
@param {Page} page
*/
function onPage(output, page) {
var options = output.getOptions();
var file = page.getFile();
var prefix = options.get('prefix');
var book = output.getBook();
var plugins = output.getPlugins();
var options = output.getOptions();
var prefix = options.get('prefix');
var file = page.getFile();
var book = output.getBook();
var plugins = output.getPlugins();
var state = output.getState();
var resources = state.getResources();
var engine = createTemplateEngine(output, page.getPath());
@@ -38,7 +42,7 @@ function onPage(output, page) {
// Generate the context
var context = JSONUtils.encodeBookWithPage(output.getBook(), resultPage);
context.plugins = {
resources: Plugins.listResources(plugins, prefix).toJS()
resources: Plugins.listResources(plugins, resources).toJS()
};
context.template = {
+54
View File
@@ -0,0 +1,54 @@
var is = require('is');
var Immutable = require('immutable');
var Promise = require('../../utils/promise');
var Api = require('../../api');
/**
Prepare plugins resources, add all output corresponding type resources
@param {Output}
@return {Promise<Output>}
*/
function prepareResources(output) {
var plugins = output.getPlugins();
var options = output.getOptions();
var type = options.get('prefix');
var state = output.getState();
var context = Api.encodeGlobal(output);
var result = Immutable.Map();
return Promise.forEach(plugins, function(plugin) {
var pluginResources = plugin.getResources(type);
return Promise()
.then(function() {
// Apply resources if is a function
if (is.fn(pluginResources)) {
return Promise()
.then(pluginResources.bind(context));
}
else {
return pluginResources;
}
})
.then(function(resources) {
result = result.set(plugin.getName(), Immutable.Map(resources));
});
})
.then(function() {
// Set output resources
state = state.merge({
resources: result
});
output = output.merge({
state: state
});
return output;
});
}
module.exports = prepareResources;
+8 -1
View File
@@ -2,11 +2,18 @@ var I18n = require('i18n-t');
var Immutable = require('immutable');
var GeneratorState = Immutable.Record({
i18n: I18n()
i18n: I18n(),
// List of plugins' resources
resources: Immutable.Map()
});
GeneratorState.prototype.getI18n = function() {
return this.get('i18n');
};
GeneratorState.prototype.getResources = function() {
return this.get('resources');
};
module.exports = GeneratorState;
-6
View File
@@ -1,5 +1,3 @@
var is = require('is');
var Promise = require('../utils/promise');
var Config = require('../models/config');
@@ -45,10 +43,6 @@ function parseConfig(book) {
values = validateConfig(values);
if (is.string(values.plugins)) {
values.plugins = values.plugins.split(',');
}
var config = Config.create(file, values);
return book.set('config', config);
});
-3
View File
@@ -27,9 +27,6 @@ function parseIgnore(book) {
// Skip book outputs
'_book',
'*.pdf',
'*.epub',
'*.mobi',
// Ignore files in the templates folder
'_layouts'
+10 -1
View File
@@ -1,12 +1,21 @@
var path = require('path');
var Immutable = require('immutable');
describe('findInstalled', function() {
var findInstalled = require('../findInstalled');
pit('must list default plugins for gitbook directory', function() {
// Read gitbook-plugins from package.json
var pkg = require(path.resolve(__dirname, '../../../package.json'));
var gitbookPlugins = Immutable.Seq(pkg.dependencies)
.filter(function(v, k) {
return k.indexOf('gitbook-plugin') === 0;
})
.cacheResult();
return findInstalled(path.resolve(__dirname, '../../../'))
.then(function(plugins) {
expect(plugins.size > 7).toBeTruthy();
expect(plugins.size >= gitbookPlugins.size).toBeTruthy();
expect(plugins.has('fontsettings')).toBe(true);
expect(plugins.has('search')).toBe(true);
+9 -44
View File
@@ -1,10 +1,10 @@
jest.autoMockOff();
var PluginDependency = require('../../models/pluginDependency');
var listAll = require('../listAll');
describe('listAll', function() {
var listAll = require('../listAll');
it('must list from string', function() {
var plugins = listAll('ga,great');
it('must list default', function() {
var deps = PluginDependency.listFromString('ga,great');
var plugins = listAll(deps);
expect(plugins.size).toBe(8);
@@ -14,45 +14,9 @@ describe('listAll', function() {
expect(plugins.has('search')).toBe(true);
});
it('must list from array', function() {
var plugins = listAll(['ga', 'great']);
expect(plugins.size).toBe(8);
expect(plugins.has('ga')).toBe(true);
expect(plugins.has('great')).toBe(true);
expect(plugins.has('search')).toBe(true);
});
it('must parse version (semver)', function() {
var plugins = listAll(['ga@1.0.0', 'great@>=4.0.0']);
expect(plugins.has('ga')).toBe(true);
expect(plugins.has('great')).toBe(true);
var ga = plugins.get('ga');
expect(ga.getVersion()).toBe('1.0.0');
var great = plugins.get('great');
expect(great.getVersion()).toBe('>=4.0.0');
});
it('must parse version (git)', function() {
var plugins = listAll(['ga@git+https://github.com/GitbookIO/plugin-ga.git', 'great@git+ssh://samy@github.com/GitbookIO/plugin-ga.git']);
expect(plugins.has('ga')).toBe(true);
expect(plugins.has('great')).toBe(true);
var ga = plugins.get('ga');
expect(ga.getVersion()).toBe('git+https://github.com/GitbookIO/plugin-ga.git');
var great = plugins.get('great');
expect(great.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
it('must list from array with -', function() {
var plugins = listAll(['ga', '-great']);
var deps = PluginDependency.listFromString('ga,-great');
var plugins = listAll(deps);
expect(plugins.size).toBe(7);
@@ -61,7 +25,8 @@ describe('listAll', function() {
});
it('must remove default plugins using -', function() {
var plugins = listAll(['ga', '-search']);
var deps = PluginDependency.listFromString('ga,-search');
var plugins = listAll(deps);
expect(plugins.size).toBe(6);
+1 -1
View File
@@ -137,7 +137,7 @@ function installPlugins(book) {
return Promise();
}
logger.info.ln('installing', plugins.size, 'plugins');
logger.info.ln('installing', plugins.size, 'plugins using npm@' + npmi.NPM_VERSION);
return Promise.forEach(plugins, function(plugin) {
return installPlugin(book, plugin);
+13 -30
View File
@@ -1,4 +1,3 @@
var is = require('is');
var Immutable = require('immutable');
var Plugin = require('../models/plugin');
@@ -8,43 +7,27 @@ var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
/**
List all plugins for a book
@param {List<Plugin|String>}
@param {List<PluginDependency>} deps
@return {OrderedMap<Plugin>}
*/
function listAll(plugins) {
if (is.string(plugins)) {
plugins = Immutable.List(plugins.split(','));
}
// Convert to an ordered map
plugins = plugins.map(function(plugin) {
if (is.string(plugin)) {
plugin = Plugin.createFromString(plugin);
} else {
plugin = new Plugin(plugin);
}
return [plugin.getName(), plugin];
});
plugins = Immutable.OrderedMap(plugins);
function listAll(deps) {
// Extract list of plugins to disable (starting with -)
var toRemove = plugins.toList()
var toRemove = deps
.filter(function(plugin) {
return plugin.getName()[0] === '-';
return !plugin.isEnabled();
})
.map(function(plugin) {
return plugin.getName().slice(1);
return plugin.getName();
});
// Remove the '-'
plugins = plugins.mapKeys(function(name) {
if (name[0] === '-') {
return name.slice(1);
} else {
return name;
}
});
// Convert to an ordered map of Plugin
var plugins = deps
.map(function(dep) {
var plugin = Plugin.createFromDep(dep);
return [dep.getName(), plugin];
});
plugins = Immutable.OrderedMap(plugins);
// Append default plugins
DEFAULT_PLUGINS.forEach(function(pluginName) {
+1 -1
View File
@@ -10,7 +10,7 @@ var listAll = require('./listAll');
*/
function listForBook(book) {
var config = book.getConfig();
var plugins = config.getValue('plugins');
var plugins = config.getPluginDependencies();
return listAll(plugins);
}
+4 -4
View File
@@ -11,13 +11,13 @@ var PLUGIN_RESOURCES = require('../constants/pluginResources');
@param {String} type
@return {Map<String:List<{url, path}>}
*/
function listResources(plugins, type) {
function listResources(plugins, resources) {
return plugins.reduce(function(result, plugin) {
var npmId = plugin.getNpmID();
var resources = plugin.getResources(type);
var npmId = plugin.getNpmID();
var pluginResources = resources.get(plugin.getName());
PLUGIN_RESOURCES.forEach(function(resourceType) {
var assets = resources.get(resourceType);
var assets = pluginResources.get(resourceType);
if (!assets) return;
var list = result.get(resourceType) || Immutable.List();
+9
View File
@@ -9,6 +9,15 @@ describe('LocationUtils', function() {
expect(LocationUtils.isExternal('test.md')).toBe(false);
expect(LocationUtils.isExternal('folder/test.md')).toBe(false);
expect(LocationUtils.isExternal('/folder/test.md')).toBe(false);
expect(LocationUtils.isExternal('data:image/png')).toBe(false);
});
it('should correctly test data:uri location', function() {
expect(LocationUtils.isDataURI('data:image/png')).toBe(true);
expect(LocationUtils.isDataURI('http://google.fr')).toBe(false);
expect(LocationUtils.isDataURI('https://google.fr')).toBe(false);
expect(LocationUtils.isDataURI('test.md')).toBe(false);
expect(LocationUtils.isDataURI('data.md')).toBe(false);
});
it('should correctly detect anchor location', function() {
+42 -10
View File
@@ -1,8 +1,15 @@
var is = require('is');
var childProcess = require('child_process');
var spawn = require('spawn-cmd').spawn;
var Promise = require('./promise');
// Execute a command
/**
Execute a command
@param {String} command
@param {Object} options
@return {Promise}
*/
function exec(command, options) {
var d = Promise.defer();
@@ -26,7 +33,14 @@ function exec(command, options) {
return d.promise;
}
// Spawn an executable
/**
Spawn an executable
@param {String} command
@param {Array} args
@param {Object} options
@return {Promise}
*/
function spawnCmd(command, args, options) {
var d = Promise.defer();
var child = spawn(command, args, options);
@@ -54,12 +68,29 @@ function spawnCmd(command, args, options) {
return d.promise;
}
// Transform an option object to a command line string
function escapeShellArg(s) {
s = s.replace(/"/g, '\\"');
return '"' + s + '"';
/**
Transform an option object to a command line string
@param {String|number} value
@param {String}
*/
function escapeShellArg(value) {
if (is.number(value)) {
return value;
}
value = String(value);
value = value.replace(/"/g, '\\"');
return '"' + value + '"';
}
/**
Transform a map of options into a command line arguments string
@param {Object} options
@return {String}
*/
function optionsToShellArgs(options) {
var result = [];
@@ -69,11 +100,12 @@ function optionsToShellArgs(options) {
if (value === null || value === undefined || value === false) {
continue;
}
if (value === true) {
result.push(key);
}
result.push(key + '=' + escapeShellArg(value));
if (is.bool(value)) {
result.push(key);
} else {
result.push(key + '=' + escapeShellArg(value));
}
}
return result.join(' ');
+17 -1
View File
@@ -38,7 +38,23 @@ function convertSVGBufferToPNG(buf, dest) {
});
}
// Converts a inline data: to png file
function convertInlinePNG(source, dest) {
if (!/^data\:image\/png/.test(source)) return Promise.reject(new Error('Source is not a PNG data-uri'));
var base64data = source.split('data:image/png;base64,')[1];
var buf = new Buffer(base64data, 'base64');
return fs.writeFile(dest, buf)
.then(function() {
if (fs.existsSync(dest)) return;
throw new Error('Error converting '+source+' into '+dest);
});
}
module.exports = {
convertSVGToPNG: convertSVGToPNG,
convertSVGBufferToPNG: convertSVGBufferToPNG
convertSVGBufferToPNG: convertSVGBufferToPNG,
convertInlinePNG: convertInlinePNG
};
+14 -3
View File
@@ -4,7 +4,16 @@ var path = require('path');
// Is the url an external url
function isExternal(href) {
try {
return Boolean(url.parse(href).protocol);
return Boolean(url.parse(href).protocol) && !isDataURI(href);
} catch(err) {
return false;
}
}
// Is the url an iniline data-uri
function isDataURI(href) {
try {
return Boolean(url.parse(href).protocol) && (url.parse(href).protocol === 'data:');
} catch(err) {
return false;
}
@@ -39,7 +48,7 @@ function normalize(s) {
@return {String}
*/
function toAbsolute(_href, dir, outdir) {
if (isExternal(_href)) return _href;
if (isExternal(_href) || isDataURI(_href)) return _href;
outdir = outdir == undefined? dir : outdir;
_href = normalize(_href);
@@ -68,7 +77,8 @@ function toAbsolute(_href, dir, outdir) {
@return {String}
*/
function relative(dir, file) {
return normalize(path.relative(dir, file));
var isDirectory = file.slice(-1) === '/';
return normalize(path.relative(dir, file)) + (isDirectory? '/': '');
}
/**
@@ -97,6 +107,7 @@ function areIdenticalPaths(p1, p2) {
module.exports = {
areIdenticalPaths: areIdenticalPaths,
isDataURI: isDataURI,
isExternal: isExternal,
isRelative: isRelative,
isAnchor: isAnchor,
+5
View File
@@ -1,6 +1,11 @@
var Q = require('q');
var Immutable = require('immutable');
// Debugging for long stack traces
if (global.__DEV__ || process.env.DEBUG) {
Q.longStackSupport = true;
}
/**
Reduce an array to a promise
+16 -14
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "3.0.0-pre.10",
"version": "3.0.0-pre.12",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -18,23 +18,22 @@
"error": "7.0.2",
"escape-html": "^1.0.3",
"escape-string-regexp": "1.0.5",
"eslint": "^2.2.0",
"extend": "^3.0.0",
"fresh-require": "1.0.3",
"front-matter": "2.0.7",
"gitbook-asciidoc": "1.1.0",
"gitbook-markdown": "1.2.0",
"gitbook-asciidoc": "1.1.4",
"gitbook-markdown": "1.2.3",
"gitbook-plugin-fontsettings": "1.0.3",
"gitbook-plugin-highlight": "2.0.2",
"gitbook-plugin-livereload": "0.0.1",
"gitbook-plugin-lunr": "1.0.0",
"gitbook-plugin-lunr": "1.1.0",
"gitbook-plugin-search": "2.2.1",
"gitbook-plugin-sharing": "1.0.2",
"gitbook-plugin-theme-default": "1.0.0-pre.9",
"github-slugid": "1.0.1",
"graceful-fs": "4.1.3",
"i18n-t": "1.0.0",
"ignore": "3.1.1",
"ignore": "3.1.2",
"immutable": "^3.8.1",
"is": "^3.1.0",
"json-schema-defaults": "0.1.1",
@@ -42,16 +41,16 @@
"juice": "1.10.0",
"merge-defaults": "0.2.1",
"mkdirp": "0.5.1",
"moment": "2.12.0",
"npm": "3.8.6",
"npmi": "1.0.1",
"moment": "2.13.0",
"npm": "3.8.8",
"npmi": "2.0.1",
"nunjucks": "2.4.2",
"nunjucks-do": "1.0.0",
"object-path": "^0.9.2",
"omit-keys": "^0.1.0",
"q": "1.4.1",
"read-installed": "^4.0.3",
"request": "2.70.0",
"request": "2.72.0",
"resolve": "1.1.7",
"rmdir": "1.2.0",
"semver": "5.1.0",
@@ -59,11 +58,11 @@
"spawn-cmd": "0.0.2",
"tiny-lr": "0.2.1",
"tmp": "0.0.28",
"urijs": "1.17.1"
"urijs": "1.18.0"
},
"devDependencies": {
"eslint": "2.7.0",
"jest-cli": "^11.0.2"
"eslint": "2.9.0",
"jest-cli": "12.0.2"
},
"scripts": {
"test": "node_modules/.bin/jest --bail",
@@ -98,6 +97,9 @@
],
"jest": {
"automock": false,
"setupTestFrameworkScriptFile": "<rootDir>/jest/customMatchers.js"
"setupTestFrameworkScriptFile": "<rootDir>/jest/customMatchers.js",
"globals": {
"__DEV__": true
}
}
}