Compare commits

...

20 Commits

Author SHA1 Message Date
Aaron O'Mullan 6bce93d893 Bump version to 0.2.3 2014-04-18 11:53:18 -07:00
Aaron O'Mullan c9db068227 Refactor navigation generation to be functional
This makes it a lot less error prone and far more robust. As well as a
lot easier to understand
2014-04-18 11:49:55 -07:00
Samy Pessé 8a6d36ac4d Improve stability of mixpanel tracking 2014-04-18 20:42:18 +02:00
Samy Pessé 64622c5945 Load css before content 2014-04-18 20:19:45 +02:00
Samy Pessé 837813edb0 Add warning about theme 2014-04-18 20:16:21 +02:00
Aaron O'Mullan 0d9d62f1a9 Merge pull request #117 from GitbookIO/fix/99
Fix #99
2014-04-18 10:17:20 -07:00
Samy Pessé f7e9abf984 Fix quiz interractivity 2014-04-18 19:10:13 +02:00
Samy Pessé dd0579b60d Update tests for relative images 2014-04-18 17:23:33 +02:00
Samy Pessé b5bb953e47 Fix image path calcul 2014-04-18 17:15:03 +02:00
Samy Pessé ef4a8ea1b6 Fix #116: update url before updating html content 2014-04-18 15:49:53 +02:00
Samy Pessé 49cde96ee7 Add contributors to package.json and change homepage 2014-04-18 15:00:40 +02:00
Samy Pessé 627be20e48 Add editor and twitter links in the readme 2014-04-18 14:48:03 +02:00
Samy Pessé e45bb84b02 Fix navigation on chrome for iOS: disable pushState 2014-04-18 13:09:16 +02:00
Aaron O'Mullan 4e8876c725 Update README.md 2014-04-17 16:39:06 -07:00
Aaron O'Mullan 6d5be15ec9 Bump version to 0.2.2 2014-04-17 15:31:57 -07:00
Samy Pessé 1a8db646f2 Improve styling for pdf and ebook 2014-04-17 12:15:28 +02:00
Samy Pessé d3aba2c906 Fix state update after navigation 2014-04-17 11:48:04 +02:00
Samy Pessé 457a83282f Improve style for pdf and ebook 2014-04-17 11:27:21 +02:00
Samy Pessé 48e94c937d Remove useless caracter from page format 2014-04-17 11:17:40 +02:00
Samy Pessé 6e613e9f71 Fix #107: Render all the page in single page format 2014-04-17 11:16:18 +02:00
21 changed files with 323 additions and 168 deletions
-4
View File
@@ -35,7 +35,6 @@ module.exports = function (grunt) {
"lodash": 'vendors/lodash',
"requireLib": 'vendors/require',
"Mousetrap": 'vendors/mousetrap',
"mixpanel": 'vendors/mixpanel',
"lunr": path.join(__dirname, "node_modules/lunr/lunr")
},
shim: {
@@ -48,9 +47,6 @@ module.exports = function (grunt) {
'Mousetrap': {
exports: 'Mousetrap'
},
'mixpanel': {
exports: 'mixpanel'
},
'lunr': {
exports: 'lunr'
}
+3 -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 programming books and exercises using GitHub/Git and Markdown. You can see an example: [Learn Javascript](http://gitbookio.github.io/javascript/).
GitBook is a command line tool (and Node.js library) for building beautiful programming books and exercises using GitHub/Git and Markdown. You can see an example: [Learn Javascript](http://gitbookio.github.io/javascript/). An [editor](https://github.com/GitbookIO/editor) is available for Windows, Mac and Linux. You can follow [@GitBookIO](https://twitter.com/GitBookIO) on Twitter.
![Image](https://raw.github.com/GitbookIO/gitbook/master/preview.png)
@@ -39,6 +39,8 @@ Options for commands `build` and `serve` are:
--theme <path> Path to theme directory
```
You can publish your books to our index by visiting [GitBook.io](http://www.gitbook.io)
## Output Formats
GitBook can generate your book in the following formats:
+30 -49
View File
@@ -6,6 +6,12 @@ function clean(obj) {
return obj && _.omit(obj, ['articles']);
}
function flattenChapters(chapters) {
return _.reduce(chapters, function(accu, chapter) {
return accu.concat([clean(chapter)].concat(chapter.articles));
}, []);
}
// Returns from a summary a map of
/*
{
@@ -20,62 +26,37 @@ function navigation(summary, files) {
// Support single files as well as list
files = _.isArray(files) ? files : (_.isString(files) ? [files] : null);
// Mapping of prev/next for a give path
var mapping = {};
// Special README nav
var README_NAV = {
path: 'README.md',
title: 'Introduction',
};
// Walk the chapter/article tree and create navigation entires
_.each(summary.chapters, function(chapter, idx, chapters) {
// Skip if no path
if(!chapter.path) return;
var currentChapter = clean(chapter);
var prevChapter = (idx-1 < 0) ? README_NAV : chapters[idx-1];
var nextChapter = (idx+1 >= chapters.length) ? null : chapters[idx+1];
var prev = (!prevChapter || _.isEmpty(prevChapter.articles)) ?
prevChapter : _.last(prevChapter.articles);
var next = (!chapter || _.isEmpty(chapter.articles)) ?
nextChapter : _.first(chapter.articles);
// Add chapter mapping
mapping[chapter.path] = {
title: chapter.title,
prev: clean(prev),
next: clean(next),
level: (idx+1).toString(),
};
// Check a chapter's articles
_.each(chapter.articles, function(article, _idx, articles) {
// Skip if no path
if(!article.path) return;
var prev = (_idx-1 < 0) ? currentChapter : clean(articles[_idx-1]);
var next = (_idx+1 >= articles.length) ? nextChapter : clean(articles[_idx+1]);
mapping[article.path] = {
title: article.title,
prev: clean(prev),
next: clean(next),
level: [idx+1, _idx+1].join('.'),
};
});
});
// Hack for README.html
mapping['README.md'] = {
title: README_NAV.title,
prev: null,
next: clean(summary.chapters[0]),
level: '0',
};
// List of all navNodes
var navNodes = [README_NAV].concat(flattenChapters(summary.chapters));
var prevNodes = [null].concat(navNodes.slice(0, -1));
var nextNodes = navNodes.slice(1).concat([null]);
// Mapping of prev/next for a give path
var mapping = _.chain(_.zip(navNodes, prevNodes, nextNodes))
.map(function(nodes) {
var current = nodes[0], prev = nodes[1], next = nodes[2];
// Skip if no path
if(!current.path) return null;
return [current.path, {
title: current.title,
prev: prev,
next: next,
level: current.level,
}];
})
.filter()
.object()
.value();
// Filter for only files we want
if(files) {
return _.pick(mapping, files);
+8 -2
View File
@@ -79,8 +79,14 @@ GitBookRenderer.prototype.image = function(href, title, text) {
// Relative image, rewrite it depending output
if(!parsed.protocol && parsed.path && parsed.path[0] != '/' && o && o.dir && o.outdir) {
var outdir = o.outdir.charAt(o.outdir.length - 1) === '/' ? o.outdir : o.outdir + '/';
_href = url.resolve(outdir, [o.dir, href].join('/'));
// o.dir: directory parent of the file currently in rendering process
// o.outdir: directory parent from the html output
// Absolute file in source
_href = path.join(o.dir, _href);
// make it relative to output
_href = path.relative(o.outdir, _href);
}
return GitBookRenderer.super_.prototype.image.call(this, _href, title, text);
+14 -3
View File
@@ -1,6 +1,7 @@
{
"name": "gitbook",
"version": "0.2.1",
"version": "0.2.3",
"homepage": "http://www.gitbook.io/",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
"dependencies": {
@@ -39,9 +40,19 @@
"book",
"gitbook"
],
"author": "Aaron O'Mullan <aaron.omullan@friendco.de>",
"author": "FriendCode <contact@friendco.de>",
"license": "Apache 2",
"bugs": {
"url": "https://github.com/GitbookIO/gitbook/issues"
}
},
"contributors": [
{
"name": "Aaron O'Mullan",
"email": "aaron.omullan@gmail.com"
},
{
"name": "Samy Pessé",
"email": "samypesse@gmail.com"
}
]
}
+12
View File
@@ -0,0 +1,12 @@
# Images Test
This is a test for images path calculation. It supposed this fiel is in a syntax/ folder
### #
[![Screen](./preview.png)](./preview.png)
### 2
[![Screen](../preview2.png)](./preview2.png)
+30 -4
View File
@@ -36,10 +36,6 @@ describe('Page parsing', function() {
assert(LEXED[2].content);
});
it('should make image URLs relative', function() {
assert(LEXED[2].content.indexOf('_book/assets/my-pretty-picture.png') !== -1);
});
it('should gen code and content for exercise sections', function() {
assert(LEXED[1].content);
assert(LEXED[1].code);
@@ -94,3 +90,33 @@ describe('Relative links', function() {
assert(LEXED[0].content.indexOf('https://github.com/GitBookIO/javascript/blob/src/something.cpp') !== -1);
});
});
describe('Relative images', function() {
it('should keep image relative with considering output directory in site format', function() {
var LEXED = loadPage('IMAGES', {
// GitHub repo ID
repo: 'GitBookIO/javascript',
// Imaginary folder of markdown file
dir: 'syntax',
outdir: 'syntax'
});
assert(LEXED[0].content.indexOf('"preview.png"') !== -1);
assert(LEXED[0].content.indexOf('"../preview2.png"') !== -1);
});
it('should keep image relative with considering output directory in page format', function() {
var LEXED = loadPage('IMAGES', {
// GitHub repo ID
repo: 'GitBookIO/javascript',
// Imaginary folder of markdown file
dir: 'syntax',
outdir: './'
});
assert(LEXED[0].content.indexOf('"syntax/preview.png"') !== -1);
assert(LEXED[0].content.indexOf('"preview2.png"') !== -1);
});
});
+2
View File
@@ -3,3 +3,5 @@
For creating a theme for GitBook, just copy this folder. The theme folder should at least contains two folders: `assets` and `templates`.
The theme can be changed using the option ```--theme=<path>```
#### Warning! Theme system is going to change before version 1.0.0
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6 -4
View File
@@ -10,12 +10,14 @@ define([
var prev, next;
var githubCountStars, githubCountWatch;
var usePushState = !navigator.userAgent.match('CriOS') && (typeof history.pushState !== "undefined");
var updateHistory = function(url, title) {
history.pushState({ path: url }, title, url);
};
var handleNavigation = function(url, push) {
if (typeof history.pushState === "undefined") {
if (!usePushState) {
// Refresh the page to the new URL if pushState not supported
location.href = url;
return
@@ -23,6 +25,8 @@ define([
return $.get(url)
.done(function (html) {
if (push) updateHistory(url, null);
html = html.replace( /<(\/?)(html|head|body)([^>]*)>/ig, function(a,b,c,d){
return '<' + b + 'div' + ( b ? '' : ' data-element="' + c + '"' ) + d + '>';
});
@@ -48,9 +52,7 @@ define([
$('.book-summary .summary').scrollTop(scrollPosition);
// Update state
state.update($page);
if (push) updateHistory(url, null);
state.update($("html"));
preparePage();
})
.fail(function () {
+5 -2
View File
@@ -20,8 +20,11 @@ define([
$quiz.find(".question").each(function(q) {
var result = true;
var $answers = $quiz.find(".question-answers").slice(q).find("input[type=radio], input[type=checkbox]");
$(this).find("input[type=radio],input[type=checkbox]").each(function(i) {
var $questions = $(this).find(".question-content").find("input[type=radio], input[type=checkbox]");
var $answers = $(this).find(".question-answers").find("input[type=radio], input[type=checkbox]");
$questions.each(function(i) {
var correct = $(this).is(":checked") === $answers.slice(i).first().is(":checked");
result = result && correct;
if (!correct) {
+13 -10
View File
@@ -1,13 +1,6 @@
define([
"lodash",
"mixpanel"
], function(_, mixpanel) {
mixpanel.init("01eb2b950ae09a5fdb15a98dcc5ff20e", {
loaded: function() {
track("page.start");
}
});
"lodash"
], function(_) {
var isAvailable = function() {
return (
typeof mixpanel !== "undefined" &&
@@ -15,9 +8,14 @@ define([
);
};
var track = function(e, data) {
var track = function(e, data, t) {
if (!isAvailable()) {
console.warn("tracking not available!");
t = t || 500;
setTimeout(function() {
console.log(" -> retest tracking");
track(e, data, t*2);
}, t);
return;
}
console.log("track", e);
@@ -26,6 +24,11 @@ define([
}));
};
setTimeout(function() {
mixpanel.init("01eb2b950ae09a5fdb15a98dcc5ff20e");
track("page.start");
}, 0);
return {
isAvailable: isAvailable,
track: track
-2
View File
@@ -1,2 +0,0 @@
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
+1 -6
View File
@@ -5,8 +5,7 @@
@import "variables.less";
@import "fonts.less";
@import "highlight.less";
@import "book/highlight.less";
@import "book/languages.less";
@import "book/header.less";
@import "book/summary.less";
@@ -25,10 +24,6 @@
-webkit-font-smoothing: antialiased;
}
.form-control {
.box-shadow(none);
}
html, body {
height: 100%;
}
+90
View File
@@ -0,0 +1,90 @@
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Tomorrow Comment */
.hljs-comment,
.hljs-title {
color: #8e908c;
}
/* Tomorrow Red */
.hljs-variable,
.hljs-attribute,
.hljs-tag,
.hljs-regexp,
.ruby .hljs-constant,
.xml .hljs-tag .hljs-title,
.xml .hljs-pi,
.xml .hljs-doctype,
.html .hljs-doctype,
.css .hljs-id,
.css .hljs-class,
.css .hljs-pseudo {
color: #c82829;
}
/* Tomorrow Orange */
.hljs-number,
.hljs-preprocessor,
.hljs-pragma,
.hljs-built_in,
.hljs-literal,
.hljs-params,
.hljs-constant {
color: #f5871f;
}
/* Tomorrow Yellow */
.ruby .hljs-class .hljs-title,
.css .hljs-rules .hljs-attribute {
color: #eab700;
}
/* Tomorrow Green */
.hljs-string,
.hljs-value,
.hljs-inheritance,
.hljs-header,
.ruby .hljs-symbol,
.xml .hljs-cdata {
color: #718c00;
}
/* Tomorrow Aqua */
.css .hljs-hexcolor {
color: #3e999f;
}
/* Tomorrow Blue */
.hljs-function,
.python .hljs-decorator,
.python .hljs-title,
.ruby .hljs-function .hljs-title,
.ruby .hljs-title .hljs-keyword,
.perl .hljs-sub,
.javascript .hljs-title,
.coffeescript .hljs-title {
color: #4271ae;
}
/* Tomorrow Purple */
.hljs-keyword,
.javascript .hljs-function {
color: #8959a8;
}
.hljs {
display: block;
background: white;
color: #4d4d4c;
padding: 0.5em;
}
.coffeescript .javascript,
.javascript .xml,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}
+55 -21
View File
@@ -1,8 +1,47 @@
h1, h2 {
@import "vendors/bootstrap/bootstrap.less";
@import "vendors/fontawesome/font-awesome.less";
@import "mixins.less";
@import "variables.less";
@import "fonts.less";
@import "page/highlight.less";
@font-size-base: 13px;
* {
-webkit-overflow-scrolling: touch;
-webkit-tap-highlight-color: transparent;
-webkit-text-size-adjust: none;
-webkit-touch-callout: none;
-webkit-font-smoothing: antialiased;
}
html, body {
height: 100%;
}
body {
text-rendering: optimizeLegibility;
font-smoothing: antialiased;
font-family: @font-family-base;
}
h1, h2, h3 {
page-break-after: avoid;
page-break-before: auto;
}
h1 { font-size: floor(@font-size-base * 2.15); }
h2 { font-size: floor(@font-size-base * 1.70); }
h3 { font-size: ceil(@font-size-base * 1.25); }
h4 { font-size: ceil(@font-size-base * 1); }
h5 { font-size: ceil(@font-size-base * 0.85); }
h6 { font-size: ceil(@font-size-base * 0.65); }
pre, blockquote {
border: 1px solid #999;
page-break-inside: avoid;
@@ -26,41 +65,36 @@ section {
}
&#summary {
text-align: center;
margin: 1.5cm;
ul {
font-size: 0.5cm;
line-height: 1.8em;
padding: 0px;
margin: 0px;
list-style: none;
h1 {
text-align: center;
}
> ul {
> li {
margin-bottom: 1cm;
ol {
list-style: none;
padding: 0px;
margin: 0px;
margin-left: 1cm;
}
> a {
font-size: 0.6cm;
}
> ol {
> li {
}
}
}
article {
margin: 1.5cm;
/* Big centered title */
margin-bottom: 1.5cm;
&.new-chapter {
page-break-after: always;
font-size: 0.6cm;
text-align: center;
padding: 3cm 0cm;
border-top: 1px solid #ccc;
@media print {
border: none;
h1 {
font-size: floor(@font-size-base * 2.7);
}
}
+4 -1
View File
@@ -35,11 +35,14 @@
{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
{% block style %}
<link rel="stylesheet" href="{{ staticBase }}/style.css">
{% endblock %}
{% block content %}{% endblock %}
{% block javascript %}
<script type="text/javascript">(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/mode-javascript.js"></script>
<script src="{{ staticBase }}/jsrepl/jsrepl.js" id="jsrepl-script"></script>
+47 -56
View File
@@ -3,10 +3,32 @@
{% block title %}{{ title }}{% endblock %}
{% block style %}
{% parent %}
<link rel="stylesheet" href="{{ staticBase }}/print.css">
{% endblock %}
{% macro articleContent(content) %}
{% for section in content %}
{% if section.type == "normal" %}
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% elif section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre><code>{% autoescape false %}{{ section.code.base|code }}{% endautoescape %}</code></pre>
{% set exercise = exercise + 1 %}
</div>
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% autoescape false %}{{ section.quiz.base }}{% endautoescape %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
{% endmacro %}
{% set exercise = 1 %}
{% block content %}
{# Cover #}
@@ -19,87 +41,56 @@
{# Summary #}
<section id="summary">
<h1>Summary</h1>
<ul class="summary">
<h1>Table of Contents</h1>
<ol>
<li>
<a href="#README.md">Introduction</a>
<a href="#README.md">
<h2>Introduction</h2>
</a>
</li>
{% for item in summary.chapters %}
<li>
<a href="#{{ item.path }}">{{ item.level }}) {{ item.title }}</a>
<h2><span>{{ item.level }}.</span> <a href="#{{ item.path }}">{{ item.title }}</a></h2>
{% if item.articles.length > 0 %}
<ul>
<ol>
{% for article in item.articles %}
<li>
<a href="#{{ article.path }}">{{ article.level }}) {{ article.title }}</a>
<span>{{ article.level }}</span> <a href="#{{ article.path }}">{{ article.title }}</a>
</li>
{% endfor %}
</ul>
</ol>
{% endif %}
</li>
{% endfor %}
</ul>
</ol>
</section>
{# Pages content #}
<section>
<article id="README.md">
{% for section in pages["README.md"].content %}
{% if section.type == "normal" %}
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% elif section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre><code>{% autoescape false %}{{ section.code.base|code }}{% endautoescape %}</code></pre>
{% set exercise = exercise + 1 %}
</div>
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% autoescape false %}{{ section.quiz.base }}{% endautoescape %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
{{ articleContent(pages["README.md"].content) }}
</article>
</section>
{% for item in summary.chapters %}
{% if item.articles.length > 0 %}
<section>
<article id="{{ item.path }}" class="new-chapter">
<h1>{{ item.title }}</h1>
</article>
{% for article in item.articles %}
{% if pages[article.path] %}
<article id="{{ article.path }}">
{% for section in pages[article.path].content %}
{% if section.type == "normal" %}
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% elif section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre><code>{% autoescape false %}{{ section.code.base|code }}{% endautoescape %}</code></pre>
{% set exercise = exercise + 1 %}
</div>
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% autoescape false %}{{ section.quiz.base }}{% endautoescape %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
</article>
{% endif %}
{% endfor %}
<article>
{{ articleContent(pages[item.path].content) }}
</article>
{% if item.articles.length > 0 %}
{% for article in item.articles %}
{% if pages[article.path] %}
<article id="{{ article.path }}">
{{ articleContent(pages[article.path].content) }}
</article>
{% endif %}
{% endfor %}
{% endif %}
</section>
{% endif %}
{% endfor %}