mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 07:35:16 +00:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99253eecea | |||
| 916a02cb36 | |||
| 114c4abcd1 | |||
| e1e3d11932 | |||
| df46217c78 | |||
| d18cc92396 | |||
| 8f63b4b790 | |||
| ce7de93936 | |||
| ef2a24fe6f | |||
| 038cd0155b | |||
| fb430ac1ed | |||
| 53cfa3703b | |||
| 7ed9e8d145 | |||
| 93e0b0cbea | |||
| 0118bd3168 | |||
| 07d5245a5f | |||
| 6b844213ec | |||
| dabdeb8a80 | |||
| ecd56315e6 | |||
| 15b9e839e9 | |||
| d7f7dcd47b | |||
| b1981e09b8 | |||
| c9edd0a795 | |||
| d83e0716bb | |||
| aed38afff3 | |||
| 1df522e6ec | |||
| bd4ef2354e | |||
| 32a49a6969 | |||
| 21806687e6 | |||
| 7ade91a091 | |||
| 4e41f96d89 | |||
| 325c32b4c9 | |||
| 7d6606a8e5 | |||
| c887d520eb | |||
| e6050fc558 | |||
| cae4853334 | |||
| 9ef2bebfe9 | |||
| 53294af810 | |||
| 7d21c815a5 | |||
| e07719a86c | |||
| fa5bee6ca3 | |||
| 3a43ef2c3f | |||
| 55deab03cd | |||
| edda84fefd | |||
| 10b191f631 | |||
| d5a575c5e0 | |||
| 82d8c3e2d9 | |||
| bc0c8dbcf3 | |||
| de0245ff40 | |||
| 7f56ff0281 | |||
| 2cfbf5411c | |||
| f70da1c82a | |||
| 0b4db49f57 | |||
| fbffd54aa2 | |||
| 4d19a33af2 | |||
| 9d67a0e656 | |||
| 093005bfd9 | |||
| 514883f0bd | |||
| 27681aad98 | |||
| 0d367da776 | |||
| c499a8a13a | |||
| 64a6de751d | |||
| d39f0d6ff0 | |||
| bdbfbf7300 | |||
| ccac3d2c3c | |||
| f07278490f | |||
| f1880fc53c | |||
| e8fe4a2dea | |||
| 1d79507665 | |||
| f05dc67887 | |||
| ca5e57c3b7 | |||
| 6b1fabdd92 |
@@ -15,6 +15,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Multi-lingual books share assets folder
|
||||
- YAML front matter is parsed and can extend page's properties
|
||||
- Fix `uk` translation
|
||||
- Fix heading ID including dashes
|
||||
- Fix error in code highlighting for unknown languages
|
||||
- Accept SSH url as plugin version
|
||||
- Add templating blocks `markdown`, `asciidoc` and `markup`
|
||||
|
||||
## 2.6.7
|
||||
- Fix bug with filenames including spaces
|
||||
|
||||
@@ -12,7 +12,7 @@ You can publish and host books easily online using [gitbook.com](https://www.git
|
||||
|
||||
Check out the [GitBook Community Slack Channel](https://slack.gitbook.com), Stay updated by following [@GitBookIO](https://twitter.com/GitBookIO) on Twitter or [GitBook](https://www.facebook.com/gitbookcom) on Facebook.
|
||||
|
||||
Complete documentation is available at [help.gitbook.com](http://help.gitbook.com/).
|
||||
Complete documentation is available at [toolchain.gitbook.com](http://toolchain.gitbook.com/).
|
||||
|
||||

|
||||
|
||||
@@ -32,14 +32,14 @@ We're always happy to help out with your books or any other questions you might
|
||||
|
||||
## Features
|
||||
|
||||
* [Output as a website or ebook (pdf, epub, mobi)](http://help.gitbook.com/format/output.html)
|
||||
* [Multi-Languages](http://help.gitbook.com/format/languages.html)
|
||||
* [Glossary](http://help.gitbook.com/format/glossary.html)
|
||||
* [Cover](http://help.gitbook.com/format/cover.html)
|
||||
* [AsciiDoc Support](http://help.gitbook.com/format/asciidoc.html)
|
||||
* [Variables and Templating](http://help.gitbook.com/format/templating.html)
|
||||
* [Content References](http://help.gitbook.com/format/conrefs.html)
|
||||
* [Plugins](http://help.gitbook.com/format/plugins.html)
|
||||
* Write using [Markdown](http://toolchain.gitbook.com/syntax/markdown.html) or [AsciiDoc](http://toolchain.gitbook.com/syntax/asciidoc.html)
|
||||
* Output as a website or [ebook (pdf, epub, mobi)](http://toolchain.gitbook.com/ebook.html)
|
||||
* [Multi-Languages](http://toolchain.gitbook.com/languages.html)
|
||||
* [Lexicon / Glossary](http://toolchain.gitbook.com/lexicon.html)
|
||||
* [Cover](http://toolchain.gitbook.com/ebook.html)
|
||||
* [Variables and Templating](http://toolchain.gitbook.com/templating/)
|
||||
* [Content References](http://toolchain.gitbook.com/templating/conrefs.html)
|
||||
* [Plugins](http://toolchain.gitbook.com/plugins/)
|
||||
* [Beautiful default theme](https://github.com/GitbookIO/theme-default)
|
||||
|
||||
## Publish your book
|
||||
|
||||
@@ -3,7 +3,7 @@ var pkg = require('./package.json');
|
||||
module.exports = {
|
||||
// Documentation for GitBook is stored under "docs"
|
||||
root: './docs',
|
||||
title: 'GitBook Documentation',
|
||||
title: 'GitBook Toolchain Documentation',
|
||||
|
||||
// Enforce use of GitBook v3
|
||||
gitbook: pkg.version,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# GitBook Format Documentation
|
||||
# GitBook Toolchain Documentation
|
||||
|
||||
This document aims to be a comprehensive guide to GitBook. It contains the full documentation for version **{{ book.version }}**. Help for GitBook.com specific questions can be found at [help.gitbook.com](https://help.gitbook.com).
|
||||
|
||||
|
||||
+18
-2
@@ -12,16 +12,32 @@
|
||||
* [Configuration](config.md)
|
||||
* [Glossary](lexicon.md)
|
||||
* [Multi-Lingual](languages.md)
|
||||
* [AsciiDoc](asciidoc.md)
|
||||
* [Markdown](syntax/markdown.md)
|
||||
* [Headings](syntax/markdown.md#headings)
|
||||
* [Paragraphs](syntax/markdown.md#paragraphs)
|
||||
* [Lists](syntax/markdown.md#lists)
|
||||
* [Links](syntax/markdown.md#links)
|
||||
* [Images](syntax/markdown.md#images)
|
||||
* [Blockquotes](syntax/markdown.md#blockquotes)
|
||||
* [Tables](syntax/markdown.md#tables)
|
||||
* [Code](syntax/markdown.md#code)
|
||||
* [HTML](syntax/markdown.md#html)
|
||||
* [Footnotes](syntax/markdown.md#footnotes)
|
||||
* [AsciiDoc](syntax/asciidoc.md)
|
||||
* [eBook and PDF](ebook.md)
|
||||
|
||||
### Customization
|
||||
|
||||
* [Cover](cover.md)
|
||||
* [Templating](templating/README.md)
|
||||
* [Content References](templating/conrefs.md)
|
||||
* [Variables](templating/variables.md)
|
||||
* [Builtin](templating/builtin.md)
|
||||
* [Plugins](plugins/README.md)
|
||||
* [Create a plugin](plugins/create.md)
|
||||
* [Hooks](plugins/hooks.md)
|
||||
* [Blocks](plugins/blocks.md)
|
||||
* [Filters](plugins/filters.md)
|
||||
* [API & Context](plugins/api.md)
|
||||
* [Test your plugin](plugins/testing.md)
|
||||
* [Theming](themes/README.md)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends template.theme %}
|
||||
{% extends template.self %}
|
||||
|
||||
{% block header_nav %}
|
||||
<a href="https://github.com/GitbookIO/gitbook/blob/master/docs/{{ file.path }}" target="_blank" class="btn btn-link pull-right hidden-xs">
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Cover
|
||||
|
||||
Covers are used for all the ebook formats, and to make books more elegant on GitBook.com.
|
||||
|
||||
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.
|
||||
|
||||
### Best Sizes
|
||||
|
||||
| | Big | Small |
|
||||
|:-------------------:|:-----------:|:-----------------:|
|
||||
| **File** | `cover.jpg` | `cover_small.jpg` |
|
||||
| **Size(in pixels)** | 1800x2360 | 200x262 |
|
||||
|
||||
### Guidelines
|
||||
|
||||
A good cover respects the following guidelines:
|
||||
|
||||
* No border
|
||||
* Clearly visible book title
|
||||
* Any important text should be visible in the small version
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generating eBooks and PDFs
|
||||
|
||||
GitBook can generates a website, but can also output content as ebook (ePub, Mobi, PDF).
|
||||
|
||||
### Installing ebook-convert
|
||||
|
||||
`ebook-convert` is required to generate ebooks (epub, mobi, pdf).
|
||||
|
||||
##### OS X
|
||||
|
||||
Download the [Calibre application](https://calibre-ebook.com/download). After moving the `calibre.app` to your Applications folder create a symbolic link to the ebook-convert tool:
|
||||
|
||||
```
|
||||
$ sudo ln -s ~/Applications/calibre.app/Contents/MacOS/ebook-convert /usr/bin
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
A good cover should respect the following guidelines:
|
||||
|
||||
* Size of 1800x2360 (pixels)
|
||||
* No border
|
||||
* Clearly visible book title
|
||||
* Any important text should be visible in the small version
|
||||
|
||||
+22
@@ -1,5 +1,9 @@
|
||||
# GitBook FAQ
|
||||
|
||||
This page gathers common questions and answers concerning the GitBook format and toolchain.
|
||||
|
||||
Questions about GitBook.com and the Editor are gather into the [help.gitbook.com's FAQ](http://help.gitbook.com/faq.html).
|
||||
|
||||
#### How can I host/publish my book?
|
||||
|
||||
Books can easily be published and hosted on [GitBook.com](https://www.gitbook.com). But GitBook output can be hosted on any static file hosting solution.
|
||||
@@ -10,6 +14,20 @@ Any text editor should work! But we advise using the [GitBook Editor](https://ww
|
||||
|
||||
---
|
||||
|
||||
#### Does GitBook supports RTL/bi-directional text ?
|
||||
|
||||
The GitBook format supports right to left, and bi-directional writing. To enable it, you either need to specify a language (ex: `ar`), or force GitBook to use RTL in your `book.json`:
|
||||
|
||||
``` json
|
||||
{
|
||||
"language": "ar",
|
||||
"direction": "rtl"
|
||||
}
|
||||
```
|
||||
|
||||
With version 3.0 of GitBook, it's automatically detected according to the content.
|
||||
_Note that, while the output book will indeed respect RTL, the Editor doesn't support RTL writing yet_.
|
||||
|
||||
#### Should I use an `.html` or `.md` extensions in my links?
|
||||
|
||||
You should always use paths and the `.md` extensions when linking to your files, GitBook will automatically replace these paths by the appropriate link when the pointing file is referenced in the Table of Contents.
|
||||
@@ -18,6 +36,10 @@ You should always use paths and the `.md` extensions when linking to your files,
|
||||
|
||||
Yes, GitBooks can be created in [sub-directories](structure.md#subdirectory). GitBook.com and the CLI also looks by default in a serie of [folders](structure.md).
|
||||
|
||||
#### Does GitBook supports RTL languages?
|
||||
|
||||
Yes, GitBook automatically detect the direction in your pages (`rtl` or `ltr`) and adjust the layout accordingly. The direction can also be specified globally in the [book.json](config.md).
|
||||
|
||||
---
|
||||
|
||||
#### Does GitBook support Math equations?
|
||||
|
||||
+26
-2
@@ -23,7 +23,27 @@ Adding a nested list to a parent chapter will create subchapters.
|
||||
|
||||
Each chapter has a dedicated page (`part#/README.md`) and is split into subchapters.
|
||||
|
||||
##### Example with subchapters split into parts
|
||||
##### Anchors
|
||||
|
||||
Chapters in the Table of Contents can be pointing to specific part of a file using anchor.
|
||||
|
||||
```markdown
|
||||
# Summary
|
||||
|
||||
### Part I
|
||||
|
||||
* [Part I](part1/README.md)
|
||||
* [Writing is nice](part1/README.md#writing)
|
||||
* [GitBook is nice](part1/README.md#gitbook)
|
||||
* [Part II](part2/README.md)
|
||||
* [We love feedback](part2/README.md#feedback)
|
||||
* [Better tools for authors](part2/README.md#tools)
|
||||
```
|
||||
|
||||
|
||||
##### Parts
|
||||
|
||||
The Table of Contents can be divided into parts separated by headings or horizontal lines:
|
||||
|
||||
```markdown
|
||||
# Summary
|
||||
@@ -37,9 +57,13 @@ Each chapter has a dedicated page (`part#/README.md`) and is split into subchapt
|
||||
|
||||
* [We love feedback](part2/feedback_please.md)
|
||||
* [Better tools for authors](part2/better_tools.md)
|
||||
|
||||
----
|
||||
|
||||
* [Last part without title](part3/title.md)
|
||||
```
|
||||
|
||||
Here, parts are just groups of chapters and do not have dedicated pages, but will show in the navigation for example.
|
||||
Parts are just groups of chapters and do not have dedicated pages, but according to the theme, it will show in the navigation.
|
||||
|
||||
### Pages
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Context and APIs
|
||||
|
||||
GitBooks provides different APIs and contexts to plugins. These APIs can vary according to the GitBook version being used, your plugin should specify the `engines.gitbook` field in `package.json` accordingly.
|
||||
|
||||
#### Book instance
|
||||
|
||||
The `Book` class is the central point of GitBook, it centralize all access read methods. This class is defined in [book.js](https://github.com/GitbookIO/gitbook/blob/master/lib/book.js).
|
||||
|
||||
```js
|
||||
// Read configuration from book.json
|
||||
var value = book.config.get('title', 'Default Value');
|
||||
|
||||
// Resolve a filename to an absolute path
|
||||
var filepath = book.resolve('README.md');
|
||||
|
||||
// Render an inline markup string
|
||||
book.renderInline('markdown', 'This is **Markdown**')
|
||||
.then(function(str) { ... })
|
||||
|
||||
// Render a markup string (block mode)
|
||||
book.renderBlock('markdown', '* This is **Markdown**')
|
||||
.then(function(str) { ... })
|
||||
```
|
||||
|
||||
#### Output instance
|
||||
|
||||
The `Output` class represent the output/write process.
|
||||
|
||||
```js
|
||||
// Return root folder for the output
|
||||
var root = output.root();
|
||||
|
||||
// Resolve a file in the output folder
|
||||
var filepath = output.resolve('myimage.png');
|
||||
|
||||
// Convert a filename to an URL (returns a path to an html file)
|
||||
var fileurl = output.toURL('mychapter/README.md');
|
||||
|
||||
// Write a file in the output folder
|
||||
output.write('hello.txt', 'Hello World')
|
||||
.then(function() { ... });
|
||||
|
||||
// Copy a file to the output folder
|
||||
output.copyFile('./myfile.jpg', 'cover.jpg')
|
||||
.then(function() { ... });
|
||||
|
||||
// Verify that a file exists
|
||||
output.hasFile('hello.txt')
|
||||
.then(function(exists) { ... });
|
||||
```
|
||||
|
||||
#### Page instance
|
||||
|
||||
A page instance represent the current parsed page.
|
||||
|
||||
```js
|
||||
// Title of the page (from SUMMARY)
|
||||
page.title
|
||||
|
||||
// Content of the page (Markdown/Asciidoc/HTML according to the stage)
|
||||
page.content
|
||||
|
||||
// Relative path in the book
|
||||
page.path
|
||||
|
||||
// Absolute path to the file
|
||||
page.rawPath
|
||||
|
||||
// Type of parser used for this file
|
||||
page.type ('markdown' or 'asciidoc')
|
||||
```
|
||||
|
||||
#### Context for Blocks and Filters
|
||||
|
||||
Blocks and filters have access to the same context, this context is bind to the template engine execution:
|
||||
|
||||
```js
|
||||
{
|
||||
// Current templating syntax
|
||||
"ctx": {
|
||||
// For example, after a {% set message = "hello" %}
|
||||
"message": "hello"
|
||||
},
|
||||
|
||||
// Book instance
|
||||
"book" <Book>,
|
||||
|
||||
// Output instance
|
||||
"output": <Output>
|
||||
}
|
||||
```
|
||||
|
||||
For example a filter or block function can access the current book using: `this.book`.
|
||||
|
||||
#### Context for Hooks
|
||||
|
||||
Hooks only have access to the `<Book>` instance using `this.book`.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Extend Blocks
|
||||
|
||||
Extending templating blocks is the best way to provide extra functionalities to authors.
|
||||
|
||||
The most common usage is to process the content within some tags at runtime. It's like [filters](./filters.md), but on steroids because you aren't confined to a single expression.
|
||||
|
||||
### Defining a new block
|
||||
|
||||
Blocks are defined by the plugin, blocks is a map of name associated with a block descriptor. The block descriptor needs to contain at least a `process` method.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
blocks: {
|
||||
tag1: {
|
||||
process: function(block) {
|
||||
return "Hello "+block.body+", How are you?";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `process` should return the html content that will replace the tag. Refer to [Context and APIs](./api.md) to learn more about `this` and GitBook API.
|
||||
|
||||
### Handling block arguments
|
||||
|
||||
Arguments can be passed to blocks:
|
||||
|
||||
```
|
||||
{% tag1 "argument 1", "argument 2", name="Test" %}
|
||||
This is the body of the block.
|
||||
{% endtag1 %}
|
||||
```
|
||||
|
||||
And arguments are easily accessible in the `process` method:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
blocks: {
|
||||
tag1: {
|
||||
process: function(block) {
|
||||
// block.args equals ["argument 1", "argument 2"]
|
||||
// block.kwargs equals { "name": "Test" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Handling sub-blocks
|
||||
|
||||
A defined block can be parsed into different sub-blocks, for example let's consider the source:
|
||||
|
||||
```
|
||||
{% myTag %}
|
||||
Main body
|
||||
{% subblock1 %}
|
||||
Body of sub-block 1
|
||||
{% subblock 2 %}
|
||||
Body of sub-block 1
|
||||
{% endmyTag %}
|
||||
```
|
||||
+12
-1
@@ -8,13 +8,24 @@ A GitBook plugin is a node package published on NPM that follow a defined conven
|
||||
|
||||
The `package.json` is a manifest format for describing **Node.js modules**. GitBook plugins are built on top of Node modules. It declares dependencies, version, ownership, and other information required to run a plugin in GitBook. This document describes the schema in detail.
|
||||
|
||||
```
|
||||
A plugin manifest `package.json` can also contain details about the required configuration. The configuration schema is defined in the `gitbook` field of the `package.json` (This field follow the [JSON-Schema](http://json-schema.org) guidelines):
|
||||
|
||||
```js
|
||||
{
|
||||
"name": "gitbook-plugin-mytest",
|
||||
"version": "0.0.1",
|
||||
"description": "This is my first GitBook plugin",
|
||||
"engines": {
|
||||
"gitbook": ">1.x.x"
|
||||
},
|
||||
"gitbook": {
|
||||
"properties": {
|
||||
"myConfigKey": {
|
||||
"type": "string",
|
||||
"default": "it's the default value",
|
||||
"description": "It defines my awesome config!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Extend Filters
|
||||
|
||||
Filters are essentially functions that can be applied to variables. They are called with a pipe operator (`|`) and can take arguments.
|
||||
|
||||
```
|
||||
{{ foo | title }}
|
||||
{{ foo | join(",") }}
|
||||
{{ foo | replace("foo", "bar") | capitalize }}
|
||||
```
|
||||
|
||||
### Defining a new filter
|
||||
|
||||
Plugins can extend filters by defining custom functions in their entry point under the `filters` scope.
|
||||
|
||||
A filter function takes as first argument the content to filter, and should return the new content.
|
||||
Refer to [Context and APIs](./api.md) to learn more about `this` and GitBook API.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
filters: {
|
||||
hello: function(name) {
|
||||
return 'Hello '+name;
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The filter `hello` can then be used in the book:
|
||||
|
||||
```
|
||||
{{ "Aaron"|hello }}, how are you?
|
||||
```
|
||||
|
||||
### Handling block arguments
|
||||
|
||||
Arguments can be passed to filters:
|
||||
|
||||
```
|
||||
Hello {{ "Samy"|fullName("Pesse", man=true}} }}
|
||||
```
|
||||
|
||||
Arguments are passed to the function, named-arguments are passed as a last argument (object).
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
filters: {
|
||||
fullName: function(firstName, lastName, kwargs) {
|
||||
var name = firstName + ' ' + lastName;
|
||||
|
||||
if (kwargs.man) name = "Mr" + name;
|
||||
else name = "Mrs" + name;
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# Hooks
|
||||
|
||||
Hooks is a method of augmenting or altering the behavior of the process, with custom callbacks.
|
||||
|
||||
### List of hooks
|
||||
|
||||
### Relative to the global pipeline
|
||||
|
||||
| Name | Description | Arguments |
|
||||
| ---- | ----------- | --------- |
|
||||
| `init` | Called after parsing the book, before generating output and pages. | None |
|
||||
| `finish:before` | Called after generating the pages, before copying assets, cover, ... | None |
|
||||
| `finish` | Called after everything else. | None |
|
||||
|
||||
### Relative to the page pipeline
|
||||
|
||||
> It is recommended using [templating](./templating.md) to extend page parsing.
|
||||
|
||||
| Name | Description | Arguments |
|
||||
| ---- | ----------- | --------- |
|
||||
| `page:before` | Called before running the templating engine on the page | Page Object |
|
||||
| `page` | Called before outputting and indexing the page. | Page Object |
|
||||
|
||||
##### Page Object
|
||||
|
||||
```js
|
||||
{
|
||||
// Parser named
|
||||
"type": "markdown",
|
||||
|
||||
// File Path relative to book root
|
||||
"path": "page.md",
|
||||
|
||||
// Absolute file path
|
||||
"rawpath": "/usr/...",
|
||||
|
||||
// Title of the page in the SUMMARY
|
||||
"title": "",
|
||||
|
||||
// Content of the page
|
||||
// Markdown/Asciidoc in "page:before"
|
||||
// HTML in "page"
|
||||
"content": "# Hello"
|
||||
}
|
||||
```
|
||||
|
||||
##### Example to add a title
|
||||
|
||||
In the `page:before` hook, `page.content` is the markdown/asciidoc content.
|
||||
|
||||
```js
|
||||
{
|
||||
"page:before": function(page) {
|
||||
page.content = "# Title\n" +page.content;
|
||||
return page;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Example to replace some html
|
||||
|
||||
In the `page` hook, `page.content` is the HTML generated from the markdown/asciidoc conversion.
|
||||
|
||||
```js
|
||||
{
|
||||
"page": function(page) {
|
||||
page.content = page.content.replace("<b>", "<strong>")
|
||||
.replace("</b>", "</strong>");
|
||||
return page;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Asynchronous Operations
|
||||
|
||||
Hooks callbacks can be asynchronous and return promises.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
{
|
||||
"init": function() {
|
||||
return writeSomeFile()
|
||||
.then(function() {
|
||||
return writeAnotherFile();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
# Markdown
|
||||
|
||||
Most of the examples from this documentation are in Markdown. Markdown is default parser for GitBook, but one can also opt for the [AsciiDoc syntax](asciidoc.md).
|
||||
|
||||
Here’s an overview of Markdown syntax that you can use with GitBook (same as GitHub with some additions).
|
||||
|
||||
### Headings
|
||||
|
||||
To create a heading, add one to six `#` symbols before your heading text. The number of # you use will determine the size of the heading.
|
||||
|
||||
```markdown
|
||||
# This is an <h1> tag
|
||||
## This is an <h2> tag
|
||||
###### This is an <h6> tag
|
||||
```
|
||||
|
||||
GitBook supports a nice way for explicitly setting the header ID. If you follow the header text with an opening curly bracket (separated from the text with a least one space), a hash, the ID and a closing curly bracket, the ID is set on the header. If you use the trailing hash feature of atx style headers, the header ID has to go after the trailing hashes. For example:
|
||||
|
||||
```markdown
|
||||
Hello {#id}
|
||||
-----
|
||||
|
||||
# Hello {#id}
|
||||
|
||||
# Hello # {#id}
|
||||
```
|
||||
|
||||
### Paragraphs and Line Breaks {#paragraphs}
|
||||
|
||||
A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line — a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.
|
||||
|
||||
```
|
||||
Here's a line for us to start with.
|
||||
|
||||
This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
|
||||
```
|
||||
|
||||
### Emphasis {#emphasis}
|
||||
|
||||
```markdown
|
||||
*This text will be italic*
|
||||
_This will also be italic_
|
||||
|
||||
**This text will be bold**
|
||||
__This will also be bold__
|
||||
|
||||
~~This text will be crossed out.~~
|
||||
|
||||
_You **can** combine them_
|
||||
```
|
||||
|
||||
### Lists {#lists}
|
||||
|
||||
Markdown supports ordered (numbered) and unordered (bulleted) lists.
|
||||
|
||||
##### Unordered
|
||||
|
||||
Unordered lists use asterisks, pluses, and hyphens — interchangably — as list markers:
|
||||
|
||||
```markdown
|
||||
* Item 1
|
||||
* Item 2
|
||||
* Item 2a
|
||||
* Item 2b
|
||||
```
|
||||
|
||||
##### Ordered
|
||||
|
||||
Ordered lists use numbers followed by periods:
|
||||
|
||||
```markdown
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
3. Item 3
|
||||
* Item 3a
|
||||
* Item 3b
|
||||
```
|
||||
|
||||
### Links {#links}
|
||||
|
||||
Markdown supports two style of links: inline and reference.
|
||||
|
||||
A simple link can be created by surrounding the text with square brackets and the link URL with parentheses:
|
||||
|
||||
```markdown
|
||||
This is [an example](http://example.com/ "Title") inline link with a title.
|
||||
|
||||
[This link](http://example.net/) has no title attribute.
|
||||
```
|
||||
|
||||
Links can point to relative paths, anchors or absolute urls.
|
||||
|
||||
|
||||
### References
|
||||
|
||||
There is another way to create links which does not interrupt the text flow. The URL and title are defined using a reference name and this reference name is then used in square brackets instead of the link URL:
|
||||
|
||||
```markdown
|
||||
This is [an example][id] reference-style link.
|
||||
```
|
||||
|
||||
Then, anywhere in the document, you define your link label like this, on a line by itself:
|
||||
|
||||
```markdown
|
||||
[id]: http://example.com/ "Optional Title Here"
|
||||
```
|
||||
|
||||
### Images {#images}
|
||||
|
||||
Images can be created in a similar way than links: just use an exclamation mark before the square brackets. The link text will become the alternative text of the image and the link URL specifies the image source:
|
||||
|
||||
```markdown
|
||||
An image: 
|
||||
```
|
||||
|
||||
### Blockquotes {#blockquotes}
|
||||
|
||||
A blockquote is started using the `>` marker followed by an optional space; all following lines that are also started with the blockquote marker belong to the blockquote. You can use any block-level elements inside a blockquote:
|
||||
|
||||
```markdown
|
||||
As Kanye West said:
|
||||
|
||||
> We're living the future so
|
||||
> the present is our past.
|
||||
```
|
||||
|
||||
### Tables {#tables}
|
||||
|
||||
You can create tables by assembling a list of words and dividing them with hyphens `-` (for the first row), and then separating each column with a pipe `|`:
|
||||
|
||||
```markdown
|
||||
| First Header | Second Header |
|
||||
| ------------- | ------------- |
|
||||
| Content Cell | Content Cell |
|
||||
| Content Cell | Content Cell |
|
||||
```
|
||||
|
||||
The pipes on either end of the table are optional. Cells can vary in width and do not need to be perfectly aligned within columns. There must be at least three hyphens in each column of the header row.
|
||||
|
||||
### Code {#code}
|
||||
|
||||
Markdown supports two different code block styles. One uses lines indented with either four spaces or one tab whereas the other uses lines with tilde characters as delimiters – therefore the content does not need to be indented:
|
||||
|
||||
```markdown
|
||||
This is a sample code block.
|
||||
|
||||
Continued here.
|
||||
|
||||
```
|
||||
|
||||
##### Fenced code blocks
|
||||
|
||||
You can create fenced code blocks by placing triple backticks ` ``` ` before and after the code block. We recommend placing a blank line before and after code blocks to make the raw formatting easier to read.
|
||||
|
||||
```
|
||||
function test() {
|
||||
console.log("notice the blank line before this function?");
|
||||
}
|
||||
```
|
||||
|
||||
##### Syntax highlighting
|
||||
|
||||
You can add an optional language identifier to enable syntax highlighting in your fenced code block.
|
||||
|
||||
For example, to syntax highlight Ruby code:
|
||||
|
||||
```ruby
|
||||
require 'redcarpet'
|
||||
markdown = Redcarpet.new("Hello World!")
|
||||
puts markdown.to_html
|
||||
```
|
||||
|
||||
##### Inline code
|
||||
|
||||
Text phrases can be marked up as code by surrounding them with backticks:
|
||||
|
||||
Use `gitbook` to convert the `text` in markdown
|
||||
syntax to HTML.
|
||||
|
||||
### Footnotes
|
||||
|
||||
GitBook supports a simple syntax for such footnotes. Footnotes are relative to each pages.
|
||||
|
||||
```markdown
|
||||
Text prior to footnote reference.[^2]
|
||||
|
||||
[^2]: Comment to include in footnote.
|
||||
```
|
||||
|
||||
### HTML
|
||||
|
||||
GitBook supports use of raw HTML in your text, Markdown syntax in HTML is not processed:
|
||||
|
||||
```
|
||||
<div>
|
||||
Markdown here will not be **parsed**
|
||||
</div>
|
||||
```
|
||||
|
||||
### Horizontal Rule
|
||||
|
||||
Horizontal Rules can be inserted using three or more asterisks, dashes or underscores, optionally separated by spaces or tabs, on an otherwise blank line:
|
||||
|
||||
```markdown
|
||||
Three or more...
|
||||
|
||||
---
|
||||
|
||||
Hyphens
|
||||
|
||||
***
|
||||
|
||||
Asterisks
|
||||
|
||||
```
|
||||
|
||||
### Ignoring Markdown formatting
|
||||
|
||||
You can tell GitBook to ignore (or escape) Markdown formatting by using `\` before the Markdown character.
|
||||
|
||||
```
|
||||
Let's rename \*our-new-project\* to \*our-old-project\*.
|
||||
```
|
||||
@@ -87,3 +87,13 @@ Current version is {{ softwareVersion }}.
|
||||
##### include and block
|
||||
|
||||
Inclusion and inheritance is detailled in the [Content References](conrefs.md) section.
|
||||
|
||||
### Escaping
|
||||
|
||||
If you want GitBook to ignore any of the special templating tags, you can use raw and anything inside of it will be output as plain text.
|
||||
|
||||
``` twig
|
||||
{% raw %}
|
||||
this will {{ not be processed }}
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Builtin Templating Helpers
|
||||
|
||||
GitBook provides a serie of builtin filters and blocks to help you write templates.
|
||||
|
||||
### Filters
|
||||
|
||||
`value|default(default, [boolean])`If value is strictly undefined, return default, otherwise value. If boolean is true, any JavaScript falsy value will return default (false, "", etc)
|
||||
|
||||
`arr|sort(reverse, caseSens, attr)`
|
||||
Sort arr with JavaScript's arr.sort function. If reverse is true, result will be reversed. Sort is case-insensitive by default, but setting caseSens to true makes it case-sensitive. If attr is passed, will compare attr from each item.
|
||||
|
||||
### Blocks
|
||||
|
||||
`{% markdown %}Markdown string{% endmarkdown %}`
|
||||
Render inline markdown
|
||||
|
||||
`{% asciidoc %}AsciiDoc string{% endasciidoc %}`
|
||||
Render inline asciidoc
|
||||
@@ -12,6 +12,7 @@ The following is a reference of the available data during book's parsing and the
|
||||
| `file` | File associated with the current page specific information |
|
||||
| `summary` | Information about the table of contents |
|
||||
| `languages` | List of languages for multi-lingual books |
|
||||
| `output` | Information about the output generator |
|
||||
| `config` | Dump of the `book.json` |
|
||||
|
||||
### Book Variables
|
||||
@@ -62,3 +63,11 @@ The whole table of contents (`SUMMARY.md`) can be accessed:
|
||||
| `languages.list` | List of languages for this book |
|
||||
|
||||
Languages are defined by `{ id: 'en', title: 'English' }`.
|
||||
|
||||
### Output Variables
|
||||
|
||||
| Variable | Description |
|
||||
| -------- | ----------- |
|
||||
| `output.name` | Name of the output generator, possible values are `website`, `json`, `ebook` |
|
||||
| `output.format` | When `output.name == "ebook"`, `format` defines the ebook format that will be generated, possible values are `pdf`, `epub` or `mobi` |
|
||||
|
||||
|
||||
Vendored
+3
-5
@@ -1,14 +1,12 @@
|
||||
# Theming
|
||||
|
||||
Since version 3.0.0, GitBook can be easily themed. Books are using by default the [theme-default](https://github.com/GitbookIO/theme-default).
|
||||
|
||||
The theme to use is specified in the [book's configuration](config.md) using key `theme`.
|
||||
Since version 3.0.0, GitBook can be easily themed. Books use the [theme-default](https://github.com/GitbookIO/theme-default) theme by default.
|
||||
|
||||
> **Caution**: Custom theming can block some plugins from working correctly.
|
||||
|
||||
### Structure of a theme
|
||||
|
||||
A theme is a folder containing templates and assets. All the templates are optionnal, since theme are always extending the default theme.
|
||||
A theme is a plugin containing templates and assets. Overriding any individual template is optional, since themes always extend the default theme.
|
||||
|
||||
| Folder | Description |
|
||||
| -------- | ----------- |
|
||||
@@ -19,7 +17,7 @@ A theme is a folder containing templates and assets. All the templates are optio
|
||||
|
||||
### Extend/Customize theme in a book
|
||||
|
||||
Authors can extend the templates of a theme directly from the book 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
|
||||
|
||||
### Publish a theme
|
||||
|
||||
|
||||
+18
-1
@@ -320,7 +320,7 @@ Book.prototype.findParsableFile = function(filename) {
|
||||
if (!realFilepath) return null;
|
||||
|
||||
return {
|
||||
parser: parsers.get(ext),
|
||||
parser: parsers.getByExt(ext),
|
||||
path: realFilepath
|
||||
};
|
||||
});
|
||||
@@ -359,6 +359,23 @@ Book.prototype.isInLanguageBook = function(filename) {
|
||||
});
|
||||
};
|
||||
|
||||
// ----- Parser Methods
|
||||
|
||||
// Render a markup string in inline mode
|
||||
Book.prototype.renderInline = function(type, src) {
|
||||
var parser = parsers.get(type);
|
||||
return parser.inline(src)
|
||||
.get('content');
|
||||
};
|
||||
|
||||
// Render a markup string in block mode
|
||||
Book.prototype.renderBlock = function(type, src) {
|
||||
var parser = parsers.get(type);
|
||||
return parser.page(src)
|
||||
.get('content');
|
||||
};
|
||||
|
||||
|
||||
// ----- DEPRECATED METHODS
|
||||
|
||||
Book.prototype.contentLink = error.deprecateMethod(function(s) {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ function watch(dir) {
|
||||
dir = path.resolve(dir);
|
||||
|
||||
var toWatch = [
|
||||
'book.json', 'book.js'
|
||||
'book.json', 'book.js', '_layouts/**'
|
||||
];
|
||||
|
||||
// Watch all parsable files
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ Config.prototype.load = function() {
|
||||
throw new Error('GitBook version doesn\'t satisfy version required by the book: '+that.options.gitbook);
|
||||
}
|
||||
if (that.options.gitbook != '*' && !semver.satisfies(semver.inc(gitbook.version, 'patch'), that.options.gitbook)) {
|
||||
that.log.warn.ln('gitbook version specified in your book.json might be too strict for future patches, \''+(_.first(gitbook.version.split('.'))+'.x.x')+'\' is more adequate');
|
||||
that.log.warn.ln('gitbook version specified in your book.json might be too strict for future patches, \'>='+(_.first(gitbook.version.split('.'))+'.x.x')+'\' is more adequate');
|
||||
}
|
||||
|
||||
that.options.plugins = plugins.toList(that.options.plugins);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
// Default plugins added to each books
|
||||
var DEFAULT_PLUGINS = ['highlight', 'search', 'sharing', 'fontsettings', 'theme-default'];
|
||||
var DEFAULT_PLUGINS = ['highlight', 'search', 'lunr', 'sharing', 'fontsettings', 'theme-default'];
|
||||
|
||||
// Return true if a plugin is a default plugin
|
||||
function isDefaultPlugin(name, version) {
|
||||
@@ -22,7 +22,7 @@ function normalizePluginsList(plugins) {
|
||||
|
||||
var parts = plugin.split('@');
|
||||
var name = parts[0];
|
||||
var version = parts[1];
|
||||
var version = parts.slice(1).join('@');
|
||||
return {
|
||||
'name': name,
|
||||
'version': version // optional
|
||||
|
||||
+31
-4
@@ -5,8 +5,10 @@ var path = require('path');
|
||||
var Promise = require('../utils/promise');
|
||||
var pathUtil = require('../utils/path');
|
||||
var location = require('../utils/location');
|
||||
var error = require('../utils/error');
|
||||
var PluginsManager = require('../plugins');
|
||||
var TemplateEngine = require('../template');
|
||||
var gitbook = require('../gitbook');
|
||||
|
||||
/*
|
||||
Output is like a stream interface for a parsed book
|
||||
@@ -35,8 +37,14 @@ function Output(book, opts, parent) {
|
||||
|
||||
// Files to ignore in output
|
||||
this.ignore = Ignore();
|
||||
|
||||
// Hack to inherits from rules of the book
|
||||
this.ignore.add(this.book.ignore);
|
||||
}
|
||||
|
||||
// Default name for generator
|
||||
Output.prototype.name = 'base';
|
||||
|
||||
// Default extension for output
|
||||
Output.prototype.defaultExtension = '.html';
|
||||
|
||||
@@ -199,6 +207,10 @@ Output.prototype.onOutputSVG = function(page, svg) {
|
||||
// Output an image as a file
|
||||
// Normalize the relative link
|
||||
Output.prototype.onOutputImage = function(page, imgFile) {
|
||||
if (location.isExternal(imgFile)) {
|
||||
return imgFile;
|
||||
}
|
||||
|
||||
imgFile = page.resolveLocal(imgFile);
|
||||
return page.relative(imgFile);
|
||||
};
|
||||
@@ -221,16 +233,31 @@ Output.prototype.onLanguageBook = function(book) {
|
||||
|
||||
// ---- Utilities ----
|
||||
|
||||
// Return conetxt for the output itself
|
||||
Output.prototype.getSelfContext = function() {
|
||||
return {
|
||||
name: this.name
|
||||
};
|
||||
};
|
||||
|
||||
// Return a default context for templates
|
||||
Output.prototype.getContext = function() {
|
||||
return _.extend(
|
||||
{},
|
||||
var ctx = _.extend(
|
||||
{
|
||||
output: this.getSelfContext()
|
||||
},
|
||||
this.book.getContext(),
|
||||
this.book.langs.getContext(),
|
||||
(this.book.isLanguageBook()? this.book.parent: this.book).langs.getContext(),
|
||||
this.book.summary.getContext(),
|
||||
this.book.glossary.getContext(),
|
||||
this.book.config.getContext()
|
||||
this.book.config.getContext(),
|
||||
gitbook.getContext()
|
||||
);
|
||||
|
||||
// Deprecated fields
|
||||
error.deprecateField(ctx.gitbook, 'generator', this.name, '"gitbook.generator" property is deprecated, use "output.name" instead');
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
// Resolve a file path in the context of a specific page
|
||||
|
||||
@@ -21,6 +21,15 @@ var EbookOutput = assetsInliner(_EbookOutput);
|
||||
|
||||
EbookOutput.prototype.name = 'ebook';
|
||||
|
||||
// Return context for templating
|
||||
// Incldue type of ebbook generated
|
||||
EbookOutput.prototype.getSelfContext = function() {
|
||||
var ctx = EbookOutput.super_.prototype.getSelfContext.apply(this);
|
||||
ctx.format = this.opts.format;
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
// Finish generation, create ebook using ebook-convert
|
||||
EbookOutput.prototype.finish = function() {
|
||||
var that = this;
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ JSONOutput.prototype.onPage = function(page) {
|
||||
|
||||
// Write as json
|
||||
.then(function() {
|
||||
var json = page.getContext();
|
||||
var json = page.getOutputContext(that);
|
||||
|
||||
// Delete some private properties
|
||||
delete json.config;
|
||||
|
||||
+34
-50
@@ -8,13 +8,10 @@ var Promise = require('../utils/promise');
|
||||
var location = require('../utils/location');
|
||||
var fs = require('../utils/fs');
|
||||
var defaultFilters = require('../template/filters');
|
||||
var FSLoader = require('../template/fs-loader');
|
||||
var conrefsLoader = require('./conrefs');
|
||||
var Output = require('./base');
|
||||
|
||||
// Tranform a theme ID into a plugin
|
||||
function themeID(plugin) {
|
||||
return 'theme-' + plugin;
|
||||
}
|
||||
|
||||
// Directory for a theme with the templates
|
||||
function templatesPath(dir) {
|
||||
@@ -57,32 +54,11 @@ WebsiteOutput.prototype.prepare = function() {
|
||||
})
|
||||
|
||||
.then(function() {
|
||||
var themeName = that.book.config.get('theme');
|
||||
that.theme = that.plugins.get(themeID(themeName));
|
||||
that.themeDefault = that.plugins.get(themeID('default'));
|
||||
|
||||
if (!that.theme) {
|
||||
throw new Error('Theme "' + themeName + '" is not installed, add "' + themeID(themeName) + '" to your "book.json"');
|
||||
}
|
||||
|
||||
if (that.themeDefault.root != that.theme.root) {
|
||||
that.log.info.ln('build using theme "' + themeName + '"');
|
||||
}
|
||||
|
||||
// This list is ordered to give priority to templates in the book
|
||||
var searchPaths = _.chain([
|
||||
// The book itself can contains a "_layouts" folder
|
||||
that.book.root,
|
||||
var searchPaths = _.pluck(that.plugins.list(), 'root');
|
||||
|
||||
// Installed plugin (it can be identical to themeDefault.root)
|
||||
that.theme.root,
|
||||
|
||||
// Is default theme still installed
|
||||
that.themeDefault? that.themeDefault.root : null
|
||||
])
|
||||
.compact()
|
||||
.uniq()
|
||||
.value();
|
||||
// The book itself can contains a "_layouts" folder
|
||||
searchPaths.unshift(that.book.root);
|
||||
|
||||
// Load i18n
|
||||
_.each(searchPaths.concat().reverse(), function(searchPath) {
|
||||
@@ -92,7 +68,7 @@ WebsiteOutput.prototype.prepare = function() {
|
||||
that.i18n.load(i18nRoot);
|
||||
});
|
||||
|
||||
that.env = new nunjucks.Environment(new nunjucks.FileSystemLoader(_.map(searchPaths, templatesPath)));
|
||||
that.env = new nunjucks.Environment(new FSLoader(_.map(searchPaths, templatesPath)));
|
||||
|
||||
// Add GitBook default filters
|
||||
_.each(defaultFilters, function(fn, filter) {
|
||||
@@ -120,6 +96,14 @@ WebsiteOutput.prototype.prepare = function() {
|
||||
return that.toURL(s);
|
||||
});
|
||||
|
||||
// Get an article using its path
|
||||
that.env.addFilter('getArticleByPath', function(s) {
|
||||
var article = that.book.summary.getArticle(s);
|
||||
if (!article) return undefined;
|
||||
|
||||
return article.getContext();
|
||||
});
|
||||
|
||||
// Relase path to an asset
|
||||
that.env.addFilter('resolveAsset', function(href) {
|
||||
href = path.join('gitbook', href);
|
||||
@@ -142,21 +126,11 @@ WebsiteOutput.prototype.prepare = function() {
|
||||
.then(function() {
|
||||
if (that.book.isLanguageBook()) return;
|
||||
|
||||
return Promise.serie([
|
||||
// Assets from the book are already copied
|
||||
// The order is reversed from the template's one
|
||||
|
||||
// Is default theme still installed
|
||||
that.themeDefault && that.themeDefault.root != that.theme.root?
|
||||
that.themeDefault.root : null,
|
||||
|
||||
// Installed plugin (it can be identical to themeDefault.root)
|
||||
that.theme.root
|
||||
], function(folder) {
|
||||
if (!folder) return;
|
||||
|
||||
// Assets from the book are already copied
|
||||
// Copy assets from plugins (start with default plugins)
|
||||
return Promise.serie(that.plugins.list().reverse(), function(plugin) {
|
||||
// Copy assets only if exists (don't fail otherwise)
|
||||
var assetFolder = path.join(folder, '_assets', that.name);
|
||||
var assetFolder = path.join(plugin.root, '_assets', that.name);
|
||||
if (!fs.existsSync(assetFolder)) return;
|
||||
|
||||
that.log.debug.ln('copy assets from theme', assetFolder);
|
||||
@@ -164,7 +138,7 @@ WebsiteOutput.prototype.prepare = function() {
|
||||
assetFolder,
|
||||
that.resolve('gitbook'),
|
||||
{
|
||||
deleteFirst: false, // Delete "to" before
|
||||
deleteFirst: false,
|
||||
overwrite: true,
|
||||
confirm: true
|
||||
}
|
||||
@@ -190,7 +164,7 @@ WebsiteOutput.prototype.onPage = function(page) {
|
||||
|
||||
// Render the page template with the same context as the json output
|
||||
.then(function() {
|
||||
return that.render('page', page.getContext());
|
||||
return that.render('page', page.getOutputContext(that));
|
||||
})
|
||||
|
||||
// Write the HTML file
|
||||
@@ -242,14 +216,24 @@ WebsiteOutput.prototype.outputMultilingualIndex = function() {
|
||||
// Render a template using nunjucks
|
||||
// Templates are stored in `_layouts` folders
|
||||
WebsiteOutput.prototype.render = function(tpl, context) {
|
||||
var that = this;
|
||||
var filename = this.templateName(tpl);
|
||||
|
||||
context = _.extend(context, {
|
||||
template: {
|
||||
// Same template but in the default theme
|
||||
default: this.themeDefault? path.resolve(templatesPath(this.themeDefault.root), filename) : null,
|
||||
|
||||
// Same template but in the theme
|
||||
theme: path.resolve(templatesPath(this.theme.root), filename)
|
||||
self: filename,
|
||||
getJSContext: function() {
|
||||
return {
|
||||
page: _.omit(context.page, 'content'),
|
||||
config: context.config,
|
||||
file: context.file,
|
||||
gitbook: context.gitbook,
|
||||
basePath: location.normalize(that.resolveForPage(context.file.path, './')),
|
||||
book: {
|
||||
language: context.book.language
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
plugins: {
|
||||
|
||||
+12
-2
@@ -184,8 +184,18 @@ HTMLPipeline.prototype.applyAnnotations = function() {
|
||||
// Extract page description from html
|
||||
// This can totally be improved
|
||||
HTMLPipeline.prototype.extractDescription = function() {
|
||||
var $p = this.$('p').first();
|
||||
var description = $p.text().trim().slice(0, 155);
|
||||
var $ = this.$;
|
||||
var $p = $('p').first();
|
||||
var $next = $p.nextUntil('h1,h2,h3,h4,h5,h6,pre,blockquote,ul,ol,div');
|
||||
|
||||
var description = $p.text().trim();
|
||||
|
||||
$next.each(function() {
|
||||
description += ' ' + $(this).text().trim();
|
||||
});
|
||||
|
||||
// Truncate description
|
||||
description = _.trunc(description, 300);
|
||||
|
||||
this.opts.onDescription(description);
|
||||
};
|
||||
|
||||
+22
-26
@@ -7,7 +7,6 @@ var error = require('../utils/error');
|
||||
var pathUtil = require('../utils/path');
|
||||
var location = require('../utils/location');
|
||||
var parsers = require('../parsers');
|
||||
var gitbook = require('../gitbook');
|
||||
var pluginCompatibility = require('../plugins/compatibility');
|
||||
var HTMLPipeline = require('./html');
|
||||
|
||||
@@ -41,7 +40,7 @@ function Page(book, filename) {
|
||||
|
||||
// Can we parse it?
|
||||
extension = path.extname(this.path);
|
||||
this.parser = parsers.get(extension);
|
||||
this.parser = parsers.getByExt(extension);
|
||||
if (!this.parser) throw error.ParsingError(new Error('Can\'t parse file "'+this.path+'"'));
|
||||
|
||||
this.type = this.parser.name;
|
||||
@@ -116,30 +115,27 @@ Page.prototype.getContext = function() {
|
||||
if (dir == 'neutral') dir = null;
|
||||
}
|
||||
|
||||
return _.extend(
|
||||
{
|
||||
file: {
|
||||
path: this.path,
|
||||
mtime: this.mtime,
|
||||
type: this.type
|
||||
},
|
||||
page: _.extend({}, this.attributes, {
|
||||
title: article? article.title : null,
|
||||
next: next? next.getContext() : null,
|
||||
previous: prev? prev.getContext() : null,
|
||||
level: article? article.level : null,
|
||||
depth: article? article.depth : 0,
|
||||
content: this.content,
|
||||
dir: dir
|
||||
})
|
||||
return {
|
||||
file: {
|
||||
path: this.path,
|
||||
mtime: this.mtime,
|
||||
type: this.type
|
||||
},
|
||||
gitbook.getContext(),
|
||||
this.book.getContext(),
|
||||
this.book.langs.getContext(),
|
||||
this.book.summary.getContext(),
|
||||
this.book.glossary.getContext(),
|
||||
this.book.config.getContext()
|
||||
);
|
||||
page: _.extend({}, this.attributes, {
|
||||
title: article? article.title : null,
|
||||
next: next? next.getContext() : null,
|
||||
previous: prev? prev.getContext() : null,
|
||||
level: article? article.level : null,
|
||||
depth: article? article.depth() : 0,
|
||||
content: this.content,
|
||||
dir: dir
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
// Return complete context for templating (page + book + summary + ...)
|
||||
Page.prototype.getOutputContext = function(output) {
|
||||
return _.extend({}, this.getContext(), output.getContext());
|
||||
};
|
||||
|
||||
// Parse the page and return its content
|
||||
@@ -184,7 +180,7 @@ Page.prototype.toHTML = function(output) {
|
||||
|
||||
// Render template
|
||||
.then(function() {
|
||||
return output.template.render(that.content, that.getContext(), {
|
||||
return output.template.render(that.content, that.getOutputContext(output), {
|
||||
path: that.path
|
||||
})
|
||||
.then(that.update);
|
||||
|
||||
+11
-1
@@ -37,11 +37,20 @@ function createParser(parser, base) {
|
||||
nparser.page = Promise.wrapfn(parser.page);
|
||||
nparser.page.prepare = Promise.wrapfn(parser.page.prepare || _.identity);
|
||||
|
||||
nparser.inline = Promise.wrapfn(parser.inline);
|
||||
|
||||
return nparser;
|
||||
}
|
||||
|
||||
// Return a specific parser
|
||||
function getParser(name) {
|
||||
return _.find(PARSERS, {
|
||||
name: name
|
||||
});
|
||||
}
|
||||
|
||||
// Return a specific parser according to an extension
|
||||
function getParser(ext) {
|
||||
function getParserByExt(ext) {
|
||||
return _.find(PARSERS, function(input) {
|
||||
return input.name == ext || _.contains(input.extensions, ext);
|
||||
});
|
||||
@@ -56,5 +65,6 @@ module.exports = {
|
||||
all: PARSERS,
|
||||
extensions: _.flatten(_.pluck(PARSERS, 'extensions')),
|
||||
get: getParser,
|
||||
getByExt: getParserByExt,
|
||||
getForFile: getParserForFile
|
||||
};
|
||||
|
||||
@@ -14,12 +14,13 @@ function pluginCtx(plugin) {
|
||||
|
||||
// Call a function "fn" with a context of page similar to the one in GitBook v2
|
||||
function pageHook(page, fn) {
|
||||
var ctx = {
|
||||
type: page.type,
|
||||
content: page.content,
|
||||
path: page.path,
|
||||
rawPath: page.rawPath
|
||||
};
|
||||
// Get page context
|
||||
var ctx = page.getContext().page;
|
||||
|
||||
// Add other informations
|
||||
ctx.type = page.type;
|
||||
ctx.rawPath = page.rawPath;
|
||||
ctx.path = page.path;
|
||||
|
||||
// Deprecate sections
|
||||
error.deprecateField(ctx, 'sections', [
|
||||
|
||||
+46
-13
@@ -21,6 +21,11 @@ function PluginsManager(book) {
|
||||
_.bindAll(this);
|
||||
}
|
||||
|
||||
// Returns the list of plugins
|
||||
PluginsManager.prototype.list = function() {
|
||||
return this.plugins;
|
||||
};
|
||||
|
||||
// Return count of plugins loaded
|
||||
PluginsManager.prototype.count = function() {
|
||||
return _.size(this.plugins);
|
||||
@@ -33,24 +38,21 @@ PluginsManager.prototype.get = function(name) {
|
||||
});
|
||||
};
|
||||
|
||||
// Load a plugin, or a list of plugins
|
||||
PluginsManager.prototype.load = function(name) {
|
||||
// Load a plugin (could be a BookPlugin or {name,path})
|
||||
PluginsManager.prototype.load = function(plugin) {
|
||||
var that = this;
|
||||
|
||||
if (_.isArray(name)) {
|
||||
return Promise.serie(name, function(_name) {
|
||||
return that.load(_name);
|
||||
});
|
||||
if (_.isArray(plugin)) {
|
||||
return Promise.serie(plugin, that.load);
|
||||
}
|
||||
|
||||
return Promise()
|
||||
|
||||
// Initiate and load the plugin
|
||||
.then(function() {
|
||||
var plugin;
|
||||
|
||||
if (!_.isString(name)) plugin = name;
|
||||
else plugin = new BookPlugin(that.book, name);
|
||||
if (!(plugin instanceof BookPlugin)) {
|
||||
plugin = new BookPlugin(that.book, plugin.name, plugin.path);
|
||||
}
|
||||
|
||||
if (that.get(plugin.id)) {
|
||||
throw new Error('Plugin "'+plugin.id+'" is already loaded');
|
||||
@@ -68,10 +70,41 @@ PluginsManager.prototype.load = function(name) {
|
||||
|
||||
// Load all plugins from the book's configuration
|
||||
PluginsManager.prototype.loadAll = function() {
|
||||
var plugins = _.pluck(this.book.config.get('plugins'), 'name');
|
||||
var that = this;
|
||||
var pluginNames = _.pluck(this.book.config.get('plugins'), 'name');
|
||||
|
||||
this.log.info.ln('loading', plugins.length, 'plugins');
|
||||
return this.load(plugins);
|
||||
return registry.list(this.book)
|
||||
.then(function(plugins) {
|
||||
// Filter out plugins not listed of first level
|
||||
// (aka pre-installed plugins)
|
||||
plugins = _.filter(plugins, function(plugin) {
|
||||
return (
|
||||
plugin.depth > 1 ||
|
||||
_.contains(pluginNames, plugin.name)
|
||||
);
|
||||
});
|
||||
|
||||
// Sort plugins to match list in book.json
|
||||
plugins.sort(function(a, b){
|
||||
return pluginNames.indexOf(a.name) < pluginNames.indexOf(b.name) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Log state
|
||||
that.log.info.ln(_.size(plugins) + ' are installed');
|
||||
if (_.size(pluginNames) != _.size(plugins)) that.log.info.ln(_.size(pluginNames) + ' explicitly listed');
|
||||
|
||||
// Verify that all plugins are present
|
||||
var notInstalled = _.filter(pluginNames, function(name) {
|
||||
return !_.find(plugins, { name: name });
|
||||
});
|
||||
|
||||
if (_.size(notInstalled) > 0) {
|
||||
throw new Error('Couldn\'t locate plugins "' + notInstalled.join(', ') + '", Run \'gitbook install\' to install plugins from registry.');
|
||||
}
|
||||
|
||||
// Load plugins
|
||||
return that.load(plugins);
|
||||
});
|
||||
};
|
||||
|
||||
// Setup a plugin
|
||||
|
||||
+11
-22
@@ -24,13 +24,14 @@ function isModuleNotFound(err) {
|
||||
return err.message.indexOf('Cannot find module') >= 0;
|
||||
}
|
||||
|
||||
function BookPlugin(book, pluginId) {
|
||||
function BookPlugin(book, pluginId, pluginFolder) {
|
||||
this.book = book;
|
||||
this.log = this.book.log.prefix(pluginId);
|
||||
|
||||
|
||||
this.id = pluginId;
|
||||
this.npmId = registry.npmId(pluginId);
|
||||
this.root;
|
||||
this.root = pluginFolder;
|
||||
|
||||
this.packageInfos = undefined;
|
||||
this.content = undefined;
|
||||
@@ -51,8 +52,7 @@ BookPlugin.prototype.bind = function(fn) {
|
||||
return fn.bind(compatibility.pluginCtx(this));
|
||||
};
|
||||
|
||||
// Load this plugin
|
||||
// An optional folder to search in can be passed
|
||||
// Load this plugin from its root folder
|
||||
BookPlugin.prototype.load = function(folder) {
|
||||
var that = this;
|
||||
|
||||
@@ -60,18 +60,12 @@ BookPlugin.prototype.load = function(folder) {
|
||||
return Promise.reject(new Error('Plugin "' + this.id + '" is already loaded'));
|
||||
}
|
||||
|
||||
// Fodlers to search plugins in
|
||||
var searchPaths = _.compact([
|
||||
folder,
|
||||
this.book.resolve('node_modules'),
|
||||
__dirname
|
||||
]);
|
||||
|
||||
// Try loading plugins from different location
|
||||
var p = Promise.some(searchPaths, function(baseDir) {
|
||||
var p = Promise()
|
||||
.then(function() {
|
||||
// Locate plugin and load pacjage.json
|
||||
try {
|
||||
var res = resolve.sync(that.npmId + '/package.json', { basedir: baseDir });
|
||||
var res = resolve.sync('./package.json', { basedir: that.root });
|
||||
|
||||
that.root = path.dirname(res);
|
||||
that.packageInfos = require(res);
|
||||
@@ -81,12 +75,12 @@ BookPlugin.prototype.load = function(folder) {
|
||||
that.packageInfos = undefined;
|
||||
that.content = undefined;
|
||||
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load plugin JS content
|
||||
try {
|
||||
that.content = require(resolve.sync(that.npmId, { basedir: baseDir }));
|
||||
that.content = require(that.root);
|
||||
} catch(err) {
|
||||
// It's no big deal if the plugin doesn't have an "index.js"
|
||||
// (For example: themes)
|
||||
@@ -98,8 +92,6 @@ BookPlugin.prototype.load = function(folder) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
|
||||
.then(that.validate)
|
||||
@@ -122,18 +114,15 @@ BookPlugin.prototype.load = function(folder) {
|
||||
// This method throws erros if plugin is invalid
|
||||
BookPlugin.prototype.validate = function() {
|
||||
var isValid = (
|
||||
this.isLoaded() &&
|
||||
this.packageInfos &&
|
||||
this.packageInfos.name &&
|
||||
this.packageInfos.engines &&
|
||||
this.packageInfos.engines.gitbook
|
||||
);
|
||||
|
||||
if (!this.isLoaded()) {
|
||||
throw new Error('Couldn\'t locate plugin "' + this.id + '", Run \'gitbook install\' to install plugins from registry.');
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
throw new Error('Invalid plugin "' + this.id + '"');
|
||||
throw new Error('Error loading plugin "' + this.id + '" at "' + this.root + '"');
|
||||
}
|
||||
|
||||
if (!gitbook.satisfies(this.packageInfos.engines.gitbook)) {
|
||||
|
||||
+58
-2
@@ -1,7 +1,9 @@
|
||||
var npm = require('npm');
|
||||
var npmi = require('npmi');
|
||||
var path = require('path');
|
||||
var semver = require('semver');
|
||||
var _ = require('lodash');
|
||||
var readInstalled = require('read-installed');
|
||||
|
||||
var Promise = require('../utils/promise');
|
||||
var gitbook = require('../gitbook');
|
||||
@@ -21,7 +23,7 @@ function pluginId(name) {
|
||||
|
||||
// Validate an NPM plugin ID
|
||||
function validateId(name) {
|
||||
return name.indexOf(PLUGIN_PREFIX) === 0;
|
||||
return name && name.indexOf(PLUGIN_PREFIX) === 0;
|
||||
}
|
||||
|
||||
// Initialize NPM for operations
|
||||
@@ -104,6 +106,58 @@ function installPlugin(book, plugin, version) {
|
||||
});
|
||||
}
|
||||
|
||||
// List all packages installed inside a folder
|
||||
// Returns an ordered list of plugins
|
||||
function listInstalled(folder) {
|
||||
var options = {
|
||||
dev: false,
|
||||
log: function() {},
|
||||
depth: 4
|
||||
};
|
||||
var results = [];
|
||||
|
||||
function onPackage(pkg, isRoot) {
|
||||
if (!validateId(pkg.name)){
|
||||
if (!isRoot) return;
|
||||
} else {
|
||||
results.push({
|
||||
name: pluginId(pkg.name),
|
||||
version: pkg.version,
|
||||
path: pkg.realPath,
|
||||
depth: pkg.depth
|
||||
});
|
||||
}
|
||||
|
||||
_.each(pkg.dependencies, function(dep) {
|
||||
onPackage(dep);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.nfcall(readInstalled, folder, options)
|
||||
.then(function(data) {
|
||||
onPackage(data, true);
|
||||
return _.uniq(results, 'name');
|
||||
});
|
||||
}
|
||||
|
||||
// List installed plugins for a book (defaults and installed)
|
||||
function listPlugins(book) {
|
||||
return Promise.all([
|
||||
listInstalled(path.resolve(__dirname, '../..')),
|
||||
listInstalled(book.root),
|
||||
book.isLanguageBook()? listInstalled(book.parent.root) : Promise([])
|
||||
])
|
||||
.spread(function() {
|
||||
var args = _.toArray(arguments);
|
||||
|
||||
var results = _.reduce(args, function(out, a) {
|
||||
return out.concat(a);
|
||||
}, []);
|
||||
|
||||
return _.uniq(results, 'name');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
npmId: npmId,
|
||||
pluginId: pluginId,
|
||||
@@ -111,5 +165,7 @@ module.exports = {
|
||||
|
||||
resolve: resolveVersion,
|
||||
link: linkPlugin,
|
||||
install: installPlugin
|
||||
install: installPlugin,
|
||||
list: listPlugins,
|
||||
listInstalled: listInstalled
|
||||
};
|
||||
|
||||
@@ -12,5 +12,25 @@ module.exports = {
|
||||
html: false,
|
||||
body: blk.body
|
||||
};
|
||||
},
|
||||
|
||||
// Render some markdown to HTML
|
||||
markdown: function(blk) {
|
||||
return this.book.renderInline('markdown', blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
},
|
||||
asciidoc: function(blk) {
|
||||
return this.book.renderInline('asciidoc', blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
},
|
||||
markup: function(blk) {
|
||||
return this.book.renderInline(this.ctx.file.type, blk.body)
|
||||
.then(function(out) {
|
||||
return { body: out };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
var _ = require('lodash');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var nunjucks = require('nunjucks');
|
||||
|
||||
/*
|
||||
Nunjucks loader similar to FileSystemLoader, but avoid infinite looping
|
||||
*/
|
||||
|
||||
function isRelative(filename) {
|
||||
return (filename.indexOf('./') === 0 || filename.indexOf('../') === 0);
|
||||
}
|
||||
|
||||
var Loader = nunjucks.Loader.extend({
|
||||
init: function(searchPaths) {
|
||||
this.searchPaths = _.map(searchPaths, path.normalize);
|
||||
},
|
||||
|
||||
getSource: function(fullpath) {
|
||||
if (!fullpath) return null;
|
||||
|
||||
fullpath = this.resolve(null, fullpath);
|
||||
|
||||
if(!fullpath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
src: fs.readFileSync(fullpath, 'utf-8'),
|
||||
path: fullpath,
|
||||
noCache: true
|
||||
};
|
||||
},
|
||||
|
||||
// We handle absolute paths ourselves in ".resolve"
|
||||
isRelative: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
resolve: function(from, to) {
|
||||
var searchPaths = this.searchPaths;
|
||||
|
||||
// Relative template like "./test.html"
|
||||
if (isRelative(to) && from) {
|
||||
return path.resolve(path.dirname(from), to);
|
||||
}
|
||||
|
||||
// Determine in which search folder we currently are
|
||||
var originalSearchPath = _.chain(this.searchPaths)
|
||||
.sortBy(function(s) {
|
||||
return -s.length;
|
||||
})
|
||||
.find(function(basePath) {
|
||||
return (from && from.indexOf(basePath) === 0);
|
||||
})
|
||||
.value();
|
||||
var originalFilename = originalSearchPath? path.relative(originalSearchPath, from) : null;
|
||||
|
||||
// If we are including same file from a different search path
|
||||
// Slice the search paths to avoid including from previous ones
|
||||
if (originalFilename == to) {
|
||||
var currentIndex = searchPaths.indexOf(originalSearchPath);
|
||||
searchPaths = searchPaths.slice(currentIndex + 1);
|
||||
}
|
||||
|
||||
// Absolute template to resolve in root folder
|
||||
var resultFolder = _.find(searchPaths, function(basePath) {
|
||||
var p = path.resolve(basePath, to);
|
||||
|
||||
return (
|
||||
p.indexOf(basePath) === 0
|
||||
&& fs.existsSync(p)
|
||||
);
|
||||
});
|
||||
if (!resultFolder) return null;
|
||||
return path.resolve(resultFolder, to);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Loader;
|
||||
+87
-60
@@ -10,6 +10,8 @@ var defaultBlocks = require('./blocks');
|
||||
var defaultFilters = require('./filters');
|
||||
var Loader = require('./loader');
|
||||
|
||||
var NODE_ENDARGS = '%%endargs%%';
|
||||
|
||||
// Return extension name for a specific block
|
||||
function blockExtName(name) {
|
||||
return 'Block'+name+'Extension';
|
||||
@@ -21,6 +23,12 @@ function normBlockResult(blk) {
|
||||
return blk;
|
||||
}
|
||||
|
||||
// Extract kwargs from an arguments array
|
||||
function extractKwargs(args) {
|
||||
var last = _.last(args);
|
||||
return (_.isObject(last) && last.__keywords)? args.pop() : {};
|
||||
}
|
||||
|
||||
function TemplateEngine(output) {
|
||||
this.output = output;
|
||||
this.book = output.book;
|
||||
@@ -63,23 +71,33 @@ function TemplateEngine(output) {
|
||||
// Add default blocks and filters
|
||||
this.addBlocks(defaultBlocks);
|
||||
this.addFilters(defaultFilters);
|
||||
|
||||
// Build context for this book with depreacted fields
|
||||
this.ctx = {
|
||||
template: this,
|
||||
book: this.book,
|
||||
output: this.output
|
||||
};
|
||||
error.deprecateField(this.ctx, 'generator', this.output.name, '"generator" property is deprecated, use "output.generator" instead');
|
||||
}
|
||||
|
||||
// Bind a function to a context
|
||||
// Filters and blocks are binded to this context
|
||||
TemplateEngine.prototype.bindContext = function(func) {
|
||||
var ctx = {
|
||||
ctx: this.ctx,
|
||||
output: this.output,
|
||||
generator: this.output.name
|
||||
};
|
||||
var that = this;
|
||||
|
||||
return _.bind(func, ctx);
|
||||
return function() {
|
||||
var ctx = _.extend({
|
||||
ctx: this.ctx
|
||||
}, that.ctx);
|
||||
|
||||
return func.apply(ctx, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
// Interpolate a string content to replace shortcuts according to the filetype
|
||||
TemplateEngine.prototype.interpolate = function(filepath, source) {
|
||||
var parser = parsers.get(path.extname(filepath));
|
||||
var parser = parsers.getByExt(path.extname(filepath));
|
||||
var type = parser? parser.name : null;
|
||||
|
||||
return this.applyShortcuts(type, source);
|
||||
@@ -169,91 +187,100 @@ TemplateEngine.prototype.addBlock = function(name, block) {
|
||||
this.tags = [name];
|
||||
|
||||
this.parse = function(parser, nodes) {
|
||||
var body = null;
|
||||
var lastBlockName = null;
|
||||
var lastBlockArgs = null;
|
||||
var allBlocks = block.blocks.concat([block.end]);
|
||||
var subbodies = {};
|
||||
|
||||
// Parse first block
|
||||
var tok = parser.nextToken();
|
||||
var args = parser.parseSignature(null, true);
|
||||
lastBlockArgs = parser.parseSignature(null, true);
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
var args = new nodes.NodeList();
|
||||
var bodies = [];
|
||||
var blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
|
||||
var blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
|
||||
|
||||
// Parse while we found "end<block>"
|
||||
do {
|
||||
// Read body
|
||||
var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
|
||||
|
||||
// Handle body with previous block name and args
|
||||
if (lastBlockName) {
|
||||
subbodies[lastBlockName] = subbodies[lastBlockName] || [];
|
||||
subbodies[lastBlockName].push({
|
||||
body: currentBody,
|
||||
args: lastBlockArgs
|
||||
});
|
||||
} else {
|
||||
body = currentBody;
|
||||
}
|
||||
blockNamesNode.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockName));
|
||||
blockArgCounts.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockArgs.children.length));
|
||||
bodies.push(currentBody);
|
||||
|
||||
// Append arguments of this block as arguments of the run function
|
||||
_.each(lastBlockArgs.children, function(child) {
|
||||
args.addChild(child);
|
||||
});
|
||||
|
||||
// Read new block
|
||||
lastBlockName = parser.peekToken().value;
|
||||
lastBlockName = parser.nextToken().value;
|
||||
|
||||
// Parse signature and move to the end of the block
|
||||
if (lastBlockName != block.end) {
|
||||
lastBlockArgs = parser.parseSignature(null, true);
|
||||
parser.advanceAfterBlockEnd(lastBlockName);
|
||||
}
|
||||
|
||||
parser.advanceAfterBlockEnd(lastBlockName);
|
||||
} while (lastBlockName != block.end);
|
||||
|
||||
parser.advanceAfterBlockEnd();
|
||||
|
||||
var bodies = [body];
|
||||
_.each(block.blocks, function(blockName) {
|
||||
subbodies[blockName] = subbodies[blockName] || [];
|
||||
if (subbodies[blockName].length === 0) {
|
||||
subbodies[blockName].push({
|
||||
args: new nodes.NodeList(),
|
||||
body: new nodes.NodeList()
|
||||
});
|
||||
}
|
||||
|
||||
bodies.push(subbodies[blockName][0].body);
|
||||
});
|
||||
args.addChild(blockNamesNode);
|
||||
args.addChild(blockArgCounts);
|
||||
args.addChild(new nodes.Literal(args.lineno, args.colno, NODE_ENDARGS));
|
||||
|
||||
return new nodes.CallExtensionAsync(this, 'run', args, bodies);
|
||||
};
|
||||
|
||||
this.run = function(context) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
var callback = args.pop();
|
||||
var fnArgs = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
// Extract blocks
|
||||
var blocks = args
|
||||
.concat([])
|
||||
.slice(-block.blocks.length);
|
||||
var args;
|
||||
var blocks = [];
|
||||
var bodies = [];
|
||||
var blockNames;
|
||||
var blockArgCounts;
|
||||
var callback;
|
||||
|
||||
// Eliminate blocks from list
|
||||
if (block.blocks.length > 0) args = args.slice(0, -block.blocks.length);
|
||||
// Extract callback
|
||||
callback = fnArgs.pop();
|
||||
|
||||
// Extract main body and kwargs
|
||||
var body = args.pop();
|
||||
var kwargs = _.isObject(_.last(args))? args.pop() : {};
|
||||
// Detect end of arguments
|
||||
var endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
|
||||
|
||||
// Extract blocks body
|
||||
var _blocks = _.map(block.blocks, function(blockName, i){
|
||||
return {
|
||||
name: blockName,
|
||||
body: blocks[i]()
|
||||
};
|
||||
// Extract arguments and bodies
|
||||
args = fnArgs.slice(0, endArgIndex);
|
||||
bodies = fnArgs.slice(endArgIndex + 1);
|
||||
|
||||
// Extract block counts
|
||||
blockArgCounts = args.pop();
|
||||
blockNames = args.pop();
|
||||
|
||||
// Recreate list of blocks
|
||||
_.each(blockNames, function(name, i) {
|
||||
var countArgs = blockArgCounts[i];
|
||||
var blockBody = bodies.shift();
|
||||
|
||||
var blockArgs = countArgs > 0? args.slice(0, countArgs) : [];
|
||||
args = args.slice(countArgs);
|
||||
var blockKwargs = extractKwargs(blockArgs);
|
||||
|
||||
blocks.push({
|
||||
name: name,
|
||||
body: blockBody(),
|
||||
args: blockArgs,
|
||||
kwargs: blockKwargs
|
||||
});
|
||||
});
|
||||
|
||||
var mainBlock = blocks.shift();
|
||||
mainBlock.blocks = blocks;
|
||||
|
||||
Promise()
|
||||
.then(function() {
|
||||
return that.applyBlock(name, {
|
||||
body: body(),
|
||||
args: args,
|
||||
kwargs: kwargs,
|
||||
blocks: _blocks
|
||||
}, context);
|
||||
return that.applyBlock(name, mainBlock, context);
|
||||
})
|
||||
|
||||
// Process the block returned
|
||||
@@ -338,7 +365,7 @@ TemplateEngine.prototype.processBlock = function(blk) {
|
||||
}
|
||||
|
||||
// Return it as a position marker
|
||||
return '@%@'+blk.id+'@%@';
|
||||
return '{{-%'+blk.id+'%-}}';
|
||||
};
|
||||
|
||||
// Render a string (without post processing)
|
||||
@@ -386,7 +413,7 @@ TemplateEngine.prototype.applyShortcut = function(content, shortcut) {
|
||||
TemplateEngine.prototype.replaceBlocks = function(content) {
|
||||
var that = this;
|
||||
|
||||
return content.replace(/\@\%\@([\s\S]+?)\@\%\@/g, function(match, key) {
|
||||
return content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
|
||||
var blk = that.blockBodies[key];
|
||||
if (!blk) return match;
|
||||
|
||||
|
||||
+22
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "3.0.0-pre.4",
|
||||
"version": "3.0.0-pre.8",
|
||||
"homepage": "https://www.gitbook.com",
|
||||
"description": "Library and cmd utility to generate GitBooks",
|
||||
"main": "lib/index.js",
|
||||
@@ -9,7 +9,7 @@
|
||||
"cheerio": "0.20.0",
|
||||
"chokidar": "1.4.3",
|
||||
"cp": "0.2.0",
|
||||
"cpr": "1.0.0",
|
||||
"cpr": "1.1.1",
|
||||
"crc": "3.4.0",
|
||||
"deprecated": "0.0.1",
|
||||
"destroy": "1.0.4",
|
||||
@@ -18,45 +18,47 @@
|
||||
"error": "7.0.2",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"eslint": "^2.2.0",
|
||||
"front-matter": "2.0.6",
|
||||
"gitbook-asciidoc": "1.0.2",
|
||||
"gitbook-markdown": "1.0.3",
|
||||
"gitbook-plugin-fontsettings": "1.0.2",
|
||||
"gitbook-plugin-highlight": "2.0.0",
|
||||
"front-matter": "2.0.7",
|
||||
"gitbook-asciidoc": "1.1.0",
|
||||
"gitbook-markdown": "1.2.0",
|
||||
"gitbook-plugin-fontsettings": "1.0.3",
|
||||
"gitbook-plugin-highlight": "2.0.2",
|
||||
"gitbook-plugin-livereload": "0.0.1",
|
||||
"gitbook-plugin-search": "1.2.0",
|
||||
"gitbook-plugin-sharing": "1.0.1",
|
||||
"gitbook-plugin-theme-default": "1.0.0-pre.4",
|
||||
"github-slugid": "1.0.0",
|
||||
"gitbook-plugin-search": "2.0.0",
|
||||
"gitbook-plugin-lunr": "1.0.0",
|
||||
"gitbook-plugin-theme-default": "1.0.0-pre.7",
|
||||
"gitbook-plugin-sharing": "1.0.2",
|
||||
"github-slugid": "1.0.1",
|
||||
"graceful-fs": "4.1.3",
|
||||
"i18n-t": "1.0.0",
|
||||
"ignore": "3.0.3",
|
||||
"ignore": "3.1.1",
|
||||
"json-schema-defaults": "0.1.1",
|
||||
"jsonschema": "1.1.0",
|
||||
"juice": "1.10.0",
|
||||
"lodash": "3.10.1",
|
||||
"merge-defaults": "0.2.1",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "2.11.2",
|
||||
"npm": "3.7.5",
|
||||
"moment": "2.12.0",
|
||||
"npm": "3.8.6",
|
||||
"npmi": "1.0.1",
|
||||
"nunjucks": "2.3.0",
|
||||
"nunjucks": "2.4.1",
|
||||
"nunjucks-autoescape": "1.0.1",
|
||||
"q": "1.4.1",
|
||||
"request": "2.69.0",
|
||||
"resolve": "0.6.3",
|
||||
"read-installed": "^4.0.3",
|
||||
"request": "2.70.0",
|
||||
"resolve": "1.1.7",
|
||||
"rmdir": "1.2.0",
|
||||
"semver": "5.1.0",
|
||||
"send": "0.13.1",
|
||||
"send": "0.13.2",
|
||||
"spawn-cmd": "0.0.2",
|
||||
"tiny-lr": "0.2.1",
|
||||
"tmp": "0.0.28",
|
||||
"urijs": "1.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "2.2.0",
|
||||
"eslint": "2.7.0",
|
||||
"mocha": "2.4.5",
|
||||
"should": "8.2.2"
|
||||
"should": "8.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha --reporter spec --bail --timeout 15000 ./test/all.js",
|
||||
|
||||
@@ -19,6 +19,7 @@ require('./git');
|
||||
require('./plugins');
|
||||
require('./template');
|
||||
require('./conrefs');
|
||||
require('./ignore');
|
||||
|
||||
// Page and HTML generation
|
||||
require('./page');
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
var mock = require('./mock');
|
||||
var WebsiteOutput = require('../lib/output/website');
|
||||
|
||||
describe('Ignore', function() {
|
||||
var output;
|
||||
|
||||
before(function() {
|
||||
return mock.outputDefaultBook(WebsiteOutput, {
|
||||
'.ignore': 'test-1.js',
|
||||
'.gitignore': 'test-2.js\ntest-3.js',
|
||||
'.bookignore': '!test-3.js',
|
||||
'test-1.js': '1',
|
||||
'test-2.js': '2',
|
||||
'test-3.js': '3'
|
||||
})
|
||||
.then(function(_output) {
|
||||
output = _output;
|
||||
});
|
||||
});
|
||||
|
||||
it('should load rules from .ignore', function() {
|
||||
output.should.not.have.file('test-1.js');
|
||||
});
|
||||
|
||||
it('should load rules from .gitignore', function() {
|
||||
output.should.not.have.file('test-2.js');
|
||||
});
|
||||
|
||||
it('should load rules from .bookignore', function() {
|
||||
output.should.have.file('test-3.js');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,8 +41,24 @@ describe('JSON Output', function() {
|
||||
output.should.have.file('fr/README.json');
|
||||
});
|
||||
|
||||
it('should correctly add languages list to all json', function() {
|
||||
var jsonFR = require(output.resolve('fr/README.json'));
|
||||
var jsonEN = require(output.resolve('en/README.json'));
|
||||
|
||||
jsonFR.should.have.property('languages')
|
||||
.with.property('list').with.lengthOf(2);
|
||||
jsonEN.should.have.property('languages')
|
||||
.with.property('list').with.lengthOf(2);
|
||||
});
|
||||
|
||||
it('should correctly generate a README.json for the whole book', function() {
|
||||
output.should.have.file('README.json');
|
||||
var json = require(output.resolve('README.json'));
|
||||
|
||||
json.book.language.should.equal('en');
|
||||
|
||||
json.should.have.property('languages')
|
||||
.with.property('list').with.lengthOf(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
var fs = require('fs');
|
||||
|
||||
var mock = require('./mock');
|
||||
var WebsiteOutput = require('../lib/output/website');
|
||||
|
||||
@@ -95,5 +97,28 @@ describe('Website Output', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Theming', function() {
|
||||
var output;
|
||||
|
||||
before(function() {
|
||||
return mock.outputDefaultBook(WebsiteOutput, {
|
||||
'_layouts/website/page.html': '{% extends "website/page.html" %}{% block body %}{{ super() }}<div id="theming-added"></div>{% endblock %}'
|
||||
|
||||
})
|
||||
.then(function(_output) {
|
||||
output = _output;
|
||||
});
|
||||
});
|
||||
|
||||
it('should extend default theme', function() {
|
||||
var readme = fs.readFileSync(output.resolve('index.html'), 'utf-8');
|
||||
|
||||
readme.should.be.html({
|
||||
'#theming-added': {
|
||||
count: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+26
-4
@@ -8,7 +8,7 @@ describe('Page', function() {
|
||||
return mock.setupDefaultBook({
|
||||
'README.md': ' # Hello World\n\nThis is a description',
|
||||
'heading.md': '# Hello\n\n## World',
|
||||
'description.md': '# This is a title\n\nThis is the short description.\n\nNot this one.',
|
||||
'description.md': '# This is a title\n\nThis is the short description.\n\nAnd the rest of the description.\n\n# Heading\n\nThis is not in the description',
|
||||
'frontmatter/description.md': '---\ndescription: Hello World\n---\n\n# This is a title\n\nThis is not the description',
|
||||
'frontmatter/var.md': '---\ntest: Hello World\n---\n\n{{ page.test }}',
|
||||
|
||||
@@ -36,8 +36,12 @@ describe('Page', function() {
|
||||
'variables/page/dir/ltr.md': 'This is english: {{ page.dir }}',
|
||||
'variables/page/dir/rtl.md': 'بسيطة {{ page.dir }}',
|
||||
'variables/config/title.md': '{{ config.title}}',
|
||||
'variables/gitbook/generator.md': '{{ gitbook.generator }}',
|
||||
|
||||
'GLOSSARY.md': '# Glossary\n\n\n## abracadabra\n\nthis is the description'
|
||||
'GLOSSARY.md': '# Glossary\n\n\n## abracadabra\n\nthis is the description',
|
||||
|
||||
'blocks/markdown.md': 'Hello <span>{% markdown %}**World**{% endmarkdown %}</span>',
|
||||
'blocks/asciidoc.md': 'Hello <span>{% asciidoc %}^super^script phrase{% endasciidoc %}</span>'
|
||||
}, [
|
||||
{
|
||||
title: 'Test page.next',
|
||||
@@ -137,7 +141,7 @@ describe('Page', function() {
|
||||
|
||||
return page.toHTML(output)
|
||||
.then(function() {
|
||||
page.attributes.description.should.equal('This is the short description.');
|
||||
page.attributes.description.should.equal('This is the short description. And the rest of the description.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -285,7 +289,7 @@ describe('Page', function() {
|
||||
|
||||
it('should keep external images path', function() {
|
||||
page.content.should.be.html({
|
||||
'img[src="https:/upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"]': {
|
||||
'img[src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"]': {
|
||||
count: 1
|
||||
}
|
||||
});
|
||||
@@ -358,6 +362,12 @@ describe('Page', function() {
|
||||
.should.be.fulfilledWith('<p>Hello World</p>\n');
|
||||
});
|
||||
|
||||
it('should set gitbook.generator', function() {
|
||||
var page = book.addPage('variables/gitbook/generator.md');
|
||||
return page.toHTML(output)
|
||||
.should.be.fulfilledWith('<p>base</p>\n');
|
||||
});
|
||||
|
||||
describe('page.dir', function() {
|
||||
it('should detect ltr', function() {
|
||||
var page = book.addPage('variables/page/dir/ltr.md');
|
||||
@@ -406,4 +416,16 @@ describe('Page', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Default Blocks', function() {
|
||||
it('should render block "markdown"', function() {
|
||||
return book.addPage('blocks/markdown.md').toHTML(output)
|
||||
.should.finally.equal('<p>Hello <span><strong>World</strong></span></p>\n');
|
||||
});
|
||||
|
||||
it('should render block "asciidoc"', function() {
|
||||
return book.addPage('blocks/asciidoc.md').toHTML(output)
|
||||
.should.finally.equal('<p>Hello <span><sup>super</sup>script phrase</span></p>\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+29
-6
@@ -9,6 +9,10 @@ var BookPlugin = require('../lib/plugins/plugin');
|
||||
|
||||
var PLUGINS_ROOT = path.resolve(__dirname, 'node_modules');
|
||||
|
||||
function TestPlugin(book, name) {
|
||||
return new BookPlugin(book, name, path.resolve(PLUGINS_ROOT, 'gitbook-plugin-'+name));
|
||||
}
|
||||
|
||||
describe('Plugins', function() {
|
||||
var book;
|
||||
|
||||
@@ -77,6 +81,25 @@ describe('Plugins', function() {
|
||||
})
|
||||
.should.be.fulfilledWith(1);
|
||||
});
|
||||
|
||||
// This test requires a SSH key, we only run it locally
|
||||
if (!process.env.CI) {
|
||||
it('should correctly install dependencies from GitHub via ssh', function() {
|
||||
return mock.setupBook({
|
||||
'book.json': {
|
||||
plugins: ['ga@git@github.com:GitbookIO/plugin-ga.git#master']
|
||||
}
|
||||
})
|
||||
.then(function(book) {
|
||||
return book.prepareConfig()
|
||||
.then(function() {
|
||||
var plugins = new PluginsManager(book);
|
||||
return plugins.install();
|
||||
});
|
||||
})
|
||||
.should.be.fulfilledWith(1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Loading', function() {
|
||||
@@ -90,7 +113,7 @@ describe('Plugins', function() {
|
||||
|
||||
describe('Configuration', function() {
|
||||
it('should fail loading a plugin with an invalid configuration', function() {
|
||||
var plugin = new BookPlugin(book, 'test-config');
|
||||
var plugin = TestPlugin(book, 'test-config');
|
||||
return plugin.load(PLUGINS_ROOT)
|
||||
.should.be.rejectedWith('Error with book\'s configuration: pluginsConfig.test-config.myProperty is required');
|
||||
});
|
||||
@@ -108,7 +131,7 @@ describe('Plugins', function() {
|
||||
.then(function(book2) {
|
||||
return book2.prepareConfig()
|
||||
.then(function() {
|
||||
var plugin = new BookPlugin(book2, 'test-config');
|
||||
var plugin = TestPlugin(book2, 'test-config');
|
||||
return plugin.load(PLUGINS_ROOT);
|
||||
})
|
||||
.then(function() {
|
||||
@@ -122,7 +145,7 @@ describe('Plugins', function() {
|
||||
var plugin;
|
||||
|
||||
before(function() {
|
||||
plugin = new BookPlugin(book, 'test-resources');
|
||||
plugin = TestPlugin(book, 'test-resources');
|
||||
return plugin.load(PLUGINS_ROOT);
|
||||
});
|
||||
|
||||
@@ -146,7 +169,7 @@ describe('Plugins', function() {
|
||||
var plugin, filters;
|
||||
|
||||
before(function() {
|
||||
plugin = new BookPlugin(book, 'test-filters');
|
||||
plugin = TestPlugin(book, 'test-filters');
|
||||
return plugin.load(PLUGINS_ROOT)
|
||||
|
||||
.then(function() {
|
||||
@@ -171,7 +194,7 @@ describe('Plugins', function() {
|
||||
var plugin, blocks;
|
||||
|
||||
before(function() {
|
||||
plugin = new BookPlugin(book, 'test-blocks');
|
||||
plugin = TestPlugin(book, 'test-blocks');
|
||||
return plugin.load(PLUGINS_ROOT)
|
||||
|
||||
.then(function() {
|
||||
@@ -196,7 +219,7 @@ describe('Plugins', function() {
|
||||
var plugin;
|
||||
|
||||
before(function() {
|
||||
plugin = new BookPlugin(book, 'test-hooks');
|
||||
plugin = TestPlugin(book, 'test-hooks');
|
||||
return plugin.load(PLUGINS_ROOT);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user