├── .gitignore ├── README.md ├── book ├── script.js └── style.css ├── index.js ├── package.json └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitbook-plugin-quiz 2 |

3 | Downloads 4 |

5 | Allows to insert simple quizes into gitbook. 6 | 7 | ![Link](screenshot.png) 8 | 9 | ### How to use? 10 | 11 | Add plugin to your `book.json`, then run `gitbook install`: 12 | 13 | ``` 14 | { 15 | "plugins": ["quiz"] 16 | } 17 | ``` 18 | 19 | It is possible to configure generic labels and text: 20 | 21 | ``` 22 | "pluginsConfig": { 23 | "quiz": { 24 | "labels": { 25 | "check" : "Check my answers", 26 | "showExplanation" : "Explain", 27 | "showCorrect" : "Show all", 28 | "explanationHeader" : "Explanation" 29 | }, 30 | "text": { 31 | "noChosen" : "Choose at least one answer", 32 | "incomplete" : "Some correct answers are missing" 33 | }, 34 | "buttons": { 35 | "showCorrect" : true, 36 | "showExplanation" : true 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | Add quiz markup in your gitbook: 43 | 44 | ``` 45 | 46 | 47 |

What is gitbook used for?

48 | To read books 49 | To book hotel named git 50 | To write and publish beautiful books 51 | GitBook.com lets you write, publish and manage your books online as a service. 52 |
53 | 54 |

Is it quiz?

55 | Yes 56 | No 57 |
58 | 59 |

This is multiple dropdown quiz, in each dropdown select a correct number corresponding to the dropdown's order

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 |
86 | ``` 87 | -------------------------------------------------------------------------------- /book/script.js: -------------------------------------------------------------------------------- 1 | require(["jquery", "gitbook"], function($, gitbook) { 2 | var configuration = {"labels": {}, text: {}, "buttons": {"showCorrect": true, "showExplanation": true}}; 3 | var isStarted = false; 4 | /** 5 | * Enumeration for default labels and text 6 | * @type {Object} 7 | */ 8 | var LABELS_AND_TEXT_ENUM = { 9 | "labels": { 10 | "showCorrect" : "Show correct answers", 11 | "check" : "Check", 12 | "showExplanation" : "Show explanation", 13 | "explanationHeader" : "" 14 | }, 15 | "text": { 16 | "noChosen" : "No answer is chosen", 17 | "incomplete" : "Some correct answers missing", 18 | "rightAnswer" : "Right answer!", 19 | "wrongAnswer" : "Wrong answer!" 20 | } 21 | }; 22 | /** 23 | * ENumeration for button actions 24 | * 25 | * @type {{CHECK: string, EXPLAIN: string, SHOW: string}} 26 | */ 27 | var BUTTON_ACTIONS_ENUM = { 28 | CHECK : "check", 29 | EXPLAIN : "explain", 30 | SHOW : "show" 31 | }; 32 | /** 33 | * Generates random string of given length 34 | * 35 | * @param {Number} [length] 36 | * @returns {string} 37 | */ 38 | function randomString(length) { 39 | var CHARS; 40 | 41 | if (length === undefined) { 42 | length = 20; 43 | } 44 | CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 45 | var result = ""; 46 | for (var i = length; i > 0; --i) { 47 | result += CHARS[Math.round(Math.random() * (CHARS.length - 1))]; 48 | } 49 | return result; 50 | } 51 | /** 52 | * Asserts value is a String object 53 | * 54 | * @param {String} [value] 55 | * @returns {Bool} 56 | */ 57 | function isString(value) { 58 | return typeof value == 'string' || 59 | (!$.isArray(value) && (!!value && typeof value == 'object') && Object.prototype.toString.call(value) == '[object String]'); 60 | } 61 | 62 | /** 63 | * Template for the whole question 64 | * 65 | * @param {jQuery} $el 66 | * @returns {String} 67 | */ 68 | var questionTemplate = function ($el) { 69 | var html, question, multiple, $answers, $explanation, name, withExplanation; 70 | 71 | html = ""; 72 | 73 | question = $el.find("p").first().html(); 74 | multiple = isString($el.attr("multiple")); 75 | $answers = $el.find("answer"); 76 | $explanation = $el.find("explanation"); 77 | 78 | html += "
"; 79 | html += "

" + question + "

"; 80 | 81 | name = randomString(); 82 | $answers.each(function () { 83 | var $answer; 84 | 85 | $answer = $(this); 86 | html += answerTemplate($answer, multiple, name); 87 | }); 88 | withExplanation = $explanation.length > 0 && configuration.buttons.showExplanation === true; 89 | html += withExplanation ? explanationTemplate($explanation.html()) : ""; 90 | html += messageTemplate(); 91 | html += checkButtonTemplate(withExplanation); 92 | html += "
"; 93 | return html; 94 | }; 95 | /** 96 | * Generates html string for the check button 97 | * 98 | * @param {Boolean} withExplanation 99 | * @returns {String} 100 | */ 101 | var checkButtonTemplate = function (withExplanation) { 102 | var checkButton, showExplanationButton, showCorrectAnswers, html; 103 | 104 | html = ""; 105 | 106 | checkButton = "" + 107 | configuration.labels.check + 108 | ""; 109 | showExplanationButton = "" + 110 | configuration.labels.showExplanation + 111 | ""; 112 | showCorrectAnswers = "" + 113 | configuration.labels.showCorrect + 114 | ""; 115 | 116 | html += "
" + 117 | checkButton + 118 | (configuration.buttons.showExplanation && withExplanation ? showExplanationButton : "") + 119 | (configuration.buttons.showCorrect ? showCorrectAnswers : "") + 120 | "
"; 121 | 122 | return html; 123 | }; 124 | /** 125 | * Generates html string for the answer 126 | * 127 | * @param {jQuery} $el 128 | * @param {Boolean} multiple 129 | * @param {String} name 130 | * @returns {String} 131 | */ 132 | var answerTemplate = function ($el, multiple, name) { 133 | var correct, text, html, $options; 134 | 135 | correct = isString($el.attr("correct")); 136 | text = $el.text(); 137 | html = $el.html(); 138 | $options = $el.find("option"); 139 | if ($options.length > 0) { 140 | var result; 141 | 142 | result = "
" + "
"; 148 | return result; 149 | } else if (multiple) { 150 | return "
" + 151 | "
"; 154 | } else { 155 | return "
" + 156 | "
"; 159 | } 160 | }; 161 | /** 162 | * Generates html string for the message for explanation 163 | * 164 | * @param {String} text 165 | */ 166 | var explanationTemplate = function (text) { 167 | return "
" + 168 | (configuration.labels.explanationHeader && configuration.labels.explanationHeader.length > 0 ? 169 | "
" + configuration.labels.explanationHeader + "
" : "") + 170 | text + 171 | "
"; 172 | }; 173 | /** 174 | * Generates html string for the message for the answer 175 | */ 176 | var messageTemplate = function () { 177 | return "
"; 178 | }; 179 | /** 180 | * Analyzes answers to questions and takes action 181 | * 182 | * @param {jQuery} $question the question element to analyze 183 | * @param {Number} numberOfCorrect the number of correct answers 184 | * @param {Number} totalNumberOfAnswers total number of existing answers to question 185 | * @param {jQuery} $correctChecked correct answers checked 186 | * @param {jQuery} $incorrectChecked incorrect answers checked 187 | */ 188 | var analyseAnswers = function ($question, numberOfCorrect, totalNumberOfAnswers, $correctChecked, $incorrectChecked) { 189 | var numberIncorrectChecked, numberCorrectChecked; 190 | 191 | numberCorrectChecked = $correctChecked.length; 192 | numberIncorrectChecked = $incorrectChecked.length; 193 | 194 | //no selected 195 | if (numberCorrectChecked === 0 && numberIncorrectChecked === 0) { 196 | noAnswerChecked($question); 197 | return; 198 | } 199 | //mark answers 200 | markAnswersAsCorrectOrIncorrect($correctChecked, $incorrectChecked); 201 | //if incomplete 202 | if ($correctChecked.length < numberOfCorrect && $incorrectChecked.length === 0) { 203 | handleIncomplete($question); 204 | return; 205 | } 206 | //if correct 207 | if ($correctChecked.length === numberOfCorrect && $incorrectChecked.length === 0) { 208 | showExplanation($question); 209 | return; 210 | } 211 | }; 212 | /** 213 | * Handles correct state of the question 214 | * 215 | * @param {jQuery} $question 216 | */ 217 | var handleCorrect = function ($question) { 218 | var $message, $explanation; 219 | 220 | $message = getMessageElement($question); 221 | $explanation = getExplanationElement($question); 222 | $message.text(configuration.text.rightAnswer).removeClass("hidden"); 223 | $explanation.removeClass("hidden"); 224 | }; 225 | /** 226 | * Handles incorrect state of the question 227 | * 228 | * @param {jQuery} $question 229 | */ 230 | var handleIncorrect = function ($question) { 231 | var $message; 232 | 233 | $message = getMessageElement($question); 234 | $message.text(configuration.text.wrongAnswer).removeClass("hidden"); 235 | }; 236 | /** 237 | * Handles incorrect state of the question 238 | * 239 | * @param {jQuery} $question 240 | */ 241 | var handleIncomplete = function ($question) { 242 | var $message; 243 | 244 | $message = getMessageElement($question); 245 | $message.text(configuration.text.incomplete).removeClass("hidden").addClass("error"); 246 | }; 247 | /** 248 | * Action to take when no answer is checked in the question 249 | * 250 | * @param {jQuery} $question 251 | */ 252 | var noAnswerChecked = function ($question) { 253 | var $message; 254 | 255 | $message = $question.find(".quiz-question-message"); 256 | $message.show().text(configuration.text.noChosen).addClass("error"); 257 | }; 258 | /** 259 | * Marks answers with corresponding classes 260 | * 261 | * @param {jQuery} $correctChecked 262 | * @param {jQuery} [$incorrectChecked] 263 | */ 264 | var markAnswersAsCorrectOrIncorrect = function ($correctChecked, $incorrectChecked) { 265 | $correctChecked.each(function () { 266 | $(this).closest(".quiz-answer").find(".quiz-answer-check").addClass("correct"); 267 | }); 268 | if ($incorrectChecked) { 269 | $incorrectChecked.each(function () { 270 | $(this).closest(".quiz-answer").find(".quiz-answer-check").addClass("incorrect"); 271 | }); 272 | } 273 | }; 274 | /** 275 | * Returns all answers elements of the question 276 | * 277 | * @param {jQuery} $question 278 | * @returns {jQuery} 279 | */ 280 | var getAllAnswersElements = function ($question) { 281 | return $question.find(".quiz-answer"); 282 | }; 283 | /** 284 | * Returns message element of the question 285 | * 286 | * @param {jQuery} $question 287 | * @returns {jQuery} 288 | */ 289 | var getMessageElement = function ($question) { 290 | return $question.find(".quiz-question-message"); 291 | }; 292 | /** 293 | * Returns explanation element of the question 294 | * 295 | * @param {jQuery} $question 296 | * @returns {jQuery} 297 | */ 298 | var getExplanationElement = function ($question) { 299 | return $question.find(".quiz-question-explanation"); 300 | }; 301 | /** 302 | * Clears question state - clears messages and correct/incorrect states 303 | * 304 | * @param {jQuery} $question question element to clear 305 | */ 306 | var clearQuestionState = function ($question) { 307 | var $answers, $message, $explanation; 308 | 309 | $answers = getAllAnswersElements($question); 310 | $message = getMessageElement($question); 311 | $explanation = getExplanationElement($question); 312 | $answers.each(function () { 313 | $(this).find(".quiz-answer-check").removeClass("correct").removeClass("incorrect"); 314 | }); 315 | $message.text("").addClass("hidden"); 316 | $explanation.addClass("hidden"); 317 | }; 318 | /** 319 | * Handles click on all buttons 320 | * @param {Event} ev 321 | */ 322 | var onClickButton = function (ev) { 323 | var $target, $question, action; 324 | 325 | ev.preventDefault(); 326 | ev.stopPropagation(); 327 | $target = $(ev.currentTarget); 328 | $question = $target.closest(".quiz-question"); 329 | action = $target.data("action"); 330 | 331 | switch (action) { 332 | case BUTTON_ACTIONS_ENUM.CHECK: 333 | clearQuestionState($question); 334 | checkAnswers($question); 335 | break; 336 | case BUTTON_ACTIONS_ENUM.EXPLAIN: 337 | showExplanation($question); 338 | break; 339 | case BUTTON_ACTIONS_ENUM.SHOW: 340 | clearQuestionState($question); 341 | showCorrect($question); 342 | break; 343 | default: 344 | checkAnswers($question); 345 | } 346 | }; 347 | /** 348 | * Shows an explanation 349 | * 350 | * @param {jQuery} $question 351 | */ 352 | var showExplanation = function ($question) { 353 | var $explanation; 354 | 355 | $explanation = getExplanationElement($question); 356 | $explanation.removeClass("hidden"); 357 | }; 358 | /** 359 | * Shows correct answers 360 | * 361 | * @param {jQuery} $question 362 | */ 363 | var showCorrect = function ($question) { 364 | var $correct; 365 | 366 | $correct = $question.find(".quiz-answer input[data-correct=true]"); 367 | if ($correct.length === 0) { 368 | $correct = $question.find(".quiz-answer option[data-correct=true]"); 369 | } 370 | $correct.prop("checked", true).prop("selected", true); 371 | markAnswersAsCorrectOrIncorrect($correct); 372 | }; 373 | /** 374 | * Handles check button click 375 | */ 376 | var checkAnswers = function ($question) { 377 | var numberOfCorrect, totalNumberOfAnswers, $incorrectChecked, $correctChecked; 378 | 379 | numberOfCorrect = $question.find(".quiz-answer input[data-correct=true]").length; 380 | totalNumberOfAnswers = $question.find(".quiz-answer").length; 381 | $correctChecked = $question.find(".quiz-answer input[data-correct=true]:checked"); 382 | if ($correctChecked.length === 0) { 383 | $correctChecked = $question.find(".quiz-answer select option[data-correct=true]:selected"); 384 | } 385 | $incorrectChecked = $question.find(".quiz-answer input[data-correct=false]:checked"); 386 | if ($incorrectChecked.length === 0) { 387 | $incorrectChecked = $question.find(".quiz-answer select option[data-correct=false]:selected"); 388 | } 389 | 390 | analyseAnswers($question, numberOfCorrect, totalNumberOfAnswers, $correctChecked, $incorrectChecked); 391 | }; 392 | /** 393 | * Transforms quiz element into appropriate markup 394 | * 395 | * @param {jQuery} $quiz 396 | */ 397 | var prepareQuiz = function ($quiz) { 398 | var name, $questions, html; 399 | 400 | if ($quiz.data("prepared")) { 401 | return; 402 | } 403 | // Create HTML for the quiz 404 | html = ""; 405 | name = $quiz.prop("name") || $quiz.attr("name") || ""; 406 | $questions = $quiz.find("question"); 407 | 408 | html += "
"; 409 | html += "
" + name + "
"; 410 | $questions.each(function () { 411 | html += questionTemplate($(this)); 412 | }); 413 | html += "
"; 414 | $quiz.html(html); 415 | $quiz.data("prepared", true); 416 | // Assign events 417 | $quiz.find(".buttons .btn").click(onClickButton); 418 | }; 419 | /** 420 | * Starting point 421 | */ 422 | var init = function () { 423 | if (!isStarted) { 424 | return; 425 | } 426 | $("quiz").each(function () { 427 | prepareQuiz($(this)); 428 | }); 429 | }; 430 | 431 | configuration = $.extend(true, configuration, LABELS_AND_TEXT_ENUM, {}); 432 | 433 | gitbook.events.bind("start", function (e, config) { 434 | isStarted = true; 435 | config = config.quiz || {}; 436 | configuration = $.extend(true, configuration, LABELS_AND_TEXT_ENUM, config); 437 | init(); 438 | }); 439 | 440 | gitbook.events.bind("page.change", init); 441 | }); -------------------------------------------------------------------------------- /book/style.css: -------------------------------------------------------------------------------- 1 | .quiz { 2 | padding-left: 10px; 3 | margin-bottom: 20px; 4 | } 5 | /* QUIZ NAME */ 6 | .quiz-name { 7 | text-align: center; 8 | font-weight: bold; 9 | font-size: 1.1em; 10 | } 11 | /* QUIZ QUESTION */ 12 | .quiz-question { 13 | font-weight: 600; 14 | color: #3c3c3c; 15 | border-bottom: 1px solid #E7EAED; 16 | padding-bottom: 15px; 17 | } 18 | .quiz-question-text { 19 | font-weight: 600; 20 | color: #3C3C3C; 21 | } 22 | .book.color-theme-1 .quiz-question-text{ 23 | color: #704214; 24 | border-bottom:1px solid #704214; 25 | } 26 | .book.color-theme-2 .quiz-question-text{ 27 | color: #bdcadb; 28 | border-bottom:1px solid #bdcadb; 29 | } 30 | /* ANSWER */ 31 | .quiz-answer { 32 | margin-left: 20px; 33 | color: #3c3c3c; 34 | font-weight: 400; 35 | } 36 | .quiz-answer select { 37 | width: 58%; 38 | } 39 | .book.color-theme-1 .quiz-answer { 40 | color: #704214; 41 | } 42 | .book.color-theme-2 .quiz-answer { 43 | color: #bdcadb; 44 | } 45 | .quiz-answer input { 46 | margin-right: 10px; 47 | } 48 | .quiz-answer label { 49 | font-weight: normal; 50 | } 51 | /* EXPLANATION */ 52 | .quiz-question-explanation { 53 | font-size: 1.6rem; 54 | margin: 20px 0; 55 | border: 1px solid #ccc; 56 | padding: 9px 15px 20px; 57 | background: #fbfbfb; 58 | box-shadow: inset 0 0 0 1px #eee; 59 | border-radius: 3px; 60 | } 61 | .quiz-question-explanation-header { 62 | color: #aaa; 63 | text-transform: uppercase; 64 | font-weight: bold; 65 | font-style: normal; 66 | margin-bottom: 18px; 67 | } 68 | .book.color-theme-1 .quiz-question-explanation { 69 | border-color: #704214; 70 | background-color: #F3EACB; 71 | color: #704214; 72 | } 73 | .book.color-theme-1 .quiz-question-explanation-header { 74 | color: #8e5916; 75 | } 76 | .book.color-theme-2 .quiz-question-explanation { 77 | border-color: #2c3043; 78 | background-color: #1c1f2b; 79 | color: #BDCADB; 80 | } 81 | .book.color-theme-2 .quiz-question-explanation-header { 82 | color: #E7F2FF; 83 | } 84 | /* MESSAGE */ 85 | .quiz-question-message { 86 | font-size: 0.8em; 87 | } 88 | .hidden { 89 | display: none; 90 | } 91 | .correct { 92 | color: green; 93 | } 94 | .incorrect, .error { 95 | color: red; 96 | } 97 | .correct:before { 98 | content: " \2714" 99 | } 100 | .incorrect:before { 101 | content: " \2718" 102 | } 103 | /* buttons */ 104 | .quiz .buttons .btn, .quiz .quiz-answer select { 105 | background-color: #fff; 106 | border-radius: 4px; 107 | margin-top: 5px; 108 | margin-bottom: 5px; 109 | display: inline-block; 110 | padding: 6px 12px; 111 | font-size: 14px; 112 | font-weight: 400; 113 | line-height: 1.42857143; 114 | text-align: center; 115 | white-space: nowrap; 116 | vertical-align: middle; 117 | cursor: pointer; 118 | border: 1px solid #ccc; 119 | margin-right: 10px; 120 | } 121 | .quiz .buttons .btn:hover, .quiz .buttons .btn:active { 122 | color: #333; 123 | background-color: #e6e6e6; 124 | border-color: #adadad; 125 | } 126 | .quiz .buttons .btn:active { 127 | outline: 0; 128 | box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 129 | } 130 | /* buttons for theme 1 */ 131 | .book.color-theme-1 .quiz .buttons .btn { 132 | background-color: #F3EACB; 133 | color: #704214; 134 | border-color: #704214; 135 | } 136 | .book.color-theme-1 .quiz .buttons .btn:hover, .quiz .buttons .btn:active { 137 | color: #F3EACB; 138 | background-color: #704214; 139 | } 140 | .book.color-theme-1 .quiz .buttons .btn:active { 141 | outline: 0; 142 | box-shadow: inset 0 3px 5px rgba(103, 59, 19, 0.12); 143 | } 144 | /* buttons for theme 2 */ 145 | .book.color-theme-2 .quiz .buttons .btn { 146 | color: #BDCADB; 147 | background-color: transparent; 148 | } 149 | .book.color-theme-2 .quiz .buttons .btn:hover, .quiz .buttons .btn:active { 150 | color: #1C1F2B; 151 | background-color: #BDCADB; 152 | } 153 | .book.color-theme-2 .quiz .buttons .btn:active { 154 | outline: 0; 155 | box-shadow: inset 0 3px 5px rgba(80, 86, 100, 0.12); 156 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | website: { 3 | assets: "./book", 4 | js: [ 5 | "script.js" 6 | ], 7 | css: [ 8 | "style.css" 9 | ] 10 | } 11 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitbook-plugin-quiz", 3 | "version": "0.3.0", 4 | "description": "Allows to insert simple quizes into gitbook", 5 | "keywords": [ 6 | "quiz", 7 | "gitbook" 8 | ], 9 | "author": { 10 | "name": "Olga Filipova", 11 | "email": "chudaol@gmail.com" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/chudaol/gitbook-plugin-quiz" 16 | }, 17 | "engines": { 18 | "gitbook": ">1.x.x" 19 | }, 20 | "dependencies": { 21 | "jquery": "^2.1.4", 22 | "underscore": "^1.8.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chudaol/gitbook-plugin-quiz/ac979c009908e4df3cf34fe66b6fc76bbecaf1b5/screenshot.png --------------------------------------------------------------------------------