mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-27 04:29:05 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb6204aa42 | |||
| a70439f375 | |||
| a3b562b4e8 | |||
| aa4e3abc33 | |||
| f24a7fbb6e | |||
| f3a49306b1 | |||
| f312472abd | |||
| ecb07468f6 | |||
| 8fc09e2e05 | |||
| ed7798237a | |||
| faf63566df | |||
| c88382aea0 | |||
| 9ee5f48aef | |||
| e1dfe5c0f6 | |||
| 3adcd77ae4 | |||
| 4bdcfd07a9 | |||
| a9967df6eb | |||
| 62a2e8f81c | |||
| 6bcc2d8cf2 | |||
| ad1867e4d8 | |||
| 445c2327c4 | |||
| 7a33881b3b | |||
| 417ee789c4 | |||
| 22c118779c | |||
| 53dc206d71 | |||
| 3c9d8878df | |||
| 03cbe7b18f | |||
| 85f4c3be58 | |||
| eb09f61029 | |||
| 2cee253be4 | |||
| 25162ceaa2 | |||
| 840113a48f | |||
| ddffd02b2e | |||
| 520ea847da | |||
| e77e3b724e | |||
| d313589736 | |||
| 466ceb581c | |||
| 835953d8dd | |||
| a69e8ec328 | |||
| 970107c656 | |||
| 7121b7efe3 | |||
| d02581a21c | |||
| fd5c2f6706 | |||
| b93f67c84a |
@@ -1,4 +1,93 @@
|
||||
gitbook
|
||||
GitBook
|
||||
=======
|
||||
|
||||
Library and command line utility for generating GitBooks
|
||||
GiBook is a command line tool (and Node.js library) for building beautiful programming books and exercises using GitHub/Git and Markdown. You can see an example: [Learn Javascript](http://gitbookio.github.io/javascript/).
|
||||
|
||||

|
||||
|
||||
## How to use it:
|
||||
|
||||
GitBook can be installed from **NPM** using:
|
||||
|
||||
```
|
||||
$ npm install gitbook -g
|
||||
```
|
||||
|
||||
You can serve a repository as a book using:
|
||||
|
||||
```
|
||||
$ gitbook serve ./repository
|
||||
```
|
||||
|
||||
Or simply build the static website using:
|
||||
|
||||
```
|
||||
$ gitbook build ./repository --output=./outputFolder
|
||||
```
|
||||
|
||||
Options for commands `build` and `serve` are:
|
||||
|
||||
```
|
||||
-t, --title <name> Name of the book to generate, defaults to repo name
|
||||
-i, --intro <intro> Description of the book to generate
|
||||
-g, --github <repo_path> ID of github repo like : username/repo
|
||||
```
|
||||
|
||||
## Book Format
|
||||
|
||||
A book is a GitHub repository containing at least 2 files: `README.md` and `SUMMARY.md`.
|
||||
|
||||
#### README.md
|
||||
|
||||
As usual, it should contains an introduction for your book. It will be automatically added to the final summary.
|
||||
|
||||
#### SUMMARY.md
|
||||
|
||||
The `SUMMARY.md` defines your book's structure. It should contain a list of chapters, linking to their respective pages.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
# Summary
|
||||
|
||||
This is the summary of my book.
|
||||
|
||||
* [section 1](section1/README.md)
|
||||
* [example 1](section1/example1.md)
|
||||
* [example 2](section1/example2.md)
|
||||
* [section 2](section2/README.md)
|
||||
* [example 1](section2/example1.md)
|
||||
```
|
||||
|
||||
Files that are not included in the `SUMMARY.md` will not be processed by `gitbook`.
|
||||
|
||||
#### Exercises
|
||||
|
||||
A book can contain interactive exercises (currently only in Javascript but Python and Ruby are coming soon ;) ). An exercise is a code challenge provided to the reader, which is given a code editor to write a solution which is checked against the book author's validation code.
|
||||
|
||||
An exercise is defined by 4 simple parts:
|
||||
|
||||
* Exercise **Message**/Goals (in markdown/text)
|
||||
* **Initial** code to show to the user, providing a starting point
|
||||
* **Solution** code, being a correct solution to the exercise
|
||||
* **Validation** code that tests the correctness of the user's input
|
||||
|
||||
Exercises need to start and finish with a separation bar (```---``` or ```***```). It should contain 3 code elements (**base**, **solution** and **validation**).
|
||||
|
||||
---
|
||||
|
||||
Define a variable `x` equal to 10.
|
||||
|
||||
```js
|
||||
var x =
|
||||
```
|
||||
|
||||
```js
|
||||
var x = 10;
|
||||
```
|
||||
|
||||
```js
|
||||
assert(x == 10);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
require([
|
||||
"jQuery",
|
||||
"utils/storage",
|
||||
"utils/analytic",
|
||||
"utils/sharing",
|
||||
|
||||
"core/state",
|
||||
"core/exercise",
|
||||
"core/progress",
|
||||
], function($, _state, exercise, progress){
|
||||
"core/sidebar"
|
||||
], function($, storage, analytic, sharing, _state, exercise, progress, sidebar){
|
||||
$(document).ready(function() {
|
||||
var state = _state();
|
||||
var $book = state.$book;
|
||||
|
||||
// Toggle summary
|
||||
$book.find(".book-header .toggle-summary").click(function(e) {
|
||||
e.preventDefault();
|
||||
$book.toggleClass("with-summary");
|
||||
});
|
||||
// Initialize storage
|
||||
storage.setBaseKey(state.githubId);
|
||||
|
||||
// Tract page view
|
||||
analytic.track("View");
|
||||
|
||||
// Init sidebar
|
||||
sidebar.init();
|
||||
|
||||
// Star and watch count
|
||||
$.getJSON("https://api.github.com/repos/"+state.githubId)
|
||||
@@ -24,6 +32,9 @@ require([
|
||||
// Bind exercise
|
||||
exercise.init();
|
||||
|
||||
// Bind sharing button
|
||||
sharing.init();
|
||||
|
||||
// Show progress
|
||||
progress.show();
|
||||
});
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
define([
|
||||
"jQuery",
|
||||
"utils/execute",
|
||||
"utils/analytic",
|
||||
"core/state"
|
||||
], function($, execute, state){
|
||||
], function($, execute, analytic, state){
|
||||
// Bind an exercise
|
||||
var prepareExercise = function($exercise) {
|
||||
var codeSolution = $exercise.find(".code-solution").html();
|
||||
var codeValidation = $exercise.find(".code-validation").html();
|
||||
var codeSolution = $exercise.find(".code-solution").text();
|
||||
var codeValidation = $exercise.find(".code-validation").text();
|
||||
|
||||
var editor = ace.edit($exercise.find(".editor").get(0));
|
||||
editor.setTheme("ace/theme/tomorrow");
|
||||
editor.getSession().setUseWorker(false);
|
||||
editor.getSession().setMode("ace/mode/javascript");
|
||||
|
||||
// Submit: test code
|
||||
$exercise.find(".action-submit").click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
analytic.track("exercise.submit");
|
||||
|
||||
execute(editor.getValue(), codeValidation, function(err, result) {
|
||||
$exercise.toggleClass("return-error", err != null);
|
||||
$exercise.toggleClass("return-success", err == null);
|
||||
@@ -28,6 +32,7 @@ define([
|
||||
e.preventDefault();
|
||||
|
||||
editor.setValue(codeSolution);
|
||||
editor.gotoLine(0);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -43,10 +43,7 @@ define([
|
||||
};
|
||||
|
||||
// Show progress
|
||||
var showProgress = function() {
|
||||
// Mark current progress
|
||||
markProgress(getCurrentLevel(), true);
|
||||
|
||||
var showProgress = function() {
|
||||
// Update progress
|
||||
var progress = getProgress();
|
||||
var $summary = $(".book-summary");
|
||||
@@ -54,6 +51,9 @@ define([
|
||||
_.each(progress, function(value, level) {
|
||||
$summary.find("li[data-level='"+level+"']").toggleClass("done", value > 0);
|
||||
});
|
||||
|
||||
// Mark current progress
|
||||
markProgress(getCurrentLevel(), true);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
define([
|
||||
"utils/storage",
|
||||
"utils/platform",
|
||||
"core/state"
|
||||
], function(storage, platform, state) {
|
||||
|
||||
// Toggle sidebar with or withour animation
|
||||
var toggleSidebar = function(_state, animation) {
|
||||
if (animation == null) animation = true;
|
||||
|
||||
var $book = state().$book;
|
||||
$book.toggleClass("without-animation", !animation);
|
||||
$book.toggleClass("with-summary", _state);
|
||||
|
||||
storage.set("sidebar", isOpen());
|
||||
};
|
||||
|
||||
// Return true if sidebar is open
|
||||
var isOpen = function() {
|
||||
return state().$book.hasClass("with-summary");
|
||||
};
|
||||
|
||||
// Prepare sidebar: state and toggle button
|
||||
var init = function() {
|
||||
var $book = state().$book;
|
||||
|
||||
// Toggle summary
|
||||
$book.find(".book-header .toggle-summary").click(function(e) {
|
||||
e.preventDefault();
|
||||
toggleSidebar();
|
||||
});
|
||||
|
||||
// Init last state if not mobile and not homepage
|
||||
if (!isOpen()) toggleSidebar(platform.isMobile ? false : storage.get("sidebar", false), false);
|
||||
};
|
||||
|
||||
return {
|
||||
init: init,
|
||||
toggle: toggleSidebar
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
define([], function() {
|
||||
var isAvailable = function() {
|
||||
return (typeof mixpanel !== "undefined");
|
||||
};
|
||||
|
||||
var track = function(event, data) {
|
||||
if (!isAvailable()) {
|
||||
console.log("tracking not available!");
|
||||
return;
|
||||
}
|
||||
mixpanel.track(event, data);
|
||||
};
|
||||
|
||||
return {
|
||||
isAvailable: isAvailable,
|
||||
track: track
|
||||
};
|
||||
});
|
||||
@@ -65,17 +65,12 @@ define(function(){
|
||||
};
|
||||
|
||||
|
||||
var ass = "function assert(condition, message) { \nif (!condition) { \n throw message || \"Assertion failed\"; \n } \n }\n";
|
||||
|
||||
var code = {
|
||||
"base": "var firstName = \"John\";\nvar middleName = \"James\";\nvar lastName = \"Smith\";\n\nvar fullName =",
|
||||
"solution": "var firstName = \"John\";\nvar middleName = \"James\";\nvar lastName = \"Smith\";\n\nvar fullName = firstName + \" \" + middleName + \" \" + lastName;",
|
||||
"validation": "console.log(fullName); assert(fullName == 'John James Smith');"
|
||||
};
|
||||
var ass = "function assert(condition, message) { \nif (!condition) { \n throw message || \"Assertion failed\"; \n } \n }\n";
|
||||
|
||||
var execute = function(solution, validation, callback) {
|
||||
// Validate with validation code
|
||||
evalJS([solution, ass, validation].join(";\n"), function(err, res) {
|
||||
var code = [solution, ass, validation].join(";\n");
|
||||
evalJS(code, function(err, res) {
|
||||
if(err) return callback(err);
|
||||
|
||||
if (res.type == "error") callback(new Error(res.value));
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
define([], function() {
|
||||
return {
|
||||
isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
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))
|
||||
},
|
||||
"facebook": function($el) {
|
||||
window.open("http://www.facebook.com/sharer/sharer.php?s=100&p[url]="+encodeURIComponent(url))
|
||||
},
|
||||
"google-plus": function($el) {
|
||||
window.open("https://plus.google.com/share?url="+encodeURIComponent(url))
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Bind all sharing button
|
||||
var init = function() {
|
||||
$("a[data-sharing]").click(function(e) {
|
||||
if (e) e.preventDefault();
|
||||
var type = $(this).data("sharing");
|
||||
|
||||
types[type]($(this));
|
||||
})
|
||||
};
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
});
|
||||
@@ -1,12 +1,19 @@
|
||||
define(function(){
|
||||
var baseKey = "";
|
||||
|
||||
/*
|
||||
* Simple module for storing data in the browser's local storage
|
||||
*/
|
||||
return {
|
||||
setBaseKey: function(key) {
|
||||
baseKey = key;
|
||||
},
|
||||
set: function(key, value) {
|
||||
key = baseKey+":"+key;
|
||||
localStorage[key] = JSON.stringify(value);
|
||||
},
|
||||
get: function(key, def) {
|
||||
key = baseKey+":"+key;
|
||||
try {
|
||||
return JSON.parse(localStorage[key]) || def;
|
||||
} catch(err) {
|
||||
@@ -14,6 +21,7 @@ define(function(){
|
||||
}
|
||||
},
|
||||
remove: function(key) {
|
||||
key = baseKey+":"+key;
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because one or more lines are too long
@@ -24,7 +24,9 @@
|
||||
margin: 0px 0px;
|
||||
padding: 5px 15px;
|
||||
background: #fff;
|
||||
border-radius: 0px;
|
||||
border-radius: 2px;
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
@@ -46,4 +48,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.without-animation {
|
||||
.book-body {
|
||||
.transition(none) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
section.exercise {
|
||||
padding: 0px;
|
||||
margin: 20px 0px;
|
||||
border: 2px solid #2f8cde;
|
||||
border: 3px solid #2f8cde;
|
||||
|
||||
.header {
|
||||
padding: 5px 15px;
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
.editor {
|
||||
min-height: 50px;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
color: #fff;
|
||||
@@ -16,7 +18,8 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 24px;
|
||||
line-height: 80px;
|
||||
line-height: 60px;
|
||||
border-radius: 2px;
|
||||
|
||||
background: @brand-success;
|
||||
color: inherit;
|
||||
@@ -34,12 +37,6 @@
|
||||
&.finished, &.finished:hover {
|
||||
background: darken(@btn-success-bg, 5%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,4 +58,9 @@
|
||||
margin-left: 250px;
|
||||
}
|
||||
}
|
||||
&.without-animation {
|
||||
.book-header h1 {
|
||||
.transition(none) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
.book .book-body .page-wrapper .page-inner section.normal {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
padding: 30px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding: 25px;
|
||||
padding-top: 15px;
|
||||
background-color: white;
|
||||
|
||||
|
||||
@@ -113,6 +110,13 @@
|
||||
|
||||
ul :last-child, ol :last-child {
|
||||
margin-bottom: 0; }
|
||||
|
||||
ul p {
|
||||
margin: 0px;
|
||||
}
|
||||
ul ul {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
dl {
|
||||
padding: 0; }
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
.book .book-body {
|
||||
@chapter-size: 16px;
|
||||
|
||||
@bar-background: #eee;
|
||||
|
||||
.book-progress {
|
||||
@@ -65,10 +66,22 @@
|
||||
background: @bar-background;
|
||||
box-shadow: 0px 0px 1px #bbb;
|
||||
|
||||
&.new-chapter {
|
||||
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: @brand-success;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
display: none;
|
||||
|
||||
&.new-chapter {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +90,10 @@
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.without-animation {
|
||||
.book-summary {
|
||||
.transition(none) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||
|
||||
/* Tomorrow Comment */
|
||||
.hljs-comment,
|
||||
.hljs-title {
|
||||
color: #8e908c;
|
||||
}
|
||||
|
||||
/* Tomorrow Red */
|
||||
.hljs-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-regexp,
|
||||
.ruby .hljs-constant,
|
||||
.xml .hljs-tag .hljs-title,
|
||||
.xml .hljs-pi,
|
||||
.xml .hljs-doctype,
|
||||
.html .hljs-doctype,
|
||||
.css .hljs-id,
|
||||
.css .hljs-class,
|
||||
.css .hljs-pseudo {
|
||||
color: #c82829;
|
||||
}
|
||||
|
||||
/* Tomorrow Orange */
|
||||
.hljs-number,
|
||||
.hljs-preprocessor,
|
||||
.hljs-pragma,
|
||||
.hljs-built_in,
|
||||
.hljs-literal,
|
||||
.hljs-params,
|
||||
.hljs-constant {
|
||||
color: #f5871f;
|
||||
}
|
||||
|
||||
/* Tomorrow Yellow */
|
||||
.ruby .hljs-class .hljs-title,
|
||||
.css .hljs-rules .hljs-attribute {
|
||||
color: #eab700;
|
||||
}
|
||||
|
||||
/* Tomorrow Green */
|
||||
.hljs-string,
|
||||
.hljs-value,
|
||||
.hljs-inheritance,
|
||||
.hljs-header,
|
||||
.ruby .hljs-symbol,
|
||||
.xml .hljs-cdata {
|
||||
color: #718c00;
|
||||
}
|
||||
|
||||
/* Tomorrow Aqua */
|
||||
.css .hljs-hexcolor {
|
||||
color: #3e999f;
|
||||
}
|
||||
|
||||
/* Tomorrow Blue */
|
||||
.hljs-function,
|
||||
.python .hljs-decorator,
|
||||
.python .hljs-title,
|
||||
.ruby .hljs-function .hljs-title,
|
||||
.ruby .hljs-title .hljs-keyword,
|
||||
.perl .hljs-sub,
|
||||
.javascript .hljs-title,
|
||||
.coffeescript .hljs-title {
|
||||
color: #4271ae;
|
||||
}
|
||||
|
||||
/* Tomorrow Purple */
|
||||
.hljs-keyword,
|
||||
.javascript .hljs-function {
|
||||
color: #8959a8;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
color: #4d4d4c;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.coffeescript .javascript,
|
||||
.javascript .xml,
|
||||
.tex .hljs-formula,
|
||||
.xml .javascript,
|
||||
.xml .vbscript,
|
||||
.xml .css,
|
||||
.xml .hljs-cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
@import "variables.less";
|
||||
@import "fonts.less";
|
||||
|
||||
@import "highlight.less";
|
||||
|
||||
@import "book/header.less";
|
||||
@import "book/summary.less";
|
||||
@import "book/body.less";
|
||||
|
||||
+3
-1
@@ -26,11 +26,12 @@ prog
|
||||
.description('Build a gitbook from a directory')
|
||||
.option('-o, --output <directory>', 'Path to output directory, defaults to ./_book')
|
||||
.option('-t, --title <name>', 'Name of the book to generate, defaults to repo name')
|
||||
.option('-i, --intro <intro>', 'Description of the book to generate')
|
||||
.option('-g, --github <repo_path>', 'ID of github repo like : username/repo')
|
||||
.action(buildFunc = function(dir, options) {
|
||||
dir = dir || process.cwd();
|
||||
outputDir = options.output || path.join(dir, '_book');
|
||||
|
||||
|
||||
console.log('Starting build ...');
|
||||
// Get repo's URL
|
||||
return utils.gitURL(dir)
|
||||
@@ -47,6 +48,7 @@ prog
|
||||
outputDir,
|
||||
{
|
||||
title: options.title || utils.titleCase(repo),
|
||||
description: options.intro,
|
||||
github: options.github || repoID
|
||||
}
|
||||
);
|
||||
|
||||
@@ -11,8 +11,9 @@ var generate = function(root, output, options) {
|
||||
var files, summary, navigation, tpl;
|
||||
|
||||
options = _.defaults(options || {}, {
|
||||
// Book title
|
||||
// Book title, keyword, description
|
||||
title: null,
|
||||
description: "Book generated using GitBook",
|
||||
|
||||
// Origin github repository id
|
||||
github: null
|
||||
@@ -59,9 +60,12 @@ var generate = function(root, output, options) {
|
||||
root: root,
|
||||
output: output,
|
||||
locals: {
|
||||
title: options.title,
|
||||
description: options.description,
|
||||
|
||||
githubAuthor: options.github.split("/")[0],
|
||||
githubId: options.github,
|
||||
title: options.title,
|
||||
|
||||
summary: summary,
|
||||
allNavigation: navigation
|
||||
}
|
||||
|
||||
@@ -38,7 +38,10 @@ var initTemplate = function(options) {
|
||||
|
||||
// Parse sections
|
||||
.then(function(markdown) {
|
||||
return parse.page(markdown);
|
||||
return parse.page(markdown, {
|
||||
repo: options.locals.githubId,
|
||||
dir: path.dirname(_input) || '/'
|
||||
});
|
||||
})
|
||||
|
||||
//Calcul template
|
||||
@@ -49,7 +52,6 @@ var initTemplate = function(options) {
|
||||
content: sections,
|
||||
basePath: basePath,
|
||||
staticBase: path.join(basePath, "gitbook"),
|
||||
navigation: options.locals.allNavigation[_output],
|
||||
progress: parse.progress(options.locals.allNavigation, _output)
|
||||
})
|
||||
);
|
||||
|
||||
@@ -45,6 +45,7 @@ function navigation(summary, files) {
|
||||
|
||||
// Add chapter mapping
|
||||
mapping[chapter.path] = {
|
||||
title: chapter.title,
|
||||
prev: clean(prev),
|
||||
next: clean(next),
|
||||
level: (idx+1).toString(),
|
||||
@@ -59,6 +60,7 @@ function navigation(summary, files) {
|
||||
var next = (_idx+1 >= articles.length) ? nextChapter : clean(articles[_idx+1]);
|
||||
|
||||
mapping[article.path] = {
|
||||
title: article.title,
|
||||
prev: clean(prev),
|
||||
next: clean(next),
|
||||
level: [idx+1, _idx+1].join('.'),
|
||||
@@ -68,6 +70,7 @@ function navigation(summary, files) {
|
||||
|
||||
// Hack for README.html
|
||||
mapping['README.html'] = {
|
||||
title: README_NAV.title,
|
||||
prev: null,
|
||||
next: clean(summary.chapters[0]),
|
||||
level: '0',
|
||||
|
||||
+32
-4
@@ -1,8 +1,20 @@
|
||||
var _ = require('lodash');
|
||||
var marked = require('marked');
|
||||
var hljs = require('highlight.js');
|
||||
|
||||
var renderer = require('./renderer');
|
||||
|
||||
// Synchronous highlighting with highlight.js
|
||||
marked.setOptions({
|
||||
highlight: function (code, lang) {
|
||||
try {
|
||||
return hljs.highlight(lang, code).value;
|
||||
} catch(e) {
|
||||
return hljs.highlightAuto(code).value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Split a page up into sections (lesson, exercises, ...)
|
||||
function splitSections(nodes) {
|
||||
@@ -34,19 +46,22 @@ function sectionType(nodes, idx) {
|
||||
}
|
||||
|
||||
// Render a section using our custom renderer
|
||||
function render(section) {
|
||||
function render(section, _options) {
|
||||
// marked's Render expects this, we don't use it yet
|
||||
section.links = {};
|
||||
|
||||
// Build options using defaults and our custom renderer
|
||||
var options = _.extend({}, marked.defaults, {
|
||||
renderer: renderer()
|
||||
renderer: renderer(null, _options)
|
||||
});
|
||||
|
||||
return marked.parser(section, options);
|
||||
}
|
||||
|
||||
function parsePage(src) {
|
||||
function parsePage(src, options) {
|
||||
options = options || {};
|
||||
|
||||
// Lex file
|
||||
var nodes = marked.lexer(src);
|
||||
|
||||
return _.chain(splitSections(nodes))
|
||||
@@ -58,6 +73,19 @@ function parsePage(src) {
|
||||
.filter(function(section) {
|
||||
return !_.isEmpty(section);
|
||||
})
|
||||
.reduce(function(sections, section) {
|
||||
var last = _.last(sections);
|
||||
|
||||
// Merge normal sections together
|
||||
if(last && last.type === section.type && last.type === 'normal') {
|
||||
last.push.apply(last, [{'type': 'hr'}].concat(section));
|
||||
} else {
|
||||
// Add to list of sections
|
||||
sections.push(section);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}, [])
|
||||
.map(function(section) {
|
||||
// Generate a uniqueId to identify this section in our code
|
||||
var id = _.uniqueId('gitbook_');
|
||||
@@ -88,7 +116,7 @@ function parsePage(src) {
|
||||
return {
|
||||
id: id,
|
||||
type: section.type,
|
||||
content: render(section)
|
||||
content: render(section, options)
|
||||
};
|
||||
})
|
||||
.value();
|
||||
|
||||
+11
-3
@@ -2,7 +2,7 @@ var _ = require("lodash");
|
||||
|
||||
var calculProgress = function(navigation, current) {
|
||||
var n = _.size(navigation);
|
||||
var percent = 0, prevPercent = 0;
|
||||
var percent = 0, prevPercent = 0, currentChapter = null;
|
||||
var done = true;
|
||||
|
||||
var chapters = _.chain(navigation)
|
||||
@@ -20,6 +20,7 @@ var calculProgress = function(navigation, current) {
|
||||
// Is it done
|
||||
nav.done = done;
|
||||
if (nav.path == current) {
|
||||
currentChapter = nav;
|
||||
percent = nav.percent;
|
||||
done = false;
|
||||
} else if (done) {
|
||||
@@ -30,11 +31,18 @@ var calculProgress = function(navigation, current) {
|
||||
})
|
||||
.value();
|
||||
|
||||
|
||||
return {
|
||||
// Previous percent
|
||||
prevPercent: prevPercent,
|
||||
|
||||
// Current percent
|
||||
percent: percent,
|
||||
chapters: chapters
|
||||
|
||||
// List of chapter with progress
|
||||
chapters: chapters,
|
||||
|
||||
// Current chapter
|
||||
current: currentChapter
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -1,14 +1,18 @@
|
||||
var url = require('url');
|
||||
var inherits = require('util').inherits;
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var marked = require('marked');
|
||||
|
||||
|
||||
function GitBookRenderer(options) {
|
||||
function GitBookRenderer(options, extra_options) {
|
||||
if(!(this instanceof GitBookRenderer)) {
|
||||
return new GitBookRenderer(options);
|
||||
return new GitBookRenderer(options, extra_options);
|
||||
}
|
||||
GitBookRenderer.super_.call(this, options);
|
||||
|
||||
this._extra_options = extra_options;
|
||||
}
|
||||
inherits(GitBookRenderer, marked.Renderer);
|
||||
|
||||
@@ -39,6 +43,16 @@ GitBookRenderer.prototype.link = function(href, title, text) {
|
||||
// Parsed version of the url
|
||||
var parsed = url.parse(href);
|
||||
|
||||
var o = this._extra_options;
|
||||
// Relative link, rewrite it to point to github repo
|
||||
if(parsed.path[0] != '/' && o && o.repo && o.dir) {
|
||||
href = 'https://github.com/' + o.repo + '/blob' + path.normalize(path.join(
|
||||
'/',
|
||||
o.dir,
|
||||
href
|
||||
));
|
||||
parsed = url.parse(href);
|
||||
}
|
||||
|
||||
// Generate HTML for link
|
||||
var out = '<a href="' + href + '"';
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.4",
|
||||
"description": "Library and cmd utility to generate GitBooks",
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
@@ -11,7 +11,8 @@
|
||||
"send": "0.2.0",
|
||||
"fstream-ignore": "0.0.7",
|
||||
"commander": "2.2.0",
|
||||
"fs-extra": "0.8.1"
|
||||
"fs-extra": "0.8.1",
|
||||
"highlight.js": "8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "1.18.2",
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
@@ -1,9 +1,13 @@
|
||||
<div class="book-header">
|
||||
<!-- Actions Left -->
|
||||
<a href="https://github.com/{{ githubId }}" target="_blank" class="btn pull-left"><i class="fa fa-github-alt"></i></a>
|
||||
<a href="#" class="btn pull-left toggle-summary"><i class="fa fa-align-justify"></i> Summary</a>
|
||||
<a href="#" class="btn pull-left toggle-summary"><i class="fa fa-align-justify"></i></a>
|
||||
|
||||
<!-- Actions Right -->
|
||||
<a href="#" target="_blank" class="btn pull-right" data-sharing="google-plus"><i class="fa fa-google-plus"></i></a>
|
||||
<a href="#" target="_blank" class="btn pull-right" data-sharing="facebook"><i class="fa fa-facebook"></i></a>
|
||||
<a href="#" target="_blank" class="btn pull-right" data-sharing="twitter"><i class="fa fa-twitter"></i></a>
|
||||
|
||||
<a href="https://github.com/{{ githubId }}/stargazers" target="_blank" class="btn pull-right count-star"><i class="fa fa-star-o"></i> Star (<span>-</span>)</a>
|
||||
<a href="https://github.com/{{ githubId }}/watchers" target="_blank" class="btn pull-right count-watch"><i class="fa fa-eye"></i> Watch (<span>-</span>)</a>
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<li>
|
||||
<a href="https://github.com/{{ githubId }}/issues" target="blank">Questions and Issues</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/{{ githubId }}/edit/master/{{ _input }}" target="blank">Edit and Contribute</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li data-level="0">
|
||||
<a href="{{ basePath }}/README.html"><i class="fa fa-check"></i> Introduction</a>
|
||||
|
||||
+26
-7
@@ -1,18 +1,37 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<head prefix="og: http://ogp.me/ns# book: http://ogp.me/ns/book#">
|
||||
{% block head %}
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %} | GitBook{% endblock %}</title>
|
||||
<meta name="robots" content="{% block robots %}noindex, nofollow{% endblock %}">
|
||||
<link rel="icon" href="{{ staticBase }}/images/icons/32.png">
|
||||
<link rel="stylesheet" href="{{ staticBase }}/style.css">
|
||||
<title>{% block title %} | {{ title }}{% endblock %}</title>
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="{% block description %}{% endblock %}">
|
||||
<meta name="keywords" content="{% block keywords %}{% endblock %}" >
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta name="author" content="{{ githubAuthor }}">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="keywords" content="gitbook,github" >
|
||||
<meta name="generator" content="www.gitbook.io">
|
||||
|
||||
<meta property="og:title" content="{% block title %} | {{ title }}{% endblock %}">
|
||||
<meta property="og:site_name" content="{{ title }}">
|
||||
<meta property="og:type" content="book">
|
||||
<meta property="og:locale" content="en_US">
|
||||
|
||||
<meta property="book:author" content="https://github.com/{{ githubAuthor }}">
|
||||
<meta property="book:tag" content="GitBook">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<link rel="shortcut icon" href="{{ staticBase }}/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ staticBase }}/style.css">
|
||||
|
||||
<script type="text/javascript">
|
||||
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");
|
||||
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
|
||||
mixpanel.init("01eb2b950ae09a5fdb15a98dcc5ff20e");
|
||||
</script>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+7
-9
@@ -1,10 +1,8 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}{{ _input }} {{ title }}{% parent %}{% endblock %}
|
||||
{% block description %}{% endblock %}
|
||||
{% block robots %}index, follow{% endblock %}
|
||||
{% block title %}{{ progress.current.title }}{% parent %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="book {% if _input == "README.md" %}with-summary{% endif %}" data-github="{{ githubId }}" data-level="{{ navigation.level }}">
|
||||
<div class="book {% if _input == "README.md" %}with-summary{% endif %}" data-github="{{ githubId }}" data-level="{{ progress.current.level }}">
|
||||
{% include "includes/book/header.html" %}
|
||||
{% include "includes/book/summary.html" %}
|
||||
<div class="book-body">
|
||||
@@ -15,7 +13,7 @@
|
||||
</div>
|
||||
<div class="chapters">
|
||||
{% for p in progress.chapters %}
|
||||
<div class="chapter {% if p.done %}done{% endif %}" data-progress="{{ p.level }}" style="left: {{ p.percent }}%;"></div>
|
||||
<a href="{{ basePath }}/{{ p.path }}" title="{{ p.title }}" class="chapter {% if p.done %}done{% endif %} {% if p.level.length == 1 %}new-chapter{% endif %}" data-progress="{{ p.level }}" style="left: {{ p.percent }}%;"></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,11 +32,11 @@
|
||||
|
||||
<div class="page-footer">
|
||||
{% if _input == "README.md" %}
|
||||
<a href="{{ basePath }}/{{ navigation.next.path }}" class="navigation-link">Start this book</a>
|
||||
<a href="{{ basePath }}/{{ progress.current.next.path }}" class="navigation-link">Start</a>
|
||||
{% else %}
|
||||
{% if navigation.next %}
|
||||
{% if navigation.next.path %}
|
||||
<a href="{{ basePath }}/{{ navigation.next.path }}" class="navigation-link next">Next<span>: {{ navigation.next.title }}</span></a>
|
||||
{% if progress.current.next %}
|
||||
{% if progress.current.next.path %}
|
||||
<a href="{{ basePath }}/{{ progress.current.next.path }}" class="navigation-link next">Next</a>
|
||||
{% else %}
|
||||
<div class="navigation-link coming-soon">Coming soon</div>
|
||||
{% endif %}
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
# Nice course
|
||||
|
||||
Check out this source file [in C++](../src/something.cpp)
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
## Wow such book
|
||||
|
||||
Some nice content here
|
||||
|
||||
---
|
||||
|
||||
A beautiful separator, but non an exercise !
|
||||
|
||||
---
|
||||
|
||||
Some more beautiful text, because `this` book is awesome ...
|
||||
@@ -8,6 +8,12 @@ var page = require('../').parse.page;
|
||||
var CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/PAGE.md'), 'utf8');
|
||||
var LEXED = page(CONTENT);
|
||||
|
||||
var HR_CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/HR_PAGE.md'), 'utf8');
|
||||
var HR_LEXED = page(HR_CONTENT);
|
||||
|
||||
var LINKS_CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/GITHUB_LINKS.md'), 'utf8');
|
||||
|
||||
|
||||
describe('Page parsing', function() {
|
||||
it('should detection sections', function() {
|
||||
assert.equal(LEXED.length, 3);
|
||||
@@ -31,4 +37,27 @@ describe('Page parsing', function() {
|
||||
assert(LEXED[1].code.solution);
|
||||
assert(LEXED[1].code.validation);
|
||||
});
|
||||
|
||||
it('should merge sections correctly', function() {
|
||||
// One big section
|
||||
assert.equal(HR_LEXED.length, 1);
|
||||
|
||||
// HRs inserted correctly
|
||||
assert.equal(HR_LEXED[0].content.match(/<hr>/g).length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Relative links', function() {
|
||||
it('should be resolved to their GitHub counterparts', function() {
|
||||
var LEXED = page(LINKS_CONTENT, {
|
||||
// GitHub repo ID
|
||||
repo: 'GitBookIO/javascript',
|
||||
|
||||
// Imaginary folder of markdown file
|
||||
dir: 'course',
|
||||
});
|
||||
|
||||
assert(LEXED[0].content.indexOf('https://github.com/GitBookIO/javascript/blob/src/something.cpp') !== -1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user