.
8 | .list-group {
9 | // No need to set list-style: none; since .list-group-item is block level
10 | margin-bottom: 20px;
11 | padding-left: 0; // reset padding because ul and ol
12 | }
13 |
14 | // Individual list items
15 | // -------------------------
16 |
17 | .list-group-item {
18 | position: relative;
19 | display: block;
20 | padding: 10px 15px;
21 | // Place the border on the list items and negative margin up for better styling
22 | margin-bottom: -1px;
23 | background-color: @list-group-bg;
24 | border: 1px solid @list-group-border;
25 |
26 | // Round the first and last items
27 | &:first-child {
28 | .border-top-radius(@list-group-border-radius);
29 | }
30 | &:last-child {
31 | margin-bottom: 0;
32 | .border-bottom-radius(@list-group-border-radius);
33 | }
34 |
35 | // Align badges within list items
36 | > .badge {
37 | float: right;
38 | }
39 | > .badge + .badge {
40 | margin-right: 5px;
41 | }
42 |
43 | // Linked list items
44 | a& {
45 | color: @list-group-link-color;
46 |
47 | .list-group-item-heading {
48 | color: @list-group-link-heading-color;
49 | }
50 |
51 | // Hover state
52 | &:hover,
53 | &:focus {
54 | text-decoration: none;
55 | background-color: @list-group-hover-bg;
56 | }
57 | }
58 |
59 | // Active class on item itself, not parent
60 | &.active,
61 | &.active:hover,
62 | &.active:focus {
63 | z-index: 2; // Place active items above their siblings for proper border styling
64 | color: @list-group-active-color;
65 | background-color: @list-group-active-bg;
66 | border-color: @list-group-active-border;
67 |
68 | // Force color to inherit for custom content
69 | .list-group-item-heading {
70 | color: inherit;
71 | }
72 | .list-group-item-text {
73 | color: lighten(@list-group-active-bg, 40%);
74 | }
75 | }
76 | }
77 |
78 | // Custom content options
79 | // -------------------------
80 |
81 | .list-group-item-heading {
82 | margin-top: 0;
83 | margin-bottom: 5px;
84 | }
85 | .list-group-item-text {
86 | margin-bottom: 0;
87 | line-height: 1.3;
88 | }
89 |
--------------------------------------------------------------------------------
/theme/templates/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% block head %}
5 |
6 |
{% block title %} | {{ title }}{% endblock %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% if progress.current.next and progress.current.next.path %}
16 |
17 | {% endif %}
18 | {% if progress.current.prev and progress.current.prev.path %}
19 |
20 | {% endif %}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {% endblock %}
36 |
37 |
38 | {% block style %}
39 |
40 | {% endblock %}
41 | {% block content %}{% endblock %}
42 | {% block javascript %}
43 |
44 |
45 |
46 |
47 | {% endblock %}
48 |
49 |
50 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/progress-bars.less:
--------------------------------------------------------------------------------
1 | //
2 | // Progress bars
3 | // --------------------------------------------------
4 |
5 |
6 | // Bar animations
7 | // -------------------------
8 |
9 | // Webkit
10 | @-webkit-keyframes progress-bar-stripes {
11 | from { background-position: 40px 0; }
12 | to { background-position: 0 0; }
13 | }
14 |
15 | // Firefox
16 | @-moz-keyframes progress-bar-stripes {
17 | from { background-position: 40px 0; }
18 | to { background-position: 0 0; }
19 | }
20 |
21 | // Opera
22 | @-o-keyframes progress-bar-stripes {
23 | from { background-position: 0 0; }
24 | to { background-position: 40px 0; }
25 | }
26 |
27 | // Spec and IE10+
28 | @keyframes progress-bar-stripes {
29 | from { background-position: 40px 0; }
30 | to { background-position: 0 0; }
31 | }
32 |
33 |
34 |
35 | // Bar itself
36 | // -------------------------
37 |
38 | // Outer container
39 | .progress {
40 | overflow: hidden;
41 | height: @line-height-computed;
42 | margin-bottom: @line-height-computed;
43 | background-color: @progress-bg;
44 | border-radius: @border-radius-base;
45 | .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
46 | }
47 |
48 | // Bar of progress
49 | .progress-bar {
50 | float: left;
51 | width: 0%;
52 | height: 100%;
53 | font-size: @font-size-small;
54 | color: @progress-bar-color;
55 | text-align: center;
56 | background-color: @progress-bar-bg;
57 | .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
58 | .transition(width .6s ease);
59 | }
60 |
61 | // Striped bars
62 | .progress-striped .progress-bar {
63 | #gradient > .striped(@progress-bar-bg);
64 | background-size: 40px 40px;
65 | }
66 |
67 | // Call animation for the active one
68 | .progress.active .progress-bar {
69 | -webkit-animation: progress-bar-stripes 2s linear infinite;
70 | -moz-animation: progress-bar-stripes 2s linear infinite;
71 | -ms-animation: progress-bar-stripes 2s linear infinite;
72 | -o-animation: progress-bar-stripes 2s linear infinite;
73 | animation: progress-bar-stripes 2s linear infinite;
74 | }
75 |
76 |
77 |
78 | // Variations
79 | // -------------------------
80 |
81 | .progress-bar-success {
82 | .progress-bar-variant(@progress-bar-success-bg);
83 | }
84 |
85 | .progress-bar-info {
86 | .progress-bar-variant(@progress-bar-info-bg);
87 | }
88 |
89 | .progress-bar-warning {
90 | .progress-bar-variant(@progress-bar-warning-bg);
91 | }
92 |
93 | .progress-bar-danger {
94 | .progress-bar-variant(@progress-bar-danger-bg);
95 | }
96 |
--------------------------------------------------------------------------------
/lib/generate/page/index.js:
--------------------------------------------------------------------------------
1 | var _ = require("lodash");
2 | var util = require("util");
3 | var path = require("path");
4 | var Q = require("q");
5 | var swig = require('swig');
6 | var hljs = require('highlight.js');
7 |
8 | var fs = require("../fs");
9 | var parse = require("../../parse");
10 | var BaseGenerator = require("../site");
11 |
12 | // Swig filter: highlight coloration
13 | swig.setFilter('code', function(code, lang) {
14 | try {
15 | return hljs.highlight(lang, code).value;
16 | } catch(e) {
17 | return hljs.highlightAuto(code).value;
18 | }
19 | });
20 |
21 |
22 | /*
23 | * This generator will generate a simple index.html which can be converted as a PDF
24 | */
25 | var Generator = function() {
26 | BaseGenerator.apply(this, arguments);
27 |
28 | // List of pages content
29 | this.pages = {};
30 | };
31 | util.inherits(Generator, BaseGenerator);
32 |
33 | // Load all templates
34 | Generator.prototype.loadTemplates = function() {
35 | this.template = swig.compileFile(
36 | this.plugins.template("page") || path.resolve(this.options.theme, 'templates/page.html')
37 | );
38 | };
39 |
40 | Generator.prototype.convertFile = function(content, input) {
41 | var that = this;
42 | var json = {
43 | path: input,
44 | progress: parse.progress(this.options.navigation, input)
45 | };
46 |
47 | return Q()
48 | .then(function() {
49 | return parse.page(content, {
50 | repo: that.options.githubId,
51 | dir: path.dirname(input) || '/',
52 | outdir: './',
53 | singleFile: true
54 | });
55 | })
56 | .then(function(sections) {
57 | json.content = sections;
58 | })
59 | .then(function() {
60 | that.pages[input] = json;
61 | });
62 | };
63 |
64 | Generator.prototype.finish = function() {
65 | var that = this;
66 | var basePath = ".";
67 | var output = path.join(this.options.output, "index.html");
68 |
69 | return Q()
70 | // Generate html
71 | .then(function(pages) {
72 | return that._writeTemplate(that.template, {
73 | pages: that.pages,
74 |
75 | basePath: basePath,
76 | staticBase: path.join(basePath, "gitbook"),
77 | }, output);
78 | })
79 |
80 | // Copy assets
81 | .then(function() {
82 | return fs.copy(
83 | path.join(that.options.theme, "assets"),
84 | path.join(that.options.output, "gitbook")
85 | );
86 | });
87 | };
88 |
89 | module.exports = Generator;
--------------------------------------------------------------------------------
/bin/server.js:
--------------------------------------------------------------------------------
1 | var Q = require('q');
2 | var _ = require('lodash');
3 |
4 | var events = require('events');
5 | var http = require('http');
6 | var send = require('send');
7 | var util = require('util');
8 | var url = require('url');
9 |
10 | var Server = function() {
11 | this.running = null;
12 | this.dir = null;
13 | this.port = 0;
14 | this.sockets = [];
15 | };
16 | util.inherits(Server, events.EventEmitter);
17 |
18 | // Return true if the server is running
19 | Server.prototype.isRunning = function() {
20 | return this.running != null;
21 | };
22 |
23 | // Stop the server
24 | Server.prototype.stop = function() {
25 | var that = this;
26 | if (!this.isRunning()) return Q();
27 |
28 | var d = Q.defer();
29 | this.running.close(function(err) {
30 | that.running = null;
31 | that.emit("state", false);
32 |
33 | if (err) d.reject(err);
34 | else d.resolve();
35 | });
36 |
37 | for (var i = 0; i < this.sockets.length; i++) {
38 | this.sockets[i].destroy();
39 | }
40 |
41 | return d.promise;
42 | };
43 |
44 | Server.prototype.start = function(dir, port) {
45 | var that = this, pre = Q();
46 | port = port || 8004;
47 |
48 | if (that.isRunning()) pre = this.stop();
49 | return pre
50 | .then(function() {
51 | var d = Q.defer();
52 |
53 | that.running = http.createServer(function(req, res){
54 | // Render error
55 | function error(err) {
56 | res.statusCode = err.status || 500;
57 | res.end(err.message);
58 | }
59 |
60 | // Redirect to directory's index.html
61 | function redirect() {
62 | res.statusCode = 301;
63 | res.setHeader('Location', req.url + '/');
64 | res.end('Redirecting to ' + req.url + '/');
65 | }
66 |
67 | // Send file
68 | send(req, url.parse(req.url).pathname)
69 | .root(dir)
70 | .on('error', error)
71 | .on('directory', redirect)
72 | .pipe(res);
73 | });
74 |
75 | that.running.on('connection', function (socket) {
76 | that.sockets.push(socket);
77 | socket.setTimeout(4000);
78 | socket.on('close', function () {
79 | that.sockets.splice(that.sockets.indexOf(socket), 1);
80 | });
81 | });
82 |
83 | that.running.listen(port, function(err) {
84 | if (err) return d.reject(err);
85 |
86 | that.port = port;
87 | that.dir = dir;
88 | that.emit("state", true);
89 | d.resolve();
90 | });
91 |
92 | return d.promise;
93 | });
94 | }
95 |
96 | module.exports = Server;
97 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/scaffolding.less:
--------------------------------------------------------------------------------
1 | //
2 | // Scaffolding
3 | // --------------------------------------------------
4 |
5 |
6 | // Reset the box-sizing
7 |
8 | *,
9 | *:before,
10 | *:after {
11 | .box-sizing(border-box);
12 | }
13 |
14 |
15 | // Body reset
16 |
17 | html {
18 | font-size: 62.5%;
19 | -webkit-tap-highlight-color: rgba(0,0,0,0);
20 | }
21 |
22 | body {
23 | font-family: @font-family-base;
24 | font-size: @font-size-base;
25 | line-height: @line-height-base;
26 | color: @text-color;
27 | background-color: @body-bg;
28 | }
29 |
30 | // Reset fonts for relevant elements
31 | input,
32 | button,
33 | select,
34 | textarea {
35 | font-family: inherit;
36 | font-size: inherit;
37 | line-height: inherit;
38 | }
39 |
40 | // Reset unusual Firefox-on-Android default style.
41 | //
42 | // See https://github.com/necolas/normalize.css/issues/214
43 |
44 | button,
45 | input,
46 | select[multiple],
47 | textarea {
48 | background-image: none;
49 | }
50 |
51 |
52 | // Links
53 |
54 | a {
55 | color: @link-color;
56 | text-decoration: none;
57 |
58 | &:hover,
59 | &:focus {
60 | color: @link-hover-color;
61 | text-decoration: underline;
62 | }
63 |
64 | &:focus {
65 | .tab-focus();
66 | }
67 | }
68 |
69 |
70 | // Images
71 |
72 | img {
73 | vertical-align: middle;
74 | }
75 |
76 | // Responsive images (ensure images don't scale beyond their parents)
77 | .img-responsive {
78 | .img-responsive();
79 | }
80 |
81 | // Rounded corners
82 | .img-rounded {
83 | border-radius: @border-radius-large;
84 | }
85 |
86 | // Image thumbnails
87 | //
88 | // Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.
89 | .img-thumbnail {
90 | padding: @thumbnail-padding;
91 | line-height: @line-height-base;
92 | background-color: @thumbnail-bg;
93 | border: 1px solid @thumbnail-border;
94 | border-radius: @thumbnail-border-radius;
95 | .transition(all .2s ease-in-out);
96 |
97 | // Keep them at most 100% wide
98 | .img-responsive(inline-block);
99 | }
100 |
101 | // Perfect circle
102 | .img-circle {
103 | border-radius: 50%; // set radius in percents
104 | }
105 |
106 |
107 | // Horizontal rules
108 |
109 | hr {
110 | margin-top: @line-height-computed;
111 | margin-bottom: @line-height-computed;
112 | border: 0;
113 | border-top: 1px solid @hr-border;
114 | }
115 |
116 |
117 | // Only display content to screen readers
118 | //
119 | // See: http://a11yproject.com/posts/how-to-hide-content/
120 |
121 | .sr-only {
122 | position: absolute;
123 | width: 1px;
124 | height: 1px;
125 | margin: -1px;
126 | padding: 0;
127 | overflow: hidden;
128 | clip: rect(0 0 0 0);
129 | border: 0;
130 | }
131 |
--------------------------------------------------------------------------------
/theme/stylesheets/print.less:
--------------------------------------------------------------------------------
1 | @import "vendors/bootstrap/bootstrap.less";
2 | @import "vendors/fontawesome/font-awesome.less";
3 |
4 | @import "mixins.less";
5 | @import "variables.less";
6 | @import "fonts.less";
7 |
8 | @import "page/highlight.less";
9 |
10 |
11 | @font-size-base: 13px;
12 |
13 |
14 | * {
15 | -webkit-overflow-scrolling: touch;
16 | -webkit-tap-highlight-color: transparent;
17 | -webkit-text-size-adjust: none;
18 | -webkit-touch-callout: none;
19 | -webkit-font-smoothing: antialiased;
20 | }
21 |
22 | html, body {
23 | height: 100%;
24 | }
25 |
26 | body {
27 | text-rendering: optimizeLegibility;
28 | font-smoothing: antialiased;
29 | font-family: @font-family-base;
30 | }
31 |
32 |
33 | h1, h2, h3 {
34 | page-break-after: avoid;
35 | page-break-before: auto;
36 | }
37 |
38 | h1 { font-size: floor(@font-size-base * 2.15); }
39 | h2 { font-size: floor(@font-size-base * 1.70); }
40 | h3 { font-size: ceil(@font-size-base * 1.25); }
41 | h4 { font-size: ceil(@font-size-base * 1); }
42 | h5 { font-size: ceil(@font-size-base * 0.85); }
43 | h6 { font-size: ceil(@font-size-base * 0.65); }
44 |
45 | pre, blockquote {
46 | border: 1px solid #999;
47 | page-break-inside: avoid;
48 | }
49 |
50 | img {
51 | max-width: 100% !important;
52 | page-break-inside: avoid;
53 | }
54 |
55 | section {
56 | page-break-after: always;
57 |
58 | cover {
59 | padding: 3cm 0cm;
60 | text-align: center;
61 |
62 | h1 {
63 | font-size: 1.5cm;
64 | }
65 | }
66 |
67 | summary {
68 | margin: 1.5cm;
69 |
70 | h1 {
71 | text-align: center;
72 | }
73 |
74 | ol {
75 | list-style: none;
76 | padding: 0px;
77 | margin: 0px;
78 | margin-left: 1cm;
79 | }
80 |
81 | > ol {
82 | > li {
83 | }
84 | }
85 | }
86 |
87 | article {
88 | margin-bottom: 1.5cm;
89 |
90 | &.new-chapter {
91 | page-break-after: always;
92 | font-size: 0.6cm;
93 | text-align: center;
94 | padding: 3cm 0cm;
95 |
96 | h1 {
97 | font-size: floor(@font-size-base * 2.7);
98 | }
99 | }
100 |
101 | .exercise {
102 | margin: 1cm 0cm;
103 | padding: 0.4cm;
104 | page-break-inside: avoid;
105 |
106 | border: 3px solid #ddd;
107 |
108 | .exercise-header {
109 | margin-bottom: 0.4cm;
110 | padding-bottom: 0.2cm;
111 | border-bottom: 1px solid #ddd;
112 | }
113 | }
114 | }
115 | }
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | var path = require("path");
3 |
4 | // Load NPM tasks
5 | grunt.loadNpmTasks('grunt-contrib-less');
6 | grunt.loadNpmTasks('grunt-contrib-requirejs');
7 | grunt.loadNpmTasks("grunt-bower-install-simple");
8 |
9 | // Init GRUNT configuraton
10 | grunt.initConfig({
11 | pkg: grunt.file.readJSON('package.json'),
12 | 'bower-install-simple': {
13 | options: {
14 | color: true,
15 | production: false,
16 | directory: "theme/javascript/vendors"
17 | }
18 | },
19 | less: {
20 | development: {
21 | options: {
22 | compress: true,
23 | yuicompress: true,
24 | optimization: 2
25 | },
26 | files: {
27 | "theme/assets/style.css": "theme/stylesheets/main.less",
28 | "theme/assets/print.css": "theme/stylesheets/print.less"
29 | }
30 | }
31 | },
32 | requirejs: {
33 | compile: {
34 | options: {
35 | name: "gitbook",
36 | baseUrl: "theme/javascript/",
37 | out: "theme/assets/app.js",
38 | preserveLicenseComments: false,
39 | optimize: "uglify", //"uglify",
40 | include: ["requireLib"],
41 | paths: {
42 | "jQuery": 'vendors/jquery/dist/jquery',
43 | "lodash": 'vendors/lodash/dist/lodash',
44 | "requireLib": 'vendors/requirejs/require',
45 | "Mousetrap": 'vendors/mousetrap/mousetrap',
46 | "lunr": 'vendors/lunr.js/lunr',
47 | "URI": 'vendors/URIjs/src/URI'
48 | },
49 | shim: {
50 | 'jQuery': {
51 | exports: '$'
52 | },
53 | 'lodash': {
54 | exports: '_'
55 | },
56 | 'Mousetrap': {
57 | exports: 'Mousetrap'
58 | },
59 | 'lunr': {
60 | exports: 'lunr'
61 | }
62 | }
63 | }
64 | }
65 | }
66 | });
67 |
68 | grunt.registerTask("bower-install", [ "bower-install-simple" ]);
69 |
70 | // Build
71 | grunt.registerTask('build', [
72 | 'bower-install',
73 | 'less',
74 | 'requirejs'
75 | ]);
76 |
77 | grunt.registerTask('default', [
78 | 'build'
79 | ]);
80 | };
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/tooltip.less:
--------------------------------------------------------------------------------
1 | //
2 | // Tooltips
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | .tooltip {
8 | position: absolute;
9 | z-index: @zindex-tooltip;
10 | display: block;
11 | visibility: visible;
12 | font-size: @font-size-small;
13 | line-height: 1.4;
14 | .opacity(0);
15 |
16 | &.in { .opacity(.9); }
17 | &.top { margin-top: -3px; padding: 5px 0; }
18 | &.right { margin-left: 3px; padding: 0 5px; }
19 | &.bottom { margin-top: 3px; padding: 5px 0; }
20 | &.left { margin-left: -3px; padding: 0 5px; }
21 | }
22 |
23 | // Wrapper for the tooltip content
24 | .tooltip-inner {
25 | max-width: @tooltip-max-width;
26 | padding: 3px 8px;
27 | color: @tooltip-color;
28 | text-align: center;
29 | text-decoration: none;
30 | background-color: @tooltip-bg;
31 | border-radius: @border-radius-base;
32 | }
33 |
34 | // Arrows
35 | .tooltip-arrow {
36 | position: absolute;
37 | width: 0;
38 | height: 0;
39 | border-color: transparent;
40 | border-style: solid;
41 | }
42 | .tooltip {
43 | &.top .tooltip-arrow {
44 | bottom: 0;
45 | left: 50%;
46 | margin-left: -@tooltip-arrow-width;
47 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
48 | border-top-color: @tooltip-arrow-color;
49 | }
50 | &.top-left .tooltip-arrow {
51 | bottom: 0;
52 | left: 5px;
53 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
54 | border-top-color: @tooltip-arrow-color;
55 | }
56 | &.top-right .tooltip-arrow {
57 | bottom: 0;
58 | right: 5px;
59 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
60 | border-top-color: @tooltip-arrow-color;
61 | }
62 | &.right .tooltip-arrow {
63 | top: 50%;
64 | left: 0;
65 | margin-top: -@tooltip-arrow-width;
66 | border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;
67 | border-right-color: @tooltip-arrow-color;
68 | }
69 | &.left .tooltip-arrow {
70 | top: 50%;
71 | right: 0;
72 | margin-top: -@tooltip-arrow-width;
73 | border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;
74 | border-left-color: @tooltip-arrow-color;
75 | }
76 | &.bottom .tooltip-arrow {
77 | top: 0;
78 | left: 50%;
79 | margin-left: -@tooltip-arrow-width;
80 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
81 | border-bottom-color: @tooltip-arrow-color;
82 | }
83 | &.bottom-left .tooltip-arrow {
84 | top: 0;
85 | left: 5px;
86 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
87 | border-bottom-color: @tooltip-arrow-color;
88 | }
89 | &.bottom-right .tooltip-arrow {
90 | top: 0;
91 | right: 5px;
92 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
93 | border-bottom-color: @tooltip-arrow-color;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/test/navigation.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var assert = require('assert');
4 |
5 | var summary = require('../').parse.summary;
6 | var navigation = require('../').parse.navigation;
7 |
8 |
9 | var CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/SUMMARY.md'), 'utf8');
10 | var LEXED = summary(CONTENT);
11 |
12 |
13 | describe('Summary navigation', function() {
14 | it('should provide next & prev entries for a file', function() {
15 | var nav = navigation(LEXED, [
16 | 'README.md',
17 | 'chapter-1/README.md',
18 | 'chapter-1/ARTICLE1.md',
19 | 'chapter-1/ARTICLE2.md',
20 | 'chapter-2/README.md',
21 | ]);
22 |
23 | // Make sure it found the files we gave it
24 | assert(nav['README.md']);
25 | assert(nav['chapter-1/README.md']);
26 | assert(nav['chapter-1/ARTICLE1.md']);
27 | assert(nav['chapter-1/ARTICLE2.md']);
28 | assert(nav['chapter-2/README.md']);
29 |
30 |
31 | assert.equal(nav['README.md'].prev, null);
32 | assert.equal(nav['README.md'].next.path, 'chapter-1/README.md');
33 |
34 | assert.equal(nav['chapter-1/README.md'].prev.path, 'README.md');
35 | assert.equal(nav['chapter-1/README.md'].next.path, 'chapter-1/ARTICLE1.md');
36 |
37 | assert.equal(nav['chapter-1/ARTICLE1.md'].prev.path, 'chapter-1/README.md');
38 | assert.equal(nav['chapter-1/ARTICLE1.md'].next.path, 'chapter-1/ARTICLE2.md');
39 |
40 | assert.equal(nav['chapter-1/ARTICLE2.md'].prev.path, 'chapter-1/ARTICLE1.md');
41 | assert.equal(nav['chapter-1/ARTICLE2.md'].next.path, 'chapter-2/README.md');
42 |
43 | assert.equal(nav['chapter-2/README.md'].prev.path, 'chapter-1/ARTICLE2.md');
44 | assert.equal(nav['chapter-2/README.md'].next.path, 'chapter-3/README.md');
45 | });
46 |
47 | it('should give full tree, when not limited', function() {
48 | var nav = navigation(LEXED);
49 |
50 | assert(nav['README.md']);
51 | assert(nav['chapter-1/README.md']);
52 | assert(nav['chapter-1/ARTICLE1.md']);
53 | assert(nav['chapter-1/ARTICLE2.md']);
54 | assert(nav['chapter-2/README.md']);
55 | assert(nav['chapter-3/README.md']);
56 | });
57 |
58 | it('should detect levels correctly', function() {
59 | var nav = navigation(LEXED);
60 |
61 | assert.equal(nav['README.md'].level, '0');
62 | assert.equal(nav['chapter-1/README.md'].level, '1');
63 | assert.equal(nav['chapter-1/ARTICLE1.md'].level, '1.1');
64 | assert.equal(nav['chapter-1/ARTICLE2.md'].level, '1.2');
65 | assert.equal(nav['chapter-2/README.md'].level, '2');
66 | assert.equal(nav['chapter-3/README.md'].level, '3');
67 | });
68 |
69 | it('should not accept null paths', function() {
70 | var nav = navigation(LEXED);
71 |
72 | assert(!nav[null]);
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/theme/javascript/core/search.js:
--------------------------------------------------------------------------------
1 | define([
2 | "jQuery",
3 | "lodash",
4 | "lunr",
5 | "utils/storage",
6 | "core/state",
7 | "core/sidebar"
8 | ], function($, _, lunr, storage, state, sidebar) {
9 | var index = null;
10 |
11 | // Use a specific idnex
12 | var useIndex = function(data) {
13 | index = lunr.Index.load(data);
14 | };
15 |
16 | // Load complete index
17 | var loadIndex = function() {
18 | $.getJSON(state.basePath+"/search_index.json")
19 | .then(useIndex);
20 | };
21 |
22 | // Search for a term
23 | var search = function(q) {
24 | if (!index) return;
25 | var results = _.chain(index.search(q))
26 | .map(function(result) {
27 | var parts = result.ref.split("#")
28 | return {
29 | path: parts[0],
30 | hash: parts[1]
31 | }
32 | })
33 | .value();
34 |
35 | return results;
36 | };
37 |
38 | // Toggle search bar
39 | var toggleSearch = function(_state) {
40 | if (state != null && isSearchOpen() == _state) return;
41 |
42 | var $searchInput = $(".book-search input");
43 | state.$book.toggleClass("with-search", _state);
44 |
45 | // If search bar is open: focus input
46 | if (isSearchOpen()) {
47 | sidebar.toggle(true);
48 | $searchInput.focus();
49 | } else {
50 | $searchInput.blur();
51 | $searchInput.val("");
52 | sidebar.filter(null);
53 | }
54 | };
55 |
56 | // Return true if search bar is open
57 | var isSearchOpen = function() {
58 | return state.$book.hasClass("with-search");
59 | };
60 |
61 |
62 | var init = function() {
63 | loadIndex();
64 |
65 | // Toggle search
66 | $(document).on("click", ".book-header .toggle-search", function(e) {
67 | e.preventDefault();
68 | toggleSearch();
69 | });
70 | };
71 |
72 | var prepare = function() {
73 | var $searchInput = $(".book-search input");
74 |
75 | $searchInput.keyup(function(e) {
76 | var key = (e.keyCode ? e.keyCode : e.which);
77 | var q = $(this).val();
78 |
79 | if (key == 27) {
80 | e.preventDefault();
81 | toggleSearch(false);
82 | return;
83 | }
84 | if (q.length == 0) {
85 | sidebar.filter(null);
86 | } else {
87 | var results = search(q);
88 | sidebar.filter(
89 | _.pluck(results, "path")
90 | );
91 | }
92 | });
93 | }
94 |
95 | return {
96 | init: init,
97 | search: search,
98 | toggle: toggleSearch,
99 | prepare: prepare
100 | };
101 | });
--------------------------------------------------------------------------------
/theme/templates/site.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 |
3 | {% block htmlTag %}manifest="{{ basePath }}/manifest.appcache"{% endblock %}
4 | {% block title %}{{ progress.current.title }}{% parent %}{% endblock %}
5 | {% block content %}
6 |
7 | {% include "includes/book/header.html" %}
8 | {% include "includes/book/summary.html" %}
9 |
10 |
11 |
12 | {% include "includes/book/progress.html" %}
13 |
14 |
15 | {% for section in content %}
16 |
17 | {% if section.type == "normal" %}
18 | {% autoescape false %}{{ section.content }}{% endautoescape %}
19 | {% elif section.type == "exercise" %}
20 | {% include "./includes/book/exercise.html" with {section: section} %}
21 | {% elif section.type == "quiz" %}
22 | {% include "./includes/book/quiz.html" with {section: section} %}
23 | {% endif %}
24 |
25 | {% endfor %}
26 |
27 |
28 |
29 |
30 | {% if progress.current.prev and progress.current.prev.path %}
31 |
32 | {% endif %}
33 | {% if progress.current.next and progress.current.next.path %}
34 |
35 | {% endif %}
36 |
37 |
38 | {% endblock %}
39 |
40 | {% block javascript %}
41 | {% parent %}
42 | {% for resource in plugins.resources.js %}
43 | {% if resource.url %}
44 |
45 | {% else %}
46 |
47 | {% endif %}
48 | {% endfor %}
49 |
55 | {% endblock %}
56 |
57 | {% block style %}
58 | {% parent %}
59 | {% for resource in plugins.resources.css %}
60 | {% if resource.url %}
61 |
62 | {% else %}
63 |
64 | {% endif %}
65 | {% endfor %}
66 | {% endblock %}
67 |
--------------------------------------------------------------------------------
/theme/stylesheets/book/header.less:
--------------------------------------------------------------------------------
1 | .book {
2 | .book-header {
3 | font-family: @font-family-sans;
4 |
5 | position: absolute;
6 | overflow: visible;
7 | top: 0px;
8 | right: 0px;
9 | left: 0px;
10 | height: @header-height;
11 | z-index: 2;
12 |
13 | font-size: 0.85em;
14 | color: @header-color;
15 | background: @header-background;
16 | box-shadow: 0 1px 2px hsla(200,10%,80%,0.6);
17 |
18 | .btn {
19 | display: block;
20 | height: @header-height;
21 | padding: 0px 15px;
22 | border-bottom: none;
23 | color: @header-button-color;
24 | text-transform: uppercase;
25 | line-height: @header-height;
26 | .box-shadow(none) !important;
27 | position:relative;
28 |
29 | &:hover {
30 | position: relative;
31 | text-decoration: none;
32 | color: @header-button-hover-color;
33 | background: @header-button-hover-background;
34 | }
35 | }
36 |
37 | h1 {
38 | margin: 0px;
39 | font-size: 20px;
40 | text-align: center;
41 | line-height: @header-height;
42 |
43 | padding-left: 200px;
44 | padding-right: 200px;
45 | .transition(margin-left 0.5s ease);
46 |
47 | a, a:hover {
48 | color: inherit;
49 | text-decoration: none;
50 | }
51 |
52 | @media (max-width: 800px) {
53 | display: none;
54 | }
55 |
56 | i {
57 | display: none;
58 | }
59 | }
60 | }
61 |
62 | &.is-loading {
63 | .book-header h1 {
64 | i {
65 | display: inline-block;
66 | }
67 | a {
68 | display: none;
69 | }
70 | }
71 | }
72 | &.with-summary {
73 | .book-header h1 {
74 | margin-left: 250px;
75 | }
76 | }
77 | &.without-animation {
78 | .book-header h1 {
79 | .transition(none) !important;
80 | }
81 | }
82 | &.color-theme-1{
83 | .book-header{
84 | color: @header-color-1;
85 | background: @header-background-1;
86 | .btn {
87 | color: @header-button-color-1;
88 | &:hover {
89 | color: @header-button-hover-color-1;
90 | background: @header-button-hover-background-1;
91 | }
92 | }
93 | }
94 | }
95 | &.color-theme-2{
96 | .book-header{
97 | color: @header-color-2;
98 | background: @header-background-2;
99 | .btn {
100 | color: @header-button-color-2;
101 | &:hover {
102 | color: @header-button-hover-color-2;
103 | background: @header-button-hover-background-2;
104 | }
105 | }
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/theme/templates/includes/book/summary.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {% if options.links.about !== false && (options.links.about != null || githubId) %}
7 | -
8 | About the author
9 |
10 | {% endif %}
11 |
12 | {% if options.links.issues !== false && (options.links.issues != null || githubId) %}
13 | -
14 | Questions and Issues
15 |
16 | {% endif %}
17 |
18 | {% if options.links.contribute !== false && (options.links.contribute != null || githubId) %}
19 | -
20 | Edit and Contribute
21 |
22 | {% endif %}
23 |
24 | {% if options.links.contribute || options.links.issues || options.links.about %}
25 |
26 | {% endif %}
27 |
28 | -
29 | Introduction
30 |
31 | {% for item in summary.chapters %}
32 | -
33 | {% if item.path %}
34 |
35 | {{ item.level }}. {{ item.title }}
36 |
37 | {% else %}
38 | {{ item.level }}. {{ item.title }}
39 | {% endif %}
40 | {% if item.articles.length > 0 %}
41 |
42 | {% for article in item.articles %}
43 | -
44 | {% if article.path %}
45 |
46 | {{ article.level }}. {{ article.title }}
47 |
48 | {% else %}
49 | {{ article.level }}. {{ article.title }}
50 | {% endif %}
51 |
52 | {% endfor %}
53 |
54 | {% endif %}
55 |
56 | {% endfor %}
57 |
58 | {% if options.links.gitbook !== false %}
59 |
60 | -
61 | Generated using GitBook
62 |
63 | {% endif %}
64 |
65 |
66 |
--------------------------------------------------------------------------------
/bin/gitbook.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | var Q = require('q');
4 | var _ = require('lodash');
5 | var path = require('path');
6 | var prog = require('commander');
7 |
8 | var pkg = require('../package.json');
9 | var generators = require("../lib/generate").generators;
10 | var initDir = require("../lib/generate/init");
11 | var fs = require('../lib/generate/fs');
12 |
13 | var utils = require('./utils');
14 | var build = require('./build');
15 | var Server = require('./server');
16 |
17 | // General options
18 | prog
19 | .version(pkg.version);
20 |
21 | build.command(prog.command('build [source_dir]'))
22 | .description('Build a gitbook from a directory')
23 | .action(build.folder);
24 |
25 | build.command(prog.command('serve [source_dir]'))
26 | .description('Build then serve a gitbook from a directory')
27 | .option('-p, --port
', 'Port for server to listen on', 4000)
28 | .option('--no-watch', 'Disable restart with file watching')
29 | .action(function(dir, options) {
30 | var server = new Server();
31 |
32 | var generate = function() {
33 | if (server.isRunning()) console.log("Stopping server");
34 |
35 | server.stop()
36 | .then(function() {
37 | return build.folder(dir, options);
38 | })
39 | .then(function(_options) {
40 | console.log();
41 | console.log('Starting server ...');
42 | return server.start(_options.output, options.port)
43 | .then(function() {
44 | console.log('Serving book on http://localhost:'+options.port);
45 |
46 | if (!options.watch) return;
47 | return utils.watch(_options.input)
48 | .then(function(filepath) {
49 | console.log("Restart after change in files");
50 | console.log('');
51 | return generate();
52 | })
53 | })
54 | })
55 | .fail(utils.logError);
56 | };
57 |
58 | console.log('Press CTRL+C to quit ...');
59 | console.log('')
60 | generate();
61 | });
62 |
63 | build.command(prog.command('pdf [source_dir]'))
64 | .description('Build a gitbook as a PDF')
65 | .option('-pf, --paperformat ', 'PDF paper format (default is A4): "5in*7.5in", "10cm*20cm", "A4", "Letter"')
66 | .action(function(dir, options) {
67 | build.file(dir, _.extend(options, {
68 | extension: "pdf",
69 | format: "pdf"
70 | }));
71 | });
72 |
73 | build.command(prog.command('ebook [source_dir]'))
74 | .description('Build a gitbook as a eBook')
75 | .option('-c, --cover ', 'Cover image, default is cover.png if exists')
76 | .action(function(dir, options) {
77 | var ext = options.output ? path.extname(options.output) : "epub";
78 |
79 | build.file(dir, _.extend(options, {
80 | extension: ext,
81 | format: "ebook"
82 | }));
83 | });
84 |
85 | prog
86 | .command('init [source_dir]')
87 | .description('Create files and folders based on contents of SUMMARY.md')
88 | .action(function(dir) {
89 | dir = dir || process.cwd();
90 | return initDir(dir);
91 | });
92 |
93 | // Parse and fallback to help if no args
94 | if(_.isEmpty(prog.parse(process.argv).args) && process.argv.length === 2) {
95 | prog.help();
96 | }
97 |
--------------------------------------------------------------------------------
/theme/javascript/utils/execute.js:
--------------------------------------------------------------------------------
1 | define([
2 | "execute/javascript"
3 | ], function(javascript) {
4 | var LANGUAGES = {
5 | "javascript": javascript
6 | };
7 |
8 |
9 | var evalJS = function(lang, code, callback) {
10 | var ready = false;
11 | var finished = false;
12 |
13 | var finish = function() {
14 | if(finished) {
15 | return console.error('Already finished');
16 | }
17 | finished = true;
18 | return callback.apply(null, arguments);
19 | };
20 |
21 | var repl;
22 |
23 | // Handles all our events
24 | var eventHandler = function(data, eventType) {
25 | console.log([eventType, data]);
26 | switch(eventType) {
27 | case 'progress':
28 | // Update UI loading bar
29 | break;
30 | case 'timeout':
31 | finish(new Error(data));
32 | break;
33 | case 'result':
34 | finish(null, {
35 | value: data,
36 | type: 'result'
37 | });
38 | break;
39 | case 'error':
40 | if(ready) {
41 | return finish(null, {
42 | value: data,
43 | type: 'error'
44 | });
45 | }
46 | return finish(new Error(data));
47 | break
48 | case 'ready':
49 | // We're good to get results and stuff back now
50 | ready = true;
51 | // Eval our code now that the runtime is ready
52 | repl.eval(code);
53 | break;
54 | default:
55 | console.log('Unhandled event =', eventType, 'data =', data);
56 | }
57 | };
58 |
59 | repl = new lang.REPL({
60 | input: eventHandler,
61 | output: eventHandler,
62 | result: eventHandler,
63 | error: eventHandler,
64 | progress: eventHandler,
65 | timeout: {
66 | time: 30000,
67 | callback: eventHandler
68 | }
69 | });
70 |
71 | repl.loadLanguage(lang.id, eventHandler);
72 | };
73 |
74 | var execute = function(lang, solution, validation, context, callback) {
75 | // Language data
76 | var langd = LANGUAGES[lang];
77 |
78 | // Check language is supported
79 | if (!langd) return callback(new Error("Language '"+lang+"' not available for execution"));
80 |
81 | // Validate with validation code
82 | var code = [
83 | context,
84 | solution,
85 | langd.assertCode,
86 | validation,
87 | ].join(langd.sep);
88 | evalJS(langd, code, function(err, res) {
89 | if(err) return callback(err);
90 |
91 | if (res.type == "error") callback(new Error(res.value));
92 | else callback(null, res.value);
93 | });
94 | };
95 |
96 | return execute;
97 | });
98 |
--------------------------------------------------------------------------------
/theme/stylesheets/book/progress.less:
--------------------------------------------------------------------------------
1 | /* Chrome, Safari, Opera */
2 | @-webkit-keyframes animate-loading {
3 | from {width: 0%;}
4 | to {}
5 | }
6 |
7 | /* Standard syntax */
8 | @keyframes animate-loading {
9 | from {width: 0%;}
10 | to {}
11 | }
12 |
13 | .book .book-body {
14 | .book-progress {
15 | height: @progress-height;
16 | width: 100%;
17 | position: relative;
18 | background: #fff;
19 | margin-bottom: 10px;
20 |
21 | .bar {
22 | height: @bar-height;
23 | position: @bar-position;
24 | right: @bar-right;
25 | left: @bar-left;
26 | top: @bar-top;
27 |
28 | background: @bar-background;
29 | border-radius: 5px;
30 | overflow: hidden;
31 |
32 | .inner {
33 | height: 100%;
34 | width: 0%;
35 |
36 | background: @bar-progress-background;
37 | -webkit-animation: animate-loading 1s; /* Chrome, Safari, Opera */
38 | animation: animate-loading 1s;
39 |
40 | .in-inner {
41 | height: 100%;
42 | width: 50%;
43 | }
44 | }
45 | }
46 |
47 | .chapters {
48 | display: @chapter-display;
49 |
50 | position: absolute;
51 | right: 20px + @chapter-size;
52 | left: 20px;
53 | top: 7px;
54 |
55 | .chapter {
56 | position: absolute;
57 | width: @chapter-size;
58 | height: @chapter-size;
59 | border-radius: @chapter-size;
60 |
61 | background: @bar-background;
62 | box-shadow: 0px 0px 1px #bbb;
63 |
64 | &.new-chapter {
65 |
66 | }
67 |
68 | &.done {
69 | background: @bar-progress-background;
70 | box-shadow: none;
71 | }
72 |
73 | @media (max-width: 800px) {
74 | display: none;
75 |
76 | &.new-chapter {
77 | display: block;
78 | }
79 | }
80 | }
81 | }
82 | }
83 | }
84 | .book.color-theme-1 .book-body {
85 | .book-progress {
86 | .bar {
87 | background: @bar-background-1;
88 | .inner {
89 | background: @bar-progress-background-1;
90 | }
91 | }
92 | }
93 | .chapters .chapter{
94 | background: @bar-background-1;
95 | &.done{
96 | background: @bar-progress-background-1;
97 | }
98 | }
99 | }
100 | .book.color-theme-2 .book-body {
101 | .book-progress {
102 | .bar {
103 | background: @bar-background-2;
104 | .inner {
105 | background: @bar-progress-background-2;
106 | }
107 | }
108 | }
109 | .chapters .chapter{
110 | background: @bar-background-2;
111 | &.done{
112 | background: @bar-progress-background-2;
113 | }
114 | }
115 | }
--------------------------------------------------------------------------------
/lib/parse/summary.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var marked = require('marked');
3 |
4 |
5 | // Utility function for splitting a list into groups
6 | function splitBy(list, starter, ender) {
7 | var starts = 0;
8 | var ends = 0;
9 | var group = [];
10 |
11 | // Groups
12 | return _.reduce(list, function(groups, value) {
13 | // Ignore start and end delimiters in resulted groups
14 | if(starter(value)) {
15 | starts++;
16 | } else if(ender(value)) {
17 | ends++;
18 | }
19 |
20 | // Add current value to group
21 | group.push(value);
22 |
23 | // We've got a matching
24 | if(starts === ends && starts !== 0) {
25 | // Add group to end groups
26 | // (remove starter and ender token)
27 | groups.push(group.slice(1, -1));
28 |
29 | // Reset group
30 | group = [];
31 | }
32 |
33 | return groups;
34 | }, []);
35 | }
36 |
37 | function listSplit(nodes, start_type, end_type) {
38 | return splitBy(nodes, function(el) {
39 | return el.type === start_type;
40 | }, function(el) {
41 | return el.type === end_type;
42 | });
43 | }
44 |
45 | // Get the biggest list
46 | // out of a list of marked nodes
47 | function filterList(nodes) {
48 | return _.chain(nodes)
49 | .toArray()
50 | .rest(function(el) {
51 | // Get everything after list_start
52 | return el.type !== 'list_start';
53 | })
54 | .reverse()
55 | .rest(function(el) {
56 | // Get everything after list_end (remember we're reversed)
57 | return el.type !== 'list_end';
58 | })
59 | .reverse()
60 | .value().slice(1, -1);
61 | }
62 |
63 | // Parses an Article or Chapter title
64 | // supports extracting links
65 | function parseTitle(src, nums) {
66 | // Check if it's a link
67 | var matches = marked.InlineLexer.rules.link.exec(src);
68 |
69 | var level = nums.join('.');
70 |
71 | // Not a link, return plain text
72 | if(!matches) {
73 | return {
74 | title: src,
75 | level: level,
76 | path: null,
77 | };
78 | }
79 |
80 | return {
81 | title: matches[1],
82 | level: level,
83 |
84 | // Replace .md references with .html
85 | path: matches[2].replace(/\\/g, '/'),
86 | };
87 | }
88 |
89 | function parseArticle(chapterNum, nodes, idx) {
90 | return parseTitle(_.first(nodes).text, [chapterNum, idx+1]);
91 | }
92 |
93 | function parseChapter(nodes, idx) {
94 | return _.extend(parseTitle(_.first(nodes).text, [idx+1]), {
95 | articles: _.map(listSplit(filterList(nodes), 'list_item_start', 'list_item_end'), parseArticle.bind(null, idx+1))
96 | });
97 | }
98 |
99 | function parseSummary(src) {
100 | var nodes = marked.lexer(src);
101 |
102 | // Get out list of chapters
103 | var chapterList = filterList(nodes);
104 |
105 | // Split out chapter sections
106 | var chapters = _.chain(listSplit(chapterList, 'list_item_start', 'list_item_end'))
107 | .map(parseChapter)
108 | .value();
109 |
110 | return {
111 | chapters: chapters
112 | };
113 | }
114 |
115 |
116 | // Exports
117 | module.exports = parseSummary;
118 |
--------------------------------------------------------------------------------
/theme/stylesheets/book/body.less:
--------------------------------------------------------------------------------
1 | .book {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 |
6 | .book-body {
7 | position: absolute;
8 | top: @header-height;
9 | right: 0px;
10 | left: 0px;
11 | bottom: 0px;
12 |
13 | color: @page-color;
14 | background: @body-background;
15 | .transition(left 0.5s ease);
16 |
17 | .body-inner {
18 | position: absolute;
19 | top: 0px;
20 | right: 0px;
21 | left: 0px;
22 | bottom: 0px;
23 | overflow-y: auto;
24 | }
25 |
26 | .page-wrapper {
27 | position: relative;
28 | outline: none;
29 |
30 | .page-inner {
31 | max-width: 800px;
32 | margin: 0px auto;
33 |
34 | section {
35 | margin: 0px 0px;
36 | padding: 5px 15px;
37 |
38 | background: @page-background;
39 | border-radius: 2px;
40 | line-height: 1.5em;
41 | }
42 |
43 | .btn-group {
44 | .btn {
45 | border-radius: 0px;
46 | background: #eee;
47 | border: 0px;
48 | }
49 | }
50 | }
51 | }
52 |
53 | @media (max-width: @mobileMaxWidth) {
54 | overflow-y: auto;
55 |
56 | .body-inner {
57 | position: static;
58 | padding-bottom: 20px;
59 | min-height: calc(~"100% - 57px")
60 | }
61 | }
62 | &.font-size-0{
63 | font-size:@s-font-size;
64 | }
65 | &.font-size-1{
66 | font-size:@m-font-size;
67 | }
68 | &.font-size-2{
69 | font-size:@l-font-size;
70 | }
71 | &.font-size-3{
72 | font-size:@xl-font-size;
73 | }
74 | &.font-size-4{
75 | font-size:@xxl-font-size;
76 | }
77 |
78 | &.font-family-0{
79 | font-family: @font-family-serif;
80 | }
81 | &.font-family-1{
82 | font-family: @font-family-sans;
83 | }
84 | }
85 |
86 | &.with-summary {
87 | @media (min-width: 800px) {
88 | .book-body {
89 | left: 250px;
90 | }
91 | }
92 | }
93 |
94 | &.without-animation {
95 | .book-body {
96 | .transition(none) !important;
97 | }
98 | }
99 | &.color-theme-1{
100 | .book-body {
101 | color: @page-color-1;
102 | background: @body-background-1;
103 | .page-wrapper {
104 | .page-inner {
105 | section{
106 | background: @page-background-1;
107 | }
108 | }
109 | }
110 | }
111 | }
112 | &.color-theme-2{
113 | .book-body {
114 | color: @page-color-2;
115 | background: @body-background-2;
116 | .page-wrapper {
117 | .page-inner {
118 | section{
119 | background: @page-background-2;
120 | }
121 | }
122 | }
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/lib/parse/lex.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var marked = require('marked');
3 |
4 | // Split a page up into sections (lesson, exercises, ...)
5 | function splitSections(nodes) {
6 | var section = [];
7 |
8 | return _.reduce(nodes, function(sections, el) {
9 | if(el.type === 'hr') {
10 | sections.push(section);
11 | section = [];
12 | } else {
13 | section.push(el);
14 | }
15 |
16 | return sections;
17 | }, []).concat([section]); // Add remaining nodes
18 | }
19 |
20 | function isQuizNode(node) {
21 | return (/^[(\[][ x][)\]]/).test(node.text || node);
22 | }
23 |
24 | function isExercise(nodes) {
25 | var codeType = { type: 'code' };
26 |
27 | // Number of code nodes in section
28 | var len = _.filter(nodes, codeType).length;
29 |
30 | return (
31 | // Got 3 or 4 code blocks
32 | (len === 3 || len === 4) &&
33 | // Ensure all nodes are at the end
34 | _.all(_.last(nodes, len), codeType)
35 | );
36 | }
37 |
38 | function isQuiz(nodes) {
39 | if (nodes.length < 3) {
40 | return false;
41 | }
42 |
43 | // Support having a first paragraph block
44 | // before our series of questions
45 | var quizNodes = nodes.slice(nodes[0].type === 'paragraph' ? 1 : 0);
46 |
47 | // No questions
48 | if (!_.some(quizNodes, { type: 'blockquote_start' })) {
49 | return false;
50 | }
51 |
52 | // Check if section has list of questions
53 | // or table of questions
54 | var listIdx = _.findIndex(quizNodes, { type: 'list_item_start' });
55 | var tableIdx = _.findIndex(quizNodes, { type: 'table' });
56 |
57 | if(
58 | // List of questions
59 | listIdx !== -1 && isQuizNode(quizNodes[listIdx + 1]) ||
60 |
61 | // Table of questions
62 | (
63 | tableIdx !== -1 &&
64 | _.every(quizNodes[tableIdx].cells[0].slice(1), isQuizNode)
65 | )
66 | ) {
67 | return true;
68 | }
69 |
70 | return false;
71 | }
72 |
73 | // What is the type of this section
74 | function sectionType(nodes, idx) {
75 | if(isExercise(nodes)) {
76 | return 'exercise';
77 | } else if(isQuiz(nodes)) {
78 | return 'quiz';
79 | }
80 |
81 | return 'normal';
82 | }
83 |
84 | // Generate a uniqueId to identify this section in our code
85 | function sectionId(section, idx) {
86 | return _.uniqueId('gitbook_');
87 | }
88 |
89 | function lexPage(src) {
90 | // Lex file
91 | var nodes = marked.lexer(src);
92 |
93 | return _.chain(splitSections(nodes))
94 | .map(function(section, idx) {
95 | // Detect section type
96 | section.type = sectionType(section, idx);
97 | return section;
98 | })
99 | .map(function(section, idx) {
100 | // Give each section an ID
101 | section.id = sectionId(section, idx);
102 | return section;
103 |
104 | })
105 | .filter(function(section) {
106 | return !_.isEmpty(section);
107 | })
108 | .reduce(function(sections, section) {
109 | var last = _.last(sections);
110 |
111 | // Merge normal sections together
112 | if(last && last.type === section.type && last.type === 'normal') {
113 | last.push.apply(last, [{'type': 'hr'}].concat(section));
114 | } else {
115 | // Add to list of sections
116 | sections.push(section);
117 | }
118 |
119 | return sections;
120 | }, [])
121 | .value();
122 | }
123 |
124 | // Exports
125 | module.exports = lexPage;
126 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/input-groups.less:
--------------------------------------------------------------------------------
1 | //
2 | // Input groups
3 | // --------------------------------------------------
4 |
5 | // Base styles
6 | // -------------------------
7 | .input-group {
8 | position: relative; // For dropdowns
9 | display: table;
10 | border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table
11 |
12 | // Undo padding and float of grid classes
13 | &.col {
14 | float: none;
15 | padding-left: 0;
16 | padding-right: 0;
17 | }
18 |
19 | .form-control {
20 | width: 100%;
21 | margin-bottom: 0;
22 | }
23 | }
24 |
25 | // Sizing options
26 | //
27 | // Remix the default form control sizing classes into new ones for easier
28 | // manipulation.
29 |
30 | .input-group-lg > .form-control,
31 | .input-group-lg > .input-group-addon,
32 | .input-group-lg > .input-group-btn > .btn { .input-lg(); }
33 | .input-group-sm > .form-control,
34 | .input-group-sm > .input-group-addon,
35 | .input-group-sm > .input-group-btn > .btn { .input-sm(); }
36 |
37 |
38 | // Display as table-cell
39 | // -------------------------
40 | .input-group-addon,
41 | .input-group-btn,
42 | .input-group .form-control {
43 | display: table-cell;
44 |
45 | &:not(:first-child):not(:last-child) {
46 | border-radius: 0;
47 | }
48 | }
49 | // Addon and addon wrapper for buttons
50 | .input-group-addon,
51 | .input-group-btn {
52 | width: 1%;
53 | white-space: nowrap;
54 | vertical-align: middle; // Match the inputs
55 | }
56 |
57 | // Text input groups
58 | // -------------------------
59 | .input-group-addon {
60 | padding: @padding-base-vertical @padding-base-horizontal;
61 | font-size: @font-size-base;
62 | font-weight: normal;
63 | line-height: 1;
64 | text-align: center;
65 | background-color: @input-group-addon-bg;
66 | border: 1px solid @input-group-addon-border-color;
67 | border-radius: @border-radius-base;
68 |
69 | // Sizing
70 | &.input-sm {
71 | padding: @padding-small-vertical @padding-small-horizontal;
72 | font-size: @font-size-small;
73 | border-radius: @border-radius-small;
74 | }
75 | &.input-lg {
76 | padding: @padding-large-vertical @padding-large-horizontal;
77 | font-size: @font-size-large;
78 | border-radius: @border-radius-large;
79 | }
80 |
81 | // Nuke default margins from checkboxes and radios to vertically center within.
82 | input[type="radio"],
83 | input[type="checkbox"] {
84 | margin-top: 0;
85 | }
86 | }
87 |
88 | // Reset rounded corners
89 | .input-group .form-control:first-child,
90 | .input-group-addon:first-child,
91 | .input-group-btn:first-child > .btn,
92 | .input-group-btn:first-child > .dropdown-toggle,
93 | .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
94 | .border-right-radius(0);
95 | }
96 | .input-group-addon:first-child {
97 | border-right: 0;
98 | }
99 | .input-group .form-control:last-child,
100 | .input-group-addon:last-child,
101 | .input-group-btn:last-child > .btn,
102 | .input-group-btn:last-child > .dropdown-toggle,
103 | .input-group-btn:first-child > .btn:not(:first-child) {
104 | .border-left-radius(0);
105 | }
106 | .input-group-addon:last-child {
107 | border-left: 0;
108 | }
109 |
110 | // Button input groups
111 | // -------------------------
112 | .input-group-btn {
113 | position: relative;
114 | white-space: nowrap;
115 | }
116 | .input-group-btn > .btn {
117 | position: relative;
118 | // Jankily prevent input button groups from wrapping
119 | + .btn {
120 | margin-left: -4px;
121 | }
122 | // Bring the "active" button to the front
123 | &:hover,
124 | &:active {
125 | z-index: 2;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/popovers.less:
--------------------------------------------------------------------------------
1 | //
2 | // Popovers
3 | // --------------------------------------------------
4 |
5 |
6 | .popover {
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | z-index: @zindex-popover;
11 | display: none;
12 | max-width: @popover-max-width;
13 | padding: 1px;
14 | text-align: left; // Reset given new insertion method
15 | background-color: @popover-bg;
16 | background-clip: padding-box;
17 | border: 1px solid @popover-fallback-border-color;
18 | border: 1px solid @popover-border-color;
19 | border-radius: @border-radius-large;
20 | .box-shadow(0 5px 10px rgba(0,0,0,.2));
21 |
22 | // Overrides for proper insertion
23 | white-space: normal;
24 |
25 | // Offset the popover to account for the popover arrow
26 | &.top { margin-top: -10px; }
27 | &.right { margin-left: 10px; }
28 | &.bottom { margin-top: 10px; }
29 | &.left { margin-left: -10px; }
30 | }
31 |
32 | .popover-title {
33 | margin: 0; // reset heading margin
34 | padding: 8px 14px;
35 | font-size: @font-size-base;
36 | font-weight: normal;
37 | line-height: 18px;
38 | background-color: @popover-title-bg;
39 | border-bottom: 1px solid darken(@popover-title-bg, 5%);
40 | border-radius: 5px 5px 0 0;
41 | }
42 |
43 | .popover-content {
44 | padding: 9px 14px;
45 | }
46 |
47 | // Arrows
48 | //
49 | // .arrow is outer, .arrow:after is inner
50 |
51 | .popover .arrow {
52 | &,
53 | &:after {
54 | position: absolute;
55 | display: block;
56 | width: 0;
57 | height: 0;
58 | border-color: transparent;
59 | border-style: solid;
60 | }
61 | }
62 | .popover .arrow {
63 | border-width: @popover-arrow-outer-width;
64 | }
65 | .popover .arrow:after {
66 | border-width: @popover-arrow-width;
67 | content: "";
68 | }
69 |
70 | .popover {
71 | &.top .arrow {
72 | left: 50%;
73 | margin-left: -@popover-arrow-outer-width;
74 | border-bottom-width: 0;
75 | border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback
76 | border-top-color: @popover-arrow-outer-color;
77 | bottom: -@popover-arrow-outer-width;
78 | &:after {
79 | content: " ";
80 | bottom: 1px;
81 | margin-left: -@popover-arrow-width;
82 | border-bottom-width: 0;
83 | border-top-color: @popover-arrow-color;
84 | }
85 | }
86 | &.right .arrow {
87 | top: 50%;
88 | left: -@popover-arrow-outer-width;
89 | margin-top: -@popover-arrow-outer-width;
90 | border-left-width: 0;
91 | border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback
92 | border-right-color: @popover-arrow-outer-color;
93 | &:after {
94 | content: " ";
95 | left: 1px;
96 | bottom: -@popover-arrow-width;
97 | border-left-width: 0;
98 | border-right-color: @popover-arrow-color;
99 | }
100 | }
101 | &.bottom .arrow {
102 | left: 50%;
103 | margin-left: -@popover-arrow-outer-width;
104 | border-top-width: 0;
105 | border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback
106 | border-bottom-color: @popover-arrow-outer-color;
107 | top: -@popover-arrow-outer-width;
108 | &:after {
109 | content: " ";
110 | top: 1px;
111 | margin-left: -@popover-arrow-width;
112 | border-top-width: 0;
113 | border-bottom-color: @popover-arrow-color;
114 | }
115 | }
116 |
117 | &.left .arrow {
118 | top: 50%;
119 | right: -@popover-arrow-outer-width;
120 | margin-top: -@popover-arrow-outer-width;
121 | border-right-width: 0;
122 | border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback
123 | border-left-color: @popover-arrow-outer-color;
124 | &:after {
125 | content: " ";
126 | right: 1px;
127 | border-right-width: 0;
128 | border-left-color: @popover-arrow-color;
129 | bottom: -@popover-arrow-width;
130 | }
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/modals.less:
--------------------------------------------------------------------------------
1 | //
2 | // Modals
3 | // --------------------------------------------------
4 |
5 | // .modal-open - body class for killing the scroll
6 | // .modal - container to scroll within
7 | // .modal-dialog - positioning shell for the actual modal
8 | // .modal-content - actual modal w/ bg and corners and shit
9 |
10 | // Kill the scroll on the body
11 | .modal-open {
12 | overflow: hidden;
13 |
14 |
15 | // Account for hiding of scrollbar
16 | body&,
17 | .navbar-fixed-top,
18 | .navbar-fixed-bottom {
19 | margin-right: 15px
20 | }
21 | }
22 |
23 | // Container that the modal scrolls within
24 | .modal {
25 | display: none;
26 | overflow: auto;
27 | overflow-y: scroll;
28 | position: fixed;
29 | top: 0;
30 | right: 0;
31 | bottom: 0;
32 | left: 0;
33 | z-index: @zindex-modal-background;
34 |
35 | // When fading in the modal, animate it to slide down
36 | &.fade .modal-dialog {
37 | .translate(0, -25%);
38 | .transition-transform(~"0.3s ease-out");
39 | }
40 | &.in .modal-dialog { .translate(0, 0)}
41 | }
42 |
43 | // Shell div to position the modal with bottom padding
44 | .modal-dialog {
45 | margin-left: auto;
46 | margin-right: auto;
47 | width: auto;
48 | padding: 10px;
49 | z-index: (@zindex-modal-background + 10);
50 | }
51 |
52 | // Actual modal
53 | .modal-content {
54 | position: relative;
55 | background-color: @modal-content-bg;
56 | border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)
57 | border: 1px solid @modal-content-border-color;
58 | border-radius: @border-radius-large;
59 | .box-shadow(0 3px 9px rgba(0,0,0,.5));
60 | background-clip: padding-box;
61 | // Remove focus outline from opened modal
62 | outline: none;
63 | }
64 |
65 | // Modal background
66 | .modal-backdrop {
67 | position: fixed;
68 | top: 0;
69 | right: 0;
70 | bottom: 0;
71 | left: 0;
72 | z-index: (@zindex-modal-background - 10);
73 | background-color: @modal-backdrop-bg;
74 | // Fade for backdrop
75 | &.fade { .opacity(0); }
76 | &.in { .opacity(.5); }
77 | }
78 |
79 | // Modal header
80 | // Top section of the modal w/ title and dismiss
81 | .modal-header {
82 | padding: @modal-title-padding;
83 | border-bottom: 1px solid @modal-header-border-color;
84 | min-height: (@modal-title-padding + @modal-title-line-height);
85 | }
86 | // Close icon
87 | .modal-header .close {
88 | margin-top: -2px;
89 | }
90 |
91 | // Title text within header
92 | .modal-title {
93 | margin: 0;
94 | line-height: @modal-title-line-height;
95 | }
96 |
97 | // Modal body
98 | // Where all modal content resides (sibling of .modal-header and .modal-footer)
99 | .modal-body {
100 | position: relative;
101 | padding: @modal-inner-padding;
102 | }
103 |
104 | // Footer (for actions)
105 | .modal-footer {
106 | margin-top: 15px;
107 | padding: (@modal-inner-padding - 1) @modal-inner-padding @modal-inner-padding;
108 | text-align: right; // right align buttons
109 | border-top: 1px solid @modal-footer-border-color;
110 | .clearfix(); // clear it in case folks use .pull-* classes on buttons
111 |
112 | // Properly space out buttons
113 | .btn + .btn {
114 | margin-left: 5px;
115 | margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
116 | }
117 | // but override that for button groups
118 | .btn-group .btn + .btn {
119 | margin-left: -1px;
120 | }
121 | // and override it for block buttons as well
122 | .btn-block + .btn-block {
123 | margin-left: 0;
124 | }
125 | }
126 |
127 | // Scale up the modal
128 | @media screen and (min-width: @screen-tablet) {
129 |
130 | .modal-dialog {
131 | left: 50%;
132 | right: auto;
133 | width: 600px;
134 | padding-top: 30px;
135 | padding-bottom: 30px;
136 | }
137 | .modal-content {
138 | .box-shadow(0 5px 15px rgba(0,0,0,.5));
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/buttons.less:
--------------------------------------------------------------------------------
1 | //
2 | // Buttons
3 | // --------------------------------------------------
4 |
5 |
6 | // Base styles
7 | // --------------------------------------------------
8 |
9 | // Core styles
10 | .btn {
11 | display: inline-block;
12 | padding: @padding-base-vertical @padding-base-horizontal;
13 | margin-bottom: 0; // For input.btn
14 | font-size: @font-size-base;
15 | font-weight: @btn-font-weight;
16 | line-height: @line-height-base;
17 | text-align: center;
18 | vertical-align: middle;
19 | cursor: pointer;
20 | border: 1px solid transparent;
21 | border-radius: @border-radius-base;
22 | white-space: nowrap;
23 | .user-select(none);
24 |
25 | &:focus {
26 | .tab-focus();
27 | }
28 |
29 | &:hover,
30 | &:focus {
31 | color: @btn-default-color;
32 | text-decoration: none;
33 | }
34 |
35 | &:active,
36 | &.active {
37 | outline: 0;
38 | background-image: none;
39 | .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
40 | }
41 |
42 | &.disabled,
43 | &[disabled],
44 | fieldset[disabled] & {
45 | cursor: not-allowed;
46 | pointer-events: none; // Future-proof disabling of clicks
47 | .opacity(.65);
48 | .box-shadow(none);
49 | }
50 |
51 | }
52 |
53 |
54 | // Alternate buttons
55 | // --------------------------------------------------
56 |
57 | .btn-default {
58 | .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
59 | }
60 | .btn-primary {
61 | .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
62 | }
63 | // Warning appears as orange
64 | .btn-warning {
65 | .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);
66 | }
67 | // Danger and error appear as red
68 | .btn-danger {
69 | .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);
70 | }
71 | // Success appears as green
72 | .btn-success {
73 | .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);
74 | }
75 | // Info appears as blue-green
76 | .btn-info {
77 | .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);
78 | }
79 |
80 |
81 | // Link buttons
82 | // -------------------------
83 |
84 | // Make a button look and behave like a link
85 | .btn-link {
86 | color: @link-color;
87 | font-weight: normal;
88 | cursor: pointer;
89 | border-radius: 0;
90 |
91 | &,
92 | &:active,
93 | &[disabled],
94 | fieldset[disabled] & {
95 | background-color: transparent;
96 | .box-shadow(none);
97 | }
98 | &,
99 | &:hover,
100 | &:focus,
101 | &:active {
102 | border-color: transparent;
103 | }
104 | &:hover,
105 | &:focus {
106 | color: @link-hover-color;
107 | text-decoration: underline;
108 | background-color: transparent;
109 | }
110 | &[disabled],
111 | fieldset[disabled] & {
112 | &:hover,
113 | &:focus {
114 | color: @btn-link-disabled-color;
115 | text-decoration: none;
116 | }
117 | }
118 | }
119 |
120 |
121 | // Button Sizes
122 | // --------------------------------------------------
123 |
124 | .btn-lg {
125 | // line-height: ensure even-numbered height of button next to large input
126 | .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);
127 | }
128 | .btn-sm,
129 | .btn-xs {
130 | // line-height: ensure proper height of button next to small input
131 | .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);
132 | }
133 | .btn-xs {
134 | padding: 1px 5px;
135 | }
136 |
137 |
138 | // Block button
139 | // --------------------------------------------------
140 |
141 | .btn-block {
142 | display: block;
143 | width: 100%;
144 | padding-left: 0;
145 | padding-right: 0;
146 | }
147 |
148 | // Vertically space out multiple block buttons
149 | .btn-block + .btn-block {
150 | margin-top: 5px;
151 | }
152 |
153 | // Specificity overrides
154 | input[type="submit"],
155 | input[type="reset"],
156 | input[type="button"] {
157 | &.btn-block {
158 | width: 100%;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/panels.less:
--------------------------------------------------------------------------------
1 | //
2 | // Panels
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | .panel {
8 | margin-bottom: @line-height-computed;
9 | background-color: @panel-bg;
10 | border: 1px solid transparent;
11 | border-radius: @panel-border-radius;
12 | .box-shadow(0 1px 1px rgba(0,0,0,.05));
13 | }
14 |
15 | // Panel contents
16 | .panel-body {
17 | padding: 15px;
18 | .clearfix();
19 | }
20 |
21 |
22 | // List groups in panels
23 | //
24 | // By default, space out list group content from panel headings to account for
25 | // any kind of custom content between the two.
26 |
27 | .panel {
28 | > .list-group {
29 | margin-bottom: 0;
30 |
31 | .list-group-item {
32 | border-width: 1px 0;
33 |
34 | // Remove border radius for top one
35 | &:first-child {
36 | .border-top-radius(0);
37 | }
38 | // But keep it for the last one
39 | &:last-child {
40 | border-bottom: 0;
41 | }
42 | }
43 | }
44 | }
45 | // Collapse space between when there's no additional content.
46 | .panel-heading + .list-group {
47 | .list-group-item:first-child {
48 | border-top-width: 0;
49 | }
50 | }
51 |
52 |
53 | // Tables in panels
54 | //
55 | // Place a non-bordered `.table` within a panel (not within a `.panel-body`) and
56 | // watch it go full width.
57 |
58 | .panel {
59 | > .table {
60 | margin-bottom: 0;
61 | }
62 | > .panel-body + .table {
63 | border-top: 1px solid @table-border-color;
64 | }
65 | }
66 |
67 |
68 | // Optional heading
69 | .panel-heading {
70 | padding: 10px 15px;
71 | border-bottom: 1px solid transparent;
72 | .border-top-radius(@panel-border-radius - 1);
73 | }
74 |
75 | // Within heading, strip any `h*` tag of it's default margins for spacing.
76 | .panel-title {
77 | margin-top: 0;
78 | margin-bottom: 0;
79 | font-size: ceil((@font-size-base * 1.125));
80 | > a {
81 | color: inherit;
82 | }
83 | }
84 |
85 | // Optional footer (stays gray in every modifier class)
86 | .panel-footer {
87 | padding: 10px 15px;
88 | background-color: @panel-footer-bg;
89 | border-top: 1px solid @panel-inner-border;
90 | .border-bottom-radius(@panel-border-radius - 1);
91 | }
92 |
93 |
94 | // Collapsable panels (aka, accordion)
95 | //
96 | // Wrap a series of panels in `.panel-group` to turn them into an accordion with
97 | // the help of our collapse JavaScript plugin.
98 |
99 | .panel-group {
100 | // Tighten up margin so it's only between panels
101 | .panel {
102 | margin-bottom: 0;
103 | border-radius: @panel-border-radius;
104 | overflow: hidden; // crop contents when collapsed
105 | + .panel {
106 | margin-top: 5px;
107 | }
108 | }
109 |
110 | .panel-heading {
111 | border-bottom: 0;
112 | + .panel-collapse .panel-body {
113 | border-top: 1px solid @panel-inner-border;
114 | }
115 | }
116 | .panel-footer {
117 | border-top: 0;
118 | + .panel-collapse .panel-body {
119 | border-bottom: 1px solid @panel-inner-border;
120 | }
121 | }
122 |
123 | // New subcomponent for wrapping collapsable content for proper animations
124 | .panel-collapse {
125 |
126 | }
127 | }
128 |
129 |
130 | // Contextual variations
131 | .panel-default {
132 | .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);
133 | }
134 | .panel-primary {
135 | .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);
136 | }
137 | .panel-success {
138 | .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);
139 | }
140 | .panel-warning {
141 | .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);
142 | }
143 | .panel-danger {
144 | .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);
145 | }
146 | .panel-info {
147 | .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);
148 | }
149 |
--------------------------------------------------------------------------------
/theme/stylesheets/book/font-settings.less:
--------------------------------------------------------------------------------
1 | .book-header{
2 | #font-settings-wrapper{
3 | position:relative;
4 | .dropdown-menu{
5 | background-color:@header-background;
6 | border-color:@sidebar-divider-color;
7 | padding: 0px;
8 |
9 | .dropdown-caret{
10 | position: absolute;
11 | top: 14px;
12 | left: -8px;
13 | width: 10px;
14 | height: 18px;
15 | float: left;
16 | overflow: hidden;
17 | .caret-outer{
18 | position: absolute;
19 | border-bottom: 9px solid transparent;
20 | border-top: 9px solid transparent;
21 | border-right: 9px solid rgba(0,0,0,0.1);
22 | height: auto;
23 | left: 0;
24 | top: 0;
25 | width: auto;
26 | display: inline-block;
27 | margin-left: -1px;
28 | }
29 | .caret-inner{
30 | position: absolute;
31 | display: inline-block;
32 | margin-left: -1px;
33 | top: 0;
34 | left: 1px;
35 | border-bottom: 9px solid transparent;
36 | border-top: 9px solid transparent;
37 | border-right: 9px solid @header-background;
38 | }
39 | }
40 | button{
41 | border: 0;
42 | background-color:transparent;
43 | color:@header-button-color;
44 | &:hover{
45 | color:@header-button-hover-color;
46 | background-color:@header-button-hover-background;
47 | }
48 | }
49 | #enlarge-font-size{
50 | width: 50%;
51 | font-size:1.4em;
52 | }
53 | #reduce-font-size{
54 | width: 50.5%;
55 | font-size:1em;
56 | }
57 | .btn-group-xs{
58 | .btn{
59 | width: 33.7%;
60 | padding: initial;
61 | }
62 | }
63 | .list-group{
64 | margin: 0px 0;
65 |
66 | .list-group-item{
67 | cursor:pointer;
68 | background-color:transparent;
69 | border-color: @sidebar-divider-color;
70 | border-width: 1px 0 !important;
71 |
72 | &:hover{
73 | color:@header-button-hover-color;
74 | background-color:@header-button-hover-background !important;
75 | }
76 | &.active{
77 | color:@header-button-hover-color;
78 | background-color:@header-button-hover-background !important;
79 | }
80 | }
81 | }
82 | &.open{
83 | display:block;
84 | }
85 | }
86 | }
87 | }
88 |
89 | /*
90 | * Theme 1
91 | */
92 |
93 | .color-theme-1{
94 | #font-settings-wrapper{
95 | .dropdown-menu{
96 | background-color:@sidebar-background-1;
97 | border-color:@sidebar-divider-color-1;
98 | .dropdown-caret .caret-inner{
99 | border-right: 9px solid @sidebar-background-1;
100 | }
101 | button{
102 | color:@header-button-color-1;
103 | &:hover{
104 | color:@header-button-hover-color-1;
105 | background-color:@header-button-hover-background-1;
106 | }
107 | }
108 | .list-group{
109 | .list-group-item{
110 | border-color:@sidebar-divider-color-1;
111 | &:hover{
112 | color:@header-button-hover-color-1;
113 | background-color:@header-button-hover-background-1 !important;
114 | }
115 | &.active{
116 | color:@header-button-hover-color-1;
117 | background-color:@header-button-hover-background-1 !important;
118 | }
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
125 | /*
126 | * Theme 2
127 | */
128 |
129 | .color-theme-2{
130 | #font-settings-wrapper{
131 | .dropdown-menu{
132 | background-color:@sidebar-background-2;
133 | border-color:@sidebar-divider-color-2;
134 | .dropdown-caret .caret-inner{
135 | border-right: 9px solid @sidebar-background-2;
136 | }
137 | button{
138 | color:@header-button-color-2;
139 | &:hover{
140 | color:@header-button-hover-color-2;
141 | background-color:@header-button-hover-background-2;
142 | }
143 | }
144 | .list-group{
145 | .list-group-item{
146 | border-color:@sidebar-divider-color-2;
147 | &:hover{
148 | color:@header-button-hover-color-2;
149 | background-color:@header-button-hover-background-2 !important;
150 | }
151 | &.active{
152 | color:@header-button-hover-color-2;
153 | background-color:@header-button-hover-background-2 !important;
154 | }
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/theme/javascript/core/font-settings.js:
--------------------------------------------------------------------------------
1 | define([
2 | "jQuery",
3 | "utils/storage"
4 | ], function($, storage) {
5 | var fontState;
6 |
7 | var THEMES = {
8 | "white": 0,
9 | "sepia": 1,
10 | "night": 2
11 | };
12 |
13 | var FAMILY = {
14 | "serif": 0,
15 | "sans": 1
16 | };
17 |
18 | var togglePopover = function(e) {
19 | var $dropdown = $("#font-settings-wrapper .dropdown-menu");
20 |
21 | $dropdown.toggleClass("open");
22 | e.stopPropagation();
23 | e.preventDefault();
24 | };
25 |
26 | var closePopover = function(e) {
27 | var $dropdown = $("#font-settings-wrapper .dropdown-menu");
28 |
29 | $dropdown.removeClass("open");
30 | };
31 |
32 | var enlargeFontSize = function(e){
33 | var $bookBody = $(".book-body");
34 |
35 | if (fontState.size < 4){
36 | $bookBody.toggleClass("font-size-"+fontState.size, false);
37 | fontState.size++;
38 |
39 | $bookBody.toggleClass("font-size-"+fontState.size, true);
40 | fontState.save();
41 | }
42 | };
43 |
44 | var reduceFontSize = function(e){
45 | var $bookBody = $(".book-body");
46 |
47 | if (fontState.size > 0){
48 | $bookBody.toggleClass("font-size-"+fontState.size);
49 | fontState.size--;
50 |
51 | $bookBody.toggleClass("font-size-"+fontState.size);
52 | fontState.save();
53 | }
54 | };
55 |
56 | var changeFontFamily = function(){
57 | var $bookBody = $(".book-body");
58 | var index = $(this).data("font");
59 |
60 | $bookBody.toggleClass("font-family-"+fontState.family);
61 | $(".font-settings .font-family-list li:nth-child("+(fontState.family+1)+")")
62 | .removeClass("active");
63 |
64 | fontState.family = index;
65 | $bookBody.toggleClass("font-family-"+fontState.family);
66 | $(this).addClass("active");
67 | fontState.save();
68 | };
69 |
70 | var changeColorTheme = function(){
71 | var $book = $(".book");
72 | var index = $(this).data("theme");
73 |
74 | if (fontState.theme !== 0)
75 | $book.removeClass("color-theme-"+fontState.theme);
76 |
77 | fontState.theme = index;
78 | if (fontState.theme !== 0)
79 | $book.addClass("color-theme-"+fontState.theme);
80 |
81 | fontState.save();
82 | };
83 |
84 | var init = function(config) {
85 | var $toggle, $bookBody, $dropdown, $book;
86 |
87 | //Find DOM elements.
88 | $book = $(".book");
89 | $toggle = $(".book-header .toggle-font-settings");
90 | $dropdown = $("#font-settings-wrapper .dropdown-menu");
91 | $bookBody = $(".book-body");
92 |
93 | // Instantiate font state object
94 | fontState = storage.get("fontState", {
95 | size: config.size || 1,
96 | family: FAMILY[config.family || "serif"],
97 | theme: THEMES[config.theme || "white"]
98 | });
99 | fontState.save = function(){
100 | storage.set("fontState",fontState);
101 | };
102 |
103 | $bookBody.addClass("font-size-"+fontState.size);
104 | $bookBody.addClass("font-family-"+fontState.family);
105 |
106 | $(".font-settings .font-family-list li:nth-child("+(fontState.family+1)+")").addClass("active");
107 |
108 | if(fontState.theme !== 0)
109 | $book.addClass("color-theme-"+fontState.theme);
110 |
111 | //Add event listeners
112 | $(document).on('click', "#enlarge-font-size", enlargeFontSize);
113 | $(document).on('click', "#reduce-font-size", reduceFontSize);
114 |
115 | $(document).on('click', "#font-settings-wrapper .font-family-list li", changeFontFamily);
116 | $(document).on('click', "#font-settings-wrapper .color-theme-list button", changeColorTheme);
117 |
118 | $(document).on('click', ".book-header .toggle-font-settings", togglePopover);
119 | $(document).on('click', "#font-settings-wrapper .dropdown-menu", function(e){ e.stopPropagation(); });
120 | $(document).on("click", closePopover);
121 | };
122 |
123 | return {
124 | init: init
125 | }
126 | });
--------------------------------------------------------------------------------
/theme/assets/jsrepl/engines/javascript-default.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright Joyent, Inc. and other Node contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to permit
10 | persons to whom the Software is furnished to do so, subject to the
11 | following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included
14 | in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
22 | USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | Original at: https://github.com/joyent/node/blob/master/lib/util.js
25 | */
26 | (function(){function o(c){return c instanceof Array||Array.isArray(c)||c&&c!==Object.prototype&&o(c.__proto__)}function p(c){return c instanceof RegExp||typeof c==="function"&&c.constructor.name==="RegExp"&&c.compile&&c.test&&c.exec&&(""+c).match(/^\/.*\/[gim]{0,3}$/)}var q=80,l=function(c,h,b,f){function m(a,c){switch(typeof a){case "undefined":return d("undefined","undefined");case "string":var b="'"+JSON.stringify(a).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return d(b,"string");
27 | case "number":return d(""+a,"number");case "boolean":return d(""+a,"boolean")}if(a===null)return d("null","null");var f=Object.keys(a),i=h?Object.getOwnPropertyNames(a):f;if(typeof a==="function"&&i.length===0)return p(a)?d(""+a,"regexp"):d("[Function"+(a.name?": "+a.name:"")+"]","special");if(a instanceof Date&&i.length===0)return d(a.toUTCString(),"date");var j,l;o(a)?(l="Array",b=["[","]"]):(l="Object",b=["{","}"]);typeof a==="function"?(j=a.name?": "+a.name:"",j=p(a)?" "+a:" [Function"+j+"]"):
28 | j="";a instanceof Date&&(j=" "+a.toUTCString());if(i.length===0)return b[0]+j+b[1];if(c<0)return p(a)?d(""+a,"regexp"):d("[Object]","special");k.push(a);i=i.map(function(b){var e,g;a.__lookupGetter__&&(a.__lookupGetter__(b)?g=a.__lookupSetter__(b)?d("[Getter/Setter]","special"):d("[Getter]","special"):a.__lookupSetter__(b)&&(g=d("[Setter]","special")));f.indexOf(b)<0&&(e="["+b+"]");g||(k.indexOf(a[b])<0?(g=c===null?m(a[b]):m(a[b],c-1),g.indexOf("\n")>-1&&(g=o(a)?g.split("\n").map(function(a){return" "+
29 | a}).join("\n").substr(2):"\n"+g.split("\n").map(function(a){return" "+a}).join("\n"))):g=d("[Circular]","special"));if(typeof e==="undefined"){if(l==="Array"&&b.match(/^\d+$/))return g;e=JSON.stringify(""+b);e.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(e=e.substr(1,e.length-2),e=d(e,"name")):(e=e.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),e=d(e,"string"))}return e+": "+g});k.pop();var n=0;return i=i.reduce(function(a,b){n++;b.indexOf("\n")>=0&&n++;return a+b.length+1},0)>q?b[0]+
30 | (j===""?"":j+"\n ")+" "+i.join(",\n ")+" "+b[1]:b[0]+j+" "+i.join(", ")+" "+b[1]}var k=[],d=function(a,b){var c={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},d={special:"cyan",number:"blue","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"}[b];return d?"\u001b["+c[d][0]+"m"+a+"\u001b["+c[d][1]+"m":a};f||(d=function(a){return a});
31 | return m(c,typeof b==="undefined"?2:b)},r=/%[sdj%]/g,s=function(c){if(typeof c!=="string"){for(var h=[],b=0;b=m)return c;switch(c){case "%s":return String(f[b++]);case "%d":return Number(f[b++]);case "%j":return JSON.stringify(f[b++]);case "%%":return"%";default:return c}}),k=f[b];b' + text + '';
85 | return out;
86 | };
87 |
88 | GitBookRenderer.prototype.image = function(href, title, text) {
89 | // Our "fixed" href
90 | var _href = href;
91 |
92 | // Parsed version of the url
93 | var parsed = url.parse(href);
94 |
95 | // Options
96 | var o = this._extra_options;
97 |
98 | // Relative image, rewrite it depending output
99 | if(links.isRelative(href) && o && o.dir && o.outdir) {
100 | // o.dir: directory parent of the file currently in rendering process
101 | // o.outdir: directory parent from the html output
102 |
103 | _href = links.toAbsolute(_href, o.dir, o.outdir);
104 | }
105 |
106 | return GitBookRenderer.super_.prototype.image.call(this, _href, title, text);
107 | };
108 |
109 | GitBookRenderer.prototype.tablerow = function(content) {
110 | this.quizRowId += 1;
111 | return GitBookRenderer.super_.prototype.tablerow(content);
112 | };
113 |
114 | var fieldRegex = /^([(\[])([ x])[\])]/;
115 | GitBookRenderer.prototype._createCheckboxAndRadios = function(text) {
116 | var match = fieldRegex.exec(text);
117 | if (!match) {
118 | return text;
119 | }
120 | var quizIdentifier = 'quiz-row-' + this.id + '-' + this.quizRowId + '-' + this.quizIndex++;
121 | var field = "" : "'/>";
124 | var splittedText = text.split(fieldRegex);
125 | var length = splittedText.length;
126 | var label = '';
127 | return text.replace(fieldRegex, field).replace(splittedText[length - 1], label);
128 | }
129 |
130 | GitBookRenderer.prototype.tablecell = function(content, flags) {
131 | return GitBookRenderer.super_.prototype.tablecell(this._createCheckboxAndRadios(content), flags);
132 | };
133 |
134 | GitBookRenderer.prototype.listitem = function(text) {
135 | return GitBookRenderer.super_.prototype.listitem(this._createCheckboxAndRadios(text));
136 | };
137 |
138 | // Exports
139 | module.exports = GitBookRenderer;
140 |
--------------------------------------------------------------------------------
/theme/stylesheets/vendors/bootstrap/carousel.less:
--------------------------------------------------------------------------------
1 | //
2 | // Carousel
3 | // --------------------------------------------------
4 |
5 |
6 | // Wrapper for the slide container and indicators
7 | .carousel {
8 | position: relative;
9 | }
10 |
11 | .carousel-inner {
12 | position: relative;
13 | overflow: hidden;
14 | width: 100%;
15 |
16 | > .item {
17 | display: none;
18 | position: relative;
19 | .transition(.6s ease-in-out left);
20 |
21 | // Account for jankitude on images
22 | > img,
23 | > a > img {
24 | .img-responsive();
25 | line-height: 1;
26 | }
27 | }
28 |
29 | > .active,
30 | > .next,
31 | > .prev { display: block; }
32 |
33 | > .active {
34 | left: 0;
35 | }
36 |
37 | > .next,
38 | > .prev {
39 | position: absolute;
40 | top: 0;
41 | width: 100%;
42 | }
43 |
44 | > .next {
45 | left: 100%;
46 | }
47 | > .prev {
48 | left: -100%;
49 | }
50 | > .next.left,
51 | > .prev.right {
52 | left: 0;
53 | }
54 |
55 | > .active.left {
56 | left: -100%;
57 | }
58 | > .active.right {
59 | left: 100%;
60 | }
61 |
62 | }
63 |
64 | // Left/right controls for nav
65 | // ---------------------------
66 |
67 | .carousel-control {
68 | position: absolute;
69 | top: 0;
70 | left: 0;
71 | bottom: 0;
72 | width: @carousel-control-width;
73 | .opacity(@carousel-control-opacity);
74 | font-size: @carousel-control-font-size;
75 | color: @carousel-control-color;
76 | text-align: center;
77 | text-shadow: @carousel-text-shadow;
78 | // We can't have this transition here because webkit cancels the carousel
79 | // animation if you trip this while in the middle of another animation.
80 |
81 | // Set gradients for backgrounds
82 | &.left {
83 | #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));
84 | }
85 | &.right {
86 | left: auto;
87 | right: 0;
88 | #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));
89 | }
90 |
91 | // Hover/focus state
92 | &:hover,
93 | &:focus {
94 | color: @carousel-control-color;
95 | text-decoration: none;
96 | .opacity(.9);
97 | }
98 |
99 | // Toggles
100 | .icon-prev,
101 | .icon-next,
102 | .glyphicon-chevron-left,
103 | .glyphicon-chevron-right {
104 | position: absolute;
105 | top: 50%;
106 | left: 50%;
107 | z-index: 5;
108 | display: inline-block;
109 | }
110 | .icon-prev,
111 | .icon-next {
112 | width: 20px;
113 | height: 20px;
114 | margin-top: -10px;
115 | margin-left: -10px;
116 | font-family: serif;
117 | }
118 |
119 | .icon-prev {
120 | &:before {
121 | content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)
122 | }
123 | }
124 | .icon-next {
125 | &:before {
126 | content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)
127 | }
128 | }
129 | }
130 |
131 | // Optional indicator pips
132 | //
133 | // Add an unordered list with the following class and add a list item for each
134 | // slide your carousel holds.
135 |
136 | .carousel-indicators {
137 | position: absolute;
138 | bottom: 10px;
139 | left: 50%;
140 | z-index: 15;
141 | width: 60%;
142 | margin-left: -30%;
143 | padding-left: 0;
144 | list-style: none;
145 | text-align: center;
146 |
147 | li {
148 | display: inline-block;
149 | width: 10px;
150 | height: 10px;
151 | margin: 1px;
152 | text-indent: -999px;
153 | border: 1px solid @carousel-indicator-border-color;
154 | border-radius: 10px;
155 | cursor: pointer;
156 | }
157 | .active {
158 | margin: 0;
159 | width: 12px;
160 | height: 12px;
161 | background-color: @carousel-indicator-active-bg;
162 | }
163 | }
164 |
165 | // Optional captions
166 | // -----------------------------
167 | // Hidden by default for smaller viewports
168 | .carousel-caption {
169 | position: absolute;
170 | left: 15%;
171 | right: 15%;
172 | bottom: 20px;
173 | z-index: 10;
174 | padding-top: 20px;
175 | padding-bottom: 20px;
176 | color: @carousel-caption-color;
177 | text-align: center;
178 | text-shadow: @carousel-text-shadow;
179 | & .btn {
180 | text-shadow: none; // No shadow for button elements in carousel-caption
181 | }
182 | }
183 |
184 |
185 | // Scale up controls for tablets and up
186 | @media screen and (min-width: @screen-tablet) {
187 |
188 | // Scale up the controls a smidge
189 | .carousel-control .icon-prev,
190 | .carousel-control .icon-next {
191 | width: 30px;
192 | height: 30px;
193 | margin-top: -15px;
194 | margin-left: -15px;
195 | font-size: 30px;
196 | }
197 |
198 | // Show and left align the captions
199 | .carousel-caption {
200 | left: 20%;
201 | right: 20%;
202 | padding-bottom: 30px;
203 | }
204 |
205 | // Move up the indicators
206 | .carousel-indicators {
207 | bottom: 20px;
208 | }
209 | }
210 |
--------------------------------------------------------------------------------