Compare commits

...

76 Commits

Author SHA1 Message Date
Aaron O'Mullan ba2134c480 Bump version to 0.6.3 2014-07-30 10:23:11 -07:00
Aaron O'Mullan 4705548860 Fix resource normalization in when loading plugins
Fixes #384
2014-07-30 10:17:07 -07:00
Samy Pessé 7d51ae3689 Add changelogs for 0.6.2 2014-07-30 00:26:44 -07:00
Aaron O'Mullan b9360de3e3 Bump version to 0.6.2 2014-07-30 00:22:49 -07:00
Aaron O'Mullan 9f241457bb Fix minor issues with new plugin additions 2014-07-29 23:45:54 -07:00
Aaron O'Mullan 60a7c28e66 Support generating a plugin's book info dynamically 2014-07-29 21:50:26 -07:00
Samy Pessé 5dff9b795e Fix #345: Improve navigation on dark theme 2014-07-29 21:33:47 -07:00
Aaron O'Mullan 0134677e0e Improve path normalization when parsing SUMMARY.md 2014-07-29 20:49:11 -07:00
Aaron O'Mullan 39ef4fa199 Add invalid paths in SUMMARY.md fixture
To test for correct path normalization
2014-07-29 20:46:14 -07:00
Samy Pessé b507a30301 Bump version to 0.6.1 2014-07-23 16:49:57 -07:00
Samy Pessé 539c2d0c57 Update plugin-mathjax@0.0.4 2014-07-23 16:28:38 -07:00
Samy Pessé aa6f3530f9 Fix readme spacing 2014-07-22 23:28:13 -07:00
Samy Pessé 98057af219 Merge pull request #372 from justanotheranonymoususer/patch-1
Update summary.html (fixed an HTML typo)
2014-07-20 14:04:12 -07:00
justanotheranonymoususer bb48c8233c Update summary.html 2014-07-20 23:57:56 +03:00
Aaron O'Mullan 8f14db64df Merge pull request #370 from nightwing/patch-1
Add keyboard shortcut for submitting exercise
2014-07-15 21:33:58 -07:00
Harutyun Amirjanyan 37d40dc66c Add keyboard shortcut for submitting exercise 2014-07-16 08:07:22 +04:00
Samy Pessé 6c96680cca Merge pull request #366 from shaform/ignore
Ignore config files by default in fs.list
2014-07-14 10:08:29 -07:00
Shaform 4e475857d5 Ignore config files by default in fs.list 2014-07-13 08:40:23 -07:00
Samy Pessé f8ff0453dd Merge pull request #359 from jcouyang/patch-1
add jsbin embedded plugin
2014-07-12 07:02:18 -07:00
Samy Pessé 3cc5c47535 Bump version to 0.6.0 2014-07-12 23:28:07 +02:00
Samy Pessé a2046814b4 Change command "git:push" to "publish" 2014-07-12 23:25:40 +02:00
Samy Pessé 82f814fdbd Fix #303: generate header id the same as github 2014-07-12 23:23:03 +02:00
Jichao Ouyang cd382dc8fc add jsbin embedded support 2014-07-12 21:42:52 +08:00
Samy Pessé 3842e5bf4a Merge pull request #358 from GitbookIO/featureplatform
Add base command git:push and git:remote
2014-07-11 17:42:16 -07:00
Samy Pessé f05b138fd6 Add base command git:push and git:remote 2014-07-12 09:23:15 +02:00
Samy Pessé 1b87cec893 Merge pull request #266 from shaform/ref_links
Add reference links support
2014-07-11 17:20:43 -07:00
Samy Pessé 4a894b1e18 Merge pull request #268 from rlmv/master
Fix page.path for plugins
2014-07-11 17:19:26 -07:00
Samy Pessé c7c98009e7 Merge pull request #347 from rlmv/custom_links
Add custom links to TOC
2014-07-11 17:15:37 -07:00
Samy Pessé 356322e0ed Merge pull request #353 from citizenmatt/active_page
Highlight active page in summary
2014-07-11 17:14:36 -07:00
Samy Pessé 7a47049367 Merge pull request #354 from citizenmatt/summary_hooks
Plugin hooks for summary data
2014-07-11 17:13:40 -07:00
Samy Pessé a9f473260c Merge pull request #356 from GitbookIO/feature/includes
Support importing code snippets
2014-07-10 17:59:34 -07:00
Aaron O'Mullan d16577e8bf Add tests for includes 2014-07-10 17:49:50 +02:00
Aaron O'Mullan 15b4053d6b Support code includes for exercises 2014-07-10 17:30:12 +02:00
Aaron O'Mullan 925a9b1388 Support importing code snippets
Allowing to keep code in separate files, which can be cleaner in a
quite a few scenarios
2014-07-11 01:22:07 +02:00
Matt Ellis fc942fdeca Hooks for summary data 2014-07-09 17:17:40 +01:00
Matt Ellis 75a6ecbf52 Highlight active page in summary 2014-07-09 16:06:55 +01:00
Aaron O'Mullan 520045441b Merge pull request #352 from citizenmatt/links_with_fragments
Convert links with fragments from md to html
2014-07-08 21:45:01 +02:00
Matt Ellis 00c3e966d3 Convert links with fragments from md to html 2014-07-08 16:25:39 +01:00
Samy Pessé 8b6ebe86b8 Merge pull request #348 from rlmv/anchors_readme
Add anchor plugin to README
2014-07-06 17:12:57 +02:00
Bo Marchman d5bd3453e1 add heading anchors plugin to readme 2014-07-02 23:49:07 -04:00
Bo Marchman ddacbbe73b update readme with custom links 2014-07-02 23:40:57 -04:00
Bo Marchman f7d52f0eff Add custom link capability
Users can add custom links at the top of the table of contents by
adding a links.custom object to book.json. Issue #304
2014-07-02 23:38:14 -04:00
Robert Marchman 39c50640ea pass 'rawPath' page path to plugins 2014-06-24 12:33:19 -04:00
Samy Pessé 16128c7e60 Bump version to 0.5.6 2014-06-24 12:35:13 +02:00
Samy Pessé 8b6a80cffb Fix error when chapters list is empty
Fix GitbookIO/gitbook.io#101 and GitbookIO/gitbook.io#102
2014-06-24 12:34:29 +02:00
Aaron O'Mullan 86f8a9d8fb Remove useless (and bad) sort on progress
Fixes bad ordering of chapters on gitbook.io
2014-06-20 03:40:47 -07:00
Aaron O'Mullan 790dfd601e Add csharp language highlight mapping
Fixes #327
2014-06-18 11:26:50 -07:00
Aaron O'Mullan 3426428142 Bump version to 0.5.5 2014-06-18 00:25:23 -07:00
Aaron O'Mullan 6c5c8e6eb7 Fix LANGS.md parsing
And cleanup Summary parsing
2014-06-17 16:18:08 -07:00
Aaron O'Mullan 3aefcaea73 Bump version to 0.5.4 2014-06-17 10:56:45 -07:00
Aaron O'Mullan 04c025774d Improve robustness of link utility functions
Fixes #321, fixes #323
2014-06-17 10:56:13 -07:00
Aaron O'Mullan 80aac7f581 Support --verbose flag, fixes #325 2014-06-17 10:46:56 -07:00
Samy Pessé 266f7ca73d Add infos about debugging 2014-06-17 18:45:44 +02:00
Shaform 6bbefad24e Merge remote-tracking branch 'upstream/master' into ref_links 2014-06-17 22:08:46 +08:00
Aaron O'Mullan d6eb8e4c60 Support URLs in "github" config
Fixes GitbookIO/gitbook.io#90
2014-06-16 18:52:36 -07:00
Aaron O'Mullan c87edb155a Better string slicing in parse.git.githubID() 2014-06-16 17:48:18 -07:00
Samy Pessé ef0468ad5d Fix #310: improve display of long title in header 2014-06-16 14:03:28 +02:00
Samy Pessé f03153eb0c Remove page break between articles in ebook
Add title for exercise solutions sections
2014-06-16 13:55:07 +02:00
Samy Pessé 3d8b6e550a Merge pull request #319 from GitbookIO/enchancement/custom_intro
Enchancement/custom intro
2014-06-16 13:02:38 +02:00
Aaron O'Mullan 4c3dbca5ca Normalize intro node in Summary parsing 2014-06-16 00:26:11 -07:00
Aaron O'Mullan a258feea37 Remove special case in navigation
For intro node
2014-06-16 00:25:59 -07:00
Aaron O'Mullan 3a3332d0b9 Adapt summary tests to new summary parsing
We now insert default README.md (introduction) node in parsing the
SUMMARY.md logic
2014-06-16 00:25:33 -07:00
Aaron O'Mullan 4259283f32 Refactor Summary rendering
Introduction is no longer an edge case
2014-06-16 00:09:48 -07:00
Aaron O'Mullan 243d977cd6 Add test for level regarding #318 2014-06-15 23:36:40 -07:00
Aaron O'Mullan 94bbbecedf Support custom intro node, fixes #318 2014-06-15 23:30:43 -07:00
Aaron O'Mullan f49eab42a9 Fix test for #318 2014-06-15 23:29:24 -07:00
Aaron O'Mullan ce6b2bc47d Add extra navigation tests for #318 2014-06-15 23:19:28 -07:00
Aaron O'Mullan 3245d7e430 Drop Travis tests for Node 0.8 2014-06-15 22:49:15 -07:00
Aaron O'Mullan 6ee17c32ae Bump version to 0.5.3 2014-06-15 22:39:18 -07:00
Aaron O'Mullan bf7ffe6d4c Skip extra files and folders in copy phase of generation 2014-06-15 22:37:15 -07:00
Aaron O'Mullan ec7bc6b347 Add link to gitbook.io in readme 2014-06-15 22:36:51 -07:00
Aaron O'Mullan b425db0979 Allow external links in summary, fixes #300 2014-06-08 12:31:52 -07:00
Samy Pessé 30c6d1c9de Remove useless log 2014-06-08 02:21:05 +02:00
Shaform 1f4cf33dd0 Improve code syntax for links assignment 2014-05-28 06:57:50 +08:00
Shaform 7274aa6940 fix links error 2014-05-27 22:24:23 +08:00
Shaform 125e804cd5 Add reference links support
This commit resolves #184
2014-05-27 17:24:47 +08:00
41 changed files with 646 additions and 176 deletions
-1
View File
@@ -2,4 +2,3 @@ language: node_js
node_js:
- "0.11"
- "0.10"
- "0.8"
+10
View File
@@ -1,2 +1,12 @@
# Release notes
## 0.6.2
- Support generating a plugin's book info dynamically
- Improve navigation on dark theme
- Improve path normalization when parsing SUMMARY.md
## 0.6.0
- Generate header id the same as github
- Custom links can be added at top of sidebar
- Summary can now be transformed by plugins
- Support importing code snippets
+17 -1
View File
@@ -3,7 +3,7 @@ GitBook
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
GitBook is a command line tool (and Node.js library) for building beautiful books and exercises using GitHub/Git and Markdown. You can see an example: [Learn Javascript](https://www.gitbook.io/book/GitBookIO/javascript). An [editor](https://github.com/GitbookIO/editor) is available for Windows, Mac and Linux. You can follow [@GitBookIO](https://twitter.com/GitBookIO) on Twitter. Complete documentation is available at [help.gitbook.io](http://help.gitbook.io/).
GitBook is a command line tool (and Node.js library) for building beautiful books and exercises using GitHub/Git and Markdown. Here is an example: [Learn Javascript](https://www.gitbook.io/book/GitBookIO/javascript). You can publish book easily online using [gitbook.io](https://www.gitbook.io) and an [editor](https://github.com/GitbookIO/editor) is available for Windows, Mac and Linux. You can follow [@GitBookIO](https://twitter.com/GitBookIO) on Twitter. Complete documentation is available at [help.gitbook.io](http://help.gitbook.io/).
![Image](https://raw.github.com/GitbookIO/gitbook/master/preview.png)
@@ -92,6 +92,11 @@ Here are the options that can be stored in this file:
"issues": null,
"contribute": null,
// Custom links at top of sidebar
"custom": {
"Custom link name": "https://customlink.com"
},
// Sharing links
"sharing": {
"google": null,
@@ -253,3 +258,14 @@ Plugins can used to extend your book's functionality. Read [GitbookIO/plugin](ht
* [Markdown within HTML](https://github.com/mrpotes/gitbook-plugin-nestedmd): Process markdown within HTML blocks - allows custom layout options for individual pages
* [Bootstrap JavaScript plugins](https://github.com/mrpotes/gitbook-plugin-bootstrapjs): Use the [Bootstrap JavaScript plugins](http://getbootstrap.com/javascript) in your online GitBook
* [Piwik Open Analytics](https://github.com/emmanuel-keller/gitbook-plugin-piwik): Piwik Open Analytics tracking for your book
* [Heading Anchors](https://github.com/rlmv/gitbook-plugin-anchors): Add linkable Github-style anchors to headings
* [JSBin](https://github.com/jcouyang/gitbook-plugin-jsbin): Ebedded jsbin frame into your book
#### Debugging
You can use the environment variable `DEBUG=true` to get better error messages (with stack trace). For example:
```
$ export DEBUG=true
$ gitbook build ./
```
+13 -3
View File
@@ -10,9 +10,10 @@ var generators = require("../lib/generate").generators;
var buildCommand = function(command) {
return command
.option('-v, --verbose', 'Activate verbose mode, useful for debugging errors')
.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('--config <config file>', 'Configuration file to use, defaults to book.js or book.json')
.option('--config <config file>', 'Configuration file to use, defaults to book.js or book.json');
};
@@ -26,6 +27,11 @@ var makeBuildFunc = function(converter) {
dir = dir || process.cwd();
outputDir = options.output;
// Set debugging
if(options.verbose) {
process.env.DEBUG = "true";
}
console.log('Starting build ...');
return converter(
_.extend({}, options || {}, {
@@ -38,8 +44,12 @@ var makeBuildFunc = function(converter) {
.then(function(output) {
console.log("Successfully built!");
return output;
}, utils.logError)
.fail(function() {
})
.fail(function(err) {
// Log error
utils.logError(err);
// Exit process with failure code
process.exit(-1);
});
};
+17
View File
@@ -14,6 +14,7 @@ var fs = require('../lib/generate/fs');
var utils = require('./utils');
var build = require('./build');
var Server = require('./server');
var platform = require("./platform");
// General options
prog
@@ -115,6 +116,22 @@ prog
return initDir(dir);
});
prog
.command('publish [source_dir]')
.description('Publish content to the associated gitbook.io book')
.action(function(dir) {
dir = dir || process.cwd();
return platform.publish(dir);
});
prog
.command('git:remote [source_dir] [book_id]')
.description('Adds a git remote to a book repository')
.action(function(dir, bookId) {
dir = dir || process.cwd();
return platform.remote(dir, bookId);
});
// Parse and fallback to help if no args
if(_.isEmpty(prog.parse(process.argv).args) && process.argv.length === 2) {
prog.help();
+53
View File
@@ -0,0 +1,53 @@
var Q = require("q");
var utils = require("./utils");
var publish = function(folder) {
if (!folder) {
console.log("Need a repository folder");
return process.exit(-1);
}
utils.gitCmd("push", ["gitbook", "master"])
.then(function(out) {
console.log(out.stdout);
}, function(err) {
if (err.code == 128) {
console.log("No book on gitbook.io is configured with this git repository.");
console.log("Run 'gitbook git:remote username/book' to intialize this repository.");
} else {
console.log(err.message);
}
process.exit(-1);
});
};
var remote = function(folder, bookId) {
if (!folder || !bookId) {
console.log("Need a repository folder and a book id");
return process.exit(-1);
}
var url = "https://push.gitbook.io/"+bookId+".git";
var addRemote = function() {
return utils.gitCmd("remote", ["add", "gitbook", url]);
}
addRemote()
.fail(function(err) {
if (err.code == 128) {
return utils.gitCmd("remote", ["rm", "gitbook"]).then(addRemote);
}
return Q.reject(err);
})
.then(function(out) {
console.log("Book remote '"+url+"' added to the folder");
}, function(err) {
console.log(err.message);
process.exit(-1);
});
};
module.exports = {
publish: publish,
remote: remote
};
+23 -1
View File
@@ -1,6 +1,7 @@
var Q = require('q');
var _ = require('lodash');
var exec = require('child_process').exec;
var http = require('http');
var send = require('send');
@@ -36,9 +37,30 @@ function logError(err) {
return Q.reject(err);
};
function runGitCommand(command, args) {
var d = Q.defer(), child;
args = ["git", command].concat(args).join(" ");
child = exec(args, function (error, stdout, stderr) {
if (error !== null) {
error.stdout = stdout;
error.stderr = stderr;
d.reject(error);
} else {
d.resolve({
stdout: stdout,
stderr: stderr
})
}
});
return d.promise;
};
// Exports
module.exports = {
watch: watch,
logError: logError
logError: logError,
gitCmd: runGitCommand
};
+17 -1
View File
@@ -16,9 +16,25 @@ var getFiles = function(path) {
// Add extra rules to ignore common folders
ig.addIgnoreRules([
// Skip Git stuff
'.git/',
'.gitignore',
'.DS_Store'
// Skip OS X meta data
'.DS_Store',
// Skip stuff installed by plugins
'node_modules',
// Skip book outputs
'*.pdf',
'*.epub',
'*.mobi',
// Skip config files
'.ignore',
'.bookignore',
'book.json',
], '__custom_stuff');
// Push each file to our list
+2 -3
View File
@@ -14,13 +14,13 @@ var BaseGenerator = function(options) {
};
BaseGenerator.prototype.callHook = function(name, data) {
return this.plugins.hook(name, this, data);
return this.plugins.hook(name, data);
};
BaseGenerator.prototype.loadPlugins = function() {
var that = this;
return Plugin.fromList(this.options.plugins, this.options.input)
return Plugin.fromList(this.options.plugins, this.options.input, this)
.then(function(_plugins) {
that.plugins = _plugins;
@@ -62,7 +62,6 @@ BaseGenerator.prototype.copyCover = function() {
]);
})
.fail(function(err) {
console.log(err);
return Q();
});
};
+33 -8
View File
@@ -224,8 +224,15 @@ var generateBook = function(options) {
options.github = null;
return null;
} else if(options.github) {
// Git already specified in options
return options.github;
// Git already specified in options
options.github = (
// Support URLs in "github" entry of book.json
parse.git.githubID(options.github) ||
// Fallback
options.github
);
return;
}
// Try auto detecting
@@ -264,13 +271,31 @@ var generateBook = function(options) {
// Get summary
.then(function() {
return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
.then(function(_summary) {
options.summary = parse.summary(_summary);
var summary = {
path: path.join(options.input, "SUMMARY.md")
};
// Parse navigation
options.navigation = parse.navigation(options.summary);
});
var _callHook = function(name) {
return generator.callHook(name, summary)
.then(function(_summary) {
summary = _summary;
return summary;
});
};
return fs.readFile(summary.path, "utf-8")
.then(function(_content) {
summary.content = _content;
return _callHook("summary:before");
})
.then(function() {
summary.content = parse.summary(summary.content);
return _callHook("summary:after");
})
.then(function() {
options.summary = summary.content;
options.navigation = parse.navigation(options.summary);
})
})
// Skip processing some files
+150 -78
View File
@@ -10,11 +10,15 @@ var pkg = require("../../package.json");
var RESOURCES = ["js", "css"];
var Plugin = function(name, root) {
var Plugin = function(name, root, generator) {
this.name = name;
this.root = root;
this.packageInfos = {};
this.infos = {};
this.generator = generator;
// Bind methods
_.bindAll(this);
_.each([
"gitbook-plugin-"+name,
@@ -43,28 +47,67 @@ Plugin.prototype.load = function(name, baseDir) {
}
};
// Return resources
Plugin.prototype.getResources = function(resource) {
if (!this.infos.book || !this.infos.book[resource]) {
return [];
Plugin.prototype.normalizeResource = function(resource) {
// Parse the resource path
var parsed = url.parse(resource);
// This is a remote resource
// so we will simply link to using it's URL
if (parsed.protocol) {
return {
"url": resource
};
}
return _.chain(this.infos.book[resource])
.map(function(resource) {
var parsed = url.parse(resource);
if (parsed.protocol) return {"url": resource}
else return { "path": this.name+"/"+resource };
}.bind(this))
.value();
// This will be copied over from disk
// and shipped with the book's build
return { "path": this.name+"/"+resource };
};
// Return resources
Plugin.prototype._getResources = function() {
var book = this.infos.book;
// Nothing specified, fallback to default
if (!book) {
return Q({});
}
// Dynamic function
if(typeof book === "function") {
// Call giving it the context of our generator
return Q().then(book.bind(this.generator));
}
// Plain data object
return Q(_.cloneDeep(book));
};
// Normalize resources and return them
Plugin.prototype.getResources = function() {
var that = this;
return this._getResources()
.then(function(resources) {
_.each(RESOURCES, function(resourceType) {
resources[resourceType] = (resources[resourceType] || []).map(that.normalizeResource);
});
return resources;
});
};
// Test if it's a valid plugin
Plugin.prototype.isValid = function() {
return (
this.packageInfos
&& this.packageInfos.name
&& this.packageInfos.engines
&& this.packageInfos.engines.gitbook
&& semver.satisfies(pkg.version, this.packageInfos.engines.gitbook)
this.packageInfos &&
this.packageInfos.name &&
this.packageInfos.engines &&
this.packageInfos.engines.gitbook &&
semver.satisfies(pkg.version, this.packageInfos.engines.gitbook)
);
};
@@ -74,7 +117,10 @@ Plugin.prototype.resolveFile = function(filename) {
};
// Resolve file path
Plugin.prototype.callHook = function(name, context, data) {
Plugin.prototype.callHook = function(name, data) {
// Our generator will be the context to apply
var context = this.generator;
var hookFunc = this.infos.hooks? this.infos.hooks[name] : null;
data = data || {};
@@ -86,18 +132,20 @@ Plugin.prototype.callHook = function(name, context, data) {
});
};
// Has assets
Plugin.prototype.hasAssets = function(out) {
return (this.infos.book && this.infos.book.assets);
};
// Copy plugin assets fodler
Plugin.prototype.copyAssets = function(out) {
if (!this.hasAssets()) return Q();
return fs.copy(
this.resolveFile(this.infos.book.assets),
out
);
var that = this;
return this.getResources().get('assets')
.then(function(assets) {
// Assets are undefined
if(!assets) return false;
return fs.copy(
that.resolveFile(assets),
out
).then(_.constant(true));
}, _.constant(false));
};
@@ -114,7 +162,7 @@ Plugin.normalizeNames = function(names) {
return name.length > 0 && name[0] == "-";
})
.map(function(name) {
return name.slice(1)
return name.slice(1);
})
.value();
@@ -133,69 +181,93 @@ Plugin.normalizeNames = function(names) {
};
// Extract data from a list of plugin
Plugin.fromList = function(names, root) {
Plugin.fromList = function(names, root, generator) {
var failed = [];
// Load plugins
var plugins = _.map(names, function(name) {
var plugin = new Plugin(name, root);
var plugin = new Plugin(name, root, generator);
if (!plugin.isValid()) failed.push(name);
return plugin;
});
if (_.size(failed) > 0) return Q.reject(new Error("Error loading plugins: "+failed.join(",")));
// Get all resources
var resources = _.chain(RESOURCES)
.map(function(resource) {
return [
resource,
_.chain(plugins)
.map(function(plugin) {
return plugin.getResources(resource);
})
.flatten()
.value()
];
})
.object()
.value();
resources.html = {}
// The raw resources extracted from each plugin
var pluginResources;
_.each(plugins, function(plugin) {
if (!plugin.infos.book || !plugin.infos.book.html) return;
var html = plugin.infos.book.html || {};
_.each(html, function(code, key) {
if (!_.isFunction(code)) code = _.constant(code);
if (!resources.html[key]) resources.html[key] = [];
resources.html[key].push(code);
// Get resources of plugins
return Q.all(_.map(plugins, function(plugin) {
return plugin.getResources();
}))
// Extract resources out
// css, js, etc ...
.then(function(resources) {
pluginResources = resources;
// Group by resource types
return _.chain(RESOURCES)
.map(function(resourceType) {
// Get resources from all the plugins for this current type
return [
// Key
resourceType,
// Value
_.chain(resources)
.pluck(resourceType)
.compact()
.flatten()
.value()
];
})
});
return Q({
'list': plugins,
'resources': resources,
'hook': function(name, context, data) {
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function(ret) {
return plugin.callHook(name, context, ret);
})
}, Q(data));
},
'template': function(name) {
var withTpl = _.find(plugins, function(plugin) {
return (plugin.infos.templates
&& plugin.infos.templates[name]);
.object()
.value();
})
// Extract html snippets
.then(function(resources) {
// Map of html resources by name added by each plugin
resources.html = pluginResources.reduce(function(accu, resource) {
var html = (resource && resource.html) || {};
_.each(html, function(code, key) {
// Turn into function if not one already
if (!_.isFunction(code)) code = _.constant(code);
// Append
accu[key] = (accu[key] || []).concat([code]);
});
if (!withTpl) return null;
return withTpl.resolveFile(withTpl.infos.templates[name]);
},
'html': function(tag, context, options) {
return _.map(resources.html[tag] || [], function(code) {
return code.call(context, options);
}).join("\n");
}
return accu;
}, {});
return resources;
})
// Return big multi-plugin object
.then(function(resources) {
return {
'list': plugins,
'resources': resources,
'hook': function(name, data) {
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function(ret) {
return plugin.callHook(name, ret);
});
}, Q(data));
},
'template': function(name) {
var withTpl = _.find(plugins, function(plugin) {
return (
plugin.infos.templates &&
plugin.infos.templates[name]
);
});
if (!withTpl) return null;
return withTpl.resolveFile(withTpl.infos.templates[name]);
},
'html': function(tag, context, options) {
return _.map(resources.html[tag] || [], function(code) {
return code.call(context, options);
}).join("\n");
}
};
});
};
+5 -4
View File
@@ -102,6 +102,7 @@ Generator.prototype.convertFile = function(content, _input) {
var page = {
path: _input,
rawPath: input, // path to raw md file
content: content,
progress: parse.progress(this.options.navigation, _input)
};
@@ -197,16 +198,16 @@ Generator.prototype.copyAssets = function() {
.then(function() {
return Q.all(
_.map(that.plugins.list, function(plugin) {
if (!plugin.hasAssets()) return Q();
var pluginAssets = path.join(that.options.output, "gitbook/plugins/", plugin.name);
return plugin.copyAssets(pluginAssets)
.then(function() {
.then(function(copiedStuff) {
// Nothing was copied
if(!copiedStuff) { return; }
return that.manifest.addFolder(pluginAssets, "gitbook/plugins/"+plugin.name);
});
})
);
})
});
};
// Dump search index to disk
+6 -1
View File
@@ -2,6 +2,7 @@ var path = require("path");
var swig = require('swig');
var hljs = require('highlight.js');
var links = require('../utils/').links;
var pkg = require('../../package.json');
swig.setDefaults({
@@ -24,7 +25,6 @@ swig.setFilter('mdLink', function(link) {
return link;
});
// Swig filter: highlight coloration
swig.setFilter('code', function(code, lang) {
try {
@@ -44,4 +44,9 @@ swig.setFilter('pathJoin', function(base, _path) {
return path.join(base, _path);
});
// Is a link an absolute link
swig.setFilter('isExternalLink', function(link) {
return links.isExternal(link);
});
module.exports = swig;
+20
View File
@@ -0,0 +1,20 @@
var fs = require('fs');
var path = require('path');
module.exports = function(code, folder) {
folder = folder || '';
return code.replace(/{{([\s\S]+?)}}/g, function(match, filename) {
// Normalize filename
var fname = path.join(folder, filename.trim());
// Try including snippet from FS
try {
// Trim trailing newlines/space of imported snippets
return fs.readFileSync(fname, 'utf8').trimRight();
} catch(err) {}
// If fails leave content as is
return match;
});
};
+1 -1
View File
@@ -28,7 +28,7 @@ function gitURL(path) {
// Parse a git URL to a github ID : username/reponame
function githubID(_url) {
// Remove .git if it's in _url
var sliceEnd = _url.slice(-4) === '.git' ? -4 : _url.length;
var sliceEnd = _url.slice(-4) === '.git' ? -4 : undefined;
// Detect HTTPS repos
var parsed = url.parse(_url);
+8 -6
View File
@@ -1,13 +1,14 @@
var _ = require("lodash")
var parseSummary = require("./summary");
var _ = require("lodash");
var parseEntries = require("./summary").entries;
var parseLangs = function(content) {
var summary = parseSummary(content);
var entries = parseEntries(content);
return {
list: _.chain(summary.chapters)
list: _.chain(entries)
.filter(function(entry) {
return entry.path != null;
return Boolean(entry.path);
})
.map(function(entry) {
return {
@@ -17,7 +18,8 @@ var parseLangs = function(content) {
};
})
.value()
}
};
};
module.exports = parseLangs;
+4
View File
@@ -118,6 +118,10 @@ function lexPage(src) {
return sections;
}, [])
.map(function(section) {
section.links = nodes.links;
return section;
})
.value();
}
+2 -8
View File
@@ -26,15 +26,9 @@ function navigation(summary, files) {
// Support single files as well as list
files = _.isArray(files) ? files : (_.isString(files) ? [files] : null);
// Special README nav
var README_NAV = {
path: 'README.md',
title: 'Introduction',
level: '0',
};
// List of all navNodes
var navNodes = [README_NAV].concat(flattenChapters(summary.chapters));
// Flatten chapters, then add in default README node if ndeeded etc ...
var navNodes = flattenChapters(summary.chapters);
var prevNodes = [null].concat(navNodes.slice(0, -1));
var nextNodes = navNodes.slice(1).concat([null]);
+11 -6
View File
@@ -5,6 +5,7 @@ var hljs = require('highlight.js');
var lex = require('./lex');
var renderer = require('./renderer');
var codeInclude = require('./code_include');
var lnormalize = require('../utils/lang').normalize;
@@ -12,10 +13,9 @@ var lnormalize = require('../utils/lang').normalize;
// Render a section using our custom renderer
function render(section, _options) {
// Copy section
var links = section.links || {};
section = _.toArray(section);
// marked's Render expects this, we don't use it yet
section.links = {};
section.links = links;
// Build options using defaults and our custom renderer
var options = _.extend({}, marked.defaults, {
@@ -74,15 +74,20 @@ function parsePage(src, options) {
// Main language
var lang = validLangs ? langs[0] : null;
// codeInclude shortcut
var ci = function(code) {
return codeInclude(code, options.dir);
};
return {
id: section.id,
type: section.type,
content: render(nonCodeNodes),
lang: lang,
code: {
base: codeNodes[0].text,
solution: codeNodes[1].text,
validation: codeNodes[2].text,
base: ci(codeNodes[0].text),
solution: ci(codeNodes[1].text),
validation: ci(codeNodes[2].text),
// Context is optional
context: codeNodes[3] ? codeNodes[3].text : null,
}
-3
View File
@@ -11,9 +11,6 @@ var calculProgress = function(navigation, current) {
nav.path = path;
return nav;
})
.sortBy(function(nav) {
return parseFloat(nav.level);
})
.map(function(nav, i) {
// Calcul percent
nav.percent = (i * 100) / Math.max((n - 1), 1);
+18 -2
View File
@@ -1,6 +1,7 @@
var url = require('url');
var inherits = require('util').inherits;
var links = require('../utils').links;
var codeInclude = require('./code_include');
var path = require('path');
@@ -56,7 +57,7 @@ GitBookRenderer.prototype.link = function(href, title, text) {
// Relative link, rewrite it to point to github repo
if(links.isRelative(_href)) {
if (path.extname(_href) == ".md") {
if (path.extname(parsed.path) == ".md") {
_href = links.toAbsolute(_href, o.dir || "./", o.outdir || "./");
if (o.singleFile) {
@@ -125,7 +126,7 @@ GitBookRenderer.prototype._createCheckboxAndRadios = function(text) {
var length = splittedText.length;
var label = '<label class="quiz-label" for="' + quizIdentifier + '">' + splittedText[length - 1] + '</label>';
return text.replace(fieldRegex, field).replace(splittedText[length - 1], label);
}
};
GitBookRenderer.prototype.tablecell = function(content, flags) {
return GitBookRenderer.super_.prototype.tablecell(this._createCheckboxAndRadios(content), flags);
@@ -135,5 +136,20 @@ GitBookRenderer.prototype.listitem = function(text) {
return GitBookRenderer.super_.prototype.listitem(this._createCheckboxAndRadios(text));
};
GitBookRenderer.prototype.code = function(code, lang, escaped) {
return GitBookRenderer.super_.prototype.code.call(
this,
// Import code snippets
codeInclude(code, this._extra_options.dir),
lang,
escaped
);
};
GitBookRenderer.prototype.heading = function(text, level, raw) {
var id = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w -]+/g, '').replace(/ /g, '-');
return '<h' + level + ' id="' + id + '">' + text + '</h' + level + '>\n';
};
// Exports
module.exports = GitBookRenderer;
+39 -10
View File
@@ -81,12 +81,17 @@ function parseTitle(src, nums) {
title: matches[1],
level: level,
// Replace .md references with .html
path: matches[2].replace(/\\/g, '/'),
// Normalize path
// 1. Convert Window's "\" to "/"
// 2. Remove leading "/" if exists
path: matches[2].replace(/\\/g, '/').replace(/^\/+/, ''),
};
}
function parseChapter(nodes, nums) {
// Convert single number to an array
nums = _.isArray(nums) ? nums : [nums];
return _.extend(parseTitle(_.first(nodes).text, nums), {
articles: _.map(listSplit(filterList(nodes), 'list_item_start', 'list_item_end'), function(nodes, i) {
return parseChapter(nodes, nums.concat(i + 1));
@@ -94,24 +99,48 @@ function parseChapter(nodes, nums) {
});
}
function parseSummary(src) {
function defaultChapterList(chapterList) {
var first = _.first(chapterList);
if (first) {
var chapter = parseChapter(first, [0]);
// Already have README node, we're good to go
if(chapter.path === 'README.md') {
return chapterList;
}
}
return [
[ { type: 'text', text: '[Introduction](README.md)' } ]
].concat(chapterList);
}
function listGroups(src) {
var nodes = marked.lexer(src);
// Get out list of chapters
var chapterList = filterList(nodes);
// Get out groups of lists
return listSplit(
filterList(nodes),
'list_item_start', 'list_item_end'
);
}
function parseSummary(src) {
// Split out chapter sections
var chapters = _.chain(listSplit(chapterList, 'list_item_start', 'list_item_end'))
.map(function(nodes, i) {
return parseChapter(nodes, [i + 1]);
})
.value();
var chapters = defaultChapterList(listGroups(src))
.map(parseChapter);
return {
chapters: chapters
};
}
function parseEntries (src) {
return listGroups(src).map(parseChapter);
}
// Exports
module.exports = parseSummary;
module.exports.entries = parseEntries;
+1
View File
@@ -2,6 +2,7 @@ var MAP = {
'py': 'python',
'js': 'javascript',
'rb': 'ruby',
'csharp': 'cs',
};
function normalize(lang) {
+17 -3
View File
@@ -1,11 +1,24 @@
var url = require('url');
var path = require('path');
// Is the link an external link
var isExternal = function(href) {
try {
return Boolean(url.parse(href).protocol);
} catch(err) { }
return false;
};
// Return true if the link is relative
var isRelative = function(href) {
var parsed = url.parse(href);
try {
var parsed = url.parse(href);
return !parsed.protocol && parsed.path && parsed.path[0] != '/';
return !parsed.protocol && parsed.path && parsed.path[0] != '/';
} catch(err) {}
return true;
};
// Relative to absolute path
@@ -41,6 +54,7 @@ var join = function() {
module.exports = {
isRelative: isRelative,
isExternal: isExternal,
toAbsolute: toAbsolute,
join: join
};
};
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.5.2",
"version": "0.6.3",
"homepage": "http://www.gitbook.io/",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -22,7 +22,7 @@
"resolve": "0.6.3",
"tiny-lr-fork": "0.0.5",
"gitbook-plugin": "0.0.2",
"gitbook-plugin-mathjax": "0.0.3",
"gitbook-plugin-mathjax": "0.0.4",
"gitbook-plugin-livereload": "0.0.1"
},
"devDependencies": {
+13
View File
@@ -0,0 +1,13 @@
# Summary
* [Custom name for Introduction](README.md)
* [Chapter 1](chapter-1/README.md)
* [Article 1](chapter-1/ARTICLE1.md)
* [Article 2](chapter-1/ARTICLE2.md)
* [article 1.2.1](chapter-1/ARTICLE-1-2-1.md)
* [article 1.2.2](chapter-1/ARTICLE-1-2-2.md)
* [Chapter 2](chapter-2/README.md)
* [Chapter 3](chapter-3/README.md)
* [Chapter 4](chapter-4/README.md)
* Unfinished article
* Unfinished Chapter
+29
View File
@@ -0,0 +1,29 @@
# Beautiful chapter
Here is a nice included snippet :
```c
{{ included.c }}
```
----
An exercise using includes
```c
{{ included.c }}
Remove this extra code at the end
```
```c
{{ included.c }}
```
```c
{{ included.c }}
This validation code is wrong but who cares ?
```
----
+2 -2
View File
@@ -3,8 +3,8 @@
* [Chapter 1](chapter-1/README.md)
* [Article 1](chapter-1/ARTICLE1.md)
* [Article 2](chapter-1/ARTICLE2.md)
* [article 1.2.1](chapter-1/ARTICLE-1-2-1.md)
* [article 1.2.2](chapter-1/ARTICLE-1-2-2.md)
* [article 1.2.1](\chapter-1\ARTICLE-1-2-1.md)
* [article 1.2.2](/chapter-1/ARTICLE-1-2-2.md)
* [Chapter 2](chapter-2/README.md)
* [Chapter 3](chapter-3/README.md)
* [Chapter 4](chapter-4/README.md)
+7
View File
@@ -0,0 +1,7 @@
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("All is well\n");
return 0;
}
+38
View File
@@ -0,0 +1,38 @@
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var page = require('../').parse.page;
var FIXTURES_DIR = path.join(__dirname, './fixtures/');
function loadPage (name, options) {
var CONTENT = fs.readFileSync(FIXTURES_DIR + name + '.md', 'utf8');
return page(CONTENT, options);
}
describe('Code includes', function() {
var LEXED = loadPage('INCLUDES', {
'dir': FIXTURES_DIR,
});
var INCLUDED_C = fs.readFileSync(path.join(FIXTURES_DIR, 'included.c'), 'utf8');
it('should work for snippets', function() {
assert.equal(LEXED[0].type, 'normal');
// Has replaced include
assert.equal(
LEXED[0].content.indexOf('{{ included.c }}'),
-1
);
});
it('should work for exercises', function() {
assert.equal(LEXED[1].type, 'exercise');
// Solution is trimmed version of source
assert.equal(LEXED[1].code.solution, INCLUDED_C.trim());
});
});
+17
View File
@@ -7,7 +7,9 @@ var navigation = require('../').parse.navigation;
var CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/SUMMARY.md'), 'utf8');
var ALT_CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/ALTERNATIVE_SUMMARY.md'), 'utf8');
var LEXED = summary(CONTENT);
var ALT_LEXED = summary(ALT_CONTENT);
describe('Summary navigation', function() {
@@ -76,6 +78,21 @@ describe('Summary navigation', function() {
assert.equal(nav['chapter-3/README.md'].level, '3');
});
it('should have a default README node', function() {
var nav = navigation(LEXED);
assert.equal(nav['README.md'].level, '0');
assert.equal(nav['README.md'].title, 'Introduction');
});
it('Should allow README node to be customized', function() {
var nav = navigation(ALT_LEXED);
assert(nav['README.md']);
assert.equal(nav['README.md'].level, '0');
assert.notEqual(nav['README.md'].title, 'Introduction');
});
it('should not accept null paths', function() {
var nav = navigation(LEXED);
+17 -13
View File
@@ -12,13 +12,13 @@ var LEXED = summary(CONTENT);
describe('Summary parsing', function () {
it('should detect chapters', function() {
assert.equal(LEXED.chapters.length, 5);
assert.equal(LEXED.chapters.length, 6);
});
it('should support articles', function() {
assert.equal(LEXED.chapters[0].articles.length, 2);
assert.equal(LEXED.chapters[1].articles.length, 0);
assert.equal(LEXED.chapters[1].articles.length, 2);
assert.equal(LEXED.chapters[2].articles.length, 0);
assert.equal(LEXED.chapters[3].articles.length, 0);
});
it('should detect paths and titles', function() {
@@ -26,30 +26,34 @@ describe('Summary parsing', function () {
assert(LEXED.chapters[1].path);
assert(LEXED.chapters[2].path);
assert(LEXED.chapters[3].path);
assert.equal(LEXED.chapters[4].path, null);
assert(LEXED.chapters[4].path);
assert.equal(LEXED.chapters[5].path, null);
assert(LEXED.chapters[0].title);
assert(LEXED.chapters[1].title);
assert(LEXED.chapters[2].title);
assert(LEXED.chapters[3].title);
assert(LEXED.chapters[4].title);
assert(LEXED.chapters[5].title);
});
it('should normalize paths from .md to .html', function() {
assert.equal(LEXED.chapters[0].path,'chapter-1/README.md');
assert.equal(LEXED.chapters[1].path,'chapter-2/README.md');
assert.equal(LEXED.chapters[2].path,'chapter-3/README.md');
assert.equal(LEXED.chapters[0].path,'README.md');
assert.equal(LEXED.chapters[1].path,'chapter-1/README.md');
assert.equal(LEXED.chapters[2].path,'chapter-2/README.md');
assert.equal(LEXED.chapters[3].path,'chapter-3/README.md');
});
it('should detect levels correctly', function() {
var c = LEXED.chapters;
assert.equal(c[0].level, '1');
assert.equal(c[1].level, '2');
assert.equal(c[2].level, '3');
assert.equal(c[0].level, '0');
assert.equal(c[1].level, '1');
assert.equal(c[2].level, '2');
assert.equal(c[3].level, '3');
assert.equal(c[0].articles[0].level, '1.1');
assert.equal(c[0].articles[1].level, '1.2');
assert.equal(c[0].articles[1].articles[0].level, '1.2.1');
assert.equal(c[1].articles[0].level, '1.1');
assert.equal(c[1].articles[1].level, '1.2');
assert.equal(c[1].articles[1].articles[0].level, '1.2.1');
});
});
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
.link-inherit{color:inherit}.link-inherit:hover,.link-inherit:focus{color:inherit}.hljs-comment,.hljs-title{color:#8e908c}.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}.hljs-number,.hljs-preprocessor,.hljs-pragma,.hljs-built_in,.hljs-literal,.hljs-params,.hljs-constant{color:#f5871f}.ruby .hljs-class .hljs-title,.css .hljs-rules .hljs-attribute{color:#eab700}.hljs-string,.hljs-value,.hljs-inheritance,.hljs-header,.ruby .hljs-symbol,.xml .hljs-cdata{color:#718c00}.css .hljs-hexcolor{color:#3e999f}.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}.hljs-keyword,.javascript .hljs-function{color:#8959a8}.hljs{display:block;background:white;color:#4d4d4c;padding:.5em}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:.5}.book-chapter{display:none}article{page-break-after:always}pre,blockquote{border:1px solid #999;page-break-inside:avoid;background:#f1f1f1;padding:8px}img{max-width:100%!important;page-break-inside:avoid;margin:0 auto}.exercise,.quiz{margin:1cm 0;padding:.4cm;page-break-inside:avoid;border:3px solid #ddd}.exercise .exercise-header,.quiz .exercise-header{margin-bottom:.4cm;padding-bottom:.2cm;border-bottom:1px solid #ddd}.exercise .question,.quiz .question{margin-top:.4cm}
.link-inherit{color:inherit}.link-inherit:hover,.link-inherit:focus{color:inherit}.hljs-comment,.hljs-title{color:#8e908c}.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}.hljs-number,.hljs-preprocessor,.hljs-pragma,.hljs-built_in,.hljs-literal,.hljs-params,.hljs-constant{color:#f5871f}.ruby .hljs-class .hljs-title,.css .hljs-rules .hljs-attribute{color:#eab700}.hljs-string,.hljs-value,.hljs-inheritance,.hljs-header,.ruby .hljs-symbol,.xml .hljs-cdata{color:#718c00}.css .hljs-hexcolor{color:#3e999f}.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}.hljs-keyword,.javascript .hljs-function{color:#8959a8}.hljs{display:block;background:white;color:#4d4d4c;padding:.5em}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:.5}.book-chapter{display:none}pre,blockquote{border:1px solid #999;page-break-inside:avoid;background:#f1f1f1;padding:8px}img{max-width:100%!important;page-break-inside:avoid;margin:0 auto}.exercise,.quiz{margin:1cm 0;padding:.4cm;page-break-inside:avoid;border:3px solid #ddd}.exercise .exercise-header,.quiz .exercise-header{margin-bottom:.4cm;padding-bottom:.2cm;border-bottom:1px solid #ddd}.exercise .question,.quiz .question{margin-top:.4cm}
File diff suppressed because one or more lines are too long
+9 -1
View File
@@ -18,6 +18,14 @@ define([
editor.setTheme("ace/theme/tomorrow");
editor.getSession().setUseWorker(false);
editor.getSession().setMode("ace/mode/javascript");
editor.commands.addCommand({
name: "submit",
bindKey: "Ctrl-Return|Cmd-Return",
exec: function() {
$exercise.find(".action-submit").click();
}
});
// Submit: test code
$exercise.find(".action-submit").click(function(e) {
@@ -52,4 +60,4 @@ define([
init: init,
prepare: prepareExercise
};
});
});
-3
View File
@@ -7,9 +7,6 @@
display: none;
}
article {
page-break-after: always;
}
pre, blockquote {
border: 1px solid #999;
+5 -1
View File
@@ -44,12 +44,16 @@
padding-right: 200px;
.transition(margin-left 0.5s ease);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
a, a:hover {
color: inherit;
text-decoration: none;
}
@media (max-width: 800px) {
@media (max-width: 1000px) {
display: none;
}
+7 -3
View File
@@ -49,10 +49,14 @@
}
}
.book.color-theme-1 .book-body {
.navigation:hover{
}
}
.book.color-theme-2 .book-body {
.navigation:hover{
.navigation {
color: rgba(255, 255, 255, 0.4);
&:hover {
color: rgba(255, 255, 255, 0.9);
}
}
}
+29 -8
View File
@@ -1,10 +1,25 @@
{% macro articles(_articles) %}
{% for item in _articles %}
<li class="chapter {% if item._path == _input %}active{% endif %}" data-level="{{ item.level }}" {% if item.path %}data-path="{{ item.path|mdLink }}"{% endif %}>
{% set externalLink = item.path|isExternalLink %}
<li class="chapter {% if item.path == _input %}active{% endif %}" data-level="{{ item.level }}" {% if item.path && !externalLink %}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 }}
</a>
{% if !externalLink %}
<a href="{{ basePath }}/{{ item.path|mdLink }}">
<i class="fa fa-check"></i>
{% if item.level !== "0" %}
<b>{{ item.level }}.</b>
{% endif %}
{{ item.title }}
</a>
{% else %}
<a target="_blank" href="{{ item.path }}">
<i class="fa fa-check"></i>
{% if item.level !== "0" %}
<b>{{ item.level }}.</b>
{% endif %}
{{ item.title }}
</a>
{% endif %}
{% else %}
<span><b>{{ item.level }}.</b> {{ item.title }}</span>
{% endif %}
@@ -33,7 +48,7 @@
{% if options.links.issues !== false && (options.links.issues || githubId) %}
{% set _divider = true %}
<li>
<a href="{{ options.links.issues|default(githubHost+githubId+"/issues") }}" target="blank"class="issues-link">Questions and Issues</a>
<a href="{{ options.links.issues|default(githubHost+githubId+"/issues") }}" target="blank" class="issues-link">Questions and Issues</a>
</li>
{% endif %}
@@ -44,13 +59,19 @@
</li>
{% endif %}
{% if options.links.custom %}
{% for link in options.links.custom %}
{% set _divider = true %}
<li>
<a href="{{ options.links.custom[loop.key] }}" target="blank" class="custom-link">{{ loop.key }}</a>
</li>
{% endfor %}
{% endif %}
{% if _divider %}
<li class="divider"></li>
{% endif %}
<li data-level="0" data-path="index.html">
<a href="{{ basePath }}/"><i class="fa fa-check"></i> Introduction</a>
</li>
{{ articles(summary.chapters) }}
{% if options.links.gitbook !== false %}
+1
View File
@@ -52,6 +52,7 @@
{% set exercise = 1 %}
<article>
<h1 class="book-chapter book-chapter-1">Exercise Solutions</h1>
<h1>Exercise Solutions</h1>
{% for item in progress.chapters %}
{% if pages[item.path] %}