diff options
41 files changed, 504 insertions, 233 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..4311ae2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ +Thank you for giving us your feedback! We listed some guidelines to help you: + +If possible, open separate issues for each bug report. Avoid grouping bugs that are unrelated. + +### What is the current behavior? + +If the current behavior is a bug, please provide the steps to reproduce. if possible, provide the GitBook version being used (`gitbook -V`) and the system infromations (OS and version). + +### What is the expected behavior? + +How do you think, or how would you like it should behave. + +#### For GitBook.com + +Provide, if possible, your username and the URL of the concerned book. + +---- + +The GitBook Team diff --git a/.travis.yml b/.travis.yml index 45ba3e1..b35331d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ sudo: false language: node_js +os: + - linux + - osx node_js: - "stable" - "4.1" @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 3.2.1 +- Fix bug on Firefox when navigating to an url containing an hash +- Update nunjucks to fix JS error when page contains a lot of templating blocks + +## 3.2.0 +- Switch markdown parser from `kramed` to `markup-it` +- Fix support of `|` in tables +- Fix access to `output.name` in templating +- Fix i18n for website and PDF +- Fix minor scrolling issues on website +- Improve options of default search indexer (keywords, disabling per pages) +- Improve pertinence of search results + ## 3.1.1 - Fix order of plugins during loading - Fix error when using math and conrefs @@ -6,10 +6,10 @@ module.exports = { title: 'GitBook Toolchain Documentation', // Enforce use of GitBook v3 - gitbook: '>=3.0.0-pre.0', + gitbook: '3.1.1', // Use the "official" theme - plugins: ['theme-official', 'sitemap'], + plugins: ['theme-official@2.1.1', '-sharing', '-fontsettings', 'sitemap'], variables: { version: pkg.version diff --git a/docs/examples.md b/docs/examples.md index a748c33..2bfcdb3 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -23,5 +23,7 @@ More than 50,000 books have been published on [GitBook.com](https://www.gitbook. ### Documentation - [DuckDuckHack Documentation](http://docs.duckduckhack.com) by [DuckDuckGo](https://duckduckgo.com/about) +- [Loomio Handbook](http://loomio.coop/) by [Loomio](https://www.loomio.org/) +- [Enspiral Handbook](http://handbook.enspiral.com/) by [Enspiral](http://enspiral.com/) - This documentation diff --git a/docs/plugins/testing.md b/docs/plugins/testing.md index bf00da2..c832f11 100644 --- a/docs/plugins/testing.md +++ b/docs/plugins/testing.md @@ -2,7 +2,7 @@ ### Testing your plugin locally -Testing your plugin on your book before plushing it is possible using [npm link](https://docs.npmjs.com/cli/link). +Testing your plugin on your book before publishing it is possible using [npm link](https://docs.npmjs.com/cli/link). In the plugin's folder, run: @@ -10,7 +10,7 @@ In the plugin's folder, run: $ npm link ``` -The nin your book's folder: +Then in your book's folder: ``` $ npm link gitbook-plugin-<plugin's name> diff --git a/docs/styles/website.css b/docs/styles/website.css new file mode 100644 index 0000000..c5e8889 --- /dev/null +++ b/docs/styles/website.css @@ -0,0 +1,4 @@ +/* Reduce size of logo to allow long versions like 3.0.0-pre.1 */ +.gb-page-header .container .logo h1 { + font-size: 24px ; +} diff --git a/lib/browser.js b/lib/browser.js index 7329d74..87a4dc4 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -6,6 +6,7 @@ module.exports = { // Models Book: require('./models/book'), FS: require('./models/fs'), + File: require('./models/file'), Summary: require('./models/summary'), Glossary: require('./models/glossary'), Config: require('./models/config'), diff --git a/lib/cli/serve.js b/lib/cli/serve.js index 4ba5148..5340851 100644 --- a/lib/cli/serve.js +++ b/lib/cli/serve.js @@ -1,6 +1,7 @@ /* eslint-disable no-console */ var tinylr = require('tiny-lr'); +var open = require('open'); var Parse = require('../parse'); var Output = require('../output'); @@ -32,9 +33,11 @@ function generateBook(args, kwargs) { var outputFolder = getOutputFolder(args); var book = getBook(args, kwargs); var Generator = Output.getGenerator(kwargs.format); + var browser = kwargs['browser']; var hasWatch = kwargs['watch']; var hasLiveReloading = kwargs['live']; + var hasOpen = kwargs['open']; // Stop server if running if (server.isRunning()) console.log('Stopping server'); @@ -71,6 +74,10 @@ function generateBook(args, kwargs) { } }); } + + if (hasOpen) { + open('http://localhost:'+port, browser); + } }) .then(function() { if (!hasWatch) { @@ -112,6 +119,16 @@ module.exports = { description: 'Enable live reloading', defaults: true }, + { + name: 'open', + description: 'Enable opening book in browser', + defaults: false + }, + { + name: 'browser', + description: 'Specify browser for opening book', + defaults: '' + }, options.log, options.format ], diff --git a/lib/constants/configSchema.js b/lib/constants/configSchema.js index e693977..d2126c6 100644 --- a/lib/constants/configSchema.js +++ b/lib/constants/configSchema.js @@ -18,6 +18,10 @@ module.exports = { 'type': 'string', 'title': 'ISBN for published book' }, + 'language': { + 'type': 'string', + 'title': 'Language of the book' + }, 'author': { 'type': 'string', 'title': 'Name of the author' diff --git a/lib/json/encodeBookWithPage.js b/lib/json/encodeBookWithPage.js index 5600a82..1c5c7a3 100644 --- a/lib/json/encodeBookWithPage.js +++ b/lib/json/encodeBookWithPage.js @@ -3,12 +3,12 @@ var encodePage = require('./encodePage'); var encodeFile = require('./encodeFile'); /** - Return a JSON representation of a book with a specific file - - @param {Book} output - @param {Page} page - @return {Object} -*/ + * Return a JSON representation of a book with a specific file + * + * @param {Book} output + * @param {Page} page + * @return {Object} + */ function encodeBookWithPage(book, page) { var file = page.getFile(); diff --git a/lib/json/encodeOutput.js b/lib/json/encodeOutput.js index 9054124..7347e57 100644 --- a/lib/json/encodeOutput.js +++ b/lib/json/encodeOutput.js @@ -1,11 +1,11 @@ var encodeBook = require('./encodeBook'); /** - Encode an output to JSON - - @param {Output} - @return {Object} -*/ + * Encode an output to JSON + * + * @param {Output} + * @return {Object} + */ function encodeOutputToJson(output) { var book = output.getBook(); var generator = output.getGenerator(); diff --git a/lib/json/encodeOutputWithPage.js b/lib/json/encodeOutputWithPage.js new file mode 100644 index 0000000..8b21e3d --- /dev/null +++ b/lib/json/encodeOutputWithPage.js @@ -0,0 +1,23 @@ +var encodeOutput = require('./encodeOutput'); +var encodePage = require('./encodePage'); +var encodeFile = require('./encodeFile'); + +/** + * Return a JSON representation of a book with a specific file + * + * @param {Book} output + * @param {Page} page + * @return {Object} + */ +function encodeOutputWithPage(output, page) { + var file = page.getFile(); + var book = output.getBook(); + + var result = encodeOutput(output); + result.page = encodePage(page, book.getSummary()); + result.file = encodeFile(file); + + return result; +} + +module.exports = encodeOutputWithPage; diff --git a/lib/json/index.js b/lib/json/index.js index 4387ae0..3b68f5e 100644 --- a/lib/json/index.js +++ b/lib/json/index.js @@ -2,6 +2,7 @@ module.exports = { encodeOutput: require('./encodeOutput'), encodeBookWithPage: require('./encodeBookWithPage'), + encodeOutputWithPage: require('./encodeOutputWithPage'), encodeBook: require('./encodeBook'), encodeFile: require('./encodeFile'), encodePage: require('./encodePage'), diff --git a/lib/models/__tests__/summaryArticle.js b/lib/models/__tests__/summaryArticle.js index 7c4bc57..22a7a20 100644 --- a/lib/models/__tests__/summaryArticle.js +++ b/lib/models/__tests__/summaryArticle.js @@ -1,4 +1,5 @@ var SummaryArticle = require('../summaryArticle'); +var File = require('../file'); describe('SummaryArticle', function() { describe('createChildLevel', function() { @@ -18,6 +19,35 @@ describe('SummaryArticle', function() { expect(article.createChildLevel()).toBe('1.1.2'); }); }); + + describe('isFile', function() { + it('must return true when exactly the file', function() { + var article = SummaryArticle.create({ + ref: 'hello.md' + }, '1.1'); + var file = File.createWithFilepath('hello.md'); + + expect(article.isFile(file)).toBe(true); + }); + + it('must return true when path is not normalized', function() { + var article = SummaryArticle.create({ + ref: '/hello.md' + }, '1.1'); + var file = File.createWithFilepath('hello.md'); + + expect(article.isFile(file)).toBe(true); + }); + + it('must return false when has anchor', function() { + var article = SummaryArticle.create({ + ref: 'hello.md#world' + }, '1.1'); + var file = File.createWithFilepath('hello.md'); + + expect(article.isFile(file)).toBe(false); + }); + }); }); diff --git a/lib/models/summaryArticle.js b/lib/models/summaryArticle.js index 9b5b653..6da8d1d 100644 --- a/lib/models/summaryArticle.js +++ b/lib/models/summaryArticle.js @@ -40,7 +40,8 @@ SummaryArticle.prototype.getDepth = function() { }; /** - * Get path (without anchor) to the pointing file + * Get path (without anchor) to the pointing file. + * It also normalizes the file path. * * @return {String} */ @@ -58,8 +59,8 @@ SummaryArticle.prototype.getPath = function() { var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref); - // Normalize path to remove ('./', etc) - return location.normalize(pathname); + // Normalize path to remove ('./', '/...', etc) + return location.flatten(pathname); }; /** @@ -107,6 +108,32 @@ SummaryArticle.prototype.isPage = function() { }; /** + * Check if this article is a file (exatcly) + * + * @param {File} file + * @return {Boolean} + */ +SummaryArticle.prototype.isFile = function(file) { + return ( + file.getPath() === this.getPath() + && this.getAnchor() === undefined + ); +}; + +/** + * Check if this article is the introduction of the book + * + * @param {Book|Readme} book + * @return {Boolean} + */ +SummaryArticle.prototype.isReadme = function(book) { + var readme = book.getFile? book : book.getReadme(); + var file = readme.getFile(); + + return this.isFile(file); +}; + +/** * Is article pointing to aan absolute url * * @return {Boolean} diff --git a/lib/modifiers/config/editPlugin.js b/lib/modifiers/config/editPlugin.js index f9b6551..a792acd 100644 --- a/lib/modifiers/config/editPlugin.js +++ b/lib/modifiers/config/editPlugin.js @@ -7,7 +7,7 @@ * @return {Config} */ function editPlugin(config, pluginName, pluginConfig) { - return config.set('pluginsConfig.'+pluginName, pluginConfig); + return config.setValue('pluginsConfig.'+pluginName, pluginConfig); } module.exports = editPlugin; diff --git a/lib/modifiers/config/getPluginConfig.js b/lib/modifiers/config/getPluginConfig.js new file mode 100644 index 0000000..ae76de8 --- /dev/null +++ b/lib/modifiers/config/getPluginConfig.js @@ -0,0 +1,20 @@ +/** + * Return the configuration for a plugin + * @param {Config} config + * @param {String} pluginName + * @return {Object} + */ +function getPluginConfig(config, pluginName) { + var pluginsConfig = config.getValues().get('pluginsConfig'); + if (pluginsConfig === undefined) { + return {}; + } + var pluginConf = pluginsConfig.get(pluginName); + if (pluginConf === undefined) { + return {}; + } else { + return pluginConf.toJS(); + } +} + +module.exports = getPluginConfig; diff --git a/lib/modifiers/config/index.js b/lib/modifiers/config/index.js index 5705dbb..b3de0b0 100644 --- a/lib/modifiers/config/index.js +++ b/lib/modifiers/config/index.js @@ -5,5 +5,6 @@ module.exports = { togglePlugin: require('./togglePlugin'), editPlugin: require('./editPlugin'), hasPlugin: require('./hasPlugin'), + getPluginConfig: require('./getPluginConfig'), isDefaultPlugin: require('./isDefaultPlugin') }; diff --git a/lib/output/__tests__/createMock.js b/lib/output/__tests__/createMock.js new file mode 100644 index 0000000..f21c544 --- /dev/null +++ b/lib/output/__tests__/createMock.js @@ -0,0 +1,38 @@ +var Immutable = require('immutable'); + +var Output = require('../../models/output'); +var Book = require('../../models/book'); +var parseBook = require('../../parse/parseBook'); +var createMockFS = require('../../fs/mock'); +var preparePlugins = require('../preparePlugins'); + +/** + * Create an output using a generator + * + * FOR TESTING PURPOSE ONLY + * + * @param {Generator} generator + * @param {Map<String:String|Map>} files + * @return {Promise<Output>} + */ +function createMockOutput(generator, files, options) { + var fs = createMockFS(files); + var book = Book.createForFS(fs); + var state = generator.State? generator.State({}) : Immutable.Map(); + + book = book.setLogLevel('disabled'); + options = generator.Options(options); + + return parseBook(book) + .then(function(resultBook) { + return new Output({ + book: resultBook, + options: options, + state: state, + generator: generator.name + }); + }) + .then(preparePlugins); +} + +module.exports = createMockOutput; diff --git a/lib/output/__tests__/ebook.js b/lib/output/__tests__/ebook.js index bcac990..9266e9f 100644 --- a/lib/output/__tests__/ebook.js +++ b/lib/output/__tests__/ebook.js @@ -1,4 +1,4 @@ -var generateMock = require('../generateMock'); +var generateMock = require('./generateMock'); var EbookGenerator = require('../ebook'); describe('EbookGenerator', function() { diff --git a/lib/output/generateMock.js b/lib/output/__tests__/generateMock.js index ac1e193..691ee2d 100644 --- a/lib/output/generateMock.js +++ b/lib/output/__tests__/generateMock.js @@ -1,21 +1,20 @@ var tmp = require('tmp'); -var Book = require('../models/book'); -var createMockFS = require('../fs/mock'); -var parseBook = require('../parse/parseBook'); -var generateBook = require('./generateBook'); - +var Book = require('../../models/book'); +var createMockFS = require('../../fs/mock'); +var parseBook = require('../../parse/parseBook'); +var generateBook = require('../generateBook'); /** - Generate a book using JSON generator - And returns the path to the output dir. - - FOR TESTING PURPOSE ONLY - - @param {Generator} - @param {Map<String:String|Map>} files - @return {Promise<String>} -*/ + * Generate a book using a generator + * And returns the path to the output dir. + * + * FOR TESTING PURPOSE ONLY + * + * @param {Generator} + * @param {Map<String:String|Map>} files + * @return {Promise<String>} + */ function generateMock(Generator, files) { var fs = createMockFS(files); var book = Book.createForFS(fs); diff --git a/lib/output/__tests__/json.js b/lib/output/__tests__/json.js index 9897813..12ab567 100644 --- a/lib/output/__tests__/json.js +++ b/lib/output/__tests__/json.js @@ -1,4 +1,4 @@ -var generateMock = require('../generateMock'); +var generateMock = require('./generateMock'); var JSONGenerator = require('../json'); describe('JSONGenerator', function() { diff --git a/lib/output/__tests__/website.js b/lib/output/__tests__/website.js index 501503a..1f8c3c0 100644 --- a/lib/output/__tests__/website.js +++ b/lib/output/__tests__/website.js @@ -1,5 +1,5 @@ var fs = require('fs'); -var generateMock = require('../generateMock'); +var generateMock = require('./generateMock'); var WebsiteGenerator = require('../website'); describe('WebsiteGenerator', function() { diff --git a/lib/output/generateBook.js b/lib/output/generateBook.js index 3c10b1a..46712bd 100644 --- a/lib/output/generateBook.js +++ b/lib/output/generateBook.js @@ -13,13 +13,12 @@ var generateAssets = require('./generateAssets'); var generatePages = require('./generatePages'); /** - Process an output to generate the book - - @param {Generator} generator - @param {Output} output - - @return {Promise<Output>} -*/ + * Process an output to generate the book + * + * @param {Generator} generator + * @param {Output} output + * @return {Promise<Output>} + */ function processOutput(generator, startOutput) { return Promise(startOutput) .then(preparePlugins) @@ -133,27 +132,26 @@ function processOutput(generator, startOutput) { } /** - Generate a book using a generator. - - The overall process is: - 1. List and load plugins for this book - 2. Call hook "config" - 3. Call hook "init" - 4. Initialize generator - 5. List all assets and pages - 6. Copy all assets to output - 7. Generate all pages - 8. Call hook "finish:before" - 9. Finish generation - 10. Call hook "finish" - - - @param {Generator} generator - @param {Book} book - @param {Object} options - - @return {Promise<Output>} -*/ + * Generate a book using a generator. + * + * The overall process is: + * 1. List and load plugins for this book + * 2. Call hook "config" + * 3. Call hook "init" + * 4. Initialize generator + * 5. List all assets and pages + * 6. Copy all assets to output + * 7. Generate all pages + * 8. Call hook "finish:before" + * 9. Finish generation + * 10. Call hook "finish" + * + * + * @param {Generator} generator + * @param {Book} book + * @param {Object} options + * @return {Promise<Output>} + */ function generateBook(generator, book, options) { options = generator.Options(options); var state = generator.State? generator.State({}) : Immutable.Map(); diff --git a/lib/output/generatePage.js b/lib/output/generatePage.js index fa6fc0e..090a870 100644 --- a/lib/output/generatePage.js +++ b/lib/output/generatePage.js @@ -10,12 +10,12 @@ var createTemplateEngine = require('./createTemplateEngine'); var callPageHook = require('./callPageHook'); /** - Prepare and generate HTML for a page - - @param {Output} output - @param {Page} page - @return {Promise<Page>} -*/ + * Prepare and generate HTML for a page + * + * @param {Output} output + * @param {Page} page + * @return {Promise<Page>} + */ function generatePage(output, page) { var book = output.getBook(); var engine = createTemplateEngine(output); @@ -27,7 +27,7 @@ function generatePage(output, page) { var file = resultPage.getFile(); var filePath = file.getPath(); var parser = file.getParser(); - var context = JSONUtils.encodeBookWithPage(book, resultPage); + var context = JSONUtils.encodeOutputWithPage(output, resultPage); if (!parser) { return Promise.reject(error.FileNotParsableError({ diff --git a/lib/output/getModifiers.js b/lib/output/getModifiers.js index 66fbc1a..bb44e80 100644 --- a/lib/output/getModifiers.js +++ b/lib/output/getModifiers.js @@ -9,11 +9,11 @@ var fileToOutput = require('./helper/fileToOutput'); var CODEBLOCK = 'code'; /** - Return default modifier to prepare a page for - rendering. - - @return {Array<Modifier>} -*/ + * Return default modifier to prepare a page for + * rendering. + * + * @return {Array<Modifier>} + */ function getModifiers(output, page) { var book = output.getBook(); var plugins = output.getPlugins(); diff --git a/lib/output/helper/fileToOutput.js b/lib/output/helper/fileToOutput.js index 9673162..361c6eb 100644 --- a/lib/output/helper/fileToOutput.js +++ b/lib/output/helper/fileToOutput.js @@ -6,12 +6,12 @@ var LocationUtils = require('../../utils/location'); var OUTPUT_EXTENSION = '.html'; /** - Convert a filePath (absolute) to a filename for output - - @param {Output} output - @param {String} filePath - @return {String} -*/ + * Convert a filePath (absolute) to a filename for output + * + * @param {Output} output + * @param {String} filePath + * @return {String} + */ function fileToOutput(output, filePath) { var book = output.getBook(); var readme = book.getReadme(); diff --git a/lib/output/helper/resolveFileToURL.js b/lib/output/helper/resolveFileToURL.js index 026b0e5..3f52713 100644 --- a/lib/output/helper/resolveFileToURL.js +++ b/lib/output/helper/resolveFileToURL.js @@ -3,12 +3,12 @@ var LocationUtils = require('../../utils/location'); var fileToURL = require('./fileToURL'); /** - Resolve an absolute path (extracted from a link) - - @param {Output} output - @param {String} filePath - @return {String} -*/ + * Resolve an absolute path (extracted from a link) + * + * @param {Output} output + * @param {String} filePath + * @return {String} + */ function resolveFileToURL(output, filePath) { // Convert /test.png -> test.png filePath = LocationUtils.toAbsolute(filePath, '', ''); diff --git a/lib/output/json/onPage.js b/lib/output/json/onPage.js index fece540..2315ba0 100644 --- a/lib/output/json/onPage.js +++ b/lib/output/json/onPage.js @@ -7,11 +7,11 @@ var getModifiers = require('../getModifiers'); var JSON_VERSION = '3'; /** - Write a page as a json file - - @param {Output} output - @param {Page} page -*/ + * Write a page as a json file + * + * @param {Output} output + * @param {Page} page + */ function onPage(output, page) { var file = page.getFile(); var readme = output.getBook().getReadme().getFile(); diff --git a/lib/output/modifiers/annotateText.js b/lib/output/modifiers/annotateText.js index 2b4b439..490c228 100644 --- a/lib/output/modifiers/annotateText.js +++ b/lib/output/modifiers/annotateText.js @@ -59,19 +59,18 @@ function replaceText($, el, search, replace, text_only ) { } /** - Annotate text using a list of GlossaryEntry - - @param {List<GlossaryEntry>} - @param {String} glossaryFilePath - @param {HTMLDom} $ -*/ + * Annotate text using a list of GlossaryEntry + * + * @param {List<GlossaryEntry>} + * @param {String} glossaryFilePath + * @param {HTMLDom} $ + */ function annotateText(entries, glossaryFilePath, $) { entries.forEach(function(entry) { - var entryId = entry.getID(); - var name = entry.getName(); + var entryId = entry.getID(); + var name = entry.getName(); var description = entry.getDescription(); - - var searchRegex = new RegExp( '\\b(' + pregQuote(name.toLowerCase()) + ')\\b' , 'gi' ); + var searchRegex = new RegExp( '\\b(' + pregQuote(name.toLowerCase()) + ')\\b' , 'gi' ); $('*').each(function() { var $this = $(this); diff --git a/lib/output/preparePlugins.js b/lib/output/preparePlugins.js index 54837ed..5c4be93 100644 --- a/lib/output/preparePlugins.js +++ b/lib/output/preparePlugins.js @@ -2,11 +2,11 @@ var Plugins = require('../plugins'); var Promise = require('../utils/promise'); /** - Load and setup plugins - - @param {Output} - @return {Promise<Output>} -*/ + * Load and setup plugins + * + * @param {Output} + * @return {Promise<Output>} + */ function preparePlugins(output) { var book = output.getBook(); diff --git a/lib/output/website/__tests__/i18n.js b/lib/output/website/__tests__/i18n.js new file mode 100644 index 0000000..fd610fb --- /dev/null +++ b/lib/output/website/__tests__/i18n.js @@ -0,0 +1,38 @@ +var createMockOutput = require('../../__tests__/createMock'); +var prepareI18n = require('../prepareI18n'); +var createTemplateEngine = require('../createTemplateEngine'); + +var WebsiteGenerator = require('../'); + +describe('i18n', function() { + it('should correctly use english as default language', function() { + return createMockOutput(WebsiteGenerator, { + 'README.md': 'Hello World' + }) + .then(function(output) { + return prepareI18n(output); + }) + .then(function(output) { + var engine = createTemplateEngine(output, 'README.md'); + var t = engine.getFilters().get('t'); + + expect(t('SUMMARY_INTRODUCTION')).toEqual('Introduction'); + }); + }); + + it('should correctly use language from book.json', function() { + return createMockOutput(WebsiteGenerator, { + 'README.md': 'Hello World', + 'book.json': JSON.stringify({ language: 'fr' }) + }) + .then(function(output) { + return prepareI18n(output); + }) + .then(function(output) { + var engine = createTemplateEngine(output, 'README.md'); + var t = engine.getFilters().get('t'); + + expect(t('GITBOOK_LINK')).toEqual('PubliƩ avec GitBook'); + }); + }); +}); diff --git a/lib/output/website/createTemplateEngine.js b/lib/output/website/createTemplateEngine.js index c60b3a1..02ec796 100644 --- a/lib/output/website/createTemplateEngine.js +++ b/lib/output/website/createTemplateEngine.js @@ -18,19 +18,19 @@ var fileToURL = require('../helper/fileToURL'); var resolveFileToURL = require('../helper/resolveFileToURL'); /** - Directory for a theme with the templates -*/ + * Directory for a theme with the templates + */ function templateFolder(dir) { return path.join(dir, templatesFolder); } /** - Create templating engine to render themes - - @param {Output} output - @param {String} currentFile - @return {TemplateEngine} -*/ + * Create templating engine to render themes + * + * @param {Output} output + * @param {String} currentFile + * @return {TemplateEngine} + */ function createTemplateEngine(output, currentFile) { var book = output.getBook(); var state = output.getState(); @@ -47,18 +47,17 @@ function createTemplateEngine(output, currentFile) { var loader = new Templating.ThemesLoader(tplSearchPaths); // Get languages - var language = config.get('language'); + var language = config.getValue('language'); // Create API context var context = Api.encodeGlobal(output); /** - Check if a file exists - - @param {String} fileName - @return {Boolean} - */ + * Check if a file exists + * @param {String} fileName + * @return {Boolean} + */ function fileExists(fileName) { if (!fileName) { return false; @@ -69,11 +68,10 @@ function createTemplateEngine(output, currentFile) { } /** - Return an article by its path - - @param {String} filePath - @return {Object|undefined} - */ + * Return an article by its path + * @param {String} filePath + * @return {Object|undefined} + */ function getArticleByPath(filePath) { var article = summary.getByPath(filePath); if (!article) return undefined; @@ -82,11 +80,10 @@ function createTemplateEngine(output, currentFile) { } /** - Return a page by its path - - @param {String} filePath - @return {Object|undefined} - */ + * Return a page by its path + * @param {String} filePath + * @return {Object|undefined} + */ function getPageByPath(filePath) { var page = output.getPage(filePath); if (!page) return undefined; @@ -94,7 +91,6 @@ function createTemplateEngine(output, currentFile) { return JSONUtils.encodePage(page, summary); } - return TemplateEngine.create({ loader: loader, @@ -108,17 +104,17 @@ function createTemplateEngine(output, currentFile) { filters: defaultFilters.merge({ /** - Translate a sentence - */ + * Translate a sentence + */ t: function t(s) { return i18n.t(language, s); }, /** - Resolve an absolute file path into a - relative path. - it also resolve pages - */ + * Resolve an absolute file path into a + * relative path. + * it also resolve pages + */ resolveFile: function(filePath) { filePath = resolveFileToURL(output, filePath); return LocationUtils.relativeForFile(currentFile, filePath); diff --git a/lib/output/website/onPage.js b/lib/output/website/onPage.js index 14c7b22..5fb40a7 100644 --- a/lib/output/website/onPage.js +++ b/lib/output/website/onPage.js @@ -12,11 +12,11 @@ var createTemplateEngine = require('./createTemplateEngine'); var fileToOutput = require('../helper/fileToOutput'); /** - Write a page as a json file - - @param {Output} output - @param {Page} page -*/ + * Write a page as a json file + * + * @param {Output} output + * @param {Page} page + */ function onPage(output, page) { var options = output.getOptions(); var prefix = options.get('prefix'); @@ -40,7 +40,7 @@ function onPage(output, page) { return Modifiers.modifyHTML(page, getModifiers(output, page)) .then(function(resultPage) { // Generate the context - var context = JSONUtils.encodeBookWithPage(output.getBook(), resultPage); + var context = JSONUtils.encodeOutputWithPage(output, resultPage); context.plugins = { resources: Plugins.listResources(plugins, resources).toJS() }; diff --git a/lib/output/website/prepareI18n.js b/lib/output/website/prepareI18n.js index b57d178..cedd3b9 100644 --- a/lib/output/website/prepareI18n.js +++ b/lib/output/website/prepareI18n.js @@ -5,11 +5,11 @@ var Promise = require('../../utils/promise'); var listSearchPaths = require('./listSearchPaths'); /** - Prepare i18n, load translations from plugins and book - - @param {Output} - @return {Promise<Output>} -*/ + * Prepare i18n, load translations from plugins and book + * + * @param {Output} + * @return {Promise<Output>} + */ function prepareI18n(output) { var state = output.getState(); var i18n = state.getI18n(); diff --git a/lib/utils/__tests__/location.js b/lib/utils/__tests__/location.js index 2d01714..822338e 100644 --- a/lib/utils/__tests__/location.js +++ b/lib/utils/__tests__/location.js @@ -39,6 +39,21 @@ describe('LocationUtils', function() { }); }); + describe('.flatten', function() { + it('should remove leading slash', function() { + expect(LocationUtils.flatten('/test.md')).toBe('test.md'); + expect(LocationUtils.flatten('/hello/cool.md')).toBe('hello/cool.md'); + }); + + it('should remove leading slashes', function() { + expect(LocationUtils.flatten('///test.md')).toBe('test.md'); + }); + + it('should not break paths', function() { + expect(LocationUtils.flatten('hello/cool.md')).toBe('hello/cool.md'); + }); + }); + describe('.toAbsolute', function() { it('should correctly transform as absolute', function() { expect(LocationUtils.toAbsolute('http://google.fr')).toBe('http://google.fr'); diff --git a/lib/utils/location.js b/lib/utils/location.js index 17edc00..00d8004 100644 --- a/lib/utils/location.js +++ b/lib/utils/location.js @@ -40,13 +40,28 @@ function normalize(s) { } /** - Convert a relative path to absolute + * Flatten a path, it removes the leading "/" + * + * @param {String} href + * @return {String} + */ +function flatten(href) { + href = normalize(href); + if (href[0] == '/') { + href = normalize(href.slice(1)); + } + + return href; +} - @param {String} href - @param {String} dir: directory parent of the file currently in rendering process - @param {String} outdir: directory parent from the html output - @return {String} -*/ +/** + * Convert a relative path to absolute + * + * @param {String} href + * @param {String} dir: directory parent of the file currently in rendering process + * @param {String} outdir: directory parent from the html output + * @return {String} + */ function toAbsolute(_href, dir, outdir) { if (isExternal(_href) || isDataURI(_href)) { return _href; @@ -74,50 +89,51 @@ function toAbsolute(_href, dir, outdir) { } /** - Convert an absolute path to a relative path for a specific folder (dir) - ('test/', 'hello.md') -> '../hello.md' - - @param {String} dir: current directory - @param {String} file: absolute path of file - @return {String} -*/ + * Convert an absolute path to a relative path for a specific folder (dir) + * ('test/', 'hello.md') -> '../hello.md' + * + * @param {String} dir: current directory + * @param {String} file: absolute path of file + * @return {String} + */ function relative(dir, file) { var isDirectory = file.slice(-1) === '/'; return normalize(path.relative(dir, file)) + (isDirectory? '/': ''); } /** - Convert an absolute path to a relative path for a specific folder (dir) - ('test/test.md', 'hello.md') -> '../hello.md' - - @param {String} baseFile: current file - @param {String} file: absolute path of file - @return {String} -*/ + * Convert an absolute path to a relative path for a specific folder (dir) + * ('test/test.md', 'hello.md') -> '../hello.md' + * + * @param {String} baseFile: current file + * @param {String} file: absolute path of file + * @return {String} + */ function relativeForFile(baseFile, file) { return relative(path.dirname(baseFile), file); } /** - Compare two paths, return true if they are identical - ('README.md', './README.md') -> true - - @param {String} p1: first path - @param {String} p2: second path - @return {Boolean} -*/ + * Compare two paths, return true if they are identical + * ('README.md', './README.md') -> true + * + * @param {String} p1: first path + * @param {String} p2: second path + * @return {Boolean} + */ function areIdenticalPaths(p1, p2) { return normalize(p1) === normalize(p2); } module.exports = { areIdenticalPaths: areIdenticalPaths, - isDataURI: isDataURI, - isExternal: isExternal, - isRelative: isRelative, - isAnchor: isAnchor, - normalize: normalize, - toAbsolute: toAbsolute, - relative: relative, - relativeForFile: relativeForFile + isDataURI: isDataURI, + isExternal: isExternal, + isRelative: isRelative, + isAnchor: isAnchor, + normalize: normalize, + toAbsolute: toAbsolute, + relative: relative, + relativeForFile: relativeForFile, + flatten: flatten }; diff --git a/lib/utils/promise.js b/lib/utils/promise.js index 138546b..b5cca4b 100644 --- a/lib/utils/promise.js +++ b/lib/utils/promise.js @@ -2,34 +2,35 @@ var Q = require('q'); var Immutable = require('immutable'); // Debugging for long stack traces -if (global.__DEV__ || process.env.DEBUG) { +if (process.env.DEBUG || process.env.CI) { Q.longStackSupport = true; } /** - Reduce an array to a promise - - @param {Array|List} arr - @param {Function(value, element, index)} - @return {Promise<Mixed>} -*/ + * Reduce an array to a promise + * + * @param {Array|List} arr + * @param {Function(value, element, index)} + * @return {Promise<Mixed>} + */ function reduce(arr, iter, base) { arr = Immutable.Iterable.isIterable(arr)? arr : Immutable.List(arr); return arr.reduce(function(prev, elem, key) { - return prev.then(function(val) { + return prev + .then(function(val) { return iter(val, elem, key); }); }, Q(base)); } /** - Iterate over an array using an async iter - - @param {Array|List} arr - @param {Function(value, element, index)} - @return {Promise} -*/ + * Iterate over an array using an async iter + * + * @param {Array|List} arr + * @param {Function(value, element, index)} + * @return {Promise} + */ function forEach(arr, iter) { return reduce(arr, function(val, el, key) { return iter(el, key); @@ -37,12 +38,12 @@ function forEach(arr, iter) { } /** - Transform an array - - @param {Array|List} arr - @param {Function(value, element, index)} - @return {Promise} -*/ + * Transform an array + * + * @param {Array|List} arr + * @param {Function(value, element, index)} + * @return {Promise} + */ function serie(arr, iter, base) { return reduce(arr, function(before, item, key) { return Q(iter(item, key)) @@ -54,12 +55,12 @@ function serie(arr, iter, base) { } /** - Iter over an array and return first result (not null) - - @param {Array|List} arr - @param {Function(element, index)} - @return {Promise<Mixed>} -*/ + * Iter over an array and return first result (not null) + * + * @param {Array|List} arr + * @param {Function(element, index)} + * @return {Promise<Mixed>} + */ function some(arr, iter) { arr = Immutable.List(arr); @@ -73,12 +74,12 @@ function some(arr, iter) { } /** - Map an array using an async (promised) iterator - - @param {Array|List} arr - @param {Function(element, index)} - @return {Promise<List>} -*/ + * Map an array using an async (promised) iterator + * + * @param {Array|List} arr + * @param {Function(element, index)} + * @return {Promise<List>} + */ function mapAsList(arr, iter) { return reduce(arr, function(prev, entry, i) { return Q(iter(entry, i)) @@ -90,12 +91,12 @@ function mapAsList(arr, iter) { } /** - Map an array or map - - @param {Array|List|Map|OrderedMap} arr - @param {Function(element, key)} - @return {Promise<List|Map|OrderedMap>} -*/ + * Map an array or map + * + * @param {Array|List|Map|OrderedMap} arr + * @param {Function(element, key)} + * @return {Promise<List|Map|OrderedMap>} + */ function map(arr, iter) { if (Immutable.Map.isMap(arr)) { var type = 'Map'; @@ -122,11 +123,11 @@ function map(arr, iter) { /** - Wrap a function in a promise - - @param {Function} func - @return {Funciton} -*/ + * Wrap a function in a promise + * + * @param {Function} func + * @return {Funciton} + */ function wrap(func) { return function() { var args = Array.prototype.slice.call(arguments, 0); diff --git a/lib/utils/reducedObject.js b/lib/utils/reducedObject.js index fa5d32c..7bcfd5b 100644 --- a/lib/utils/reducedObject.js +++ b/lib/utils/reducedObject.js @@ -4,8 +4,13 @@ var Immutable = require('immutable'); * Reduce the difference between a map and its default version * @param {Map} defaultVersion * @param {Map} currentVersion + * @return {Map} The properties of currentVersion that differs from defaultVersion */ function reducedObject(defaultVersion, currentVersion) { + if(defaultVersion === undefined) { + return currentVersion; + } + return currentVersion.reduce(function(result, value, key) { var defaultValue = defaultVersion.get(key); diff --git a/package.json b/package.json index b91620e..04be84b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitbook", - "version": "3.1.1", + "version": "3.2.1", "homepage": "https://www.gitbook.com", "description": "Library and cmd utility to generate GitBooks", "main": "lib/index.js", @@ -22,14 +22,14 @@ "fresh-require": "1.0.3", "front-matter": "^2.1.0", "gitbook-asciidoc": "1.2.2", - "gitbook-markdown": "1.3.2", + "gitbook-markdown": "2.0.1", "gitbook-plugin-fontsettings": "2.0.0", "gitbook-plugin-highlight": "2.0.2", "gitbook-plugin-livereload": "0.0.1", - "gitbook-plugin-lunr": "1.1.0", + "gitbook-plugin-lunr": "1.2.0", "gitbook-plugin-search": "2.2.1", "gitbook-plugin-sharing": "1.0.2", - "gitbook-plugin-theme-default": "1.0.4", + "gitbook-plugin-theme-default": "1.0.6", "github-slugid": "1.0.1", "graceful-fs": "4.1.4", "i18n-t": "1.0.1", @@ -44,10 +44,11 @@ "moment": "2.13.0", "npm": "3.9.2", "npmi": "2.0.1", - "nunjucks": "2.4.2", + "nunjucks": "2.5.2", "nunjucks-do": "1.0.0", "object-path": "^0.9.2", "omit-keys": "^0.1.0", + "open": "0.0.5", "q": "1.4.1", "read-installed": "^4.0.3", "request": "2.72.0", |