mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-22 02:23:30 +00:00
Tabular quiz
This commit is contained in:
committed by
Aaron O'Mullan
parent
ab27725b99
commit
f6595f5111
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
+1
-1
@@ -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 !
|
||||
|
||||
---
|
||||
|
||||
|
||||
Vendored
+25
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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();
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user