Compare commits

...

80 Commits

Author SHA1 Message Date
Samy Pessé 9d541a0a4d Bump version to 0.1.2 2014-04-07 01:22:58 -07:00
Samy Pessé 8f3df085d8 Center images in markdown body 2014-04-07 01:11:08 -07:00
Samy Pessé b7e04b5475 Don't use caching for search index 2014-04-07 01:08:11 -07:00
Samy Pessé ee8bf6dc4c Fix display of table in page body 2014-04-07 01:06:44 -07:00
Samy Pessé 9670f564d9 Improve new navigation on mobile 2014-04-07 01:03:35 -07:00
Samy Pessé b541df2b93 Add new lateral navigations 2014-04-07 00:44:30 -07:00
Samy Pessé cf3a641350 Fix progress link to README 2014-04-07 00:02:00 -07:00
Samy Pessé ec719891a1 Remove need for README.md for multi-languages 2014-04-06 23:53:39 -07:00
Samy Pessé 1bfa8c186c Fix overflow on languages page 2014-04-06 23:51:16 -07:00
Samy Pessé 5887f1de28 Merge pull request #42 from GitbookIO/feature/ebook
Fix #10: Add generation to ebook
2014-04-06 22:28:17 -07:00
Samy Pessé a9a66bc240 Add format ebook to README.md 2014-04-06 22:09:52 -07:00
Samy Pessé 742229a989 Add command "ebook" to generate an ebook 2014-04-06 22:03:44 -07:00
Samy Pessé 9fe80b878e Add option cover for ebook generation 2014-04-06 21:46:11 -07:00
Samy Pessé 4b64c87011 Add base ebook generator using ebook-convert 2014-04-06 19:10:22 -07:00
Samy Pessé 4c36c592c1 Merge pull request #40 from GitbookIO/feature/searchclient
Feature/searchclient
2014-04-06 17:13:32 -07:00
Samy Pessé 9ffb1a062f Use cache in localstorage to store search index 2014-04-06 17:13:10 -07:00
Samy Pessé 536c480969 Fix loading of search index in sub directories 2014-04-06 17:06:01 -07:00
Samy Pessé d14bd7245f Open sidebar when opening search 2014-04-06 16:51:14 -07:00
Samy Pessé 6fc8255b7f Reset search results when hidding search bar 2014-04-06 16:47:28 -07:00
Samy Pessé a42f287a92 Filter summary using search bar 2014-04-06 16:46:29 -07:00
Samy Pessé d2f62fd109 Move search bar as global 2014-04-06 16:20:31 -07:00
Samy Pessé f24b66f2c4 Blur search input when hidding search bar 2014-04-06 16:17:54 -07:00
Samy Pessé 607b45f172 Change norm of core/state 2014-04-06 16:10:25 -07:00
Aaron O'Mullan c166a9863e Resolve image URLs, fixes #38
Using outidr & dir options
2014-04-06 16:05:12 -07:00
Aaron O'Mullan 1a4b196851 Add outdir option to generators' generateFile 2014-04-06 16:04:28 -07:00
Samy Pessé ff52a2317b Hide the search bar when pressing ESC key 2014-04-06 16:02:43 -07:00
Samy Pessé b598c9a272 Add keyboard shortcut "f" for opening search bar 2014-04-06 15:58:00 -07:00
Samy Pessé 9cd7c9ad0e Focus search input when opening 2014-04-06 15:56:28 -07:00
Aaron O'Mullan 75a8d3069d Fix dir option in site generator
Use relative folder not absolute
2014-04-06 15:55:24 -07:00
Samy Pessé ed41429d58 Add base search bar in sidebar 2014-04-06 15:53:53 -07:00
Aaron O'Mullan eecb33baa8 Fix relative link detection in renderer 2014-04-06 15:46:20 -07:00
Aaron O'Mullan b8f2da734e Fix minor repetion in README
.gitignore was repeated twice
2014-04-06 15:25:39 -07:00
Aaron O'Mullan 3687a1701b Fix a few typos in README 2014-04-06 15:23:07 -07:00
Aaron O'Mullan c8f83e2684 Document ignores in README, fixes #37 2014-04-06 15:21:57 -07:00
Aaron O'Mullan aa7835e913 Add .bookignore to ignoreFiles #37 2014-04-06 15:19:31 -07:00
Samy Pessé 74edc4f279 Merge pull request #39 from GitbookIO/feature/search
Feature/search
2014-04-06 15:03:27 -07:00
Aaron O'Mullan 696cbdc3b1 Generate section.id in lexer
This allows generating correct URLs for our search index
2014-04-06 14:53:33 -07:00
Aaron O'Mullan 2f8543d710 Generate search_index.json when generating site
Fixes #24
2014-04-06 14:50:11 -07:00
Aaron O'Mullan aee33d67b6 Add lunr@0.5.2 dependency 2014-04-06 14:49:44 -07:00
Samy Pessé 52ccc2b46d Fix #33: copy files when generating pdf or single page 2014-04-06 14:37:33 -07:00
Aaron O'Mullan 9270d7949a In page parser, lex only if not already lexed 2014-04-06 14:13:48 -07:00
Samy Pessé 855c9b9aef Add documentation about multi-languages books 2014-04-06 14:08:11 -07:00
Aaron O'Mullan c98ba22b08 Move page lexer to separate module
Allow lexer to be used independently of page parser/renderer
2014-04-06 14:02:49 -07:00
Samy Pessé d1e55aef8e Add support of multi-languages book in command pdf 2014-04-06 13:59:58 -07:00
Aaron O'Mullan ef059c8737 Add marked-text-renderer@0.0.1 dependency 2014-04-06 13:50:44 -07:00
Samy Pessé fde66ed849 bin/build after generation return all the options 2014-04-06 13:49:51 -07:00
Samy Pessé abce29866e Return options after generate 2014-04-06 13:47:29 -07:00
Samy Pessé 014b2f9c69 Merge pull request #35 from GitbookIO/feature/multilangs
Fix #34: Feature/multilangs
2014-04-06 13:44:14 -07:00
Samy Pessé 65cb23feab Fix generation for page format 2014-04-06 13:43:20 -07:00
Samy Pessé 2b3c67a26b Fix commands options 2014-04-06 13:16:06 -07:00
Samy Pessé 174ce965d9 Fix command build 2014-04-06 13:09:42 -07:00
Samy Pessé adf20e6a16 Add base style for languages index page 2014-04-06 13:07:57 -07:00
Samy Pessé 53f714b7f3 Add base for generating languages index 2014-04-06 12:52:35 -07:00
Samy Pessé d19906d5b2 Add base for multi-languages book 2014-04-06 12:11:43 -07:00
Samy Pessé fb26b634ce Fix page format when chapters are not yet written 2014-04-06 01:59:53 -07:00
Samy Pessé a34dc458c9 Fix footer link when no chapters is ready yet 2014-04-06 00:36:44 -07:00
Aaron O'Mullan eeb715d5d6 Prevent renderer from mutating sections 2014-04-06 00:21:55 -07:00
Samy Pessé a8e026a642 Add information about output format 2014-04-05 23:30:48 -07:00
Samy Pessé cc096d2253 Rename Index.js to index.js 2014-04-06 05:25:22 +02:00
Samy Pessé a4c1cf9334 Refactor bin/gitbook.js 2014-04-05 20:22:27 -07:00
Samy Pessé 9ffec175a5 Add command gitbook pdf to generate PDF 2014-04-05 20:22:27 -07:00
Aaron O'Mullan 465188cae6 Remove useless console.log in test/summary.js 2014-04-05 20:09:50 -07:00
Aaron O'Mullan 4547ec73fd Merge pull request #31 from GitbookIO/feature/lang
Feature/lang
2014-04-05 20:06:38 -07:00
Aaron O'Mullan cae6267665 Adapt test to new code language normalization 2014-04-05 20:04:54 -07:00
Aaron O'Mullan 58bee15f37 Add basic code language normalization 2014-04-05 20:04:24 -07:00
Samy Pessé 761cfb4a2c Add pdf generator using gitbook-pdf 2014-04-05 19:52:51 -07:00
Samy Pessé 8e3339ead2 Fix link to introduction 2014-04-05 18:45:56 -07:00
Aaron O'Mullan b330277ed7 Normalize paths on Windows to Unix convention
Convert “\” to “/“ in relative paths in fs.list
2014-04-05 18:44:53 -07:00
Samy Pessé 12b5baf433 Start exercise index at 1 on pdf format 2014-04-05 18:44:17 -07:00
Samy Pessé f38d68c355 Improve style of exercises in pdf format 2014-04-05 18:42:21 -07:00
Samy Pessé 865bf40e1a Add exercise solutions at the end of the pdf book 2014-04-05 18:08:25 -07:00
Samy Pessé 562ee2b77f Add README introduction to page/pdf generation 2014-04-05 17:33:09 -07:00
Samy Pessé 8b0f2f6329 Fix rendering of code in exercises 2014-04-05 17:28:46 -07:00
Samy Pessé 44717a3e3e Generate an index.html instead of a README.html 2014-04-05 17:15:22 -07:00
Samy Pessé 6f7e3fda02 Fix conflict in app.js 2014-04-05 13:54:13 -07:00
Samy Pessé 511fc7bd46 Merge branch 'master' of https://github.com/GitbookIO/gitbook
Conflicts:
	theme/assets/app.js
2014-04-05 13:54:00 -07:00
Aaron O'Mullan 4987beec2b Add test for exercise language detection 2014-04-05 13:53:44 -07:00
Aaron O'Mullan e414cbd6d1 Detect exercise language
And ensure that all code blocks use the same language
2014-04-05 13:53:31 -07:00
Samy Pessé 694afeccc5 Add base for exercise for others languages 2014-04-05 13:53:22 -07:00
Aaron O'Mullan 959a2d830a Improve robustness of analytics.isAvailable
Fixes GitbookIO/javascript#18

Safari has issues with loading mixpanel, this adds and extra check so
Safari doesn’t fail on event tracking
2014-04-05 12:47:49 -07:00
52 changed files with 22655 additions and 416 deletions
+6 -2
View File
@@ -28,14 +28,15 @@ module.exports = function (grunt) {
baseUrl: "theme/javascript/",
out: "theme/assets/app.js",
preserveLicenseComments: false,
optimize: "uglify",
optimize: "none", //"uglify",
include: ["requireLib"],
paths: {
"jQuery": 'vendors/jquery',
"lodash": 'vendors/lodash',
"requireLib": 'vendors/require',
"Mousetrap": 'vendors/mousetrap',
"mixpanel": 'vendors/mixpanel'
"mixpanel": 'vendors/mixpanel',
"lunr": path.join(__dirname, "node_modules/lunr/lunr")
},
shim: {
'jQuery': {
@@ -49,6 +50,9 @@ module.exports = function (grunt) {
},
'mixpanel': {
exports: 'mixpanel'
},
'lunr': {
exports: 'lunr'
}
}
}
+37 -6
View File
@@ -33,8 +33,23 @@ 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
-o, --output <directory> Path to output directory, defaults to ./_book
-f, --format <name> Change generation format, defaults to site, availables are: site, page, pdf, json
-i, --intro <intro> Description of the book to generate
-gh, --githubHost <url> The url of the github host (defaults to https://github.com/)
--theme <path> Path to theme directory
```
## Output Formats
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.
* **PDF**: A complete PDF book with exercise solutions at the end of the book. Generate to this format using: ```gitbook pdf ./myrepo```, you need to have [gitbook-pdf](https://github.com/GitbookIO/gitbook-pdf) installed.
* **eBook**: A complete eBook with exercise solutions at the end of the book. Generate to this format using: ```gitbook ebook ./myrepo```, you need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed.
* **Single Page**: The book will be stored in a single printable HTML page, this format is used for conversion to PDF or eBook. Generate to this format using: ```gitbook build ./myrepo -f page```.
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate to this format using: ```gitbook build ./myrepo -f json```.
## Book Format
A book is a GitHub repository containing at least 2 files: `README.md` and `SUMMARY.md`.
@@ -77,19 +92,35 @@ An exercise is defined by 4 simple parts:
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 =
var x =
```
```js
var x = 10;
```
```js
assert(x == 10);
```
---
#### Multi-Languages
GitBook supports building books written in multiple languages. Each language should be a sub-directory following the normal GitBook format, and a file named `LANGS.md` should be present at the root of the repository with the following format:
```
* [English](en/)
* [French](fr/)
* [Español](es/)
```
You can see a complete example with the [Learn Git](https://github.com/GitbookIO/git) book.
#### Ignoring files & folders
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`)
+113
View File
@@ -0,0 +1,113 @@
var path = require('path');
var Q = require('q');
var _ = require('lodash');
var tmp = require('tmp');
var utils = require('./utils');
var generate = require("../lib/generate");
var parse = require("../lib/parse");
var fs = require('../lib/generate/fs');
var generators = require("../lib/generate").generators;
var 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)
.then(function(url) {
// Get ID of repo
return utils.githubID(url);
}, function(err) {
return null;
})
.then(function(repoID) {
var githubID = options.github || repoID;
if(!githubID) {
throw new Error('Needs a githubID (username/repo). Either set repo origin to a github repo or use the -g flag');
}
var parts = githubID.split('/', 2);
var user = parts[0], repo = parts[1];
var title = options.title || utils.titleCase(repo);
return generate.folder(
_.extend(options.options || {}, {
input: dir,
output: outputDir,
title: title,
description: options.intro,
github: githubID,
generator: options.format,
theme: options.theme
})
);
})
.then(function(output) {
console.log("Successfuly built !");
return output;
}, utils.logError);
};
var buildFiles = function(dir, outputFile, options, masterOptions) {
var ext = masterOptions.extension;
outputFile = outputFile || path.resolve(dir, "book."+ext);
Q.nfcall(tmp.dir)
.then(function(tmpDir) {
return buildFunc(
dir,
_.extend(options, {
output: tmpDir,
format: masterOptions.format,
options: masterOptions.options
})
)
.then(function(_options) {
var copyPDF = function(lang) {
var _outputFile = outputFile;
var _tmpDir = tmpDir;
if (lang) {
_outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile);
_tmpDir = path.join(_tmpDir, lang);
}
console.log("Generating in", _outputFile);
return fs.copy(
path.join(_tmpDir, "index."+ext),
_outputFile
);
};
// Multi-langs book
return Q()
.then(function() {
if (_options.langsSummary) {
console.log("Generating for all the languages");
return Q.all(
_.map(_options.langsSummary.list, function(lang) {
return copyPDF(lang.lang);
})
);
} else {
return copyPDF();
}
})
.then(function() {
return fs.remove(tmpDir);
})
.fail(utils.logError);
});
})
};
module.exports = {
folder: buildFunc,
files: buildFiles
};
+52 -77
View File
@@ -1,102 +1,48 @@
#! /usr/bin/env node
// Requires
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var prog = require('commander');
var fs = require('fs');
var pkg = require('../package.json');
var generate = require("../lib/generate");
var parse = require("../lib/parse");
var generators = require("../lib/generate").generators;
var fs = require('../lib/generate/fs');
var utils = require('./utils');
var logError = function(err) {
console.log(err.message || err);
return Q.reject(err);
};
var build = require('./build');
// General options
prog
.version(pkg.version)
.option('-d, --dir <source_directory>', 'Source directory of book, containing Markdown files');
.version(pkg.version);
var buildCommand = function(command, action) {
return command
.option('-o, --output <directory>', 'Path to output directory, defaults to ./_book')
.option('-f, --format <name>', 'Change generation format, defaults to site, availables are: '+_.keys(generators).join(", "))
.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')
.option('-gh, --githubHost <url>', 'The url of the github host (defaults to https://github.com/')
.option('--theme <path>', 'Path to theme directory')
.action(action);
}
var buildFunc;
prog
buildCommand(prog
.command('build [source_dir]')
.description('Build a gitbook from a directory')
.option('-o, --output <directory>', 'Path to output directory, defaults to ./_book')
.option('-f, --format <name>', 'Change generation format, defaults to site, availables are: '+_.keys(generators).join(", "))
.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')
.option('-gh, --githubHost <url>', 'The url of the github host (defaults to https://github.com/')
.option('--theme <path>', 'Path to theme directory')
.action(buildFunc = function(dir, options) {
dir = dir || process.cwd();
outputDir = options.output || path.join(dir, '_book');
.description('Build a gitbook from a directory'), build.folder);
console.log('Starting build ...');
// Get repo's URL
return utils.gitURL(dir)
.then(function(url) {
// Get ID of repo
return utils.githubID(url);
}, function(err) {
return null;
})
.then(function(repoID) {
var githubID = options.github || repoID;
if(!githubID) {
throw new Error('Needs a githubID (username/repo). Either set repo origin to a github repo or use the -g flag');
}
var parts = githubID.split('/', 2);
var user = parts[0], repo = parts[1];
var title = options.title || utils.titleCase(repo);
return generate.folder(
{
input: dir,
output: outputDir,
title: title,
description: options.intro,
github: githubID,
generator: options.format,
theme: options.theme
}
);
})
.then(function(output) {
console.log("Successfuly built !");
}, logError)
.then(_.constant(outputDir));
});
prog
buildCommand(prog
.command('serve [source_dir]')
.description('Build then serve a gitbook from a directory')
.option('-p, --port <port>', 'Port for server to listen on', 4000)
.option('-o, --output <directory>', 'Path to output directory, defaults to ./_book')
.option('-f, --format <name>', 'Change generation format, defaults to site, availables are: '+_.keys(generators).join(", "))
.option('-t, --title <name>', 'Name of the book to generate, defaults to repo name')
.option('-g, --github <repo_path>', 'ID of github repo like : username/repo')
.option('-gh, --githubHost <url>', 'The url of the github host (defaults to https://github.com/')
.option('--theme <path>', 'Path to theme directory')
.action(function(dir, options) {
buildFunc(dir, options)
.then(function(outputDir) {
.option('-p, --port <port>', 'Port for server to listen on', 4000),
function(dir, options) {
build.folder(dir, options)
.then(function(_options) {
console.log();
console.log('Starting server ...');
return utils.serveDir(outputDir, options.port)
.fail(logError);
return utils.serveDir(_options.output, options.port)
.fail(utils.logError);
})
.then(function() {
console.log('Serving book on http://localhost:'+options.port);
@@ -105,7 +51,36 @@ prog
});
});
buildCommand(prog
.command('pdf [source_dir] [output_file]')
.description('Build a gitbook as a PDF')
.option('-pf, --paperformat <format>', 'PDF paper format (default is A4): "5in*7.5in", "10cm*20cm", "A4", "Letter"'),
function(dir, output, options) {
build.files(dir, output, options, {
extension: "pdf",
format: "pdf",
options: {
paperformat: options.paperformat
}
});
});
buildCommand(prog
.command('ebook [source_dir] [output_file]')
.description('Build a gitbook as a eBook')
.option('-c, --cover <path>', 'Cover image, default is cover.png if exists'),
function(dir, output, options) {
var ext = output ? path.extname(output) : "epub";
build.files(dir, output, options, {
extension: ext,
format: "ebook",
options: {
extension: ext,
cover: options.cover
}
});
});
// Parse and fallback to help if no args
if(_.isEmpty(prog.parse(process.argv).args) && process.argv.length === 2) {
+6
View File
@@ -82,6 +82,11 @@ function serveDir(dir, port) {
return d.promise;
}
function logError(err) {
console.log(err.stack || err.message || err);
return Q.reject(err);
};
// Exports
module.exports = {
@@ -89,4 +94,5 @@ module.exports = {
githubID: githubID,
titleCase: titleCase,
serveDir: serveDir,
logError: logError
};
+70
View File
@@ -0,0 +1,70 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var exec = require('child_process').exec;
var fs = require("fs");
var parse = require("../../parse");
var BaseGenerator = require("../page");
/*
* This generator inherits from the single page generator
* and convert the page output to ebook
*/
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Options for eBook generation
this.options = _.defaults(this.options, {
extension: "epub"
});
if (!this.options.cover && fs.existsSync(path.join(this.options.input, "cover.png"))) {
this.options.cover = path.join(this.options.input, "cover.png")
}
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.finish = function() {
var that = this;
return BaseGenerator.prototype.finish.apply(this)
.then(function() {
var d = Q.defer();
var _options = {
"--cover": that.options.cover
};
var command = [
"ebook-convert",
path.join(that.options.output, "index.html"),
path.join(that.options.output, "index."+that.options.extension),
_.chain(_options)
.map(function(value, key) {
if (value == null) return null;
return key+"="+value;
})
.compact()
.value()
.join(" ")
].join(" ");
exec(command, function (error, stdout, stderr) {
if (error) {
if (error.code == 127) {
error.message = "Need to install ebook-convert from Calibre";
} else {
error.message = error.message + " "+stdout;
}
return d.reject(error);
}
d.resolve();
});
return d.promise;
});
};
module.exports = Generator;
+11 -3
View File
@@ -11,7 +11,7 @@ var getFiles = function(path) {
var ig = Ignore({
path: path,
ignoreFiles: ['.ignore', '.gitignore']
ignoreFiles: ['.ignore', '.gitignore', '.bookignore']
});
// Add extra rules to ignore common folders
@@ -27,13 +27,21 @@ var getFiles = function(path) {
});
ig.on('end', function() {
d.resolve(files);
// Normalize paths on Windows
if(process.platform === 'win32') {
return d.resolve(files.map(function(file) {
return file.replace(/\\/g, '/');
}));
}
// Simply return paths otherwise
return d.resolve(files);
});
ig.on('error', d.reject);
return d.promise;
}
};
module.exports = {
list: getFiles,
+4
View File
@@ -23,6 +23,10 @@ BaseGenerator.prototype.transferFolder = function(input) {
);
};
BaseGenerator.prototype.langsIndex = function(langs) {
return Q.reject(new Error("Langs index is not supported in this generator"));
};
BaseGenerator.prototype.finish = function() {
return Q.reject(new Error("Could not finish generation"));
};
+73 -40
View File
@@ -2,6 +2,7 @@ var Q = require("q");
var _ = require("lodash");
var path = require("path");
var swig = require('swig');
var fs = require("./fs");
var parse = require("../parse");
@@ -9,6 +10,8 @@ var parse = require("../parse");
var generators = {
"site": require("./site"),
"page": require("./page"),
"pdf": require("./pdf"),
"ebook": require("./ebook"),
"json": require("./json")
};
@@ -53,27 +56,10 @@ var generate = function(options) {
// List all files in the repository
.then(function() {
return fs.list(options.input);
})
// Check repository is valid
.then(function(_files) {
files = _files;
if (!_.contains(files, "SUMMARY.md") || !_.contains(files, "README.md")) {
return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
}
})
// Get summary
.then(function() {
return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
.then(function(_summary) {
options.summary = parse.summary(_summary);
// Parse navigation
options.navigation = parse.navigation(options.summary);
});
return fs.list(options.input)
.then(function(_files) {
files = _files;
})
})
// Create the generator
@@ -81,31 +67,78 @@ var generate = function(options) {
generator = new generators[options.generator](options);
})
// Copy file and replace markdown file
// Detect multi-languages book
.then(function() {
return Q.all(
_.chain(files)
.map(function(file) {
if (!file) return;
if (_.contains(files, "LANGS.md")) {
// Multi-languages book
return fs.readFile(path.join(options.input, "LANGS.md"), "utf-8")
if (file[file.length -1] == "/") {
return Q(generator.transferFolder(file));
} else if (path.extname(file) == ".md" && options.navigation[file] != null) {
return fs.readFile(path.join(options.input, file), "utf-8")
.then(function(content) {
return Q(generator.convertFile(content, file));
});
} else {
return Q(generator.transferFile(file));
}
// Generate sub-books
.then(function(_langsSummary) {
options.langsSummary = parse.langs(_langsSummary);
// Generated a book for each valid entry
return Q.all(
_.map(options.langsSummary.list, function(entry) {
return generate(_.extend({}, options, {
input: path.join(options.input, entry.path),
output: path.join(options.output, entry.path)
}));
})
);
})
.value()
);
// Generate languages index
.then(function() {
return generator.langsIndex(options.langsSummary);
});
} else if (!_.contains(files, "SUMMARY.md") || !_.contains(files, "README.md")) {
// Invalid book
return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
} else {
// Generate the book
return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
// Get summary
.then(function(_summary) {
options.summary = parse.summary(_summary);
// Parse navigation
options.navigation = parse.navigation(options.summary);
})
// Copy file and replace markdown file
.then(function() {
return Q.all(
_.chain(files)
.map(function(file) {
if (!file) return;
if (file[file.length -1] == "/") {
return Q(generator.transferFolder(file));
} else if (path.extname(file) == ".md" && options.navigation[file] != null) {
return fs.readFile(path.join(options.input, file), "utf-8")
.then(function(content) {
return Q(generator.convertFile(content, file));
});
} else {
return Q(generator.transferFile(file));
}
})
.value()
);
})
// Finish gneration
.then(function() {
return generator.finish();
});
}
})
// Finish gneration
// Return all options
.then(function() {
return generator.finish();
return options;
});
};
+5 -30
View File
@@ -7,7 +7,7 @@ var hljs = require('highlight.js');
var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../generator");
var BaseGenerator = require("../site");
// Swig filter: highlight coloration
swig.setFilter('code', function(code, lang) {
@@ -33,14 +33,6 @@ var Generator = function() {
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.transferFile = function(input) {
// ignore
};
Generator.prototype.transferFolder = function(input) {
// ignore
};
Generator.prototype.convertFile = function(content, input) {
var that = this;
var json = {
@@ -52,7 +44,8 @@ Generator.prototype.convertFile = function(content, input) {
.then(function() {
return parse.page(content, {
repo: that.options.githubId,
dir: path.dirname(input) || '/'
dir: path.dirname(input) || '/',
outdir: './',
});
})
.then(function(sections) {
@@ -71,30 +64,12 @@ Generator.prototype.finish = function() {
return Q()
// Generate html
.then(function(pages) {
return that.template({
title: that.options.title,
description: that.options.description,
githubAuthor: that.options.github.split("/")[0],
githubId: that.options.github,
githubHost: that.options.githubHost,
summary: that.options.summary,
allNavigation: that.options.navigation,
return that._writeTemplate(that.template, {
pages: that.pages,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
});
})
// Write html to index.html
.then(function(html) {
return fs.writeFile(
output,
html
);
}, output);
})
// Copy assets
+56
View File
@@ -0,0 +1,56 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var exec = require('child_process').exec;
var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../page");
/*
* This generator inherits from the single page generator
* and convert the page output to pdf using gitbook-pdf
*/
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Options for PDF generation
this.options = _.defaults(this.options, {
paperformat: "A4"
});
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.finish = function() {
var that = this;
return BaseGenerator.prototype.finish.apply(this)
.then(function() {
var d = Q.defer();
var command = [
"gitbook-pdf",
"generate",
path.join(that.options.output, "index.html"),
path.join(that.options.output, "index.pdf"),
"--format="+that.options.paperformat
].join(" ");
exec(command, function (error, stdout, stderr) {
if (error) {
if (error.code == 127) {
error.message = "Need to install gitbook-pdf using: npm install gitbook-pdf -g";
} else {
error.message = error.message + " "+stdout;
}
return d.reject(error);
}
d.resolve();
});
return d.promise;
});
};
module.exports = Generator;
+102 -38
View File
@@ -8,6 +8,8 @@ var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../generator");
var indexer = require('./search_indexer');
// Swig filter for returning the count of lines in a code section
swig.setFilter('lines', function(content) {
return content.split('\n').length;
@@ -15,38 +17,35 @@ swig.setFilter('lines', function(content) {
// Swig filter for returning a link to the associated html file of a markdown file
swig.setFilter('mdLink', function(link) {
return link.replace(".md", ".html");
var link = link.replace(".md", ".html");
if (link == "README.html") link = "index.html";
return link;
});
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Attach methods to instance
_.bindAll(this);
this.revision = Date.now();
this.indexer = indexer();
// Load base template
this.template = swig.compileFile(path.resolve(this.options.theme, 'templates/site.html'));
this.langsTemplate = swig.compileFile(path.resolve(this.options.theme, 'templates/langs.html'));
};
util.inherits(Generator, BaseGenerator);
// Convert a markdown file to html
Generator.prototype.convertFile = function(content, _input) {
// Generate a template
Generator.prototype._writeTemplate = function(tpl, options, output) {
var that = this;
var progress = parse.progress(this.options.navigation, _input);
_output = _input.replace(".md", ".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) || ".";
return Q()
.then(function() {
return parse.page(content, {
repo: that.options.githubId,
dir: path.dirname(input) || '/'
});
})
.then(function(sections) {
return that.template({
return tpl(_.extend({
revision: that.revision,
title: that.options.title,
description: that.options.description,
@@ -55,15 +54,8 @@ Generator.prototype.convertFile = function(content, _input) {
githubHost: that.options.githubHost,
summary: that.options.summary,
allNavigation: that.options.navigation,
progress: progress,
_input: _input,
content: sections,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
});
allNavigation: that.options.navigation
}, options));
})
.then(function(html) {
return fs.writeFile(
@@ -73,20 +65,92 @@ Generator.prototype.convertFile = function(content, _input) {
});
};
// Symlink index.html and copy assets
Generator.prototype.finish = function() {
var that = this;
Generator.prototype.indexPage = function(lexed, pagePath) {
this.indexer.add(lexed, pagePath);
return Q();
};
return fs.copy(
path.join(that.options.output, 'README.html'),
path.join(that.options.output, 'index.html')
)
// Convert a markdown file to html
Generator.prototype.convertFile = function(content, _input) {
var that = this;
var progress = parse.progress(this.options.navigation, _input);
_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) || ".";
return Q()
.then(function() {
return fs.copy(
path.join(that.options.theme, "assets"),
path.join(that.options.output, "gitbook")
);
// Lex page
return parse.lex(content);
})
.then(function(lexed) {
// Index page in search
return that.indexPage(lexed, _output)
.then(_.constant(lexed));
})
.then(function(lexed) {
// Get HTML generated sections
return parse.page(lexed, {
repo: that.options.githubId,
dir: path.dirname(_input) || '/',
outdir: path.dirname(_input) || '/',
});
})
.then(function(sections) {
return that._writeTemplate(that.template, {
progress: progress,
_input: _input,
content: sections,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
}, output);
});
};
// Generate languages index
Generator.prototype.langsIndex = function(langs) {
var that = this;
var basePath = ".";
return this._writeTemplate(this.langsTemplate, {
langs: langs.list,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
}, path.join(this.options.output, "index.html"))
.then(function() {
// Copy assets
return that.copyAssets();
});
};
// Copy assets
Generator.prototype.copyAssets = function() {
var that = this;
return fs.copy(
path.join(that.options.theme, "assets"),
path.join(that.options.output, "gitbook")
);
};
// Dump search index to disk
Generator.prototype.writeSearchIndex = function() {
return fs.writeFile(
path.join(this.options.output, 'search_index.json'),
this.indexer.dump()
);
};
Generator.prototype.finish = function() {
return this.copyAssets()
.then(this.writeSearchIndex);
};
module.exports = Generator;
+71
View File
@@ -0,0 +1,71 @@
var Q = require("q");
var _ = require("lodash");
var lunr = require('lunr');
var marked = require('marked');
var textRenderer = require('marked-text-renderer');
function Indexer() {
if(!(this instanceof Indexer)) {
return new Indexer();
}
_.bindAll(this);
// Setup lunr index
this.idx = lunr(function () {
this.ref('url');
this.field('title', { boost: 10 });
this.field('body');
});
this.renderer = textRenderer();
}
Indexer.prototype.text = function(nodes) {
// Copy section
var section = _.toArray(nodes);
// marked's Render expects this, we don't use it yet
section.links = {};
var options = _.extend({}, marked.defaults, {
renderer: this.renderer
});
return marked.parser(section, options);
};
Indexer.prototype.addSection = function(path, section) {
var url = [path, section.id].join('#');
var title = this.text(
_.filter(section, {'type': 'heading'})
);
var body = this.text(
_.omit(section, {'type': 'heading'})
);
// Add to lunr index
this.idx.add({
url: url,
title: title,
body: body,
});
};
Indexer.prototype.add = function(lexedPage, url) {
var sections = lexedPage;
_.map(sections, _.partial(this.addSection, url));
};
Indexer.prototype.dump = function() {
return JSON.stringify(this.idx);
};
// Exports
module.exports = Indexer;
+2
View File
@@ -1,6 +1,8 @@
module.exports = {
summary: require('./summary'),
langs: require('./langs'),
page: require('./page'),
lex: require('./lex'),
progress: require('./progress'),
navigation: require('./navigation'),
};
+23
View File
@@ -0,0 +1,23 @@
var _ = require("lodash")
var parseSummary = require("./summary");
var parseLangs = function(content) {
var summary = parseSummary(content);
return {
list: _.chain(summary.chapters)
.filter(function(entry) {
return entry.path != null;
})
.map(function(entry) {
return {
title: entry.title,
path: entry.path,
lang: entry.path.replace("/", "")
};
})
.value()
}
};
module.exports = parseLangs;
+74
View File
@@ -0,0 +1,74 @@
var _ = require('lodash');
var marked = require('marked');
// Split a page up into sections (lesson, exercises, ...)
function splitSections(nodes) {
var section = [];
return _.reduce(nodes, function(sections, el) {
if(el.type === 'hr') {
sections.push(section);
section = [];
} else {
section.push(el);
}
return sections;
}, []).concat([section]); // Add remaining nodes
}
// What is the type of this section
function sectionType(nodes, idx) {
var codeNodes = _.filter(nodes, {
type: 'code'
}).length;
if(codeNodes === 3 && (idx % 2) == 1) {
return 'exercise';
}
return 'normal';
}
// Generate a uniqueId to identify this section in our code
function sectionId(section, idx) {
return _.uniqueId('gitbook_');
}
function lexPage(src) {
// Lex file
var nodes = marked.lexer(src);
return _.chain(splitSections(nodes))
.map(function(section, idx) {
// Detect section type
section.type = sectionType(section, idx);
return section;
})
.map(function(section, idx) {
// Give each section an ID
section.id = sectionId(section, idx);
return section;
})
.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;
}, [])
.value();
}
// Exports
module.exports = lexPage;
+24 -61
View File
@@ -2,8 +2,12 @@ var _ = require('lodash');
var marked = require('marked');
var hljs = require('highlight.js');
var lex = require('./lex');
var renderer = require('./renderer');
var lnormalize = require('../utils/lang').normalize;
// Synchronous highlighting with highlight.js
marked.setOptions({
highlight: function (code, lang) {
@@ -16,37 +20,11 @@ marked.setOptions({
});
// Split a page up into sections (lesson, exercises, ...)
function splitSections(nodes) {
var section = [];
return _.reduce(nodes, function(sections, el) {
if(el.type === 'hr') {
sections.push(section);
section = [];
} else {
section.push(el);
}
return sections;
}, []).concat([section]); // Add remaining nodes
}
// What is the type of this section
function sectionType(nodes, idx) {
var codeNodes = _.filter(nodes, {
type: 'code'
}).length;
if(codeNodes === 3 && (idx % 2) == 1) {
return 'exercise';
}
return 'normal';
}
// Render a section using our custom renderer
function render(section, _options) {
// Copy section
section = _.toArray(section);
// marked's Render expects this, we don't use it yet
section.links = {};
@@ -61,35 +39,9 @@ function render(section, _options) {
function parsePage(src, options) {
options = options || {};
// Lex file
var nodes = marked.lexer(src);
return _.chain(splitSections(nodes))
.map(function(section, idx) {
// Detect section type
section.type = sectionType(section, idx);
return section;
})
.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;
}, [])
// Lex if not already lexed
return (_.isArray(src) ? src : lex(src))
.map(function(section) {
// Generate a uniqueId to identify this section in our code
var id = _.uniqueId('gitbook_');
// Transform given type
if(section.type === 'exercise') {
var nonCodeNodes = _.reject(section, {
@@ -100,10 +52,22 @@ function parsePage(src, options) {
'type': 'code'
});
// Languages in code blocks
var langs = _.pluck(codeNodes, 'lang').map(lnormalize);
// Check that they are all the same
var validLangs = _.all(_.map(langs, function(lang) {
return lang && lang === langs[0];
}));
// Main language
var lang = validLangs ? langs[0] : null;
return {
id: id,
id: section.id,
type: section.type,
content: render(nonCodeNodes),
lang: lang,
code: {
base: codeNodes[0].text,
solution: codeNodes[1].text,
@@ -114,12 +78,11 @@ function parsePage(src, options) {
// Render normal pages
return {
id: id,
id: section.id,
type: section.type,
content: render(section, options)
};
})
.value();
});
}
// Exports
+23 -1
View File
@@ -45,7 +45,7 @@ GitBookRenderer.prototype.link = function(href, title, text) {
var o = this._extra_options;
// Relative link, rewrite it to point to github repo
if(parsed.path[0] != '/' && o && o.repo && o.dir) {
if(!parsed.protocol && parsed.path[0] != '/' && o && o.repo && o.dir) {
href = 'https://github.com/' + o.repo + '/blob' + path.normalize(path.join(
'/',
o.dir,
@@ -68,5 +68,27 @@ GitBookRenderer.prototype.link = function(href, title, text) {
return out;
};
GitBookRenderer.prototype.image = function(href, title, text) {
// Our "fixed" href
var _href = href;
// Parsed version of the url
var parsed = url.parse(href);
// Options
var o = this._extra_options;
// Relative image, rewrite it depending output
if(!parsed.protocol && parsed.path[0] != '/' && o && o.dir && o.outdir) {
_href = path.relative(o.outdir, path.normalize(path.join(
o.dir,
href
)));
}
return GitBookRenderer.super_.prototype.image.call(this, _href, title, text);
};
// Exports
module.exports = GitBookRenderer;
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
lang: require('./lang'),
};
+16
View File
@@ -0,0 +1,16 @@
var MAP = {
'py': 'python',
'js': 'javascript',
'rb': 'ruby',
};
function normalize(lang) {
var lower = lang.toLowerCase();
return MAP[lower] || lower;
}
// Exports
module.exports = {
normalize: normalize,
MAP: MAP
};
+5 -2
View File
@@ -1,18 +1,21 @@
{
"name": "gitbook",
"version": "0.1.1",
"version": "0.1.2",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
"dependencies": {
"q": "1.0.1",
"lodash": "2.4.1",
"marked": "0.3.2",
"marked-text-renderer": "0.0.1",
"lunr": "0.5.2",
"swig": "1.3.2",
"send": "0.2.0",
"fstream-ignore": "0.0.7",
"commander": "2.2.0",
"fs-extra": "0.8.1",
"highlight.js": "8.0.0"
"highlight.js": "8.0.0",
"tmp": "0.0.23"
},
"devDependencies": {
"mocha": "1.18.2",
+4
View File
@@ -45,6 +45,10 @@ describe('Page parsing', function() {
// HRs inserted correctly
assert.equal(HR_LEXED[0].content.match(/<hr>/g).length, 2);
});
it('should detect an exercise\'s language', function() {
assert.equal(LEXED[1].lang, 'python');
});
});
-1
View File
@@ -10,7 +10,6 @@ var LEXED = summary(CONTENT);
describe('Summary parsing', function () {
console.log(LEXED);
it('should detect chapters', function() {
assert.equal(LEXED.chapters.length, 5);
+21406 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
h1,h2{page-break-after:avoid;page-break-before:auto}pre,blockquote{border:1px solid #999;page-break-inside:avoid}img{max-width:100%!important;page-break-inside:avoid}section{page-break-after:always}section#cover{padding:3cm 0;text-align:center}section#cover h1{font-size:1.5cm}section#summary{text-align:center}section#summary ul{font-size:.5cm;line-height:1.8em;padding:0;margin:0;list-style:none}section#summary>ul>li{margin-bottom:1cm}section#summary>ul>li>a{font-size:.6cm}section article{margin:1.5cm}section article.new-chapter{page-break-after:always;font-size:.6cm;text-align:center;padding:3cm 0;border-top:1px solid #ccc}@media print{section article.new-chapter{border:0}}section article .exercise{border:3px solid #ddd}
h1,h2{page-break-after:avoid;page-break-before:auto}pre,blockquote{border:1px solid #999;page-break-inside:avoid}img{max-width:100%!important;page-break-inside:avoid}section{page-break-after:always}section#cover{padding:3cm 0;text-align:center}section#cover h1{font-size:1.5cm}section#summary{text-align:center}section#summary ul{font-size:.5cm;line-height:1.8em;padding:0;margin:0;list-style:none}section#summary>ul>li{margin-bottom:1cm}section#summary>ul>li>a{font-size:.6cm}section article{margin:1.5cm}section article.new-chapter{page-break-after:always;font-size:.6cm;text-align:center;padding:3cm 0;border-top:1px solid #ccc}@media print{section article.new-chapter{border:0}}section article .exercise{margin:1cm 0;padding:.4cm;page-break-inside:avoid;border:3px solid #ddd}section article .exercise .exercise-header{margin-bottom:.4cm;padding-bottom:.2cm;border-bottom:1px solid #ddd}
File diff suppressed because one or more lines are too long
+6 -3
View File
@@ -8,10 +8,10 @@ require([
"core/keyboard",
"core/exercise",
"core/progress",
"core/sidebar"
], function($, storage, analytic, sharing, _state, keyboard, exercise, progress, sidebar){
"core/sidebar",
"core/search"
], function($, storage, analytic, sharing, state, keyboard, exercise, progress, sidebar, search){
$(document).ready(function() {
var state = _state();
var $book = state.$book;
// Initialize storage
@@ -20,6 +20,9 @@ require([
// Init sidebar
sidebar.init();
// Load search
search.init();
// Init keyboard
keyboard.init();
+2 -2
View File
@@ -20,7 +20,7 @@ define([
analytic.track("exercise.submit");
execute(editor.getValue(), codeValidation, function(err, result) {
execute("javascript", editor.getValue(), codeValidation, function(err, result) {
$exercise.toggleClass("return-error", err != null);
$exercise.toggleClass("return-success", err == null);
if (err) $exercise.find(".alert-danger").text(err.message || err);
@@ -38,7 +38,7 @@ define([
// Prepare all exercise
var init = function() {
state().$book.find("section.exercise").each(function() {
state.$book.find("section.exercise").each(function() {
prepareExercise($(this));
});
};
+11 -3
View File
@@ -2,8 +2,9 @@ define([
"jQuery",
"Mousetrap",
"core/navigation",
"core/sidebar"
], function($, Mousetrap, navigation, sidebar){
"core/sidebar",
"core/search"
], function($, Mousetrap, navigation, sidebar, search){
// Bind keyboard shortcuts
var init = function() {
// Next
@@ -23,9 +24,16 @@ define([
sidebar.toggle();
return false;
});
// Toggle Search
Mousetrap.bind(['f'], function(e) {
search.toggle();
return false;
});
};
return {
init: init
init: init,
search: search
};
});
+1 -1
View File
@@ -6,7 +6,7 @@ define([
], function(_, $, storage, state) {
// Get current level
var getCurrentLevel = function() {
return state().level;
return state.level;
};
// Return all levels
+97
View File
@@ -0,0 +1,97 @@
define([
"jQuery",
"lodash",
"lunr",
"utils/storage",
"core/state",
"core/sidebar"
], function($, _, lunr, storage, state, sidebar) {
var index = null;
var $searchBar = state.$book.find(".book-search");
var $searchInput = $searchBar.find("input");
// Use a specific idnex
var useIndex = function(data) {
index = lunr.Index.load(data);
};
// Load complete index
var loadIndex = function() {
$.getJSON(state.basePath+"/search_index.json")
.then(useIndex);
};
// Search for a term
var search = function(q) {
if (!index) return;
var results = _.chain(index.search(q))
.map(function(result) {
var parts = result.ref.split("#")
return {
path: parts[0],
hash: parts[1]
}
})
.value();
return results;
};
// Toggle search bar
var toggleSearch = function(_state) {
if (state != null && isSearchOpen() == _state) return;
state.$book.toggleClass("with-search", _state);
// If search bar is open: focus input
if (isSearchOpen()) {
sidebar.toggle(true);
$searchInput.focus();
} else {
$searchInput.blur();
$searchInput.val("");
sidebar.filter(null);
}
};
// Return true if search bar is open
var isSearchOpen = function() {
return state.$book.hasClass("with-search");
};
var init = function() {
loadIndex();
// Toggle search
state.$book.find(".book-header .toggle-search").click(function(e) {
e.preventDefault();
toggleSearch();
});
$searchInput.keyup(function(e) {
var key = (e.keyCode ? e.keyCode : e.which);
var q = $(this).val();
if (key == 27) {
e.preventDefault();
toggleSearch(false);
return;
}
if (q.length == 0) {
sidebar.filter(null);
} else {
var results = search(q);
sidebar.filter(
_.pluck(results, "path")
);
}
});
};
return {
init: init,
search: search,
toggle: toggleSearch
};
});
+22 -10
View File
@@ -1,32 +1,31 @@
define([
"lodash",
"utils/storage",
"utils/platform",
"core/state"
], function(storage, platform, state) {
], function(_, storage, platform, state) {
var $summary = state.$book.find(".book-summary");
// Toggle sidebar with or withour animation
var toggleSidebar = function(_state, animation) {
if (-state != null && isOpen() == _state) return;
if (state != null && isOpen() == _state) return;
if (animation == null) animation = true;
var $book = state().$book;
$book.toggleClass("without-animation", !animation);
$book.toggleClass("with-summary", _state);
state.$book.toggleClass("without-animation", !animation);
state.$book.toggleClass("with-summary", _state);
storage.set("sidebar", isOpen());
};
// Return true if sidebar is open
var isOpen = function() {
return state().$book.hasClass("with-summary");
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) {
state.$book.find(".book-header .toggle-summary").click(function(e) {
e.preventDefault();
toggleSidebar();
});
@@ -37,8 +36,21 @@ define([
}
};
// Filter summary with a list of path
var filterSummary = function(paths) {
$summary.find("li").each(function() {
var path = $(this).data("path");
var st = paths == null || _.contains(paths, path);
$(this).toggle(st);
if (st) $(this).parents("li").show();
});
};
return {
$el: $summary,
init: init,
toggle: toggleSidebar
toggle: toggleSidebar,
filter: filterSummary
}
});
+7 -7
View File
@@ -1,14 +1,14 @@
define([
"jQuery"
], function() {
return function() {
var $book = $(".book");
var $book = $(".book");
return {
'$book': $book,
return {
'$book': $book,
'githubId': $book.data("github"),
'level': $book.data("level")
};
'githubId': $book.data("github"),
'level': $book.data("level"),
'basePath': $book.data("basepath"),
'revision': $book.data("revision")
};
});
+7
View File
@@ -0,0 +1,7 @@
define(function() {
return {
id: "javascript",
assertCode: "function assert(condition, message) { \nif (!condition) { \n throw message || \"Assertion failed\"; \n } \n }\n",
REPL: JSREPL
};
});
+4 -1
View File
@@ -9,7 +9,10 @@ define([
});
var isAvailable = function() {
return (typeof mixpanel !== "undefined");
return (
typeof mixpanel !== "undefined" &&
typeof mixpanel.track === "function"
);
};
var track = function(e, data) {
+18 -11
View File
@@ -1,5 +1,12 @@
define(function(){
var evalJS = function(code, callback) {
define([
"execute/javascript"
], function(javascript) {
var LANGUAGES = {
"javascript": javascript
};
var evalJS = function(lang, code, callback) {
var ready = false;
var finished = false;
@@ -11,7 +18,7 @@ define(function(){
return callback.apply(null, arguments);
};
var jsrepl;
var repl;
// Handles all our events
var eventHandler = function(data, eventType) {
@@ -42,14 +49,14 @@ define(function(){
// We're good to get results and stuff back now
ready = true;
// Eval our code now that the runtime is ready
jsrepl.eval(code);
repl.eval(code);
break;
default:
console.log('Unhandled event =', eventType, 'data =', data);
}
};
jsrepl = new JSREPL({
repl = new lang.REPL({
input: eventHandler,
output: eventHandler,
result: eventHandler,
@@ -61,16 +68,16 @@ define(function(){
}
});
jsrepl.loadLanguage('javascript', eventHandler);
repl.loadLanguage(lang.id, eventHandler);
};
var execute = function(lang, solution, validation, callback) {
// Check language is supported
if (!LANGUAGES[lang]) return callback(new Error("Language '"+lang+"' not available for execution"));
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
var code = [solution, ass, validation].join(";\n");
evalJS(code, function(err, res) {
var code = [solution, LANGUAGES[lang].assertCode, validation].join(";\n");
evalJS(LANGUAGES[lang], code, function(err, res) {
if(err) return callback(err);
if (res.type == "error") callback(new Error(res.value));
+19 -2
View File
@@ -5,7 +5,15 @@
right: 0px;
left: 0px;
bottom: 0px;
overflow-y: auto;
.body-inner {
position: absolute;
top: 0px;
right: 0px;
left: 0px;
bottom: 0px;
overflow-y: auto;
}
.transition(left 0.5s ease);
@@ -14,7 +22,6 @@
.page-wrapper {
position: relative;
min-height: 100%;
padding-bottom: 100px;
.page-inner {
max-width: 800px;
@@ -39,6 +46,16 @@
}
}
}
@media (max-width: @mobileMaxWidth) {
overflow-y: auto;
.body-inner {
position: static;
padding-bottom: 20px;
min-height: calc(~"100% - 57px")
}
}
}
&.with-summary {
-42
View File
@@ -1,42 +0,0 @@
.book {
.page-footer {
position: absolute;
bottom: 0px;
width: 100%;
height: 80px;
padding: 10px;
overflow: hidden;
color: #fff;
.navigation-link {
display: inline-block;
text-align: center;
width: 100%;
height: 100%;
font-size: 24px;
line-height: 60px;
border-radius: 2px;
background: @brand-success;
color: inherit;
text-transform: uppercase;
&:hover {
background: darken(@btn-success-bg, 5%);
text-decoration: none;
}
&.coming-soon, &.coming-soon:hover {
background: #f0ad4e;
}
&.finished, &.finished:hover {
background: darken(@btn-success-bg, 5%);
}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
.book-langs-index {
width: 100%;
height: 100%;
background: #edece4;
padding: 40px 0px;
margin: 0px;
overflow: auto;
.inner {
max-width: 350px;
width: 100%;
margin: 0px auto;
padding: 15px;
background: #fff;
border-radius: 3px;
h1 {
margin: 0px;
text-align: center;
}
.languages {
list-style: none;
padding: 0px;
margin: 0px;
margin-top: 20px;
border-top: 1px solid #eee;
li {
text-align: center;
padding: 10px 5px;
font-size: 16px;
border-bottom: 1px solid #eee;
a {
}
}
}
}
}
+8 -2
View File
@@ -156,7 +156,10 @@
margin-bottom: 0; }
table {
padding: 0; }
width: 100%;
padding: 0;
}
table tr {
border-top: 1px solid #cccccc;
background-color: white;
@@ -181,7 +184,10 @@
margin-bottom: 0; }
img {
max-width: 100%; }
max-width: 100%;
display: block;
margin: 0px auto;
}
span.frame {
display: block;
+45
View File
@@ -0,0 +1,45 @@
.book .book-body {
.navigation {
position: absolute;
top: 0px;
bottom: 0px;
margin: 0;
max-width: 150px;
min-width: 90px;
display: flex;
justify-content:center;
align-content:center;
flex-direction:column;
font-size: 40px;
color: rgba(0,0,0, 0.5);
text-align: center;
.transition(all 350ms ease);
&:hover {
background-color: rgba(0,0,0, 0.06);
text-decoration: none;
}
&.navigation-next {
right: 0px;
}
&.navigation-prev {
left: 0px;
}
}
@media (max-width: @mobileMaxWidth) {
.navigation {
position: static;
top: auto;
max-width: 50%;
width: 50%;
display: inline-block;
float: left;
}
}
}
+1
View File
@@ -23,6 +23,7 @@
position: relative;
background: #fff;
margin-bottom: 20px;
z-index: 10;
.bar {
height: 8px;
+46 -2
View File
@@ -1,4 +1,6 @@
.book {
@searchHeight: 41px;
.book-summary {
@width: 250px;
@@ -8,19 +10,49 @@
bottom: 0px;
z-index: 1;
overflow-y: auto;
width: @width;
background: #2e3133;
color: #e2edf2;
.transition(all 0.5s ease);
.book-search {
padding: 6px;
padding-top: 0px;
background: #242628;
border-bottom: 1px solid #333;
position: absolute;
top: -@searchHeight;
left: 0px;
right: 0px;
.transition(top 0.5s ease);
input, input:focus {
width: 100%;
border: none;
.box-shadow(none);
}
}
ul.summary {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
overflow-y: auto;
list-style: none;
margin: 0px;
padding: 0px;
.transition(top 0.5s ease);
li {
list-style: none;
@@ -96,4 +128,16 @@
.transition(none) !important;
}
}
&.with-search {
.book-summary {
.book-search {
top: 0px;
}
ul.summary {
top: @searchHeight;
}
}
}
}
+2 -1
View File
@@ -7,13 +7,14 @@
@import "highlight.less";
@import "book/languages.less";
@import "book/header.less";
@import "book/summary.less";
@import "book/body.less";
@import "book/footer.less";
@import "book/exercise.less";
@import "book/markdown.less";
@import "book/progress.less";
@import "book/navigation.less";
* {
-webkit-overflow-scrolling: touch;
+10 -1
View File
@@ -65,8 +65,17 @@ section {
}
.exercise {
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;
}
}
}
}
+2 -11
View File
@@ -1,11 +1,9 @@
/* Base static */
@staticPath: "./";
/* Colors */
@codeboxMainColor: #F16956; /* red */
@codeboxMainColorDarker: darken(@codeboxMainColor, 15%);
// Sizes
@mobileMaxWidth: 1240px;
@codeboxMainColor2: #353D48;
// Basics of a navbar
@navbar-height: 50px;
@@ -22,13 +20,6 @@
@navbar-default-brand-hover-color: @navbar-default-link-color;
@navbar-default-brand-hover-bg: transparent;
// Navbar toggle
@navbar-default-toggle-hover-bg: #fff;
@navbar-default-toggle-icon-bar-bg: @codeboxMainColorDarker;
@navbar-default-toggle-border-color: @codeboxMainColorDarker;
@navbar-default-link-active-color: @codeboxMainColor;
@navbar-default-link-active-bg: #fff;
@border-radius-base: 1px;
@border-radius-large: 3px;
@border-radius-small: 1px;
-15
View File
@@ -1,15 +0,0 @@
<div class="page-footer">
{% if _input == "README.md" %}
<a href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" class="navigation-link">Start</a>
{% else %}
{% if progress.current.next %}
{% if progress.current.next.path %}
<a href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" class="navigation-link next">Next</a>
{% else %}
<div class="navigation-link coming-soon">Coming soon</div>
{% endif %}
{% else %}
<div class="navigation-link finished">Finished!</div>
{% endif %}
{% endif %}
</div>
+2 -1
View File
@@ -2,6 +2,7 @@
<!-- Actions Left -->
<a href="{{ githubHost }}{{ 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></a>
<a href="#" class="btn pull-left toggle-search"><i class="fa fa-search"></i></a>
<!-- Actions Right -->
<a href="#" target="_blank" class="btn pull-right" data-sharing="google-plus"><i class="fa fa-google-plus"></i></a>
@@ -13,5 +14,5 @@
<!-- Title -->
<h1><a href="{{ basePath }}/README.html" >{{ title }}</a></h1>
<h1><a href="{{ basePath }}/" >{{ title }}</a></h1>
</div>
+7 -4
View File
@@ -1,4 +1,7 @@
<div class="book-summary">
<div class="book-search">
<input type="text" placeholder="Search" class="form-control" />
</div>
<ul class="summary">
<li>
<a href="{{ githubHost }}{{ githubAuthor }}" target="blank">About the author</a>
@@ -10,11 +13,11 @@
<a href="{{ githubHost }}{{ 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>
<li data-level="0" data-path="index.html">
<a href="{{ basePath }}/"><i class="fa fa-check"></i> Introduction</a>
</li>
{% for item in summary.chapters %}
<li {% if item._path == _input %}class="active"{% endif %} data-level="{{ item.level }}">
<li {% if item._path == _input %}class="active"{% endif %} data-level="{{ item.level }}" {% if item.path %}data-path="{{ item.path|mdLink }}"{% endif %}>
{% if item.path %}
<a href="{{ basePath }}/{{ item.path|mdLink }}">
<i class="fa fa-check"></i> <b>{{ item.level }})</b> {{ item.title }}
@@ -25,7 +28,7 @@
{% if item.articles.length > 0 %}
<ul class="articles">
{% for article in item.articles %}
<li {% if article._path == _input %}class="active"{% endif %} data-level="{{ article.level }}">
<li {% if article._path == _input %}class="active"{% endif %} data-level="{{ article.level }}" {% if article.path %}data-path="{{ article.path|mdLink }}"{% endif %}>
{% if article.path %}
<a href="{{ basePath }}/{{ article.path|mdLink }}">
<i class="fa fa-check"></i> <b>{{ article.level }})</b> {{ article.title }}
+21
View File
@@ -0,0 +1,21 @@
{% extends "layout.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="book-langs-index">
<div class="inner">
<h1>{{ title }}</h1>
<ul class="languages">
{% for lang in langs %}
<li>
<a href="{{ basePath}}/{{ lang.path }}">{{ lang.title }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}
{% block javascript %}{% endblock %}
+61 -18
View File
@@ -7,7 +7,7 @@
<link rel="stylesheet" href="{{ staticBase }}/print.css">
{% endblock %}
{% set exercise = 0 %}
{% set exercise = 1 %}
{% block content %}
{# Cover #}
<section id="cover">
@@ -41,6 +41,22 @@
</section>
{# Pages content #}
<section>
<article id="README.md">
{% for section in pages["README.md"].content %}
{% 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>
{% endif %}
{% endfor %}
</article>
</section>
{% for item in summary.chapters %}
{% if item.articles.length > 0 %}
<section>
@@ -49,28 +65,55 @@
</article>
{% for article in item.articles %}
<article id="{{ article.path }}">
{% for section in pages[article.path].content %}
{% if section.type == "normal" %}
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% elif section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% if pages[article.path] %}
<article id="{{ article.path }}">
{% for section in pages[article.path].content %}
{% if section.type == "normal" %}
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre>
<code>
{% autoescape false %}{{ section.code.base|code }}{% endautoescape %}
</code>
</pre>
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
</article>
{% 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>
{% endif %}
{% endfor %}
</article>
{% endif %}
{% endfor %}
</section>
{% endif %}
{% endfor %}
{# Exercise solutions #}
{% if exercise > 1 %}
{% set exercise = 1 %}
<section>
<article class="new-chapter">
<h1>Exercise Solutions</h1>
</article>
<article>
{% for item in summary.chapters %}
{% for article in item.articles %}
{% if pages[article.path] %}
{% for section in pages[article.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>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endfor %}
</article>
</section>
{% endif %}
{% endblock %}
{% block javascript %}{% endblock %}
+22 -15
View File
@@ -2,27 +2,34 @@
{% block title %}{{ progress.current.title }}{% parent %}{% endblock %}
{% block content %}
<div class="book" data-github="{{ githubId }}" data-level="{{ progress.current.level }}">
<div class="book" data-github="{{ githubId }}" data-level="{{ progress.current.level }}" data-basepath="{{ basePath }}" data-revision="{{ revision }}">
{% include "includes/book/header.html" %}
{% include "includes/book/summary.html" %}
<div class="book-body" tabindex="-1">
<div class="page-wrapper">
{% include "includes/book/progress.html" %}
<div class="body-inner">
<div class="page-wrapper">
{% include "includes/book/progress.html" %}
<div class="page-inner">
{% 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" %}
{% include "./includes/book/exercise.html" with {section: section} %}
{% endif %}
</section>
{% endfor %}
<div class="page-inner">
{% 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" %}
{% include "./includes/book/exercise.html" with {section: section} %}
{% endif %}
</section>
{% endfor %}
</div>
</div>
{% include "includes/book/footer.html" %}
</div>
{% if progress.current.prev and progress.current.prev.path %}
<a href="{{ basePath }}/{{ progress.current.prev.path|mdLink }}" class="navigation navigation-prev"><i class="fa fa-angle-left"></i></a>
{% endif %}
{% if progress.current.next and progress.current.next.path %}
<a href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" class="navigation navigation-next"><i class="fa fa-angle-right"></i></a>
{% endif %}
</div>
</div>
{% endblock %}