Tabular quiz

This commit is contained in:
James Phillpotts
2014-04-08 09:51:11 +01:00
committed by Aaron O'Mullan
parent ab27725b99
commit f6595f5111
15 changed files with 225 additions and 21 deletions
+16
View File
@@ -34,6 +34,22 @@ function sectionType(nodes, idx) {
return 'exercise';
}
if (nodes.length > 2) {
var nonBlockquoteNodes = nodes.slice(nodes[0].type === 'paragraph' ? 1 : 0);
nonBlockquoteNodes.splice(_.findIndex(nonBlockquoteNodes, { type: 'blockquote_start' }),
_.findIndex(nonBlockquoteNodes, { type: 'blockquote_end' }));
if (nonBlockquoteNodes.length === 2) {
if (_.every(nonBlockquoteNodes, { type: 'table' })) {
if (_.every(nonBlockquoteNodes[0].cells, function(row) {
return _.every(row.slice(1), function(cell) { return cell === "( )"; });
})) {
return 'quiz';
}
}
}
}
return 'normal';
}
+18
View File
@@ -36,6 +36,10 @@ function render(section, _options) {
return marked.parser(section, options);
}
function quizNodesTest(node) {
return node.type === 'table' || node.type === 'list';
}
function parsePage(src, options) {
options = options || {};
@@ -76,6 +80,20 @@ function parsePage(src, options) {
context: codeNodes[3] ? codeNodes[3].text : null,
}
};
} else if (section.type === 'quiz') {
var nonQuizNodes = _.reject(section, quizNodesTest);
var quizNodes = _.filter(section, quizNodesTest);
var feedback = nonQuizNodes.splice(_.findIndex(nonQuizNodes, { type: 'blockquote_start' }), _.findIndex(nonQuizNodes, { type: 'blockquote_end' }));
return {
id: section.id,
type: section.type,
content: render(nonQuizNodes),
quiz: {
base: render([quizNodes[0]]),
solution: render([quizNodes[1]]),
feedback: render(feedback.slice(1, feedback.length - 1))
}
};
}
// Render normal pages
+17
View File
@@ -5,6 +5,7 @@ var path = require('path');
var marked = require('marked');
var rendererId = 0;
function GitBookRenderer(options, extra_options) {
if(!(this instanceof GitBookRenderer)) {
@@ -13,6 +14,8 @@ function GitBookRenderer(options, extra_options) {
GitBookRenderer.super_.call(this, options);
this._extra_options = extra_options;
this.quizRowId = 0;
this.id = rendererId++;
}
inherits(GitBookRenderer, marked.Renderer);
@@ -83,6 +86,20 @@ GitBookRenderer.prototype.image = function(href, title, text) {
return GitBookRenderer.super_.prototype.image.call(this, _href, title, text);
};
GitBookRenderer.prototype.tablerow = function(content) {
this.quizRowId += 1;
return GitBookRenderer.super_.prototype.tablerow(content);
};
GitBookRenderer.prototype.tablecell = function(content, flags) {
var radioContent = content;
if (content === "( )") {
radioContent = "<input type='radio' name='quiz-row-" + this.id + "-" + this.quizRowId + "'/>";
} else if (content === "(x)") {
radioContent = "<input type='radio' name='quiz-row-" + this.id + "-" + this.quizRowId + "' checked/>";
}
return GitBookRenderer.super_.prototype.tablecell(radioContent, flags);
};
// Exports
module.exports = GitBookRenderer;
+1 -1
View File
@@ -4,7 +4,7 @@ Some nice content here
---
A beautiful separator, but non an exercise !
A beautiful separator, but non an exercise or a quiz !
---
+25
View File
@@ -0,0 +1,25 @@
# Gitbook quiz
Gitbook lets you write a quiz using GFM tables:
---
Here's a quiz about Gitbook
| | Good | Bad |
| ---------------- | ---- | --- |
| What is Gitbook? | ( ) | ( ) |
| | Good | Bad |
| ---------------- | ---- | --- |
| What is Gitbook? | (x) | ( ) |
> Gitbook is good
---
Some more nice content ....
[Cool stuff](http://gitbook.io)
[Link to another Markdown file](./xyz/file.md)
+21 -14
View File
@@ -4,28 +4,27 @@ var assert = require('assert');
var page = require('../').parse.page;
function loadPage (name, options) {
var CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/' + name + '.md'), 'utf8');
return page(CONTENT, options);
}
var CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/PAGE.md'), 'utf8');
var LEXED = page(CONTENT, {
dir: 'course',
outdir: '_book'
});
var HR_CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/HR_PAGE.md'), 'utf8');
var HR_LEXED = page(HR_CONTENT);
var LINKS_CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/GITHUB_LINKS.md'), 'utf8');
var LEXED = loadPage('PAGE');
var QUIZ_LEXED = loadPage('QUIZ_PAGE');
var HR_LEXED = loadPage('HR_PAGE');
describe('Page parsing', function() {
it('should detection sections', function() {
it('should detect sections', function() {
assert.equal(LEXED.length, 4);
});
it('should detection section types', function() {
it('should detect section types', function() {
assert.equal(LEXED[0].type, 'normal');
assert.equal(LEXED[1].type, 'exercise');
assert.equal(LEXED[2].type, 'normal');
assert.equal(QUIZ_LEXED[0].type, 'normal');
assert.equal(QUIZ_LEXED[1].type, 'quiz');
assert.equal(QUIZ_LEXED[2].type, 'normal');
});
it('should gen content for normal sections', function() {
@@ -64,12 +63,20 @@ describe('Page parsing', function() {
it('should detect an exercise\'s language', function() {
assert.equal(LEXED[1].lang, 'python');
});
it('should render a quiz', function() {
assert(QUIZ_LEXED[1].content);
assert(QUIZ_LEXED[1].quiz);
assert(QUIZ_LEXED[1].quiz.base);
assert(QUIZ_LEXED[1].quiz.solution);
assert(QUIZ_LEXED[1].quiz.feedback);
});
});
describe('Relative links', function() {
it('should be resolved to their GitHub counterparts', function() {
var LEXED = page(LINKS_CONTENT, {
var LEXED = loadPage('GITHUB_LINKS', {
// GitHub repo ID
repo: 'GitBookIO/javascript',
+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
+4 -2
View File
@@ -7,10 +7,11 @@ require([
"core/state",
"core/keyboard",
"core/exercise",
"core/quiz",
"core/progress",
"core/sidebar",
"core/search"
], function($, storage, analytic, sharing, state, keyboard, exercise, progress, sidebar, search){
], function($, storage, analytic, sharing, state, keyboard, exercise, quiz, progress, sidebar, search){
$(document).ready(function() {
var $book = state.$book;
@@ -35,8 +36,9 @@ require([
});
}
// Bind exercise
// Bind exercises
exercise.init();
quiz.init();
// Bind sharing button
sharing.init();
+50
View File
@@ -0,0 +1,50 @@
define([
"jQuery",
"utils/execute",
"utils/analytic",
"core/state"
], function($, execute, analytic, state){
// Bind an exercise
var prepareExercise = function($exercise) {
var $answers = $exercise.find(".quiz-answers").find("input[type=radio], input[type=checkbox]");
$answers.click(function(e) {
e.preventDefault();
});
// Submit: test code
$exercise.find(".action-submit").click(function(e) {
e.preventDefault();
analytic.track("exercise.submit");
$exercise.find("tr.alert-danger").removeClass("alert-danger");
$exercise.find(".alert-success,.alert-danger").addClass("hidden");
var result = true;
$exercise.find(".quiz input[type=radio],.quiz input[type=checkbox]").each(function(i) {
var correct = $(this).is(":checked") === $answers.slice(i).first().is(":checked");
result = result && correct;
if (!correct) {
$(this).closest("tr").addClass("alert-danger");
}
});
$exercise.find(result ? "div.alert-success" : "div.alert-danger").toggleClass("hidden");
});
$exercise.find(".action-solution").click(function(e) {
$exercise.find(".quiz, .quiz-answers").toggleClass("hidden");
});
};
// Prepare all exercise
var init = function() {
state.$book.find("section.quiz").each(function() {
prepareExercise($(this));
});
};
return {
init: init,
prepare: prepareExercise
};
});
+13
View File
@@ -0,0 +1,13 @@
.book .book-body .page-wrapper .page-inner {
section.quiz {
.quiz, .quiz-answers {
table {
margin-bottom: 10px;
width: 100%;
}
th, td {
padding-right: 5px;
}
}
}
}
+3 -2
View File
@@ -15,12 +15,13 @@
@import "book/markdown.less";
@import "book/progress.less";
@import "book/navigation.less";
@import "book/quiz.less";
* {
-webkit-overflow-scrolling: touch;
-webkit-tap-highlight-color: transparent;
-webkit-text-size-adjust: none;
-webkit-touch-callout: none;
-webkit-touch-callout: none;
-webkit-font-smoothing: antialiased;
}
@@ -33,7 +34,7 @@ html, body {
}
body {
font-smoothing: antialiased;
font-family: 'appNormal';
}
+32
View File
@@ -0,0 +1,32 @@
<div class="header">
<h2>Quiz</h2>
</div>
<div class="message">
{% autoescape false %}{{ section.content }}{% endautoescape %}
</div>
<div class="quiz">
{% autoescape false %}{{ section.quiz.base }}{% endautoescape %}
<div class="alert alert-success hidden">
<b>Correct!</b>
</div>
<div class="alert alert-danger error-message hidden">
<p><b>Not quite!</b></p>
{% autoescape false %}{{ section.quiz.feedback }}{% endautoescape %}
</div>
</div>
<div class="hidden quiz-answers">
{% autoescape false %}{{ section.quiz.solution }}{% endautoescape %}
</div>
<div class="btn-group btn-group-justified">
<a href="#" class="btn btn-default action-submit">Submit</a>
<a href="#" class="btn btn-default action-solution">Solution</a>
{% if githubId %}
<a href="{{ githubHost }}{{ githubId }}/issues/new" target="_blank" class="btn btn-default">Have a Question?</a>
{% endif %}
</div>
+21
View File
@@ -55,6 +55,13 @@
<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>
@@ -79,6 +86,13 @@
<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>
@@ -108,6 +122,13 @@
<pre><code>{% autoescape false %}{{ section.code.solution|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.solution }}{% endautoescape %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
{% endif %}
+2
View File
@@ -21,6 +21,8 @@
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% elif section.type == "exercise" %}
{% include "./includes/book/exercise.html" with {section: section} %}
{% elif section.type == "quiz" %}
{% include "./includes/book/quiz.html" with {section: section} %}
{% endif %}
</section>
{% endfor %}