mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-26 20:27:00 +00:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce50a1d50f | |||
| a76e018d36 | |||
| b06f1fd677 | |||
| 3d8b1880dc | |||
| 1a13c8c9bb | |||
| 852fae3a09 | |||
| d342d31c8e | |||
| 5b3f54fbc8 | |||
| 308ece784c | |||
| 0758ddf04e | |||
| f24d9aef73 | |||
| f8bb55f258 | |||
| 76daebeb84 | |||
| b0d366b7e8 | |||
| 3d0054dd64 | |||
| 13bc89ff44 | |||
| bb910f984e | |||
| e576fdea14 | |||
| c7864927ec | |||
| 54956de59f | |||
| 2cbd30d1f2 | |||
| 55614f1675 | |||
| 4f7421993f | |||
| 32ad7b530e | |||
| ee5d5010f6 | |||
| cf7e5884cd | |||
| 11a0a64d9e | |||
| b6051babac | |||
| 34947b5e20 | |||
| c621380b66 | |||
| c4b54033ce | |||
| 4d85d6eb6e | |||
| cc9274c9af | |||
| bbdf7efc34 | |||
| 38a986995a | |||
| ee8d35df7a | |||
| be411cd5ff | |||
| 2104019171 | |||
| 4e564707df | |||
| da8611e0c3 | |||
| 5d11641a5f | |||
| 24d38e4fcf | |||
| efb65a8413 | |||
| 3fc90554f5 | |||
| 4ebe28faae | |||
| 693171cb05 | |||
| 42a2d090aa | |||
| c84eaa83dd | |||
| 74c3ff80cb | |||
| 5893d3ee64 | |||
| 7f435c56a0 | |||
| 59b7100cf6 | |||
| 0923d1aa24 | |||
| ffe6c36f2f | |||
| 8c4752c5d8 | |||
| d6202e522b | |||
| 50a24c62c5 | |||
| 6b17d08892 | |||
| 79c91fd8c5 | |||
| 95764258ab | |||
| 41e6872557 | |||
| 30c96c37c2 | |||
| 20f1cf3dff | |||
| 8de0d6186e | |||
| edd7d8aefe | |||
| a8da4903f4 | |||
| c8df5033f7 | |||
| 412f418da3 | |||
| 3782d21069 | |||
| 82a29f4470 | |||
| 4a65326b24 | |||
| d75d8729be | |||
| c87060454e | |||
| bbba52c7ff | |||
| 33418bdb33 | |||
| de47db2b66 | |||
| fa6fcffbe1 | |||
| 93d3f11891 | |||
| 59479f10fc |
@@ -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
|
||||
|
||||
+26
-2
@@ -1,15 +1,18 @@
|
||||
var path = require('path');
|
||||
var Promise = require('../utils/promise');
|
||||
var PathUtils = require('../utils/path');
|
||||
var fs = require('../utils/fs');
|
||||
|
||||
var Plugins = require('../plugins');
|
||||
var deprecate = require('./deprecate');
|
||||
var encodeConfig = require('./encodeConfig');
|
||||
var encodeNavigation = require('./encodeNavigation');
|
||||
var fileToURL = require('../output/helper/fileToURL');
|
||||
var defaultBlocks = require('../constants/defaultBlocks');
|
||||
var gitbook = require('../gitbook');
|
||||
|
||||
var encodeConfig = require('./encodeConfig');
|
||||
var encodeSummary = require('./encodeSummary');
|
||||
var encodeNavigation = require('./encodeNavigation');
|
||||
|
||||
/**
|
||||
Encode a global context into a JS object
|
||||
It's the context for page's hook, etc
|
||||
@@ -28,6 +31,7 @@ function encodeGlobal(output) {
|
||||
var result = {
|
||||
log: logger,
|
||||
config: encodeConfig(output, book.getConfig()),
|
||||
summary: encodeSummary(output, book.getSummary()),
|
||||
|
||||
/**
|
||||
Check if the book is a multilingual book
|
||||
@@ -67,6 +71,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
|
||||
@@ -96,6 +110,16 @@ function encodeGlobal(output) {
|
||||
return outputFolder;
|
||||
},
|
||||
|
||||
/**
|
||||
Resolve a file from the output root
|
||||
|
||||
@param {String} fileName
|
||||
@return {String}
|
||||
*/
|
||||
resolve: function(fileName) {
|
||||
return path.resolve(outputFolder, fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
Convert a filepath into an url
|
||||
@return {String}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
var encodeSummaryArticle = require('../json/encodeSummaryArticle');
|
||||
|
||||
/**
|
||||
Encode summary to provide an API to plugin
|
||||
|
||||
@param {Output} output
|
||||
@param {Config} config
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeSummary(output, summary) {
|
||||
var result = {
|
||||
/**
|
||||
Iterate over the summary, it stops when the "iter" returns false
|
||||
|
||||
@param {Function} iter
|
||||
*/
|
||||
walk: function (iter) {
|
||||
summary.getArticle(function(article) {
|
||||
var jsonArticle = encodeSummaryArticle(article, false);
|
||||
|
||||
return iter(jsonArticle);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Get an article by its level
|
||||
|
||||
@param {String} level
|
||||
@return {Object}
|
||||
*/
|
||||
getArticleByLevel: function(level) {
|
||||
var article = summary.getByLevel(level);
|
||||
return (article? encodeSummaryArticle(article) : undefined);
|
||||
},
|
||||
|
||||
/**
|
||||
Get an article by its path
|
||||
|
||||
@param {String} level
|
||||
@return {Object}
|
||||
*/
|
||||
getArticleByPath: function(level) {
|
||||
var article = summary.getByPath(level);
|
||||
return (article? encodeSummaryArticle(article) : undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = encodeSummary;
|
||||
+1
-1
@@ -4,7 +4,7 @@ var options = require('./options');
|
||||
var initBook = require('../init');
|
||||
|
||||
module.exports = {
|
||||
name: 'install [book]',
|
||||
name: 'init [book]',
|
||||
description: 'setup and create files for chapters',
|
||||
options: [
|
||||
options.log
|
||||
|
||||
+45
-10
@@ -16,12 +16,26 @@ var watch = require('./watch');
|
||||
|
||||
var server, lrServer, lrPath;
|
||||
|
||||
function waitForCtrlC() {
|
||||
var d = Promise.defer();
|
||||
|
||||
process.on('SIGINT', function() {
|
||||
d.resolve();
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
|
||||
function generateBook(args, kwargs) {
|
||||
var port = kwargs.port;
|
||||
var outputFolder = getOutputFolder(args);
|
||||
var book = getBook(args, kwargs);
|
||||
var Generator = Output.getGenerator(kwargs.format);
|
||||
|
||||
var hasWatch = kwargs['watch'];
|
||||
var hasLiveReloading = kwargs['live'];
|
||||
|
||||
// Stop server if running
|
||||
if (server.isRunning()) console.log('Stopping server');
|
||||
|
||||
@@ -29,8 +43,10 @@ function generateBook(args, kwargs) {
|
||||
.then(function() {
|
||||
return Parse.parseBook(book)
|
||||
.then(function(resultBook) {
|
||||
// Enable livereload plugin
|
||||
resultBook = ConfigModifier.addPlugin(resultBook, 'livereload');
|
||||
if (hasLiveReloading) {
|
||||
// Enable livereload plugin
|
||||
resultBook = ConfigModifier.addPlugin(resultBook, 'livereload');
|
||||
}
|
||||
|
||||
return Output.generate(Generator, resultBook, {
|
||||
root: outputFolder
|
||||
@@ -45,7 +61,7 @@ function generateBook(args, kwargs) {
|
||||
.then(function() {
|
||||
console.log('Serving book on http://localhost:'+port);
|
||||
|
||||
if (lrPath) {
|
||||
if (lrPath && hasLiveReloading) {
|
||||
// trigger livereload
|
||||
lrServer.changed({
|
||||
body: {
|
||||
@@ -55,7 +71,9 @@ function generateBook(args, kwargs) {
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
if (!kwargs.watch) return;
|
||||
if (!hasWatch) {
|
||||
return waitForCtrlC();
|
||||
}
|
||||
|
||||
return watch(book.getRoot())
|
||||
.then(function(filepath) {
|
||||
@@ -84,7 +102,12 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
name: 'watch',
|
||||
description: 'Enable/disable file watcher',
|
||||
description: 'Enable file watcher and live reloading',
|
||||
defaults: true
|
||||
},
|
||||
{
|
||||
name: 'live',
|
||||
description: 'Enable live reloading',
|
||||
defaults: true
|
||||
},
|
||||
options.log,
|
||||
@@ -92,13 +115,25 @@ module.exports = {
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
server = new Server();
|
||||
lrServer = tinylr({});
|
||||
var hasWatch = kwargs['watch'];
|
||||
var hasLiveReloading = kwargs['live'];
|
||||
|
||||
return Promise.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport)
|
||||
return Promise()
|
||||
.then(function() {
|
||||
if (!hasWatch || !hasLiveReloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
lrServer = tinylr({});
|
||||
return Promise.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport)
|
||||
.then(function() {
|
||||
console.log('Live reload server started on port:', kwargs.lrport);
|
||||
console.log('Press CTRL+C to quit ...');
|
||||
console.log('');
|
||||
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
console.log('Live reload server started on port:', kwargs.lrport);
|
||||
console.log('Press CTRL+C to quit ...');
|
||||
console.log('');
|
||||
return generateBook(args, kwargs);
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
-1
@@ -36,6 +36,7 @@ module.exports = function createNodeFS(root) {
|
||||
fsReadFile: fs.readFile,
|
||||
fsStatFile: fs.stat,
|
||||
fsReadDir: fsReadDir,
|
||||
fsLoadObject: fsLoadObject
|
||||
fsLoadObject: fsLoadObject,
|
||||
fsReadAsStream: fs.readStream
|
||||
});
|
||||
};
|
||||
|
||||
+5
-2
@@ -47,8 +47,11 @@ function initBook(rootFolder) {
|
||||
|
||||
// Write pages
|
||||
return Promise.forEach(articles, function(article) {
|
||||
var filePath = path.join(rootFolder, article.getPath());
|
||||
if (!filePath) return;
|
||||
var articlePath = article.getPath();
|
||||
var filePath = articlePath? path.join(rootFolder, articlePath) : null;
|
||||
if (!filePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fs.assertFile(filePath, function() {
|
||||
return fs.ensureFile(filePath)
|
||||
|
||||
@@ -20,6 +20,7 @@ function encodeSummaryArticle(article, recursive) {
|
||||
anchor: article.getAnchor(),
|
||||
url: article.getUrl(),
|
||||
path: article.getPath(),
|
||||
ref: article.getRef(),
|
||||
articles: articles
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('Plugin', function() {
|
||||
var Plugin = require('../plugin');
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,11 +8,18 @@ describe('Summary', function() {
|
||||
articles: [
|
||||
{
|
||||
title: 'My First Article',
|
||||
path: 'README.md'
|
||||
ref: 'README.md'
|
||||
},
|
||||
{
|
||||
title: 'My Second Article',
|
||||
path: 'article.md'
|
||||
ref: 'article.md'
|
||||
},
|
||||
{
|
||||
title: 'Article without ref'
|
||||
},
|
||||
{
|
||||
title: 'Article with absolute ref',
|
||||
ref: 'https://google.fr'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -33,7 +40,7 @@ describe('Summary', function() {
|
||||
var part = summary.getByLevel('1');
|
||||
|
||||
expect(part).toBeDefined();
|
||||
expect(part.getArticles().size).toBe(2);
|
||||
expect(part.getArticles().size).toBe(4);
|
||||
});
|
||||
|
||||
it('can return a Part (2)', function() {
|
||||
@@ -66,6 +73,12 @@ describe('Summary', function() {
|
||||
expect(article).toBeDefined();
|
||||
expect(article.getTitle()).toBe('My Second Article');
|
||||
});
|
||||
|
||||
it('return undefined if not found', function() {
|
||||
var article = summary.getByPath('NOT_EXISTING.md');
|
||||
|
||||
expect(article).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toText', function() {
|
||||
|
||||
@@ -8,16 +8,16 @@ describe('TemplateBlock', function() {
|
||||
describe('create', function() {
|
||||
pit('must initialize a simple TemplateBlock from a function', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Check basic templateBlock properties
|
||||
expect(templateBlock.getName()).toBe('sayhello');
|
||||
expect(templateBlock.getPost()).toBeNull();
|
||||
expect(templateBlock.getParse()).toBeTruthy();
|
||||
expect(templateBlock.getEndTag()).toBe('endsayhello');
|
||||
expect(templateBlock.getBlocks().size).toBe(0);
|
||||
expect(templateBlock.getShortcuts().size).toBe(0);
|
||||
expect(templateBlock.getExtensionName()).toBe('BlocksayhelloExtension');
|
||||
|
||||
// Check result of applying block
|
||||
@@ -32,10 +32,77 @@ describe('TemplateBlock', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getShortcuts', function() {
|
||||
it('must return undefined if no shortcuts', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
expect(templateBlock.getShortcuts()).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('must return complete shortcut', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', {
|
||||
process: function(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
},
|
||||
shortcuts: {
|
||||
parsers: ['markdown'],
|
||||
start: '$',
|
||||
end: '-'
|
||||
}
|
||||
});
|
||||
|
||||
var shortcut = templateBlock.getShortcuts();
|
||||
|
||||
expect(shortcut).toBeDefined();
|
||||
expect(shortcut.getStart()).toEqual('$');
|
||||
expect(shortcut.getEnd()).toEqual('-');
|
||||
expect(shortcut.getStartTag()).toEqual('sayhello');
|
||||
expect(shortcut.getEndTag()).toEqual('endsayhello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNunjucksExt()', function() {
|
||||
pit('should replace by block anchor', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return 'Hello';
|
||||
});
|
||||
|
||||
var blocks = {};
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
var env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Add template block to environement
|
||||
var Ext = templateBlock.toNunjucksExt({}, blocks);
|
||||
env.addExtension(templateBlock.getExtensionName(), new Ext());
|
||||
|
||||
// Render a template using the block
|
||||
var src = '{% sayhello %}{% endsayhello %}';
|
||||
return Promise.nfcall(env.renderString.bind(env), src)
|
||||
.then(function(res) {
|
||||
blocks = Immutable.fromJS(blocks);
|
||||
expect(blocks.size).toBe(1);
|
||||
|
||||
var blockId = blocks.keySeq().get(0);
|
||||
var block = blocks.get(blockId);
|
||||
|
||||
expect(res).toBe('{{-%' + blockId + '%-}}');
|
||||
expect(block.get('body')).toBe('Hello');
|
||||
expect(block.get('name')).toBe('sayhello');
|
||||
});
|
||||
});
|
||||
|
||||
pit('must create a valid nunjucks extension', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
@@ -55,7 +122,10 @@ describe('TemplateBlock', function() {
|
||||
|
||||
pit('must apply block arguments correctly', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return '<'+block.kwargs.tag+'>Hello, '+block.kwargs.name+'!</'+block.kwargs.tag+'>';
|
||||
return {
|
||||
body: '<'+block.kwargs.tag+'>Hello, '+block.kwargs.name+'!</'+block.kwargs.tag+'>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
@@ -77,7 +147,10 @@ describe('TemplateBlock', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
return 'Hello ' + block.body;
|
||||
return {
|
||||
body: 'Hello ' + block.body,
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,7 +180,10 @@ describe('TemplateBlock', function() {
|
||||
nested[blk.name] = blk.body.trim();
|
||||
});
|
||||
|
||||
return '<p class="yoda">'+nested.end+' '+nested.start+'</p>';
|
||||
return {
|
||||
body: '<p class="yoda">'+nested.end+' '+nested.start+'</p>',
|
||||
parse: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+28
-1
@@ -1,5 +1,6 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
var stream = require('stream');
|
||||
|
||||
var File = require('./file');
|
||||
var Promise = require('../utils/promise');
|
||||
@@ -13,7 +14,9 @@ var FS = Immutable.Record({
|
||||
fsReadFile: Function(),
|
||||
fsStatFile: Function(),
|
||||
fsReadDir: Function(),
|
||||
fsLoadObject: null
|
||||
|
||||
fsLoadObject: null,
|
||||
fsReadAsStream: null
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -111,6 +114,30 @@ FS.prototype.readAsString = function(filename, encoding) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read file as a stream
|
||||
|
||||
@param {String} filename
|
||||
@return {Promise<Stream>}
|
||||
*/
|
||||
FS.prototype.readAsStream = function(filename) {
|
||||
var that = this;
|
||||
var filepath = that.resolve(filename);
|
||||
var fsReadAsStream = this.get('fsReadAsStream');
|
||||
|
||||
if (fsReadAsStream) {
|
||||
return Promise(fsReadAsStream(filepath));
|
||||
}
|
||||
|
||||
return this.read(filename)
|
||||
.then(function(buf) {
|
||||
var bufferStream = new stream.PassThrough();
|
||||
bufferStream.end(buf);
|
||||
|
||||
return bufferStream;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Read stat infos about a 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);
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
+39
-2
@@ -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) {
|
||||
@@ -74,7 +74,12 @@ Summary.prototype.getByLevel = function(level) {
|
||||
*/
|
||||
Summary.prototype.getByPath = function(filePath) {
|
||||
return this.getArticle(function(article) {
|
||||
return (LocationUtils.areIdenticalPaths(article.getPath(), filePath));
|
||||
var articlePath = article.getPath();
|
||||
|
||||
return (
|
||||
articlePath &&
|
||||
LocationUtils.areIdenticalPaths(articlePath, filePath)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -129,6 +134,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 +214,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;
|
||||
|
||||
@@ -30,12 +30,13 @@ SummaryArticle.prototype.getArticles = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Return how deep the article is
|
||||
Return how deep the article is.
|
||||
The README has a depth of 1
|
||||
|
||||
@return {Number}
|
||||
*/
|
||||
SummaryArticle.prototype.getDepth = function() {
|
||||
return this.getLevel().split('.').length;
|
||||
return (this.getLevel().split('.').length - 1);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+31
-53
@@ -4,11 +4,10 @@ var Immutable = require('immutable');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var genKey = require('../utils/genKey');
|
||||
var TemplateShortcut = require('./templateShortcut');
|
||||
|
||||
var NODE_ENDARGS = '%%endargs%%';
|
||||
|
||||
var blockBodies = {};
|
||||
|
||||
var TemplateBlock = Immutable.Record({
|
||||
// Name of block, also the start tag
|
||||
name: String(),
|
||||
@@ -23,26 +22,13 @@ var TemplateBlock = Immutable.Record({
|
||||
blocks: Immutable.List(),
|
||||
|
||||
// List of shortcuts to replace with this block
|
||||
shortcuts: Immutable.List(),
|
||||
|
||||
// Function to execute in post processing
|
||||
post: null,
|
||||
|
||||
parse: true
|
||||
shortcuts: Immutable.Map()
|
||||
}, 'TemplateBlock');
|
||||
|
||||
TemplateBlock.prototype.getName = function() {
|
||||
return this.get('name');
|
||||
};
|
||||
|
||||
TemplateBlock.prototype.getPost = function() {
|
||||
return this.get('post');
|
||||
};
|
||||
|
||||
TemplateBlock.prototype.getParse = function() {
|
||||
return this.get('parse');
|
||||
};
|
||||
|
||||
TemplateBlock.prototype.getEndTag = function() {
|
||||
return this.get('end') || ('end' + this.getName());
|
||||
};
|
||||
@@ -55,8 +41,19 @@ TemplateBlock.prototype.getBlocks = function() {
|
||||
return this.get('blocks');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Return shortcuts associated with this block or undefined
|
||||
|
||||
@return {TemplateShortcut|undefined}
|
||||
*/
|
||||
TemplateBlock.prototype.getShortcuts = function() {
|
||||
return this.get('shortcuts');
|
||||
var shortcuts = this.get('shortcuts');
|
||||
if (shortcuts.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return TemplateShortcut.createForBlock(this, shortcuts);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -73,7 +70,9 @@ TemplateBlock.prototype.getExtensionName = function() {
|
||||
|
||||
@return {Nunjucks.Extension}
|
||||
*/
|
||||
TemplateBlock.prototype.toNunjucksExt = function(mainContext) {
|
||||
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
||||
blocksOutput = blocksOutput || {};
|
||||
|
||||
var that = this;
|
||||
var name = this.getName();
|
||||
var endTag = this.getEndTag();
|
||||
@@ -183,7 +182,7 @@ TemplateBlock.prototype.toNunjucksExt = function(mainContext) {
|
||||
return that.applyBlock(mainBlock, ctx);
|
||||
})
|
||||
.then(function(result) {
|
||||
return that.blockResultToHtml(result);
|
||||
return that.blockResultToHtml(result, blocksOutput);
|
||||
})
|
||||
.nodeify(callback);
|
||||
};
|
||||
@@ -209,19 +208,19 @@ TemplateBlock.prototype.applyBlock = function(inner, context) {
|
||||
var r = processFn.call(context, inner);
|
||||
|
||||
if (Promise.isPromiseAlike(r)) {
|
||||
return r.then(this.handleBlockResult.bind(this));
|
||||
return r.then(this.normalizeBlockResult.bind(this));
|
||||
} else {
|
||||
return this.handleBlockResult(r);
|
||||
return this.normalizeBlockResult(r);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Handle result from a block process function
|
||||
Normalize result from a block process function
|
||||
|
||||
@param {Object} result
|
||||
@param {Object|String} result
|
||||
@return {Object}
|
||||
*/
|
||||
TemplateBlock.prototype.handleBlockResult = function(result) {
|
||||
TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
||||
if (is.string(result)) {
|
||||
result = { body: result };
|
||||
}
|
||||
@@ -234,19 +233,20 @@ TemplateBlock.prototype.handleBlockResult = function(result) {
|
||||
Convert a block result to HTML
|
||||
|
||||
@param {Object} result
|
||||
@param {Object} blocksOutput: stored post processing blocks in this object
|
||||
@return {String}
|
||||
*/
|
||||
TemplateBlock.prototype.blockResultToHtml = function(result) {
|
||||
var parse = this.getParse();
|
||||
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
||||
var indexedKey;
|
||||
var toIndex = (!parse) || (this.getPost() !== undefined);
|
||||
var toIndex = (!result.parse) || (result.post !== undefined);
|
||||
|
||||
if (toIndex) {
|
||||
indexedKey = TemplateBlock.indexBlockResult(result);
|
||||
indexedKey = genKey();
|
||||
blocksOutput[indexedKey] = result;
|
||||
}
|
||||
|
||||
// Parsable block, just return it
|
||||
if (parse) {
|
||||
if (result.parse) {
|
||||
return result.body;
|
||||
}
|
||||
|
||||
@@ -255,29 +255,6 @@ TemplateBlock.prototype.blockResultToHtml = function(result) {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
Index a block result, and return the indexed key
|
||||
|
||||
@param {Object} blk
|
||||
@return {String}
|
||||
*/
|
||||
TemplateBlock.indexBlockResult = function(blk) {
|
||||
var key = genKey();
|
||||
blockBodies[key] = blk;
|
||||
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
Get a block results indexed for a specific key
|
||||
|
||||
@param {String} key
|
||||
@return {Object|undefined}
|
||||
*/
|
||||
TemplateBlock.getBlockResultByKey = function(key) {
|
||||
return blockBodies[key];
|
||||
};
|
||||
|
||||
/**
|
||||
Create a template block from a function or an object
|
||||
|
||||
@@ -292,8 +269,9 @@ TemplateBlock.create = function(blockName, block) {
|
||||
});
|
||||
}
|
||||
|
||||
block = new TemplateBlock(block);
|
||||
block = block.set('name', blockName);
|
||||
return new TemplateBlock(block);
|
||||
return block;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,7 +67,7 @@ TemplateEngine.prototype.getBlock = function(name) {
|
||||
|
||||
@return {Nunjucks.Environment}
|
||||
*/
|
||||
TemplateEngine.prototype.toNunjucks = function() {
|
||||
TemplateEngine.prototype.toNunjucks = function(blocksOutput) {
|
||||
var loader = this.getLoader();
|
||||
var blocks = this.getBlocks();
|
||||
var filters = this.getFilters();
|
||||
@@ -101,7 +101,7 @@ TemplateEngine.prototype.toNunjucks = function() {
|
||||
// Add blocks
|
||||
blocks.forEach(function(block) {
|
||||
var extName = block.getExtensionName();
|
||||
var Ext = block.toNunjucksExt(context);
|
||||
var Ext = block.toNunjucksExt(context, blocksOutput);
|
||||
|
||||
env.addExtension(extName, new Ext());
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var TemplateOutput = Immutable.Record({
|
||||
// Text content of the template
|
||||
content: String(),
|
||||
|
||||
// Map of blocks to replace / post process
|
||||
blocks: Immutable.Map()
|
||||
}, 'TemplateOutput');
|
||||
|
||||
TemplateOutput.prototype.getContent = function() {
|
||||
return this.get('content');
|
||||
};
|
||||
|
||||
TemplateOutput.prototype.getBlocks = function() {
|
||||
return this.get('blocks');
|
||||
};
|
||||
|
||||
/**
|
||||
Update content of this output
|
||||
|
||||
@param {String} content
|
||||
@return {TemplateContent}
|
||||
*/
|
||||
TemplateOutput.prototype.setContent = function(content) {
|
||||
return this.set('content', content);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a TemplateOutput from a text content
|
||||
and an object containing block definition
|
||||
|
||||
@param {String} content
|
||||
@param {Object} blocks
|
||||
@return {TemplateOutput}
|
||||
*/
|
||||
TemplateOutput.create = function(content, blocks) {
|
||||
return new TemplateOutput({
|
||||
content: content,
|
||||
blocks: Immutable.fromJS(blocks)
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = TemplateOutput;
|
||||
@@ -0,0 +1,73 @@
|
||||
var Immutable = require('immutable');
|
||||
var is = require('is');
|
||||
|
||||
/*
|
||||
A TemplateShortcut is defined in plugin's template blocks
|
||||
to replace content with a templating block using delimiters.
|
||||
*/
|
||||
var TemplateShortcut = Immutable.Record({
|
||||
// List of parser names accepting this shortcut
|
||||
parsers: Immutable.Map(),
|
||||
|
||||
start: String(),
|
||||
end: String(),
|
||||
|
||||
startTag: String(),
|
||||
endTag: String()
|
||||
}, 'TemplateShortcut');
|
||||
|
||||
TemplateShortcut.prototype.getStart = function() {
|
||||
return this.get('start');
|
||||
};
|
||||
|
||||
TemplateShortcut.prototype.getEnd = function() {
|
||||
return this.get('end');
|
||||
};
|
||||
|
||||
TemplateShortcut.prototype.getStartTag = function() {
|
||||
return this.get('startTag');
|
||||
};
|
||||
|
||||
TemplateShortcut.prototype.getEndTag = function() {
|
||||
return this.get('endTag');
|
||||
};
|
||||
|
||||
TemplateShortcut.prototype.getParsers = function() {
|
||||
return this.get('parsers');
|
||||
};
|
||||
|
||||
/**
|
||||
Test if this shortcut accept a parser
|
||||
|
||||
@param {Parser|String} parser
|
||||
@return {Boolean}
|
||||
*/
|
||||
TemplateShortcut.prototype.acceptParser = function(parser) {
|
||||
if (!is.string(parser)) {
|
||||
parser = parser.getName();
|
||||
}
|
||||
|
||||
var parserNames = this.get('parsers');
|
||||
return parserNames.includes(parser);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a shortcut for a block
|
||||
|
||||
@param {TemplateBlock} block
|
||||
@param {Map} details
|
||||
@return {TemplateShortcut}
|
||||
*/
|
||||
TemplateShortcut.createForBlock = function(block, details) {
|
||||
details = Immutable.fromJS(details);
|
||||
|
||||
return new TemplateShortcut({
|
||||
parsers: details.get('parsers'),
|
||||
start: details.get('start'),
|
||||
end: details.get('end'),
|
||||
startTag: block.getName(),
|
||||
endTag: block.getEndTag()
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = TemplateShortcut;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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,4 +1,3 @@
|
||||
|
||||
/**
|
||||
Edit title of a part in the summary
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
module.exports = {
|
||||
insertArticle: require('./insertArticle'),
|
||||
moveArticle: require('./moveArticle'),
|
||||
removeArticle: require('./removeArticle'),
|
||||
unshiftArticle: require('./unshiftArticle'),
|
||||
|
||||
editPartTitle: require('./editPartTitle'),
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -12,6 +12,30 @@ describe('WebsiteGenerator', function() {
|
||||
});
|
||||
});
|
||||
|
||||
pit('should copy asset files', function() {
|
||||
return generateMock(WebsiteGenerator, {
|
||||
'README.md': 'Hello World',
|
||||
'myJsFile.js': 'var a = "test";',
|
||||
'folder': {
|
||||
'AnotherAssetFile.md': '# Even md'
|
||||
}
|
||||
})
|
||||
.then(function(folder) {
|
||||
expect(folder).toHaveFile('index.html');
|
||||
expect(folder).toHaveFile('myJsFile.js');
|
||||
expect(folder).toHaveFile('folder/AnotherAssetFile.md');
|
||||
});
|
||||
});
|
||||
|
||||
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',
|
||||
|
||||
@@ -22,16 +22,15 @@ function getPDFTemplate(output, type) {
|
||||
var context = JSONUtils.encodeOutput(output);
|
||||
context.page = {
|
||||
num: '_PAGENUM_',
|
||||
title: '_TITLE_',
|
||||
section: '_SECTION_'
|
||||
title: '_SECTION_'
|
||||
};
|
||||
|
||||
// Render the theme
|
||||
return Templating.renderFile(engine, 'ebook/' + filePath, context)
|
||||
|
||||
// Inline css and assets
|
||||
.then(function(html) {
|
||||
return Promise.nfcall(juice.juiceResources, html, {
|
||||
.then(function(tplOut) {
|
||||
return Promise.nfcall(juice.juiceResources, tplOut.getContent(), {
|
||||
webResources: {
|
||||
relativeTo: outputRoot
|
||||
}
|
||||
|
||||
@@ -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,16 +21,16 @@ 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) {
|
||||
return writeFile(output, filePath, html);
|
||||
.then(function(tplOut) {
|
||||
return writeFile(output, filePath, tplOut.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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(' ');
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -47,12 +47,18 @@ function generatePage(output, page) {
|
||||
return Templating.render(engine, filePath, content, context);
|
||||
})
|
||||
|
||||
// Render page using parser (markdown -> HTML)
|
||||
.then(parser.parsePage.bind(parser)).get('content')
|
||||
.then(function(output) {
|
||||
var content = output.getContent();
|
||||
|
||||
return parser.parsePage(content)
|
||||
.then(function(result) {
|
||||
return output.setContent(result.content);
|
||||
});
|
||||
})
|
||||
|
||||
// Post processing for templating syntax
|
||||
.then(function(content) {
|
||||
return Templating.postRender(engine, content);
|
||||
.then(function(output) {
|
||||
return Templating.postRender(engine, output);
|
||||
})
|
||||
|
||||
// Return new page
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, $));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ function resolveLinks(currentFile, resolveFile, $) {
|
||||
return editHTMLElement($, 'a', function($a) {
|
||||
var href = $a.attr('href');
|
||||
|
||||
// Don't change a tag without href
|
||||
if (!href) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (LocationUtils.isExternal(href)) {
|
||||
$a.attr('_target', 'blank');
|
||||
return;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -10,16 +10,17 @@ var fs = require('../../utils/fs');
|
||||
function onAsset(output, asset) {
|
||||
var book = output.getBook();
|
||||
var options = output.getOptions();
|
||||
var bookFS = book.getContentFS();
|
||||
|
||||
var rootFolder = book.getContentRoot();
|
||||
var outputFolder = options.get('root');
|
||||
|
||||
var filePath = path.resolve(rootFolder, asset);
|
||||
var outputPath = path.resolve(outputFolder, asset);
|
||||
|
||||
return fs.ensureFile(outputPath)
|
||||
.then(function() {
|
||||
return fs.copy(filePath, outputPath);
|
||||
return bookFS.readAsStream(asset)
|
||||
.then(function(stream) {
|
||||
return fs.writeStream(outputPath, stream);
|
||||
});
|
||||
})
|
||||
.thenResolve(output);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ function onFinish(output) {
|
||||
return Templating.renderFile(engine, prefix + '/languages.html', context)
|
||||
|
||||
// Write it to the disk
|
||||
.then(function(html) {
|
||||
return writeFile(output, filePath, html);
|
||||
.then(function(tplOut) {
|
||||
return writeFile(output, filePath, tplOut.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
@@ -63,8 +67,8 @@ function onPage(output, page) {
|
||||
return Templating.renderFile(engine, prefix + '/page.html', context)
|
||||
|
||||
// Write it to the disk
|
||||
.then(function(html) {
|
||||
return writeFile(output, filePath, html);
|
||||
.then(function(tplOut) {
|
||||
return writeFile(output, filePath, tplOut.getContent());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Book = require('../../models/book');
|
||||
var createMockFS = require('../../fs/mock');
|
||||
var listAssets = require('../listAssets');
|
||||
var parseGlossary = require('../parseGlossary');
|
||||
|
||||
describe('listAssets', function() {
|
||||
pit('should not list glossary as asset', function() {
|
||||
var fs = createMockFS({
|
||||
'GLOSSARY.md': '# Glossary\n\n## Hello\nDescription for hello',
|
||||
'assetFile.js': '',
|
||||
'assets': {
|
||||
'file.js': ''
|
||||
}
|
||||
});
|
||||
var book = Book.createForFS(fs);
|
||||
|
||||
return parseGlossary(book)
|
||||
.then(function(resultBook) {
|
||||
return listAssets(resultBook, Immutable.Map());
|
||||
})
|
||||
.then(function(assets) {
|
||||
expect(assets.size).toBe(2);
|
||||
expect(assets.includes('assetFile.js'));
|
||||
expect(assets.includes('assets/file.js'));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,9 @@ function listAssets(book, pages) {
|
||||
var glossary = book.getGlossary();
|
||||
var glossaryFile = glossary.getFile().getPath();
|
||||
|
||||
var langs = book.getLanguages();
|
||||
var langsFile = langs.getFile().getPath();
|
||||
|
||||
return timing.measure(
|
||||
'parse.listAssets',
|
||||
fs.listAllFiles()
|
||||
@@ -25,8 +28,9 @@ function listAssets(book, pages) {
|
||||
return (
|
||||
book.isContentFileIgnored(file) ||
|
||||
pages.has(file) ||
|
||||
file !== summaryFile ||
|
||||
file !== glossaryFile
|
||||
file === summaryFile ||
|
||||
file === glossaryFile ||
|
||||
file === langsFile
|
||||
);
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -27,9 +27,6 @@ function parseIgnore(book) {
|
||||
|
||||
// Skip book outputs
|
||||
'_book',
|
||||
'*.pdf',
|
||||
'*.epub',
|
||||
'*.mobi',
|
||||
|
||||
// Ignore files in the templates folder
|
||||
'_layouts'
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ function getParserByExt(ext) {
|
||||
@return {Parser|undefined}
|
||||
*/
|
||||
function getParserForFile(filename) {
|
||||
return getParser(path.extname(filename));
|
||||
return getParserByExt(path.extname(filename));
|
||||
}
|
||||
|
||||
// List all parsable extensions
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -63,6 +63,9 @@ function findInstalled(folder) {
|
||||
|
||||
// List all folders in node_modules
|
||||
return fs.readdir(node_modules)
|
||||
.fail(function() {
|
||||
return Promise([]);
|
||||
})
|
||||
.then(function(modules) {
|
||||
return Promise.serie(modules, function(module) {
|
||||
// Not a gitbook-plugin
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -11,22 +11,22 @@ describe('ConrefsLoader', function() {
|
||||
describe('Git', function() {
|
||||
pit('should include content from git', function() {
|
||||
return renderTemplate(engine, 'test.md', '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}')
|
||||
.then(function(str) {
|
||||
expect(str).toBe('Hello from git');
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('Hello from git');
|
||||
});
|
||||
});
|
||||
|
||||
pit('should handle deep inclusion (1)', function() {
|
||||
return renderTemplate(engine, 'test.md', '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}')
|
||||
.then(function(str) {
|
||||
expect(str).toBe('First Hello. Hello from git');
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('First Hello. Hello from git');
|
||||
});
|
||||
});
|
||||
|
||||
pit('should handle deep inclusion (2)', function() {
|
||||
return renderTemplate(engine, 'test.md', '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test3.md" %}')
|
||||
.then(function(str) {
|
||||
expect(str).toBe('First Hello. Hello from git');
|
||||
.then(function(out) {
|
||||
expect(out.getContent()).toBe('First Hello. Hello from git');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
var TemplateEngine = require('../../models/templateEngine');
|
||||
var TemplateBlock = require('../../models/templateBlock');
|
||||
var replaceShortcuts = require('../replaceShortcuts');
|
||||
|
||||
describe('replaceShortcuts', function() {
|
||||
var engine = TemplateEngine.create({
|
||||
blocks:[
|
||||
TemplateBlock.create('math', {
|
||||
shortcuts: {
|
||||
start: '$$',
|
||||
end: '$$',
|
||||
parsers: ['markdown']
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
it('should correctly replace inline matches by block', function() {
|
||||
var content = replaceShortcuts(engine, 'test.md', 'Hello $$a = b$$');
|
||||
expect(content).toBe('Hello {% math %}a = b{% endmath %}');
|
||||
});
|
||||
|
||||
it('should correctly replace block matches', function() {
|
||||
var content = replaceShortcuts(engine, 'test.md', 'Hello\n$$\na = b\n$$\n');
|
||||
expect(content).toBe('Hello\n{% math %}\na = b\n{% endmath %}\n');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,25 +7,26 @@ var parsers = require('../parsers');
|
||||
|
||||
@param {TemplateEngine} engine
|
||||
@param {String} filePath
|
||||
@return {List<Shortcut>}
|
||||
@return {List<TemplateShortcut>}
|
||||
*/
|
||||
function listShortcuts(engine, filePath) {
|
||||
var blocks = engine.getBlocks();
|
||||
var parser = parsers.getForFile(filePath);
|
||||
|
||||
if (!parser) {
|
||||
return Immutable.List();
|
||||
}
|
||||
|
||||
return blocks
|
||||
.map(function(block) {
|
||||
var shortcuts = block.getShortcuts();
|
||||
|
||||
return shortcuts.filter(function(shortcut) {
|
||||
var parsers = shortcut.get('parsers');
|
||||
return parsers.includes(parser.name);
|
||||
});
|
||||
return block.getShortcuts();
|
||||
})
|
||||
.flatten(1);
|
||||
.filter(function(shortcuts) {
|
||||
return (
|
||||
shortcuts &&
|
||||
shortcuts.acceptParser(parser.getName())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = listShortcuts;
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
var Promise = require('../utils/promise');
|
||||
var replaceBlocks = require('./replaceBlocks');
|
||||
|
||||
|
||||
/**
|
||||
Replace position markers of blocks by body after processing
|
||||
This is done to avoid that markdown/asciidoc processer parse the block content
|
||||
|
||||
@param {String} content
|
||||
@return {Object} {blocks: Set, content: String}
|
||||
*/
|
||||
function replaceBlocks(content, blocks) {
|
||||
var newContent = content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
|
||||
var replacedWith = match;
|
||||
|
||||
var block = blocks.get(key);
|
||||
if (block) {
|
||||
replacedWith = replaceBlocks(block.get('body'), blocks);
|
||||
}
|
||||
|
||||
return replacedWith;
|
||||
});
|
||||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
/**
|
||||
Post render a template:
|
||||
@@ -7,22 +29,25 @@ var replaceBlocks = require('./replaceBlocks');
|
||||
- Replace block content
|
||||
|
||||
@param {TemplateEngine} engine
|
||||
@param {String} content
|
||||
@param {TemplateOutput} content
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
function postRender(engine, content) {
|
||||
var result = replaceBlocks(content);
|
||||
function postRender(engine, output) {
|
||||
var content = output.getContent();
|
||||
var blocks = output.getBlocks();
|
||||
|
||||
var result = replaceBlocks(content, blocks);
|
||||
|
||||
return Promise.forEach(blocks, function(block) {
|
||||
var post = block.get('post');
|
||||
|
||||
return Promise.forEach(result.blocks, function(blockType) {
|
||||
var block = engine.getBlock(blockType);
|
||||
var post = block.getPost();
|
||||
if (!post) {
|
||||
return;
|
||||
}
|
||||
|
||||
return post();
|
||||
})
|
||||
.thenResolve(result.content);
|
||||
.thenResolve(result);
|
||||
}
|
||||
|
||||
module.exports = postRender;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var Promise = require('../utils/promise');
|
||||
var timing = require('../utils/timing');
|
||||
|
||||
var TemplateOutput = require('../models/templateOutput');
|
||||
var replaceShortcuts = require('./replaceShortcuts');
|
||||
|
||||
/**
|
||||
@@ -10,16 +10,23 @@ var replaceShortcuts = require('./replaceShortcuts');
|
||||
@param {String} filePath
|
||||
@param {String} content
|
||||
@param {Object} context
|
||||
@return {Promise<String>}
|
||||
@return {Promise<TemplateOutput>}
|
||||
*/
|
||||
function renderTemplate(engine, filePath, content, context) {
|
||||
context = context || {};
|
||||
var env = engine.toNunjucks();
|
||||
|
||||
// Mutable objects to contains all blocks requiring post-processing
|
||||
var blocks = {};
|
||||
|
||||
// Create nunjucks environment
|
||||
var env = engine.toNunjucks(blocks);
|
||||
|
||||
// Replace shortcuts from plugin's blocks
|
||||
content = replaceShortcuts(engine, filePath, content);
|
||||
|
||||
return timing.measure(
|
||||
'template.render',
|
||||
|
||||
Promise.nfcall(
|
||||
env.renderString.bind(env),
|
||||
content,
|
||||
@@ -28,6 +35,9 @@ function renderTemplate(engine, filePath, content, context) {
|
||||
path: filePath
|
||||
}
|
||||
)
|
||||
.then(function(content) {
|
||||
return TemplateOutput.create(content, blocks);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ var render = require('./render');
|
||||
@param {TemplateEngine} engine
|
||||
@param {String} filePath
|
||||
@param {Object} context
|
||||
@return {Promise<String>}
|
||||
@return {Promise<TemplateOutput>}
|
||||
*/
|
||||
function renderTemplateFile(engine, filePath, context) {
|
||||
var loader = engine.getLoader();
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
var Immutable = require('immutable');
|
||||
var TemplateBlock = require('../models/templateBlock');
|
||||
|
||||
/**
|
||||
Replace position markers of blocks by body after processing
|
||||
This is done to avoid that markdown/asciidoc processer parse the block content
|
||||
|
||||
@param {String} content
|
||||
@return {Object} {blocks: Set, content: String}
|
||||
*/
|
||||
function replaceBlocks(content) {
|
||||
var blockTypes = new Immutable.Set();
|
||||
var newContent = content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
|
||||
var replacedWith = match;
|
||||
|
||||
var block = TemplateBlock.getBlockResultByKey(key);
|
||||
if (block) {
|
||||
var result = replaceBlocks(block.body);
|
||||
|
||||
blockTypes = blockTypes.add(block.name);
|
||||
blockTypes = blockTypes.concat(result.blocks);
|
||||
replacedWith = result.content;
|
||||
}
|
||||
|
||||
return replacedWith;
|
||||
});
|
||||
|
||||
return {
|
||||
content: newContent,
|
||||
blocks: blockTypes
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = replaceBlocks;
|
||||
@@ -8,16 +8,18 @@ var listShortcuts = require('./listShortcuts');
|
||||
@return {String}
|
||||
*/
|
||||
function applyShortcut(content, shortcut) {
|
||||
var tags = shortcut.get('tag');
|
||||
var start = shortcut.get('start');
|
||||
var end = shortcut.get('end');
|
||||
var start = shortcut.getStart();
|
||||
var end = shortcut.getEnd();
|
||||
|
||||
var tagStart = shortcut.getStartTag();
|
||||
var tagEnd = shortcut.getEndTag();
|
||||
|
||||
var regex = new RegExp(
|
||||
escapeStringRegexp(start) + '([\\s\\S]*?[^\\$])' + escapeStringRegexp(end),
|
||||
'g'
|
||||
);
|
||||
return content.replace(regex, function(all, match) {
|
||||
return '{% ' + tags.start + ' %}' + match + '{% ' + tags.end + ' %}';
|
||||
return '{% ' + tagStart + ' %}' + match + '{% ' + tagEnd + ' %}';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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(' ');
|
||||
|
||||
@@ -157,6 +157,7 @@ module.exports = {
|
||||
statSync: fs.statSync,
|
||||
readdir: Promise.nfbind(fs.readdir),
|
||||
writeStream: writeStream,
|
||||
readStream: fs.createReadStream,
|
||||
copy: Promise.nfbind(cp),
|
||||
copyDir: Promise.nfbind(cpr),
|
||||
tmpFile: genTmpFile,
|
||||
|
||||
+17
-1
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+18
-16
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "3.0.0-pre.10",
|
||||
"version": "3.0.0-pre.14",
|
||||
"homepage": "https://www.gitbook.com",
|
||||
"description": "Library and cmd utility to generate GitBooks",
|
||||
"main": "lib/index.js",
|
||||
"browser": "./lib/browser.js",
|
||||
"dependencies": {
|
||||
"bash-color": "0.0.3",
|
||||
"bash-color": "0.0.4",
|
||||
"cheerio": "0.20.0",
|
||||
"chokidar": "1.4.3",
|
||||
"cp": "0.2.0",
|
||||
@@ -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",
|
||||
"gitbook-plugin-theme-default": "1.0.0-pre.10",
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user