mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-26 12:18:01 +00:00
Compare commits
214 Commits
3.0.0-pre.11
...
3.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 33d7e022e6 | |||
| 09782b964b | |||
| 7938ee8ec6 | |||
| 2e8e4f63d7 | |||
| b9fa6c3051 | |||
| 9c071dade5 | |||
| 56e44651fa | |||
| 42fac98992 | |||
| 4f5513add0 | |||
| 90cbfc0596 | |||
| 38ab9fa05a | |||
| 12e20c5fcc | |||
| 6b103f4532 | |||
| d4fa5c18a8 | |||
| 2a116dfdb0 | |||
| 0349f54f3c | |||
| 695bda312c | |||
| 144bd4c97a | |||
| e105983cf8 | |||
| ea3afbed75 | |||
| d563abe3df | |||
| 70fd4ded29 | |||
| 9ce9388dfc | |||
| c575ad83ae | |||
| 936fa1545b | |||
| c9c2dde2b6 | |||
| d9a1d387c7 | |||
| bf29d187c8 | |||
| d86f9b2667 | |||
| aca4313fc3 | |||
| 4cb5bb06fb | |||
| 24988b75ea | |||
| 775e858d05 | |||
| 1e3d871c1e | |||
| 94a4310c1e | |||
| e8ee57be83 | |||
| 50a132cb2c | |||
| 59be32a54f | |||
| 4ae7cdb5d1 | |||
| 990a4efeb5 | |||
| 9c927dcf13 | |||
| 7a08b3f109 | |||
| 94d4234cb8 | |||
| fbc851bc4e | |||
| e92b661a2a | |||
| b9fe60915f | |||
| 1bf7d092f0 | |||
| 826d472511 | |||
| a6b1f4bd87 | |||
| 004f91f02c | |||
| e6c6ec4293 | |||
| a1d3acf065 | |||
| a732660f59 | |||
| e1bb0638f8 | |||
| 788c0c7e7e | |||
| 762fe16a17 | |||
| 15123805d1 | |||
| 23aecb4f2b | |||
| f1d200b81b | |||
| b566711ee1 | |||
| 22da5aac11 | |||
| f7b95102c5 | |||
| ca70c934d6 | |||
| 1cc48fa314 | |||
| b63b5574d3 | |||
| 67106e8698 | |||
| dc2ab51a88 | |||
| cedfa186f2 | |||
| 80f0d1bfed | |||
| 7178ab1f82 | |||
| bf895b707e | |||
| 996170a4ad | |||
| aa66a4f928 | |||
| 23010fa54a | |||
| 0baebc7979 | |||
| 257158321c | |||
| ddf0a41e29 | |||
| 357e1e8e96 | |||
| 7a89f9869a | |||
| 30fbd22efe | |||
| 29df73ba72 | |||
| f8676483c1 | |||
| c79a6f2fba | |||
| c38e67ea61 | |||
| 97a5f8805e | |||
| 421fef6ebf | |||
| 33406c3082 | |||
| c3851889b0 | |||
| 4a5de2da6b | |||
| 1afb23cada | |||
| a3b0f3f868 | |||
| 0aa32916d6 | |||
| ebe845d46c | |||
| edee8d2fc5 | |||
| 5f3de0876f | |||
| 2ceab04d01 | |||
| de37de44e8 | |||
| ccb87d5a13 | |||
| e2e0ca3f05 | |||
| b33a79704b | |||
| 6def174b84 | |||
| 346a18b444 | |||
| b32685c698 | |||
| 09b3618da8 | |||
| 901328f247 | |||
| b47ebaa9d9 | |||
| 465e8d6c0a | |||
| ecd86ec877 | |||
| 5d7864154c | |||
| 7b915428f7 | |||
| 0bce3517fd | |||
| 089815a8fd | |||
| 7e8f1a9632 | |||
| 8469648241 | |||
| e7a44358a5 | |||
| fb930d889f | |||
| 721a75309b | |||
| b856bff91b | |||
| 0744f000f9 | |||
| d1e226dba5 | |||
| 1923d28d71 | |||
| 484a306adb | |||
| 5bfc9f2f56 | |||
| 86a65e5287 | |||
| 956d301dd7 | |||
| 00d1a7b5a6 | |||
| 2718c9c017 | |||
| 3234c1e03f | |||
| c8f8eece92 | |||
| 7394efa9dc | |||
| 3e4241fc88 | |||
| a5ae79604c | |||
| fc4bdd68a1 | |||
| f83975b022 | |||
| 167d3957bf | |||
| a9571fd67d | |||
| ac87f48ba5 | |||
| 05403f527d | |||
| 53c72f0806 | |||
| c8c2a61f96 | |||
| 08d780d1f6 | |||
| 2e74fafa36 | |||
| 28ac9ab88a | |||
| 26acab4dfb | |||
| d4b562e001 | |||
| 61956e0646 | |||
| 7892259fe6 | |||
| b97d1c7796 | |||
| a091c5b331 | |||
| 784b3379b5 | |||
| 62446e75d2 | |||
| 8aa2f3075c | |||
| 839ee0385a | |||
| d6b60c6116 | |||
| 9013f02850 | |||
| 961d43f085 | |||
| 7bd49606e3 | |||
| d7c8635350 | |||
| 19e9ff81d2 | |||
| e137bf632a | |||
| 2fef898a1e | |||
| 0945807184 | |||
| d85663b153 | |||
| 481a7d790c | |||
| ef589a6b13 | |||
| d5c4af3377 | |||
| 0b4df4cdf2 | |||
| d6814dc48e | |||
| 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 |
@@ -13,8 +13,10 @@
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"jest": true,
|
||||
"jasmine": true
|
||||
"mocha": true
|
||||
},
|
||||
"globals": {
|
||||
"expect": true
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,5 +1,8 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
node_js:
|
||||
- "stable"
|
||||
- "4.1"
|
||||
|
||||
+36
-2
@@ -2,7 +2,41 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 3.0.0 (pre)
|
||||
## 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
|
||||
- Fix target attribute for external links
|
||||
- Fix serve command
|
||||
|
||||
## 3.1.0
|
||||
- Always load themes after plugins
|
||||
- README/Introduction doesn't require to be the first entry in the summary
|
||||
- Highlight active entry in summary when scrolling a page with anchors
|
||||
- Support empty parts in summary
|
||||
|
||||
## 3.0.3
|
||||
- Fix redirection in `gitbook serve` when accessing a folder without trailing slash
|
||||
- Fix links/annotations for glossary not being correctly resolved
|
||||
- Fix regression for supporting uppercase filenames in structure files
|
||||
- Fix `gitbook install` when using Git URLs for plugins
|
||||
|
||||
## 3.0.2
|
||||
- Fix crash for anchor links
|
||||
|
||||
## 3.0.1
|
||||
- Fix regression in link resolution when contain anchor
|
||||
- Fix `structure.<name>` configuration not supporting filenames with dots
|
||||
|
||||
## 3.0.0
|
||||
- Summary can contain external links and anchors (Fix [#776](https://github.com/GitbookIO/gitbook/issues/776))
|
||||
- Summary can contain differents entitled sections
|
||||
- Glossary is generated as a normal page
|
||||
@@ -11,7 +45,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- `links.sidebar` configuration is no longer supported, use summary sections instead
|
||||
- `pdf.headerTemplate` and `pdf.footerTemplate` have been replaced by a template in theme/book: `_layout/ebook/pdf_header.html` and `_layout/ebook/pdf_footer.html`
|
||||
- Markdown parser is now using CommonMark
|
||||
- Root folder for the book can be specified in a `.gitbook` file
|
||||
- Root folder for the book can be specified in the `"root"` property of the `book.json` file
|
||||
- Multi-lingual books share assets folder
|
||||
- YAML front matter is parsed and can extend page's properties
|
||||
- Fix `uk` translation
|
||||
|
||||
@@ -5,13 +5,18 @@ init:
|
||||
# Test against these versions of Node.js.
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "6"
|
||||
- nodejs_version: "5"
|
||||
- nodejs_version: "4"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
# Install scripts. (runs after repo cloning)
|
||||
install:
|
||||
# Get the latest stable version of Node.js or io.js
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- set CI=true
|
||||
# install svgexport
|
||||
- npm install svgexport -g
|
||||
# install modules
|
||||
@@ -27,3 +32,7 @@ test_script:
|
||||
|
||||
# Don't actually build.
|
||||
build: off
|
||||
|
||||
# Clone only last commit
|
||||
shallow_clone: true
|
||||
clone_depth: 1
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-3
@@ -7,6 +7,7 @@ GitBook allows you to customize your book using a flexible configuration. These
|
||||
| Variable | Description |
|
||||
| -------- | ----------- |
|
||||
| `root` | Path to the root folder containing all the book's files, except `book.json`|
|
||||
| `structure` | To specify paths for Readme, Summary, Glossary etc. See [Structure paragraph](#structure). |
|
||||
| `title` | Title of your book, default value is extracted from the README. On GitBook.com this field is pre-filled. |
|
||||
| `description` | Description of your book, default value is extracted from the README. On GitBook.com this field is pre-filled. |
|
||||
| `author` | Name of the author. On GitBook.com this field is pre-filled. |
|
||||
@@ -19,18 +20,24 @@ GitBook allows you to customize your book using a flexible configuration. These
|
||||
|
||||
Plugins and their configurations are specified in the `book.json`. See [the plugins section](plugins/README.md) for more details.
|
||||
|
||||
Since version 3.0.0, GitBook can use themes. See [the theming section](themes/README.md) for more details.
|
||||
|
||||
| Variable | Description |
|
||||
| -------- | ----------- |
|
||||
| `plugins` | List of plugins to load |
|
||||
| `pluginsConfig` |Configuration for plugins |
|
||||
|
||||
### Theme
|
||||
### Structure
|
||||
|
||||
Since version 3.0.0, GitBook can use themes. See [the theming section](themes/README.md) for more details.
|
||||
In addition to the `root` variable, you can tell Gitbook the name of the files for Readme, Summary, Glossary, Languages (instead of using the default names such as `README.md`).
|
||||
These files must be at the root of your book (or the root of every language book). Paths such as `dir/MY_README.md` are not accepted.
|
||||
|
||||
| Variable | Description |
|
||||
| -------- | ----------- |
|
||||
| `theme` | The theme to use for the book |
|
||||
| `structure.readme` | Readme file name (defaults to `README.md`) |
|
||||
| `structure.summary` | Summary file name (defaults to `SUMMARY.md`) |
|
||||
| `structure.glossary` | Glossary file name (defaults to `GLOSSARY.md`) |
|
||||
| `structure.languages` | Languages file name (defaults to `LANGS.md`) |
|
||||
|
||||
### PDF Options
|
||||
|
||||
|
||||
+4
-3
@@ -29,12 +29,13 @@ You can replace `/usr/bin` with any directory that is in your $PATH.
|
||||
|
||||
### Cover
|
||||
|
||||
Covers are used for all the ebook formats. It's an important part of an ebook brandline.
|
||||
Covers are used for all the ebook formats. You can either provide one yourself, or generate one using the [autocover plugin](https://plugins.gitbook.com/plugin/autocover).
|
||||
|
||||
To provide a cover, place a **`cover.jpg`** file at the root directory of your book. Adding a **`cover_small.jpg`** will specify a smaller version of the cover. The cover should be a **JPEG** file.
|
||||
|
||||
A good cover should respect the following guidelines:
|
||||
|
||||
* Size of 1800x2360 (pixels)
|
||||
* Size of 1800x2360 pixels for `cover.jpg`, 200x262 for `cover_small.jpg`
|
||||
* No border
|
||||
* Clearly visible book title
|
||||
* Any important text should be visible in the small version
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@
|
||||
|
||||
Allows you to specify terms and their respective definitions to be displayed as annotations. Based on those terms, GitBook will automatically build an index and highlight those terms in pages.
|
||||
|
||||
The `GLOSSARY.md` format is very simple :
|
||||
The `GLOSSARY.md` format is a list of `h2` headings, along with a description paragraph:
|
||||
|
||||
```markdown
|
||||
# Term
|
||||
## Term
|
||||
Definition for this term
|
||||
|
||||
# Another term
|
||||
## Another term
|
||||
With it's definition, this can contain bold text
|
||||
and all other kinds of inline markup ...
|
||||
```
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ var filepath = output.resolve('myimage.png');
|
||||
var fileurl = output.toURL('mychapter/README.md');
|
||||
|
||||
// Write a file in the output folder
|
||||
output.write('hello.txt', 'Hello World')
|
||||
output.writeFile('hello.txt', 'Hello World')
|
||||
.then(function() { ... });
|
||||
|
||||
// Copy a file to the output folder
|
||||
|
||||
@@ -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>
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ It integrates well with the [GitBook Editor](https://www.gitbook.com/editor).
|
||||
|
||||
Installing GitBook is easy and straightforward. Your system just needs to meet these two requirements:
|
||||
|
||||
* NodeJS (v4.0.0 and above are adviced)
|
||||
* NodeJS (v4.0.0 and above is recommended)
|
||||
* Windows, Linux, Unix, or Mac OS X
|
||||
|
||||
##### Install with NPM
|
||||
|
||||
@@ -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 ;
|
||||
}
|
||||
@@ -51,5 +51,5 @@ This is the default content
|
||||
|
||||
# License
|
||||
|
||||
{% import "./LICENSE" %}
|
||||
{% include "./LICENSE" %}
|
||||
```
|
||||
|
||||
Vendored
+16
-1
@@ -17,7 +17,22 @@ A theme is a plugin containing templates and assets. Overriding any individual t
|
||||
|
||||
### Extend/Customize theme in a book
|
||||
|
||||
Authors can extend the templates of a theme directly from their book's source (without creating an external theme). Templates will be resolved in the `_layouts` folder of the book first, then in
|
||||
Authors can extend the templates of a theme directly from their book's source (without creating an external theme). Templates will be resolved in the `_layouts` folder of the book first, then in the installed plugins/themes.
|
||||
|
||||
### Extend instead of Forking
|
||||
|
||||
When you want to make your theme changes available to multiple books, instead of forking the default theme, you can extend it using the [templating syntax](../templating/README.md):
|
||||
|
||||
```html
|
||||
{% extends template.self %}
|
||||
|
||||
{% block body %}
|
||||
{{ super() }}
|
||||
... This will be added to the "body" block
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
Take a look at the [API](https://github.com/GitbookIO/theme-api) theme for a more complete example.
|
||||
|
||||
### Publish a theme
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
var matchers = {
|
||||
/**
|
||||
Verify that a file exists in a directory
|
||||
*/
|
||||
toHaveFile: function () {
|
||||
return {
|
||||
compare: function (actual, expected) {
|
||||
var filePath = path.join(actual, expected);
|
||||
var exists = fs.existsSync(filePath);
|
||||
|
||||
return {
|
||||
pass: exists
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.getEnv().beforeEach(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
|
||||
jasmine.addMatchers(matchers);
|
||||
});
|
||||
@@ -3,7 +3,7 @@ var initBook = require('../init');
|
||||
|
||||
describe('initBook', function() {
|
||||
|
||||
pit('should create a README and SUMMARY for empty book', function() {
|
||||
it('should create a README and SUMMARY for empty book', function() {
|
||||
var dir = tmp.dirSync();
|
||||
|
||||
return initBook(dir.name)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
var Config = require('../models/config');
|
||||
|
||||
/**
|
||||
Decode changes from a JS API to a config object
|
||||
|
||||
@@ -13,7 +11,7 @@ function decodeGlobal(config, result) {
|
||||
delete values.generator;
|
||||
delete values.output;
|
||||
|
||||
return Config.updateValues(config, values);
|
||||
return config.updateValues(values);
|
||||
}
|
||||
|
||||
module.exports = decodeGlobal;
|
||||
|
||||
+105
-2
@@ -1,14 +1,19 @@
|
||||
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 parsers = require('../parsers');
|
||||
|
||||
var encodeConfig = require('./encodeConfig');
|
||||
var encodeSummary = require('./encodeSummary');
|
||||
var encodeNavigation = require('./encodeNavigation');
|
||||
var encodePage = require('./encodePage');
|
||||
|
||||
/**
|
||||
Encode a global context into a JS object
|
||||
@@ -28,6 +33,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 +73,57 @@ 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);
|
||||
},
|
||||
|
||||
/**
|
||||
Resolve a page by it path
|
||||
|
||||
@param {String} filePath
|
||||
@return {String}
|
||||
*/
|
||||
getPageByPath: function(filePath) {
|
||||
var page = output.getPage(filePath);
|
||||
if (!page) return undefined;
|
||||
|
||||
return encodePage(output, page);
|
||||
},
|
||||
|
||||
/**
|
||||
Render a block of text (markdown/asciidoc)
|
||||
|
||||
@param {String} type
|
||||
@param {String} text
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
renderBlock: function(type, text) {
|
||||
var parser = parsers.get(type);
|
||||
|
||||
return parser.parsePage(text)
|
||||
.get('content');
|
||||
},
|
||||
|
||||
/**
|
||||
Render an inline text (markdown/asciidoc)
|
||||
|
||||
@param {String} type
|
||||
@param {String} text
|
||||
@return {Promise<String>}
|
||||
*/
|
||||
renderInline: function(type, text) {
|
||||
var parser = parsers.get(type);
|
||||
|
||||
return parser.parseInline(text)
|
||||
.get('content');
|
||||
},
|
||||
|
||||
template: {
|
||||
/**
|
||||
Apply a templating block and returns its result
|
||||
@@ -96,6 +153,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}
|
||||
@@ -104,6 +171,21 @@ function encodeGlobal(output) {
|
||||
return fileToURL(output, filePath);
|
||||
},
|
||||
|
||||
/**
|
||||
Check that a file exists.
|
||||
|
||||
@param {String} fileName
|
||||
@return {Promise}
|
||||
*/
|
||||
hasFile: function(fileName, content) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
|
||||
|
||||
return fs.exists(filePath);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Write a file to the output folder,
|
||||
It creates the required folder
|
||||
@@ -122,6 +204,27 @@ function encodeGlobal(output) {
|
||||
return fs.writeFile(filePath, content);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Copy a file to the output folder
|
||||
It creates the required folder.
|
||||
|
||||
@param {String} inputFile
|
||||
@param {String} outputFile
|
||||
@param {Buffer} content
|
||||
@return {Promise}
|
||||
*/
|
||||
copyFile: function(inputFile, outputFile, content) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
var outputFilePath = PathUtils.resolveInRoot(outputFolder, outputFile);
|
||||
|
||||
return fs.ensureFile(outputFilePath)
|
||||
.then(function() {
|
||||
return fs.copy(inputFile, outputFilePath);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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;
|
||||
+13
-2
@@ -6,10 +6,21 @@ 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'),
|
||||
Page: require('./models/page'),
|
||||
PluginDependency: require('./models/pluginDependency'),
|
||||
|
||||
// Modifiers
|
||||
SummaryModifier: Modifiers.Summary,
|
||||
ConfigModifier: Modifiers.Config
|
||||
SummaryModifier: Modifiers.Summary,
|
||||
ConfigModifier: Modifiers.Config,
|
||||
|
||||
// Constants
|
||||
CONFIG_FILES: require('./constants/configFiles.js'),
|
||||
IGNORE_FILES: require('./constants/ignoreFiles.js'),
|
||||
DEFAULT_PLUGINS: require('./constants/defaultPlugins'),
|
||||
EXTENSIONS_MARKDOWN: require('./constants/extsMarkdown'),
|
||||
EXTENSIONS_ASCIIDOC: require('./constants/extsAsciidoc')
|
||||
};
|
||||
|
||||
@@ -18,8 +18,10 @@ module.exports = function(format) {
|
||||
options.log
|
||||
],
|
||||
exec: function(args, kwargs) {
|
||||
var extension = '.' + format;
|
||||
|
||||
// Output file will be stored in
|
||||
var outputFile = args[1] || ('book.' + format);
|
||||
var outputFile = args[1] || ('book' + extension);
|
||||
|
||||
// Create temporary directory
|
||||
var outputFolder = tmp.dirSync().name;
|
||||
@@ -42,23 +44,23 @@ module.exports = function(format) {
|
||||
var languages = book.getLanguages();
|
||||
|
||||
if (book.isMultilingual()) {
|
||||
return Promise.ForEach(languages, function(lang) {
|
||||
return Promise.forEach(languages.getList(), function(lang) {
|
||||
var langID = lang.getID();
|
||||
|
||||
var langOutputFile = path.join(
|
||||
path.dirname(outputFile),
|
||||
path.basename(outputFile, format) + '_' + langID + '.' + format
|
||||
path.basename(outputFile, extension) + '_' + langID + extension
|
||||
);
|
||||
|
||||
return fs.copy(
|
||||
path.resolve(outputFolder, langID, 'index.' + format),
|
||||
path.resolve(outputFolder, langID, 'index' + extension),
|
||||
langOutputFile
|
||||
);
|
||||
})
|
||||
.thenResolve(languages.getCount());
|
||||
} else {
|
||||
return fs.copy(
|
||||
path.resolve(outputFolder, 'index.' + format),
|
||||
path.resolve(outputFolder, 'index' + extension),
|
||||
outputFile
|
||||
).thenResolve(1);
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
+64
-10
@@ -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');
|
||||
@@ -16,11 +17,27 @@ 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 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');
|
||||
@@ -29,8 +46,12 @@ 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
|
||||
var config = resultBook.getConfig();
|
||||
config = ConfigModifier.addPlugin(config, 'livereload');
|
||||
resultBook = resultBook.set('config', config);
|
||||
}
|
||||
|
||||
return Output.generate(Generator, resultBook, {
|
||||
root: outputFolder
|
||||
@@ -45,7 +66,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: {
|
||||
@@ -53,9 +74,15 @@ function generateBook(args, kwargs) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (hasOpen) {
|
||||
open('http://localhost:'+port, browser);
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
if (!kwargs.watch) return;
|
||||
if (!hasWatch) {
|
||||
return waitForCtrlC();
|
||||
}
|
||||
|
||||
return watch(book.getRoot())
|
||||
.then(function(filepath) {
|
||||
@@ -84,21 +111,48 @@ 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
|
||||
},
|
||||
{
|
||||
name: 'open',
|
||||
description: 'Enable opening book in browser',
|
||||
defaults: false
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
description: 'Specify browser for opening book',
|
||||
defaults: ''
|
||||
},
|
||||
options.log,
|
||||
options.format
|
||||
],
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
+21
-2
@@ -71,11 +71,18 @@ Server.prototype.start = function(dir, port) {
|
||||
|
||||
// Redirect to directory's index.html
|
||||
function redirect() {
|
||||
var resultURL = urlTransform(req.url, function(parsed) {
|
||||
parsed.pathname += '/';
|
||||
return parsed;
|
||||
});
|
||||
|
||||
res.statusCode = 301;
|
||||
res.setHeader('Location', req.url + '/');
|
||||
res.end('Redirecting to ' + req.url + '/');
|
||||
res.setHeader('Location', resultURL);
|
||||
res.end('Redirecting to ' + resultURL);
|
||||
}
|
||||
|
||||
res.setHeader('X-Current-Location', req.url);
|
||||
|
||||
// Send file
|
||||
send(req, url.parse(req.url).pathname, {
|
||||
root: dir
|
||||
@@ -106,4 +113,16 @@ Server.prototype.start = function(dir, port) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
urlTransform is a helper function that allows a function to transform
|
||||
a url string in it's parsed form and returns the new url as a string
|
||||
|
||||
@param {String} uri
|
||||
@param {Function} fn
|
||||
@return {String}
|
||||
*/
|
||||
function urlTransform(uri, fn) {
|
||||
return url.format(fn(url.parse(uri)));
|
||||
}
|
||||
|
||||
module.exports = Server;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
var jsonschema = require('jsonschema');
|
||||
var schema = require('../configSchema');
|
||||
|
||||
describe('configSchema', function() {
|
||||
|
||||
function validate(cfg) {
|
||||
var v = new jsonschema.Validator();
|
||||
return v.validate(cfg, schema, {
|
||||
propertyName: 'config'
|
||||
});
|
||||
}
|
||||
|
||||
describe('structure', function() {
|
||||
|
||||
it('should accept dot in filename', function() {
|
||||
var result = validate({
|
||||
structure: {
|
||||
readme: 'book-intro.adoc'
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should accept uppercase in filename', function() {
|
||||
var result = validate({
|
||||
structure: {
|
||||
readme: 'BOOK.adoc'
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not accept filepath', function() {
|
||||
var result = validate({
|
||||
structure: {
|
||||
readme: 'folder/myFile.md'
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.errors.length).toBe(1);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
var FILENAME_REGEX = '^[a-zA-Z-._\d,\s]+$';
|
||||
|
||||
module.exports = {
|
||||
'$schema': 'http://json-schema.org/schema#',
|
||||
'id': 'https://gitbook.com/schemas/book.json',
|
||||
@@ -16,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'
|
||||
@@ -57,25 +63,25 @@ module.exports = {
|
||||
'default': 'LANGS.md',
|
||||
'type': 'string',
|
||||
'title': 'File to use as languages index',
|
||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
||||
'pattern': FILENAME_REGEX
|
||||
},
|
||||
'readme': {
|
||||
'default': 'README.md',
|
||||
'type': 'string',
|
||||
'title': 'File to use as preface',
|
||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
||||
'pattern': FILENAME_REGEX
|
||||
},
|
||||
'glossary': {
|
||||
'default': 'GLOSSARY.md',
|
||||
'type': 'string',
|
||||
'title': 'File to use as glossary index',
|
||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
||||
'pattern': FILENAME_REGEX
|
||||
},
|
||||
'summary': {
|
||||
'default': 'SUMMARY.md',
|
||||
'type': 'string',
|
||||
'title': 'File to use as table of contents',
|
||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
||||
'pattern': FILENAME_REGEX
|
||||
}
|
||||
},
|
||||
'additionalProperties': false
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
var Immutable = require('immutable');
|
||||
var PluginDependency = require('../models/pluginDependency');
|
||||
|
||||
var pkg = require('../../package.json');
|
||||
|
||||
/**
|
||||
* Create a PluginDependency from a dependency of gitbook
|
||||
* @param {String} pluginName
|
||||
* @return {PluginDependency}
|
||||
*/
|
||||
function createFromDependency(pluginName) {
|
||||
var npmID = PluginDependency.nameToNpmID(pluginName);
|
||||
var version = pkg.dependencies[npmID];
|
||||
|
||||
return PluginDependency.create(pluginName, version);
|
||||
}
|
||||
|
||||
/*
|
||||
List of default plugins for all books,
|
||||
default plugins should be installed in node dependencies of GitBook
|
||||
*/
|
||||
* List of default plugins for all books,
|
||||
* default plugins should be installed in node dependencies of GitBook
|
||||
*/
|
||||
module.exports = Immutable.List([
|
||||
'highlight',
|
||||
'search',
|
||||
@@ -11,4 +26,4 @@ module.exports = Immutable.List([
|
||||
'sharing',
|
||||
'fontsettings',
|
||||
'theme-default'
|
||||
]);
|
||||
]).map(createFromDependency);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
'.adoc',
|
||||
'.asciidoc'
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = [
|
||||
'.md',
|
||||
'.markdown',
|
||||
'.mdown'
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
All GitBook themes plugins name start with this prefix once shorted.
|
||||
*/
|
||||
module.exports = 'theme-';
|
||||
@@ -1,7 +1,6 @@
|
||||
jest.autoMockOff();
|
||||
var createMockFS = require('../mock');
|
||||
|
||||
describe('MockFS', function() {
|
||||
var createMockFS = require('../mock');
|
||||
var fs = createMockFS({
|
||||
'README.md': 'Hello World',
|
||||
'SUMMARY.md': '# Summary',
|
||||
@@ -15,35 +14,35 @@ describe('MockFS', function() {
|
||||
});
|
||||
|
||||
describe('exists', function() {
|
||||
pit('must return true for a file', function() {
|
||||
it('must return true for a file', function() {
|
||||
return fs.exists('README.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return false for a non existing file', function() {
|
||||
it('must return false for a non existing file', function() {
|
||||
return fs.exists('README_NOTEXISTS.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return true for a directory', function() {
|
||||
it('must return true for a directory', function() {
|
||||
return fs.exists('folder')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return true for a deep file', function() {
|
||||
it('must return true for a deep file', function() {
|
||||
return fs.exists('folder/test.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return true for a deep file (2)', function() {
|
||||
it('must return true for a deep file (2)', function() {
|
||||
return fs.exists('folder/folder2/hello.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBeTruthy();
|
||||
@@ -52,14 +51,14 @@ describe('MockFS', function() {
|
||||
});
|
||||
|
||||
describe('readAsString', function() {
|
||||
pit('must return content for a file', function() {
|
||||
it('must return content for a file', function() {
|
||||
return fs.readAsString('README.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBe('Hello World');
|
||||
});
|
||||
});
|
||||
|
||||
pit('must return content for a deep file', function() {
|
||||
it('must return content for a deep file', function() {
|
||||
return fs.readAsString('folder/test.md')
|
||||
.then(function(result) {
|
||||
expect(result).toBe('Cool');
|
||||
@@ -68,7 +67,7 @@ describe('MockFS', function() {
|
||||
});
|
||||
|
||||
describe('readDir', function() {
|
||||
pit('must return content for a directory', function() {
|
||||
it('must return content for a directory', function() {
|
||||
return fs.readDir('./')
|
||||
.then(function(files) {
|
||||
expect(files.size).toBe(3);
|
||||
|
||||
+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)
|
||||
|
||||
@@ -4,6 +4,7 @@ var gitbook = require('../gitbook');
|
||||
var encodeSummary = require('./encodeSummary');
|
||||
var encodeGlossary = require('./encodeGlossary');
|
||||
var encodeReadme = require('./encodeReadme');
|
||||
var encodeLanguages = require('./encodeLanguages');
|
||||
|
||||
/**
|
||||
Encode a book to JSON
|
||||
@@ -22,6 +23,9 @@ function encodeBookToJson(book) {
|
||||
glossary: encodeGlossary(book.getGlossary()),
|
||||
readme: encodeReadme(book.getReadme()),
|
||||
config: book.getConfig().getValues().toJS(),
|
||||
|
||||
languages: book.isMultilingual()? encodeLanguages(book.getLanguages()) : undefined,
|
||||
|
||||
gitbook: {
|
||||
version: gitbook.version,
|
||||
time: gitbook.START_TIME
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
var encodeFile = require('./encodeFile');
|
||||
|
||||
/**
|
||||
Encode a languages listing to JSON
|
||||
|
||||
@param {Languages}
|
||||
@return {Object}
|
||||
*/
|
||||
function encodeLanguages(languages) {
|
||||
var file = languages.getFile();
|
||||
var list = languages.getList();
|
||||
|
||||
return {
|
||||
file: encodeFile(file),
|
||||
list: list
|
||||
.valueSeq()
|
||||
.map(function(lang) {
|
||||
return {
|
||||
id: lang.getID(),
|
||||
title: lang.getTitle()
|
||||
};
|
||||
}).toJS()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = encodeLanguages;
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
@@ -20,6 +20,7 @@ function encodeSummaryArticle(article, recursive) {
|
||||
anchor: article.getAnchor(),
|
||||
url: article.getUrl(),
|
||||
path: article.getPath(),
|
||||
ref: article.getRef(),
|
||||
articles: articles
|
||||
};
|
||||
}
|
||||
|
||||
+3
-1
@@ -2,10 +2,12 @@
|
||||
module.exports = {
|
||||
encodeOutput: require('./encodeOutput'),
|
||||
encodeBookWithPage: require('./encodeBookWithPage'),
|
||||
encodeOutputWithPage: require('./encodeOutputWithPage'),
|
||||
encodeBook: require('./encodeBook'),
|
||||
encodeFile: require('./encodeFile'),
|
||||
encodePage: require('./encodePage'),
|
||||
encodeSummary: require('./encodeSummary'),
|
||||
encodeSummaryArticle: require('./encodeSummaryArticle'),
|
||||
encodeReadme: require('./encodeReadme')
|
||||
encodeReadme: require('./encodeReadme'),
|
||||
encodeLanguages: require('./encodeLanguages')
|
||||
};
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
jest.autoMockOff();
|
||||
|
||||
var Immutable = require('immutable');
|
||||
var Config = require('../config');
|
||||
|
||||
describe('Config', function() {
|
||||
var Config = require('../config');
|
||||
|
||||
var config = Config.createWithValues({
|
||||
hello: {
|
||||
world: 1,
|
||||
@@ -58,6 +55,36 @@ describe('Config', function() {
|
||||
expect(world).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toReducedVersion', function() {
|
||||
it('must only return diffs for simple values', function() {
|
||||
var _config = Config.createWithValues({
|
||||
gitbook: '3.0.0'
|
||||
});
|
||||
|
||||
var reducedVersion = _config.toReducedVersion();
|
||||
|
||||
expect(reducedVersion.toJS()).toEqual({
|
||||
gitbook: '3.0.0'
|
||||
});
|
||||
});
|
||||
|
||||
it('must only return diffs for deep values', function() {
|
||||
var _config = Config.createWithValues({
|
||||
structure: {
|
||||
readme: 'intro.md'
|
||||
}
|
||||
});
|
||||
|
||||
var reducedVersion = _config.toReducedVersion();
|
||||
|
||||
expect(reducedVersion.toJS()).toEqual({
|
||||
structure: {
|
||||
readme: 'intro.md'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
jest.autoMockOff();
|
||||
var File = require('../file');
|
||||
var Glossary = require('../glossary');
|
||||
var GlossaryEntry = require('../glossaryEntry');
|
||||
|
||||
describe('Glossary', function() {
|
||||
var File = require('../file');
|
||||
var Glossary = require('../glossary');
|
||||
var GlossaryEntry = require('../glossaryEntry');
|
||||
|
||||
var glossary = Glossary.createFromEntries(File(), [
|
||||
{
|
||||
name: 'Hello World',
|
||||
@@ -30,7 +28,7 @@ describe('Glossary', function() {
|
||||
});
|
||||
|
||||
describe('toText', function() {
|
||||
pit('return as markdown', function() {
|
||||
it('return as markdown', function() {
|
||||
return glossary.toText('.md')
|
||||
.then(function(text) {
|
||||
expect(text).toContain('# Glossary');
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
jest.autoMockOff();
|
||||
var GlossaryEntry = require('../glossaryEntry');
|
||||
|
||||
describe('GlossaryEntry', function() {
|
||||
var GlossaryEntry = require('../glossaryEntry');
|
||||
|
||||
describe('getID', function() {
|
||||
it('must return a normalized ID', function() {
|
||||
var entry = new GlossaryEntry({
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
var Immutable = require('immutable');
|
||||
var Page = require('../page');
|
||||
|
||||
describe('Page', function() {
|
||||
|
||||
describe('toText', function() {
|
||||
it('must not prepend frontmatter if no attributes', function() {
|
||||
var page = Page().merge({
|
||||
content: 'Hello World'
|
||||
});
|
||||
|
||||
expect(page.toText()).toBe('Hello World');
|
||||
});
|
||||
|
||||
it('must prepend frontmatter if attributes', function() {
|
||||
var page = Page().merge({
|
||||
content: 'Hello World',
|
||||
attributes: Immutable.fromJS({
|
||||
hello: 'world'
|
||||
})
|
||||
});
|
||||
|
||||
expect(page.toText()).toBe('---\nhello: world\n---\n\nHello World');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
describe('PluginDependency', function() {
|
||||
var PluginDependency = require('../pluginDependency');
|
||||
var Immutable = require('immutable');
|
||||
var PluginDependency = require('../pluginDependency');
|
||||
|
||||
describe('PluginDependency', function() {
|
||||
describe('createFromString', function() {
|
||||
it('must parse name', function() {
|
||||
var plugin = PluginDependency.createFromString('hello');
|
||||
@@ -41,7 +42,39 @@ describe('PluginDependency', function() {
|
||||
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||
});
|
||||
});
|
||||
|
||||
describe('listToArray', function() {
|
||||
it('must create an array from a list of plugin dependencies', function() {
|
||||
var list = PluginDependency.listToArray(Immutable.List([
|
||||
PluginDependency.createFromString('hello@1.0.0'),
|
||||
PluginDependency.createFromString('noversion'),
|
||||
PluginDependency.createFromString('-disabled')
|
||||
]));
|
||||
|
||||
expect(list).toEqual([
|
||||
'hello@1.0.0',
|
||||
'noversion',
|
||||
'-disabled'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listFromArray', function() {
|
||||
it('must create an array from a list of plugin dependencies', function() {
|
||||
var arr = Immutable.fromJS([
|
||||
'hello@1.0.0',
|
||||
{
|
||||
'name': 'plugin-ga',
|
||||
'version': 'git+ssh://samy@github.com/GitbookIO/plugin-ga.git'
|
||||
}
|
||||
]);
|
||||
var list = PluginDependency.listFromArray(arr);
|
||||
|
||||
expect(list.first().getName()).toBe('hello');
|
||||
expect(list.first().getVersion()).toBe('1.0.0');
|
||||
expect(list.last().getName()).toBe('plugin-ga');
|
||||
expect(list.last().getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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,10 +73,16 @@ 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() {
|
||||
pit('return as markdown', function() {
|
||||
it('return as markdown', function() {
|
||||
return summary.toText('.md')
|
||||
.then(function(text) {
|
||||
expect(text).toContain('# Summary');
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
var SummaryArticle = require('../summaryArticle');
|
||||
var File = require('../file');
|
||||
|
||||
describe('SummaryArticle', function() {
|
||||
describe('createChildLevel', function() {
|
||||
it('must create the right level', function() {
|
||||
var article = SummaryArticle.create({}, '1.1');
|
||||
expect(article.createChildLevel()).toBe('1.1.1');
|
||||
});
|
||||
|
||||
it('must create the right level when has articles', function() {
|
||||
var article = SummaryArticle.create({
|
||||
articles: [
|
||||
{
|
||||
title: 'Test'
|
||||
}
|
||||
]
|
||||
}, '1.1');
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
var SummaryPart = require('../summaryPart');
|
||||
|
||||
describe('SummaryPart', function() {
|
||||
describe('createChildLevel', function() {
|
||||
it('must create the right level', function() {
|
||||
var article = SummaryPart.create({}, '1');
|
||||
expect(article.createChildLevel()).toBe('1.1');
|
||||
});
|
||||
|
||||
it('must create the right level when has articles', function() {
|
||||
var article = SummaryPart.create({
|
||||
articles: [
|
||||
{
|
||||
title: 'Test'
|
||||
}
|
||||
]
|
||||
}, '1');
|
||||
expect(article.createChildLevel()).toBe('1.2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,18 +6,18 @@ describe('TemplateBlock', function() {
|
||||
var TemplateBlock = require('../templateBlock');
|
||||
|
||||
describe('create', function() {
|
||||
pit('must initialize a simple TemplateBlock from a function', function() {
|
||||
it('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('toNunjucksExt()', function() {
|
||||
pit('must create a valid nunjucks extension', function() {
|
||||
describe('getShortcuts', function() {
|
||||
it('must return undefined if no shortcuts', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return '<p>Hello, World!</p>';
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
expect(templateBlock.getShortcuts()).toNotExist();
|
||||
});
|
||||
|
||||
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() {
|
||||
it('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');
|
||||
});
|
||||
});
|
||||
|
||||
it('must create a valid nunjucks extension', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return {
|
||||
body: '<p>Hello, World!</p>',
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
|
||||
// Create a fresh Nunjucks environment
|
||||
@@ -53,9 +120,12 @@ describe('TemplateBlock', function() {
|
||||
});
|
||||
});
|
||||
|
||||
pit('must apply block arguments correctly', function() {
|
||||
it('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
|
||||
@@ -73,11 +143,14 @@ describe('TemplateBlock', function() {
|
||||
});
|
||||
});
|
||||
|
||||
pit('must accept an async function', function() {
|
||||
it('must accept an async function', function() {
|
||||
var templateBlock = TemplateBlock.create('sayhello', function(block) {
|
||||
return Promise()
|
||||
.then(function() {
|
||||
return 'Hello ' + block.body;
|
||||
return {
|
||||
body: 'Hello ' + block.body,
|
||||
parse: true
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,7 +169,7 @@ describe('TemplateBlock', function() {
|
||||
});
|
||||
});
|
||||
|
||||
pit('must handle nested blocks', function() {
|
||||
it('must handle nested blocks', function() {
|
||||
var templateBlock = new TemplateBlock({
|
||||
name: 'yoda',
|
||||
blocks: Immutable.List(['start', 'end']),
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+56
-12
@@ -1,6 +1,5 @@
|
||||
var path = require('path');
|
||||
var Immutable = require('immutable');
|
||||
var Ignore = require('ignore');
|
||||
|
||||
var Logger = require('../utils/logger');
|
||||
|
||||
@@ -10,7 +9,7 @@ var Readme = require('./readme');
|
||||
var Summary = require('./summary');
|
||||
var Glossary = require('./glossary');
|
||||
var Languages = require('./languages');
|
||||
|
||||
var Ignore = require('./ignore');
|
||||
|
||||
var Book = Immutable.Record({
|
||||
// Logger for outptu message
|
||||
@@ -128,8 +127,7 @@ Book.prototype.isFileIgnored = function(filename) {
|
||||
filename = path.join(language, filename);
|
||||
}
|
||||
|
||||
|
||||
return ignore.filter([filename]).length == 0;
|
||||
return ignore.isFileIgnored(filename);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -177,6 +175,17 @@ Book.prototype.isLanguageBook = function() {
|
||||
return Boolean(this.getLanguage());
|
||||
};
|
||||
|
||||
/**
|
||||
Return a languages book
|
||||
|
||||
@param {String} language
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.getLanguageBook = function(language) {
|
||||
var books = this.getBooks();
|
||||
return books.get(language);
|
||||
};
|
||||
|
||||
/**
|
||||
Add a new language book
|
||||
|
||||
@@ -221,6 +230,16 @@ Book.prototype.setConfig = function(config) {
|
||||
return this.set('config', config);
|
||||
};
|
||||
|
||||
/**
|
||||
Set the ignore instance for this book
|
||||
|
||||
@param {Ignore}
|
||||
@return {Book}
|
||||
*/
|
||||
Book.prototype.setIgnore = function(ignore) {
|
||||
return this.set('ignore', ignore);
|
||||
};
|
||||
|
||||
/**
|
||||
Change log level
|
||||
|
||||
@@ -274,26 +293,47 @@ Book.prototype.getDefaultExt = function() {
|
||||
|
||||
/**
|
||||
Infer the default path for a Readme.
|
||||
@param {Boolean} [absolute=false] False for a path relative to
|
||||
this book's content root
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultReadmePath = function() {
|
||||
return this.getContentRoot()+'README'+this.getDefaultExt();
|
||||
Book.prototype.getDefaultReadmePath = function(absolute) {
|
||||
var defaultPath = 'README'+this.getDefaultExt();
|
||||
if (absolute) {
|
||||
return path.join(this.getContentRoot(), defaultPath);
|
||||
} else {
|
||||
return defaultPath;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Summary.
|
||||
@param {Boolean} [absolute=false] False for a path relative to
|
||||
this book's content root
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultSummaryPath = function() {
|
||||
return this.getContentRoot()+'SUMMARY'+this.getDefaultExt();
|
||||
Book.prototype.getDefaultSummaryPath = function(absolute) {
|
||||
var defaultPath = 'SUMMARY'+this.getDefaultExt();
|
||||
if (absolute) {
|
||||
return path.join(this.getContentRoot(), defaultPath);
|
||||
} else {
|
||||
return defaultPath;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Infer the default path for a Glossary.
|
||||
@param {Boolean} [absolute=false] False for a path relative to
|
||||
this book's content root
|
||||
@return {String}
|
||||
*/
|
||||
Book.prototype.getDefaultGlossaryPath = function() {
|
||||
return this.getContentRoot()+'GLOSSARY'+this.getDefaultExt();
|
||||
Book.prototype.getDefaultGlossaryPath = function(absolute) {
|
||||
var defaultPath = 'GLOSSARY'+this.getDefaultExt();
|
||||
if (absolute) {
|
||||
return path.join(this.getContentRoot(), defaultPath);
|
||||
} else {
|
||||
return defaultPath;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -305,12 +345,16 @@ Book.prototype.getDefaultGlossaryPath = function() {
|
||||
*/
|
||||
Book.createFromParent = function createFromParent(parent, language) {
|
||||
var ignore = parent.getIgnore();
|
||||
var config = parent.getConfig();
|
||||
|
||||
// Set language in configuration
|
||||
config = config.setValue('language', language);
|
||||
|
||||
return new Book({
|
||||
// Inherits config. logegr and list of ignored files
|
||||
logger: parent.getLogger(),
|
||||
config: parent.getConfig(),
|
||||
ignore: Ignore().add(ignore),
|
||||
config: config,
|
||||
ignore: ignore,
|
||||
|
||||
language: language,
|
||||
fs: FS.reduceScope(parent.getContentFS(), language)
|
||||
|
||||
+95
-48
@@ -4,6 +4,7 @@ var Immutable = require('immutable');
|
||||
var File = require('./file');
|
||||
var PluginDependency = require('./pluginDependency');
|
||||
var configDefault = require('../constants/configDefault');
|
||||
var reducedObject = require('../utils/reducedObject');
|
||||
|
||||
var Config = Immutable.Record({
|
||||
file: File(),
|
||||
@@ -19,11 +20,36 @@ Config.prototype.getValues = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Return a configuration value by its key path
|
||||
* Return minimum version of configuration,
|
||||
* Basically it returns the current config minus the default one
|
||||
* @return {Map}
|
||||
*/
|
||||
Config.prototype.toReducedVersion = function() {
|
||||
return reducedObject(configDefault, this.getValues());
|
||||
};
|
||||
|
||||
@param {String} key
|
||||
@return {Mixed}
|
||||
*/
|
||||
/**
|
||||
* Render config as text
|
||||
* @return {Promise<String>}
|
||||
*/
|
||||
Config.prototype.toText = function() {
|
||||
return JSON.stringify(this.toReducedVersion().toJS(), null, 4);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the file for the configuration
|
||||
* @param {File} file
|
||||
* @return {Config}
|
||||
*/
|
||||
Config.prototype.setFile = function(file) {
|
||||
return this.set('file', file);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a configuration value by its key path
|
||||
* @param {String} key
|
||||
* @return {Mixed}
|
||||
*/
|
||||
Config.prototype.getValue = function(keyPath, def) {
|
||||
var values = this.getValues();
|
||||
keyPath = Config.keyToKeyPath(keyPath);
|
||||
@@ -36,12 +62,11 @@ Config.prototype.getValue = function(keyPath, def) {
|
||||
};
|
||||
|
||||
/**
|
||||
Update a configuration value
|
||||
|
||||
@param {String} key
|
||||
@param {Mixed} value
|
||||
@return {Mixed}
|
||||
*/
|
||||
* Update a configuration value
|
||||
* @param {String} key
|
||||
* @param {Mixed} value
|
||||
* @return {Config}
|
||||
*/
|
||||
Config.prototype.setValue = function(keyPath, value) {
|
||||
keyPath = Config.keyToKeyPath(keyPath);
|
||||
|
||||
@@ -54,10 +79,9 @@ Config.prototype.setValue = function(keyPath, value) {
|
||||
};
|
||||
|
||||
/**
|
||||
Return a list of plugin dependencies
|
||||
|
||||
@return {List<PluginDependency>}
|
||||
*/
|
||||
* Return a list of plugin dependencies
|
||||
* @return {List<PluginDependency>}
|
||||
*/
|
||||
Config.prototype.getPluginDependencies = function() {
|
||||
var plugins = this.getValue('plugins');
|
||||
|
||||
@@ -69,24 +93,62 @@ Config.prototype.getPluginDependencies = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Update the list of plugins dependencies
|
||||
* Return a plugin dependency by its name
|
||||
* @param {String} name
|
||||
* @return {PluginDependency}
|
||||
*/
|
||||
Config.prototype.getPluginDependency = function(name) {
|
||||
var plugins = this.getPluginDependencies();
|
||||
|
||||
@param {List<PluginDependency>}
|
||||
@return {Config}
|
||||
*/
|
||||
return plugins.find(function(dep) {
|
||||
return dep.getName() === name;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the list of plugins dependencies
|
||||
* @param {List<PluginDependency>}
|
||||
* @return {Config}
|
||||
*/
|
||||
Config.prototype.setPluginDependencies = function(deps) {
|
||||
var plugins = PluginDependency.listFromArray(deps);
|
||||
var plugins = PluginDependency.listToArray(deps);
|
||||
|
||||
return this.setValue('plugins', plugins);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new config for a file
|
||||
|
||||
@param {File} file
|
||||
@param {Object} values
|
||||
@returns {Config}
|
||||
*/
|
||||
/**
|
||||
* Update values for an existing configuration
|
||||
* @param {Object} values
|
||||
* @returns {Config}
|
||||
*/
|
||||
Config.prototype.updateValues = function(values) {
|
||||
values = Immutable.fromJS(values);
|
||||
|
||||
return this.set('values', values);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update values for an existing configuration
|
||||
* @param {Config} config
|
||||
* @param {Object} values
|
||||
* @returns {Config}
|
||||
*/
|
||||
Config.prototype.mergeValues = function(values) {
|
||||
var currentValues = this.getValues();
|
||||
values = Immutable.fromJS(values);
|
||||
|
||||
currentValues = currentValues.mergeDeep(values);
|
||||
|
||||
return this.set('values', currentValues);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new config for a file
|
||||
* @param {File} file
|
||||
* @param {Object} values
|
||||
* @returns {Config}
|
||||
*/
|
||||
Config.create = function(file, values) {
|
||||
return new Config({
|
||||
file: file,
|
||||
@@ -95,37 +157,22 @@ Config.create = function(file, values) {
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new config
|
||||
|
||||
@param {Object} values
|
||||
@returns {Config}
|
||||
*/
|
||||
* Create a new config
|
||||
* @param {Object} values
|
||||
* @returns {Config}
|
||||
*/
|
||||
Config.createWithValues = function(values) {
|
||||
return new Config({
|
||||
values: Immutable.fromJS(values)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Update values for an existing configuration
|
||||
|
||||
@param {Config} config
|
||||
@param {Object} values
|
||||
@returns {Config}
|
||||
*/
|
||||
Config.updateValues = function(config, values) {
|
||||
values = Immutable.fromJS(values);
|
||||
|
||||
return config.set('values', values);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Convert a keyPath to an array of keys
|
||||
|
||||
@param {String|Array}
|
||||
@return {Array}
|
||||
*/
|
||||
* Convert a keyPath to an array of keys
|
||||
* @param {String|Array}
|
||||
* @return {Array}
|
||||
*/
|
||||
Config.keyToKeyPath = function(keyPath) {
|
||||
if (is.string(keyPath)) keyPath = keyPath.split('.');
|
||||
return keyPath;
|
||||
|
||||
+42
-9
@@ -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
|
||||
|
||||
@@ -171,23 +198,29 @@ FS.prototype.listFiles = function(dirname) {
|
||||
/**
|
||||
List all files in a directory
|
||||
|
||||
@param {String} dirname
|
||||
@param {String} dirName
|
||||
@param {Function(dirName)} filterFn: call it for each file/directory to test if it should stop iterating
|
||||
@return {Promise<List<String>>}
|
||||
*/
|
||||
FS.prototype.listAllFiles = function(folder) {
|
||||
FS.prototype.listAllFiles = function(dirName, filterFn) {
|
||||
var that = this;
|
||||
folder = folder || '.';
|
||||
dirName = dirName || '.';
|
||||
|
||||
return this.readDir(folder)
|
||||
return this.readDir(dirName)
|
||||
.then(function(files) {
|
||||
return Promise.reduce(files, function(out, file) {
|
||||
var isDirectory = pathIsFolder(file);
|
||||
var newDirName = path.join(dirName, file);
|
||||
|
||||
if (!isDirectory) {
|
||||
return out.push(path.join(folder, file));
|
||||
if (filterFn && filterFn(newDirName) === false) {
|
||||
return out;
|
||||
}
|
||||
|
||||
return that.listAllFiles(path.join(folder, file))
|
||||
if (!isDirectory) {
|
||||
return out.push(newDirName);
|
||||
}
|
||||
|
||||
return that.listAllFiles(newDirName, filterFn)
|
||||
.then(function(inner) {
|
||||
return out.concat(inner);
|
||||
});
|
||||
@@ -196,7 +229,7 @@ FS.prototype.listAllFiles = function(folder) {
|
||||
};
|
||||
|
||||
/**
|
||||
Find a file in a folder (case incensitive)
|
||||
Find a file in a folder (case insensitive)
|
||||
Return the found filename
|
||||
|
||||
@param {String} dirname
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
var Immutable = require('immutable');
|
||||
var IgnoreMutable = require('ignore');
|
||||
|
||||
/*
|
||||
Immutable version of node-ignore
|
||||
*/
|
||||
var Ignore = Immutable.Record({
|
||||
ignore: new IgnoreMutable()
|
||||
}, 'Ignore');
|
||||
|
||||
Ignore.prototype.getIgnore = function() {
|
||||
return this.get('ignore');
|
||||
};
|
||||
|
||||
/**
|
||||
Test if a file is ignored by these rules
|
||||
|
||||
@param {String} filePath
|
||||
@return {Boolean}
|
||||
*/
|
||||
Ignore.prototype.isFileIgnored = function(filename) {
|
||||
var ignore = this.getIgnore();
|
||||
return ignore.filter([filename]).length == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
Add rules
|
||||
|
||||
@param {String}
|
||||
@return {Ignore}
|
||||
*/
|
||||
Ignore.prototype.add = function(rule) {
|
||||
var ignore = this.getIgnore();
|
||||
var newIgnore = new IgnoreMutable();
|
||||
|
||||
newIgnore.add(ignore);
|
||||
newIgnore.add(rule);
|
||||
|
||||
return this.set('ignore', newIgnore);
|
||||
};
|
||||
|
||||
module.exports = Ignore;
|
||||
@@ -1,6 +1,7 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var Book = require('./book');
|
||||
var LocationUtils = require('../utils/location');
|
||||
|
||||
var Output = Immutable.Record({
|
||||
book: Book(),
|
||||
@@ -52,6 +53,19 @@ Output.prototype.getState = function() {
|
||||
return this.get('state');
|
||||
};
|
||||
|
||||
/**
|
||||
Return a page byt its file path
|
||||
|
||||
@param {String} filePath
|
||||
@return {Page|undefined}
|
||||
*/
|
||||
Output.prototype.getPage = function(filePath) {
|
||||
filePath = LocationUtils.normalize(filePath);
|
||||
|
||||
var pages = this.getPages();
|
||||
return pages.get(filePath);
|
||||
};
|
||||
|
||||
/**
|
||||
Get root folder for output
|
||||
|
||||
|
||||
+21
-6
@@ -1,4 +1,5 @@
|
||||
var Immutable = require('immutable');
|
||||
var yaml = require('js-yaml');
|
||||
|
||||
var File = require('./file');
|
||||
|
||||
@@ -32,19 +33,33 @@ Page.prototype.getDir = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Return path of the page
|
||||
* Return page as text
|
||||
* @return {String}
|
||||
*/
|
||||
Page.prototype.toText = function() {
|
||||
var attrs = this.getAttributes();
|
||||
var content = this.getContent();
|
||||
|
||||
@return {String}
|
||||
if (attrs.size === 0) {
|
||||
return content;
|
||||
}
|
||||
|
||||
var frontMatter = '---\n' + yaml.safeDump(attrs.toJS(), { skipInvalid: true }) + '---\n\n';
|
||||
return (frontMatter + content);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return path of the page
|
||||
* @return {String}
|
||||
*/
|
||||
Page.prototype.getPath = function() {
|
||||
return this.getFile().getPath();
|
||||
};
|
||||
|
||||
/**
|
||||
Create a page for a file
|
||||
|
||||
@param {File} file
|
||||
@return {Page}
|
||||
* Create a page for a file
|
||||
* @param {File} file
|
||||
* @return {Page}
|
||||
*/
|
||||
Page.createForFile = function(file) {
|
||||
return new Page({
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
@@ -51,6 +55,11 @@ Parser.prototype.parsePage = function(content) {
|
||||
return Promise(page(content));
|
||||
};
|
||||
|
||||
Parser.prototype.parseInline = function(content) {
|
||||
var inline = this.get('inline');
|
||||
return Promise(inline(content));
|
||||
};
|
||||
|
||||
Parser.prototype.parseLanguages = function(content) {
|
||||
var langs = this.get('langs');
|
||||
return Promise(langs(content));
|
||||
|
||||
+53
-49
@@ -1,7 +1,9 @@
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var TemplateBlock = require('./templateBlock');
|
||||
var PREFIX = require('../constants/pluginPrefix');
|
||||
var PluginDependency = require('./pluginDependency');
|
||||
var THEME_PREFIX = require('../constants/themePrefix');
|
||||
|
||||
var DEFAULT_VERSION = '*';
|
||||
|
||||
var Plugin = Immutable.Record({
|
||||
@@ -16,6 +18,9 @@ var Plugin = Immutable.Record({
|
||||
// Depth of this plugin in the dependency tree
|
||||
depth: Number(0),
|
||||
|
||||
// Parent depending on this plugin
|
||||
parent: String(),
|
||||
|
||||
// Content of the "package.json"
|
||||
package: Immutable.Map(),
|
||||
|
||||
@@ -47,38 +52,48 @@ Plugin.prototype.getDepth = function() {
|
||||
return this.get('depth');
|
||||
};
|
||||
|
||||
/**
|
||||
Return the ID on NPM for this plugin
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
Plugin.prototype.getNpmID = function() {
|
||||
return Plugin.nameToNpmID(this.getName());
|
||||
Plugin.prototype.getParent = function() {
|
||||
return this.get('parent');
|
||||
};
|
||||
|
||||
/**
|
||||
Check if a plugin is loaded
|
||||
* Return the ID on NPM for this plugin
|
||||
* @return {String}
|
||||
*/
|
||||
Plugin.prototype.getNpmID = function() {
|
||||
return PluginDependency.nameToNpmID(this.getName());
|
||||
};
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
/**
|
||||
* Check if a plugin is loaded
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Plugin.prototype.isLoaded = function() {
|
||||
return Boolean(this.getPackage().size > 0);
|
||||
};
|
||||
|
||||
/**
|
||||
Return map of hooks
|
||||
@return {Map<String:Function>}
|
||||
*/
|
||||
* Check if a plugin is a theme given its name
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Plugin.prototype.isTheme = function() {
|
||||
var name = this.getName();
|
||||
return (name && name.indexOf(THEME_PREFIX) === 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return map of hooks
|
||||
* @return {Map<String:Function>}
|
||||
*/
|
||||
Plugin.prototype.getHooks = function() {
|
||||
return this.getContent().get('hooks') || Immutable.Map();
|
||||
};
|
||||
|
||||
/**
|
||||
Return infos about resources for a specific type
|
||||
|
||||
@param {String} type
|
||||
@return {Map<String:Mixed>}
|
||||
*/
|
||||
* Return infos about resources for a specific type
|
||||
* @param {String} type
|
||||
* @return {Map<String:Mixed>}
|
||||
*/
|
||||
Plugin.prototype.getResources = function(type) {
|
||||
if (type != 'website' && type != 'ebook') {
|
||||
throw new Error('Invalid assets type ' + type);
|
||||
@@ -91,17 +106,17 @@ Plugin.prototype.getResources = function(type) {
|
||||
};
|
||||
|
||||
/**
|
||||
Return map of filters
|
||||
@return {Map<String:Function>}
|
||||
*/
|
||||
* Return map of filters
|
||||
* @return {Map<String:Function>}
|
||||
*/
|
||||
Plugin.prototype.getFilters = function() {
|
||||
return this.getContent().get('filters');
|
||||
};
|
||||
|
||||
/**
|
||||
Return map of blocks
|
||||
@return {Map<String:TemplateBlock>}
|
||||
*/
|
||||
* Return map of blocks
|
||||
* @return {Map<String:TemplateBlock>}
|
||||
*/
|
||||
Plugin.prototype.getBlocks = function() {
|
||||
var blocks = this.getContent().get('blocks');
|
||||
blocks = blocks || Immutable.Map();
|
||||
@@ -113,21 +128,19 @@ Plugin.prototype.getBlocks = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Return a specific hook
|
||||
|
||||
@param {String} name
|
||||
@return {Function|undefined}
|
||||
*/
|
||||
* Return a specific hook
|
||||
* @param {String} name
|
||||
* @return {Function|undefined}
|
||||
*/
|
||||
Plugin.prototype.getHook = function(name) {
|
||||
return this.getHooks().get(name);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a plugin from a string
|
||||
|
||||
@param {String}
|
||||
@return {Plugin}
|
||||
*/
|
||||
* Create a plugin from a string
|
||||
* @param {String}
|
||||
* @return {Plugin}
|
||||
*/
|
||||
Plugin.createFromString = function(s) {
|
||||
var parts = s.split('@');
|
||||
var name = parts[0];
|
||||
@@ -140,11 +153,10 @@ Plugin.createFromString = function(s) {
|
||||
};
|
||||
|
||||
/**
|
||||
Create a plugin from a dependency
|
||||
|
||||
@param {PluginDependency}
|
||||
@return {Plugin}
|
||||
*/
|
||||
* Create a plugin from a dependency
|
||||
* @param {PluginDependency}
|
||||
* @return {Plugin}
|
||||
*/
|
||||
Plugin.createFromDep = function(dep) {
|
||||
return new Plugin({
|
||||
name: dep.getName(),
|
||||
@@ -152,14 +164,6 @@ Plugin.createFromDep = function(dep) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Return NPM id for a plugin name
|
||||
|
||||
@param {String}
|
||||
@return {String}
|
||||
*/
|
||||
Plugin.nameToNpmID = function(s) {
|
||||
return PREFIX + s;
|
||||
};
|
||||
Plugin.nameToNpmID = PluginDependency.nameToNpmID;
|
||||
|
||||
module.exports = Plugin;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
var is = require('is');
|
||||
var semver = require('semver');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var PREFIX = require('../constants/pluginPrefix');
|
||||
var DEFAULT_VERSION = '*';
|
||||
|
||||
/*
|
||||
PluginDependency represents the informations about a plugin
|
||||
stored in config.plugins
|
||||
*/
|
||||
* PluginDependency represents the informations about a plugin
|
||||
* stored in config.plugins
|
||||
*/
|
||||
var PluginDependency = Immutable.Record({
|
||||
name: String(),
|
||||
|
||||
@@ -30,11 +32,56 @@ PluginDependency.prototype.isEnabled = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Create a plugin from a string
|
||||
* Toggle this plugin state
|
||||
* @param {Boolean}
|
||||
* @return {PluginDependency}
|
||||
*/
|
||||
PluginDependency.prototype.toggle = function(state) {
|
||||
if (is.undef(state)) {
|
||||
state = !this.isEnabled();
|
||||
}
|
||||
|
||||
@param {String}
|
||||
@return {Plugin|undefined}
|
||||
*/
|
||||
return this.set('enabled', state);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return NPM ID for the dependency
|
||||
* @return {String}
|
||||
*/
|
||||
PluginDependency.prototype.getNpmID = function() {
|
||||
return PluginDependency.nameToNpmID(this.getName());
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the plugin using a git dependency
|
||||
* @return {Boolean}
|
||||
*/
|
||||
PluginDependency.prototype.isGitDependency = function() {
|
||||
return !semver.validRange(this.getVersion());
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a plugin with a name and a plugin
|
||||
* @param {String}
|
||||
* @return {Plugin|undefined}
|
||||
*/
|
||||
PluginDependency.create = function(name, version, enabled) {
|
||||
if (is.undefined(enabled)) {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
return new PluginDependency({
|
||||
name: name,
|
||||
version: version || DEFAULT_VERSION,
|
||||
enabled: Boolean(enabled)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a plugin from a string
|
||||
* @param {String}
|
||||
* @return {Plugin|undefined}
|
||||
*/
|
||||
PluginDependency.createFromString = function(s) {
|
||||
var parts = s.split('@');
|
||||
var name = parts[0];
|
||||
@@ -54,22 +101,20 @@ PluginDependency.createFromString = function(s) {
|
||||
};
|
||||
|
||||
/**
|
||||
Create a PluginDependency from a string
|
||||
|
||||
@param {String}
|
||||
@return {List<PluginDependency>}
|
||||
*/
|
||||
* Create a PluginDependency from a string
|
||||
* @param {String}
|
||||
* @return {List<PluginDependency>}
|
||||
*/
|
||||
PluginDependency.listFromString = function(s) {
|
||||
var parts = s.split(',');
|
||||
return PluginDependency.listFromArray(parts);
|
||||
};
|
||||
|
||||
/**
|
||||
Create a PluginDependency from an array
|
||||
|
||||
@param {Array}
|
||||
@return {List<PluginDependency>}
|
||||
*/
|
||||
* Create a PluginDependency from an array
|
||||
* @param {Array}
|
||||
* @return {List<PluginDependency>}
|
||||
*/
|
||||
PluginDependency.listFromArray = function(arr) {
|
||||
return Immutable.List(arr)
|
||||
.map(function(entry) {
|
||||
@@ -77,8 +122,8 @@ PluginDependency.listFromArray = function(arr) {
|
||||
return PluginDependency.createFromString(entry);
|
||||
} else {
|
||||
return PluginDependency({
|
||||
name: entry.name,
|
||||
version: entry.version
|
||||
name: entry.get('name'),
|
||||
version: entry.get('version')
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -88,17 +133,16 @@ PluginDependency.listFromArray = function(arr) {
|
||||
};
|
||||
|
||||
/**
|
||||
Export plugin dependencies as an array
|
||||
|
||||
@param {List<PluginDependency>}
|
||||
@return {Array<String>}
|
||||
*/
|
||||
PluginDependency.listToArray = function(arr) {
|
||||
return arr
|
||||
* Export plugin dependencies as an array
|
||||
* @param {List<PluginDependency>} list
|
||||
* @return {Array<String>}
|
||||
*/
|
||||
PluginDependency.listToArray = function(list) {
|
||||
return list
|
||||
.map(function(dep) {
|
||||
var result;
|
||||
var result = '';
|
||||
|
||||
if (dep.isEnabled()) {
|
||||
if (!dep.isEnabled()) {
|
||||
result += '-';
|
||||
}
|
||||
|
||||
@@ -112,4 +156,13 @@ PluginDependency.listToArray = function(arr) {
|
||||
.toJS();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return NPM id for a plugin name
|
||||
* @param {String}
|
||||
* @return {String}
|
||||
*/
|
||||
PluginDependency.nameToNpmID = function(s) {
|
||||
return PREFIX + s;
|
||||
};
|
||||
|
||||
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,19 +30,21 @@ SummaryArticle.prototype.getArticles = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Return how deep the article is
|
||||
|
||||
@return {Number}
|
||||
*/
|
||||
* 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);
|
||||
};
|
||||
|
||||
/**
|
||||
Get path (without anchor) to the pointing file
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
* Get path (without anchor) to the pointing file.
|
||||
* It also normalizes the file path.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
SummaryArticle.prototype.getPath = function() {
|
||||
if (this.isExternal()) {
|
||||
return undefined;
|
||||
@@ -53,29 +55,28 @@ SummaryArticle.prototype.getPath = function() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
var parts = ref.split('#');
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
Return url if article is external
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
* Return url if article is external
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
SummaryArticle.prototype.getUrl = function() {
|
||||
return this.isExternal()? this.getRef() : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
Get anchor for this article (or undefined)
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
* Get anchor for this article (or undefined)
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
SummaryArticle.prototype.getAnchor = function() {
|
||||
var ref = this.getRef();
|
||||
var parts = ref.split('#');
|
||||
@@ -85,29 +86,68 @@ SummaryArticle.prototype.getAnchor = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Is article pointing to a page of an absolute url
|
||||
* Create a new level for a new child article
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
SummaryArticle.prototype.createChildLevel = function() {
|
||||
var level = this.getLevel();
|
||||
var subArticles = this.getArticles();
|
||||
var childLevel = level + '.' + (subArticles.size + 1);
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
return childLevel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is article pointing to a page of an absolute url
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
SummaryArticle.prototype.isPage = function() {
|
||||
return !this.isExternal() && this.getRef();
|
||||
};
|
||||
|
||||
/**
|
||||
Is article pointing to aan absolute url
|
||||
* 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
|
||||
);
|
||||
};
|
||||
|
||||
@return {Boolean}
|
||||
*/
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
SummaryArticle.prototype.isExternal = function() {
|
||||
return location.isExternal(this.getRef());
|
||||
};
|
||||
|
||||
/**
|
||||
Create a SummaryArticle
|
||||
|
||||
@param {Object} def
|
||||
@return {SummaryArticle}
|
||||
*/
|
||||
* Create a SummaryArticle
|
||||
*
|
||||
* @param {Object} def
|
||||
* @return {SummaryArticle}
|
||||
*/
|
||||
SummaryArticle.create = function(def, level) {
|
||||
var articles = (def.articles || []).map(function(article, i) {
|
||||
if (article instanceof SummaryArticle) {
|
||||
@@ -124,14 +164,13 @@ SummaryArticle.create = function(def, level) {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Find an article from a base one
|
||||
|
||||
@param {Article|Part} base
|
||||
@param {Function(article)} iter
|
||||
@return {Article}
|
||||
*/
|
||||
* Find an article from a base one
|
||||
*
|
||||
* @param {Article|Part} base
|
||||
* @param {Function(article)} iter
|
||||
* @return {Article}
|
||||
*/
|
||||
SummaryArticle.findArticle = function(base, iter) {
|
||||
var articles = base.getArticles();
|
||||
|
||||
|
||||
@@ -25,11 +25,24 @@ SummaryPart.prototype.getArticles = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
Create a SummaryPart
|
||||
* Create a new level for a new child article
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
SummaryPart.prototype.createChildLevel = function() {
|
||||
var level = this.getLevel();
|
||||
var subArticles = this.getArticles();
|
||||
var childLevel = level + '.' + (subArticles.size + 1);
|
||||
|
||||
@param {Object} def
|
||||
@return {SummaryPart}
|
||||
*/
|
||||
return childLevel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a SummaryPart
|
||||
*
|
||||
* @param {Object} def
|
||||
* @return {SummaryPart}
|
||||
*/
|
||||
SummaryPart.create = function(def, level) {
|
||||
var articles = (def.articles || []).map(function(article, i) {
|
||||
if (article instanceof SummaryArticle) {
|
||||
|
||||
+55
-84
@@ -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,25 +41,35 @@ 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);
|
||||
};
|
||||
|
||||
/**
|
||||
Return name for the nunjucks extension
|
||||
|
||||
@return {String}
|
||||
*/
|
||||
* Return name for the nunjucks extension
|
||||
* @return {String}
|
||||
*/
|
||||
TemplateBlock.prototype.getExtensionName = function() {
|
||||
return 'Block' + this.getName() + 'Extension';
|
||||
};
|
||||
|
||||
/**
|
||||
Return a nunjucks extension to represents this block
|
||||
* Return a nunjucks extension to represents this block
|
||||
* @return {Nunjucks.Extension}
|
||||
*/
|
||||
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
||||
blocksOutput = blocksOutput || {};
|
||||
|
||||
@return {Nunjucks.Extension}
|
||||
*/
|
||||
TemplateBlock.prototype.toNunjucksExt = function(mainContext) {
|
||||
var that = this;
|
||||
var name = this.getName();
|
||||
var endTag = this.getEndTag();
|
||||
@@ -183,7 +179,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);
|
||||
};
|
||||
@@ -193,11 +189,11 @@ TemplateBlock.prototype.toNunjucksExt = function(mainContext) {
|
||||
};
|
||||
|
||||
/**
|
||||
Apply a block to a content
|
||||
@param {Object} inner
|
||||
@param {Object} context
|
||||
@return {Promise<String>|String}
|
||||
*/
|
||||
* Apply a block to a content
|
||||
* @param {Object} inner
|
||||
* @param {Object} context
|
||||
* @return {Promise<String>|String}
|
||||
*/
|
||||
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
||||
var processFn = this.getProcess();
|
||||
|
||||
@@ -209,19 +205,18 @@ 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
|
||||
|
||||
@param {Object} result
|
||||
@return {Object}
|
||||
*/
|
||||
TemplateBlock.prototype.handleBlockResult = function(result) {
|
||||
* Normalize result from a block process function
|
||||
* @param {Object|String} result
|
||||
* @return {Object}
|
||||
*/
|
||||
TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
||||
if (is.string(result)) {
|
||||
result = { body: result };
|
||||
}
|
||||
@@ -231,22 +226,22 @@ TemplateBlock.prototype.handleBlockResult = function(result) {
|
||||
};
|
||||
|
||||
/**
|
||||
Convert a block result to HTML
|
||||
|
||||
@param {Object} result
|
||||
@return {String}
|
||||
*/
|
||||
TemplateBlock.prototype.blockResultToHtml = function(result) {
|
||||
var parse = this.getParse();
|
||||
* Convert a block result to HTML
|
||||
* @param {Object} result
|
||||
* @param {Object} blocksOutput: stored post processing blocks in this object
|
||||
* @return {String}
|
||||
*/
|
||||
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
||||
var indexedKey;
|
||||
var toIndex = (!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;
|
||||
}
|
||||
|
||||
@@ -256,35 +251,11 @@ 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
|
||||
|
||||
@param {String} blockName
|
||||
@param {Object} block
|
||||
@return {TemplateBlock}
|
||||
*/
|
||||
* Create a template block from a function or an object
|
||||
* @param {String} blockName
|
||||
* @param {Object} block
|
||||
* @return {TemplateBlock}
|
||||
*/
|
||||
TemplateBlock.create = function(blockName, block) {
|
||||
if (is.fn(block)) {
|
||||
block = new Immutable.Map({
|
||||
@@ -292,16 +263,16 @@ TemplateBlock.create = function(blockName, block) {
|
||||
});
|
||||
}
|
||||
|
||||
block = new TemplateBlock(block);
|
||||
block = block.set('name', blockName);
|
||||
return new TemplateBlock(block);
|
||||
return block;
|
||||
};
|
||||
|
||||
/**
|
||||
Extract kwargs from an arguments array
|
||||
|
||||
@param {Array} args
|
||||
@return {Object}
|
||||
*/
|
||||
* Extract kwargs from an arguments array
|
||||
* @param {Array} args
|
||||
* @return {Object}
|
||||
*/
|
||||
function extractKwargs(args) {
|
||||
var last = args[args.length - 1];
|
||||
return (is.object(last) && last.__keywords)? args.pop() : {};
|
||||
|
||||
@@ -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,42 @@
|
||||
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;
|
||||
@@ -0,0 +1,32 @@
|
||||
var addPlugin = require('../addPlugin');
|
||||
var Config = require('../../../models/config');
|
||||
|
||||
describe('addPlugin', function() {
|
||||
var config = Config.createWithValues({
|
||||
plugins: ['hello', 'world', '-disabled']
|
||||
});
|
||||
|
||||
it('should have correct state of dependencies', function() {
|
||||
var disabledDep = config.getPluginDependency('disabled');
|
||||
|
||||
expect(disabledDep).toBeDefined();
|
||||
expect(disabledDep.getVersion()).toEqual('*');
|
||||
expect(disabledDep.isEnabled()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should add the plugin to the list', function() {
|
||||
var newConfig = addPlugin(config, 'test');
|
||||
|
||||
var testDep = newConfig.getPluginDependency('test');
|
||||
expect(testDep).toBeDefined();
|
||||
expect(testDep.getVersion()).toEqual('*');
|
||||
expect(testDep.isEnabled()).toBeTruthy();
|
||||
|
||||
var disabledDep = newConfig.getPluginDependency('disabled');
|
||||
expect(disabledDep).toBeDefined();
|
||||
expect(disabledDep.getVersion()).toEqual('*');
|
||||
expect(disabledDep.isEnabled()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
var removePlugin = require('../removePlugin');
|
||||
var Config = require('../../../models/config');
|
||||
|
||||
describe('removePlugin', function() {
|
||||
var config = Config.createWithValues({
|
||||
plugins: ['hello', 'world', '-disabled']
|
||||
});
|
||||
|
||||
it('should remove the plugin from the list', function() {
|
||||
var newConfig = removePlugin(config, 'hello');
|
||||
|
||||
var testDep = newConfig.getPluginDependency('hello');
|
||||
expect(testDep).toNotBeDefined();
|
||||
});
|
||||
|
||||
it('should remove the disabled plugin from the list', function() {
|
||||
var newConfig = removePlugin(config, 'disabled');
|
||||
|
||||
var testDep = newConfig.getPluginDependency('disabled');
|
||||
expect(testDep).toNotBeDefined();
|
||||
});
|
||||
|
||||
it('should disable default plugin', function() {
|
||||
var newConfig = removePlugin(config, 'search');
|
||||
|
||||
var disabledDep = newConfig.getPluginDependency('search');
|
||||
expect(disabledDep).toBeDefined();
|
||||
expect(disabledDep.getVersion()).toEqual('*');
|
||||
expect(disabledDep.isEnabled()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
var togglePlugin = require('../togglePlugin');
|
||||
var Config = require('../../../models/config');
|
||||
|
||||
describe('togglePlugin', function() {
|
||||
var config = Config.createWithValues({
|
||||
plugins: ['hello', 'world', '-disabled']
|
||||
});
|
||||
|
||||
it('should enable plugin', function() {
|
||||
var newConfig = togglePlugin(config, 'disabled');
|
||||
|
||||
var testDep = newConfig.getPluginDependency('disabled');
|
||||
expect(testDep).toBeDefined();
|
||||
expect(testDep.getVersion()).toEqual('*');
|
||||
expect(testDep.isEnabled()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should disable plugin', function() {
|
||||
var newConfig = togglePlugin(config, 'world');
|
||||
|
||||
var testDep = newConfig.getPluginDependency('world');
|
||||
expect(testDep).toBeDefined();
|
||||
expect(testDep.getVersion()).toEqual('*');
|
||||
expect(testDep.isEnabled()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
var PluginDependency = require('../../models/pluginDependency');
|
||||
var togglePlugin = require('./togglePlugin');
|
||||
var isDefaultPlugin = require('./isDefaultPlugin');
|
||||
|
||||
/**
|
||||
Add a plugin to a book's configuration
|
||||
* Add a plugin to a book's configuration
|
||||
* @param {Config} config
|
||||
* @param {String} pluginName
|
||||
* @param {String} version (optional)
|
||||
* @return {Config}
|
||||
*/
|
||||
function addPlugin(config, pluginName, version) {
|
||||
// For default plugin, we only ensure it is enabled
|
||||
if (isDefaultPlugin(pluginName, version)) {
|
||||
return togglePlugin(config, pluginName, true);
|
||||
}
|
||||
|
||||
@param {Book} book
|
||||
@param {String} plugin
|
||||
@param {String} version (optional)
|
||||
@return {Book}
|
||||
*/
|
||||
function addPlugin(book, plugin, version) {
|
||||
var config = book.getConfig();
|
||||
var deps = config.getPluginDependencies();
|
||||
|
||||
var dep = PluginDependency({
|
||||
name: plugin,
|
||||
version: version
|
||||
});
|
||||
var dep = PluginDependency.create(pluginName, version);
|
||||
|
||||
deps = deps.push(dep);
|
||||
config = config.setPluginDependencies(deps);
|
||||
|
||||
return book.setConfig(config);
|
||||
return config.setPluginDependencies(deps);
|
||||
}
|
||||
|
||||
module.exports = addPlugin;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
/**
|
||||
* Edit configuration of a plugin
|
||||
* @param {Config} config
|
||||
* @param {String} plugin
|
||||
* @param {Object} pluginConfig
|
||||
* @return {Config}
|
||||
*/
|
||||
function editPlugin(config, pluginName, pluginConfig) {
|
||||
return config.setValue('pluginsConfig.'+pluginName, pluginConfig);
|
||||
}
|
||||
|
||||
module.exports = editPlugin;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
/**
|
||||
* Test if a plugin is listed
|
||||
* @param { {List<PluginDependency}} deps
|
||||
* @param {String} plugin
|
||||
* @param {String} version
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function hasPlugin(deps, pluginName, version) {
|
||||
return !!deps.find(function(dep) {
|
||||
return dep.getName() === pluginName && (!version || dep.getVersion() === version);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = hasPlugin;
|
||||
@@ -1,5 +1,10 @@
|
||||
|
||||
module.exports = {
|
||||
addPlugin: require('./addPlugin'),
|
||||
removePlugin: require('./removePlugin')
|
||||
addPlugin: require('./addPlugin'),
|
||||
removePlugin: require('./removePlugin'),
|
||||
togglePlugin: require('./togglePlugin'),
|
||||
editPlugin: require('./editPlugin'),
|
||||
hasPlugin: require('./hasPlugin'),
|
||||
getPluginConfig: require('./getPluginConfig'),
|
||||
isDefaultPlugin: require('./isDefaultPlugin')
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
var DEFAULT_PLUGINS = require('../../constants/defaultPlugins');
|
||||
var hasPlugin = require('./hasPlugin');
|
||||
|
||||
/**
|
||||
* Test if a plugin is a default one
|
||||
* @param {String} plugin
|
||||
* @param {String} version
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function isDefaultPlugin(pluginName, version) {
|
||||
return hasPlugin(DEFAULT_PLUGINS, pluginName, version);
|
||||
}
|
||||
|
||||
module.exports = isDefaultPlugin;
|
||||
@@ -1,22 +1,25 @@
|
||||
var togglePlugin = require('./togglePlugin');
|
||||
var isDefaultPlugin = require('./isDefaultPlugin');
|
||||
|
||||
/**
|
||||
Remove a plugin from a book's configuration
|
||||
|
||||
@param {Book} book
|
||||
@param {String} plugin
|
||||
@return {Book}
|
||||
*/
|
||||
function removePlugin(book, pluginName) {
|
||||
var config = book.getConfig();
|
||||
* Remove a plugin from a book's configuration
|
||||
* @param {Config} config
|
||||
* @param {String} plugin
|
||||
* @return {Config}
|
||||
*/
|
||||
function removePlugin(config, pluginName) {
|
||||
var deps = config.getPluginDependencies();
|
||||
|
||||
// For default plugin, we have to disable it instead of removing from the list
|
||||
if (isDefaultPlugin(pluginName)) {
|
||||
return togglePlugin(config, pluginName, false);
|
||||
}
|
||||
|
||||
deps = deps.filter(function(dep) {
|
||||
// Remove the dependency from the list
|
||||
deps = deps.filterNot(function(dep) {
|
||||
return dep.getName() === pluginName;
|
||||
});
|
||||
config = config.setPluginDependencies(deps);
|
||||
|
||||
return book.setConfig(config);
|
||||
return config.setPluginDependencies(deps);
|
||||
}
|
||||
|
||||
module.exports = removePlugin;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
var PluginDependency = require('../../models/pluginDependency');
|
||||
var hasPlugin = require('./hasPlugin');
|
||||
var isDefaultPlugin = require('./isDefaultPlugin');
|
||||
|
||||
/**
|
||||
* Enable/disable a plugin dependency
|
||||
* @param {Config} config
|
||||
* @param {String} pluginName
|
||||
* @param {Boolean} state (optional)
|
||||
* @return {Config}
|
||||
*/
|
||||
function togglePlugin(config, pluginName, state) {
|
||||
var deps = config.getPluginDependencies();
|
||||
|
||||
// For default plugin, we should ensure it's listed first
|
||||
if (isDefaultPlugin(pluginName) && !hasPlugin(deps, pluginName)) {
|
||||
deps = deps.push(PluginDependency.create(pluginName));
|
||||
}
|
||||
|
||||
deps = deps.map(function(dep) {
|
||||
if (dep.getName() === pluginName) {
|
||||
return dep.toggle(state);
|
||||
}
|
||||
|
||||
return dep;
|
||||
});
|
||||
|
||||
return config.setPluginDependencies(deps);
|
||||
}
|
||||
|
||||
module.exports = togglePlugin;
|
||||
@@ -0,0 +1,78 @@
|
||||
var Summary = require('../../../models/summary');
|
||||
var SummaryArticle = require('../../../models/summaryArticle');
|
||||
var File = require('../../../models/file');
|
||||
|
||||
describe('insertArticle', function() {
|
||||
var insertArticle = require('../insertArticle');
|
||||
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 insert an article at a given level', function() {
|
||||
var article = SummaryArticle.create({
|
||||
title: 'Inserted'
|
||||
}, 'fake.level');
|
||||
|
||||
var newSummary = insertArticle(summary, article, '2.1.1');
|
||||
|
||||
var inserted = newSummary.getByLevel('2.1.1');
|
||||
var nextOne = newSummary.getByLevel('2.1.2');
|
||||
|
||||
expect(inserted.getTitle()).toBe('Inserted');
|
||||
expect(inserted.getLevel()).toBe('2.1.1');
|
||||
|
||||
expect(nextOne.getTitle()).toBe('2.1.1');
|
||||
expect(nextOne.getLevel()).toBe('2.1.2');
|
||||
});
|
||||
|
||||
it('should insert an article in last position', function() {
|
||||
var article = SummaryArticle.create({
|
||||
title: 'Inserted'
|
||||
}, 'fake.level');
|
||||
|
||||
var newSummary = insertArticle(summary, article, '2.2');
|
||||
|
||||
var inserted = newSummary.getByLevel('2.2');
|
||||
var previousOne = newSummary.getByLevel('2.1');
|
||||
|
||||
expect(inserted.getTitle()).toBe('Inserted');
|
||||
expect(inserted.getLevel()).toBe('2.2');
|
||||
|
||||
expect(previousOne.getTitle()).toBe('2.1'); // Unchanged
|
||||
expect(previousOne.getLevel()).toBe('2.1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
var Summary = require('../../../models/summary');
|
||||
var SummaryPart = require('../../../models/summaryPart');
|
||||
|
||||
var File = require('../../../models/file');
|
||||
|
||||
describe('insertPart', function() {
|
||||
var insertPart = require('../insertPart');
|
||||
var summary = Summary.createFromParts(File(), [
|
||||
{
|
||||
articles: [
|
||||
{
|
||||
title: '1.1',
|
||||
path: '1.1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Part I',
|
||||
articles: [
|
||||
{
|
||||
title: '2.1',
|
||||
path: '2.1',
|
||||
articles: []
|
||||
},
|
||||
{
|
||||
title: '2.2',
|
||||
path: '2.2'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
it('should insert an part at a given level', function() {
|
||||
var part = SummaryPart.create({
|
||||
title: 'Inserted'
|
||||
}, 'meaningless.level');
|
||||
|
||||
var newSummary = insertPart(summary, part, 1);
|
||||
|
||||
var inserted = newSummary.getPart(1);
|
||||
expect(inserted.getTitle()).toBe('Inserted');
|
||||
expect(newSummary.getParts().count()).toBe(3);
|
||||
|
||||
var otherArticle = newSummary.getByLevel('3.1');
|
||||
expect(otherArticle.getTitle()).toBe('2.1');
|
||||
expect(otherArticle.getLevel()).toBe('3.1');
|
||||
});
|
||||
|
||||
it('should insert an part in last position', function() {
|
||||
var part = SummaryPart.create({
|
||||
title: 'Inserted'
|
||||
}, 'meaningless.level');
|
||||
|
||||
var newSummary = insertPart(summary, part, 2);
|
||||
|
||||
var inserted = newSummary.getPart(2);
|
||||
expect(inserted.getTitle()).toBe('Inserted');
|
||||
expect(newSummary.getParts().count()).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -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 to the same 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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
var Immutable = require('immutable');
|
||||
var Summary = require('../../../models/summary');
|
||||
var File = require('../../../models/file');
|
||||
|
||||
describe('moveArticleAfter', function() {
|
||||
var moveArticleAfter = require('../moveArticleAfter');
|
||||
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('moving right after itself should be invariant', function() {
|
||||
var newSummary = moveArticleAfter(summary, '2.1', '2.1');
|
||||
|
||||
expect(Immutable.is(summary, newSummary)).toBe(true);
|
||||
});
|
||||
|
||||
it('moving after previous one should be invariant too', function() {
|
||||
var newSummary = moveArticleAfter(summary, '2.1', '2.0');
|
||||
|
||||
expect(Immutable.is(summary, newSummary)).toBe(true);
|
||||
});
|
||||
|
||||
it('should move an article after a previous level', function() {
|
||||
var newSummary = moveArticleAfter(summary, '2.2', '2.0');
|
||||
var moved = newSummary.getByLevel('2.1');
|
||||
|
||||
expect(moved.getTitle()).toBe('2.2');
|
||||
expect(newSummary.getByLevel('2.2').getTitle()).toBe('2.1');
|
||||
});
|
||||
|
||||
it('should move an article after a previous and less deep level', function() {
|
||||
var newSummary = moveArticleAfter(summary, '2.1.1', '2.0');
|
||||
var moved = newSummary.getByLevel('2.1');
|
||||
|
||||
expect(moved.getTitle()).toBe('2.1.1');
|
||||
expect(newSummary.getByLevel('2.2.1').getTitle()).toBe('2.1.2');
|
||||
expect(newSummary.getByLevel('2.2').getTitle()).toBe('2.1');
|
||||
});
|
||||
|
||||
it('should move an article after a next level', function() {
|
||||
var newSummary = moveArticleAfter(summary, '2.1', '2.2');
|
||||
var moved = newSummary.getByLevel('2.2');
|
||||
|
||||
expect(moved.getTitle()).toBe('2.1');
|
||||
expect(newSummary.getByLevel('2.1').getTitle()).toBe('2.2');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
var Summary = require('../../../models/summary');
|
||||
var File = require('../../../models/file');
|
||||
|
||||
describe('removeArticle', function() {
|
||||
var removeArticle = require('../removeArticle');
|
||||
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 remove an article at a given level', function() {
|
||||
var newSummary = removeArticle(summary, '2.1.1');
|
||||
|
||||
var removed = newSummary.getByLevel('2.1.1');
|
||||
var nextOne = newSummary.getByLevel('2.1.2');
|
||||
|
||||
expect(removed.getTitle()).toBe('2.1.2');
|
||||
expect(nextOne).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
var mergeAtLevel = require('./mergeAtLevel');
|
||||
|
||||
/**
|
||||
Edit the ref of an article
|
||||
|
||||
@param {Summary} summary
|
||||
@param {String} level
|
||||
@param {String} newRef
|
||||
@return {Summary}
|
||||
*/
|
||||
function editArticleRef(summary, level, newRef) {
|
||||
return mergeAtLevel(summary, level, {
|
||||
ref: newRef
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = editArticleRef;
|
||||
@@ -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,8 +1,13 @@
|
||||
|
||||
module.exports = {
|
||||
insertArticle: require('./insertArticle'),
|
||||
moveArticle: require('./moveArticle'),
|
||||
moveArticleAfter: require('./moveArticleAfter'),
|
||||
removeArticle: require('./removeArticle'),
|
||||
unshiftArticle: require('./unshiftArticle'),
|
||||
editArticleTitle: require('./editArticleTitle'),
|
||||
editArticleRef: require('./editArticleRef'),
|
||||
|
||||
editPartTitle: require('./editPartTitle'),
|
||||
editArticleTitle: require('./editArticleTitle')
|
||||
insertPart: require('./insertPart'),
|
||||
removePart: require('./removePart'),
|
||||
editPartTitle: require('./editPartTitle')
|
||||
};
|
||||
|
||||
@@ -1,63 +1,49 @@
|
||||
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 {Article} article
|
||||
@param {String|Article} level: level to insert at
|
||||
@return {Summary}
|
||||
*/
|
||||
function insertArticle(summary, level, article) {
|
||||
function insertArticle(summary, article, level) {
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
var SummaryPart = require('../../models/summaryPart');
|
||||
var indexLevels = require('./indexLevels');
|
||||
|
||||
/**
|
||||
Returns a new Summary with a part inserted at given index
|
||||
|
||||
@param {Summary} summary
|
||||
@param {Part} part
|
||||
@param {Number} index
|
||||
@return {Summary}
|
||||
*/
|
||||
function insertPart(summary, part, index) {
|
||||
part = SummaryPart(part);
|
||||
|
||||
var parts = summary.getParts().insert(index, part);
|
||||
return indexLevels(summary.set('parts', parts));
|
||||
}
|
||||
|
||||
module.exports = insertPart;
|
||||
@@ -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,25 @@
|
||||
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 first
|
||||
var removed = removeArticle(summary, originLevel);
|
||||
return insertArticle(removed, article, targetLevel);
|
||||
}
|
||||
|
||||
module.exports = moveArticle;
|
||||
@@ -0,0 +1,60 @@
|
||||
var is = require('is');
|
||||
var removeArticle = require('./removeArticle');
|
||||
var insertArticle = require('./insertArticle');
|
||||
|
||||
/**
|
||||
Returns a new summary, with the an article moved after another
|
||||
article. Unlike `moveArticle`, does not ensure that the article
|
||||
will be found at the target's level plus one.
|
||||
|
||||
@param {Summary} summary
|
||||
@param {String|SummaryArticle} origin
|
||||
@param {String|SummaryArticle} afterTarget
|
||||
@return {Summary}
|
||||
*/
|
||||
function moveArticleAfter(summary, origin, afterTarget) {
|
||||
// Coerce to level
|
||||
var originLevel = is.string(origin)? origin : origin.getLevel();
|
||||
var afterTargetLevel = is.string(afterTarget)? afterTarget : afterTarget.getLevel();
|
||||
var article = summary.getByLevel(originLevel);
|
||||
|
||||
var targetLevel = increment(afterTargetLevel);
|
||||
|
||||
if (targetLevel < origin) {
|
||||
// Remove first
|
||||
var removed = removeArticle(summary, originLevel);
|
||||
// Insert then
|
||||
return insertArticle(removed, article, targetLevel);
|
||||
} else {
|
||||
// Insert right after first
|
||||
var inserted = insertArticle(summary, article, targetLevel);
|
||||
// Remove old one
|
||||
return removeArticle(inserted, originLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@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('.');
|
||||
}
|
||||
|
||||
function increment(level) {
|
||||
level = levelToArray(level);
|
||||
level[level.length - 1]++;
|
||||
return arrayToLevel(level);
|
||||
}
|
||||
|
||||
module.exports = moveArticleAfter;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,15 @@
|
||||
var indexLevels = require('./indexLevels');
|
||||
|
||||
/**
|
||||
Remove a part at given index
|
||||
|
||||
@param {Summary} summary
|
||||
@param {Number|} index
|
||||
@return {Summary}
|
||||
*/
|
||||
function removePart(summary, index) {
|
||||
var parts = summary.getParts().remove(index);
|
||||
return indexLevels(summary.set('parts', parts));
|
||||
}
|
||||
|
||||
module.exports = removePart;
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -1,9 +1,9 @@
|
||||
var generateMock = require('../generateMock');
|
||||
var generateMock = require('./generateMock');
|
||||
var EbookGenerator = require('../ebook');
|
||||
|
||||
describe('EbookGenerator', function() {
|
||||
|
||||
pit('should generate a SUMMARY.html', function() {
|
||||
it('should generate a SUMMARY.html', function() {
|
||||
return generateMock(EbookGenerator, {
|
||||
'README.md': 'Hello World'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
var tmp = require('tmp');
|
||||
|
||||
var Book = require('../../models/book');
|
||||
var createMockFS = require('../../fs/mock');
|
||||
var parseBook = require('../../parse/parseBook');
|
||||
var generateBook = require('../generateBook');
|
||||
|
||||
/**
|
||||
* 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);
|
||||
var dir;
|
||||
|
||||
try {
|
||||
dir = tmp.dirSync();
|
||||
} catch(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
book = book.setLogLevel('disabled');
|
||||
|
||||
return parseBook(book)
|
||||
.then(function(resultBook) {
|
||||
return generateBook(Generator, resultBook, {
|
||||
root: dir.name
|
||||
});
|
||||
})
|
||||
.thenResolve(dir.name);
|
||||
}
|
||||
|
||||
module.exports = generateMock;
|
||||
@@ -1,9 +1,9 @@
|
||||
var generateMock = require('../generateMock');
|
||||
var generateMock = require('./generateMock');
|
||||
var JSONGenerator = require('../json');
|
||||
|
||||
describe('JSONGenerator', function() {
|
||||
|
||||
pit('should generate a README.json', function() {
|
||||
it('should generate a README.json', function() {
|
||||
return generateMock(JSONGenerator, {
|
||||
'README.md': 'Hello World'
|
||||
})
|
||||
@@ -12,7 +12,7 @@ describe('JSONGenerator', function() {
|
||||
});
|
||||
});
|
||||
|
||||
pit('should generate a json file for each articles', function() {
|
||||
it('should generate a json file for each articles', function() {
|
||||
return generateMock(JSONGenerator, {
|
||||
'README.md': 'Hello World',
|
||||
'SUMMARY.md': '# Summary\n\n* [Page](test/page.md)',
|
||||
@@ -25,5 +25,22 @@ describe('JSONGenerator', function() {
|
||||
expect(folder).toHaveFile('test/page.json');
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a multilingual book', function() {
|
||||
return generateMock(JSONGenerator, {
|
||||
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
|
||||
'en': {
|
||||
'README.md': 'Hello'
|
||||
},
|
||||
'fr': {
|
||||
'README.md': 'Bonjour'
|
||||
}
|
||||
})
|
||||
.then(function(folder) {
|
||||
expect(folder).toHaveFile('en/README.json');
|
||||
expect(folder).toHaveFile('fr/README.json');
|
||||
expect(folder).toHaveFile('README.json');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user