first import book

This commit is contained in:
Johann Dreo 2015-03-03 15:56:44 +01:00
commit dfd9c869d5
233 changed files with 47797 additions and 0 deletions

View file

@ -0,0 +1,468 @@
var elem; // current audio element playing
var currIndex; // current index
var len; // current length of audio files for tour
var buttonCount; // number of audio tour buttons
var aname; // the audio file name
var ahash; // hash of the audio file name to the lines to highlight
var theDivid; // div id
var afile; // file name for audio
var playing = false; // flag to say if playing or not
var tourName;
String.prototype.replaceAll = function (target, replacement) {
return this.split(target).join(replacement);
};
// function to display the audio tours
var createAudioTourHTML = function (divid, code, bnum, audio_text) {
// Replacing has been done here to make sure special characters in the code are displayed correctly
code = code.replaceAll("*doubleq*", "\"");
code = code.replaceAll("*singleq*", "'");
code = code.replaceAll("*open*", "(");
code = code.replaceAll("*close*", ")");
code = code.replaceAll("*nline*", "<br/>");
var codeArray = code.split("<br/>");
var audio_hash = new Array();
var bval = new Array();
var atype = audio_text.replaceAll("*doubleq*", "\"");
var audio_type = atype.split("*atype*");
for (var i = 0; i < audio_type.length - 1; i++) {
audio_hash[i] = audio_type[i];
var aword = audio_type[i].split(";");
bval.push(aword[0]);
}
var first = "<pre><div id='" + divid + "_l1'>" + "1. " + codeArray[0] + "</div>";
num_lines = codeArray.length;
for (var i = 1; i < num_lines; i++) {
var sec = "<div id='" + divid + "_l" + (i + 1) + "'>" + (i + 1) + ". " + codeArray[i] + "</div>";
var next = first.concat(sec);
first = next;
}
first = first + "</pre>"
//laying out the HTML content
var bcount = 0;
var html_string = "<div class='modal-lightsout'></div><div class='modal-profile'><h3>Take an audio tour!</h3><div class='modal-close-profile'></div><p id='windowcode'></p><p id='" + divid + "_audiocode'></p>";
html_string += "<p id='status'></p>";
html_string += "<input type='image' src='../_static/first.png' width='25' id='first_audio' name='first_audio' title='Play first audio in tour' alt='Play first audio in tour' disabled/>" + "<input type='image' src='../_static/prev.png' width='25' id='prev_audio' name='prev_audio' title='Play previous audio in tour' alt='Play previous audio in tour' disabled/>" + "<input type='image' src='../_static/pause.png' width='25' id='pause_audio' name='pause_audio' title='Pause current audio' alt='Pause current audio' disabled/><input type='image' src='../_static/next.png' width ='25' id='next_audio' name='next_audio' title='Play next audio in tour' alt='Play next audio in tour' disabled/><input type='image' src='../_static/last.png' width ='25' id='last_audio' name='last_audio' title='Play last audio in tour' alt='Play last audio in tour' disabled/><br/>";
for (var i = 0; i < audio_type.length - 1; i++) {
html_string += "<input type='button' style='margin-right:5px;' class='btn btn-default btn-sm' id='button_audio_" + i + "' name='button_audio_" + i + "' value=" + bval[i] + " />";
bcount++;
}
//html_string += "<p id='hightest'></p><p id='hightest1'></p><br/><br/><p id='test'></p><br/><p id='audi'></p></div>";
html_string += "</div>";
$('#cont').html(html_string);
$('#windowcode').html(first);
// Position modal box in the center of the page
$.fn.center = function () {
this.css("position", "absolute");
// y position
this.css("top", ($(window).scrollTop() + $(navbar).height() + 10 + "px"));
// show window on the left so that you can see the output from the code still
this.css("left", ($(window).scrollLeft() + "px"));
return this;
}
$(".modal-profile").center();
$('.modal-profile').fadeIn("slow");
//$('.modal-lightsout').css("height", $(document).height());
$('.modal-lightsout').fadeTo("slow", .5);
$('.modal-close-profile').show();
// closes modal box once close link is clicked, or if the lights out divis clicked
$('.modal-close-profile, .modal-lightsout').click(function () {
if (playing) {
elem.pause();
}
//log change to db
logBookEvent({'event': 'Audio', 'change': 'closeWindow', 'div_id': divid});
$('.modal-profile').fadeOut("slow");
$('.modal-lightsout').fadeOut("slow");
});
// Accommodate buttons for a maximum of five tours
$('#' + 'button_audio_0').click(function () {
tour(divid, audio_hash[0], bcount);
});
$('#' + 'button_audio_1').click(function () {
tour(divid, audio_hash[1], bcount);
});
$('#' + 'button_audio_2').click(function () {
tour(divid, audio_hash[2], bcount);
});
$('#' + 'button_audio_3').click(function () {
tour(divid, audio_hash[3], bcount);
});
$('#' + 'button_audio_4').click(function () {
tour(divid, audio_hash[4], bcount);
});
// handle the click to go to the next audio
$('#first_audio').click(function () {
firstAudio();
});
// handle the click to go to the next audio
$('#prev_audio').click(function () {
prevAudio();
});
// handle the click to pause or play the audio
$('#pause_audio').click(function () {
pauseAndPlayAudio();
});
// handle the click to go to the next audio
$('#next_audio').click(function () {
nextAudio();
});
// handle the click to go to the next audio
$('#last_audio').click(function () {
lastAudio();
});
// make the image buttons look disabled
$("#first_audio").css('opacity', 0.25);
$("#prev_audio").css('opacity', 0.25);
$("#pause_audio").css('opacity', 0.25);
$("#next_audio").css('opacity', 0.25);
$("#last_audio").css('opacity', 0.25);
};
var tour = function (divid, audio_type, bcount) {
// set globals
buttonCount = bcount;
theDivid = divid;
// enable prev, pause/play and next buttons and make visible
$('#first_audio').removeAttr('disabled');
$('#prev_audio').removeAttr('disabled');
$('#pause_audio').removeAttr('disabled');
$('#next_audio').removeAttr('disabled');
$('#last_audio').removeAttr('disabled');
$("#first_audio").css('opacity', 1.0);
$("#prev_audio").css('opacity', 1.0);
$("#pause_audio").css('opacity', 1.0);
$("#next_audio").css('opacity', 1.0);
$("#last_audio").css('opacity', 1.0);
// disable tour buttons
for (var i = 0; i < bcount; i++)
$('#button_audio_' + i).attr('disabled', 'disabled');
var atype = audio_type.split(";");
var name = atype[0].replaceAll("\"", " ");
tourName = name;
$('#status').html("Starting the " + name);
//log tour type to db
logBookEvent({'event': 'Audio', 'tour type': name, 'div_id': divid});
var max = atype.length;
var str = "";
ahash = new Array();
aname = new Array();
for (i = 1; i < max - 1; i++) {
var temp = atype[i].split(":");
var temp_line = temp[0];
var temp_aname = temp[1];
var akey = temp_aname.substring(1, temp_aname.length);
var lnums = temp_line.substring(1, temp_line.length);
//alert("akey:"+akey+"lnum:"+lnums);
// str+="<audio id="+akey+" preload='auto'><source src='http://ice-web.cc.gatech.edu/ce21/audio/"+
// akey+".mp3' type='audio/mpeg'><source src='http://ice-web.cc.gatech.edu/ce21/audio/"+akey+
// ".ogg' type='audio/ogg'>Your browser does not support the audio tag</audio>";
str += "<audio id=" + akey + " preload='auto' ><source src='../_static/audio/" + akey +
".wav' type='audio/wav'><source src='../_static/audio/" +
akey + ".mp3' type='audio/mpeg'><br />Your browser does not support the audio tag</audio>";
ahash[akey] = lnums;
aname.push(akey);
}
var ahtml = "#" + divid + "_audiocode";
$(ahtml).html(str); // set the html to the audio tags
len = aname.length; // set the number of audio file in the tour
// start at the first audio
currIndex = 0;
// play the first audio in the tour
playCurrIndexAudio();
};
function handlePlaying() {
// if playing audio pause it
if (playing) {
elem.pause();
// unbind current ended
$('#' + afile).unbind('ended');
// unhighlight the prev lines
unhighlightLines(theDivid, ahash[aname[currIndex]]);
}
}
var firstAudio = function () {
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'first', 'div_id': theDivid});
// move to the first audio
currIndex = 0;
// start at the first audio
playCurrIndexAudio();
};
var prevAudio = function () {
// if there is a previous audio
if (currIndex > 0)
{
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'prev', 'div_id': theDivid});
// move to previous to the current (but the current index has moved to the next)
currIndex = currIndex - 1;
// start at the prev audio
playCurrIndexAudio();
}
};
var nextAudio = function () {
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'next', 'div_id': theDivid});
// if not at the end
if (currIndex < (len - 1))
{
// start at the next audio
currIndex = currIndex + 1;
playCurrIndexAudio();
}
else if (currIndex == (len - 1))
{
handleTourEnd();
}
};
var lastAudio = function () {
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'last', 'div_id': theDivid});
// move to the last audio
currIndex = len - 1;
// start at last
playCurrIndexAudio();
};
// play the audio at the current index
var playCurrIndexAudio = function () {
// set playing to false
playing = false;
// play the current audio and highlight the lines
playaudio(currIndex, aname, theDivid, ahash);
};
// handle the end of the tour
var handleTourEnd = function() {
$('#status').html(" The " + tourName + " Ended");
// disable the prev, pause/play, and next buttons and make them more invisible
$('#first_audio').attr('disabled', 'disabled');
$('#prev_audio').attr('disabled', 'disabled');
$('#pause_audio').attr('disabled', 'disabled');
$('#next_audio').attr('disabled', 'disabled');
$('#last_audio').attr('disabled', 'disabled');
$("#first_audio").css('opacity', 0.25);
$("#prev_audio").css('opacity', 0.25);
$("#pause_audio").css('opacity', 0.25);
$("#next_audio").css('opacity', 0.25);
$("#last_audio").css('opacity', 0.25);
// enable the tour buttons
for (var j = 0; j < buttonCount; j++)
$('#button_audio_' + j).removeAttr('disabled');
}
// only call this one after the first time
var outerAudio = function () {
// unbind ended
$('#' + afile).unbind('ended');
// set playing to false
playing = false;
// unhighlight previous lines from the last audio
unhighlightLines(theDivid, ahash[aname[currIndex]]);
// increment the currIndex to point to the next one
currIndex++;
// if the end of the tour reset the buttons
if (currIndex == len) {
handleTourEnd();
}
// else not done yet so play the next audio
else {
// play the audio at the current index
playCurrIndexAudio();
}
};
// play the audio now that it is ready
var playWhenReady = function(afile,divid,ahash) {
// unbind current
$('#' + afile).unbind('canplaythrough');
//console.log("in playWhenReady " + elem.duration);
$('#status').html("Playing the " + tourName);
elem.currentTime = 0;
highlightLines(divid, ahash[afile]);
$('#' + afile).bind('ended',function() {
outerAudio();
});
playing = true;
elem.play();
};
// play the audio at the specified index i and set the duration and highlight the lines
var playaudio = function (i, aname, divid, ahash) {
afile = aname[i];
elem = document.getElementById(afile);
// if this isn't ready to play yet - no duration yet then wait
//console.log("in playaudio " + elem.duration);
if (isNaN(elem.duration) || elem.duration == 0)
{
// set the status
$('#status').html("Loading audio. Please wait.");
$('#' + afile).bind('canplaythrough',function() {
playWhenReady(afile,divid,ahash);
});
}
// otherwise it is ready so play it
else
playWhenReady(afile,divid,ahash);
};
// pause if playing and play if paused
var pauseAndPlayAudio = function () {
var btn = document.getElementById('pause_audio');
// if paused and clicked then continue from current
if (elem.paused) {
// calcualte the time left to play in milliseconds
counter = (elem.duration - elem.currentTime) * 1000;
elem.play(); // start the audio from current spot
document.getElementById("pause_audio").src = "../_static/pause.png";
document.getElementById("pause_audio").title = "Pause current audio";
//log change to db
logBookEvent({'event': 'Audio', 'change': 'play', 'div_id': theDivid});
}
// if audio was playing pause it
else if (playing) {
elem.pause(); // pause the audio
document.getElementById("pause_audio").src = "../_static/play.png";
document.getElementById("pause_audio").title = "Play paused audio";
//log change to db
logBookEvent({'event': 'Audio', 'change': 'pause', 'div_id': theDivid});
}
};
// process the lines
var processLines = function (divid, lnum, color) {
var comma = lnum.split(",");
if (comma.length > 1) {
for (i = 0; i < comma.length; i++) {
setBackroundForLines(divid, comma[i], color);
}
}
else {
setBackroundForLines(divid, lnum, color);
}
};
// unhighlight the lines - set the background back to transparent
var unhighlightLines = function (divid, lnum) {
processLines(divid, lnum, 'transparent');
};
// highlight the lines - set the background to a yellow color
var highlightLines = function (divid, lnum) {
processLines(divid, lnum, '#ffff99');
};
// set the background to the passed color
var setBackroundForLines = function (divid, lnum, color) {
var hyphen = lnum.split("-");
// if a range of lines
if (hyphen.length > 1) {
var start = parseInt(hyphen[0]);
var end = parseInt(hyphen[1]) + 1;
for (var k = start; k < end; k++) {
//alert(k);
var str = "#" + divid + "_l" + k;
if ($(str).text() != "")
$(str).css('background-color', color);
//$(str).effect("highlight",{},(dur*1000)+4500);
}
}
else {
//alert(lnum);
var str = "#" + divid + "_l" + lnum;
$(str).css('background-color', color);
//$(str).effect("highlight",{},(dur*1000)+4500);
}
};

View file

@ -0,0 +1,204 @@
Animator = function(m, v, divid)
{
this.model = m
this.viewer = v
this.timer = null
this.cursor = -1
this.sc = document.getElementById(divid+"_canvas")
this.ctx = this.sc.getContext("2d")
this.sc.width = this.sc.width
this.speed = 75
this.script = this.model.init() //does the animation and sends script back
this.viewer.init(this.ctx)
}
Animator.prototype.getContext=function()
{
return this.ctx
}
Animator.prototype.incCursor=function()
{
if (this.cursor < this.script.length-1)
this.cursor = this.cursor + 1
}
Animator.prototype.decCursor=function()
{
if (this.cursor > 0)
this.cursor = this.cursor -1
}
Animator.prototype.getCursor=function()
{
return this.cursor
}
Animator.prototype.setCursor=function(newc)
{
this.cursor = newc
}
Animator.prototype.run = function(animobj)
{
if (this.timer == null)
this.timer = setInterval(animobj+".forward()",this.speed)
}
Animator.prototype.stop = function()
{
clearInterval(this.timer)
this.timer=null
}
Animator.prototype.forward = function()
{
this.incCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
if (this.getCursor() == this.script.length-1 && this.timer != null)
{
clearInterval(this.timer)
this.timer = null
}
}
Animator.prototype.backward = function()
{
this.decCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.end = function()
{
this.setCursor(this.script.length-1)
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.begin = function()
{
this.setCursor(0)
this.sc.width=this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.init = function()
{
this.setCursor(0)
this.sc.width = this.sc.width
this.viewer.render(this.script[0])
}
init1 = function()
{
a = new Animator(new BubbleSortModel(), new BarViewer())
a.init()
}
init2 = function()
{
a = new Animator(new BubbleSortModel(), new ScatterViewer())
a.init()
}
init3 = function()
{
a = new Animator(new BubbleSortModel(), new BoxViewer())
a.init()
}
init4 = function()
{
a = new Animator(new SelectionSortModel(), new BarViewer())
a.init()
}
init5 = function()
{
a = new Animator(new SelectionSortModel(), new ScatterViewer())
a.init()
}
init6 = function()
{
a = new Animator(new SelectionSortModel(), new BoxViewer())
a.init()
}
init7 = function()
{
a = new Animator(new InsertionSortModel(), new BarViewer())
a.init()
}
init8 = function()
{
a = new Animator(new InsertionSortModel(), new ScatterViewer())
a.init()
}
init9 = function()
{
a = new Animator(new InsertionSortModel(), new BoxViewer())
a.init()
}
init10 = function()
{
a = new Animator(new ShellSortModel(), new BarViewer())
a.init()
}
init11 = function()
{
a = new Animator(new ShellSortModel(), new ScatterViewer())
a.init()
}
init12 = function()
{
a = new Animator(new ShellSortModel(), new BoxViewer())
a.init()
}
init13 = function()
{
a = new Animator(new MergeSortModel(), new BarViewer())
a.init()
}
init14 = function()
{
a = new Animator(new MergeSortModel(), new ScatterViewer())
a.init()
}
init15 = function()
{
a = new Animator(new MergeSortModel(), new BoxViewer())
a.init()
}
init16 = function()
{
a = new Animator(new QuickSortModel(), new BarViewer())
a.init()
}
init17 = function()
{
a = new Animator(new QuickSortModel(), new ScatterViewer())
a.init()
}
init18 = function()
{
a = new Animator(new QuickSortModel(), new BoxViewer())
a.init()
}

366
book/common/js/assess.js Normal file
View file

@ -0,0 +1,366 @@
var add_button = function (divid, expected, feedArray) {
var bdiv = divid + "_bt";
var element = document.createElement("input");
element.setAttribute("type", "button");
element.setAttribute("value", "Check Me!");
element.setAttribute("name", "do check");
element.onclick = function () {
checkMCMFRandom(divid, expected, feedArray);
};
$("#" + bdiv).html(element);
};
var feedbackMCMFRandom = function (divid, correct, feedbackText) {
if (correct) {
$(divid).html('Correct!! ' + feedbackText);
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class','alert alert-success');
} else {
if (feedbackText == null) {
feedbackText = '';
}
$(divid).html("Incorrect. " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class','alert alert-danger')
}
};
var checkFIBStorage = function (divid, blankid, expected, feedback, casi) {
var given = document.getElementById(blankid).value;
// update number of trials??
// log this to the db
modifiers = '';
if (casi) {
modifiers = 'i'
}
var patt = RegExp(expected, modifiers);
var isCorrect = patt.test(given);
if (!isCorrect) {
fbl = feedback;
for (var i = 0; i < fbl.length; i++) {
patt = RegExp(fbl[i][0]);
if (patt.test(given)) {
feedback = fbl[i][1];
break;
}
}
}
// store the answer in local storage
var storage_arr = new Array();
storage_arr.push(given);
storage_arr.push(expected);
localStorage.setItem(divid, storage_arr.join(";"));
feedBack('#' + divid + '_feedback', isCorrect, feedback);
var answerInfo = 'answer:' + given + ":" + (isCorrect ? 'correct' : 'no');
logBookEvent({'event': 'fillb', 'act': answerInfo, 'div_id': divid});
document.getElementById(divid + '_bcomp').disabled = false;
};
var feedBack = function (divid, correct, feedbackText) {
if (correct) {
$(divid).html('You are Correct!');
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class','alert alert-success');
} else {
if (feedbackText == null) {
feedbackText = '';
}
$(divid).html("Incorrect. " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class','alert alert-danger');
}
};
/*
Multiple Choice with Feedback for each answer
*/
var feedBackMCMF = function (divid, correct, feedbackText) {
if (correct) {
$(divid).html('Correct!! ' + feedbackText);
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class','alert alert-success');
} else {
if (feedbackText == null) {
feedbackText = '';
}
$(divid).html("Incorrect. " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class','alert alert-danger');
}
};
/*
Multiple Choice with Multiple Answers
*/
var feedBackMCMA = function (divid, numCorrect, numNeeded, numGiven, feedbackText) {
var answerStr = "answers";
if (numGiven == 1) answerStr = "answer";
if (numCorrect == numNeeded && numNeeded == numGiven) {
$(divid).html('Correct! <br />' + feedbackText);
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class', 'alert alert-success');
} else {
$(divid).html("Incorrect. " + "You gave " + numGiven +
" " + answerStr + " and got " + numCorrect + " correct of " +
numNeeded + " needed.<br /> " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class', 'alert alert-danger');
}
};
/* Randomize options */
var createHTML_MCMFRandom = function (divid, answerString, feedString, corrAnswer) {
var i, j, k, l, len;
var arr = new Array();
var fr = new Array();
var alpha = ['a', 'b', 'c', 'd', 'e'];
var ansArray = new Array();
var feedArray = new Array();
var hash = new Array();
arr = answerString.split("*separator*");
fr = feedString.split("*separator*");
for (j = 0; j < arr.length - 1; j++) {
ansArray[j] = arr[j];
hash[ansArray[j]] = fr[j];
}
i = ansArray.length;
len = i;
if (i == 0) return false;
while (--i) {
var j = Math.floor(Math.random() * ( i + 1 ));
var tempi = ansArray[i];
var tempj = ansArray[j];
ansArray[i] = tempj;
ansArray[j] = tempi;
}
for (i = 0; i < ansArray.length; i++) {
k = ansArray[i];
feedArray[i] = hash[k];
}
for (l = 0; l < len; l++) {
var rad = "<input type='radio' name='group1' value='" + l + "'/>" + "<label for= 'opt_" + l + "'>" + " " + alpha[l] + ") " + ansArray[l] + "</label><br />";
var opdiv = divid + "_op" + (l + 1);
$("#" + opdiv).html(rad);
}
var index = ansArray.indexOf(corrAnswer);
add_button(divid, index, feedArray);
return true;
};
var checkMCMFRandom = function (divid, expected, feed) {
var given;
var feedback = null;
var buttonObjs = document.forms[divid + "_form"].elements.group1;
for (var i = buttonObjs.length - 1; i >= 0; i--) {
if (buttonObjs[i].checked) {
given = buttonObjs[i].value;
feedback = feed[i];
}
}
// log the answer
var answerInfo = 'answer:' + given + ":" + (given == expected ? 'correct' : 'no');
logBookEvent({'event': 'mChoice', 'act': answerInfo, 'div_id': divid});
// give the user feedback
feedbackMCMFRandom('#' + divid + '_feedback', given == expected, feedback);
};
/* Local Storage */
var resetPage = function (divid) {
var i;
var id = new Array();
var keyArr = new Array();
id = divid.split("_");
var pageNum = id[1];
location.reload();
//erasing data from local storage
var len = localStorage.length;
if (len > 0) {
for (i = 0; i < len; i++) {
var key = localStorage.key(i);
var str = key.substring(0, 1);
if (str === pageNum) {
keyArr.push(key);
}
}
}
for (i = 0; i < keyArr.length; i++)
localStorage.removeItem(keyArr[i]);
};
var checkRadio = function (divid) {
// This function repopulates MCMF questions with a user's previous answers,
// which were previously stored into local storage
var qnum = divid;
var len = localStorage.length;
//retrieving data from local storage
if (len > 0) {
for (var i = 0; i < len; i++) {
var key = localStorage.key(i);
if (key === qnum) {
var ex = localStorage.getItem(key);
var arr = ex.split(";");
var str = key + "_opt_" + arr[0];
$("#" + str).attr("checked", "true");
document.getElementById(divid + '_bcomp').disabled = false;
}
}
}
};
var checkMultipleSelect = function (divid) {
// This function repopulates MCMA questions with a user's previous answers,
// which were stored into local storage.
var qnum = divid;
var len = localStorage.length;
if (len > 0) {
for (var i = 0; i < len; i++) {
var key = localStorage.key(i);
if (key === qnum) {
var ex = localStorage.getItem(key);
var arr = ex.split(";");
var answers = arr[0].split(",");
for (var a = 0; a < answers.length; a++) {
var str = key + "_opt_" + answers[a];
$("#" + str).attr("checked", "true");
document.getElementById(divid + '_bcomp').disabled = false;
}
}
}
}
};
var checkPreviousFIB = function (divid) {
// This function repoplulates FIB questions with a user's previous answers,
// which were stored into local storage
var qnum = divid;
var len = localStorage.length;
if (len > 0) {
for (var i = 0; i < len; i++) {
var key = localStorage.key(i);
if (key === qnum) {
var ex = localStorage.getItem(key);
var arr = ex.split(";");
var str = key + "_ans1";
$("#" + str).attr("value", arr[0]);
document.getElementById(divid + '_bcomp').disabled = false;
}
}
}
};
var checkMCMAStorage = function (divid, expected, feedbackArray) {
var given;
var feedback = "";
var correctArray = expected.split(",");
correctArray.sort();
var givenArray = [];
var correctCount = 0;
var correctIndex = 0;
var givenIndex = 0;
var givenlog = '';
var buttonObjs = document.forms[divid + "_form"].elements.group1;
// loop through the checkboxes
for (var i = 0; i < buttonObjs.length; i++) {
if (buttonObjs[i].checked) { // if checked box
given = buttonObjs[i].value; // get value of this button
givenArray.push(given) // add it to the givenArray
feedback += given + ": " + feedbackArray[i] + "<br />"; // add the feedback
givenlog += given + ",";
}
}
// sort the given array
givenArray.sort();
while (correctIndex < correctArray.length &&
givenIndex < givenArray.length) {
if (givenArray[givenIndex] < correctArray[correctIndex]) {
givenIndex++;
}
else if (givenArray[givenIndex] == correctArray[correctIndex]) {
correctCount++;
givenIndex++;
correctIndex++;
}
else {
correctIndex++;
}
} // end while
// save the data into local storage
var storage_arr = new Array();
storage_arr.push(givenArray);
storage_arr.push(expected);
localStorage.setItem(divid, storage_arr.join(";"));
// log the answer
var answerInfo = 'answer:' + givenlog.substring(0, givenlog.length - 1) + ':' +
(correctCount == correctArray.length ? 'correct' : 'no');
logBookEvent({'event': 'mChoice', 'act': answerInfo, 'div_id': divid});
// give the user feedback
feedBackMCMA('#' + divid + '_feedback', correctCount,
correctArray.length, givenArray.length, feedback);
document.getElementById(divid + '_bcomp').disabled = false;
};
var checkMCMFStorage = function (divid, expected, feedbackArray) {
var given;
var feedback = null;
var buttonObjs = document.forms[divid + "_form"].elements.group1;
for (var i = buttonObjs.length - 1; i >= 0; i--) {
if (buttonObjs[i].checked) {
given = buttonObjs[i].value;
feedback = feedbackArray[i];
}
}
//Saving data in local storage
var storage_arr = new Array();
storage_arr.push(given);
storage_arr.push(expected);
localStorage.setItem(divid, storage_arr.join(";"));
// log the answer
var answerInfo = 'answer:' + given + ":" + (given == expected ? 'correct' : 'no');
logBookEvent({'event': 'mChoice', 'act': answerInfo, 'div_id': divid});
// give the user feedback
feedBackMCMF('#' + divid + '_feedback', given == expected, feedback);
document.getElementById(divid + '_bcomp').disabled = false;
};
// for each form in the div
// get the id of the form
// call checkMe on the form... -- need metadata what kind of question what parms etc
// hidden fields for meta data??? each form defines a checkme function with no parameters
// that calls the actual function that checks the answer properly??
// summarize

950
book/common/js/bookfuncs.js Normal file
View file

@ -0,0 +1,950 @@
/**
* Created by IntelliJ IDEA.
* User: bmiller
* Date: 4/20/11
* Time: 2:01 PM
* To change this template use File | Settings | File Templates.
*/
/*
Copyright (C) 2011 Brad Miller bonelake@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function handleEdKeys(ed, e) {
if (e.keyCode === 13) {
if (e.ctrlKey) {
e.stop();
runit(ed.parentDiv);
}
else if (e.shiftKey) {
e.stop();
eval(Sk.importMainWithBody("<stdin>", false, ed.selection()));
}
} else {
if (ed.acEditEvent == false || ed.acEditEvent === undefined) {
$('#' + ed.parentDiv + ' .CodeMirror').css('border-top', '2px solid #b43232');
$('#' + ed.parentDiv + ' .CodeMirror').css('border-bottom', '2px solid #b43232');
}
ed.acEditEvent = true;
}
}
cm_editors = {};
function pyStr(x) {
if (x instanceof Array) {
return '[' + x.join(", ") + ']';
} else {
return x
}
}
function outf(text) {
var mypre = document.getElementById(Sk.pre);
// bnm python 3
x = text;
if (x.charAt(0) == '(') {
x = x.slice(1, -1);
x = '[' + x + ']';
try {
var xl = eval(x);
xl = xl.map(pyStr);
x = xl.join(' ');
} catch (err) {
}
}
text = x;
text = text.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br/>");
mypre.innerHTML = mypre.innerHTML + text;
}
function createEditors() {
var edList = new Array();
edList = document.getElementsByClassName("active_code");
for (var i = 0; i < edList.length; i++) {
newEdId = edList[i].id;
var includes = edList[i].getAttribute('prefixcode');
var lang = edList[i].getAttribute('lang');
var first_line = 1;
if (includes !== "undefined") {
includes = eval(includes)
for (var j in includes) {
var edinclude = document.getElementById(includes[j]+'_code')
first_line = first_line + edinclude.textContent.match(/\n/g).length + 1;
}
} else {
first_line = 1;
}
cm_editors[newEdId] = CodeMirror.fromTextArea(edList[i], {
mode: {name: lang,
version: 2,
singleLineStringErrors: false},
lineNumbers: true,
firstLineNumber: first_line,
indentUnit: 4,
indentWithTabs: false,
matchBrackets: true,
extraKeys: {"Tab": "indentMore", "Shift-Tab": "indentLess"},
onKeyEvent: handleEdKeys
}
);
cm_editors[newEdId].parentDiv = edList[i].parentNode.parentNode.id;
//requestCode(edList[i].parentNode.id) // populate with user's code
}
}
function builtinRead(x) {
if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined)
throw "File not found: '" + x + "'";
return Sk.builtinFiles["files"][x];
}
function createActiveCode(divid,suppliedSource,sid) {
var eNode;
var acblockid;
if (sid !== undefined) {
acblockid = divid + "_" + sid;
} else {
acblockid = divid;
}
edNode = document.getElementById(acblockid);
if (edNode.children.length == 0 ) {
//edNode.style.display = 'none';
edNode.style.backgroundColor = "white";
var editor;
editor = CodeMirror(edNode, {
mode: {name: "python",
version: 2,
singleLineStringErrors: false},
lineNumbers: true,
indentUnit: 4,
tabMode: "indent",
matchBrackets: true,
onKeyEvent:handleEdKeys
});
var myRun = function() {
runit(acblockid);
};
var mySave = function() {
saveEditor(divid);
};
var myLoad = function() {
requestCode(divid,sid);
};
cm_editors[acblockid+"_code"] = editor;
editor.parentDiv = acblockid;
var runButton = document.createElement("button");
runButton.appendChild(document.createTextNode('Run'));
runButton.className = runButton.className + ' btn btn-success';
runButton.onclick = myRun;
edNode.appendChild(runButton);
edNode.appendChild(document.createElement('br'));
if (sid === undefined) { // We don't need load and save buttons for grading
if(isLoggedIn() == true) {
var saveButton = document.createElement("input");
saveButton.setAttribute('type','button');
saveButton.setAttribute('value','Save');
saveButton.className = saveButton.className + ' btn btn-default';
saveButton.onclick = mySave;
edNode.appendChild(saveButton);
var loadButton = document.createElement("input");
loadButton.setAttribute('type','button');
loadButton.setAttribute('value','Load');
loadButton.className = loadButton.className + ' btn btn-default';
loadButton.onclick = myLoad;
edNode.appendChild(loadButton);
} else {
var saveButton = document.createElement("input");
saveButton.setAttribute('type','button');
saveButton.setAttribute('value','Save');
saveButton.className = saveButton.className + ' btn btn-default disabled';
saveButton.setAttribute('data-toggle','tooltip');
saveButton.setAttribute('title','Register or log in to save your code');
edNode.appendChild(saveButton);
$jqTheme(saveButton).tooltip( {
'selector': '',
'placement': 'bottom'
});
var loadButton = document.createElement("input");
loadButton.setAttribute('type','button');
loadButton.setAttribute('value','Load');
loadButton.className = loadButton.className + ' btn btn-default disabled';
loadButton.setAttribute('data-toggle','tooltip');
loadButton.setAttribute('title','Register or log in to load your saved code');
edNode.appendChild(loadButton);
$jqTheme(loadButton).tooltip( {
'selector': '',
'placement': 'bottom'
});
}
}
edNode.appendChild(document.createElement('br'));
var newCanvas = edNode.appendChild(document.createElement("canvas"));
newCanvas.id = acblockid+"_canvas";
newCanvas.height = 400;
newCanvas.width = 400;
newCanvas.style.border = '2px solid black';
newCanvas.style.display = 'none';
var newPre = edNode.appendChild(document.createElement("pre"));
newPre.id = acblockid + "_pre";
newPre.className = "active_out";
myLoad();
if (! suppliedSource ) {
suppliedSource = '\n\n\n\n\n';
}
if (! editor.getValue()) {
suppliedSource = suppliedSource.replace(new RegExp('%22','g'),'"');
suppliedSource = suppliedSource.replace(new RegExp('%27','g'),"'");
editor.setValue(suppliedSource);
}
}
}
function runit(myDiv, theButton, includes, suffix) {
//var prog = document.getElementById(myDiv + "_code").value;
Sk.divid = myDiv;
$(theButton).attr('disabled', 'disabled');
Sk.isTurtleProgram = false;
if (theButton !== undefined) {
Sk.runButton = theButton;
}
$("#" + myDiv + "_errinfo").remove();
var editor = cm_editors[myDiv + "_code"];
if (editor.acEditEvent) {
logBookEvent({'event': 'activecode', 'act': 'edit', 'div_id': myDiv}); // Log the edit event
editor.acEditEvent = false;
}
var prog = "";
var text = "";
if (includes !== undefined) {
// iterate over the includes, in-order prepending to prog
for (var x in includes) {
text = cm_editors[includes[x] + "_code"].getValue();
prog = prog + text + "\n"
}
}
prog = prog + editor.getValue();
var spre = document.getElementById(myDiv + '_suffix');
var suffix = '';
if (spre) {
suffix = spre.innerText || ''; // for some reason Firefox returns undefined when no innerText
}
prog = prog + '\n' + suffix;
var mypre = document.getElementById(myDiv + "_pre");
if (mypre) mypre.innerHTML = '';
Sk.canvas = myDiv + "_canvas";
Sk.pre = myDiv + "_pre";
var can = document.getElementById(Sk.canvas);
// The following lines reset the canvas so that each time the run button
// is pressed the turtle(s) get a clean canvas.
if (can) {
can.width = can.width;
if (Sk.tg) {
Sk.tg.canvasInit = false;
Sk.tg.turtleList = [];
}
}
// set execLimit in milliseconds -- for student projects set this to
// 25 seconds -- just less than Chrome's own timer.
Sk.execLimit = 25000;
// configure Skulpt output function, and module reader
Sk.configure({output: outf,
read: builtinRead,
python3: true
});
var lang = document.getElementById(myDiv).lang;
try {
if(lang === 'python') {
Sk.importMainWithBody("<stdin>", false, prog);
} else if (lang === 'javascript') {
eval(prog);
} else {
// html
$('#'+myDiv+'_iframe').remove()
$('#'+myDiv).append('<iframe class="activehtml" id="' + myDiv + '_iframe" srcdoc="'
+ prog + '">' + '</iframe>');
}
logRunEvent({'div_id': myDiv, 'code': prog, 'errinfo': 'success'}); // Log the run event
} catch (e) {
logRunEvent({'div_id': myDiv, 'code': prog, 'errinfo': e.toString()}); // Log the run event
//alert(e);
addErrorMessage(e, myDiv)
}
if (!Sk.isTurtleProgram) {
$(theButton).removeAttr('disabled');
}
if (typeof(allVisualizers) != "undefined") {
$.each(allVisualizers, function (i, e) {
e.redrawConnectors();
});
}
}
function addErrorMessage(err, myDiv) {
var errHead = $('<h3>').html('Error')
var divEl = document.getElementById(myDiv)
var eContainer = divEl.appendChild(document.createElement('div'))
eContainer.className = 'error alert alert-danger';
eContainer.id = myDiv + '_errinfo';
eContainer.appendChild(errHead[0]);
var errText = eContainer.appendChild(document.createElement('pre'))
var errString = err.toString()
var to = errString.indexOf(":")
var errName = errString.substring(0, to);
errText.innerHTML = errString;
$(eContainer).append('<h3>Description</h3>');
var errDesc = eContainer.appendChild(document.createElement('p'));
errDesc.innerHTML = errorText[errName];
$(eContainer).append('<h3>To Fix</h3>');
var errFix = eContainer.appendChild(document.createElement('p'));
errFix.innerHTML = errorText[errName + 'Fix'];
var moreInfo = '../ErrorHelp/' + errName.toLowerCase() + '.html';
}
var errorText = {};
errorText.ParseError = "A parse error means that Python does not understand the syntax on the line the error message points out. Common examples are forgetting commas beteween arguments or forgetting a : on a for statement";
errorText.ParseErrorFix = "To fix a parse error you just need to look carefully at the line with the error and possibly the line before it. Make sure it conforms to all of Python's rules.";
errorText.TypeError = "Type errors most often occur when an expression tries to combine two objects with types that should not be combined. Like raising a string to a power";
errorText.TypeErrorFix = "To fix a type error you will most likely need to trace through your code and make sure the variables have the types you expect them to have. It may be helpful to print out each variable along the way to be sure its value is what you think it should be.";
errorText.NameError = "A name error almost always means that you have used a variable before it has a value. Often this may be a simple typo, so check the spelling carefully.";
errorText.NameErrorFix = "Check the right hand side of assignment statements and your function calls, this is the most likely place for a NameError to be found.";
errorText.ValueError = "A ValueError most often occurs when you pass a parameter to a function and the function is expecting one type and you pass another.";
errorText.ValueErrorFix = "The error message gives you a pretty good hint about the name of the function as well as the value that is incorrect. Look at the error message closely and then trace back to the variable containing the problematic value.";
errorText.AttributeError = "This error message is telling you that the object on the left hand side of the dot, does not have the attribute or method on the right hand side.";
errorText.AttributeErrorFix = "The most common variant of this message is that the object undefined does not have attribute X. This tells you that the object on the left hand side of the dot is not what you think. Trace the variable back and print it out in various places until you discover where it becomes undefined. Otherwise check the attribute on the right hand side of the dot for a typo.";
errorText.TokenError = "Most of the time this error indicates that you have forgotten a right parenthesis or have forgotten to close a pair of quotes.";
errorText.TokenErrorFix = "Check each line of your program and make sure that your parenthesis are balanced.";
errorText.TimeLimitError = "Your program is running too long. Most programs in this book should run in less than 10 seconds easily. This probably indicates your program is in an infinite loop.";
errorText.TimeLimitErrorFix = "Add some print statements to figure out if your program is in an infinte loop. If it is not you can increase the run time with sys.setExecutionLimit(msecs)";
errorText.Error = "Your program is running for too long. Most programs in this book should run in less than 30 seconds easily. This probably indicates your program is in an infinite loop.";
errorText.ErrorFix = "Add some print statements to figure out if your program is in an infinte loop. If it is not you can increase the run time with sys.setExecutionLimit(msecs)";
errorText.SyntaxError = "This message indicates that Python can't figure out the syntax of a particular statement. Some examples are assigning to a literal, or a function call";
errorText.SyntaxErrorFix = "Check your assignment statments and make sure that the left hand side of the assignment is a variable, not a literal or a function.";
errorText.IndexError = "This message means that you are trying to index past the end of a string or a list. For example if your list has 3 things in it and you try to access the item at position 3 or more.";
errorText.IndexErrorFix = "Remember that the first item in a list or string is at index position 0, quite often this message comes about because you are off by one. Remember in a list of length 3 the last legal index is 2";
errorText.URIError = "";
errorText.URIErrorFix = "";
errorText.ImportError = "This error message indicates that you are trying to import a module that does not exist";
errorText.ImportErrorFix = "One problem may simply be that you have a typo. It may also be that you are trying to import a module that exists in 'real' Python, but does not exist in this book. If this is the case, please submit a feature request to have the module added.";
errorText.ReferenceError = "This is most likely an internal error, particularly if the message references the console.";
errorText.ReferenceErrorFix = "Try refreshing the webpage, and if the error continues, submit a bug report along with your code";
errorText.ZeroDivisionError = "This tells you that you are trying to divide by 0. Typically this is because the value of the variable in the denominator of a division expression has the value 0";
errorText.ZeroDivisionErrorFix = "You may need to protect against dividing by 0 with an if statment, or you may need to rexamine your assumptions about the legal values of variables, it could be an earlier statment that is unexpectedly assigning a value of zero to the variable in question.";
errorText.RangeError = "This message almost always shows up in the form of Maximum call stack size exceeded.";
errorText.RangeErrorFix = "This always occurs when a function calls itself. Its pretty likely that you are not doing this on purpose. Except in the chapter on recursion. If you are in that chapter then its likely you haven't identified a good base case.";
errorText.InternalError = "An Internal error may mean that you've triggered a bug in our Python";
errorText.InternalErrorFix = "Report this error, along with your code as a bug.";
errorText.IndentationError = "This error occurs when you have not indented your code properly. This is most likely to happen as part of an if, for, while or def statement.";
errorText.IndentationErrorFix = "Check your if, def, for, and while statements to be sure the lines are properly indented beneath them. Another source of this error comes from copying and pasting code where you have accidentally left some bits of code lying around that don't belong there anymore.";
errorText.NotImplementedError = "This error occurs when you try to use a builtin function of Python that has not been implemented in this in-browser version of Python.";
errorText.NotImplementedErrorFix = "For now the only way to fix this is to not use the function. There may be workarounds. If you really need this builtin function then file a bug report and tell us how you are trying to use the function.";
function logBookEvent(eventInfo) {
eventInfo.course = eBookConfig.course;
if (eBookConfig.logLevel > 0) {
jQuery.get(eBookConfig.ajaxURL + 'hsblog', eventInfo); // Log the run event
}
}
function logRunEvent(eventInfo) {
eventInfo.course = eBookConfig.course;
if (eBookConfig.logLevel > 0) {
jQuery.post(eBookConfig.ajaxURL + 'runlog', eventInfo); // Log the run event
}
}
function saveSuccess(data, status, whatever) {
if (data.redirect) {
alert("Did not save! It appears you are not logged in properly")
} else if (data == "") {
alert("Error: Program not saved");
}
else {
var acid = eval(data)[0];
if (acid.indexOf("ERROR:") == 0) {
alert(acid);
} else {
// use a tooltip to provide some success feedback
var save_btn = $("#" + acid + "_saveb");
save_btn.attr('title', 'Saved your code.');
opts = {
'trigger': 'manual',
'placement': 'bottom',
'delay': { show: 100, hide: 500}
};
save_btn.tooltip(opts);
save_btn.tooltip('show');
setTimeout(function () {
save_btn.tooltip('destroy')
}, 4000);
$('#' + acid + ' .CodeMirror').css('border-top', '2px solid #aaa');
$('#' + acid + ' .CodeMirror').css('border-bottom', '2px solid #aaa');
}
}
}
function saveEditor(divName) {
// get editor from div name
var editor = cm_editors[divName + "_code"];
var data = {acid: divName, code: editor.getValue()};
$(document).ajaxError(function (e, jqhxr, settings, exception) {
alert("Request Failed for" + settings.url)
});
jQuery.post(eBookConfig.ajaxURL + 'saveprog', data, saveSuccess);
if (editor.acEditEvent) {
logBookEvent({'event': 'activecode', 'act': 'edit', 'div_id': divName}); // Log the run event
editor.acEditEvent = false;
}
logBookEvent({'event': 'activecode', 'act': 'save', 'div_id': divName}); // Log the run event
}
function requestCode(divName, sid) {
var editor = cm_editors[divName + "_code"];
var data = {acid: divName};
if (sid !== undefined) {
data['sid'] = sid;
}
logBookEvent({'event': 'activecode', 'act': 'load', 'div_id': divName}); // Log the run event
jQuery.get(eBookConfig.ajaxURL + 'getprog', data, loadEditor);
}
function loadEditor(data, status, whatever) {
// function called when contents of database are returned successfully
var res = eval(data)[0];
var editor;
if (res.sid) {
editor = cm_editors[res.acid + "_" + res.sid + "_code"];
} else {
editor = cm_editors[res.acid + "_code"];
}
var loadbtn = $("#"+res.acid+"_loadb");
if (res.source) {
editor.setValue(res.source);
loadbtn.tooltip({'placement': 'bottom',
'title': "Loaded your saved code.",
'trigger': 'manual'
});
} else {
loadbtn.tooltip({'placement': 'bottom',
'title': "No saved code.",
'trigger': 'manual'
});
}
loadbtn.tooltip('show');
setTimeout(function () {
loadbtn.tooltip('destroy')
}, 4000);
}
function disableAcOpt() {
$jqTheme('button.ac_opt').each(function (index, value) {
value.className = value.className + ' disabled';
$jqTheme(value).attr('onclick', 'return false;');
$jqTheme(value).attr('data-toggle', 'tooltip');
if ($jqTheme(value).text() == 'Save') {
$jqTheme(value).attr('title', 'Register or log in to save your code');
} else if ($jqTheme(value).text() == 'Load') {
$jqTheme(value).attr('title', 'Register or log in to load your saved code');
}
$jqTheme(value).tooltip({
'selector': '',
'delay': { show: 100, hide: 50 },
'placement': 'bottom',
'animation': true
});
});
}
function comment(blockid) {
$.modal('<iframe width="600" height="400" src="/getcomment?id=' + blockid + '" style="background-color: white">', {
//$.modal('<form><textarea name="content"></textarea><input type="submit" name="submit" > </form>', {
overlayClose: true,
closeHTML: "",
containerCss: {
width: 600,
height: 400,
backgroundColor: "#fff"
}
});
}
function sendGrade(grade, sid, acid, id) {
data = {'sid': sid, 'acid': acid, 'grade': grade, 'id': id};
jQuery.get(eBookConfig.ajaxURL + 'savegrade', data);
}
function sendComment(comment, sid, acid, id) {
data = {'sid': sid, 'acid': acid, 'comment': comment, 'id': id};
jQuery.get(eBookConfig.ajaxURL + 'savegrade', data);
}
function gotUser(data, status, whatever) {
var mess = '';
var caughtErr = false;
var d;
try {
d = eval(data)[0];
} catch (err) {
if (eBookConfig.loginRequired) {
if (confirm("Error: " + err.toString() + "Please report this error! Click OK to continue without logging in. Cancel to retry.")) {
caughtErr = true;
mess = "Not logged in";
disableAcOpt();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/login">Login</a>')
} else {
window.location.href = eBookConfig.app + '/default/user/login?_next=' + window.location.href
}
}
}
if (d.redirect) {
if (eBookConfig.loginRequired) {
window.location.href = eBookConfig.app + '/default/user/login?_next=' + window.location.href
} else {
mess = "Not logged in";
disableAcOpt();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/login">Login</a>')
}
} else {
if (!caughtErr) {
mess = d.email;
eBookConfig.isLoggedIn = true;
addNavbarLoginLink(); // will change navbar login link to say 'Log Out'
enableUserHighlights();
timedRefresh();
}
}
x = $(".loggedinuser").html();
$(".loggedinuser").html(mess);
logBookEvent({
'event': 'page',
'act': 'view',
'div_id': window.location.pathname
})
}
function timedRefresh() {
timeoutPeriod = 4500000; // 75 minutes
$(document).bind("idle.idleTimer", function () {
// After timeout period send the user back to the index. This will force a login
// if needed when they want to go to a particular page. This may not be perfect
// but its an easy way to make sure laptop users are properly logged in when they
// take quizzes and save stuff.
if (location.href.indexOf('index.html') < 0)
location.href = eBookConfig.app + '/static/' + eBookConfig.course + '/index.html'
});
$.idleTimer(timeoutPeriod);
}
function shouldLogin() {
var sli = true;
if (window.location.href.indexOf('file://') > -1)
sli = false;
return sli;
}
function isLoggedIn() {
if (typeof eBookConfig.isLoggedIn !== undefined) {
return eBookConfig.isLoggedIn;
}
return false;
}
function addUserToFooter() {
// test to see if online before doing this.
if (shouldLogin()) {
jQuery.get(eBookConfig.ajaxURL + 'getuser', null, gotUser)
} else {
x = $(".footer").html();
$(".footer").html(x + 'not logged in');
disableAcOpt();
logBookEvent({'event': 'page', 'act': 'view', 'div_id': window.location.pathname})
}
}
function addNavbarLoginLink() {
if (isLoggedIn()) {
$('#profilelink').show();
$('#passwordlink').show();
$('#registerlink').hide();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/logout">Log Out</a>')
} else {
$('#registerlink').show();
$('#profilelink').hide();
$('#passwordlink').hide();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/login">Login</a>')
}
}
/*
Since I don't want to modify the codelens code I'll attach the logging functionality this way.
This actually seems like a better way to do it maybe across the board to separate logging
from the real funcionality. It would also allow a better way of turning off/on logging..
As long as Philip doesn't go and change the id values for the buttons and slider this will
continue to work.... In the best of all worlds we might add a function to the visualizer to
return the buttons, but I'm having a hard time thinking of any other use for that besides mine.
*/
function attachLoggers(codelens, divid) {
codelens.domRoot.find("#jmpFirstInstr").click(function () {
logBookEvent({'event': 'codelens', 'act': 'first', 'div_id': divid});
});
codelens.domRoot.find("#jmpLastInstr").click(function () {
logBookEvent({'event': 'codelens', 'act': 'last', 'div_id': divid});
});
codelens.domRoot.find("#jmpStepBack").click(function () {
logBookEvent({'event': 'codelens', 'act': 'back', 'div_id': divid});
});
codelens.domRoot.find("#jmpStepFwd").click(function () {
logBookEvent({'event': 'codelens', 'act': 'fwd', 'div_id': divid});
});
codelens.domRoot.find("#executionSlider").bind('slide', function (evt, ui) {
logBookEvent({'event': 'codelens', 'act': 'slide', 'div_id': divid});
});
}
function redrawAllVisualizerArrows() {
if (allVisualizers !== undefined) {
for (var v in allVisualizers)
allVisualizers[v].redrawConnectors();
}
}
function getNumUsers() {
$.getJSON(eBookConfig.ajaxURL + 'getnumusers', setNumUsers)
}
function getOnlineUsers() {
$.getJSON(eBookConfig.ajaxURL + 'getnumonline', setOnlineUsers)
}
function setOnlineUsers(data) {
var d = data[0];
$("#numuserspan").text(d.online);
}
function setNumUsers(data) {
var d = data[0];
$("#totalusers").html(d.numusers);
}
function instructorMchoiceModal(data) {
// data.reslist -- student and their answers
// data.answerDict -- answers and count
// data.correct - correct answer
var res = '<table><tr><th>Student</th><th>Answer(s)</th></tr>';
for (var i in data) {
res += '<tr><td>' + data[i][0] + '</td><td>' + data[i][1] + '</td></tr>';
}
res += '</table>';
return res;
}
function compareModal(data, status, whatever) {
var datadict = eval(data)[0];
var answers = datadict.answerDict;
var misc = datadict.misc;
var kl = Object.keys(answers).sort();
var body = '<table>';
body += '<tr><th>Answer</th><th>Percent</th></tr>';
var theClass= '';
for (var k in kl) {
if (kl[k] == misc.correct) {
theClass = 'success';
} else {
theClass = 'info';
}
body += '<tr><td>' + kl[k] + '</td><td class="compare-me-progress">';
pct = answers[kl[k]] + '%';
body += '<div class="progress">';
body += ' <div class="progress-bar progress-bar-' + theClass + '" style="width:'+pct+';">' + pct;
body += ' </div>';
body += '</div></td></tr>';
}
body += '</table>';
if (misc['yourpct'] !== 'unavailable') {
body += '<br /><p>You have ' + misc['yourpct'] + '% correct for all questions</p>';
}
if (datadict.reslist !== undefined) {
body += instructorMchoiceModal(datadict.reslist);
}
var html = '<div class="modal fade">' +
' <div class="modal-dialog compare-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Distribution of Answers</h4>' +
' </div>' +
' <div class="modal-body">' +
body +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
el.modal();
}
function compareAnswers(div_id) {
data = {};
data.div_id = div_id;
data.course = eBookConfig.course;
jQuery.get(eBookConfig.ajaxURL + 'getaggregateresults', data, compareModal);
}
function compareFITB(data, status, whatever) {
var answers = eval(data)[0];
var misc = eval(data)[1];
var body = '<table>';
body += '<tr><th>Answer</th><th>Count</th></tr>';
for (var row in answers) {
body += '<tr><td>' + answers[row].answer + '</td><td>' + answers[row].count + ' times</td></tr>';
}
body += '</table>';
if (misc['yourpct'] !== 'unavailable') {
body += '<br /><p>You have ' + misc['yourpct'] + '% correct for all questions</p>';
}
var html = '<div class="modal fade">' +
' <div class="modal-dialog compare-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Top Answers</h4>' +
' </div>' +
' <div class="modal-body">' +
body +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
el.modal();
}
function compareFITBAnswers(div_id) {
data = {};
data.div_id = div_id;
data.course = eBookConfig.course;
jQuery.get(eBookConfig.ajaxURL + 'gettop10Answers', data, compareFITB);
}
/* Listen for changes to/addition of a div containing unittest results
(which are generated by Skulpt) and add some nice styles to it*/
function styleUnittestResults() {
$(document).on("DOMNodeInserted", '.unittest-results', function (ev) {
// select the target node
var unittest_results_target = ev.target;
// create an observer instance
var observer = new MutationObserver(function(mutations) {
$(mutations).each(function() {
if (this.type == "attributes") {
var target = $(this.target);
// apply the .alert classes
if(target.text().indexOf("Fail") === -1) {
target.removeClass('alert-danger');
target.addClass('alert alert-success');
} else if (target.text().indexOf('Fail') >= 0) {
target.removeClass('alert-success');
target.addClass('alert alert-danger');
}
// add the progress bar indicating the percent of tests passed
var paragraph = target.find('p');
var result_text = paragraph.text().split(" ");
var pct = '';
$(result_text).each(function() {
if (this.indexOf("%") !== -1) {
pct = this;
var html = 'You passed:' +
'<div class="progress unittest-results-progress">';
if (pct == '100.0%') {
html += ' <div class="progress-bar progress-bar-success" style="width:'+pct+';">';
} else {
html += ' <div class="progress-bar progress-bar-warning" style="width:'+pct+';">';
}
html += pct +
' </div>' +
'</div>';
paragraph.html(html);
}
});
}
});
});
// configuration of the observer:
var config = { attributes: true,
attributeFilter: ['style'],
childList: true,
characterData: true,
subtree: false };
// pass in the target node, as well as the observer options
observer.observe($(unittest_results_target).get(0), config);
});
}
function createScratchActivecode() {
/* set up the scratch Activecode editor in the search menu */
// use the URL to assign a divid - each page should have a unique Activecode block id.
// Remove everything from the URL but the course and page name
var divid = document.URL.split('#')[0];
if (divid.indexOf('static') > 0 ) {
divid = divid.split('static')[1];
} else {
// divid = divid.split('//')[1];
divid = divid.split('?')[0]; // remove any query string (e.g ?lastPosition)
}
divid = divid.replaceAll(':','')
divid = divid.replaceAll('/', '').replace('.html', '');
divid = divid.replaceAll('.','')
// generate the HTML
var html = '<div id="ac_modal_' + divid + '" class="modal fade">' +
' <div class="modal-dialog scratch-ac-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Scratch ActiveCode</h4>' +
' </div> ' +
' <div class="modal-body">' +
' <div id="' + divid + '">' +
' <div id="' + divid + '_code_div" style="display: block">' +
' <textarea cols="50" rows="12" id="' + divid + '_code" class="active_code">\n\n\n\n\n</textarea>' +
' </div>' +
' <p class="ac_caption"><span class="ac_caption_text">Scratch Editor</span> </p>' +
' <button class="btn btn-success" id="' + divid + '_runb" onclick="runit(\'' + divid + '\',this, undefined);">Run</button>' +
' <div id="cont"></div>' +
' <button class="ac_opt btn btn-default" style="display: inline-block" id="' + divid + '_saveb" onclick="saveEditor(\'' + divid + '\');">Save</button>' +
' <button class="ac_opt btn btn-default" style="display: inline-block" id="' + divid + '_loadb" onclick="requestCode(\'' + divid + '\');">Load</button>' +
' <div style="text-align: center">' +
' <canvas id="' + divid + '_canvas" class="ac-canvas" height="400" width="400" style="border-style: solid; display: none; text-align: center"></canvas>' +
' </div>' +
' <pre id="' + divid + '_suffix" style="display:none">' +
' </pre>' +
' <pre id="' + divid + '_pre" class="active_out">' +
' </pre>' +
' </div>' +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
$('body').append(el);
el.on('shown.bs.modal show.bs.modal', function () {
el.find('.CodeMirror').each(function (i, e) {
e.CodeMirror.refresh();
e.CodeMirror.focus();
});
});
$(document).bind('keypress', '\\', function(evt) {
toggleScratchActivecode();
return false;
});
}
function toggleScratchActivecode() {
var bforeanchor = document.URL.split('#')[0]
var suffix = 'index'
if (bforeanchor.indexOf('static') > 0) {
suffix = bforeanchor.split('static')[1].split('?')[0].replaceAll('/', '').replace('.html', '');
} else {
suffix = bforeanchor.split('?')[0].replaceAll('/', '').replace('.html', '');
suffix = suffix.replaceAll(':','')
suffix = suffix.replaceAll('.','')
}
var divid = "ac_modal_" + suffix;
var div = $("#" + divid);
div.modal('toggle');
}
function showGradeSummary(data, status, whatever) {
var report = eval(data)[0];
// check for report['message']
if (report['grade']) {
body = "<h4>Grade Report</h4>" +
"<p>This assignment: " + report['grade'] + "</p>" +
"<p>" + report['comment'] + "</p>" +
"<p>Number of graded assignments: " + report['count'] + "</p>" +
"<p>Average score: " + report['avg'] + "</p>"
} else {
body = "<h4>You must be Logged in to see your grade</h4>";
}
var html = '<div class="modal fade">' +
' <div class="modal-dialog compare-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Assignment Feedback</h4>' +
' </div>' +
' <div class="modal-body">' +
body +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
el.modal();
}
function createGradeSummary(div_id) {
// get grade and comments for this assignment
// get summary of all grades for this student
// display grades in modal window
var data = {'div_id':div_id}
jQuery.get(eBookConfig.ajaxURL + 'getassignmentgrade', data, showGradeSummary);
}

3192
book/common/js/codemirror.js Normal file

File diff suppressed because it is too large Load diff

237
book/common/js/doctools.js Normal file
View file

@ -0,0 +1,237 @@
/*
* doctools.js
* ~~~~~~~~~~~
*
* Sphinx JavaScript utilities for all documentation.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* select a different prefix for underscore
*/
$u = _.noConflict();
/**
* make the code below compatible with browsers without
* an installed firebug like debugger
if (!window.console || !console.firebug) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
"profile", "profileEnd"];
window.console = {};
for (var i = 0; i < names.length; ++i)
window.console[names[i]] = function() {};
}
*/
/**
* small helper function to urldecode strings
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
}
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s == 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node) {
if (node.nodeType == 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
var span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this);
});
}
}
return this.each(function() {
highlight(this);
});
};
/**
* Small JavaScript module for the documentation.
*/
var Documentation = {
init : function() {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initIndexTable();
},
/**
* i18n support
*/
TRANSLATIONS : {},
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
LOCALE : 'unknown',
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated == 'undefined')
return string;
return (typeof translated == 'string') ? translated : translated[0];
},
ngettext : function(singular, plural, n) {
var translated = Documentation.TRANSLATIONS[singular];
if (typeof translated == 'undefined')
return (n == 1) ? singular : plural;
return translated[Documentation.PLURALEXPR(n)];
},
addTranslations : function(catalog) {
for (var key in catalog.messages)
this.TRANSLATIONS[key] = catalog.messages[key];
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
this.LOCALE = catalog.locale;
},
/**
* add context elements like header anchor links
*/
addContextElements : function() {
$('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
$('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
appendTo(this);
});
},
/**
* workaround a firefox stupidity
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash && $.browser.mozilla)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},
/**
* highlight the search words provided in the url in the text
*/
highlightSearchWords : function() {
var params = $.getQueryParameters();
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlighted');
});
}, 10);
$('<p class="highlight-link"><a href="javascript:Documentation.' +
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
.appendTo($('#searchbox'));
}
},
/**
* init the domain index toggle buttons
*/
initIndexTable : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
$('tr.cg-' + idnum).toggle();
if (src.substr(-9) == 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('#searchbox .highlight-link').fadeOut(300);
$('span.highlighted').removeClass('highlighted');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this == '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});

View file

@ -0,0 +1,632 @@
/*
Online Python Tutor
Copyright (C) 2010 Philip J. Guo (philip@pgbovine.net)
https://github.com/pgbovine/OnlinePythonTutor/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// The Online Python Tutor front-end, which calls the cgi-bin/web_exec.py
// back-end with a string representing the user's script POST['user_script']
// and receives a complete execution trace, which it parses and displays to HTML.
var PythonTutor;
if (! PythonTutor) {
PythonTutor = {};
}
(function () {
function Visualizer(source_code, trace_data, divid) {
this.stdOutElement = "#pyStdout_" + divid;
this.warnOutElement = "#warningOutput_" + divid;
this.errorOutputElement = "#errorOutput_" + divid;
this.vcrControlsDiv = "#vcrControls_" + divid;
this.dataVisElement = "#dataViz_" + divid;
this.outputPaneTable = "#pyOutputPane_" + divid;
this.codeOutputTable = "#pyCodeOutput_" + divid;
this.curTrace = null;
this.curInstr = 0;
this.divid = divid;
this.jmpFirstInstr = $("#jmpFirstInstr_"+divid);
this.jmpLastInstr = $("#jmpLastInstr_"+divid);
this.jmpStepBack = $("#jmpStepBack_"+divid);
this.jmpStepFwd = $("#jmpStepFwd_"+divid);
this.renderPyCodeOutput(source_code);
this.processTrace(trace_data);
$(this.outputPaneTable).show();
var myvis = this;
this.jmpFirstInstr.click(function() {
myvis.curInstr = 0;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act': 'first', 'div_id':myvis.divid}); // Log the run event
});
this.jmpLastInstr.click(function() {
myvis.curInstr = myvis.curTrace.length - 1;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act':'last', 'div_id': myvis.divid}); // Log the run event
});
this.jmpStepBack.click(function() {
if (myvis.curInstr > 0) {
myvis.curInstr -= 1;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act': 'back', 'div_id': myvis.divid}); // Log the run event
}
});
this.jmpStepFwd.click(function() {
if (myvis.curInstr < myvis.curTrace.length - 1) {
myvis.curInstr += 1;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act':'fwd', 'div_id':myvis.divid}); // Log the run event
}
});
}
Visualizer.prototype.processTrace = function(traceData) {
this.curTrace = traceData;
this.curInstr = 0;
// delete all stale output
$(this.warnOutElement).html('');
$(this.stdOutElement).val('');
if (this.curTrace.length > 0) {
var lastEntry = this.curTrace[this.curTrace.length - 1];
// if there is some sort of error, then JUMP to it so that we can
// immediately alert the user:
// (cgi-bin/pg_logger.py ensures that if there is an uncaught
// exception, then that exception event will be the FINAL
// entry in this.curTrace. a caught exception will appear somewhere in
// the MIDDLE of this.curTrace)
//
// on second thought, let's hold off on that for now
/*
if (lastEntry.event == 'exception' ||
lastEntry.event == 'uncaught_exception') {
// updateOutput should take care of the rest ...
curInstr = this.curTrace.length - 1;
}
*/
if (lastEntry.event == 'instruction_limit_reached') {
this.curTrace.pop() // kill last entry
var warningMsg = lastEntry.exception_msg;
$(this.warnOutElement).html(htmlspecialchars(warningMsg));
}
// as imran suggests, for a (non-error) one-liner, SNIP off the
// first instruction so that we start after the FIRST instruction
// has been executed ...
else if (this.curTrace.length == 2) {
this.curTrace.shift();
}
}
this.updateOutput();
}
Visualizer.prototype.highlightCodeLine = function(curLine, visitedLinesSet, hasError) {
var tbl = $(this.codeOutputTable);
/* colors - see edu-python.css */
var lightYellow = '#F5F798';
var lightLineColor = '#FFFFCC';
var errorColor = '#F87D76';
var visitedLineColor = '#3D58A2';
// reset then set:
tbl.find('td.lineNo').css('color', '');
tbl.find('td.lineNo').css('font-weight', '');
$.each(visitedLinesSet, function(k, v) {
tbl.find('td.lineNo:eq(' + (k - 1) + ')').css('color', visitedLineColor);
tbl.find('td.lineNo:eq(' + (k - 1) + ')').css('font-weight', 'bold');
});
var lineBgCol = lightLineColor;
if (hasError) {
lineBgCol = errorColor;
}
tbl.find('td.cod').css('border-bottom', '1px solid #ffffff');
if (!hasError) {
tbl.find('td.cod:eq(' + (curLine - 1) + ')').css('border-bottom', '1px solid #F87D76')
}
tbl.find('td.cod').css('background-color', '');
tbl.find('td.cod:eq(' + (curLine - 1) + ')').css('background-color', lineBgCol);
}
// relies on this.curTrace and curInstr globals
Visualizer.prototype.updateOutput = function() {
var curEntry = this.curTrace[this.curInstr];
var hasError = false;
// render VCR controls:
var totalInstrs = this.curTrace.length
var vcr = $(this.vcrControlsDiv);
vcr.find("#curInstr_"+this.divid).html(this.curInstr + 1);
vcr.find("#totalInstrs_"+this.divid).html(totalInstrs);
this.jmpFirstInstr.attr("disabled", false);
this.jmpStepBack.attr("disabled", false);
this.jmpStepFwd.attr("disabled", false);
this.jmpLastInstr.attr("disabled", false);
if (this.curInstr == 0) {
this.jmpFirstInstr.attr("disabled", true);
this.jmpStepBack.attr("disabled", true);
}
if (this.curInstr == (totalInstrs - 1)) {
this.jmpLastInstr.attr("disabled", true);
this.jmpStepFwd.attr("disabled", true);
}
// render error (if applicable):
if (curEntry.event == 'exception' ||
curEntry.event == 'uncaught_exception') {
assert(curEntry.exception_msg);
if (curEntry.exception_msg == "Unknown error") {
$(this.errorOutputElement).html('Unknown error: <a id="editCodeLinkOnError" href="#">view code</a> and please<br/>email as a bug report to philip@pgbovine.net');
}
else {
$(this.errorOutputElement).html(htmlspecialchars(curEntry.exception_msg));
}
$(this.errorOutputElement).show();
hasError = true;
}
else {
$(this.errorOutputElement).hide();
}
// render code output:
if (curEntry.line) {
// calculate all lines that have been 'visited'
// by execution up to (but NOT INCLUDING) curInstr:
var visitedLinesSet = {}
for (var i = 0; i < this.curInstr; i++) {
if (this.curTrace[i].line) {
visitedLinesSet[this.curTrace[i].line] = true;
}
}
this.highlightCodeLine(curEntry.line, visitedLinesSet, hasError);
}
// render stdout:
// keep original horizontal scroll level:
// var oldLeft = $(this.stdOutElement).scrollLeft();
// $(this.stdOutElement).val(curEntry.stdout);
// $(this.stdOutElement).scrollLeft(oldLeft);
// // scroll to bottom, tho:
// $(this.stdOutElement).scrollTop($(this.stdOutElement).attr('scrollHeight'));
$(this.stdOutElement).text(curEntry.stdout);
// render data structures:
$(this.dataVisElement).html(''); // CLEAR IT!
// render locals on stack:
if (curEntry.stack_locals != undefined) {
var self = this;
$.each(curEntry.stack_locals, function (i, frame) {
var funcName = htmlspecialchars(frame[0]); // might contain '<' or '>' for weird names like <genexpr>
var localVars = frame[1];
$(self.dataVisElement).append('<div class="vizFrame">Local variables for <span style="font-family: Andale mono, monospace;">' + funcName + '</span>:</div>');
// render locals in alphabetical order for tidiness:
var orderedVarnames = [];
$.each(localVars, function(varname, val) {
orderedVarnames.push(varname);
});
orderedVarnames.sort();
if (orderedVarnames.length > 0) {
$(self.dataVisElement + " .vizFrame:last").append('<br/><table class="frameDataViz"></table>');
var tbl = $(self.outputPaneTable + " table:last");
$.each(orderedVarnames, function(i, varname) {
var val = localVars[varname];
tbl.append('<tr><td class="varname"></td><td class="val"></td></tr>');
var curTr = tbl.find('tr:last');
if (varname == '__return__') {
curTr.find("td.varname").html('<span style="font-size: 10pt; font-style: italic;">return value</span>');
}
else {
curTr.find("td.varname").html(varname);
}
self.renderData(val, curTr.find("td.val"));
});
tbl.find("tr:last").find("td.varname").css('border-bottom', '0px');
tbl.find("tr:last").find("td.val").css('border-bottom', '0px');
}
else {
$(this.dataVisElement + " .vizFrame:last").append(' <i>none</i>');
}
});
}
// render globals LAST:
$(this.dataVisElement).append('<div class="vizFrame">Global variables:</div>');
var nonEmptyGlobals = false;
var curGlobalFields = {};
if (curEntry.globals != undefined) {
$.each(curEntry.globals, function(varname, val) {
curGlobalFields[varname] = true;
nonEmptyGlobals = true;
});
}
if (nonEmptyGlobals) {
$(this.dataVisElement + " .vizFrame:last").append('<br/><table class="frameDataViz"></table>');
// render all global variables IN THE ORDER they were created by the program,
// in order to ensure continuity:
var orderedGlobals = []
// iterating over ALL instructions (could be SLOW if not for our optimization below)
for (var i = 0; i <= this.curInstr; i++) {
// some entries (like for exceptions) don't have GLOBALS
if (this.curTrace[i].globals == undefined) continue;
$.each(this.curTrace[i].globals, function(varname, val) {
// eliminate duplicates (act as an ordered set)
if ($.inArray(varname, orderedGlobals) == -1) {
orderedGlobals.push(varname);
curGlobalFields[varname] = undefined; // 'unset it'
}
});
var earlyStop = true;
// as an optimization, STOP as soon as you've found everything in curGlobalFields:
for (o in curGlobalFields) {
if (curGlobalFields[o] != undefined) {
earlyStop = false;
break;
}
}
if (earlyStop) {
break;
}
}
var tbl = $(this.outputPaneTable + " table:last");
var self = this;
// iterate IN ORDER (it's possible that not all vars are in curEntry.globals)
$.each(orderedGlobals, function(i, varname) {
var val = curEntry.globals[varname];
if (val != undefined) { // might not be defined at this line, which is OKAY!
tbl.append('<tr><td class="varname"></td><td class="val"></td></tr>');
var curTr = tbl.find('tr:last');
curTr.find("td.varname").html(varname);
self.renderData(val, curTr.find("td.val"));
}
});
tbl.find("tr:last").find("td.varname").css('border-bottom', '0px');
tbl.find("tr:last").find("td.val").css('border-bottom', '0px');
}
else {
$(this.dataVisElement + " .vizFrame:last").append(' <i>none</i>');
}
}
// render the JS data object obj inside of jDomElt,
// which is a jQuery wrapped DOM object
// (obj is in a format encoded by cgi-bin/pg_encoder.py)
Visualizer.prototype.renderData = function(obj, jDomElt) {
// dispatch on types:
var typ = typeof obj;
if (obj == null) {
jDomElt.append('<span class="nullObj">None</span>');
}
else if (typ == "number") {
jDomElt.append('<span class="numberObj">' + obj + '</span>');
}
else if (typ == "boolean") {
if (obj) {
jDomElt.append('<span class="boolObj">True</span>');
}
else {
jDomElt.append('<span class="boolObj">False</span>');
}
}
else if (typ == "string") {
// escape using htmlspecialchars to prevent HTML/script injection
// print as a JSON literal
var literalStr = htmlspecialchars(obj);
literalStr = literalStr.replace('\"', '\\"');
literalStr = '"' + literalStr + '"';
jDomElt.append('<span class="stringObj">' + literalStr + '</span>');
}
else if (typ == "object") {
assert($.isArray(obj));
if (obj[0] == 'LIST') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty list (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">list (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="listTbl"><tr></tr><tr></tr></table>');
var tbl = jDomElt.children('table');
var headerTr = tbl.find('tr:first');
var contentTr = tbl.find('tr:last');
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'LIST' tag and ID entry
// add a new column and then pass in that newly-added column
// as jDomElt to the recursive call to child:
headerTr.append('<td class="listHeader"></td>');
headerTr.find('td:last').append(ind - 2);
contentTr.append('<td class="listElt"></td>');
self.renderData(val, contentTr.find('td:last'));
});
}
}
else if (obj[0] == 'TUPLE') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty tuple (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">tuple (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="tupleTbl"><tr></tr><tr></tr></table>');
var tbl = jDomElt.children('table');
var headerTr = tbl.find('tr:first');
var contentTr = tbl.find('tr:last');
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'TUPLE' tag and ID entry
// add a new column and then pass in that newly-added column
// as jDomElt to the recursive call to child:
headerTr.append('<td class="tupleHeader"></td>');
headerTr.find('td:last').append(ind - 2);
contentTr.append('<td class="tupleElt"></td>');
self.renderData(val, contentTr.find('td:last'));
});
}
}
else if (obj[0] == 'SET') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty set (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">set (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="setTbl"></table>');
var tbl = jDomElt.children('table');
// create an R x C matrix:
var numElts = obj.length - 2;
// gives roughly a 3x5 rectangular ratio, square is too, err,
// 'square' and boring
var numRows = Math.round(Math.sqrt(numElts));
if (numRows > 3) {
numRows -= 1;
}
var numCols = Math.round(numElts / numRows);
// round up if not a perfect multiple:
if (numElts % numRows) {
numCols += 1;
}
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'SET' tag and ID entry
if (((ind - 2) % numCols) == 0) {
tbl.append('<tr></tr>');
}
var curTr = tbl.find('tr:last');
curTr.append('<td class="setElt"></td>');
self.renderData(val, curTr.find('td:last'));
});
}
}
else if (obj[0] == 'DICT') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty dict (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">dict (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="dictTbl"></table>');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 2) return; // skip 'DICT' tag and ID entry
tbl.append('<tr class="dictEntry"><td class="dictKey"></td><td class="dictVal"></td></tr>');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
self.renderData(kvPair[0], keyTd);
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'INSTANCE') {
assert(obj.length >= 3);
jDomElt.append('<div class="typeLabel">' + obj[1] + ' instance (id=' + obj[2] + ')</div>');
if (obj.length > 3) {
jDomElt.append('<table class="instTbl"></table>');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 3) return; // skip type tag, class name, and ID entry
tbl.append('<tr class="instEntry"><td class="instKey"></td><td class="instVal"></td></tr>');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
// the keys should always be strings, so render them directly (and without quotes):
assert(typeof kvPair[0] == "string");
var attrnameStr = htmlspecialchars(kvPair[0]);
keyTd.append('<span class="stringObj">' + attrnameStr + '</span>');
// values can be arbitrary objects, so recurse:
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'CLASS') {
assert(obj.length >= 4);
var superclassStr = '';
if (obj[3].length > 0) {
superclassStr += ('[extends ' + obj[3].join(',') + '] ');
}
jDomElt.append('<div class="typeLabel">' + obj[1] + ' class ' + superclassStr + '(id=' + obj[2] + ')</div>');
if (obj.length > 4) {
jDomElt.append('<table class="classTbl"></table>');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 4) return; // skip type tag, class name, ID, and superclasses entries
tbl.append('<tr class="classEntry"><td class="classKey"></td><td class="classVal"></td></tr>');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
// the keys should always be strings, so render them directly (and without quotes):
assert(typeof kvPair[0] == "string");
var attrnameStr = htmlspecialchars(kvPair[0]);
keyTd.append('<span class="stringObj">' + attrnameStr + '</span>');
// values can be arbitrary objects, so recurse:
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'CIRCULAR_REF') {
assert(obj.length == 2);
jDomElt.append('<div class="circRefLabel">circular reference to id=' + obj[1] + '</div>');
}
else {
// render custom data type
assert(obj.length == 3);
typeName = obj[0];
id = obj[1];
strRepr = obj[2];
// if obj[2] is like '<generator object <genexpr> at 0x84760>',
// then display an abbreviated version rather than the gory details
noStrReprRE = /<.* at 0x.*>/;
if (noStrReprRE.test(strRepr)) {
jDomElt.append('<span class="customObj">' + typeName + ' (id=' + id + ')</span>');
}
else {
strRepr = htmlspecialchars(strRepr); // escape strings!
// warning: we're overloading tuple elts for custom data types
jDomElt.append('<div class="typeLabel">' + typeName + ' (id=' + id + '):</div>');
jDomElt.append('<table class="tupleTbl"><tr><td class="tupleElt">' + strRepr + '</td></tr></table>');
}
}
}
else {
alert("Error: renderData FAIL!");
}
}
Visualizer.prototype.renderPyCodeOutput = function(codeStr) {
var tbl = $(this.codeOutputTable);
tbl.html('');
var lines = codeStr.rtrim().split('\n');
$.each(lines, function(i, cod) {
var lineNo = i + 1;
var htmlCod = htmlspecialchars(cod);
tbl.append('<tr><td class="lineNo"></td><td class="cod"></td></tr>');
var curRow = tbl.find('tr:last');
curRow.find('td.lineNo').html(lineNo);
curRow.find('td.cod').html(htmlCod);
});
}
PythonTutor.Visualizer = Visualizer;
})();
String.prototype.rtrim = function() {
return this.replace(/\s*$/g, "");
}
function assert(cond) {
if (!cond) {
alert("Error: ASSERTION FAILED");
}
}
// taken from http://www.toao.net/32-my-htmlspecialchars-function-for-javascript
function htmlspecialchars(str) {
if (typeof(str) == "string") {
str = str.replace(/&/g, "&amp;"); /* must do &amp; first */
// ignore these for now ...
//str = str.replace(/"/g, "&quot;");
//str = str.replace(/'/g, "&#039;");
str = str.replace(/</g, "&lt;");
str = str.replace(/>/g, "&gt;");
// replace spaces:
str = str.replace(/ /g, "&nbsp;");
}
return str;
}

View file

@ -0,0 +1,594 @@
/**
* guiders.js
*
* version 1.3.0
*
* Developed at Optimizely. (www.optimizely.com)
* We make A/B testing you'll actually use.
*
* Released under the Apache License 2.0.
* www.apache.org/licenses/LICENSE-2.0.html
*
* Questions about Guiders?
* You may email me (Jeff Pickhardt) at pickhardt+guiders@gmail.com
*
* Questions about Optimizely should be sent to:
* sales@optimizely.com or support@optimizely.com
*
* Enjoy!
*/
var guiders = (function($) {
var guiders = {};
guiders.version = "1.3.0";
guiders._defaultSettings = {
attachTo: null, // Selector of the element to attach to.
autoFocus: true, // Determines whether or not the browser scrolls to the element.
buttons: [{name: "Next"}, {name: "Back"}],
buttonCustomHTML: "",
classString: null,
closeOnEscape: true,
description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
highlight: null,
isHashable: true,
offset: {
top: null,
left: null
},
onClose: null,
onHide: null,
onShow: null,
overlay: false,
position: 6, // 1-12 follows an analog clock, 0 means centered.
shouldSkip: function() {}, // Optional handler allows you to skip a guider if returns true.
title: "Sample title goes here",
width: 250,
xButton: true // This places a closer "x" button in the top right of the guider.
};
guiders._htmlSkeleton = [
"<div class='guider'>",
" <div class='guiders_content'>",
" <h1 class='guiders_title'></h1>",
" <div class='guiders_close'></div>",
" <p class='guiders_description'></p>",
" <div class='guiders_buttons_container' style='margin-bottom: 10px; text-align: right;'>",
" </div>",
" </div>",
" <div class='guiders_arrow'>",
" </div>",
"</div>"
].join("");
guiders._arrowSize = 42; // This is the arrow's width and height.
guiders._backButtonTitle = "Back";
guiders._buttonAttributes = {"href": "javascript:void(0);", "style": "margin-right: 8px;"};
guiders._buttonClassName = "btn btn-sm btn-success"; // Override this if you use a different class name for your buttons.
guiders._buttonClickEvent = "click touch"; // Using click touch allows this to trigger with iPad/iPhone taps, as well as browser clicks
guiders._buttonElement = "<a></a>"; // Override this if you want to use a different element for your buttons, like spans.
guiders._closeButtonTitle = "Close";
guiders._currentGuiderID = null;
guiders._fixedOrAbsolute = "fixed";
guiders._guiders = {};
guiders._lastCreatedGuiderID = null;
guiders._nextButtonTitle = "Next";
guiders._offsetNameMapping = {
"topLeft": 11,
"top": 12,
"topRight": 1,
"rightTop": 2,
"right": 3,
"rightBottom": 4,
"bottomRight": 5,
"bottom": 6,
"bottomLeft": 7,
"leftBottom": 8,
"left": 9,
"leftTop": 10
};
guiders._windowHeight = 0;
// Basic IE browser detection
var ieBrowserMatch = navigator.userAgent.match('MSIE (.)');
guiders._isIE = ieBrowserMatch && ieBrowserMatch.length > 1;
guiders._ieVersion = ieBrowserMatch && ieBrowserMatch.length > 1 ? Number(ieBrowserMatch[1]) : -1;
guiders._addButtons = function(myGuider) {
var guiderButtonsContainer = myGuider.elem.find(".guiders_buttons_container");
if (myGuider.buttons === null || myGuider.buttons.length === 0) {
guiderButtonsContainer.remove();
return;
}
for (var i = myGuider.buttons.length - 1; i >= 0; i--) {
var thisButton = myGuider.buttons[i];
var thisButtonElem = $(guiders._buttonElement,
$.extend({"class" : guiders._buttonClassName, "html" : thisButton.name }, guiders._buttonAttributes, thisButton.html || {})
);
if (typeof thisButton.classString !== "undefined" && thisButton.classString !== null) {
thisButtonElem.addClass(thisButton.classString);
}
guiderButtonsContainer.append(thisButtonElem);
var thisButtonName = thisButton.name.toLowerCase();
if (thisButton.onclick) {
thisButtonElem.bind(guiders._buttonClickEvent, thisButton.onclick);
} else {
switch (thisButtonName) {
case guiders._closeButtonTitle.toLowerCase():
thisButtonElem.bind(guiders._buttonClickEvent, function () {
guiders.hideAll();
if (myGuider.onClose) {
myGuider.onClose(myGuider, false /* close by button */);
}
});
break;
case guiders._nextButtonTitle.toLowerCase():
thisButtonElem.bind(guiders._buttonClickEvent, function () {
!myGuider.elem.data("locked") && guiders.next();
});
break;
case guiders._backButtonTitle.toLowerCase():
thisButtonElem.bind(guiders._buttonClickEvent, function () {
!myGuider.elem.data("locked") && guiders.prev();
});
break;
}
}
}
if (myGuider.buttonCustomHTML !== "") {
var myCustomHTML = $(myGuider.buttonCustomHTML);
myGuider.elem.find(".guiders_buttons_container").append(myCustomHTML);
}
if (myGuider.buttons.length === 0) {
guiderButtonsContainer.remove();
}
};
guiders._addXButton = function(myGuider) {
var xButtonContainer = myGuider.elem.find(".guiders_close");
var xButton = $("<div></div>", {
"class" : "guiders_x_button",
"role" : "button"
});
xButtonContainer.append(xButton);
xButton.click(function() {
guiders.hideAll();
if (myGuider.onClose) {
myGuider.onClose(myGuider, true);
}
});
};
guiders._wireEscape = function (myGuider) {
$(document).keydown(function(event) {
if (event.keyCode == 27 || event.which == 27) {
guiders.hideAll();
if (myGuider.onClose) {
myGuider.onClose(myGuider, true /*close by X/Escape*/);
}
return false;
}
});
};
guiders._unWireEscape = function (myGuider) {
$(document).unbind("keydown");
};
guiders._attach = function(myGuider) {
if (typeof myGuider !== 'object') {
return;
}
var attachTo = $(myGuider.attachTo);
var myHeight = myGuider.elem.innerHeight();
var myWidth = myGuider.elem.innerWidth();
if (myGuider.position === 0 || attachTo.length === 0) {
var fixedOrAbsolute = "fixed";
if (guiders._isIE && guiders._ieVersion < 9) {
fixedOrAbsolute = "absolute";
}
myGuider.elem.css("position", fixedOrAbsolute);
myGuider.elem.css("top", ($(window).height() - myHeight) / 3 + "px");
myGuider.elem.css("left", ($(window).width() - myWidth) / 2 + "px");
return;
}
// Otherwise, the guider is positioned relative to the attachTo element.
var base = attachTo.offset();
var top = base.top;
var left = base.left;
// topMarginOfBody corrects positioning if body has a top margin set on it.
var topMarginOfBody = $("body").outerHeight(true) - $("body").outerHeight(false);
top -= topMarginOfBody;
// Now, take into account how the guider should be positioned relative to the attachTo element.
// e.g. top left, bottom center, etc.
if (guiders._offsetNameMapping[myGuider.position]) {
// As an alternative to the clock model, you can also use keywords to position the guider.
myGuider.position = guiders._offsetNameMapping[myGuider.position];
}
var attachToHeight = attachTo.innerHeight();
var attachToWidth = attachTo.innerWidth();
var bufferOffset = 0.9 * guiders._arrowSize;
// offsetMap follows the form: [height, width]
var offsetMap = {
1: [-bufferOffset - myHeight, attachToWidth - myWidth],
2: [0, bufferOffset + attachToWidth],
3: [attachToHeight/2 - myHeight/2, bufferOffset + attachToWidth],
4: [attachToHeight - myHeight, bufferOffset + attachToWidth],
5: [bufferOffset + attachToHeight, attachToWidth - myWidth],
6: [bufferOffset + attachToHeight, attachToWidth/2 - myWidth/2],
7: [bufferOffset + attachToHeight, 0],
8: [attachToHeight - myHeight, -myWidth - bufferOffset],
9: [attachToHeight/2 - myHeight/2, -myWidth - bufferOffset],
10: [0, -myWidth - bufferOffset],
11: [-bufferOffset - myHeight, 0],
12: [-bufferOffset - myHeight, attachToWidth/2 - myWidth/2]
};
var offset = offsetMap[myGuider.position];
top += offset[0];
left += offset[1];
var positionType = "absolute";
// If the element you are attaching to is position: fixed, then we will make the guider
// position: fixed as well.
if (attachTo.css("position") === "fixed" && guiders._fixedOrAbsolute === "fixed") {
positionType = "fixed";
top -= $(window).scrollTop();
left -= $(window).scrollLeft();
}
// If you specify an additional offset parameter when you create the guider, it gets added here.
if (myGuider.offset.top !== null) {
top += myGuider.offset.top;
}
if (myGuider.offset.left !== null) {
left += myGuider.offset.left;
}
guiders._styleArrow(myGuider);
// Finally, set the style of the guider and return it!
myGuider.elem.css({
"position": positionType,
"top": top,
"left": left
});
return myGuider;
};
guiders._guiderById = function(id) {
if (typeof guiders._guiders[id] === "undefined") {
throw "Cannot find guider with id " + id;
}
return guiders._guiders[id];
};
guiders._showOverlay = function() {
// This callback is needed to fix an IE opacity bug.
// See also:
// http://www.kevinleary.net/jquery-fadein-fadeout-problems-in-internet-explorer/
$("#guiders_overlay").fadeIn("fast", function(){
if (this.style.removeAttribute) {
this.style.removeAttribute("filter");
}
});
if (guiders._isIE) {
$("#guiders_overlay").css("position", "absolute");
}
};
guiders._highlightElement = function(selector) {
$(selector).addClass('guiders_highlight');
};
guiders._dehighlightElement = function(selector) {
$(selector).removeClass('guiders_highlight');
};
guiders._hideOverlay = function() {
$("#guiders_overlay").fadeOut("fast");
};
guiders._initializeOverlay = function() {
if ($("#guiders_overlay").length === 0) {
$("<div id='guiders_overlay'></div>").hide().appendTo("body");
}
};
guiders._styleArrow = function(myGuider) {
var position = myGuider.position || 0;
if (!position) {
return;
}
var myGuiderArrow = $(myGuider.elem.find(".guiders_arrow"));
var newClass = {
1: "guiders_arrow_down",
2: "guiders_arrow_left",
3: "guiders_arrow_left",
4: "guiders_arrow_left",
5: "guiders_arrow_up",
6: "guiders_arrow_up",
7: "guiders_arrow_up",
8: "guiders_arrow_right",
9: "guiders_arrow_right",
10: "guiders_arrow_right",
11: "guiders_arrow_down",
12: "guiders_arrow_down"
};
myGuiderArrow.addClass(newClass[position]);
var myHeight = myGuider.elem.innerHeight();
var myWidth = myGuider.elem.innerWidth();
var arrowOffset = guiders._arrowSize / 2;
var positionMap = {
1: ["right", arrowOffset],
2: ["top", arrowOffset],
3: ["top", myHeight/2 - arrowOffset],
4: ["bottom", arrowOffset],
5: ["right", arrowOffset],
6: ["left", myWidth/2 - arrowOffset],
7: ["left", arrowOffset],
8: ["bottom", arrowOffset],
9: ["top", myHeight/2 - arrowOffset],
10: ["top", arrowOffset],
11: ["left", arrowOffset],
12: ["left", myWidth/2 - arrowOffset]
};
var position = positionMap[myGuider.position];
myGuiderArrow.css(position[0], position[1] + "px");
};
/**
* One way to show a guider to new users is to direct new users to a URL such as
* http://www.mysite.com/myapp#guider=welcome
*
* This can also be used to run guiders on multiple pages, by redirecting from
* one page to another, with the guider id in the hash tag.
*
* Alternatively, if you use a session variable or flash messages after sign up,
* you can add selectively add JavaScript to the page: "guiders.show('first');"
*/
guiders._showIfHashed = function(myGuider) {
var GUIDER_HASH_TAG = "guider=";
var hashIndex = window.location.hash.indexOf(GUIDER_HASH_TAG);
if (hashIndex !== -1) {
var hashGuiderId = window.location.hash.substr(hashIndex + GUIDER_HASH_TAG.length);
if (myGuider.id.toLowerCase() === hashGuiderId.toLowerCase()) {
guiders.show(myGuider.id);
}
}
};
guiders.reposition = function() {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
guiders._attach(currentGuider);
};
guiders.next = function() {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (typeof currentGuider === "undefined") {
return;
}
currentGuider.elem.data("locked", true);
var nextGuiderId = currentGuider.next || null;
if (nextGuiderId !== null && nextGuiderId !== "") {
var nextGuider = guiders._guiderById(nextGuiderId);
var omitHidingOverlay = nextGuider.overlay ? true : false;
guiders.hideAll(omitHidingOverlay, true);
if (currentGuider && currentGuider.highlight) {
guiders._dehighlightElement(currentGuider.highlight);
}
if (nextGuider.shouldSkip && nextGuider.shouldSkip()) {
guiders._currentGuiderID = nextGuider.id;
guiders.next();
return;
}
else {
guiders.show(nextGuiderId);
}
}
};
guiders.prev = function () {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (typeof currentGuider === "undefined") {
// not what we think it is
return;
}
if (currentGuider.prev === null) {
// no previous to look at
return;
}
var prevGuider = guiders._guiders[currentGuider.prev];
prevGuider.elem.data("locked", true);
// Note we use prevGuider.id as "prevGuider" is _already_ looking at the previous guider
var prevGuiderId = prevGuider.id || null;
if (prevGuiderId !== null && prevGuiderId !== "") {
var myGuider = guiders._guiderById(prevGuiderId);
var omitHidingOverlay = myGuider.overlay ? true : false;
guiders.hideAll(omitHidingOverlay, true);
if (prevGuider && prevGuider.highlight) {
guiders._dehighlightElement(prevGuider.highlight);
}
guiders.show(prevGuiderId);
}
};
guiders.createGuider = function(passedSettings) {
if (passedSettings === null || passedSettings === undefined) {
passedSettings = {};
}
// Extend those settings with passedSettings
myGuider = $.extend({}, guiders._defaultSettings, passedSettings);
myGuider.id = myGuider.id || String(Math.floor(Math.random() * 1000));
var guiderElement = $(guiders._htmlSkeleton);
myGuider.elem = guiderElement;
if (typeof myGuider.classString !== "undefined" && myGuider.classString !== null) {
myGuider.elem.addClass(myGuider.classString);
}
myGuider.elem.css("width", myGuider.width + "px");
var guiderTitleContainer = guiderElement.find(".guiders_title");
guiderTitleContainer.html(myGuider.title);
guiderElement.find(".guiders_description").html(myGuider.description);
guiders._addButtons(myGuider);
if (myGuider.xButton) {
guiders._addXButton(myGuider);
}
guiderElement.hide();
guiderElement.appendTo("body");
guiderElement.attr("id", myGuider.id);
// Ensure myGuider.attachTo is a jQuery element.
if (typeof myGuider.attachTo !== "undefined" && myGuider !== null) {
guiders._attach(myGuider);
}
guiders._initializeOverlay();
guiders._guiders[myGuider.id] = myGuider;
if (guiders._lastCreatedGuiderID != null) {
myGuider.prev = guiders._lastCreatedGuiderID;
}
guiders._lastCreatedGuiderID = myGuider.id;
/**
* If the URL of the current window is of the form
* http://www.myurl.com/mypage.html#guider=id
* then show this guider.
*/
if (myGuider.isHashable) {
guiders._showIfHashed(myGuider);
}
return guiders;
};
guiders.hideAll = function(omitHidingOverlay, next) {
next = next || false;
$(".guider:visible").each(function(index, elem){
var myGuider = guiders._guiderById($(elem).attr('id'));
if (myGuider.onHide) {
myGuider.onHide(myGuider, next);
}
});
$(".guider").fadeOut("fast");
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (currentGuider && currentGuider.highlight) {
guiders._dehighlightElement(currentGuider.highlight);
}
if (typeof omitHidingOverlay !== "undefined" && omitHidingOverlay === true) {
// do nothing for now
} else {
guiders._hideOverlay();
}
return guiders;
};
guiders.show = function(id) {
if (!id && guiders._lastCreatedGuiderID) {
id = guiders._lastCreatedGuiderID;
}
var myGuider = guiders._guiderById(id);
if (myGuider.overlay) {
guiders._showOverlay();
// if guider is attached to an element, make sure it's visible
if (myGuider.highlight) {
guiders._highlightElement(myGuider.highlight);
}
}
if (myGuider.closeOnEscape) {
guiders._wireEscape(myGuider);
} else {
guiders._unWireEscape(myGuider);
}
// You can use an onShow function to take some action before the guider is shown.
if (myGuider.onShow) {
myGuider.onShow(myGuider);
}
guiders._attach(myGuider);
myGuider.elem.fadeIn("fast").data("locked", false);
guiders._currentGuiderID = id;
var windowHeight = guiders._windowHeight = $(window).height();
var scrollHeight = $(window).scrollTop();
var guiderOffset = myGuider.elem.offset();
var guiderElemHeight = myGuider.elem.height();
var isGuiderBelow = (scrollHeight + windowHeight < guiderOffset.top + guiderElemHeight); /* we will need to scroll down */
var isGuiderAbove = (guiderOffset.top < scrollHeight); /* we will need to scroll up */
if (myGuider.autoFocus && (isGuiderBelow || isGuiderAbove)) {
// Sometimes the browser won't scroll if the person just clicked,
// so let's do this in a setTimeout.
setTimeout(guiders.scrollToCurrent, 10);
}
$(myGuider.elem).trigger("guiders.show");
return guiders;
};
guiders.scrollToCurrent = function() {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (typeof currentGuider === "undefined") {
return;
}
var windowHeight = guiders._windowHeight;
var scrollHeight = $(window).scrollTop();
var guiderOffset = currentGuider.elem.offset();
var guiderElemHeight = currentGuider.elem.height();
// Scroll to the guider's position.
var scrollToHeight = Math.round(Math.max(guiderOffset.top + (guiderElemHeight / 2) - (windowHeight / 2), 0));
window.scrollTo(0, scrollToHeight);
};
// Change the bubble position after browser gets resized
var _resizing = undefined;
$(window).resize(function() {
if (typeof(_resizing) !== "undefined") {
clearTimeout(_resizing); // Prevents seizures
}
_resizing = setTimeout(function() {
_resizing = undefined;
if (typeof (guiders) !== "undefined") {
guiders.reposition();
}
}, 20);
});
return guiders;
}).call(this, jQuery);

View file

@ -0,0 +1,630 @@
// TODO actually recognize syntax of TypeScript constructs
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonMode = parserConfig.json;
var isTS = parserConfig.typescript;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("interface"),
"extends": kw("extends"),
"constructor": kw("constructor"),
// scope modifiers
"public": kw("public"),
"private": kw("private"),
"protected": kw("protected"),
"static": kw("static"),
// types
"string": type, "number": type, "bool": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|~^]/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (state.lastType == "operator" || state.lastType == "keyword c" ||
state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
readRegexp(stream);
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) break;
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (/[$\w]/.test(ch)) {
sawSomething = true;
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
function inList(list) {
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false;
}
var state = cx.state;
if (state.context) {
cx.marked = "def";
if (inList(state.localVars)) return;
state.localVars = {name: varname, next: state.localVars};
} else {
if (inList(state.globalVars)) return;
if (parserConfig.globalVars)
state.globalVars = {name: varname, next: state.globalVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
if (type == "class") return cont(pushlex("form"), className, objlit, poplex);
if (type == "export") return cont(pushlex("form"), afterExport, poplex);
if (type == "import") return cont(pushlex("form"), afterImport, poplex);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
return expressionInner(type, false);
}
function expressionNoComma(type) {
return expressionInner(type, true);
}
function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeexpressionNoComma(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expressionNoComma);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (value == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { cx.cc.push(me); return quasi(value); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
}
function quasi(value) {
if (value.slice(value.length - 2) != "${") return cont();
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont();
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
if (type == "{") return pass(statement);
return pass(expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
if (type == "{") return pass(statement);
return pass(expressionNoComma);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "variable") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
} else if (type == "number" || type == "string") {
cx.marked = type + " property";
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
}
if (atomicTypes.hasOwnProperty(type)) return cont(afterprop);
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(what, proceed);
}
if (type == end) return cont();
return cont(expect(end));
}
return function(type) {
if (type == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type) {
if (isTS && type == ":") return cont(typedef);
}
function typedef(type) {
if (type == "variable"){cx.marked = "variable-3"; return cont();}
}
function vardef() {
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (type == "variable") { register(value); return cont(); }
if (type == "[") return contCommasep(pattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
return cont(expect(":"), pattern, maybeAssign);
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
}
function forspec(type) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type) {
if (type == "spread") return cont(funarg);
return pass(pattern, maybetype);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(_type, value) {
if (value == "extends") return cont(expression);
}
function objlit(type) {
if (type == "{") return contCommasep(objprop, "}");
}
function afterModule(type, value) {
if (type == "string") return cont(statement);
if (type == "variable") { register(value); return cont(maybeFrom); }
}
function afterExport(_type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
return pass(statement);
}
function afterImport(type) {
if (type == "string") return cont();
return pass(importSpec, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
return cont();
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(expressionNoComma, maybeArrayComprehension);
}
function maybeArrayComprehension(type) {
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(expressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]"));
}
function comprehension(type) {
if (type == "for") return cont(forspec, comprehension);
if (type == "if") return cont(expression, comprehension);
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
if (parserConfig.globalVars) state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
// Kludge to prevent 'maybelse' from blocking lexical scope pops
for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}",
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
fold: "brace",
helperType: jsonMode ? "json" : "javascript",
jsonMode: jsonMode
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });

4
book/common/js/jquery-fix.js vendored Normal file
View file

@ -0,0 +1,4 @@
// The Bootstrap-based Sphinx theme uses $jqTheme
// Setting $ is needed to override the (old!) version of jQuery packaged with Sphinx
var $jqTheme = jQuery.noConflict();
var $ = jQuery.noConflict();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,53 @@
/*
highlight v4
Highlights arbitrary terms.
<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>
MIT license.
Johann Burkard
<http://johannburkard.de>
<mailto:jb@eaio.com>
*/
jQuery.fn.highlight = function(pat) {
function innerHighlight(node, pat) {
var skip = 0;
if (node.nodeType == 3) {
var pos = node.data.toUpperCase().indexOf(pat);
if (pos >= 0) {
var spannode = document.createElement('span');
spannode.className = 'highlight';
var middlebit = node.splitText(pos);
var endbit = middlebit.splitText(pat.length);
var middleclone = middlebit.cloneNode(true);
spannode.appendChild(middleclone);
middlebit.parentNode.replaceChild(spannode, middlebit);
skip = 1;
}
}
else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
for (var i = 0; i < node.childNodes.length; ++i) {
i += innerHighlight(node.childNodes[i], pat);
}
}
return skip;
}
return this.length && pat && pat.length ? this.each(function() {
innerHighlight(this, pat.toUpperCase());
}) : this;
};
jQuery.fn.removeHighlight = function() {
return this.find("span.highlight").each(function() {
this.parentNode.firstChild.nodeName;
with (this.parentNode) {
replaceChild(this.firstChild, this);
normalize();
}
}).end();
};

View file

@ -0,0 +1,15 @@
/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
(function(e){function g(a){"string"===typeof a.data&&(a.data={keys:a.data});if(a.data&&a.data.keys&&"string"===typeof a.data.keys){var g=a.handler,h=a.data.keys.toLowerCase().split(" "),k="text password number email url range date month week time datetime datetime-local search color tel".split(" ");a.handler=function(c){if(this===c.target||!(/textarea|select/i.test(c.target.nodeName)||-1<e.inArray(c.target.type,k))){var d=e.hotkeys.specialKeys[c.keyCode],a="keypress"===c.type&&String.fromCharCode(c.which).toLowerCase(),
b="",f={};c.altKey&&"alt"!==d&&(b+="alt+");c.ctrlKey&&"ctrl"!==d&&(b+="ctrl+");c.metaKey&&(!c.ctrlKey&&"meta"!==d)&&(b+="meta+");c.shiftKey&&"shift"!==d&&(b+="shift+");d&&(f[b+d]=!0);a&&(f[b+a]=!0,f[b+e.hotkeys.shiftNums[a]]=!0,"shift+"===b&&(f[e.hotkeys.shiftNums[a]]=!0));d=0;for(a=h.length;d<a;d++)if(f[h[d]])return g.apply(this,arguments)}}}}e.hotkeys={version:"0.8",specialKeys:{8:"backspace",9:"tab",10:"return",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",
33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",186:";",191:"/",220:"\\",222:"'",224:"meta"},shiftNums:{"`":"~",1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")","-":"_","=":"+",";":": ",
"'":'"',",":"<",".":">","/":"?","\\":"|"}};e.each(["keydown","keyup","keypress"],function(){e.event.special[this]={add:g}})})(this.jQuery);

View file

@ -0,0 +1,262 @@
/*!
* jQuery idleTimer plugin
* version 0.9.100511
* by Paul Irish.
* http://github.com/paulirish/yui-misc/tree/
* MIT license
* adapted from YUI idle timer by nzakas:
* http://github.com/nzakas/yui-misc/
*/
/*
* Copyright (c) 2009 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* updated to fix Chrome setTimeout issue by Zaid Zawaideh */
// API available in <= v0.8
/*******************************
// idleTimer() takes an optional argument that defines the idle timeout
// timeout is in milliseconds; defaults to 30000
$.idleTimer(10000);
$(document).bind("idle.idleTimer", function(){
// function you want to fire when the user goes idle
});
$(document).bind("active.idleTimer", function(){
// function you want to fire when the user becomes active again
});
// pass the string 'destroy' to stop the timer
$.idleTimer('destroy');
// you can query if the user is idle or not with data()
$.data(document,'idleTimer'); // 'idle' or 'active'
// you can get time elapsed since user when idle/active
$.idleTimer('getElapsedTime'); // time since state change in ms
********/
// API available in >= v0.9
/*************************
// bind to specific elements, allows for multiple timer instances
$(elem).idleTimer(timeout|'destroy'|'getElapsedTime');
$.data(elem,'idleTimer'); // 'idle' or 'active'
// if you're using the old $.idleTimer api, you should not do $(document).idleTimer(...)
// element bound timers will only watch for events inside of them.
// you may just want page-level activity, in which case you may set up
// your timers on document, document.documentElement, and document.body
// You can optionally provide a second argument to override certain options.
// Here are the defaults, so you can omit any or all of them.
$(elem).idleTimer(timeout, {
startImmediately: true, //starts a timeout as soon as the timer is set up; otherwise it waits for the first event.
idle: false, //indicates if the user is idle
enabled: true, //indicates if the idle timer is enabled
events: 'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove' // activity is one of these events
});
********/
(function($){
$.idleTimer = function(newTimeout, elem, opts){
// defaults that are to be stored as instance props on the elem
opts = $.extend({
startImmediately: true, //starts a timeout as soon as the timer is set up
idle: false, //indicates if the user is idle
enabled: true, //indicates if the idle timer is enabled
timeout: 30000, //the amount of time (ms) before the user is considered idle
events: 'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove' // activity is one of these events
}, opts);
elem = elem || document;
/* (intentionally not documented)
* Toggles the idle state and fires an appropriate event.
* @return {void}
*/
var toggleIdleState = function(myelem){
// curse you, mozilla setTimeout lateness bug!
if (typeof myelem === 'number'){
myelem = undefined;
}
var obj = $.data(myelem || elem,'idleTimerObj');
//toggle the state
obj.idle = !obj.idle;
// reset timeout
var elapsed = (+new Date()) - obj.olddate;
obj.olddate = +new Date();
// handle Chrome always triggering idle after js alert or comfirm popup
if (obj.idle && (elapsed < opts.timeout)) {
obj.idle = false;
clearTimeout($.idleTimer.tId);
if (opts.enabled)
$.idleTimer.tId = setTimeout(toggleIdleState, opts.timeout);
return;
}
//fire appropriate event
// create a custom event, but first, store the new state on the element
// and then append that string to a namespace
var event = jQuery.Event( $.data(elem,'idleTimer', obj.idle ? "idle" : "active" ) + '.idleTimer' );
// we do want this to bubble, at least as a temporary fix for jQuery 1.7
// event.stopPropagation();
$(elem).trigger(event);
},
/**
* Stops the idle timer. This removes appropriate event handlers
* and cancels any pending timeouts.
* @return {void}
* @method stop
* @static
*/
stop = function(elem){
var obj = $.data(elem,'idleTimerObj') || {};
//set to disabled
obj.enabled = false;
//clear any pending timeouts
clearTimeout(obj.tId);
//detach the event handlers
$(elem).off('.idleTimer');
},
/* (intentionally not documented)
* Handles a user event indicating that the user isn't idle.
* @param {Event} event A DOM2-normalized event object.
* @return {void}
*/
handleUserEvent = function(){
var obj = $.data(this,'idleTimerObj');
//clear any existing timeout
clearTimeout(obj.tId);
//if the idle timer is enabled
if (obj.enabled){
//if it's idle, that means the user is no longer idle
if (obj.idle){
toggleIdleState(this);
}
//set a new timeout
obj.tId = setTimeout(toggleIdleState, obj.timeout);
}
};
/**
* Starts the idle timer. This adds appropriate event handlers
* and starts the first timeout.
* @param {int} newTimeout (Optional) A new value for the timeout period in ms.
* @return {void}
* @method $.idleTimer
* @static
*/
var obj = $.data(elem,'idleTimerObj') || {};
obj.olddate = obj.olddate || +new Date();
//assign a new timeout if necessary
if (typeof newTimeout === "number"){
opts.timeout = newTimeout;
} else if (newTimeout === 'destroy') {
stop(elem);
return this;
} else if (newTimeout === 'getElapsedTime'){
return (+new Date()) - obj.olddate;
}
//assign appropriate event handlers
$(elem).on($.trim((opts.events+' ').split(' ').join('.idleTimer ')),handleUserEvent);
obj.idle = opts.idle;
obj.enabled = opts.enabled;
obj.timeout = opts.timeout;
//set a timeout to toggle state. May wish to omit this in some situations
if (opts.startImmediately) {
obj.tId = setTimeout(toggleIdleState, obj.timeout);
}
// assume the user is active for the first x seconds.
$.data(elem,'idleTimer',"active");
// store our instance on the object
$.data(elem,'idleTimerObj',obj);
}; // end of $.idleTimer()
// v0.9 API for defining multiple timers.
$.fn.idleTimer = function(newTimeout,opts){
// Allow omission of opts for backward compatibility
if (!opts) {
opts = {};
}
if(this[0]){
$.idleTimer(newTimeout,this[0],opts);
}
return this;
};
})(jQuery);

5
book/common/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

219
book/common/js/navhelp.js Normal file
View file

@ -0,0 +1,219 @@
/**
* Created by isaacdontjelindell on 8/2/13.
*/
/* Sets up the interactive navhelp */
function setup() {
guiders.createGuider({
buttons: [{name: "Next"}],
attachTo: ".title-link-img",
highlight: ".title-link-img",
overlay: true,
position: 3,
title: "Table of Contents",
description: "Click on the title at any time to see the Table of Contents for this textbook.",
id: "first",
next: "second"
}).show();
guiders.createGuider({
attachTo: ".logo-link-img",
highlight: ".logo-link-img",
overlay: true,
position: 3,
title: "Homepage",
description: "Click on the Runestone Interactive logo to go back to the homepage, where you can see the other textbooks that are available.",
id: "second",
next: "third"
});
guiders.createGuider({
attachTo: ".page-dropdown-img",
highlight: ".page-dropdown-img",
overlay: true,
position: 3,
title: "Page Navigation",
description: "Click here to jump to a section within the current chapter.",
id: "third",
next: "fourth"
});
guiders.createGuider({
attachTo: ".search-dropdown-img",
highlight: ".search-dropdown-img",
overlay: true,
position: 3,
title: "Search Menu",
description: "This menu allows you to search this textbook, as well as open a scratchpad. You can also press the '\\' (backslash) key at any time to open the scratchpad.",
id: "fourth",
next: "fifth"
});
guiders.createGuider({
attachTo: ".user-dropdown-img",
highlight: ".user-dropdown-img",
overlay: true,
position: 3,
title: "Account Menu",
description: "Log in or register here so that you can save and load code you write and save your position in the textbook. Don't worry, it's easy!",
id: "fifth",
next: "sixth"
});
guiders.createGuider({
attachTo: "#codeexample1 .ac_caption",
highlight: "#codeexample1 .ac_caption",
title: "ActiveCode Blocks",
description: "ActiveCode blocks allow you to write and execute Python code right in the textbook.",
id: "sixth",
next: "seventh"
});
guiders.createGuider({
attachTo: "#codeexample1_code_div",
highlight: "#codeexample1_code_div",
overlay: true,
title: "ActiveCode Editor",
description: "Write and edit code in this text window...",
id: "seventh",
next: "eighth"
});
guiders.createGuider({
attachTo: "#codeexample1_runb",
highlight: "#codeexample1_runb",
overlay: true,
position: 3,
title: "ActiveCode Editor",
description: "...and then click the 'Run' button to execute your code.",
id: "eighth",
next: "ninth"
});
guiders.createGuider({
attachTo: "#codeexample1_saveb",
highlight: "#codeexample1_saveb",
overlay: true,
position: 3,
title: "ActiveCode Blocks",
description: "If you are logged in, you can save your code, and then load again later.",
id: "ninth",
next: "tenth"
});
guiders.createGuider({
attachTo: "#firstexample table",
highlight: "#firstexample table",
title: "CodeLens",
description: "The CodeLens visualizer allows you to execute some code step-by-step, and see the values of all the variables and objects as they are executed.",
id: "tenth",
next: "eleventh"
});
guiders.createGuider({
attachTo: "#firstexample #jmpStepFwd",
highlight: "#firstexample #jmpStepFwd",
overlay: true,
title: "CodeLens Controls",
description: "Use these buttons below the code window to control how you step through the code.",
id: "eleventh",
next: "twelfth"
});
guiders.createGuider({
attachTo: "#question1_1",
highlight: "#question1_1",
overlay: true,
title: "Self-Check Questions",
description: "These questions allow you to check your understand as you move through the textbook.",
id: "twelfth",
next: "thirteenth"
});
guiders.createGuider({
attachTo: "#question1_1 button[name='do answer']",
highlight: "#question1_1 button[name='do answer']",
overlay: true,
position: 3,
title: "Self-Check Questions",
description: "Click this button to get feedback on your answer(s).",
id: "thirteenth",
next: "fourteenth"
});
guiders.createGuider({
attachTo: "#question1_1 button[name='compare']",
highlight: "#question1_1 button[name='compare']",
overlay: true,
position: 3,
title: "Self-Check Questions",
description: "Click this button to get see how you are doing in relation to other people using the textbook.",
id: "fourteenth",
next: "fifteenth"
});
guiders.createGuider({
attachTo: ".parsons",
highlight: ".parsons",
overlay: true,
title: "Parsons Problems",
description: "Parsons exercises ask you to arrange lines of code in the correct order.",
id: "fifteenth",
next: "sixteenth"
});
guiders.createGuider({
attachTo: "#parsons-sortableTrash-111",
highlight: "#parsons-sortableTrash-111",
overlay: true,
title: "Parsons Problems",
description: "Drag lines of code from here...",
id: "sixteenth",
next: "seventeenth"
});
guiders.createGuider({
attachTo: "#ul-parsons-sortableCode-111",
highlight: "#ul-parsons-sortableCode-111",
overlay: true,
title: "Parsons Problems",
description: "...to here.",
id: "seventeenth",
next: "eighteenth"
});
guiders.createGuider({
attachTo: "#checkMe111",
highlight: "#checkMe111",
overlay: true,
position: 3,
title: "Parsons Problems",
description: "Click this button to check if you've arranged the code in the correct order.",
id: "eighteenth",
next: "nineteenth"
});
guiders.createGuider({
attachTo: "#embedded-videos img",
highlight: "#embedded-videos img",
overlay: true,
title: "Embedded Videos",
description: "To play a video embedded in the text, just click the play button.",
id: "nineteenth",
next: "twentieth"
});
guiders.createGuider({
buttons: [{name: "Close"}],
attachTo: "body",
position: 0,
overlay: true,
title: "Thank You!",
description: "Thanks for using this interactive textbook. ",
id: "twentieth"
});
}
setup();

View file

@ -0,0 +1,5 @@
// revert the version of underscore and jquery to whatever it was
// before parson's code included header files
var $pjQ = jQuery.noConflict(true);
var _p = _.noConflict();

61
book/common/js/poll.js Normal file
View file

@ -0,0 +1,61 @@
// Javascript needed for the poll Sphinx directive type
function submitPoll(div_id) {
var form = $("#"+div_id+"_poll");
var poll_val = form.find("input:radio[name="+div_id +"_opt]:checked").val();
if(poll_val === undefined)
return;
var poll_comment = form.find("input:text[name="+div_id+"_comment]").val();
var act = '';
if((poll_comment === undefined) || (poll_comment[0] !== undefined))
act = poll_val + ":" + poll_comment;
else
act = poll_val;
var eventInfo = {'event':'poll', 'act':act, 'div_id':div_id};
// log the response to the database
logBookEvent(eventInfo); // in bookfuncs.js
// log the fact that the user has answered the poll to local storage
localStorage.setItem(div_id, "true");
// hide the poll inputs
$("#"+div_id+"_poll_input").hide();
// show the results of the poll
var data = {};
data.div_id = div_id;
data.course = eBookConfig.course;
jQuery.get(eBookConfig.ajaxURL+'getpollresults', data, showPollResults);
}
function showPollResults(data) {
// create the display of the poll results
results = eval(data);
var total = results[0];
var opt_list = results[1];
var count_list = results[2];
var div_id = results[3];
var result_div = $("#"+div_id+"_results");
result_div.html("<b>Results:</b><br><br>");
result_div.show();
var list = $(document.createElement("ol"));
for(var i=0; i<opt_list.length; i++) {
var count = count_list[i];
var percent = (count / total) * 100;
var text = Math.round(10*percent)/10 + "%"; // round percent to 10ths
var html = "<li value='"+opt_list[i]+"'><div class='progress'><div class='progress-bar progress-bar-success' style='width:"+percent+"%;'><span class='poll-text'>"+text+"</span></div></div></li>";
var el = $(html);
list.append(el);
}
result_div.append(list);
}

13
book/common/js/processing-1.4.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

340
book/common/js/python.js Normal file
View file

@ -0,0 +1,340 @@
CodeMirror.defineMode("python", function(conf, parserConf) {
var ERRORCLASS = 'error';
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b");
}
var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
'def', 'del', 'elif', 'else', 'except', 'finally',
'for', 'from', 'global', 'if', 'import',
'lambda', 'pass', 'raise', 'return',
'try', 'while', 'with', 'yield'];
var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr',
'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset',
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id',
'input', 'int', 'isinstance', 'issubclass', 'iter', 'len',
'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next',
'object', 'oct', 'open', 'ord', 'pow', 'property', 'range',
'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
'type', 'vars', 'zip', '__import__', 'NotImplemented',
'Ellipsis', '__debug__'];
var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile',
'file', 'intern', 'long', 'raw_input', 'reduce', 'reload',
'unichr', 'unicode', 'xrange', 'False', 'True', 'None'],
'keywords': ['exec', 'print']};
var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'],
'keywords': ['nonlocal', 'False', 'True', 'None']};
if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
commonkeywords = commonkeywords.concat(py3.keywords);
commonBuiltins = commonBuiltins.concat(py3.builtins);
var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
} else {
commonkeywords = commonkeywords.concat(py2.keywords);
commonBuiltins = commonBuiltins.concat(py2.builtins);
var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
}
var keywords = wordRegexp(commonkeywords);
var builtins = wordRegexp(commonBuiltins);
var indentInfo = null;
// tokenizers
function tokenBase(stream, state) {
// Handle scope changes
if (stream.sol()) {
var scopeOffset = state.scopes[0].offset;
if (stream.eatSpace()) {
var lineOffset = stream.indentation();
if (lineOffset > scopeOffset) {
indentInfo = 'indent';
} else if (lineOffset < scopeOffset) {
indentInfo = 'dedent';
}
return null;
} else {
if (scopeOffset > 0) {
dedent(stream, state);
}
}
}
if (stream.eatSpace()) {
return null;
}
var ch = stream.peek();
// Handle Comments
if (ch === '#') {
stream.skipToEnd();
return 'comment';
}
// Handle Number Literals
if (stream.match(/^[0-9\.]/, false)) {
var floatLiteral = false;
// Floats
if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
if (stream.match(/^\.\d+/)) { floatLiteral = true; }
if (floatLiteral) {
// Float literals may be "imaginary"
stream.eat(/J/i);
return 'number';
}
// Integers
var intLiteral = false;
// Hex
if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
// Binary
if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
// Octal
if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
// Decimal
if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
// Decimal literals may be "imaginary"
stream.eat(/J/i);
// TODO - Can you have imaginary longs?
intLiteral = true;
}
// Zero by itself with no other piece of number.
if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
if (intLiteral) {
// Integer literals may be "long"
stream.eat(/L/i);
return 'number';
}
}
// Handle Strings
if (stream.match(stringPrefixes)) {
state.tokenize = tokenStringFactory(stream.current());
return state.tokenize(stream, state);
}
// Handle operators and Delimiters
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
return null;
}
if (stream.match(doubleOperators)
|| stream.match(singleOperators)
|| stream.match(wordOperators)) {
return 'operator';
}
if (stream.match(singleDelimiters)) {
return null;
}
if (stream.match(keywords)) {
return 'keyword';
}
if (stream.match(builtins)) {
return 'builtin';
}
if (stream.match(identifiers)) {
return 'variable';
}
// Handle non-detected items
stream.next();
return ERRORCLASS;
}
function tokenStringFactory(delimiter) {
while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
delimiter = delimiter.substr(1);
}
var singleline = delimiter.length == 1;
var OUTCLASS = 'string';
function tokenString(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"\\]/);
if (stream.eat('\\')) {
stream.next();
if (singleline && stream.eol()) {
return OUTCLASS;
}
} else if (stream.match(delimiter)) {
state.tokenize = tokenBase;
return OUTCLASS;
} else {
stream.eat(/['"]/);
}
}
if (singleline) {
if (parserConf.singleLineStringErrors) {
return ERRORCLASS;
} else {
state.tokenize = tokenBase;
}
}
return OUTCLASS;
}
tokenString.isString = true;
return tokenString;
}
function indent(stream, state, type) {
type = type || 'py';
var indentUnit = 0;
if (type === 'py') {
if (state.scopes[0].type !== 'py') {
state.scopes[0].offset = stream.indentation();
return;
}
for (var i = 0; i < state.scopes.length; ++i) {
if (state.scopes[i].type === 'py') {
indentUnit = state.scopes[i].offset + conf.indentUnit;
break;
}
}
} else {
indentUnit = stream.column() + stream.current().length;
}
state.scopes.unshift({
offset: indentUnit,
type: type
});
}
function dedent(stream, state, type) {
type = type || 'py';
if (state.scopes.length == 1) return;
if (state.scopes[0].type === 'py') {
var _indent = stream.indentation();
var _indent_index = -1;
for (var i = 0; i < state.scopes.length; ++i) {
if (_indent === state.scopes[i].offset) {
_indent_index = i;
break;
}
}
if (_indent_index === -1) {
return true;
}
while (state.scopes[0].offset !== _indent) {
state.scopes.shift();
}
return false;
} else {
if (type === 'py') {
state.scopes[0].offset = stream.indentation();
return false;
} else {
if (state.scopes[0].type != type) {
return true;
}
state.scopes.shift();
return false;
}
}
}
function tokenLexer(stream, state) {
indentInfo = null;
var style = state.tokenize(stream, state);
var current = stream.current();
// Handle '.' connected identifiers
if (current === '.') {
style = stream.match(identifiers, false) ? null : ERRORCLASS;
if (style === null && state.lastToken === 'meta') {
// Apply 'meta' style to '.' connected identifiers when
// appropriate.
style = 'meta';
}
return style;
}
// Handle decorators
if (current === '@') {
return stream.match(identifiers, false) ? 'meta' : ERRORCLASS;
}
if ((style === 'variable' || style === 'builtin')
&& state.lastToken === 'meta') {
style = 'meta';
}
// Handle scope changes.
if (current === 'pass' || current === 'return') {
state.dedent += 1;
}
if (current === 'lambda') state.lambda = true;
if ((current === ':' && !state.lambda && state.scopes[0].type == 'py')
|| indentInfo === 'indent') {
indent(stream, state);
}
var delimiter_index = '[({'.indexOf(current);
if (delimiter_index !== -1) {
indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
}
if (indentInfo === 'dedent') {
if (dedent(stream, state)) {
return ERRORCLASS;
}
}
delimiter_index = '])}'.indexOf(current);
if (delimiter_index !== -1) {
if (dedent(stream, state, current)) {
return ERRORCLASS;
}
}
if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
if (state.scopes.length > 1) state.scopes.shift();
state.dedent -= 1;
}
return style;
}
var external = {
startState: function(basecolumn) {
return {
tokenize: tokenBase,
scopes: [{offset:basecolumn || 0, type:'py'}],
lastToken: null,
lambda: false,
dedent: 0
};
},
token: function(stream, state) {
var style = tokenLexer(stream, state);
state.lastToken = style;
if (stream.eol() && stream.lambda) {
state.lambda = false;
}
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase) {
return state.tokenize.isString ? CodeMirror.Pass : 0;
}
return state.scopes[0].offset;
}
};
return external;
});
CodeMirror.defineMIME("text/x-python", "python");

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,713 @@
/**
* @license CSS Class Applier module for Rangy.
* Adds, removes and toggles CSS classes on Ranges and Selections
*
* Part of Rangy, a cross-browser JavaScript range and selection library
* http://code.google.com/p/rangy/
*
* Depends on Rangy core.
*
* Copyright 2012, Tim Down
* Licensed under the MIT license.
* Version: 1.2.3
* Build date: 26 February 2012
*/
rangy.createModule("CssClassApplier", function(api, module) {
api.requireModules( ["WrappedSelection", "WrappedRange"] );
var dom = api.dom;
var defaultTagName = "span";
function trim(str) {
return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
}
function hasClass(el, cssClass) {
return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
}
function addClass(el, cssClass) {
if (el.className) {
if (!hasClass(el, cssClass)) {
el.className += " " + cssClass;
}
} else {
el.className = cssClass;
}
}
var removeClass = (function() {
function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
}
return function(el, cssClass) {
if (el.className) {
el.className = el.className.replace(new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)"), replacer);
}
};
})();
function sortClassName(className) {
return className.split(/\s+/).sort().join(" ");
}
function getSortedClassName(el) {
return sortClassName(el.className);
}
function haveSameClasses(el1, el2) {
return getSortedClassName(el1) == getSortedClassName(el2);
}
function replaceWithOwnChildren(el) {
var parent = el.parentNode;
while (el.hasChildNodes()) {
parent.insertBefore(el.firstChild, el);
}
parent.removeChild(el);
}
function rangeSelectsAnyText(range, textNode) {
var textRange = range.cloneRange();
textRange.selectNodeContents(textNode);
var intersectionRange = textRange.intersection(range);
var text = intersectionRange ? intersectionRange.toString() : "";
textRange.detach();
return text != "";
}
function getEffectiveTextNodes(range) {
return range.getNodes([3], function(textNode) {
return rangeSelectsAnyText(range, textNode);
});
}
function elementsHaveSameNonClassAttributes(el1, el2) {
if (el1.attributes.length != el2.attributes.length) return false;
for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
attr1 = el1.attributes[i];
name = attr1.name;
if (name != "class") {
attr2 = el2.attributes.getNamedItem(name);
if (attr1.specified != attr2.specified) return false;
if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
}
}
return true;
}
function elementHasNonClassAttributes(el, exceptions) {
for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
attrName = el.attributes[i].name;
if ( !(exceptions && dom.arrayContains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
return true;
}
}
return false;
}
function elementHasProps(el, props) {
for (var p in props) {
if (props.hasOwnProperty(p) && el[p] !== props[p]) {
return false;
}
}
return true;
}
var getComputedStyleProperty;
if (typeof window.getComputedStyle != "undefined") {
getComputedStyleProperty = function(el, propName) {
return dom.getWindow(el).getComputedStyle(el, null)[propName];
};
} else if (typeof document.documentElement.currentStyle != "undefined") {
getComputedStyleProperty = function(el, propName) {
return el.currentStyle[propName];
};
} else {
module.fail("No means of obtaining computed style properties found");
}
var isEditableElement;
(function() {
var testEl = document.createElement("div");
if (typeof testEl.isContentEditable == "boolean") {
isEditableElement = function(node) {
return node && node.nodeType == 1 && node.isContentEditable;
};
} else {
isEditableElement = function(node) {
if (!node || node.nodeType != 1 || node.contentEditable == "false") {
return false;
}
return node.contentEditable == "true" || isEditableElement(node.parentNode);
};
}
})();
function isEditingHost(node) {
var parent;
return node && node.nodeType == 1
&& (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
|| (isEditableElement(node) && !isEditableElement(node.parentNode)));
}
function isEditable(node) {
return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
}
var inlineDisplayRegex = /^inline(-block|-table)?$/i;
function isNonInlineElement(node) {
return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
}
// White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
function isUnrenderedWhiteSpaceNode(node) {
if (node.data.length == 0) {
return true;
}
if (htmlNonWhiteSpaceRegex.test(node.data)) {
return false;
}
var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
switch (cssWhiteSpace) {
case "pre":
case "pre-wrap":
case "-moz-pre-wrap":
return false;
case "pre-line":
if (/[\r\n]/.test(node.data)) {
return false;
}
}
// We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
// non-inline element, it will not be rendered. This seems to be a good enough definition.
return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
}
function isSplitPoint(node, offset) {
if (dom.isCharacterDataNode(node)) {
if (offset == 0) {
return !!node.previousSibling;
} else if (offset == node.length) {
return !!node.nextSibling;
} else {
return true;
}
}
return offset > 0 && offset < node.childNodes.length;
}
function splitNodeAt(node, descendantNode, descendantOffset, rangesToPreserve) {
var newNode;
var splitAtStart = (descendantOffset == 0);
if (dom.isAncestorOf(descendantNode, node)) {
return node;
}
if (dom.isCharacterDataNode(descendantNode)) {
if (descendantOffset == 0) {
descendantOffset = dom.getNodeIndex(descendantNode);
descendantNode = descendantNode.parentNode;
} else if (descendantOffset == descendantNode.length) {
descendantOffset = dom.getNodeIndex(descendantNode) + 1;
descendantNode = descendantNode.parentNode;
} else {
throw module.createError("splitNodeAt should not be called with offset in the middle of a data node ("
+ descendantOffset + " in " + descendantNode.data);
}
}
if (isSplitPoint(descendantNode, descendantOffset)) {
if (!newNode) {
newNode = descendantNode.cloneNode(false);
if (newNode.id) {
newNode.removeAttribute("id");
}
var child;
while ((child = descendantNode.childNodes[descendantOffset])) {
newNode.appendChild(child);
}
dom.insertAfter(newNode, descendantNode);
}
return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, dom.getNodeIndex(newNode), rangesToPreserve);
} else if (node != descendantNode) {
newNode = descendantNode.parentNode;
// Work out a new split point in the parent node
var newNodeIndex = dom.getNodeIndex(descendantNode);
if (!splitAtStart) {
newNodeIndex++;
}
return splitNodeAt(node, newNode, newNodeIndex, rangesToPreserve);
}
return node;
}
function areElementsMergeable(el1, el2) {
return el1.tagName == el2.tagName && haveSameClasses(el1, el2) && elementsHaveSameNonClassAttributes(el1, el2);
}
function createAdjacentMergeableTextNodeGetter(forward) {
var propName = forward ? "nextSibling" : "previousSibling";
return function(textNode, checkParentElement) {
var el = textNode.parentNode;
var adjacentNode = textNode[propName];
if (adjacentNode) {
// Can merge if the node's previous/next sibling is a text node
if (adjacentNode && adjacentNode.nodeType == 3) {
return adjacentNode;
}
} else if (checkParentElement) {
// Compare text node parent element with its sibling
adjacentNode = el[propName];
if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
return adjacentNode[forward ? "firstChild" : "lastChild"];
}
}
return null;
}
}
var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
function Merge(firstNode) {
this.isElementMerge = (firstNode.nodeType == 1);
this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
this.textNodes = [this.firstTextNode];
}
Merge.prototype = {
doMerge: function() {
var textBits = [], textNode, parent, text;
for (var i = 0, len = this.textNodes.length; i < len; ++i) {
textNode = this.textNodes[i];
parent = textNode.parentNode;
textBits[i] = textNode.data;
if (i) {
parent.removeChild(textNode);
if (!parent.hasChildNodes()) {
parent.parentNode.removeChild(parent);
}
}
}
this.firstTextNode.data = text = textBits.join("");
return text;
},
getLength: function() {
var i = this.textNodes.length, len = 0;
while (i--) {
len += this.textNodes[i].length;
}
return len;
},
toString: function() {
var textBits = [];
for (var i = 0, len = this.textNodes.length; i < len; ++i) {
textBits[i] = "'" + this.textNodes[i].data + "'";
}
return "[Merge(" + textBits.join(",") + ")]";
}
};
var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly"];
// Allow "class" as a property name in object properties
var mappedPropertyNames = {"class" : "className"};
function CssClassApplier(cssClass, options, tagNames) {
this.cssClass = cssClass;
var normalize, i, len, propName;
var elementPropertiesFromOptions = null;
// Initialize from options object
if (typeof options == "object" && options !== null) {
tagNames = options.tagNames;
elementPropertiesFromOptions = options.elementProperties;
for (i = 0; propName = optionProperties[i++]; ) {
if (options.hasOwnProperty(propName)) {
this[propName] = options[propName];
}
}
normalize = options.normalize;
} else {
normalize = options;
}
// Backwards compatibility: the second parameter can also be a Boolean indicating whether normalization
this.normalize = (typeof normalize == "undefined") ? true : normalize;
// Initialize element properties and attribute exceptions
this.attrExceptions = [];
var el = document.createElement(this.elementTagName);
this.elementProperties = {};
for (var p in elementPropertiesFromOptions) {
if (elementPropertiesFromOptions.hasOwnProperty(p)) {
// Map "class" to "className"
if (mappedPropertyNames.hasOwnProperty(p)) {
p = mappedPropertyNames[p];
}
el[p] = elementPropertiesFromOptions[p];
// Copy the property back from the dummy element so that later comparisons to check whether elements
// may be removed are checking against the right value. For example, the href property of an element
// returns a fully qualified URL even if it was previously assigned a relative URL.
this.elementProperties[p] = el[p];
this.attrExceptions.push(p);
}
}
this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ?
sortClassName(this.elementProperties.className + " " + cssClass) : cssClass;
// Initialize tag names
this.applyToAnyTagName = false;
var type = typeof tagNames;
if (type == "string") {
if (tagNames == "*") {
this.applyToAnyTagName = true;
} else {
this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
}
} else if (type == "object" && typeof tagNames.length == "number") {
this.tagNames = [];
for (i = 0, len = tagNames.length; i < len; ++i) {
if (tagNames[i] == "*") {
this.applyToAnyTagName = true;
} else {
this.tagNames.push(tagNames[i].toLowerCase());
}
}
} else {
this.tagNames = [this.elementTagName];
}
}
CssClassApplier.prototype = {
elementTagName: defaultTagName,
elementProperties: {},
ignoreWhiteSpace: true,
applyToEditableOnly: false,
hasClass: function(node) {
return node.nodeType == 1 && dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && hasClass(node, this.cssClass);
},
getSelfOrAncestorWithClass: function(node) {
while (node) {
if (this.hasClass(node, this.cssClass)) {
return node;
}
node = node.parentNode;
}
return null;
},
isModifiable: function(node) {
return !this.applyToEditableOnly || isEditable(node);
},
// White space adjacent to an unwrappable node can be ignored for wrapping
isIgnorableWhiteSpaceNode: function(node) {
return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
},
// Normalizes nodes after applying a CSS class to a Range.
postApply: function(textNodes, range, isUndo) {
var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
var merges = [], currentMerge;
var rangeStartNode = firstNode, rangeEndNode = lastNode;
var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
var textNode, precedingTextNode;
for (var i = 0, len = textNodes.length; i < len; ++i) {
textNode = textNodes[i];
precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
if (precedingTextNode) {
if (!currentMerge) {
currentMerge = new Merge(precedingTextNode);
merges.push(currentMerge);
}
currentMerge.textNodes.push(textNode);
if (textNode === firstNode) {
rangeStartNode = currentMerge.firstTextNode;
rangeStartOffset = rangeStartNode.length;
}
if (textNode === lastNode) {
rangeEndNode = currentMerge.firstTextNode;
rangeEndOffset = currentMerge.getLength();
}
} else {
currentMerge = null;
}
}
// Test whether the first node after the range needs merging
var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
if (nextTextNode) {
if (!currentMerge) {
currentMerge = new Merge(lastNode);
merges.push(currentMerge);
}
currentMerge.textNodes.push(nextTextNode);
}
// Do the merges
if (merges.length) {
for (i = 0, len = merges.length; i < len; ++i) {
merges[i].doMerge();
}
// Set the range boundaries
range.setStart(rangeStartNode, rangeStartOffset);
range.setEnd(rangeEndNode, rangeEndOffset);
}
},
createContainer: function(doc) {
var el = doc.createElement(this.elementTagName);
api.util.extend(el, this.elementProperties);
addClass(el, this.cssClass);
return el;
},
applyToTextNode: function(textNode) {
var parent = textNode.parentNode;
if (parent.childNodes.length == 1 && dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
addClass(parent, this.cssClass);
} else {
var el = this.createContainer(dom.getDocument(textNode));
textNode.parentNode.insertBefore(el, textNode);
el.appendChild(textNode);
}
},
isRemovable: function(el) {
return el.tagName.toLowerCase() == this.elementTagName
&& getSortedClassName(el) == this.elementSortedClassName
&& elementHasProps(el, this.elementProperties)
&& !elementHasNonClassAttributes(el, this.attrExceptions)
&& this.isModifiable(el);
},
undoToTextNode: function(textNode, range, ancestorWithClass) {
if (!range.containsNode(ancestorWithClass)) {
// Split out the portion of the ancestor from which we can remove the CSS class
//var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
var ancestorRange = range.cloneRange();
ancestorRange.selectNode(ancestorWithClass);
if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)/* && isSplitPoint(range.endContainer, range.endOffset)*/) {
splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, [range]);
range.setEndAfter(ancestorWithClass);
}
if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)/* && isSplitPoint(range.startContainer, range.startOffset)*/) {
ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, [range]);
}
}
if (this.isRemovable(ancestorWithClass)) {
replaceWithOwnChildren(ancestorWithClass);
} else {
removeClass(ancestorWithClass, this.cssClass);
}
},
applyToRange: function(range) {
range.splitBoundaries();
var textNodes = getEffectiveTextNodes(range);
if (textNodes.length) {
var textNode;
for (var i = 0, len = textNodes.length; i < len; ++i) {
textNode = textNodes[i];
if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
&& this.isModifiable(textNode)) {
this.applyToTextNode(textNode);
}
}
range.setStart(textNodes[0], 0);
textNode = textNodes[textNodes.length - 1];
range.setEnd(textNode, textNode.length);
if (this.normalize) {
this.postApply(textNodes, range, false);
}
}
},
applyToSelection: function(win) {
win = win || window;
var sel = api.getSelection(win);
var range, ranges = sel.getAllRanges();
sel.removeAllRanges();
var i = ranges.length;
while (i--) {
range = ranges[i];
this.applyToRange(range);
sel.addRange(range);
}
},
undoToRange: function(range) {
range.splitBoundaries();
var textNodes = getEffectiveTextNodes(range);
var textNode, ancestorWithClass;
var lastTextNode = textNodes[textNodes.length - 1];
if (textNodes.length) {
for (var i = 0, len = textNodes.length; i < len; ++i) {
textNode = textNodes[i];
ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
if (ancestorWithClass && this.isModifiable(textNode)) {
this.undoToTextNode(textNode, range, ancestorWithClass);
}
// Ensure the range is still valid
range.setStart(textNodes[0], 0);
range.setEnd(lastTextNode, lastTextNode.length);
}
if (this.normalize) {
this.postApply(textNodes, range, true);
}
}
},
undoToSelection: function(win) {
win = win || window;
var sel = api.getSelection(win);
var ranges = sel.getAllRanges(), range;
sel.removeAllRanges();
for (var i = 0, len = ranges.length; i < len; ++i) {
range = ranges[i];
this.undoToRange(range);
sel.addRange(range);
}
},
getTextSelectedByRange: function(textNode, range) {
var textRange = range.cloneRange();
textRange.selectNodeContents(textNode);
var intersectionRange = textRange.intersection(range);
var text = intersectionRange ? intersectionRange.toString() : "";
textRange.detach();
return text;
},
isAppliedToRange: function(range) {
if (range.collapsed) {
return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
} else {
var textNodes = range.getNodes( [3] );
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
&& this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
return false;
}
}
return true;
}
},
isAppliedToSelection: function(win) {
win = win || window;
var sel = api.getSelection(win);
var ranges = sel.getAllRanges();
var i = ranges.length;
while (i--) {
if (!this.isAppliedToRange(ranges[i])) {
return false;
}
}
return true;
},
toggleRange: function(range) {
if (this.isAppliedToRange(range)) {
this.undoToRange(range);
} else {
this.applyToRange(range);
}
},
toggleSelection: function(win) {
if (this.isAppliedToSelection(win)) {
this.undoToSelection(win);
} else {
this.applyToSelection(win);
}
},
detach: function() {}
};
function createCssClassApplier(cssClass, options, tagNames) {
return new CssClassApplier(cssClass, options, tagNames);
}
CssClassApplier.util = {
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
hasSameClasses: haveSameClasses,
replaceWithOwnChildren: replaceWithOwnChildren,
elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
elementHasNonClassAttributes: elementHasNonClassAttributes,
splitNodeAt: splitNodeAt,
isEditableElement: isEditableElement,
isEditingHost: isEditingHost,
isEditable: isEditable
};
api.CssClassApplier = CssClassApplier;
api.createCssClassApplier = createCssClassApplier;
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,601 @@
DataItem = function(pos, h, col)
{
this.value = h
this.position = pos
this.color = col
}
DataItem.prototype.clone=function()
{
var newitem = new DataItem(this.position,this.value,this.color) //make a copy
return newitem
}
DataItem.prototype.getValue=function()
{
return this.value
}
DataItem.prototype.getColor=function()
{
return this.color
}
DataItem.prototype.getPosition=function()
{
return this.position
}
DataItem.prototype.setValue=function(newh)
{
this.value = newh
}
DataItem.prototype.setPosition=function(newp)
{
this.position = newp
}
DataItem.prototype.setColor=function(newc)
{
this.color = newc
}
BubbleSortModel = function() //construct the model
{
}
BubbleSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var passnum=this.valuelist.length-1; passnum>0; passnum = passnum-1)
{
for (var i=0; i<passnum; i=i+1)
{
this.valuelist[i].setColor("red")
this.valuelist[i+1].setColor("red")
this.script.push(this.makescene())
if (this.valuelist[i].getValue() > this.valuelist[i+1].getValue())
{
var temp = this.valuelist[i]
this.valuelist[i] = this.valuelist[i+1]
this.valuelist[i+1] = temp
this.script.push(this.makescene())
}
this.valuelist[i].setColor("black")
this.valuelist[i+1].setColor("black")
this.script.push(this.makescene())
}
}
return this.script
}
BubbleSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
InsertionSortModel = function()
{
}
InsertionSortModel.prototype.init=function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var index=1; index < this.valuelist.length; index = index+1)
{
this.valuelist[index].setColor("blue")
this.script.push(this.makescene())
this.valuelist[index].setColor("black")
this.script.push(this.makescene())
var currentvalue = this.valuelist[index].clone()
var position = index
while (position>0 && (this.valuelist[position-1].getValue() > currentvalue.getValue()))
{
this.valuelist[position-1].setColor("red")
this.script.push(this.makescene())
this.valuelist[position-1].setColor("black")
this.valuelist[position] = this.valuelist[position-1].clone()
//this.barlist.bars[position-1] = currentvalue
this.valuelist[position-1].setValue(0)
this.script.push(this.makescene())
position = position-1
}
this.valuelist[position] = currentvalue
this.valuelist[position].setColor("blue")
this.script.push(this.makescene())
this.valuelist[position].setColor("black")
}
this.script.push(this.makescene())
return this.script
}
InsertionSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
SelectionSortModel = function() //construct the model
{
}
SelectionSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var fillslot=this.valuelist.length-1; fillslot>0; fillslot = fillslot-1)
{ var positionOfMax=0
this.valuelist[positionOfMax].setColor("yellow")
this.valuelist[fillslot].setColor("blue")
this.script.push(this.makescene())
for (var i=1; i<fillslot+1; i=i+1)
{
this.valuelist[i].setColor("red")
this.script.push(this.makescene())
if (this.valuelist[i].getValue() > this.valuelist[positionOfMax].getValue())
{
this.valuelist[positionOfMax].setColor("black")
positionOfMax = i
this.valuelist[i].setColor("yellow")
this.script.push(this.makescene())
}
else
{
this.valuelist[i].setColor("black")
this.script.push(this.makescene())
}
}
var temp = this.valuelist[fillslot]
this.valuelist[fillslot] = this.valuelist[positionOfMax]
this.valuelist[positionOfMax] = temp
this.script.push(this.makescene())
this.valuelist[fillslot].setColor("black")
this.script.push(this.makescene())
}
return this.script
}
SelectionSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
ShellSortModel = function() //construct the model
{
}
ShellSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
var sublistcount = Math.floor(this.valuelist.length/2)
while (sublistcount > 0)
{
for (var startposition = 0; startposition < sublistcount;
startposition = startposition+1)
{
var gap = sublistcount
var start = startposition
this.valuelist[start].setColor("red")
for (var i=start+gap; i<this.valuelist.length; i = i + gap)
{
currentvalue = this.valuelist[i].clone()
currentvalue.setColor("red")
this.script.push(this.makescene())
position = i
while (position>=gap && this.valuelist[position-gap].getValue()>currentvalue.getValue())
{
this.valuelist[position] = this.valuelist[position-gap].clone()
this.valuelist[position-gap].setValue(0)
position = position-gap
this.script.push(this.makescene())
}
this.valuelist[position]=currentvalue
this.script.push(this.makescene())
}
for (var clearidx=0; clearidx<this.valuelist.length; clearidx++)
this.valuelist[clearidx].setColor("black")
this.script.push(this.makescene())
}
this.script.push(this.makescene())
sublistcount = Math.floor(sublistcount/2)
}
this.script.push(this.makescene())
return this.script
}
ShellSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
MergeSortModel = function() //construct the model
{
}
MergeSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene(this.valuelist))
this.domergesort(0,this.valuelist.length-1)
this.script.push(this.makescene(this.valuelist))
return this.script
}
MergeSortModel.prototype.chunkcolor=function(start,end,c)
{
for (var clearidx=start; clearidx<=end; clearidx++)
this.valuelist[clearidx].setColor(c)
}
MergeSortModel.prototype.domergesort = function(start,end)
{ len = end-start + 1
if (len>1)
{
var mid = start + Math.floor(len/2)
this.chunkcolor(start,mid-1,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(start,mid-1,"black")
this.domergesort(start,mid-1)
this.chunkcolor(mid,end,"blue")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(mid,end,"black")
this.domergesort(mid,end)
var i=start
var j=mid
var newlist = Array()
while (i<mid && j<=end)
{
if (this.valuelist[i].getValue()<this.valuelist[j].getValue())
{
newlist.push(this.valuelist[i])
i=i+1
}
else
{
newlist.push(this.valuelist[j])
j=j+1
}
}
while (i<mid)
{
newlist.push(this.valuelist[i])
i=i+1
}
while (j<=end)
{
newlist.push(this.valuelist[j])
j=j+1
}
this.copyback(newlist,start)
this.chunkcolor(start,end,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(start,end,"black")
}
}
MergeSortModel.prototype.copyback = function(original,i,j) //make copy from i to j excl
{
var newcopy = new Array()
for (var idx=0; idx<original.length; idx++)
{
var item = original[idx].clone() //make a copy
this.valuelist[i] = item
i=i+1
}
}
MergeSortModel.prototype.makecopy = function(original,i) //make copy to i
{
for (var idx=0; idx<original.length; idx++)
{
var item = original[idx].clone() //make a copy
this.valuelist[i] = item
i++
}
return newcopy
}
MergeSortModel.prototype.makescene = function(somearray)
{
var newscene = new Array()
for (var idx=0; idx<somearray.length; idx++)
{
var item = somearray[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
QuickSortModel = function() //construct the model
{
}
QuickSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene(this.valuelist))
this.quickSort(this.valuelist)
this.script.push(this.makescene(this.valuelist))
return this.script
}
QuickSortModel.prototype.quickSort=function(alist)
{
this.quickSortHelper(alist,0,alist.length-1)
}
QuickSortModel.prototype.quickSortHelper=function(alist,first,last)
{
if (first<last)
{
var splitpoint = this.partition(alist,first,last)
this.chunkcolor(first,splitpoint-1,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(first,splitpoint-1,"black")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(splitpoint+1,last,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(splitpoint+1,last,"black")
this.script.push(this.makescene(this.valuelist))
this.quickSortHelper(alist,first,splitpoint-1)
this.quickSortHelper(alist,splitpoint+1,last)
}
}
QuickSortModel.prototype.partition = function(alist,first,last)
{
var pivotvalue = alist[first].getValue()
alist[first].setColor("red")
this.script.push(this.makescene(this.valuelist))
var leftmark = first+1
var rightmark = last
alist[leftmark].setColor("blue")
alist[rightmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))
var done = false
while (! done)
{
while (leftmark <= rightmark && alist[leftmark].getValue() <= pivotvalue)
{
alist[leftmark].setColor("black")
leftmark = leftmark + 1
if (leftmark <= rightmark)
{
alist[leftmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))}
}
while (alist[rightmark].getValue() >= pivotvalue && rightmark >= leftmark)
{
alist[rightmark].setColor("black")
rightmark = rightmark - 1
if (rightmark >= leftmark)
{
alist[rightmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))}
}
if (rightmark < leftmark)
done = true
else
{
temp = alist[leftmark]
alist[leftmark] = alist[rightmark]
alist[rightmark] = temp
this.script.push(this.makescene(this.valuelist))
alist[leftmark].setColor("black")
alist[rightmark].setColor("black")
}
}
var temp = alist[first]
alist[first] = alist[rightmark]
alist[rightmark] = temp
alist[first].setColor("black")
alist[rightmark].setColor("red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(0,this.valuelist.length-1,"black")
this.script.push(this.makescene(this.valuelist))
return rightmark
}
QuickSortModel.prototype.chunkcolor=function(start,end,c)
{
for (var clearidx=start; clearidx<=end; clearidx++)
this.valuelist[clearidx].setColor(c)
}
QuickSortModel.prototype.makescene = function(somearray)
{
var newscene = new Array()
for (var idx=0; idx<somearray.length; idx++)
{
var item = somearray[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}

View file

@ -0,0 +1,61 @@
BarViewer = function() //construct the view
{
}
BarViewer.prototype.init = function(c)
{
this.ctx = c
}
BarViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillRect(p*7 + 2,
this.ctx.canvas.height-ascene[p].getValue(),
3,
ascene[p].getValue())
}
}
ScatterViewer = function() //contruct a list of numbers view
{
}
ScatterViewer.prototype.init = function(c)
{
this.ctx = c
}
ScatterViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillText(ascene[p].getValue(), p*7 + 2,
this.ctx.canvas.height-ascene[p].getValue())
}
}
BoxViewer = function() //contruct an array of boxes view
{
}
BoxViewer.prototype.init = function(c)
{
this.ctx = c
}
BoxViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillText(ascene[p].getValue(), p*25 + 3, 200)
this.ctx.strokeStyle = ascene[p].color
this.ctx.strokeRect(p*25+2,185,25,25)
}
}

View file

@ -0,0 +1,364 @@
/*global variable declarations*/
var myHighlightApplier;
var cssClassApplierModule = rangy.modules.CssClassApplier;
var range, sel, highlightAction = "save";
var highlightResponseText;
var extendHighlightClass;
var urlList;
var extendType;
function enableUserHighlights(){
//check if it's not the contents or index page.
if ((window.location.href).toLowerCase().indexOf("index.html") == -1){
//checksum generator for each div.section and paragraph. Add that checksum as a class _[checksumValue]
$('body p, body .section').each(function(index) {
var s = $(this).text();
var i;
var chk = 0;
for (i = 0; i < s.length; i++) {
chk += (s.charCodeAt(i)*i);
}
$(this).addClass("_"+chk);
});
var lastPositionVal = $.getUrlVar('lastPosition');
if ( typeof lastPositionVal !== "undefined"){
$("body").append('<img src="../_static/last-point.png" style="position:absolute; padding-top:40px; left: 10px; top: '+parseInt(lastPositionVal)+'px;"/>');
$("html, body").animate({scrollTop: parseInt(lastPositionVal)}, 1000);
}
//Add the highlights on the page
restoreSelection();
//Add a container for highlights in the sidebar and populate
$(".sphinxsidebarwrapper").append('<div id="highlightbox"><h3>My Highlights</h3><ul></ul></div>');
updateHighlightBox();
var sec = $("body .section");
sec.splice(0,1);
sec.each(function(index) {
$(this).waypoint(function(direction) {
processPageState($(this));
});
});
$("body").append('<ul class="dropdown-menu" id="highlight-option-box" style="display:none;"><li><a href="javascript:void(0);" id="option-highlight-text" style="display:block;">Highlight</a></li></ul>');
$('body .section').on("mouseup", function(evt) {
sel = rangy.getSelection();
if (typeof sel !== "undefined" && sel.anchorNode != null && sel.focusNode != null){
var currAnchorNode = sel.anchorNode.parentElement;
var currFocusNode = sel.focusNode.parentElement;
}
if (typeof sel === "undefined" || (sel.anchorOffset == sel.focusOffset) && sel.anchorNode == sel.focusNode) {
$("#highlight-option-box").hide();
}
else if($(currAnchorNode).hasClass("my-highlighted-text") && $(currFocusNode).hasClass("my-highlighted-text")){
sel.expand("word"); //expands selection to closest word only if user selects atleast one character
highlightAction = "delete";
toggleHighlightOptionBox(evt,"Delete Highlight");
}
else if($(sel.getRangeAt(0).getNodes([1])).hasClass("my-highlighted-text")){
sel.expand("word");
toggleHighlightOptionBox(evt,"Extend Highlight");
if($(sel.getRangeAt(0).startContainer.parentElement).hasClass("my-highlighted-text")){ //extendEnd
var classList = $(sel.getRangeAt(0).startContainer.parentElement).attr('class').split(/\s+/); //get all classes applied to anchor element
extendType = "extendEnd";
}
else if($(sel.getRangeAt(0).endContainer.parentElement).hasClass("my-highlighted-text")){ //extendBeginning
var classList = $(sel.getRangeAt(0).endContainer.parentElement).attr('class').split(/\s+/); //get all classes applied to focus element
extendType = "extendBeginning";
}
else{ //extendBoth
var classList = "";
$(sel.getRangeAt(0).getNodes([1])).each(function(index, value) {
if($(value).hasClass("my-highlighted-text")){
classList = $(value).attr('class').split(/\s+/);
}
});
extendType = "extendBoth";
}
extendHighlightClass = findHighlightClass(classList);
highlightAction = "extend";
}
else if(!($(currAnchorNode).hasClass("my-highlighted-text") || $(currFocusNode).hasClass("my-highlighted-text"))){
sel.expand("word");
toggleHighlightOptionBox(evt,"Highlight");
highlightAction = "save";
}
});
$("#option-highlight-text").on('click', function(){
$("#highlight-option-box").hide();
switch(highlightAction){
case "save":
{
var uniqueId = "hl"+saveSelection(sel);
myHighlightApplier = rangy.createCssClassApplier("my-highlighted-text "+uniqueId, {normalize: true});
myHighlightApplier.applyToSelection();
$("."+uniqueId).first().attr("id",uniqueId);
window.getSelection().removeAllRanges();
updateHighlightBox();
break;
}
case "delete":
{
var classList =$($(sel.anchorNode)[0].parentElement).attr('class').split(/\s+/); //get all classes applied to element
var toDeleteHighlightClass = findHighlightClass(classList);
range = rangy.createRange();
myHighlightApplier = rangy.createCssClassApplier("my-highlighted-text", {normalize: true});
$(".hl"+toDeleteHighlightClass).each(function() { //loop over all nodes with the given class and remove the my-highlighted-text class
range.selectNodeContents(this);
myHighlightApplier.undoToRange(range);
});
window.getSelection().removeAllRanges();
toDelete = false;
deleteHighlight(toDeleteHighlightClass);
$(".hl"+toDeleteHighlightClass).attr("id","");
$(".hl"+toDeleteHighlightClass).removeClass("hl"+toDeleteHighlightClass);
updateHighlightBox();
break;
}
case "extend":
{
var existingHighlight = $(".hl"+extendHighlightClass);
var range = sel.getRangeAt(0);
//expand the selection to include the original highlight based on if it is extendEnd or extendBeginning
if (extendType == "extendEnd")
range.setStartBefore(existingHighlight[0]);
else if (extendType == "extendBeginning")
range.setEndAfter(existingHighlight[existingHighlight.length -1]);
sel.removeAllRanges(); //remove any existing ranges in selection
sel.addRange(range); //add the new expanded range to selection
//delete old highlight and save the expanded range as a new highlight
$(existingHighlight).removeClass("my-highlighted-text");
$(".hl"+extendHighlightClass).attr("id","");
$(".hl"+extendHighlightClass).removeClass("hl"+extendHighlightClass);
deleteHighlight(extendHighlightClass);
var newExtendHighlightClass = saveSelection(sel);
myHighlightApplier = rangy.createCssClassApplier("my-highlighted-text "+"hl"+newExtendHighlightClass, {normalize: true});
myHighlightApplier.applyToSelection();
$(".hl"+newExtendHighlightClass).first().attr("id","hl"+newExtendHighlightClass);
window.getSelection().removeAllRanges();
updateHighlightBox();
break;
}
}
});
}
else if ((window.location.href).toLowerCase().indexOf("/index.html") != -1){
var data = {course:eBookConfig.course};
jQuery.get(eBookConfig.ajaxURL+'getlastpage', data, function(data) {
if (data !="None"){
lastPageData = $.parseJSON(data);
if (lastPageData[0].lastPageChapter != null){
$("body .section .section:first").before('<div id="jump-to-chapter" class="alert" ><strong>You were Last Reading:</strong> '+lastPageData[0].lastPageChapter+ ((lastPageData[0].lastPageSubchapter) ? ' &gt; '+lastPageData[0].lastPageSubchapter : "")+' <a href="'+lastPageData[0].lastPageUrl+'?lastPosition='+lastPageData[0].lastPageScrollLocation+lastPageData[0].lastPageHash+'" style="float:right; margin-right:20px;">Continue Reading</a></div>');
}
}
});
}
};
function findHighlightClass(classList){
var className;
$.each( classList, function(index, item){
if (item.indexOf("hl") !== -1) { //locate class with hl
className = item;
}
});
return className.replace("hl","");
}
function toggleHighlightOptionBox(event, highlightOptionName){
$("#option-highlight-text").text(highlightOptionName);
$("#highlight-option-box").show().offset({
top: event.pageY + 5,
left: event.pageX + 5
});
}
//function to process the selection made by the user to identify the range and the parent selector. Calls function saveHighlight
function saveSelection(sel) {
var parentNode = sel._ranges[0].commonAncestorContainer;
var parentSelectorClass;
while(!(($(parentNode).is("p") || $(parentNode).is("div")) && $(parentNode).attr('class'))){
parentNode = $(parentNode)[0].parentElement;
}
$.each($(parentNode).attr('class').split(' '), function(index, value) {
if (value.indexOf("_") == 0){
parentSelectorClass = value;
}
});
var currentRange = sel.saveCharacterRanges(parentNode);
if(currentRange.length > 1){
currentRange[0].range.end = currentRange[currentRange.length -1].range.end;
var tempRange = currentRange.slice(0,1);
currentRange = tempRange;
}
var serializedRange = JSON.stringify(currentRange);
return saveHighlight(parentSelectorClass,serializedRange,"self");
}
//function called to save a new highlight
function saveHighlight(parentSelectorClass,serializedRange,saveMethod) {
var currPage = window.location.pathname;
var newId;
var currSection = window.location.pathname+window.location.hash;
var data = {parentClass:parentSelectorClass, range:serializedRange, method:saveMethod, page:currPage, pageSection: currSection, course:eBookConfig.course};
$(document).ajaxError(function(e,jqhxr,settings,exception){
console.log("Request Failed for "+settings.url);
});
jQuery.ajax({url: eBookConfig.ajaxURL+'savehighlight',data: data, async: false}).done(function(returndata) {
newId = returndata;
});
if (eBookConfig.logLevel > 0){
logBookEvent({'event':'highlight','act': 'save', 'div_id':currPage}); // Log the run event
}
return newId;
}
//function called to delete an existing highlight
function deleteHighlight(uniqueId) {
var currPage = window.location.pathname;
var data = {uniqueId: uniqueId};
$(document).ajaxError(function(e,jqhxr,settings,exception){
console.log("Request Failed for"+settings.url)
});
jQuery.post(eBookConfig.ajaxURL+'deletehighlight',data);
if (eBookConfig.logLevel > 0){
logBookEvent({'event':'highlight','act': 'delete', 'div_id':currPage}); // Log the run event
}
}
//add links to the highlights in sidebar on the right. Function called on page load and every edit of highlight
function updateHighlightBox() {
$("#highlightbox ul").html("");
var highlightJumpText = "";
var highlightLink;
var processingHighlight = false;
$("body .my-highlighted-text").each(function(index,value){
if($(value).attr("id")){
if (processingHighlight){
highlightJumpText = highlightJumpText.split(/\s+/, 12).join(" ")+"...";
$("#highlightbox ul").append("<li><a class='sidebar-highlights' href='"+window.location.pathname+"#"+highlightLink+"'>"+highlightJumpText+"</a></li><br/>");
}
highlightJumpText = "";
highlightLink = $(value).attr("id");
if ($(value)[0].firstChild)
highlightJumpText += $(value)[0].firstChild.textContent;
else
highlightJumpText +=$(value)[0].textContent;
processingHighlight = true;
}
else{
if ($(value)[0].firstChild)
highlightJumpText += $(value)[0].firstChild.textContent;
else
highlightJumpText +=$(value)[0].textContent;
}
});
if (processingHighlight){
highlightJumpText = highlightJumpText.split(/\s+/, 12).join(" ")+"...";
$("#highlightbox ul").append("<li><a class='sidebar-highlights' href='"+window.location.pathname+"#"+highlightLink+"'>"+highlightJumpText+"</a></li><br/>");
}
}
//function called at load time to fetch all highlights from database for the user and rendered on the page
function restoreSelection() {
rangy.init();
var currPage = window.location.pathname;
var data = {page: currPage ,course:eBookConfig.course};
jQuery.ajax({url: eBookConfig.ajaxURL+'gethighlights',data: data, async: false}).done(function(data) {
highlightResponseText = $.parseJSON(data);
var parentClassName, uniqueId;
$.each(highlightResponseText, function(index, value) {
parentClassName = "."+value.parentClass;
highlightClassName = "hl"+value.uniqueId;
rangy.getSelection().restoreCharacterRanges($(parentClassName)[0], $.parseJSON(value.range));
myHighlightApplier = rangy.createCssClassApplier("my-highlighted-text "+highlightClassName, {normalize: true});
myHighlightApplier.toggleSelection();
$("."+highlightClassName).first().attr("id",highlightClassName);
window.getSelection().removeAllRanges();
});
});
}
function processPageState(subChapterSectionElement){
/*Get the chapter name and subchaptername from the Waypoints jQuery plugin. Store in the database for last visited page*/
var chapterName, subChapterName;
chapterName = $("h1:first").text();
chapterName = chapterName.substring(0,chapterName.length -1); // strip out permalink character
subChapterName = subChapterSectionElement.find("h2").text();
subChapterName = subChapterName.substring(0, subChapterName.length -1); // strip out permalink character
subChapterId = subChapterSectionElement.attr("id");
var currentLink = subChapterId
/*Log last page visited*/
var currentPathname = window.location.pathname;
if (currentPathname.indexOf("?") !== -1)
currentPathname = currentPathname.substring(0, currentPathname.lastIndexOf("?"));
var data = {lastPageUrl:currentPathname, lastPageHash: currentLink, lastPageChapter:chapterName, lastPageSubchapter:subChapterName, lastPageScrollLocation: $(window).scrollTop(), course:eBookConfig.course};
$(document).ajaxError( function(e,jqhxr,settings,exception) {
console.log("Request Failed for "+settings.url)
} );
jQuery.ajax({url: eBookConfig.ajaxURL+'updatelastpage',data: data});
}
/*function processPageUnloadState(){
var chapterName, subChapterName;
var currentLink = "";
chapterName = $("h1:first").text();
chapterName = chapterName.substring(0,chapterName.length -1);
console.log("The current scrolled position is "+$(window).scrollTop());
$(urlList).each(function(index){
var currentID = $(urlList[index]).attr("href");
if ($(currentID).position()){
if ($(window).scrollTop() >= $(currentID).position().top && (index == (urlList.length - 1) || $(window).scrollTop() < $($(urlList[index+1]).attr("href")).position().top) ){
currentLink = $(this).attr("href");
}
}
if(currentID == currentLink){ //matches current opened subchapter
subChapterName = $(this).html();
}
});
/*Log last page visited*/
/* var currentPathname = window.location.pathname;
if (currentPathname.indexOf("?") !== -1)
currentPathname = currentPathname.substring(0, currentPathname.lastIndexOf("?"));
var data = {lastPageUrl:currentPathname, lastPageHash: currentLink, lastPageChapter:chapterName, lastPageSubchapter:subChapterName, lastPageScrollLocation: $(window).scrollTop(), course:eBookConfig.course};
$(document).ajaxError( function(e,jqhxr,settings,exception) {
alert("Request Failed for "+settings.url)
} );
jQuery.ajax({url: eBookConfig.ajaxURL+'updatelastpage',data: data, async: false});
}*/
$.extend({
getUrlVars: function(){
var vars = [], hash;
var hashes = window.location.search.slice(window.location.search.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
},
getUrlVar: function(name){
return $.getUrlVars()[name];
}
});

8
book/common/js/waypoints.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,808 @@
/*
* websupport.js
* ~~~~~~~~~~~~~
*
* sphinx.websupport utilties for all documentation.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
(function($) {
$.fn.autogrow = function() {
return this.each(function() {
var textarea = this;
$.fn.autogrow.resize(textarea);
$(textarea)
.focus(function() {
textarea.interval = setInterval(function() {
$.fn.autogrow.resize(textarea);
}, 500);
})
.blur(function() {
clearInterval(textarea.interval);
});
});
};
$.fn.autogrow.resize = function(textarea) {
var lineHeight = parseInt($(textarea).css('line-height'), 10);
var lines = textarea.value.split('\n');
var columns = textarea.cols;
var lineCount = 0;
$.each(lines, function() {
lineCount += Math.ceil(this.length / columns) || 1;
});
var height = lineHeight * (lineCount + 1);
$(textarea).css('height', height);
};
})(jQuery);
(function($) {
var comp, by;
function init() {
initEvents();
initComparator();
}
function initEvents() {
$('a.comment-close').live("click", function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
});
$('a.vote').live("click", function(event) {
event.preventDefault();
handleVote($(this));
});
$('a.reply').live("click", function(event) {
event.preventDefault();
openReply($(this).attr('id').substring(2));
});
$('a.close-reply').live("click", function(event) {
event.preventDefault();
closeReply($(this).attr('id').substring(2));
});
$('a.sort-option').live("click", function(event) {
event.preventDefault();
handleReSort($(this));
});
$('a.show-proposal').live("click", function(event) {
event.preventDefault();
showProposal($(this).attr('id').substring(2));
});
$('a.hide-proposal').live("click", function(event) {
event.preventDefault();
hideProposal($(this).attr('id').substring(2));
});
$('a.show-propose-change').live("click", function(event) {
event.preventDefault();
showProposeChange($(this).attr('id').substring(2));
});
$('a.hide-propose-change').live("click", function(event) {
event.preventDefault();
hideProposeChange($(this).attr('id').substring(2));
});
$('a.accept-comment').live("click", function(event) {
event.preventDefault();
acceptComment($(this).attr('id').substring(2));
});
$('a.delete-comment').live("click", function(event) {
event.preventDefault();
deleteComment($(this).attr('id').substring(2));
});
$('a.comment-markup').live("click", function(event) {
event.preventDefault();
toggleCommentMarkupBox($(this).attr('id').substring(2));
});
}
/**
* Set comp, which is a comparator function used for sorting and
* inserting comments into the list.
*/
function setComparator() {
// If the first three letters are "asc", sort in ascending order
// and remove the prefix.
if (by.substring(0,3) == 'asc') {
var i = by.substring(3);
comp = function(a, b) { return a[i] - b[i]; };
} else {
// Otherwise sort in descending order.
comp = function(a, b) { return b[by] - a[by]; };
}
// Reset link styles and format the selected sort option.
$('a.sel').attr('href', '#').removeClass('sel');
$('a.by' + by).removeAttr('href').addClass('sel');
}
/**
* Create a comp function. If the user has preferences stored in
* the sortBy cookie, use those, otherwise use the default.
*/
function initComparator() {
by = 'rating'; // Default to sort by rating.
// If the sortBy cookie is set, use that instead.
if (document.cookie.length > 0) {
var start = document.cookie.indexOf('sortBy=');
if (start != -1) {
start = start + 7;
var end = document.cookie.indexOf(";", start);
if (end == -1) {
end = document.cookie.length;
by = unescape(document.cookie.substring(start, end));
}
}
}
setComparator();
}
/**
* Show a comment div.
*/
function show(id) {
$('#ao' + id).hide();
$('#ah' + id).show();
var context = $.extend({id: id}, opts);
var popup = $(renderTemplate(popupTemplate, context)).hide();
popup.find('textarea[name="proposal"]').hide();
popup.find('a.by' + by).addClass('sel');
var form = popup.find('#cf' + id);
form.submit(function(event) {
event.preventDefault();
addComment(form);
});
$('#s' + id).after(popup);
popup.slideDown('fast', function() {
getComments(id);
});
}
/**
* Hide a comment div.
*/
function hide(id) {
$('#ah' + id).hide();
$('#ao' + id).show();
var div = $('#sc' + id);
div.slideUp('fast', function() {
div.remove();
});
}
/**
* Perform an ajax request to get comments for a node
* and insert the comments into the comments tree.
*/
function getComments(id) {
$.ajax({
type: 'GET',
url: opts.getCommentsURL,
data: {node: id},
success: function(data, textStatus, request) {
var ul = $('#cl' + id);
var speed = 100;
$('#cf' + id)
.find('textarea[name="proposal"]')
.data('source', data.source);
if (data.comments.length === 0) {
ul.html('<li>No comments yet.</li>');
ul.data('empty', true);
} else {
// If there are comments, sort them and put them in the list.
var comments = sortComments(data.comments);
speed = data.comments.length * 100;
appendComments(comments, ul);
ul.data('empty', false);
}
$('#cn' + id).slideUp(speed + 200);
ul.slideDown(speed);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem retrieving the comments.');
},
dataType: 'json'
});
}
/**
* Add a comment via ajax and insert the comment into the comment tree.
*/
function addComment(form) {
var node_id = form.find('input[name="node"]').val();
var parent_id = form.find('input[name="parent"]').val();
var text = form.find('textarea[name="comment"]').val();
var proposal = form.find('textarea[name="proposal"]').val();
if (text == '') {
showError('Please enter a comment.');
return;
}
// Disable the form that is being submitted.
form.find('textarea,input').attr('disabled', 'disabled');
// Send the comment to the server.
$.ajax({
type: "POST",
url: opts.addCommentURL,
dataType: 'json',
data: {
node: node_id,
parent: parent_id,
text: text,
proposal: proposal
},
success: function(data, textStatus, error) {
// Reset the form.
if (node_id) {
hideProposeChange(node_id);
}
form.find('textarea')
.val('')
.add(form.find('input'))
.removeAttr('disabled');
var ul = $('#cl' + (node_id || parent_id));
if (ul.data('empty')) {
$(ul).empty();
ul.data('empty', false);
}
insertComment(data.comment);
var ao = $('#ao' + node_id);
ao.find('img').attr({'src': opts.commentBrightImage});
if (node_id) {
// if this was a "root" comment, remove the commenting box
// (the user can get it back by reopening the comment popup)
$('#ca' + node_id).slideUp();
}
},
error: function(request, textStatus, error) {
form.find('textarea,input').removeAttr('disabled');
showError('Oops, there was a problem adding the comment.');
}
});
}
/**
* Recursively append comments to the main comment list and children
* lists, creating the comment tree.
*/
function appendComments(comments, ul) {
$.each(comments, function() {
var div = createCommentDiv(this);
ul.append($(document.createElement('li')).html(div));
appendComments(this.children, div.find('ul.comment-children'));
// To avoid stagnating data, don't store the comments children in data.
this.children = null;
div.data('comment', this);
});
}
/**
* After adding a new comment, it must be inserted in the correct
* location in the comment tree.
*/
function insertComment(comment) {
var div = createCommentDiv(comment);
// To avoid stagnating data, don't store the comments children in data.
comment.children = null;
div.data('comment', comment);
var ul = $('#cl' + (comment.node || comment.parent));
var siblings = getChildren(ul);
var li = $(document.createElement('li'));
li.hide();
// Determine where in the parents children list to insert this comment.
for(i=0; i < siblings.length; i++) {
if (comp(comment, siblings[i]) <= 0) {
$('#cd' + siblings[i].id)
.parent()
.before(li.html(div));
li.slideDown('fast');
return;
}
}
// If we get here, this comment rates lower than all the others,
// or it is the only comment in the list.
ul.append(li.html(div));
li.slideDown('fast');
}
function acceptComment(id) {
$.ajax({
type: 'POST',
url: opts.acceptCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
$('#cm' + id).fadeOut('fast');
$('#cd' + id).removeClass('moderate');
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem accepting the comment.');
}
});
}
function deleteComment(id) {
$.ajax({
type: 'POST',
url: opts.deleteCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
var div = $('#cd' + id);
if (data == 'delete') {
// Moderator mode: remove the comment and all children immediately
div.slideUp('fast', function() {
div.remove();
});
return;
}
// User mode: only mark the comment as deleted
div
.find('span.user-id:first')
.text('[deleted]').end()
.find('div.comment-text:first')
.text('[deleted]').end()
.find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
.remove();
var comment = div.data('comment');
comment.username = '[deleted]';
comment.text = '[deleted]';
div.data('comment', comment);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem deleting the comment.');
}
});
}
function showProposal(id) {
$('#sp' + id).hide();
$('#hp' + id).show();
$('#pr' + id).slideDown('fast');
}
function hideProposal(id) {
$('#hp' + id).hide();
$('#sp' + id).show();
$('#pr' + id).slideUp('fast');
}
function showProposeChange(id) {
$('#pc' + id).hide();
$('#hc' + id).show();
var textarea = $('#pt' + id);
textarea.val(textarea.data('source'));
$.fn.autogrow.resize(textarea[0]);
textarea.slideDown('fast');
}
function hideProposeChange(id) {
$('#hc' + id).hide();
$('#pc' + id).show();
var textarea = $('#pt' + id);
textarea.val('').removeAttr('disabled');
textarea.slideUp('fast');
}
function toggleCommentMarkupBox(id) {
$('#mb' + id).toggle();
}
/** Handle when the user clicks on a sort by link. */
function handleReSort(link) {
var classes = link.attr('class').split(/\s+/);
for (var i=0; i<classes.length; i++) {
if (classes[i] != 'sort-option') {
by = classes[i].substring(2);
}
}
setComparator();
// Save/update the sortBy cookie.
var expiration = new Date();
expiration.setDate(expiration.getDate() + 365);
document.cookie= 'sortBy=' + escape(by) +
';expires=' + expiration.toUTCString();
$('ul.comment-ul').each(function(index, ul) {
var comments = getChildren($(ul), true);
comments = sortComments(comments);
appendComments(comments, $(ul).empty());
});
}
/**
* Function to process a vote when a user clicks an arrow.
*/
function handleVote(link) {
if (!opts.voting) {
showError("You'll need to login to vote.");
return;
}
var id = link.attr('id');
if (!id) {
// Didn't click on one of the voting arrows.
return;
}
// If it is an unvote, the new vote value is 0,
// Otherwise it's 1 for an upvote, or -1 for a downvote.
var value = 0;
if (id.charAt(1) != 'u') {
value = id.charAt(0) == 'u' ? 1 : -1;
}
// The data to be sent to the server.
var d = {
comment_id: id.substring(2),
value: value
};
// Swap the vote and unvote links.
link.hide();
$('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
.show();
// The div the comment is displayed in.
var div = $('div#cd' + d.comment_id);
var data = div.data('comment');
// If this is not an unvote, and the other vote arrow has
// already been pressed, unpress it.
if ((d.value !== 0) && (data.vote === d.value * -1)) {
$('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
$('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
}
// Update the comments rating in the local data.
data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
data.vote = d.value;
div.data('comment', data);
// Change the rating text.
div.find('.rating:first')
.text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
// Send the vote information to the server.
$.ajax({
type: "POST",
url: opts.processVoteURL,
data: d,
error: function(request, textStatus, error) {
showError('Oops, there was a problem casting that vote.');
}
});
}
/**
* Open a reply form used to reply to an existing comment.
*/
function openReply(id) {
// Swap out the reply link for the hide link
$('#rl' + id).hide();
$('#cr' + id).show();
// Add the reply li to the children ul.
var div = $(renderTemplate(replyTemplate, {id: id})).hide();
$('#cl' + id)
.prepend(div)
// Setup the submit handler for the reply form.
.find('#rf' + id)
.submit(function(event) {
event.preventDefault();
addComment($('#rf' + id));
closeReply(id);
})
.find('input[type=button]')
.click(function() {
closeReply(id);
});
div.slideDown('fast', function() {
$('#rf' + id).find('textarea').focus();
});
}
/**
* Close the reply form opened with openReply.
*/
function closeReply(id) {
// Remove the reply div from the DOM.
$('#rd' + id).slideUp('fast', function() {
$(this).remove();
});
// Swap out the hide link for the reply link
$('#cr' + id).hide();
$('#rl' + id).show();
}
/**
* Recursively sort a tree of comments using the comp comparator.
*/
function sortComments(comments) {
comments.sort(comp);
$.each(comments, function() {
this.children = sortComments(this.children);
});
return comments;
}
/**
* Get the children comments from a ul. If recursive is true,
* recursively include childrens' children.
*/
function getChildren(ul, recursive) {
var children = [];
ul.children().children("[id^='cd']")
.each(function() {
var comment = $(this).data('comment');
if (recursive)
comment.children = getChildren($(this).find('#cl' + comment.id), true);
children.push(comment);
});
return children;
}
/** Create a div to display a comment in. */
function createCommentDiv(comment) {
if (!comment.displayed && !opts.moderator) {
return $('<div class="moderate">Thank you! Your comment will show up '
+ 'once it is has been approved by a moderator.</div>');
}
// Prettify the comment rating.
comment.pretty_rating = comment.rating + ' point' +
(comment.rating == 1 ? '' : 's');
// Make a class (for displaying not yet moderated comments differently)
comment.css_class = comment.displayed ? '' : ' moderate';
// Create a div for this comment.
var context = $.extend({}, opts, comment);
var div = $(renderTemplate(commentTemplate, context));
// If the user has voted on this comment, highlight the correct arrow.
if (comment.vote) {
var direction = (comment.vote == 1) ? 'u' : 'd';
div.find('#' + direction + 'v' + comment.id).hide();
div.find('#' + direction + 'u' + comment.id).show();
}
if (opts.moderator || comment.text != '[deleted]') {
div.find('a.reply').show();
if (comment.proposal_diff)
div.find('#sp' + comment.id).show();
if (opts.moderator && !comment.displayed)
div.find('#cm' + comment.id).show();
if (opts.moderator || (opts.username == comment.username))
div.find('#dc' + comment.id).show();
}
return div;
}
/**
* A simple template renderer. Placeholders such as <%id%> are replaced
* by context['id'] with items being escaped. Placeholders such as <#id#>
* are not escaped.
*/
function renderTemplate(template, context) {
var esc = $(document.createElement('div'));
function handle(ph, escape) {
var cur = context;
$.each(ph.split('.'), function() {
cur = cur[this];
});
return escape ? esc.text(cur || "").html() : cur;
}
return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
return handle(arguments[2], arguments[1] == '%' ? true : false);
});
}
/** Flash an error message briefly. */
function showError(message) {
$(document.createElement('div')).attr({'class': 'popup-error'})
.append($(document.createElement('div'))
.attr({'class': 'error-message'}).text(message))
.appendTo('body')
.fadeIn("slow")
.delay(2000)
.fadeOut("slow");
}
/** Add a link the user uses to open the comments popup. */
$.fn.comment = function() {
return this.each(function() {
var id = $(this).attr('id').substring(1);
var count = COMMENT_METADATA[id];
var title = count + ' comment' + (count == 1 ? '' : 's');
var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
var addcls = count == 0 ? ' nocomment' : '';
$(this)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-open' + addcls,
id: 'ao' + id
})
.append($(document.createElement('img')).attr({
src: image,
alt: 'comment',
title: title
}))
.click(function(event) {
event.preventDefault();
show($(this).attr('id').substring(2));
})
)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-close hidden',
id: 'ah' + id
})
.append($(document.createElement('img')).attr({
src: opts.closeCommentImage,
alt: 'close',
title: 'close'
}))
.click(function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
})
);
});
};
var opts = {
processVoteURL: '/eds/ajax/process_vote',
addCommentURL: '/eds/ajax/add_comment.json',
getCommentsURL: '/eds/ajax/get_comments.json',
acceptCommentURL: '/eds/ajax/accept_comment',
deleteCommentURL: '/eds/ajax/delete_comment',
commentImage: '/eds/static/_static/comment.png',
closeCommentImage: '/eds/static/_static/comment-close.png',
loadingImage: '/eds/static/_static/ajax-loader.gif',
commentBrightImage: '/eds/static/_static/comment-bright.png',
upArrow: '/eds/static/_static/up.png',
downArrow: '/eds/static/_static/down.png',
upArrowPressed: '/eds/static/_static/up-pressed.png',
downArrowPressed: '/eds/static/_static/down-pressed.png',
voting: false,
moderator: false
};
if (typeof COMMENT_OPTIONS != "undefined") {
opts = jQuery.extend(opts, COMMENT_OPTIONS);
}
var popupTemplate = '\
<div class="sphinx-comments" id="sc<%id%>">\
<p class="sort-options">\
Sort by:\
<a href="#" class="sort-option byrating">best rated</a>\
<a href="#" class="sort-option byascage">newest</a>\
<a href="#" class="sort-option byage">oldest</a>\
</p>\
<div class="comment-header">Comments</div>\
<div class="comment-loading" id="cn<%id%>">\
loading comments... <img src="<%loadingImage%>" alt="" /></div>\
<ul id="cl<%id%>" class="comment-ul"></ul>\
<div id="ca<%id%>">\
<p class="add-a-comment">Add a comment\
(<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
<div class="comment-markup-box" id="mb<%id%>">\
reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
<tt>``code``</tt>, \
code blocks: <tt>::</tt> and an indented block after blank line</div>\
<form method="post" id="cf<%id%>" class="comment-form" action="">\
<textarea name="comment" cols="80"></textarea>\
<p class="propose-button">\
<a href="#" id="pc<%id%>" class="show-propose-change">\
Propose a change &#9657;\
</a>\
<a href="#" id="hc<%id%>" class="hide-propose-change">\
Propose a change &#9663;\
</a>\
</p>\
<textarea name="proposal" id="pt<%id%>" cols="80"\
spellcheck="false"></textarea>\
<input type="submit" value="Add comment" />\
<input type="hidden" name="node" value="<%id%>" />\
<input type="hidden" name="parent" value="" />\
</form>\
</div>\
</div>';
var commentTemplate = '\
<div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
<div class="vote">\
<div class="arrow">\
<a href="#" id="uv<%id%>" class="vote" title="vote up">\
<img src="<%upArrow%>" />\
</a>\
<a href="#" id="uu<%id%>" class="un vote" title="vote up">\
<img src="<%upArrowPressed%>" />\
</a>\
</div>\
<div class="arrow">\
<a href="#" id="dv<%id%>" class="vote" title="vote down">\
<img src="<%downArrow%>" id="da<%id%>" />\
</a>\
<a href="#" id="du<%id%>" class="un vote" title="vote down">\
<img src="<%downArrowPressed%>" />\
</a>\
</div>\
</div>\
<div class="comment-content">\
<p class="tagline comment">\
<span class="user-id"><%username%></span>\
<span class="rating"><%pretty_rating%></span>\
<span class="delta"><%time.delta%></span>\
</p>\
<div class="comment-text comment"><#text#></div>\
<p class="comment-opts comment">\
<a href="#" class="reply hidden" id="rl<%id%>">reply &#9657;</a>\
<a href="#" class="close-reply" id="cr<%id%>">reply &#9663;</a>\
<a href="#" id="sp<%id%>" class="show-proposal">proposal &#9657;</a>\
<a href="#" id="hp<%id%>" class="hide-proposal">proposal &#9663;</a>\
<a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
<span id="cm<%id%>" class="moderation hidden">\
<a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
</span>\
</p>\
<pre class="proposal" id="pr<%id%>">\
<#proposal_diff#>\
</pre>\
<ul class="comment-children" id="cl<%id%>"></ul>\
</div>\
<div class="clearleft"></div>\
</div>\
</div>';
var replyTemplate = '\
<li>\
<div class="reply-div" id="rd<%id%>">\
<form id="rf<%id%>">\
<textarea name="comment" cols="80"></textarea>\
<input type="submit" value="Add reply" />\
<input type="button" value="Cancel" />\
<input type="hidden" name="parent" value="<%id%>" />\
<input type="hidden" name="node" value="" />\
</form>\
</div>\
</li>';
$(document).ready(function() {
init();
});
})(jQuery);
$(document).ready(function() {
// add comment anchors for all paragraphs that are commentable
$('.sphinx-has-comment').comment();
// highlight search words in search results
$("div.context").each(function() {
var params = $.getQueryParameters();
var terms = (params.q) ? params.q[0].split(/\s+/) : [];
var result = $(this);
$.each(terms, function() {
result.highlightText(this.toLowerCase(), 'highlighted');
});
});
// directly open comment window if requested
var anchor = document.location.hash;
if (anchor.substring(0, 9) == '#comment-') {
$('#ao' + anchor.substring(9)).click();
document.location.hash = '#s' + anchor.substring(9);
}
});