Compare commits

...

46 Commits

Author SHA1 Message Date
Samy Pessé 49e3bbc3d6 Bump version to 1.1.1 2014-10-13 16:03:47 +02:00
Aaron O'Mullan c9ec911d0e Merge pull request #472 from GitbookIO/fix/quizzes
Fix/quizzes
2014-10-13 15:55:32 +02:00
Aaron O'Mullan 84d7662fd8 Allow quiz titles in validation 2014-10-13 15:55:59 +02:00
Aaron O'Mullan 676b2287ee Rewrite quiz logic to be more robust
Fixes #470
2014-10-13 15:50:12 +02:00
Samy Pessé 32a64be407 Fix #460: search for glossary term on all nodes 2014-10-13 15:23:51 +02:00
Samy Pessé 138cddf3d6 Ste GitBook as publisher 2014-10-13 15:23:00 +02:00
Aaron O'Mullan d730a3aac6 Split section classification to separate files
Maybe have a sections folder in the future and one per type
(quiz/exercise/normal) ?
2014-10-13 15:07:46 +02:00
Aaron O'Mullan ffb0b12012 Add unit test for quiz false positivies 2014-10-13 14:17:08 +02:00
Samy Pessé 78e25f8252 Add link to gitbook.gitignore 2014-10-13 11:10:28 +02:00
Samy Pessé 09f42f57e1 Improve comments 2014-10-13 11:01:00 +02:00
Samy Pessé 950ad1d060 Remove reference to single page output 2014-10-13 10:59:32 +02:00
Samy Pessé ed6b29c1a4 Adapt tests 2014-10-13 10:57:47 +02:00
Samy Pessé 36785120b1 Fix default for pdf templates 2014-10-13 10:56:14 +02:00
Samy Pessé 26fad5b888 Remove ref to github options 2014-10-13 10:53:51 +02:00
Samy Pessé f049576575 Merge branch 'alexandreaquiles-pdf_header_and_footer' 2014-10-13 10:48:33 +02:00
Samy Pessé a7ecbb87e4 Force type of header and footer templates 2014-10-13 10:48:02 +02:00
Samy Pessé 1da341741b Merge pull request #471 from GitbookIO/feature/multipages
Use multiple HTML files for ebook generation
2014-10-13 10:41:23 +02:00
Samy Pessé 2dd6192576 Remove old codes 2014-10-12 22:25:39 +02:00
Samy Pessé 52c9347b9c Improve style for exercises and quizzes in ebook 2014-10-12 22:07:37 +02:00
Samy Pessé e2295591ed Handle correctly quizzes 2014-10-12 11:42:32 +02:00
Samy Pessé 0c1cb724d7 Handle correctly exercises in ebook 2014-10-12 11:37:51 +02:00
Samy Pessé 7cee248426 Generate multiple page for ebook 2014-10-12 11:32:45 +02:00
Aaron O'Mullan 6ea5b2b539 Fix incorrect page breaks after h1 and h2 divs
Fixes #463, fixes #337, fixes #293
2014-10-08 20:08:22 +02:00
Alexandre Aquiles a56b559d43 Adding options to set header and footer in PDF generation 2014-10-06 13:17:34 -03:00
Samy Pessé df1a82e0e4 Add changelog for 1.1.0 2014-09-24 14:57:50 +02:00
Samy Pessé f15a1efe71 Bump version to 1.1.0 2014-09-24 14:56:09 +02:00
Samy Pessé b8379fc6fa Update kramed@0.4.3 2014-09-24 14:53:42 +02:00
Samy Pessé d69cc74647 Merge pull request #440 from GitbookIO/feature/ebookplugins
Apply plugins to ebook generation
2014-09-17 15:02:22 +02:00
Samy Pessé f83874a9ca Fix correct folder for assets 2014-09-17 14:16:04 +02:00
Samy Pessé 1968bb79c2 Use correct resources file for ebook 2014-09-17 13:17:30 +02:00
Samy Pessé 60c721db40 Enable css from plugins in page output 2014-09-17 13:10:36 +02:00
Samy Pessé 11b299653f Remove githubId
Enable hook for ebook
2014-09-17 13:08:30 +02:00
Samy Pessé c8c07fe8ce Add base to use plugins in page format 2014-09-17 13:00:28 +02:00
Samy Pessé be0a4f08e9 Merge pull request #435 from markcxz/master
fix radio input uncheck failed
2014-09-10 09:56:12 +02:00
markcxz 4da4e28d07 fix radio input uncheck failed 2014-09-10 10:27:27 +08:00
Samy Pessé 9dc19026c6 Add changelog for version 1.0.3 2014-09-05 17:15:04 +02:00
Samy Pessé 31ca9f7fcb Bump version to 1.0.3 2014-09-05 17:12:03 +02:00
Samy Pessé 6925d39e8d Fix GitbookIO/editor#136: update highlight.js@8.2.0 2014-09-05 17:10:47 +02:00
Samy Pessé a24cafd975 Update plugin-mathjax@0.0.6 2014-09-02 23:25:33 +02:00
Aaron O'Mullan e3fdf8b404 Bump version to 1.0.2 2014-08-20 17:03:31 -07:00
Aaron O'Mullan c861223378 Use gitbook-plugin-mathjax@0.0.5 2014-08-20 17:01:14 -07:00
Aaron O'Mullan f5c6d8df71 Bump version to 1.0.1 2014-08-20 16:30:04 -07:00
Aaron O'Mullan 452c846e90 Update framed to 0.4.2
This uses the new kramdown convention for inline math
2014-08-20 16:27:50 -07:00
Samy Pessé aedd875ab7 Fix url for sharing 2014-08-20 15:54:11 -07:00
Samy Pessé d2ec2f8f02 Add plugins exercises and quizzes by default 2014-08-20 10:33:34 -07:00
Samy Pessé 5af0df93c3 Normalize titles in readme 2014-08-19 16:39:54 -07:00
28 changed files with 496 additions and 321 deletions
+23
View File
@@ -1,5 +1,28 @@
# Release notes
## 1.1.1
- Rewrite quiz logic to be more robust
- Improve integration of glossary
- Improve generation of ebook by using a multiple HTML pages input source
- Fix incorrect page breaks after h1 and h2 divs
- New options to set header and footer in PDF generation
## 1.1.0
- Plugins can now extend the ebook generation (pdf, epub, mobi)
- Update `kramed` to version 0.4.3
## 1.0.3
- Update `mathjax` plugin and MathJAx to version 2.4
- Update `highlight.js` to 8.2.0
## 1.0.2
- Update `mathjax` plugin, fixes issues with inline math rendering (no longer wanted)
## 1.0.1
- New inline math convention (kramdown's), using `$$` rather than `$` as delimiters
- Fix instapaper sharing
- The `exercises` & `quizzes` plugins are now by default
## 1.0.0
- New design
- Support for glossary
+9 -8
View File
@@ -62,10 +62,6 @@ Here are the options that can be stored in this file:
// It's not advised this option in the book.json
"extension": null,
// GitHub information (defaults are extracted using git)
"github": null,
"githubHost": "https://github.com/",
// Plugins list, can contain "-name" for removing default plugins
"plugins": [],
@@ -118,7 +114,13 @@ Here are the options that can be stored in this file:
"left": 62,
"top": 36,
"bottom": 36
}
},
//Header HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"headerTemplate": null,
//Footer HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"footerTemplate": null
}
}
```
@@ -131,7 +133,6 @@ GitBook can generate your book in the following formats:
* **Static Website**: This is the default format. It generates a complete interactive static website that can be, for example, hosted on GitHub Pages.
* **eBook**: A complete eBook with exercise solutions at the end of the book. Generate this format using: ```gitbook ebook ./myrepo```. You need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed. The output format could be **PDF**, **ePub** or **MOBI**.
* **Single Page**: The book will be stored in a single printable HTML page. This format is used for conversion to PDF or eBook. Generate this format using: ```gitbook build ./myrepo -f page```.
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate this format using: ```gitbook build ./myrepo -f json```.
## Book Format
@@ -174,7 +175,7 @@ GitBook supports building books written in multiple languages. Each language sho
You can see a complete example with the [Learn Git](https://github.com/GitbookIO/git) book.
#### GLOSSARY.md
#### Glossary
Allows you to specify terms and their respective definitions to be displayed in the glossary. Based on those terms, `gitbook` will automatically build an index and highlight those terms in pages.
@@ -193,7 +194,7 @@ With it's definition, this can contain bold text and all other kinds of inline m
GitBook will read the `.gitignore`, `.bookignore` and `.ignore` files to get a list of files and folders to skip. (The format inside those files follows the same convention as `.gitignore`).
Best practices for the `.gitignore` is to ignore build files from [**node.js**](https://github.com/github/gitignore/blob/master/Node.gitignore) (`node_modules`, ...) and build files from GitBook: `_book`, `*.epub`, `*.mobi` and `*.pdf`.
Best practices for the `.gitignore` is to ignore build files from **node.js** (`node_modules`, ...) and build files from GitBook: `_book`, `*.epub`, `*.mobi` and `*.pdf` ([Download GitBook.gitignore](https://github.com/github/gitignore/blob/master/GitBook.gitignore)).
#### Cover
+7 -1
View File
@@ -83,7 +83,13 @@ var CONFIG = {
"left": 62,
"top": 36,
"bottom": 36
}
},
//Header HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"headerTemplate": "",
//Footer HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"footerTemplate": ""
}
};
+7 -6
View File
@@ -9,10 +9,6 @@ var parse = require("../../parse");
var BaseGenerator = require("../page");
var stringUtils = require("../../utils/string");
/*
* This generator inherits from the single page generator
* and convert the page output to ebook
*/
var Generator = function() {
BaseGenerator.apply(this, arguments);
};
@@ -35,12 +31,15 @@ Generator.prototype.finish = function() {
"--title": that.options.title,
"--comments": that.options.description,
"--authors": that.options.author,
"--publisher": "GitBook",
"--chapter": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter ')]",
"--chapter-mark": "pagebreak",
"--page-breaks-before": "/",
"--level1-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-1 ')]",
"--level2-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-2 ')]",
"--level3-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-3 ')]",
"--no-chapters-in-toc": true
"--no-chapters-in-toc": true,
"--max-levels": "1000"
};
if (format == "pdf") {
@@ -55,7 +54,9 @@ Generator.prototype.finish = function() {
"--pdf-default-font-size": String(pdfOptions.fontSize),
"--pdf-mono-font-size": String(pdfOptions.fontSize),
"--paper-size": String(pdfOptions.paperSize),
"--pdf-page-numbers": Boolean(pdfOptions.pageNumbers)
"--pdf-page-numbers": Boolean(pdfOptions.pageNumbers),
"--pdf-header-template": String(pdfOptions.headerTemplate),
"--pdf-footer-template": String(pdfOptions.footerTemplate)
});
}
+7 -4
View File
@@ -8,6 +8,9 @@ var Plugin = require("./plugin");
var BaseGenerator = function(options) {
this.options = options;
// Base for assets in plugins
this.pluginAssetsBase = "book";
this.options.plugins = Plugin.normalizeNames(this.options.plugins);
this.options.plugins = _.union(this.options.plugins, this.options.defaultsPlugins);
this.plugins = [];
@@ -20,7 +23,9 @@ BaseGenerator.prototype.callHook = function(name, data) {
BaseGenerator.prototype.loadPlugins = function() {
var that = this;
return Plugin.fromList(this.options.plugins, this.options.input, this)
return Plugin.fromList(this.options.plugins, this.options.input, this, {
assetsBase: this.pluginAssetsBase
})
.then(function(_plugins) {
that.plugins = _plugins;
@@ -74,6 +79,4 @@ BaseGenerator.prototype.finish = function() {
return Q.reject(new Error("Could not finish generation"));
};
module.exports = BaseGenerator;
module.exports = BaseGenerator;
+1 -2
View File
@@ -26,7 +26,6 @@ Generator.prototype.convertFile = function(content, input) {
return Q()
.then(function() {
return parse.page(content, {
repo: that.options.githubId,
dir: path.dirname(input) || '/'
});
})
@@ -74,4 +73,4 @@ Generator.prototype.finish = function() {
// ignore
};
module.exports = Generator;
module.exports = Generator;
+10 -49
View File
@@ -8,54 +8,23 @@ var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../site");
/*
* This generator will generate a simple index.html which can be converted as a PDF
*/
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Base for assets in plugins
this.pluginAssetsBase = "ebook";
// List of pages content
this.pages = {};
};
util.inherits(Generator, BaseGenerator);
// Load all templates
Generator.prototype.loadTemplates = function() {
this.template = swig.compileFile(
this.plugins.template("page") || path.resolve(this.options.theme, 'templates/page.html')
);
};
Generator.prototype.convertFile = function(content, input) {
var that = this;
var json = {
path: input,
progress: parse.progress(this.options.navigation, input)
};
return Q()
.then(function() {
return parse.page(content, {
repo: that.options.githubId,
dir: path.dirname(input) || '/',
outdir: './',
singleFile: true
});
})
.then(function(sections) {
json.content = sections;
})
.then(function() {
that.pages[input] = json;
});
};
// Generate languages index
Generator.prototype.langsIndex = function(langs) {
return Q();
};
Generator.prototype.finish = function() {
var that = this;
var basePath = ".";
@@ -64,16 +33,6 @@ Generator.prototype.finish = function() {
var progress = parse.progress(this.options.navigation, "README.md");
return Q()
// Generate html
.then(function(pages) {
return that._writeTemplate(that.template, {
pages: that.pages,
progress: progress,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
}, output);
})
// Copy cover
.then(function() {
@@ -82,11 +41,13 @@ Generator.prototype.finish = function() {
// Copy assets
.then(function() {
return fs.copy(
path.join(that.options.theme, "assets"),
path.join(that.options.output, "gitbook")
);
return that.copyAssets();
});
};
module.exports = Generator;
// Generate languages index
Generator.prototype.langsIndex = function(langs) {
return Q();
};
module.exports = Generator;
+18 -13
View File
@@ -8,7 +8,7 @@ var resolve = require('resolve');
var pkg = require("../../package.json");
var RESOURCES = ["js", "css"];
var RESOURCES = ["js", "css"];
var Plugin = function(name, root, generator) {
this.name = name;
@@ -65,8 +65,9 @@ Plugin.prototype.normalizeResource = function(resource) {
};
// Return resources
Plugin.prototype._getResources = function() {
var book = this.infos.book;
Plugin.prototype._getResources = function(base) {
base = base || "book";
var book = this.infos[base];
// Nothing specified, fallback to default
if (!book) {
@@ -84,12 +85,10 @@ Plugin.prototype._getResources = function() {
};
// Normalize resources and return them
Plugin.prototype.getResources = function() {
Plugin.prototype.getResources = function(base) {
var that = this;
return this._getResources()
return this._getResources(base)
.then(function(resources) {
_.each(RESOURCES, function(resourceType) {
@@ -133,10 +132,14 @@ Plugin.prototype.callHook = function(name, data) {
};
// Copy plugin assets fodler
Plugin.prototype.copyAssets = function(out) {
Plugin.prototype.copyAssets = function(out, options) {
var that = this;
options = _.defaults(options || {}, {
base: "book"
});
return this.getResources().get('assets')
return this.getResources(options.base)
.get('assets')
.then(function(assets) {
// Assets are undefined
if(!assets) return false;
@@ -149,8 +152,6 @@ Plugin.prototype.copyAssets = function(out) {
};
// Normalize a list of plugin name to use
Plugin.normalizeNames = function(names) {
// Normalize list to an array
@@ -181,7 +182,11 @@ Plugin.normalizeNames = function(names) {
};
// Extract data from a list of plugin
Plugin.fromList = function(names, root, generator) {
Plugin.fromList = function(names, root, generator, options) {
options = _.defaults(options || {}, {
assetsBase: "book"
});
var failed = [];
// Load plugins
@@ -198,7 +203,7 @@ Plugin.fromList = function(names, root, generator) {
// Get resources of plugins
return Q.all(_.map(plugins, function(plugin) {
return plugin.getResources();
return plugin.getResources(options.assetsBase);
}))
// Extract resources out
// css, js, etc ...
-1
View File
@@ -1,5 +1,4 @@
var _ = require("lodash");
var kramed = require('kramed');
var textRenderer = require('kramed-text-renderer');
+30 -21
View File
@@ -59,10 +59,6 @@ Generator.prototype._writeTemplate = function(tpl, options, output, interpolate)
title: that.options.title,
description: that.options.description,
githubAuthor: that.options.github ? that.options.github.split("/")[0] : "",
githubId: that.options.github,
githubHost: that.options.githubHost,
glossary: that.options.glossary,
summary: that.options.summary,
@@ -95,20 +91,15 @@ Generator.prototype.indexPage = function(lexed, pagePath) {
return Q();
};
// Convert a markdown file to html
Generator.prototype.convertFile = function(content, _input) {
// Convert a markdown file into a normalized data set
Generator.prototype.prepareFile = function(content, _input) {
var that = this;
_output = _input.replace(".md", ".html");
if (_output == "README.html") _output = "index.html";
var input = path.join(this.options.input, _input);
var output = path.join(this.options.output, _output);
var basePath = path.relative(path.dirname(output), this.options.output) || ".";
var page = {
path: _input,
rawPath: input, // path to raw md file
rawPath: input,
content: content,
progress: parse.progress(this.options.navigation, _input)
};
@@ -131,14 +122,10 @@ Generator.prototype.convertFile = function(content, _input) {
return parse.lex(page.content);
})
.then(function(lexed) {
// Index page in search
return that.indexPage(lexed, _output)
.then(_.constant(lexed));
})
.then(function(lexed) {
page.lexed = lexed;
// Get HTML generated sections
return parse.page(lexed, {
repo: that.options.githubId,
dir: path.dirname(_input) || '/',
outdir: path.dirname(_input) || '/',
});
@@ -150,10 +137,30 @@ Generator.prototype.convertFile = function(content, _input) {
return _callHook("page");
})
.then(function() {
return page;
});
};
// Convert a markdown file to html
Generator.prototype.convertFile = function(content, _input) {
var that = this;
var _output = _input.replace(".md", ".html");
if (_output == "README.html") _output = "index.html";
var output = path.join(this.options.output, _output);
var basePath = path.relative(path.dirname(output), this.options.output) || ".";
return this.prepareFile(content, _input)
.then(function(page) {
// Index page in search
return that.indexPage(page.lexed, _output).thenResolve(page);
})
.then(function(page) {
// Write file
return that._writeTemplate(that.template, {
progress: page.progress,
_input: _input,
_input: page.path,
content: page.sections,
basePath: basePath,
@@ -161,7 +168,7 @@ Generator.prototype.convertFile = function(content, _input) {
}, output, function(html) {
page.content = html;
return _callHook("page:after").get("content")
return that.callHook("page:after", page).get("content")
});
});
};
@@ -238,7 +245,9 @@ Generator.prototype.copyAssets = function() {
return Q.all(
_.map(that.plugins.list, function(plugin) {
var pluginAssets = path.join(that.options.output, "gitbook/plugins/", plugin.name);
return plugin.copyAssets(pluginAssets);
return plugin.copyAssets(pluginAssets, {
base: that.pluginAssetsBase
});
})
);
});
+17
View File
@@ -0,0 +1,17 @@
var _ = require('lodash');
function isExercise(nodes) {
var codeType = { type: 'code' };
// Number of code nodes in section
var len = _.filter(nodes, codeType).length;
return (
// Got 3 or 4 code blocks
(len === 3 || len === 4) &&
// Ensure all nodes are at the end
_.all(_.last(nodes, len), codeType)
);
}
module.exports = isExercise;
+87
View File
@@ -0,0 +1,87 @@
var _ = require('lodash');
function isQuizNode(node) {
return (/^[(\[][ x][)\]]/).test(node.text || node);
}
function isTableQuestion(nodes) {
var block = questionBlock(nodes);
return (
block.length === 1 &&
block[0].type === 'table' &&
_.all(block[0].cells[0].slice(1), isQuizNode)
);
}
function isListQuestion(nodes) {
var block = questionBlock(nodes);
// Counter of when we go in and out of lists
var inlist = 0;
// Number of lists we found
var lists = 0;
// Elements found outside a list
var outsiders = 0;
// Ensure that we have nothing except lists
_.each(block, function(node) {
if(node.type === 'list_start') {
inlist++;
} else if(node.type === 'list_end') {
inlist--;
lists++;
} else if(inlist === 0) {
// Found non list_start or list_end whilst outside a list
outsiders++;
}
});
return lists > 0 && outsiders === 0;
}
function isQuestion(nodes) {
return isListQuestion(nodes) || isTableQuestion(nodes);
}
// Remove (optional) paragraph header node and blockquote
function questionBlock(nodes) {
return nodes.slice(
nodes[0].type === 'paragraph' ? 1 : 0,
_.findIndex(nodes, { type: 'blockquote_start' })
);
}
function splitQuestions(nodes) {
// Represents nodes in current question
var buffer = [];
return _.reduce(nodes, function(accu, node) {
// Add node to buffer
buffer.push(node);
// Flush buffer once we hit the end of a question
if(node.type === 'blockquote_end') {
accu.push(buffer);
// Clear buffer
buffer = [];
}
return accu;
}, []);
}
function isQuiz(nodes) {
// Extract potential questions
var questions = splitQuestions(
// Skip quiz title if there
nodes.slice(
(nodes[0] && nodes[0].type) === 'paragraph' ? 1 : 0
)
);
// Nothing that looks like questions
if(questions.length === 0) {
return false;
}
// Ensure all questions are correctly structured
return _.all(questions, isQuestion);
}
module.exports = isQuiz;
+3 -53
View File
@@ -1,6 +1,9 @@
var _ = require('lodash');
var kramed = require('kramed');
var isExercise = require('./is_exercise');
var isQuiz = require('./is_quiz');
// Split a page up into sections (lesson, exercises, ...)
function splitSections(nodes) {
var section = [];
@@ -17,59 +20,6 @@ function splitSections(nodes) {
}, []).concat([section]); // Add remaining nodes
}
function isQuizNode(node) {
return (/^[(\[][ x][)\]]/).test(node.text || node);
}
function isExercise(nodes) {
var codeType = { type: 'code' };
// Number of code nodes in section
var len = _.filter(nodes, codeType).length;
return (
// Got 3 or 4 code blocks
(len === 3 || len === 4) &&
// Ensure all nodes are at the end
_.all(_.last(nodes, len), codeType)
);
}
function isQuiz(nodes) {
if (nodes.length < 3) {
return false;
}
// Support having a first paragraph block
// before our series of questions
var quizNodes = nodes.slice(nodes[0].type === 'paragraph' ? 1 : 0);
// No questions
if (!_.some(quizNodes, { type: 'blockquote_start' })) {
return false;
}
// Check if section has list of questions
// or table of questions
var listIdx = _.findIndex(quizNodes, { type: 'list_item_start' });
var tableIdx = _.findIndex(quizNodes, { type: 'table' });
if(
// List of questions
listIdx !== -1 && isQuizNode(quizNodes[listIdx + 1]) ||
// Table of questions
(
tableIdx !== -1 &&
_.every(quizNodes[tableIdx].cells[0].slice(1), isQuizNode)
)
) {
return true;
}
return false;
}
// What is the type of this section
function sectionType(nodes, idx) {
if(isExercise(nodes)) {
+7 -16
View File
@@ -56,20 +56,9 @@ GitBookRenderer.prototype.link = function(href, title, text) {
var o = this._extra_options;
// Relative link, rewrite it to point to github repo
if(links.isRelative(_href)) {
if (path.extname(parsed.path) == ".md") {
_href = links.toAbsolute(_href, o.dir || "./", o.outdir || "./");
if (o.singleFile) {
_href = "#"+_href;
} else {
_href = _href.replace(".md", ".html");
}
} else if (o && o.repo && o.dir) {
href = url.resolve('https://github.com/' + o.repo + '/blob/', [o.dir, _href].join("/"));
parsed = url.parse(href);
_href = parsed.href;
}
if(links.isRelative(_href) && path.extname(parsed.path) == ".md") {
_href = links.toAbsolute(_href, o.dir || "./", o.outdir || "./");
_href = _href.replace(".md", ".html");
}
// Generate HTML for link
@@ -118,8 +107,10 @@ GitBookRenderer.prototype._createCheckboxAndRadios = function(text) {
if (!match) {
return text;
}
var quizIdentifier = 'quiz-row-' + this.id + '-' + this.quizRowId + '-' + this.quizIndex++;
var field = "<input name='" + quizIdentifier + "' id='" + quizIdentifier + "' type='";
//fix radio input uncheck failed
var quizFieldName='quiz-row-' + this.id + '-' + this.quizRowId ;
var quizIdentifier = quizFieldName + '-' + this.quizIndex++;
var field = "<input name='" + quizFieldName + "' id='" + quizIdentifier + "' type='";
field += match[1] === '(' ? "radio" : "checkbox";
field += match[2] === 'x' ? "' checked/>" : "'/>";
var splittedText = text.split(fieldRegex);
+7 -5
View File
@@ -1,13 +1,13 @@
{
"name": "gitbook",
"version": "1.0.0",
"version": "1.1.1",
"homepage": "http://www.gitbook.io/",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
"dependencies": {
"q": "1.0.1",
"lodash": "2.4.1",
"kramed": "0.4.1",
"kramed": "0.4.3",
"kramed-text-renderer": "0.2.1",
"lunr": "0.5.2",
"swig": "1.3.2",
@@ -16,15 +16,17 @@
"commander": "2.2.0",
"graceful-fs": "3.0.2",
"fs-extra": "0.10.0",
"highlight.js": "8.1.0",
"highlight.js": "8.2.0",
"tmp": "0.0.23",
"semver": "2.2.1",
"gaze": "~0.5.1",
"resolve": "0.6.3",
"tiny-lr-fork": "0.0.5",
"gitbook-plugin": "0.0.2",
"gitbook-plugin-mathjax": "0.0.4",
"gitbook-plugin-livereload": "0.0.1"
"gitbook-plugin-mathjax": "0.0.6",
"gitbook-plugin-livereload": "0.0.1",
"gitbook-plugin-exercises": "1.0.0",
"gitbook-plugin-quizzes": "1.0.0"
},
"devDependencies": {
"mocha": "1.18.2",
+120
View File
@@ -0,0 +1,120 @@
# Learn Node.js by Example
## Requirements
- [x] A computer with internet access
- [ ] Time: 30h (e.g. 2 months 30 mins per day or **1 week intensive**)
## What is Node.js ?
Node.js lets you *easily* build networked software (websites, applications "apps",
using JavaScript).
Its not "*point-and-click*" like WordPress, SquareSpace or Salesforce;
you will need to write some "code". But as I will demonstrate, that's
a *lot* easier than it sounds and gives you more power/flexibility
and puts you in full control.
## Node.js is not "Version 1.0" yet can I used it in Production?
Yes! Some of the biggest organisations/companies in the world
are using Node.js in Production systems:
[Alibaba](https://github.com/alibaba/node-hbase-client),
[Ajax.org](Ajax.org),
[Box.com](http://tech.blog.box.com/2014/06/node-js-high-availability-at-box/), British Sky Broadcasting (Sky/Now TV),
CNN,
[Cloudup](https://cloudup.com/),
Conde Nast,
[DirectTV](http://strongloop.com/strongblog/node-summit-media-companies-embrace-node-js-for-rapidly-developing-responsive-apps/),
[Dow Jones](http://nodejs.org/industry),
eBay,
[FeedHenry](http://www.feedhenry.com/),
[GitHub](https://twitter.com/github/status/16979699217465344),
[Google](http://venturebeat.com/2012/01/24/node-at-google-mozilla-yahoo/),
[Groupon](http://nodeup.com/fiftyeight),
HBO,
Help.com,
[HP](https://github.com/joyent/node/wiki/Projects,-Applications,-and-Companies-Using-Node),
iTV,
[Joyent](https://www.joyent.com/) (duh!),
[Klout](https://klout.com),
LinkedIn,
McDonalds,
[Medium](https://medium.com/the-story),
Mozilla,
NetFlix,
[OpenTable](http://hapijs.com/community),
PayPal,
Pearson,
~~Q~~,
[Revolt](http://revolt.tv/),
[Square](https://modulus.io/companies-using-node),
Tesco,
ThomasCook,
Trello,
Uber,
Voxer,
Walmart,
Wikimedia (in progress of moving to SOA with node!)
Yahoo,
Yammer,
[Yandex](https://www.youtube.com/watch?v=zdCxgdH4wZo),
[Zendesk](http://radar.zendesk.com/)
Want more? See: http://nodejs.org/industry/ and <br />
https://github.com/joyent/node/wiki/Projects,-Applications,-and-Companies-Using-Node
# Try it!
## Download & Install
> http://nodejs.org/download/
## Node.js (Core) API
The node.js ("core") has many useful modules.
Bookmark: [http://nodejs.org/api](http://nodejs.org/api/) (you will come back to it)
## Stability (Can we use it?)
> *Which node.js* ***core*** *package(s) can/should I use?*
Every core module has a
["***Stability Index***"](http://nodejs.org/api/documentation.html#documentation_stability_index)
rating on the node.js API.
**General rule**: If you are being *paid* to write code
that runs in node.js, <br /> pick core modules/methods
with stability **Stable**, **API Frozen** and **Locked**.
![Node.js Stability Index](http://i.imgur.com/xIroFrS.png)
### Examples
- [**cluster**](http://nodejs.org/api/cluster.html) is ***Experimental*** - don't use
- [**domain**](http://nodejs.org/api/domain.html) is ***Unstable*** - don't use
- [**path**](http://nodejs.org/api/path.html) is ***Stable*** - use
- [**events**](http://nodejs.org/api/events.html) is ***Frozen*** - use
- [**assert**](http://nodejs.org/api/assert.html) is ***Locked*** - use
Core Modules to Learn
- path
- os
Community Modules to Learn:
- [jscs](https://www.npmjs.org/package/jscs) - code style checker
- [q](https://www.npmjs.org/package/q) - promises library
- [nd](https://www.npmjs.org/package/nd) - view documentation for a module
+8 -28
View File
@@ -78,19 +78,6 @@ describe('Page parsing', function() {
describe('Relative links', function() {
it('should be resolved to their GitHub counterparts', function() {
var LEXED = loadPage('GITHUB_LINKS', {
// GitHub repo ID
repo: 'GitBookIO/javascript',
// Imaginary folder of markdown file
dir: 'course',
outdir: './'
});
assert(LEXED[0].content.indexOf('https://github.com/GitBookIO/javascript/blob/src/something.cpp') !== -1);
});
it('should replace link to .md by link to .html', function() {
var LEXED = loadPage('MARKDOWN_LINKS', {
// GitHub repo ID
@@ -104,21 +91,6 @@ describe('Relative links', function() {
assert(LEXED[0].content.indexOf('test.html') !== -1);
assert(LEXED[0].content.indexOf('../before.html') !== -1);
});
it('should replace link to .md by link to page in format single page', function() {
var LEXED = loadPage('MARKDOWN_LINKS', {
// GitHub repo ID
repo: 'GitBookIO/javascript',
// Imaginary folder of markdown file
dir: 'course',
outdir: './',
singleFile: true
});
assert(LEXED[0].content.indexOf('#course/test.md') !== -1);
assert(LEXED[0].content.indexOf('#before.md') !== -1);
});
});
describe('Relative images', function() {
@@ -150,3 +122,11 @@ describe('Relative images', function() {
assert(LEXED[0].content.indexOf('"preview2.png"') !== -1);
});
});
describe('Section parsing', function() {
it('should not have false positive quiz parsing', function() {
var LEXED = loadPage('FALSE_QUIZ');
assert.equal(LEXED[0].type, 'normal');
});
});
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+40 -6
View File
@@ -31,6 +31,8 @@ define([
return d.promise();
}
var pregQuote = function( str ) {
// http://kevin.vanzonneveld.net
// + original by: booeyOH
@@ -56,15 +58,47 @@ define([
});
};
var matchText = function(node, regex, callback, excludeElements) {
excludeElements || (excludeElements = ['script', 'style', 'iframe', 'canvas']);
var child = node.firstChild;
if (!child) return;
do {
switch (child.nodeType) {
case 1:
if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1) {
continue;
}
matchText(child, regex, callback, excludeElements);
break;
case 3:
child.data.replace(regex, function(all) {
var args = [].slice.call(arguments),
offset = args[args.length - 2],
newTextNode = child.splitText(offset);
newTextNode.data = newTextNode.data.substr(all.length);
callback.apply(window, [child].concat(args));
child = newTextNode;
});
break;
}
} while (child = child.nextSibling);
return node;
};
var replaceTerm = function($el, term) {
var r = new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
$el.find("p:contains('"+term.name+"')").each( function( i, element ) {
element = $(element);
var content = $(element).html();
content = content.replace(r, '<span class="glossary-term" data-glossary-term="' + term.id + '" title="' + term.description + '">$1</span>');
element.html(content);
matchText($el.get(0), r, function(node, match, offset) {
var span = document.createElement("span");
span.className = "glossary-term";
span.textContent = match;
span.setAttribute("data-glossary-term", term.id);
span.setAttribute("title", term.description);
node.parentNode.insertBefore(span, node.nextSibling);
});
};
-1
View File
@@ -7,7 +7,6 @@ define([
var $book = $(dom.find(".book"));
state.$book = $book;
state.githubId = $book.data("github");
state.level = $book.data("level");
state.basePath = $book.data("basepath");
state.revision = $book.data("revision");
-5
View File
@@ -18,11 +18,6 @@ define([
var $book;
$book = state.$book;
if (state.githubId) {
// Initialize storage
storage.setBaseKey(state.githubId);
}
// Init sidebar
sidebar.init();
+5 -8
View File
@@ -1,24 +1,21 @@
define([
"jQuery"
], function($) {
var url = location.href;
var title = $("title").text();
var types = {
"twitter": function($el) {
window.open("http://twitter.com/home?status="+encodeURIComponent(title+" "+url))
window.open("http://twitter.com/home?status="+encodeURIComponent($("title").text()+" "+location.href))
},
"facebook": function($el) {
window.open("http://www.facebook.com/sharer/sharer.php?s=100&p[url]="+encodeURIComponent(url))
window.open("http://www.facebook.com/sharer/sharer.php?s=100&p[url]="+encodeURIComponent(location.href))
},
"google-plus": function($el) {
window.open("https://plus.google.com/share?url="+encodeURIComponent(url))
window.open("https://plus.google.com/share?url="+encodeURIComponent(location.href))
},
"weibo": function($el) {
window.open("http://service.weibo.com/share/share.php?content=utf-8&url="+encodeURIComponent(url)+"&title="+encodeURIComponent(title))
window.open("http://service.weibo.com/share/share.php?content=utf-8&url="+encodeURIComponent(location.href)+"&title="+encodeURIComponent($("title").text()))
},
"instapaper": function($el) {
window.open("http://www.instapaper.com/text?u="+encodeURIComponent(url));
window.open("http://www.instapaper.com/text?u="+encodeURIComponent(location.href));
}
};
+44 -19
View File
@@ -3,30 +3,55 @@
@import "ebook/variables.less";
@import "ebook/highlight.less";
.book-chapter {
display: none;
}
.exercise, .quiz {
margin: 1cm 0cm;
padding: 0.4cm;
page-break-inside: avoid;
border: 3px solid #ddd;
.exercise-header {
margin-bottom: 0.4cm;
padding-bottom: 0.2cm;
border-bottom: 1px solid #ddd;
.page {
.book-chapter {
display: none;
}
.question {
margin-top: 0.4cm;
.exercise, .quiz {
margin: 1cm 0cm;
border: 2px solid #ddd;
.exercise-header {
padding: 0.1cm 0.3cm;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
font-weight: bold;
page-break-inside: avoid;
}
.exercise-inner {
padding: 0.15cm 0.3cm;
p:last-child {
margin: 0px;
}
}
hr {
height: 2px;
}
.question {
margin-top: 0.1cm;
border-top: 1px solid #ddd;
.question-base {
}
.question-solution {
}
}
}
}
body {
font-family: sans-serif;
.markdown-content(#333, 1.6);
.page {
font-family: sans-serif;
.markdown-content(#333, 1.6);
}
}
@@ -0,0 +1,11 @@
<div class="exercise-header">Exercise</div>
<div class="exercise-inner">
{% autoescape false %}{{ section.content }}{% endautoescape %}
</div>
<div class="exercise-inner">
<pre><code>{% autoescape false %}{{ section.code.base|code }}{% endautoescape %}</code></pre>
</div>
<hr>
<div class="exercise-inner">
<pre><code>{% autoescape false %}{{ section.code.solution|code }}{% endautoescape %}</code></pre>
</div>
+14
View File
@@ -0,0 +1,14 @@
<div class="exercise-header">Quiz</div>
<div class="exercise-inner">{% autoescape false %}{{ section.content }}{% endautoescape %}</div>
{% for quiz in section.quiz %}
<div class="question exercise-inner">
<div class="question-header">Question {{ loop.index }} of {{ section.quiz.length }}</div>
<div class="question-base">
{% autoescape false %}{{ quiz.base }}{% endautoescape %}
</div>
<div class="question-solution">
{% autoescape false %}{{ quiz.solution }}{% endautoescape %}
</div>
</div>
{% endfor %}
+18 -69
View File
@@ -1,87 +1,36 @@
{% extends "layout.html" %}
{% block title %}{{ title }}{% endblock %}
{% block title %}{{ progress.current.title }} | {{ title }}{% endblock %}
{% block style %}
<link rel="stylesheet" href="{{ staticBase }}/print.css">
{% for resource in plugins.resources.css %}
{% if resource.url %}
<link rel="stylesheet" href="{{ resource.url }}">
{% else %}
<link rel="stylesheet" href="{{ staticBase }}/plugins/{{ resource.path }}">
{% endif %}
{% endfor %}
{% endblock %}
{% macro articleContent(content) %}
{% block content %}
<div class="page">
<h1 class="book-chapter book-chapter-{{ progress.current.level|lvl }}">{{ progress.current.title }}</h1>
{% for section in content %}
<section class="{{ section.type }}" id="section-{{ section.id }}">
{% if section.type == "normal" %}
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% elif section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre><code>{% autoescape false %}{{ section.code.base|code }}{% endautoescape %}</code></pre>
{% set exercise = exercise + 1 %}
</div>
{% include "./includes/page/exercise.html" with {section: section} %}
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Quiz #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% for quiz in section.quiz %}
<div class="question">
<div class="question-header">Question {{ loop.index }} of {{ section.quiz.length }}</div>
{% autoescape false %}{{ quiz.base }}{% endautoescape %}
</div>
{% endfor %}
{% set exercise = exercise + 1 %}
</div>
{% include "./includes/page/quiz.html" with {section: section} %}
{% endif %}
{% endfor %}
{% endmacro %}
{% set exercise = 1 %}
{% block content %}
{# Pages content #}
{% for item in progress.chapters %}
<article id="{{ article.path }}">
<h1 class="book-chapter book-chapter-{{ item.level|lvl }}">{{ item.title }}</h1>
{% if pages[item.path] %}
{{ articleContent(pages[item.path].content) }}
{% endif %}
</article>
</section>
{% endfor %}
{# Exercise solutions #}
{% if exercise > 1 %}
{% set exercise = 1 %}
<article>
<h1 class="book-chapter book-chapter-1">Exercise Solutions</h1>
<h1>Exercise Solutions</h1>
{% for item in progress.chapters %}
{% if pages[item.path] %}
{% for section in pages[item.path].content %}
{% if section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre><code>{% autoescape false %}{{ section.code.solution|code }}{% endautoescape %}</code></pre>
{% set exercise = exercise + 1 %}
</div>
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Quiz #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% for quiz in section.quiz %}
<div class="question">
<div class="question-header">Question {{ loop.index }} of {{ section.quiz.length }}</div>
{% autoescape false %}{{ quiz.solution }}{% endautoescape %}
</div>
{% endfor %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</article>
{% if progress.current.next and progress.current.next.path %}
<a href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" />
{% endif %}
</div>
{% endblock %}
{% block javascript %}{% endblock %}
+1 -4
View File
@@ -2,9 +2,6 @@
{% block head %}
{% parent %}
{% if githubAuthor %}
<meta name="author" content="{{ githubAuthor }}">
{% endif %}
{% if progress.current.next and progress.current.next.path %}
<link rel="next" href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" />
{% endif %}
@@ -17,7 +14,7 @@
{% block description %}{% if progress.current.level == "0" %}{{ description }}{% endif %}{% endblock %}
{% block content %}
<div class="book" {% if githubId %}data-github="{{ githubId }}"{% endif %} data-level="{{ progress.current.level }}" data-basepath="{{ basePath }}" data-revision="{{ revision }}">
<div class="book" data-level="{{ progress.current.level }}" data-basepath="{{ basePath }}" data-revision="{{ revision }}">
{% include "includes/book/summary.html" %}
<div class="book-body">
<div class="body-inner">