").append(ce.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Lt,type:"GET",isLocal:Ft.test(Dt[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":zt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":ce.parseJSON,"text xml":ce.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?q(q(e,ce.ajaxSettings),t):q(ce.ajaxSettings,e)},ajaxPrefilter:L($t),ajaxTransport:L(It),ajax:function(e,n){function r(e,n,r,i){var o,f,v,b,w,C=n;2!==x&&(x=2,u&&clearTimeout(u),c=t,s=i||"",T.readyState=e>0?4:0,o=e>=200&&300>e||304===e,r&&(b=_(p,T,r)),b=M(p,b,T,o),o?(p.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(ce.lastModified[a]=w),w=T.getResponseHeader("etag"),w&&(ce.etag[a]=w)),204===e||"HEAD"===p.type?C="nocontent":304===e?C="notmodified":(C=b.state,f=b.data,v=b.error,o=!v)):(v=C,(e||!C)&&(C="error",0>e&&(e=0))),T.status=e,T.statusText=(n||C)+"",o?g.resolveWith(d,[f,C,T]):g.rejectWith(d,[T,C,v]),T.statusCode(y),y=t,l&&h.trigger(o?"ajaxSuccess":"ajaxError",[T,p,o?f:v]),m.fireWith(d,[T,C]),l&&(h.trigger("ajaxComplete",[T,p]),--ce.active||ce.event.trigger("ajaxStop")))}"object"==typeof e&&(n=e,e=t),n=n||{};var i,o,a,s,u,l,c,f,p=ce.ajaxSetup({},n),d=p.context||p,h=p.context&&(d.nodeType||d.jquery)?ce(d):ce.event,g=ce.Deferred(),m=ce.Callbacks("once memory"),y=p.statusCode||{},v={},b={},x=0,w="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!f)for(f={};t=Ot.exec(s);)f[t[1].toLowerCase()]=t[2];t=f[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?s:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=b[n]=b[n]||e,v[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)y[t]=[y[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||w;return c&&c.abort(t),r(0,t),this}};if(g.promise(T).complete=m.add,T.success=T.done,T.error=T.fail,p.url=((e||p.url||Lt)+"").replace(_t,"").replace(Pt,Dt[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=ce.trim(p.dataType||"*").toLowerCase().match(pe)||[""],null==p.crossDomain&&(i=Rt.exec(p.url.toLowerCase()),p.crossDomain=!(!i||i[1]===Dt[1]&&i[2]===Dt[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(Dt[3]||("http:"===Dt[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=ce.param(p.data,p.traditional)),H($t,p,n,T),2===x)return T;l=p.global,l&&0===ce.active++&&ce.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Bt.test(p.type),a=p.url,p.hasContent||(p.data&&(a=p.url+=(qt.test(a)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=Mt.test(a)?a.replace(Mt,"$1_="+Ht++):a+(qt.test(a)?"&":"?")+"_="+Ht++)),p.ifModified&&(ce.lastModified[a]&&T.setRequestHeader("If-Modified-Since",ce.lastModified[a]),ce.etag[a]&&T.setRequestHeader("If-None-Match",ce.etag[a])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&T.setRequestHeader("Content-Type",p.contentType),T.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+zt+"; q=0.01":""):p.accepts["*"]);for(o in p.headers)T.setRequestHeader(o,p.headers[o]);if(p.beforeSend&&(p.beforeSend.call(d,T,p)===!1||2===x))return T.abort();w="abort";for(o in{success:1,error:1,complete:1})T[o](p[o]);if(c=H(It,p,n,T)){T.readyState=1,l&&h.trigger("ajaxSend",[T,p]),p.async&&p.timeout>0&&(u=setTimeout(function(){T.abort("timeout")},p.timeout));try{x=1,c.send(v,r)}catch(C){if(!(2>x))throw C;r(-1,C)}}else r(-1,"No Transport");return T},getJSON:function(e,t,n){return ce.get(e,t,n,"json")},getScript:function(e,n){return ce.get(e,t,n,"script")}}),ce.each(["get","post"],function(e,n){ce[n]=function(e,r,i,o){return ce.isFunction(r)&&(o=o||i,i=r,r=t),ce.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),ce.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return ce.globalEval(e),e}}}),ce.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),ce.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=G.head||ce("head")[0]||G.documentElement;return{send:function(t,i){n=G.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Ut=[],Vt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Ut.pop()||ce.expando+"_"+Ht++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Vt.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=ce.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Vt,"$1"+o):n.jsonp!==!1&&(n.url+=(qt.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||ce.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Ut.push(o)),s&&ce.isFunction(a)&&a(s[0]),s=a=t}),"script"):void 0});var Yt,Jt,Gt=0,Qt=e.ActiveXObject&&function(){var e;for(e in Yt)Yt[e](t,!0)};ce.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&O()||F()}:O,Jt=ce.ajaxSettings.xhr(),ce.support.cors=!!Jt&&"withCredentials"in Jt,Jt=ce.support.ajax=!!Jt,Jt&&ce.ajaxTransport(function(n){if(!n.crossDomain||ce.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,f;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=ce.noop,Qt&&delete Yt[a]),i)4!==u.readyState&&u.abort();else{f={},s=u.status,l=u.getAllResponseHeaders(),"string"==typeof u.responseText&&(f.text=u.responseText);try{c=u.statusText}catch(p){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=f.text?200:404}}catch(d){i||o(-1,d)}f&&o(s,c,f,l)},n.async?4===u.readyState?setTimeout(r):(a=++Gt,Qt&&(Yt||(Yt={},ce(e).unload(Qt)),Yt[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Kt,Zt,en=/^(?:toggle|show|hide)$/,tn=new RegExp("^(?:([+-])=|)("+fe+")([a-z%]*)$","i"),nn=/queueHooks$/,rn=[$],on={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=tn.exec(t),o=i&&i[3]||(ce.cssNumber[e]?"":"px"),a=(ce.cssNumber[e]||"px"!==o&&+r)&&tn.exec(ce.css(n.elem,e)),s=1,u=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,ce.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--u)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};ce.Animation=ce.extend(R,{tweener:function(e,t){ce.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");for(var n,r=0,i=e.length;i>r;r++)n=e[r],on[n]=on[n]||[],on[n].unshift(t)},prefilter:function(e,t){t?rn.unshift(e):rn.push(e)}}),ce.Tween=I,I.prototype={constructor:I,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(ce.cssNumber[n]?"":"px")},cur:function(){var e=I.propHooks[this.prop];return e&&e.get?e.get(this):I.propHooks._default.get(this)},run:function(e){var t,n=I.propHooks[this.prop];return this.options.duration?this.pos=t=ce.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):I.propHooks._default.set(this),this}},I.prototype.init.prototype=I.prototype,I.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=ce.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){ce.fx.step[e.prop]?ce.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[ce.cssProps[e.prop]]||ce.cssHooks[e.prop])?ce.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},I.propHooks.scrollTop=I.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},ce.each(["toggle","show","hide"],function(e,t){var n=ce.fn[t];ce.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(z(t,!0),e,r,i)}}),ce.fn.extend({fadeTo:function(e,t,n,r){return this.filter(C).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=ce.isEmptyObject(e),o=ce.speed(t,n,r),a=function(){var t=R(this,ce.extend({},e),o);(i||ce._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=ce.timers,a=ce._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&nn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&ce.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=ce._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=ce.timers,a=r?r.length:0;for(n.finish=!0,ce.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),ce.each({slideDown:z("show"),slideUp:z("hide"),slideToggle:z("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){ce.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),ce.speed=function(e,t,n){var r=e&&"object"==typeof e?ce.extend({},e):{complete:n||!n&&t||ce.isFunction(e)&&e,duration:e,easing:n&&t||t&&!ce.isFunction(t)&&t};return r.duration=ce.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in ce.fx.speeds?ce.fx.speeds[r.duration]:ce.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){ce.isFunction(r.old)&&r.old.call(this),r.queue&&ce.dequeue(this,r.queue)},r},ce.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},ce.timers=[],ce.fx=I.prototype.init,ce.fx.tick=function(){var e,n=ce.timers,r=0;for(Kt=ce.now();r
-1,f={},p={};c?(p=a.position(),i=p.top,o=p.left):(i=parseFloat(u)||0,o=parseFloat(l)||0),ce.isFunction(t)&&(t=t.call(e,n,s)),null!=t.top&&(f.top=t.top-s.top+i),null!=t.left&&(f.left=t.left-s.left+o),"using"in t?t.using.call(e,f):a.css(f)}},ce.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===ce.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),ce.nodeName(e[0],"html")||(n=e.offset()),n.top+=ce.css(e[0],"borderTopWidth",!0),n.left+=ce.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-ce.css(r,"marginTop",!0),left:t.left-n.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent||Q;e&&!ce.nodeName(e,"html")&&"static"===ce.css(e,"position");)e=e.offsetParent;return e||Q})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);ce.fn[e]=function(i){return ce.access(this,function(e,i,o){var a=X(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:void(a?a.scrollTo(r?ce(a).scrollLeft():o,r?o:ce(a).scrollTop()):e[i]=o)},e,i,arguments.length,null)}}),ce.each({Height:"height",Width:"width"},function(e,n){ce.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){ce.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return ce.access(this,function(n,r,i){var o;return ce.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?ce.css(n,r,s):ce.style(n,r,i,s)},n,a?i:t,a,null)}})}),ce.fn.size=function(){return this.length},ce.fn.andSelf=ce.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=ce:(e.jQuery=e.$=ce,"function"==typeof define&&define.amd&&define("jquery",[],function(){return ce}))}(window);
diff --git a/js/material-kit.js b/js/material-kit.js
new file mode 100644
index 0000000..9aefe03
--- /dev/null
+++ b/js/material-kit.js
@@ -0,0 +1,129 @@
+/*!
+
+ =========================================================
+ * Material Kit - v1.1.1.0
+ =========================================================
+
+ * Product Page: http://www.creative-tim.com/product/material-kit
+ * Copyright 2017 Creative Tim (http://www.creative-tim.com)
+ * Licensed under MIT (https://github.com/timcreative/material-kit/blob/master/LICENSE.md)
+
+ =========================================================
+
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+ */
+
+var transparent = true;
+
+var transparentDemo = true;
+var fixedTop = false;
+
+var navbar_initialized = false;
+
+$(document).ready(function(){
+
+ // Init Material scripts for buttons ripples, inputs animations etc, more info on the next link https://github.com/FezVrasta/bootstrap-material-design#materialjs
+ $.material.init();
+
+ // Activate the Tooltips
+ $('[data-toggle="tooltip"], [rel="tooltip"]').tooltip();
+
+ // Activate Datepicker
+ if($('.datepicker').length != 0){
+ $('.datepicker').datepicker({
+ weekStart:1
+ });
+ }
+
+ // Check if we have the class "navbar-color-on-scroll" then add the function to remove the class "navbar-transparent" so it will transform to a plain color.
+ if($('.navbar-color-on-scroll').length != 0){
+ $(window).on('scroll', materialKit.checkScrollForTransparentNavbar)
+ }
+
+ // Activate Popovers
+ $('[data-toggle="popover"]').popover();
+
+ // Active Carousel
+ $('.carousel').carousel({
+ interval: 400000
+ });
+
+});
+
+materialKit = {
+ misc:{
+ navbar_menu_visible: 0
+ },
+
+ checkScrollForTransparentNavbar: debounce(function() {
+ if($(document).scrollTop() > 260 ) {
+ if(transparent) {
+ transparent = false;
+ $('.navbar-color-on-scroll').removeClass('navbar-transparent');
+ }
+ } else {
+ if( !transparent ) {
+ transparent = true;
+ $('.navbar-color-on-scroll').addClass('navbar-transparent');
+ }
+ }
+ }, 17),
+
+ initSliders: function(){
+ // Sliders for demo purpose
+ $('#sliderRegular').noUiSlider({
+ start: 40,
+ connect: "lower",
+ range: {
+ min: 0,
+ max: 100
+ }
+ });
+
+ $('#sliderDouble').noUiSlider({
+ start: [20, 60] ,
+ connect: true,
+ range: {
+ min: 0,
+ max: 100
+ }
+ });
+ }
+}
+
+
+var big_image;
+
+materialKitDemo = {
+ checkScrollForParallax: debounce(function(){
+ var current_scroll = $(this).scrollTop();
+
+ oVal = ($(window).scrollTop() / 3);
+ big_image.css({
+ 'transform':'translate3d(0,' + oVal +'px,0)',
+ '-webkit-transform':'translate3d(0,' + oVal +'px,0)',
+ '-ms-transform':'translate3d(0,' + oVal +'px,0)',
+ '-o-transform':'translate3d(0,' + oVal +'px,0)'
+ });
+
+ }, 6)
+
+}
+// Returns a function, that, as long as it continues to be invoked, will not
+// be triggered. The function will be called after it stops being called for
+// N milliseconds. If `immediate` is passed, trigger the function on the
+// leading edge, instead of the trailing.
+
+function debounce(func, wait, immediate) {
+ var timeout;
+ return function() {
+ var context = this, args = arguments;
+ clearTimeout(timeout);
+ timeout = setTimeout(function() {
+ timeout = null;
+ if (!immediate) func.apply(context, args);
+ }, wait);
+ if (immediate && !timeout) func.apply(context, args);
+ };
+};
diff --git a/js/material.min.js b/js/material.min.js
new file mode 100755
index 0000000..e8f3b48
--- /dev/null
+++ b/js/material.min.js
@@ -0,0 +1 @@
+!function(t){function o(t){return"undefined"==typeof t.which?!0:"number"==typeof t.which&&t.which>0?!t.ctrlKey&&!t.metaKey&&!t.altKey&&8!=t.which&&9!=t.which&&13!=t.which&&16!=t.which&&17!=t.which&&20!=t.which&&27!=t.which:!1}function i(o){var i=t(o);i.prop("disabled")||i.closest(".form-group").addClass("is-focused")}function n(o){o.closest("label").hover(function(){var o=t(this).find("input");o.prop("disabled")||i(o)},function(){e(t(this).find("input"))})}function e(o){t(o).closest(".form-group").removeClass("is-focused")}t.expr[":"].notmdproc=function(o){return t(o).data("mdproc")?!1:!0},t.material={options:{validate:!0,input:!0,ripples:!0,checkbox:!0,togglebutton:!0,radio:!0,arrive:!0,autofill:!1,withRipples:[".btn:not(.btn-link)",".card-image",".navbar a:not(.withoutripple)",".footer a:not(.withoutripple)",".dropdown-menu a",".nav-tabs a:not(.withoutripple)",".withripple",".pagination li:not(.active):not(.disabled) a:not(.withoutripple)"].join(","),inputElements:"input.form-control, textarea.form-control, select.form-control",checkboxElements:".checkbox > label > input[type=checkbox]",togglebuttonElements:".togglebutton > label > input[type=checkbox]",radioElements:".radio > label > input[type=radio]"},checkbox:function(o){var i=t(o?o:this.options.checkboxElements).filter(":notmdproc").data("mdproc",!0).after(" ");n(i)},togglebutton:function(o){var i=t(o?o:this.options.togglebuttonElements).filter(":notmdproc").data("mdproc",!0).after(" ");n(i)},radio:function(o){var i=t(o?o:this.options.radioElements).filter(":notmdproc").data("mdproc",!0).after(" ");n(i)},input:function(o){t(o?o:this.options.inputElements).filter(":notmdproc").data("mdproc",!0).each(function(){var o=t(this),i=o.closest(".form-group");0===i.length&&(o.wrap("
"),i=o.closest(".form-group")),o.attr("data-hint")&&(o.after(""+o.attr("data-hint")+"
"),o.removeAttr("data-hint"));var n={"input-lg":"form-group-lg","input-sm":"form-group-sm"};if(t.each(n,function(t,n){o.hasClass(t)&&(o.removeClass(t),i.addClass(n))}),o.hasClass("floating-label")){var e=o.attr("placeholder");o.attr("placeholder",null).removeClass("floating-label");var a=o.attr("id"),r="";a&&(r="for='"+a+"'"),i.addClass("label-floating"),o.after(""+e+" ")}(null===o.val()||"undefined"==o.val()||""===o.val())&&i.addClass("is-empty"),i.append(" "),i.find("input[type=file]").length>0&&i.addClass("is-fileinput")})},attachInputEventHandlers:function(){var n=this.options.validate;t(document).on("change",".checkbox input[type=checkbox]",function(){t(this).blur()}).on("keydown paste",".form-control",function(i){o(i)&&t(this).closest(".form-group").removeClass("is-empty")}).on("keyup change",".form-control",function(){var o=t(this),i=o.closest(".form-group"),e="undefined"==typeof o[0].checkValidity||o[0].checkValidity();""===o.val()?i.addClass("is-empty"):i.removeClass("is-empty"),n&&(e?i.removeClass("has-error"):i.addClass("has-error"))}).on("focus",".form-control, .form-group.is-fileinput",function(){i(this)}).on("blur",".form-control, .form-group.is-fileinput",function(){e(this)}).on("change",".form-group input",function(){var o=t(this);if("file"!=o.attr("type")){var i=o.closest(".form-group"),n=o.val();n?i.removeClass("is-empty"):i.addClass("is-empty")}}).on("change",".form-group.is-fileinput input[type='file']",function(){var o=t(this),i=o.closest(".form-group"),n="";t.each(this.files,function(t,o){n+=o.name+", "}),n=n.substring(0,n.length-2),n?i.removeClass("is-empty"):i.addClass("is-empty"),i.find("input.form-control[readonly]").val(n)})},ripples:function(o){t(o?o:this.options.withRipples).ripples()},autofill:function(){var o=setInterval(function(){t("input[type!=checkbox]").each(function(){var o=t(this);o.val()&&o.val()!==o.attr("value")&&o.trigger("change")})},100);setTimeout(function(){clearInterval(o)},1e4)},attachAutofillEventHandlers:function(){var o;t(document).on("focus","input",function(){var i=t(this).parents("form").find("input").not("[type=file]");o=setInterval(function(){i.each(function(){var o=t(this);o.val()!==o.attr("value")&&o.trigger("change")})},100)}).on("blur",".form-group input",function(){clearInterval(o)})},init:function(o){this.options=t.extend({},this.options,o);var i=t(document);t.fn.ripples&&this.options.ripples&&this.ripples(),this.options.input&&(this.input(),this.attachInputEventHandlers()),this.options.checkbox&&this.checkbox(),this.options.togglebutton&&this.togglebutton(),this.options.radio&&this.radio(),this.options.autofill&&(this.autofill(),this.attachAutofillEventHandlers()),document.arrive&&this.options.arrive&&(t.fn.ripples&&this.options.ripples&&i.arrive(this.options.withRipples,function(){t.material.ripples(t(this))}),this.options.input&&i.arrive(this.options.inputElements,function(){t.material.input(t(this))}),this.options.checkbox&&i.arrive(this.options.checkboxElements,function(){t.material.checkbox(t(this))}),this.options.radio&&i.arrive(this.options.radioElements,function(){t.material.radio(t(this))}),this.options.togglebutton&&i.arrive(this.options.togglebuttonElements,function(){t.material.togglebutton(t(this))}))}}}(jQuery),function(t,o,i,n){"use strict";function e(o,i){r=this,this.element=t(o),this.options=t.extend({},s,i),this._defaults=s,this._name=a,this.init()}var a="ripples",r=null,s={};e.prototype.init=function(){var i=this.element;i.on("mousedown touchstart",function(n){if(!r.isTouch()||"mousedown"!==n.type){i.find(".ripple-container").length||i.append('
');var e=i.children(".ripple-container"),a=r.getRelY(e,n),s=r.getRelX(e,n);if(a||s){var l=r.getRipplesColor(i),p=t("
");p.addClass("ripple").css({left:s,top:a,"background-color":l}),e.append(p),function(){return o.getComputedStyle(p[0]).opacity}(),r.rippleOn(i,p),setTimeout(function(){r.rippleEnd(p)},500),i.on("mouseup mouseleave touchend",function(){p.data("mousedown","off"),"off"===p.data("animating")&&r.rippleOut(p)})}}})},e.prototype.getNewSize=function(t,o){return Math.max(t.outerWidth(),t.outerHeight())/o.outerWidth()*2.5},e.prototype.getRelX=function(t,o){var i=t.offset();return r.isTouch()?(o=o.originalEvent,1===o.touches.length?o.touches[0].pageX-i.left:!1):o.pageX-i.left},e.prototype.getRelY=function(t,o){var i=t.offset();return r.isTouch()?(o=o.originalEvent,1===o.touches.length?o.touches[0].pageY-i.top:!1):o.pageY-i.top},e.prototype.getRipplesColor=function(t){var i=t.data("ripple-color")?t.data("ripple-color"):o.getComputedStyle(t[0]).color;return i},e.prototype.hasTransitionSupport=function(){var t=i.body||i.documentElement,o=t.style,e=o.transition!==n||o.WebkitTransition!==n||o.MozTransition!==n||o.MsTransition!==n||o.OTransition!==n;return e},e.prototype.isTouch=function(){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)},e.prototype.rippleEnd=function(t){t.data("animating","off"),"off"===t.data("mousedown")&&r.rippleOut(t)},e.prototype.rippleOut=function(t){t.off(),r.hasTransitionSupport()?t.addClass("ripple-out"):t.animate({opacity:0},100,function(){t.trigger("transitionend")}),t.on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd",function(){t.remove()})},e.prototype.rippleOn=function(t,o){var i=r.getNewSize(t,o);r.hasTransitionSupport()?o.css({"-ms-transform":"scale("+i+")","-moz-transform":"scale("+i+")","-webkit-transform":"scale("+i+")",transform:"scale("+i+")"}).addClass("ripple-on").data("animating","on").data("mousedown","on"):o.animate({width:2*Math.max(t.outerWidth(),t.outerHeight()),height:2*Math.max(t.outerWidth(),t.outerHeight()),"margin-left":-1*Math.max(t.outerWidth(),t.outerHeight()),"margin-top":-1*Math.max(t.outerWidth(),t.outerHeight()),opacity:.2},500,function(){o.trigger("transitionend")})},t.fn.ripples=function(o){return this.each(function(){t.data(this,"plugin_"+a)||t.data(this,"plugin_"+a,new e(this,o))})}}(jQuery,window,document);
diff --git a/js/nouislider.min.js b/js/nouislider.min.js
new file mode 100644
index 0000000..03f93e7
--- /dev/null
+++ b/js/nouislider.min.js
@@ -0,0 +1,31 @@
+/*
+
+$.Link (part of noUiSlider) - WTFPL */
+(function(c){function m(a,c,d){if((a[c]||a[d])&&a[c]===a[d])throw Error("(Link) '"+c+"' can't match '"+d+"'.'");}function r(a){void 0===a&&(a={});if("object"!==typeof a)throw Error("(Format) 'format' option must be an object.");var h={};c(u).each(function(c,n){if(void 0===a[n])h[n]=A[c];else if(typeof a[n]===typeof A[c]){if("decimals"===n&&(0>a[n]||7a&&(n=this.a("negative"),k=this.a("negativeBefore"));a=Math.abs(a).toFixed(d).toString();a=a.split(".");this.a("thousand")?(m=c(a[0]).match(/.{1,3}/g),m=c(m.join(c(this.a("thousand"))))):m=a[0];this.a("mark")&&1 ")[0]};k.prototype.H=function(a){this.method="val";this.j=document.createElement("input");this.j.name=a;this.j.type="hidden"};k.prototype.G=function(a){function h(a,c){return[c?null:a,c?a:null]}var d=this;this.method="val";this.target=a.on("change",function(a){d.B.val(h(c(a.target).val(),d.t),{link:d,set:!0})})};k.prototype.p=function(a,h,d,k){this.g=d;this.update=!k;if("string"===
+typeof a&&0===a.indexOf("-tooltip-"))this.K(a,h);else if("string"===typeof a&&0!==a.indexOf("-"))this.H(a);else if("function"===typeof a)this.target=!1,this.method=a;else{if(a instanceof c||c.zepto&&c.zepto.isZ(a)){if(!h){if(a.is("input, select, textarea")){this.G(a);return}h="html"}if("function"===typeof h||"string"===typeof h&&a[h]){this.method=h;this.target=a;return}}throw new RangeError("(Link) Invalid Link.");}};k.prototype.write=function(a,c,d,k){if(!this.update||!1!==k)if(this.u=a,this.F=a=
+this.format(a),"function"===typeof this.method)this.method.call(this.target[0]||d[0],a,c,d);else this.target[this.method](a,c,d)};k.prototype.q=function(a){this.g=new r(c.extend({},a,this.g instanceof r?this.g.r:this.g))};k.prototype.J=function(a){this.B=a};k.prototype.I=function(a){this.t=a};k.prototype.format=function(a){return this.g.L(a)};k.prototype.A=function(a){return this.g.w(a)};k.prototype.p.prototype=k.prototype;c.Link=k})(window.jQuery||window.Zepto);/*
+
+$.fn.noUiSlider - WTFPL - refreshless.com/nouislider/ */
+(function(c){function m(e){return"number"===typeof e&&!isNaN(e)&&isFinite(e)}function r(e){return c.isArray(e)?e:[e]}function k(e,b){e.addClass(b);setTimeout(function(){e.removeClass(b)},300)}function u(e,b){return 100*b/(e[1]-e[0])}function A(e,b){if(b>=e.d.slice(-1)[0])return 100;for(var a=1,c,f,d;b>=e.d[a];)a++;c=e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return d+u(c,0>c[0]?b+Math.abs(c[0]):b-c[0])/(100/(e.c[a]-d))}function a(e,b){if(100<=b)return e.d.slice(-1)[0];for(var a=1,c,f,d;b>=e.c[a];)a++;c=
+e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return 100/(e.c[a]-d)*(b-d)*(c[1]-c[0])/100+c[0]}function h(a,b){for(var c=1,g;(a.dir?100-b:b)>=a.c[c];)c++;if(a.m)return g=a.c[c-1],c=a.c[c],b-g>(c-g)/2?c:g;a.h[c-1]?(g=a.h[c-1],c=a.c[c-1]+Math.round((b-a.c[c-1])/g)*g):c=b;return c}function d(a,b){if(!m(b))throw Error("noUiSlider: 'step' is not numeric.");a.h[0]=b}function n(a,b){if("object"!==typeof b||c.isArray(b))throw Error("noUiSlider: 'range' is not an object.");if(void 0===b.min||void 0===b.max)throw Error("noUiSlider: Missing 'min' or 'max' in 'range'.");
+c.each(b,function(b,g){var d;"number"===typeof g&&(g=[g]);if(!c.isArray(g))throw Error("noUiSlider: 'range' contains invalid value.");d="min"===b?0:"max"===b?100:parseFloat(b);if(!m(d)||!m(g[0]))throw Error("noUiSlider: 'range' value isn't numeric.");a.c.push(d);a.d.push(g[0]);d?a.h.push(isNaN(g[1])?!1:g[1]):isNaN(g[1])||(a.h[0]=g[1])});c.each(a.h,function(b,c){if(!c)return!0;a.h[b]=u([a.d[b],a.d[b+1]],c)/(100/(a.c[b+1]-a.c[b]))})}function E(a,b){"number"===typeof b&&(b=[b]);if(!c.isArray(b)||!b.length||
+2
").addClass(f[2]),g=["-lower","-upper"];a.dir&&g.reverse();d.children().addClass(f[3]+" "+f[3]+g[b]);return d}function Q(a,b){b.j&&(b=new c.Link({target:c(b.j).clone().appendTo(a),method:b.method,format:b.g},!0));return b}function R(a,b){var d,f=[];for(d=0;dp&&(p=h(b,p));p=Math.max(Math.min(parseFloat(p.toFixed(7)),100),0);if(p===x[g])return 1===l.length?!1:p===H||p===k?0:!1;d.css(b.style,p+"%");d.is(":first-child")&&d.toggleClass(f[17],50d&&(e+=Math.abs(d)),100 (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' +
+ ' * http://lab.hakim.se/reveal-js\n' +
+ ' * MIT licensed\n' +
+ ' *\n' +
+ ' * Copyright (C) 2017 Hakim El Hattab, http://hakim.se\n' +
+ ' */'
+ },
+
+ qunit: {
+ files: [ 'test/*.html' ]
+ },
+
+ uglify: {
+ options: {
+ banner: '<%= meta.banner %>\n',
+ screwIE8: false
+ },
+ build: {
+ src: 'js/reveal.js',
+ dest: 'js/reveal.min.js'
+ }
+ },
+
+ sass: {
+ core: {
+ src: 'css/reveal.scss',
+ dest: 'css/reveal.css'
+ },
+ themes: {
+ expand: true,
+ cwd: 'css/theme/source',
+ src: ['*.sass', '*.scss'],
+ dest: 'css/theme',
+ ext: '.css'
+ }
+ },
+
+ autoprefixer: {
+ core: {
+ src: 'css/reveal.css'
+ }
+ },
+
+ cssmin: {
+ options: {
+ compatibility: 'ie9'
+ },
+ compress: {
+ src: 'css/reveal.css',
+ dest: 'css/reveal.min.css'
+ }
+ },
+
+ jshint: {
+ options: {
+ curly: false,
+ eqeqeq: true,
+ immed: true,
+ esnext: true,
+ latedef: 'nofunc',
+ newcap: true,
+ noarg: true,
+ sub: true,
+ undef: true,
+ eqnull: true,
+ browser: true,
+ expr: true,
+ globals: {
+ head: false,
+ module: false,
+ console: false,
+ unescape: false,
+ define: false,
+ exports: false
+ }
+ },
+ files: [ 'Gruntfile.js', 'js/reveal.js' ]
+ },
+
+ connect: {
+ server: {
+ options: {
+ port: port,
+ base: root,
+ livereload: true,
+ open: true,
+ useAvailablePort: true
+ }
+ }
+ },
+
+ zip: {
+ bundle: {
+ src: [
+ 'index.html',
+ 'css/**',
+ 'js/**',
+ 'lib/**',
+ 'images/**',
+ 'plugin/**',
+ '**.md'
+ ],
+ dest: 'reveal-js-presentation.zip'
+ }
+ },
+
+ watch: {
+ js: {
+ files: [ 'Gruntfile.js', 'js/reveal.js' ],
+ tasks: 'js'
+ },
+ theme: {
+ files: [
+ 'css/theme/source/*.sass',
+ 'css/theme/source/*.scss',
+ 'css/theme/template/*.sass',
+ 'css/theme/template/*.scss'
+ ],
+ tasks: 'css-themes'
+ },
+ css: {
+ files: [ 'css/reveal.scss' ],
+ tasks: 'css-core'
+ },
+ html: {
+ files: root.map(path => path + '/*.html')
+ },
+ markdown: {
+ files: root.map(path => path + '/*.md')
+ },
+ options: {
+ livereload: true
+ }
+ },
+
+ retire: {
+ js: [ 'js/reveal.js', 'lib/js/*.js', 'plugin/**/*.js' ],
+ node: [ '.' ]
+ }
+
+ });
+
+ // Dependencies
+ grunt.loadNpmTasks( 'grunt-contrib-connect' );
+ grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
+ grunt.loadNpmTasks( 'grunt-contrib-jshint' );
+ grunt.loadNpmTasks( 'grunt-contrib-qunit' );
+ grunt.loadNpmTasks( 'grunt-contrib-uglify' );
+ grunt.loadNpmTasks( 'grunt-contrib-watch' );
+ grunt.loadNpmTasks( 'grunt-autoprefixer' );
+ grunt.loadNpmTasks( 'grunt-retire' );
+ grunt.loadNpmTasks( 'grunt-sass' );
+ grunt.loadNpmTasks( 'grunt-zip' );
+
+ // Default task
+ grunt.registerTask( 'default', [ 'css', 'js' ] );
+
+ // JS task
+ grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] );
+
+ // Theme CSS
+ grunt.registerTask( 'css-themes', [ 'sass:themes' ] );
+
+ // Core framework CSS
+ grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] );
+
+ // All CSS
+ grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] );
+
+ // Package presentation to archive
+ grunt.registerTask( 'package', [ 'default', 'zip' ] );
+
+ // Serve presentation locally
+ grunt.registerTask( 'serve', [ 'connect', 'watch' ] );
+
+ // Run tests
+ grunt.registerTask( 'test', [ 'jshint', 'qunit' ] );
+
+};
diff --git a/reveal.js/LICENSE b/reveal.js/LICENSE
new file mode 100755
index 0000000..c3e6e5f
--- /dev/null
+++ b/reveal.js/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2017 Hakim El Hattab, http://hakim.se, and reveal.js contributors
+
+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.
\ No newline at end of file
diff --git a/reveal.js/README.md b/reveal.js/README.md
new file mode 100755
index 0000000..10e743f
--- /dev/null
+++ b/reveal.js/README.md
@@ -0,0 +1,1238 @@
+# reveal.js [](https://travis-ci.org/hakimel/reveal.js)
+
+A framework for easily creating beautiful presentations using HTML. [Check out the live demo](http://lab.hakim.se/reveal-js/).
+
+reveal.js comes with a broad range of features including [nested slides](https://github.com/hakimel/reveal.js#markup), [Markdown contents](https://github.com/hakimel/reveal.js#markdown), [PDF export](https://github.com/hakimel/reveal.js#pdf-export), [speaker notes](https://github.com/hakimel/reveal.js#speaker-notes) and a [JavaScript API](https://github.com/hakimel/reveal.js#api). There's also a fully featured visual editor and platform for sharing reveal.js presentations at [slides.com](https://slides.com?ref=github).
+
+## Table of contents
+- [Online Editor](#online-editor)
+- [Instructions](#instructions)
+ - [Markup](#markup)
+ - [Markdown](#markdown)
+ - [Element Attributes](#element-attributes)
+ - [Slide Attributes](#slide-attributes)
+- [Configuration](#configuration)
+- [Presentation Size](#presentation-size)
+- [Dependencies](#dependencies)
+- [Ready Event](#ready-event)
+- [Auto-sliding](#auto-sliding)
+- [Keyboard Bindings](#keyboard-bindings)
+- [Touch Navigation](#touch-navigation)
+- [Lazy Loading](#lazy-loading)
+- [API](#api)
+ - [Slide Changed Event](#slide-changed-event)
+ - [Presentation State](#presentation-state)
+ - [Slide States](#slide-states)
+ - [Slide Backgrounds](#slide-backgrounds)
+ - [Parallax Background](#parallax-background)
+ - [Slide Transitions](#slide-transitions)
+ - [Internal links](#internal-links)
+ - [Fragments](#fragments)
+ - [Fragment events](#fragment-events)
+ - [Code syntax highlighting](#code-syntax-highlighting)
+ - [Slide number](#slide-number)
+ - [Overview mode](#overview-mode)
+ - [Fullscreen mode](#fullscreen-mode)
+ - [Embedded media](#embedded-media)
+ - [Stretching elements](#stretching-elements)
+ - [postMessage API](#postmessage-api)
+- [PDF Export](#pdf-export)
+- [Theming](#theming)
+- [Speaker Notes](#speaker-notes)
+ - [Share and Print Speaker Notes](#share-and-print-speaker-notes)
+ - [Server Side Speaker Notes](#server-side-speaker-notes)
+- [Multiplexing](#multiplexing)
+ - [Master presentation](#master-presentation)
+ - [Client presentation](#client-presentation)
+ - [Socket.io server](#socketio-server)
+- [MathJax](#mathjax)
+- [Installation](#installation)
+ - [Basic setup](#basic-setup)
+ - [Full setup](#full-setup)
+ - [Folder Structure](#folder-structure)
+- [License](#license)
+
+#### More reading
+- [Changelog](https://github.com/hakimel/reveal.js/releases): Up-to-date version history.
+- [Examples](https://github.com/hakimel/reveal.js/wiki/Example-Presentations): Presentations created with reveal.js, add your own!
+- [Browser Support](https://github.com/hakimel/reveal.js/wiki/Browser-Support): Explanation of browser support and fallbacks.
+- [Plugins](https://github.com/hakimel/reveal.js/wiki/Plugins,-Tools-and-Hardware): A list of plugins that can be used to extend reveal.js.
+
+## Online Editor
+
+Presentations are written using HTML or Markdown but there's also an online editor for those of you who prefer a graphical interface. Give it a try at [https://slides.com](https://slides.com?ref=github).
+
+
+## Instructions
+
+### Markup
+
+Here's a barebones example of a fully working reveal.js presentation:
+```html
+
+
+
+
+
+
+
+
+
+
+
+```
+
+The presentation markup hierarchy needs to be `.reveal > .slides > section` where the `section` represents one slide and can be repeated indefinitely. If you place multiple `section` elements inside of another `section` they will be shown as vertical slides. The first of the vertical slides is the "root" of the others (at the top), and will be included in the horizontal sequence. For example:
+
+```html
+
+```
+
+### Markdown
+
+It's possible to write your slides using Markdown. To enable Markdown, add the `data-markdown` attribute to your `` elements and wrap the contents in a `
"+escape(e.message+"",true)+" "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}());
\ No newline at end of file
diff --git a/reveal.js/plugin/math/math.js b/reveal.js/plugin/math/math.js
new file mode 100755
index 0000000..e3b4089
--- /dev/null
+++ b/reveal.js/plugin/math/math.js
@@ -0,0 +1,67 @@
+/**
+ * A plugin which enables rendering of math equations inside
+ * of reveal.js slides. Essentially a thin wrapper for MathJax.
+ *
+ * @author Hakim El Hattab
+ */
+var RevealMath = window.RevealMath || (function(){
+
+ var options = Reveal.getConfig().math || {};
+ options.mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js';
+ options.config = options.config || 'TeX-AMS_HTML-full';
+
+ loadScript( options.mathjax + '?config=' + options.config, function() {
+
+ MathJax.Hub.Config({
+ messageStyle: 'none',
+ tex2jax: {
+ inlineMath: [['$','$'],['\\(','\\)']] ,
+ skipTags: ['script','noscript','style','textarea','pre']
+ },
+ skipStartupTypeset: true
+ });
+
+ // Typeset followed by an immediate reveal.js layout since
+ // the typesetting process could affect slide height
+ MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] );
+ MathJax.Hub.Queue( Reveal.layout );
+
+ // Reprocess equations in slides when they turn visible
+ Reveal.addEventListener( 'slidechanged', function( event ) {
+
+ MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] );
+
+ } );
+
+ } );
+
+ function loadScript( url, callback ) {
+
+ var head = document.querySelector( 'head' );
+ var script = document.createElement( 'script' );
+ script.type = 'text/javascript';
+ script.src = url;
+
+ // Wrapper for callback to make sure it only fires once
+ var finish = function() {
+ if( typeof callback === 'function' ) {
+ callback.call();
+ callback = null;
+ }
+ }
+
+ script.onload = finish;
+
+ // IE
+ script.onreadystatechange = function() {
+ if ( this.readyState === 'loaded' ) {
+ finish();
+ }
+ }
+
+ // Normal browsers
+ head.appendChild( script );
+
+ }
+
+})();
diff --git a/reveal.js/plugin/multiplex/client.js b/reveal.js/plugin/multiplex/client.js
new file mode 100755
index 0000000..3ffd1e0
--- /dev/null
+++ b/reveal.js/plugin/multiplex/client.js
@@ -0,0 +1,13 @@
+(function() {
+ var multiplex = Reveal.getConfig().multiplex;
+ var socketId = multiplex.id;
+ var socket = io.connect(multiplex.url);
+
+ socket.on(multiplex.id, function(data) {
+ // ignore data from sockets that aren't ours
+ if (data.socketId !== socketId) { return; }
+ if( window.location.host === 'localhost:1947' ) return;
+
+ Reveal.setState(data.state);
+ });
+}());
diff --git a/reveal.js/plugin/multiplex/index.js b/reveal.js/plugin/multiplex/index.js
new file mode 100755
index 0000000..8195f04
--- /dev/null
+++ b/reveal.js/plugin/multiplex/index.js
@@ -0,0 +1,64 @@
+var http = require('http');
+var express = require('express');
+var fs = require('fs');
+var io = require('socket.io');
+var crypto = require('crypto');
+
+var app = express();
+var staticDir = express.static;
+var server = http.createServer(app);
+
+io = io(server);
+
+var opts = {
+ port: process.env.PORT || 1948,
+ baseDir : __dirname + '/../../'
+};
+
+io.on( 'connection', function( socket ) {
+ socket.on('multiplex-statechanged', function(data) {
+ if (typeof data.secret == 'undefined' || data.secret == null || data.secret === '') return;
+ if (createHash(data.secret) === data.socketId) {
+ data.secret = null;
+ socket.broadcast.emit(data.socketId, data);
+ };
+ });
+});
+
+[ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) {
+ app.use('/' + dir, staticDir(opts.baseDir + dir));
+});
+
+app.get("/", function(req, res) {
+ res.writeHead(200, {'Content-Type': 'text/html'});
+
+ var stream = fs.createReadStream(opts.baseDir + '/index.html');
+ stream.on('error', function( error ) {
+ res.write('reveal.js multiplex server. Generate token ');
+ res.end();
+ });
+ stream.on('readable', function() {
+ stream.pipe(res);
+ });
+});
+
+app.get("/token", function(req,res) {
+ var ts = new Date().getTime();
+ var rand = Math.floor(Math.random()*9999999);
+ var secret = ts.toString() + rand.toString();
+ res.send({secret: secret, socketId: createHash(secret)});
+});
+
+var createHash = function(secret) {
+ var cipher = crypto.createCipher('blowfish', secret);
+ return(cipher.final('hex'));
+};
+
+// Actually listen
+server.listen( opts.port || null );
+
+var brown = '\033[33m',
+ green = '\033[32m',
+ reset = '\033[0m';
+
+console.log( brown + "reveal.js:" + reset + " Multiplex running on port " + green + opts.port + reset );
\ No newline at end of file
diff --git a/reveal.js/plugin/multiplex/master.js b/reveal.js/plugin/multiplex/master.js
new file mode 100755
index 0000000..4becad0
--- /dev/null
+++ b/reveal.js/plugin/multiplex/master.js
@@ -0,0 +1,31 @@
+(function() {
+
+ // Don't emit events from inside of notes windows
+ if ( window.location.search.match( /receiver/gi ) ) { return; }
+
+ var multiplex = Reveal.getConfig().multiplex;
+
+ var socket = io.connect( multiplex.url );
+
+ function post() {
+
+ var messageData = {
+ state: Reveal.getState(),
+ secret: multiplex.secret,
+ socketId: multiplex.id
+ };
+
+ socket.emit( 'multiplex-statechanged', messageData );
+
+ };
+
+ // Monitor events that trigger a change in state
+ Reveal.addEventListener( 'slidechanged', post );
+ Reveal.addEventListener( 'fragmentshown', post );
+ Reveal.addEventListener( 'fragmenthidden', post );
+ Reveal.addEventListener( 'overviewhidden', post );
+ Reveal.addEventListener( 'overviewshown', post );
+ Reveal.addEventListener( 'paused', post );
+ Reveal.addEventListener( 'resumed', post );
+
+}());
\ No newline at end of file
diff --git a/reveal.js/plugin/multiplex/package.json b/reveal.js/plugin/multiplex/package.json
new file mode 100755
index 0000000..368bfd6
--- /dev/null
+++ b/reveal.js/plugin/multiplex/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "reveal-js-multiplex",
+ "version": "1.0.0",
+ "description": "reveal.js multiplex server",
+ "homepage": "http://lab.hakim.se/reveal-js",
+ "scripts": {
+ "start": "node index.js"
+ },
+ "engines": {
+ "node": "~4.1.1"
+ },
+ "dependencies": {
+ "express": "~4.13.3",
+ "grunt-cli": "~0.1.13",
+ "mustache": "~2.2.1",
+ "socket.io": "~1.3.7"
+ },
+ "license": "MIT"
+}
diff --git a/reveal.js/plugin/notes-server/client.js b/reveal.js/plugin/notes-server/client.js
new file mode 100755
index 0000000..00b277b
--- /dev/null
+++ b/reveal.js/plugin/notes-server/client.js
@@ -0,0 +1,65 @@
+(function() {
+
+ // don't emit events from inside the previews themselves
+ if( window.location.search.match( /receiver/gi ) ) { return; }
+
+ var socket = io.connect( window.location.origin ),
+ socketId = Math.random().toString().slice( 2 );
+
+ console.log( 'View slide notes at ' + window.location.origin + '/notes/' + socketId );
+
+ window.open( window.location.origin + '/notes/' + socketId, 'notes-' + socketId );
+
+ /**
+ * Posts the current slide data to the notes window
+ */
+ function post() {
+
+ var slideElement = Reveal.getCurrentSlide(),
+ notesElement = slideElement.querySelector( 'aside.notes' );
+
+ var messageData = {
+ notes: '',
+ markdown: false,
+ socketId: socketId,
+ state: Reveal.getState()
+ };
+
+ // Look for notes defined in a slide attribute
+ if( slideElement.hasAttribute( 'data-notes' ) ) {
+ messageData.notes = slideElement.getAttribute( 'data-notes' );
+ }
+
+ // Look for notes defined in an aside element
+ if( notesElement ) {
+ messageData.notes = notesElement.innerHTML;
+ messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
+ }
+
+ socket.emit( 'statechanged', messageData );
+
+ }
+
+ // When a new notes window connects, post our current state
+ socket.on( 'new-subscriber', function( data ) {
+ post();
+ } );
+
+ // When the state changes from inside of the speaker view
+ socket.on( 'statechanged-speaker', function( data ) {
+ Reveal.setState( data.state );
+ } );
+
+ // Monitor events that trigger a change in state
+ Reveal.addEventListener( 'slidechanged', post );
+ Reveal.addEventListener( 'fragmentshown', post );
+ Reveal.addEventListener( 'fragmenthidden', post );
+ Reveal.addEventListener( 'overviewhidden', post );
+ Reveal.addEventListener( 'overviewshown', post );
+ Reveal.addEventListener( 'paused', post );
+ Reveal.addEventListener( 'resumed', post );
+
+ // Post the initial state
+ post();
+
+}());
diff --git a/reveal.js/plugin/notes-server/index.js b/reveal.js/plugin/notes-server/index.js
new file mode 100755
index 0000000..b95f071
--- /dev/null
+++ b/reveal.js/plugin/notes-server/index.js
@@ -0,0 +1,69 @@
+var http = require('http');
+var express = require('express');
+var fs = require('fs');
+var io = require('socket.io');
+var Mustache = require('mustache');
+
+var app = express();
+var staticDir = express.static;
+var server = http.createServer(app);
+
+io = io(server);
+
+var opts = {
+ port : 1947,
+ baseDir : __dirname + '/../../'
+};
+
+io.on( 'connection', function( socket ) {
+
+ socket.on( 'new-subscriber', function( data ) {
+ socket.broadcast.emit( 'new-subscriber', data );
+ });
+
+ socket.on( 'statechanged', function( data ) {
+ delete data.state.overview;
+ socket.broadcast.emit( 'statechanged', data );
+ });
+
+ socket.on( 'statechanged-speaker', function( data ) {
+ delete data.state.overview;
+ socket.broadcast.emit( 'statechanged-speaker', data );
+ });
+
+});
+
+[ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) {
+ app.use( '/' + dir, staticDir( opts.baseDir + dir ) );
+});
+
+app.get('/', function( req, res ) {
+
+ res.writeHead( 200, { 'Content-Type': 'text/html' } );
+ fs.createReadStream( opts.baseDir + '/index.html' ).pipe( res );
+
+});
+
+app.get( '/notes/:socketId', function( req, res ) {
+
+ fs.readFile( opts.baseDir + 'plugin/notes-server/notes.html', function( err, data ) {
+ res.send( Mustache.to_html( data.toString(), {
+ socketId : req.params.socketId
+ }));
+ });
+
+});
+
+// Actually listen
+server.listen( opts.port || null );
+
+var brown = '\033[33m',
+ green = '\033[32m',
+ reset = '\033[0m';
+
+var slidesLocation = 'http://localhost/' + ( opts.port ? ( ':' + opts.port ) : '' );
+
+console.log( brown + 'reveal.js - Speaker Notes' + reset );
+console.log( '1. Open the slides at ' + green + slidesLocation + reset );
+console.log( '2. Click on the link in your JS console to go to the notes page' );
+console.log( '3. Advance through your slides and your notes will advance automatically' );
diff --git a/reveal.js/plugin/notes-server/notes.html b/reveal.js/plugin/notes-server/notes.html
new file mode 100755
index 0000000..ab8c5b1
--- /dev/null
+++ b/reveal.js/plugin/notes-server/notes.html
@@ -0,0 +1,585 @@
+
+
+
+
+
+ reveal.js - Slide Notes
+
+
+
+
+
+
+
+ Upcoming
+
+
+
Time Click to Reset
+
+ 0:00 AM
+
+
+ 00 :00 :00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/plugin/notes/notes.html b/reveal.js/plugin/notes/notes.html
new file mode 100755
index 0000000..e368a5f
--- /dev/null
+++ b/reveal.js/plugin/notes/notes.html
@@ -0,0 +1,746 @@
+
+
+
+
+
+ reveal.js - Slide Notes
+
+
+
+
+
+
+
+ Upcoming
+
+
+
Time Click to Reset
+
+ 0:00 AM
+
+
+ 00 :00 :00
+
+
+
+
Pacing – Time to finish current slide
+
+ 00 :00 :00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/plugin/notes/notes.js b/reveal.js/plugin/notes/notes.js
new file mode 100755
index 0000000..80fb6e2
--- /dev/null
+++ b/reveal.js/plugin/notes/notes.js
@@ -0,0 +1,155 @@
+/**
+ * Handles opening of and synchronization with the reveal.js
+ * notes window.
+ *
+ * Handshake process:
+ * 1. This window posts 'connect' to notes window
+ * - Includes URL of presentation to show
+ * 2. Notes window responds with 'connected' when it is available
+ * 3. This window proceeds to send the current presentation state
+ * to the notes window
+ */
+var RevealNotes = (function() {
+
+ function openNotes( notesFilePath ) {
+
+ if( !notesFilePath ) {
+ var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path
+ jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path
+ notesFilePath = jsFileLocation + 'notes.html';
+ }
+
+ var notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' );
+
+ // Allow popup window access to Reveal API
+ notesPopup.Reveal = this.Reveal;
+
+ /**
+ * Connect to the notes window through a postmessage handshake.
+ * Using postmessage enables us to work in situations where the
+ * origins differ, such as a presentation being opened from the
+ * file system.
+ */
+ function connect() {
+ // Keep trying to connect until we get a 'connected' message back
+ var connectInterval = setInterval( function() {
+ notesPopup.postMessage( JSON.stringify( {
+ namespace: 'reveal-notes',
+ type: 'connect',
+ url: window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search,
+ state: Reveal.getState()
+ } ), '*' );
+ }, 500 );
+
+ window.addEventListener( 'message', function( event ) {
+ var data = JSON.parse( event.data );
+ if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
+ clearInterval( connectInterval );
+ onConnected();
+ }
+ } );
+ }
+
+ /**
+ * Posts the current slide data to the notes window
+ */
+ function post( event ) {
+
+ var slideElement = Reveal.getCurrentSlide(),
+ notesElement = slideElement.querySelector( 'aside.notes' ),
+ fragmentElement = slideElement.querySelector( '.current-fragment' );
+
+ var messageData = {
+ namespace: 'reveal-notes',
+ type: 'state',
+ notes: '',
+ markdown: false,
+ whitespace: 'normal',
+ state: Reveal.getState()
+ };
+
+ // Look for notes defined in a slide attribute
+ if( slideElement.hasAttribute( 'data-notes' ) ) {
+ messageData.notes = slideElement.getAttribute( 'data-notes' );
+ messageData.whitespace = 'pre-wrap';
+ }
+
+ // Look for notes defined in a fragment
+ if( fragmentElement ) {
+ var fragmentNotes = fragmentElement.querySelector( 'aside.notes' );
+ if( fragmentNotes ) {
+ notesElement = fragmentNotes;
+ }
+ else if( fragmentElement.hasAttribute( 'data-notes' ) ) {
+ messageData.notes = fragmentElement.getAttribute( 'data-notes' );
+ messageData.whitespace = 'pre-wrap';
+
+ // In case there are slide notes
+ notesElement = null;
+ }
+ }
+
+ // Look for notes defined in an aside element
+ if( notesElement ) {
+ messageData.notes = notesElement.innerHTML;
+ messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
+ }
+
+ notesPopup.postMessage( JSON.stringify( messageData ), '*' );
+
+ }
+
+ /**
+ * Called once we have established a connection to the notes
+ * window.
+ */
+ function onConnected() {
+
+ // Monitor events that trigger a change in state
+ Reveal.addEventListener( 'slidechanged', post );
+ Reveal.addEventListener( 'fragmentshown', post );
+ Reveal.addEventListener( 'fragmenthidden', post );
+ Reveal.addEventListener( 'overviewhidden', post );
+ Reveal.addEventListener( 'overviewshown', post );
+ Reveal.addEventListener( 'paused', post );
+ Reveal.addEventListener( 'resumed', post );
+
+ // Post the initial state
+ post();
+
+ }
+
+ connect();
+
+ }
+
+ if( !/receiver/i.test( window.location.search ) ) {
+
+ // If the there's a 'notes' query set, open directly
+ if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
+ openNotes();
+ }
+
+ // Open the notes when the 's' key is hit
+ document.addEventListener( 'keydown', function( event ) {
+ // Disregard the event if the target is editable or a
+ // modifier is present
+ if ( document.querySelector( ':focus' ) !== null || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
+
+ // Disregard the event if keyboard is disabled
+ if ( Reveal.getConfig().keyboard === false ) return;
+
+ if( event.keyCode === 83 ) {
+ event.preventDefault();
+ openNotes();
+ }
+ }, false );
+
+ // Show our keyboard shortcut in the reveal.js help overlay
+ if( window.Reveal ) Reveal.registerKeyboardShortcut( 'S', 'Speaker notes view' );
+
+ }
+
+ return { open: openNotes };
+
+})();
diff --git a/reveal.js/plugin/print-pdf/print-pdf.js b/reveal.js/plugin/print-pdf/print-pdf.js
new file mode 100755
index 0000000..9ffc261
--- /dev/null
+++ b/reveal.js/plugin/print-pdf/print-pdf.js
@@ -0,0 +1,69 @@
+/**
+ * phantomjs script for printing presentations to PDF.
+ *
+ * Example:
+ * phantomjs print-pdf.js "http://lab.hakim.se/reveal-js?print-pdf" reveal-demo.pdf
+ *
+ * @author Manuel Bieh (https://github.com/manuelbieh)
+ * @author Hakim El Hattab (https://github.com/hakimel)
+ * @author Manuel Riezebosch (https://github.com/riezebosch)
+ */
+
+// html2pdf.js
+var system = require( 'system' );
+
+var probePage = new WebPage();
+var printPage = new WebPage();
+
+var inputFile = system.args[1] || 'index.html?print-pdf';
+var outputFile = system.args[2] || 'slides.pdf';
+
+if( outputFile.match( /\.pdf$/gi ) === null ) {
+ outputFile += '.pdf';
+}
+
+console.log( 'Export PDF: Reading reveal.js config [1/4]' );
+
+probePage.open( inputFile, function( status ) {
+
+ console.log( 'Export PDF: Preparing print layout [2/4]' );
+
+ var config = probePage.evaluate( function() {
+ return Reveal.getConfig();
+ } );
+
+ if( config ) {
+
+ printPage.paperSize = {
+ width: Math.floor( config.width * ( 1 + config.margin ) ),
+ height: Math.floor( config.height * ( 1 + config.margin ) ),
+ border: 0
+ };
+
+ printPage.open( inputFile, function( status ) {
+ console.log( 'Export PDF: Preparing pdf [3/4]')
+ printPage.evaluate(function() {
+ Reveal.isReady() ? window.callPhantom() : Reveal.addEventListener( 'pdf-ready', window.callPhantom );
+ });
+ } );
+
+ printPage.onCallback = function(data) {
+ // For some reason we need to "jump the queue" for syntax highlighting to work.
+ // See: http://stackoverflow.com/a/3580132/129269
+ setTimeout(function() {
+ console.log( 'Export PDF: Writing file [4/4]' );
+ printPage.render( outputFile );
+ console.log( 'Export PDF: Finished successfully!' );
+ phantom.exit();
+ }, 0);
+ };
+ }
+ else {
+
+ console.log( 'Export PDF: Unable to read reveal.js config. Make sure the input address points to a reveal.js page.' );
+ phantom.exit(1);
+
+ }
+} );
+
+
diff --git a/reveal.js/plugin/search/search.js b/reveal.js/plugin/search/search.js
new file mode 100755
index 0000000..ae6582e
--- /dev/null
+++ b/reveal.js/plugin/search/search.js
@@ -0,0 +1,196 @@
+/*
+ * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
+ * by navigatating to that slide and highlighting it.
+ *
+ * By Jon Snyder , February 2013
+ */
+
+var RevealSearch = (function() {
+
+ var matchedSlides;
+ var currentMatchedIndex;
+ var searchboxDirty;
+ var myHilitor;
+
+// Original JavaScript code by Chirp Internet: www.chirp.com.au
+// Please acknowledge use of this code by including this header.
+// 2/2013 jon: modified regex to display any match, not restricted to word boundaries.
+
+function Hilitor(id, tag)
+{
+
+ var targetNode = document.getElementById(id) || document.body;
+ var hiliteTag = tag || "EM";
+ var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM|SPAN)$");
+ var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"];
+ var wordColor = [];
+ var colorIdx = 0;
+ var matchRegex = "";
+ var matchingSlides = [];
+
+ this.setRegex = function(input)
+ {
+ input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
+ matchRegex = new RegExp("(" + input + ")","i");
+ }
+
+ this.getRegex = function()
+ {
+ return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " ");
+ }
+
+ // recursively apply word highlighting
+ this.hiliteWords = function(node)
+ {
+ if(node == undefined || !node) return;
+ if(!matchRegex) return;
+ if(skipTags.test(node.nodeName)) return;
+
+ if(node.hasChildNodes()) {
+ for(var i=0; i < node.childNodes.length; i++)
+ this.hiliteWords(node.childNodes[i]);
+ }
+ if(node.nodeType == 3) { // NODE_TEXT
+ if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) {
+ //find the slide's section element and save it in our list of matching slides
+ var secnode = node.parentNode;
+ while (secnode.nodeName != 'SECTION') {
+ secnode = secnode.parentNode;
+ }
+
+ var slideIndex = Reveal.getIndices(secnode);
+ var slidelen = matchingSlides.length;
+ var alreadyAdded = false;
+ for (var i=0; i < slidelen; i++) {
+ if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) {
+ alreadyAdded = true;
+ }
+ }
+ if (! alreadyAdded) {
+ matchingSlides.push(slideIndex);
+ }
+
+ if(!wordColor[regs[0].toLowerCase()]) {
+ wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length];
+ }
+
+ var match = document.createElement(hiliteTag);
+ match.appendChild(document.createTextNode(regs[0]));
+ match.style.backgroundColor = wordColor[regs[0].toLowerCase()];
+ match.style.fontStyle = "inherit";
+ match.style.color = "#000";
+
+ var after = node.splitText(regs.index);
+ after.nodeValue = after.nodeValue.substring(regs[0].length);
+ node.parentNode.insertBefore(match, after);
+ }
+ }
+ };
+
+ // remove highlighting
+ this.remove = function()
+ {
+ var arr = document.getElementsByTagName(hiliteTag);
+ while(arr.length && (el = arr[0])) {
+ el.parentNode.replaceChild(el.firstChild, el);
+ }
+ };
+
+ // start highlighting at target node
+ this.apply = function(input)
+ {
+ if(input == undefined || !input) return;
+ this.remove();
+ this.setRegex(input);
+ this.hiliteWords(targetNode);
+ return matchingSlides;
+ };
+
+}
+
+ function openSearch() {
+ //ensure the search term input dialog is visible and has focus:
+ var inputbox = document.getElementById("searchinput");
+ inputbox.style.display = "inline";
+ inputbox.focus();
+ inputbox.select();
+ }
+
+ function toggleSearch() {
+ var inputbox = document.getElementById("searchinput");
+ if (inputbox.style.display !== "inline") {
+ openSearch();
+ }
+ else {
+ inputbox.style.display = "none";
+ myHilitor.remove();
+ }
+ }
+
+ function doSearch() {
+ //if there's been a change in the search term, perform a new search:
+ if (searchboxDirty) {
+ var searchstring = document.getElementById("searchinput").value;
+
+ //find the keyword amongst the slides
+ myHilitor = new Hilitor("slidecontent");
+ matchedSlides = myHilitor.apply(searchstring);
+ currentMatchedIndex = 0;
+ }
+
+ //navigate to the next slide that has the keyword, wrapping to the first if necessary
+ if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) {
+ currentMatchedIndex = 0;
+ }
+ if (matchedSlides.length > currentMatchedIndex) {
+ Reveal.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v);
+ currentMatchedIndex++;
+ }
+ }
+
+ var dom = {};
+ dom.wrapper = document.querySelector( '.reveal' );
+
+ if( !dom.wrapper.querySelector( '.searchbox' ) ) {
+ var searchElement = document.createElement( 'div' );
+ searchElement.id = "searchinputdiv";
+ searchElement.classList.add( 'searchdiv' );
+ searchElement.style.position = 'absolute';
+ searchElement.style.top = '10px';
+ searchElement.style.left = '10px';
+ //embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/:
+ searchElement.innerHTML = ' ';
+ dom.wrapper.appendChild( searchElement );
+ }
+
+ document.getElementById("searchbutton").addEventListener( 'click', function(event) {
+ doSearch();
+ }, false );
+
+ document.getElementById("searchinput").addEventListener( 'keyup', function( event ) {
+ switch (event.keyCode) {
+ case 13:
+ event.preventDefault();
+ doSearch();
+ searchboxDirty = false;
+ break;
+ default:
+ searchboxDirty = true;
+ }
+ }, false );
+
+ // Open the search when the 's' key is hit (yes, this conflicts with the notes plugin, disabling for now)
+ /*
+ document.addEventListener( 'keydown', function( event ) {
+ // Disregard the event if the target is editable or a
+ // modifier is present
+ if ( document.querySelector( ':focus' ) !== null || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
+
+ if( event.keyCode === 83 ) {
+ event.preventDefault();
+ openSearch();
+ }
+ }, false );
+*/
+ return { open: openSearch };
+})();
diff --git a/reveal.js/plugin/zoom-js/zoom.js b/reveal.js/plugin/zoom-js/zoom.js
new file mode 100755
index 0000000..8738083
--- /dev/null
+++ b/reveal.js/plugin/zoom-js/zoom.js
@@ -0,0 +1,288 @@
+// Custom reveal.js integration
+(function(){
+ var isEnabled = true;
+
+ document.querySelector( '.reveal .slides' ).addEventListener( 'mousedown', function( event ) {
+ var modifier = ( Reveal.getConfig().zoomKey ? Reveal.getConfig().zoomKey : 'alt' ) + 'Key';
+
+ var zoomPadding = 20;
+ var revealScale = Reveal.getScale();
+
+ if( event[ modifier ] && isEnabled ) {
+ event.preventDefault();
+
+ var bounds;
+ var originalDisplay = event.target.style.display;
+
+ // Get the bounding rect of the contents, not the containing box
+ if( window.getComputedStyle( event.target ).display === 'block' ) {
+ event.target.style.display = 'inline-block';
+ bounds = event.target.getBoundingClientRect();
+ event.target.style.display = originalDisplay;
+ } else {
+ bounds = event.target.getBoundingClientRect();
+ }
+
+ zoom.to({
+ x: ( bounds.left * revealScale ) - zoomPadding,
+ y: ( bounds.top * revealScale ) - zoomPadding,
+ width: ( bounds.width * revealScale ) + ( zoomPadding * 2 ),
+ height: ( bounds.height * revealScale ) + ( zoomPadding * 2 ),
+ pan: false
+ });
+ }
+ } );
+
+ Reveal.addEventListener( 'overviewshown', function() { isEnabled = false; } );
+ Reveal.addEventListener( 'overviewhidden', function() { isEnabled = true; } );
+})();
+
+/*!
+ * zoom.js 0.3 (modified for use with reveal.js)
+ * http://lab.hakim.se/zoom-js
+ * MIT licensed
+ *
+ * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
+ */
+var zoom = (function(){
+
+ // The current zoom level (scale)
+ var level = 1;
+
+ // The current mouse position, used for panning
+ var mouseX = 0,
+ mouseY = 0;
+
+ // Timeout before pan is activated
+ var panEngageTimeout = -1,
+ panUpdateInterval = -1;
+
+ // Check for transform support so that we can fallback otherwise
+ var supportsTransforms = 'WebkitTransform' in document.body.style ||
+ 'MozTransform' in document.body.style ||
+ 'msTransform' in document.body.style ||
+ 'OTransform' in document.body.style ||
+ 'transform' in document.body.style;
+
+ if( supportsTransforms ) {
+ // The easing that will be applied when we zoom in/out
+ document.body.style.transition = 'transform 0.8s ease';
+ document.body.style.OTransition = '-o-transform 0.8s ease';
+ document.body.style.msTransition = '-ms-transform 0.8s ease';
+ document.body.style.MozTransition = '-moz-transform 0.8s ease';
+ document.body.style.WebkitTransition = '-webkit-transform 0.8s ease';
+ }
+
+ // Zoom out if the user hits escape
+ document.addEventListener( 'keyup', function( event ) {
+ if( level !== 1 && event.keyCode === 27 ) {
+ zoom.out();
+ }
+ } );
+
+ // Monitor mouse movement for panning
+ document.addEventListener( 'mousemove', function( event ) {
+ if( level !== 1 ) {
+ mouseX = event.clientX;
+ mouseY = event.clientY;
+ }
+ } );
+
+ /**
+ * Applies the CSS required to zoom in, prefers the use of CSS3
+ * transforms but falls back on zoom for IE.
+ *
+ * @param {Object} rect
+ * @param {Number} scale
+ */
+ function magnify( rect, scale ) {
+
+ var scrollOffset = getScrollOffset();
+
+ // Ensure a width/height is set
+ rect.width = rect.width || 1;
+ rect.height = rect.height || 1;
+
+ // Center the rect within the zoomed viewport
+ rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
+ rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;
+
+ if( supportsTransforms ) {
+ // Reset
+ if( scale === 1 ) {
+ document.body.style.transform = '';
+ document.body.style.OTransform = '';
+ document.body.style.msTransform = '';
+ document.body.style.MozTransform = '';
+ document.body.style.WebkitTransform = '';
+ }
+ // Scale
+ else {
+ var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
+ transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';
+
+ document.body.style.transformOrigin = origin;
+ document.body.style.OTransformOrigin = origin;
+ document.body.style.msTransformOrigin = origin;
+ document.body.style.MozTransformOrigin = origin;
+ document.body.style.WebkitTransformOrigin = origin;
+
+ document.body.style.transform = transform;
+ document.body.style.OTransform = transform;
+ document.body.style.msTransform = transform;
+ document.body.style.MozTransform = transform;
+ document.body.style.WebkitTransform = transform;
+ }
+ }
+ else {
+ // Reset
+ if( scale === 1 ) {
+ document.body.style.position = '';
+ document.body.style.left = '';
+ document.body.style.top = '';
+ document.body.style.width = '';
+ document.body.style.height = '';
+ document.body.style.zoom = '';
+ }
+ // Scale
+ else {
+ document.body.style.position = 'relative';
+ document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
+ document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
+ document.body.style.width = ( scale * 100 ) + '%';
+ document.body.style.height = ( scale * 100 ) + '%';
+ document.body.style.zoom = scale;
+ }
+ }
+
+ level = scale;
+
+ if( document.documentElement.classList ) {
+ if( level !== 1 ) {
+ document.documentElement.classList.add( 'zoomed' );
+ }
+ else {
+ document.documentElement.classList.remove( 'zoomed' );
+ }
+ }
+ }
+
+ /**
+ * Pan the document when the mosue cursor approaches the edges
+ * of the window.
+ */
+ function pan() {
+ var range = 0.12,
+ rangeX = window.innerWidth * range,
+ rangeY = window.innerHeight * range,
+ scrollOffset = getScrollOffset();
+
+ // Up
+ if( mouseY < rangeY ) {
+ window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
+ }
+ // Down
+ else if( mouseY > window.innerHeight - rangeY ) {
+ window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
+ }
+
+ // Left
+ if( mouseX < rangeX ) {
+ window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
+ }
+ // Right
+ else if( mouseX > window.innerWidth - rangeX ) {
+ window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
+ }
+ }
+
+ function getScrollOffset() {
+ return {
+ x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
+ y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
+ }
+ }
+
+ return {
+ /**
+ * Zooms in on either a rectangle or HTML element.
+ *
+ * @param {Object} options
+ * - element: HTML element to zoom in on
+ * OR
+ * - x/y: coordinates in non-transformed space to zoom in on
+ * - width/height: the portion of the screen to zoom in on
+ * - scale: can be used instead of width/height to explicitly set scale
+ */
+ to: function( options ) {
+
+ // Due to an implementation limitation we can't zoom in
+ // to another element without zooming out first
+ if( level !== 1 ) {
+ zoom.out();
+ }
+ else {
+ options.x = options.x || 0;
+ options.y = options.y || 0;
+
+ // If an element is set, that takes precedence
+ if( !!options.element ) {
+ // Space around the zoomed in element to leave on screen
+ var padding = 20;
+ var bounds = options.element.getBoundingClientRect();
+
+ options.x = bounds.left - padding;
+ options.y = bounds.top - padding;
+ options.width = bounds.width + ( padding * 2 );
+ options.height = bounds.height + ( padding * 2 );
+ }
+
+ // If width/height values are set, calculate scale from those values
+ if( options.width !== undefined && options.height !== undefined ) {
+ options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
+ }
+
+ if( options.scale > 1 ) {
+ options.x *= options.scale;
+ options.y *= options.scale;
+
+ magnify( options, options.scale );
+
+ if( options.pan !== false ) {
+
+ // Wait with engaging panning as it may conflict with the
+ // zoom transition
+ panEngageTimeout = setTimeout( function() {
+ panUpdateInterval = setInterval( pan, 1000 / 60 );
+ }, 800 );
+
+ }
+ }
+ }
+ },
+
+ /**
+ * Resets the document zoom state to its default.
+ */
+ out: function() {
+ clearTimeout( panEngageTimeout );
+ clearInterval( panUpdateInterval );
+
+ magnify( { x: 0, y: 0 }, 1 );
+
+ level = 1;
+ },
+
+ // Alias
+ magnify: function( options ) { this.to( options ) },
+ reset: function() { this.out() },
+
+ zoomLevel: function() {
+ return level;
+ }
+ }
+
+})();
+
+
+
diff --git a/reveal.js/reveal.js/css/theme/custom.css b/reveal.js/reveal.js/css/theme/custom.css
new file mode 100644
index 0000000..6f0770a
--- /dev/null
+++ b/reveal.js/reveal.js/css/theme/custom.css
@@ -0,0 +1,23 @@
+/*
+Errno::ENOENT: No such file or directory @ rb_sysopen - custom.scss
+
+Backtrace:
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/plugin/compiler.rb:454:in `read'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/plugin/compiler.rb:454:in `update_stylesheet'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/plugin/compiler.rb:215:in `block in update_stylesheets'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/plugin/compiler.rb:209:in `each'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/plugin/compiler.rb:209:in `update_stylesheets'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/plugin/compiler.rb:294:in `watch'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/plugin.rb:109:in `method_missing'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/exec/sass_scss.rb:360:in `watch_or_update'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/exec/sass_scss.rb:51:in `process_result'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/exec/base.rb:52:in `parse'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/lib/sass/exec/base.rb:19:in `parse!'
+/Users/tania/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/sass-3.5.1/bin/sass:13:in `'
+/Users/tania/.rbenv/versions/2.4.1/bin/sass:22:in `load'
+/Users/tania/.rbenv/versions/2.4.1/bin/sass:22:in `'
+*/
+body:before {
+ white-space: pre;
+ font-family: monospace;
+ content: "Errno::ENOENT: No such file or directory @ rb_sysopen - custom.scss"; }
diff --git a/reveal.js/test/examples/assets/image1.png b/reveal.js/test/examples/assets/image1.png
new file mode 100755
index 0000000..8747594
Binary files /dev/null and b/reveal.js/test/examples/assets/image1.png differ
diff --git a/reveal.js/test/examples/assets/image2.png b/reveal.js/test/examples/assets/image2.png
new file mode 100755
index 0000000..6c403a0
Binary files /dev/null and b/reveal.js/test/examples/assets/image2.png differ
diff --git a/reveal.js/test/examples/barebones.html b/reveal.js/test/examples/barebones.html
new file mode 100755
index 0000000..2bee3cb
--- /dev/null
+++ b/reveal.js/test/examples/barebones.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+ reveal.js - Barebones
+
+
+
+
+
+
+
+
+
+
+
+ Barebones Presentation
+ This example contains the bare minimum includes and markup required to run a reveal.js presentation.
+
+
+
+ No Theme
+ There's no theme included, so it will fall back on browser defaults.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/examples/embedded-media.html b/reveal.js/test/examples/embedded-media.html
new file mode 100755
index 0000000..bbad4be
--- /dev/null
+++ b/reveal.js/test/examples/embedded-media.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ reveal.js - Embedded Media
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/examples/math.html b/reveal.js/test/examples/math.html
new file mode 100755
index 0000000..d35e827
--- /dev/null
+++ b/reveal.js/test/examples/math.html
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+ reveal.js - Math Plugin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ reveal.js Math Plugin
+ A thin wrapper for MathJax
+
+
+
+ The Lorenz Equations
+
+ \[\begin{aligned}
+ \dot{x} & = \sigma(y-x) \\
+ \dot{y} & = \rho x - y - xz \\
+ \dot{z} & = -\beta z + xy
+ \end{aligned} \]
+
+
+
+ The Cauchy-Schwarz Inequality
+
+
+
+
+
+ A Cross Product Formula
+
+ \[\mathbf{V}_1 \times \mathbf{V}_2 = \begin{vmatrix}
+ \mathbf{i} & \mathbf{j} & \mathbf{k} \\
+ \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\
+ \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0
+ \end{vmatrix} \]
+
+
+
+ The probability of getting \(k\) heads when flipping \(n\) coins is
+
+ \[P(E) = {n \choose k} p^k (1-p)^{ n-k} \]
+
+
+
+ An Identity of Ramanujan
+
+ \[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} =
+ 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
+ {1+\frac{e^{-8\pi}} {1+\ldots} } } } \]
+
+
+
+ A Rogers-Ramanujan Identity
+
+ \[ 1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots =
+ \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})}\]
+
+
+
+ Maxwell’s Equations
+
+ \[ \begin{aligned}
+ \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\ \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
+ \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
+ \nabla \cdot \vec{\mathbf{B}} & = 0 \end{aligned}
+ \]
+
+
+
+
+ The Lorenz Equations
+
+
+ \[\begin{aligned}
+ \dot{x} & = \sigma(y-x) \\
+ \dot{y} & = \rho x - y - xz \\
+ \dot{z} & = -\beta z + xy
+ \end{aligned} \]
+
+
+
+
+ The Cauchy-Schwarz Inequality
+
+
+ \[ \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \]
+
+
+
+
+ A Cross Product Formula
+
+
+ \[\mathbf{V}_1 \times \mathbf{V}_2 = \begin{vmatrix}
+ \mathbf{i} & \mathbf{j} & \mathbf{k} \\
+ \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\
+ \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0
+ \end{vmatrix} \]
+
+
+
+
+ The probability of getting \(k\) heads when flipping \(n\) coins is
+
+
+ \[P(E) = {n \choose k} p^k (1-p)^{ n-k} \]
+
+
+
+
+ An Identity of Ramanujan
+
+
+ \[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} =
+ 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
+ {1+\frac{e^{-8\pi}} {1+\ldots} } } } \]
+
+
+
+
+ A Rogers-Ramanujan Identity
+
+
+ \[ 1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots =
+ \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})}\]
+
+
+
+
+ Maxwell’s Equations
+
+
+ \[ \begin{aligned}
+ \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\ \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
+ \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
+ \nabla \cdot \vec{\mathbf{B}} & = 0 \end{aligned}
+ \]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/examples/slide-backgrounds.html b/reveal.js/test/examples/slide-backgrounds.html
new file mode 100755
index 0000000..316c92a
--- /dev/null
+++ b/reveal.js/test/examples/slide-backgrounds.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+ reveal.js - Slide Backgrounds
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ data-background: #00ffff
+
+
+
+ data-background: #bb00bb
+
+
+
+ data-background: lightblue
+
+
+
+
+ data-background: #ff0000
+
+
+ data-background: rgba(0, 0, 0, 0.2)
+
+
+ data-background: salmon
+
+
+
+
+
+ Background applied to stack
+
+
+ Background applied to stack
+
+
+ Background applied to slide inside of stack
+
+
+
+
+
+
+
+
+ Background image
+ data-background-size="100px" data-background-repeat="repeat" data-background-color="#111"
+
+
+
+ Same background twice (1/2)
+
+
+ Same background twice (2/2)
+
+
+
+
+
+
+
+
+ Same background twice vertical (1/2)
+
+
+ Same background twice vertical (2/2)
+
+
+
+
+ Same background from horizontal to vertical (1/3)
+
+
+
+ Same background from horizontal to vertical (2/3)
+
+
+ Same background from horizontal to vertical (3/3)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/examples/slide-transitions.html b/reveal.js/test/examples/slide-transitions.html
new file mode 100755
index 0000000..88119dc
--- /dev/null
+++ b/reveal.js/test/examples/slide-transitions.html
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+ reveal.js - Slide Transitions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ data-transition: zoom
+
+
+
+ data-transition: zoom-in fade-out
+
+
+
+
+
+ data-transition: convex
+
+
+
+ data-transition: convex-in concave-out
+
+
+
+
+
+ data-transition: concave
+
+
+ data-transition: convex-in fade-out
+
+
+
+
+
+ data-transition: none
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/qunit-1.12.0.css b/reveal.js/test/qunit-1.12.0.css
new file mode 100755
index 0000000..00ac1d3
--- /dev/null
+++ b/reveal.js/test/qunit-1.12.0.css
@@ -0,0 +1,244 @@
+/**
+ * QUnit v1.12.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699a4;
+ background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ -webkit-border-top-right-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
+}
+
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+ padding: 0 .5em 0 .1em;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+ overflow: hidden;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2b81af;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+#qunit-modulefilter-container {
+ float: right;
+}
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #c2ccd1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests li .runtime {
+ float: right;
+ font-size: smaller;
+}
+
+.qunit-assert-list {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #fff;
+
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: black; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ padding: 5px;
+ background-color: #fff;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #3c510c;
+ background-color: #fff;
+ border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #fff;
+ border-left: 10px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+#qunit-testresult .module-name {
+ font-weight: bold;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
\ No newline at end of file
diff --git a/reveal.js/test/qunit-1.12.0.js b/reveal.js/test/qunit-1.12.0.js
new file mode 100755
index 0000000..61af483
--- /dev/null
+++ b/reveal.js/test/qunit-1.12.0.js
@@ -0,0 +1,2212 @@
+/**
+ * QUnit v1.12.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2013 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * https://jquery.org/license/
+ */
+
+(function( window ) {
+
+var QUnit,
+ assert,
+ config,
+ onErrorFnPrev,
+ testId = 0,
+ fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ // Keep a local reference to Date (GH-283)
+ Date = window.Date,
+ setTimeout = window.setTimeout,
+ defined = {
+ setTimeout: typeof window.setTimeout !== "undefined",
+ sessionStorage: (function() {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch( e ) {
+ return false;
+ }
+ }())
+ },
+ /**
+ * Provides a normalized error string, correcting an issue
+ * with IE 7 (and prior) where Error.prototype.toString is
+ * not properly implemented
+ *
+ * Based on http://es5.github.com/#x15.11.4.4
+ *
+ * @param {String|Error} error
+ * @return {String} error message
+ */
+ errorString = function( error ) {
+ var name, message,
+ errorString = error.toString();
+ if ( errorString.substring( 0, 7 ) === "[object" ) {
+ name = error.name ? error.name.toString() : "Error";
+ message = error.message ? error.message.toString() : "";
+ if ( name && message ) {
+ return name + ": " + message;
+ } else if ( name ) {
+ return name;
+ } else if ( message ) {
+ return message;
+ } else {
+ return "Error";
+ }
+ } else {
+ return errorString;
+ }
+ },
+ /**
+ * Makes a clone of an object using only Array or Object as base,
+ * and copies over the own enumerable properties.
+ *
+ * @param {Object} obj
+ * @return {Object} New object with only the own properties (recursively).
+ */
+ objectValues = function( obj ) {
+ // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
+ /*jshint newcap: false */
+ var key, val,
+ vals = QUnit.is( "array", obj ) ? [] : {};
+ for ( key in obj ) {
+ if ( hasOwn.call( obj, key ) ) {
+ val = obj[key];
+ vals[key] = val === Object(val) ? objectValues(val) : val;
+ }
+ }
+ return vals;
+ };
+
+function Test( settings ) {
+ extend( this, settings );
+ this.assertions = [];
+ this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+ init: function() {
+ var a, b, li,
+ tests = id( "qunit-tests" );
+
+ if ( tests ) {
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml;
+
+ // `a` initialized at top of scope
+ a = document.createElement( "a" );
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url(https://github.com/%7B%20testNumber:%20this.testNumber%20%7D);
+
+ li = document.createElement( "li" );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.className = "running";
+ li.id = this.id = "qunit-test-output" + testId++;
+
+ tests.appendChild( li );
+ }
+ },
+ setup: function() {
+ if (
+ // Emit moduleStart when we're switching from one module to another
+ this.module !== config.previousModule ||
+ // They could be equal (both undefined) but if the previousModule property doesn't
+ // yet exist it means this is the first test in a suite that isn't wrapped in a
+ // module, in which case we'll just emit a moduleStart event for 'undefined'.
+ // Without this, reporters can get testStart before moduleStart which is a problem.
+ !hasOwn.call( config, "previousModule" )
+ ) {
+ if ( hasOwn.call( config, "previousModule" ) ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0 };
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend({
+ setup: function() {},
+ teardown: function() {}
+ }, this.moduleTestEnvironment );
+
+ this.started = +new Date();
+ runLoggingCallbacks( "testStart", QUnit, {
+ name: this.testName,
+ module: this.module
+ });
+
+ /*jshint camelcase:false */
+
+
+ /**
+ * Expose the current test environment.
+ *
+ * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
+ */
+ QUnit.current_testEnvironment = this.testEnvironment;
+
+ /*jshint camelcase:true */
+
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ if ( config.notrycatch ) {
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
+ return;
+ }
+ try {
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ },
+ run: function() {
+ config.current = this;
+
+ var running = id( "qunit-testresult" );
+
+ if ( running ) {
+ running.innerHTML = "Running: " + this.nameHtml;
+ }
+
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ this.callbackStarted = +new Date();
+
+ if ( config.notrycatch ) {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ return;
+ }
+
+ try {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ } catch( e ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+
+ QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ QUnit.start();
+ }
+ }
+ },
+ teardown: function() {
+ config.current = this;
+ if ( config.notrycatch ) {
+ if ( typeof this.callbackRuntime === "undefined" ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ }
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
+ return;
+ } else {
+ try {
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ }
+ checkPollution();
+ },
+ finish: function() {
+ config.current = this;
+ if ( config.requireExpects && this.expected === null ) {
+ QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
+ QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+ } else if ( this.expected === null && !this.assertions.length ) {
+ QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+ }
+
+ var i, assertion, a, b, time, li, ol,
+ test = this,
+ good = 0,
+ bad = 0,
+ tests = id( "qunit-tests" );
+
+ this.runtime = +new Date() - this.started;
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ if ( tests ) {
+ ol = document.createElement( "ol" );
+ ol.className = "qunit-assert-list";
+
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ assertion = this.assertions[i];
+
+ li = document.createElement( "li" );
+ li.className = assertion.result ? "pass" : "fail";
+ li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+ ol.appendChild( li );
+
+ if ( assertion.result ) {
+ good++;
+ } else {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ // store result when possible
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+ } else {
+ sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+ }
+ }
+
+ if ( bad === 0 ) {
+ addClass( ol, "qunit-collapsed" );
+ }
+
+ // `b` initialized at top of scope
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml + " (" + bad + " , " + good + " , " + this.assertions.length + ") ";
+
+ addEvent(b, "click", function() {
+ var next = b.parentNode.lastChild,
+ collapsed = hasClass( next, "qunit-collapsed" );
+ ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
+ });
+
+ addEvent(b, "dblclick", function( e ) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = QUnit.url(https://github.com/%7B%20testNumber:%20test.testNumber%20%7D);
+ }
+ });
+
+ // `time` initialized at top of scope
+ time = document.createElement( "span" );
+ time.className = "runtime";
+ time.innerHTML = this.runtime + " ms";
+
+ // `li` initialized at top of scope
+ li = id( this.id );
+ li.className = bad ? "fail" : "pass";
+ li.removeChild( li.firstChild );
+ a = li.firstChild;
+ li.appendChild( b );
+ li.appendChild( a );
+ li.appendChild( time );
+ li.appendChild( ol );
+
+ } else {
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[i].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+ }
+
+ runLoggingCallbacks( "testDone", QUnit, {
+ name: this.testName,
+ module: this.module,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ duration: this.runtime
+ });
+
+ QUnit.reset();
+
+ config.current = undefined;
+ },
+
+ queue: function() {
+ var bad,
+ test = this;
+
+ synchronize(function() {
+ test.init();
+ });
+ function run() {
+ // each of these can by async
+ synchronize(function() {
+ test.setup();
+ });
+ synchronize(function() {
+ test.run();
+ });
+ synchronize(function() {
+ test.teardown();
+ });
+ synchronize(function() {
+ test.finish();
+ });
+ }
+
+ // `bad` initialized at top of scope
+ // defer when previous test run passed, if storage is available
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+ if ( bad ) {
+ run();
+ } else {
+ synchronize( run, true );
+ }
+ }
+};
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+ // call on start of module test to prepend name to all tests
+ module: function( name, testEnvironment ) {
+ config.currentModule = name;
+ config.currentModuleTestEnvironment = testEnvironment;
+ config.modules[name] = true;
+ },
+
+ asyncTest: function( testName, expected, callback ) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ QUnit.test( testName, expected, callback, true );
+ },
+
+ test: function( testName, expected, callback, async ) {
+ var test,
+ nameHtml = "" + escapeText( testName ) + " ";
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ if ( config.currentModule ) {
+ nameHtml = "" + escapeText( config.currentModule ) + " : " + nameHtml;
+ }
+
+ test = new Test({
+ nameHtml: nameHtml,
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback,
+ module: config.currentModule,
+ moduleTestEnvironment: config.currentModuleTestEnvironment,
+ stack: sourceFromStacktrace( 2 )
+ });
+
+ if ( !validTest( test ) ) {
+ return;
+ }
+
+ test.queue();
+ },
+
+ // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
+ expect: function( asserts ) {
+ if (arguments.length === 1) {
+ config.current.expected = asserts;
+ } else {
+ return config.current.expected;
+ }
+ },
+
+ start: function( count ) {
+ // QUnit hasn't been initialized yet.
+ // Note: RequireJS (et al) may delay onLoad
+ if ( config.semaphore === undefined ) {
+ QUnit.begin(function() {
+ // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
+ setTimeout(function() {
+ QUnit.start( count );
+ });
+ });
+ return;
+ }
+
+ config.semaphore -= count || 1;
+ // don't start until equal number of stop-calls
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ // ignore if start is called more often then stop
+ if ( config.semaphore < 0 ) {
+ config.semaphore = 0;
+ QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
+ return;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ setTimeout(function() {
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout( config.timeout );
+ }
+
+ config.blocking = false;
+ process( true );
+ }, 13);
+ } else {
+ config.blocking = false;
+ process( true );
+ }
+ },
+
+ stop: function( count ) {
+ config.semaphore += count || 1;
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout( config.timeout );
+ config.timeout = setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ config.semaphore = 1;
+ QUnit.start();
+ }, config.testTimeout );
+ }
+ }
+};
+
+// `assert` initialized at top of scope
+// Assert helpers
+// All of these must either call QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+// We attach it to the QUnit object *after* we expose the public API,
+// otherwise `assert` will become a global variable in browsers (#341).
+assert = {
+ /**
+ * Asserts rough true-ish result.
+ * @name ok
+ * @function
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+ */
+ ok: function( result, msg ) {
+ if ( !config.current ) {
+ throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+ result = !!result;
+ msg = msg || (result ? "okay" : "failed" );
+
+ var source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: msg
+ };
+
+ msg = "" + escapeText( msg ) + " ";
+
+ if ( !result ) {
+ source = sourceFromStacktrace( 2 );
+ if ( source ) {
+ details.source = source;
+ msg += "Source: " + escapeText( source ) + "
";
+ }
+ }
+ runLoggingCallbacks( "log", QUnit, details );
+ config.current.assertions.push({
+ result: result,
+ message: msg
+ });
+ },
+
+ /**
+ * Assert that the first two arguments are equal, with an optional message.
+ * Prints out both actual and expected values.
+ * @name equal
+ * @function
+ * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+ */
+ equal: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected == actual, actual, expected, message );
+ },
+
+ /**
+ * @name notEqual
+ * @function
+ */
+ notEqual: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected != actual, actual, expected, message );
+ },
+
+ /**
+ * @name propEqual
+ * @function
+ */
+ propEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notPropEqual
+ * @function
+ */
+ notPropEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name deepEqual
+ * @function
+ */
+ deepEqual: function( actual, expected, message ) {
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notDeepEqual
+ * @function
+ */
+ notDeepEqual: function( actual, expected, message ) {
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name strictEqual
+ * @function
+ */
+ strictEqual: function( actual, expected, message ) {
+ QUnit.push( expected === actual, actual, expected, message );
+ },
+
+ /**
+ * @name notStrictEqual
+ * @function
+ */
+ notStrictEqual: function( actual, expected, message ) {
+ QUnit.push( expected !== actual, actual, expected, message );
+ },
+
+ "throws": function( block, expected, message ) {
+ var actual,
+ expectedOutput = expected,
+ ok = false;
+
+ // 'expected' is optional
+ if ( typeof expected === "string" ) {
+ message = expected;
+ expected = null;
+ }
+
+ config.current.ignoreGlobalErrors = true;
+ try {
+ block.call( config.current.testEnvironment );
+ } catch (e) {
+ actual = e;
+ }
+ config.current.ignoreGlobalErrors = false;
+
+ if ( actual ) {
+ // we don't want to validate thrown error
+ if ( !expected ) {
+ ok = true;
+ expectedOutput = null;
+ // expected is a regexp
+ } else if ( QUnit.objectType( expected ) === "regexp" ) {
+ ok = expected.test( errorString( actual ) );
+ // expected is a constructor
+ } else if ( actual instanceof expected ) {
+ ok = true;
+ // expected is a validation function which returns true is validation passed
+ } else if ( expected.call( {}, actual ) === true ) {
+ expectedOutput = null;
+ ok = true;
+ }
+
+ QUnit.push( ok, actual, expectedOutput, message );
+ } else {
+ QUnit.pushFailure( message, null, "No exception was thrown." );
+ }
+ }
+};
+
+/**
+ * @deprecated since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility.
+ */
+extend( QUnit, assert );
+
+/**
+ * @deprecated since 1.9.0
+ * Kept root "raises()" for backwards compatibility.
+ * (Note that we don't introduce assert.raises).
+ */
+QUnit.raises = assert[ "throws" ];
+
+/**
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.equals = function() {
+ QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+ QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// We want access to the constructor's prototype
+(function() {
+ function F() {}
+ F.prototype = QUnit;
+ QUnit = new F();
+ // Make F QUnit's constructor so that we can add to the prototype later
+ QUnit.constructor = F;
+}());
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+ // The queue of tests to run
+ queue: [],
+
+ // block until document ready
+ blocking: true,
+
+ // when enabled, show only failing tests
+ // gets persisted through sessionStorage and can be changed in UI via checkbox
+ hidepassed: false,
+
+ // by default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // by default, modify document.title when suite is done
+ altertitle: true,
+
+ // when enabled, all tests must call expect()
+ requireExpects: false,
+
+ // add checkboxes that are persisted in the query-string
+ // when enabled, the id is set to `true` as a `QUnit.config` property
+ urlConfig: [
+ {
+ id: "noglobals",
+ label: "Check for Globals",
+ tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
+ },
+ {
+ id: "notrycatch",
+ label: "No try-catch",
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
+ }
+ ],
+
+ // Set of all modules.
+ modules: {},
+
+ // logging callback queues
+ begin: [],
+ done: [],
+ log: [],
+ testStart: [],
+ testDone: [],
+ moduleStart: [],
+ moduleDone: []
+};
+
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+ extend( window, QUnit.constructor.prototype );
+
+ // Expose QUnit object
+ window.QUnit = QUnit;
+}
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+ var i,
+ location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {},
+ current;
+
+ if ( params[ 0 ] ) {
+ for ( i = 0; i < length; i++ ) {
+ current = params[ i ].split( "=" );
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+
+ QUnit.urlParams = urlParams;
+
+ // String search anywhere in moduleName+testName
+ config.filter = urlParams.filter;
+
+ // Exact match of the module name
+ config.module = urlParams.module;
+
+ config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
+ assert: assert,
+
+ config: config,
+
+ // Initialize the configuration options
+ init: function() {
+ extend( config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: +new Date(),
+ updateRate: 1000,
+ blocking: false,
+ autostart: true,
+ autorun: false,
+ filter: "",
+ queue: [],
+ semaphore: 1
+ });
+
+ var tests, banner, result,
+ qunit = id( "qunit" );
+
+ if ( qunit ) {
+ qunit.innerHTML =
+ "" +
+ " " +
+ "
" +
+ " " +
+ " ";
+ }
+
+ tests = id( "qunit-tests" );
+ banner = id( "qunit-banner" );
+ result = id( "qunit-testresult" );
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ }
+
+ if ( banner ) {
+ banner.className = "";
+ }
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = "Running... ";
+ }
+ },
+
+ // Resets the test setup. Useful for tests that modify the DOM.
+ /*
+ DEPRECATED: Use multiple tests instead of resetting inside a test.
+ Use testStart or testDone for custom cleanup.
+ This method will throw an error in 2.0, and will be removed in 2.1
+ */
+ reset: function() {
+ var fixture = id( "qunit-fixture" );
+ if ( fixture ) {
+ fixture.innerHTML = config.fixture;
+ }
+ },
+
+ // Trigger an event on an element.
+ // @example triggerEvent( document.body, "click" );
+ triggerEvent: function( elem, type, event ) {
+ if ( document.createEvent ) {
+ event = document.createEvent( "MouseEvents" );
+ event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+ elem.dispatchEvent( event );
+ } else if ( elem.fireEvent ) {
+ elem.fireEvent( "on" + type );
+ }
+ },
+
+ // Safe object type checking
+ is: function( type, obj ) {
+ return QUnit.objectType( obj ) === type;
+ },
+
+ objectType: function( obj ) {
+ if ( typeof obj === "undefined" ) {
+ return "undefined";
+ // consider: typeof null === object
+ }
+ if ( obj === null ) {
+ return "null";
+ }
+
+ var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
+ type = match && match[1] || "";
+
+ switch ( type ) {
+ case "Number":
+ if ( isNaN(obj) ) {
+ return "nan";
+ }
+ return "number";
+ case "String":
+ case "Boolean":
+ case "Array":
+ case "Date":
+ case "RegExp":
+ case "Function":
+ return type.toLowerCase();
+ }
+ if ( typeof obj === "object" ) {
+ return "object";
+ }
+ return undefined;
+ },
+
+ push: function( result, actual, expected, message ) {
+ if ( !config.current ) {
+ throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+ }
+
+ var output, source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeText( message ) || ( result ? "okay" : "failed" );
+ message = "" + message + " ";
+ output = message;
+
+ if ( !result ) {
+ expected = escapeText( QUnit.jsDump.parse(expected) );
+ actual = escapeText( QUnit.jsDump.parse(actual) );
+ output += "Expected: " + expected + " ";
+
+ if ( actual !== expected ) {
+ output += "Result: " + actual + " ";
+ output += "Diff: " + QUnit.diff( expected, actual ) + " ";
+ }
+
+ source = sourceFromStacktrace();
+
+ if ( source ) {
+ details.source = source;
+ output += "Source: " + escapeText( source ) + " ";
+ }
+
+ output += "
";
+ }
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
+ pushFailure: function( message, source, actual ) {
+ if ( !config.current ) {
+ throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+
+ var output,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: false,
+ message: message
+ };
+
+ message = escapeText( message ) || "error";
+ message = "" + message + " ";
+ output = message;
+
+ output += "";
+
+ if ( actual ) {
+ output += "Result: " + escapeText( actual ) + " ";
+ }
+
+ if ( source ) {
+ details.source = source;
+ output += "Source: " + escapeText( source ) + " ";
+ }
+
+ output += "
";
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: false,
+ message: output
+ });
+ },
+
+ url: function( params ) {
+ params = extend( extend( {}, QUnit.urlParams ), params );
+ var key,
+ querystring = "?";
+
+ for ( key in params ) {
+ if ( hasOwn.call( params, key ) ) {
+ querystring += encodeURIComponent( key ) + "=" +
+ encodeURIComponent( params[ key ] ) + "&";
+ }
+ }
+ return window.location.protocol + "//" + window.location.host +
+ window.location.pathname + querystring.slice( 0, -1 );
+ },
+
+ extend: extend,
+ id: id,
+ addEvent: addEvent,
+ addClass: addClass,
+ hasClass: hasClass,
+ removeClass: removeClass
+ // load, equiv, jsDump, diff: Attached later
+});
+
+/**
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
+ * QUnit object.
+ */
+extend( QUnit.constructor.prototype, {
+
+ // Logging callbacks; all receive a single argument with the listed properties
+ // run test/logs.html for any related changes
+ begin: registerLoggingCallback( "begin" ),
+
+ // done: { failed, passed, total, runtime }
+ done: registerLoggingCallback( "done" ),
+
+ // log: { result, actual, expected, message }
+ log: registerLoggingCallback( "log" ),
+
+ // testStart: { name }
+ testStart: registerLoggingCallback( "testStart" ),
+
+ // testDone: { name, failed, passed, total, duration }
+ testDone: registerLoggingCallback( "testDone" ),
+
+ // moduleStart: { name }
+ moduleStart: registerLoggingCallback( "moduleStart" ),
+
+ // moduleDone: { name, failed, passed, total }
+ moduleDone: registerLoggingCallback( "moduleDone" )
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+ config.autorun = true;
+}
+
+QUnit.load = function() {
+ runLoggingCallbacks( "begin", QUnit, {} );
+
+ // Initialize the config, saving the execution queue
+ var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
+ urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
+ numModules = 0,
+ moduleNames = [],
+ moduleFilterHtml = "",
+ urlConfigHtml = "",
+ oldconfig = extend( {}, config );
+
+ QUnit.init();
+ extend(config, oldconfig);
+
+ config.blocking = false;
+
+ len = config.urlConfig.length;
+
+ for ( i = 0; i < len; i++ ) {
+ val = config.urlConfig[i];
+ if ( typeof val === "string" ) {
+ val = {
+ id: val,
+ label: val,
+ tooltip: "[no tooltip available]"
+ };
+ }
+ config[ val.id ] = QUnit.urlParams[ val.id ];
+ urlConfigHtml += "" + val.label + " ";
+ }
+ for ( i in config.modules ) {
+ if ( config.modules.hasOwnProperty( i ) ) {
+ moduleNames.push(i);
+ }
+ }
+ numModules = moduleNames.length;
+ moduleNames.sort( function( a, b ) {
+ return a.localeCompare( b );
+ });
+ moduleFilterHtml += "Module: < All Modules > ";
+
+
+ for ( i = 0; i < numModules; i++) {
+ moduleFilterHtml += "" + escapeText(moduleNames[i]) + " ";
+ }
+ moduleFilterHtml += " ";
+
+ // `userAgent` initialized at top of scope
+ userAgent = id( "qunit-userAgent" );
+ if ( userAgent ) {
+ userAgent.innerHTML = navigator.userAgent;
+ }
+
+ // `banner` initialized at top of scope
+ banner = id( "qunit-header" );
+ if ( banner ) {
+ banner.innerHTML = "" + banner.innerHTML + " ";
+ }
+
+ // `toolbar` initialized at top of scope
+ toolbar = id( "qunit-testrunner-toolbar" );
+ if ( toolbar ) {
+ // `filter` initialized at top of scope
+ filter = document.createElement( "input" );
+ filter.type = "checkbox";
+ filter.id = "qunit-filter-pass";
+
+ addEvent( filter, "click", function() {
+ var tmp,
+ ol = document.getElementById( "qunit-tests" );
+
+ if ( filter.checked ) {
+ ol.className = ol.className + " hidepass";
+ } else {
+ tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+ ol.className = tmp.replace( / hidepass /, " " );
+ }
+ if ( defined.sessionStorage ) {
+ if (filter.checked) {
+ sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
+ } else {
+ sessionStorage.removeItem( "qunit-filter-passed-tests" );
+ }
+ }
+ });
+
+ if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
+ filter.checked = true;
+ // `ol` initialized at top of scope
+ ol = document.getElementById( "qunit-tests" );
+ ol.className = ol.className + " hidepass";
+ }
+ toolbar.appendChild( filter );
+
+ // `label` initialized at top of scope
+ label = document.createElement( "label" );
+ label.setAttribute( "for", "qunit-filter-pass" );
+ label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
+ label.innerHTML = "Hide passed tests";
+ toolbar.appendChild( label );
+
+ urlConfigCheckboxesContainer = document.createElement("span");
+ urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
+ urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
+ // For oldIE support:
+ // * Add handlers to the individual elements instead of the container
+ // * Use "click" instead of "change"
+ // * Fallback from event.target to event.srcElement
+ addEvents( urlConfigCheckboxes, "click", function( event ) {
+ var params = {},
+ target = event.target || event.srcElement;
+ params[ target.name ] = target.checked ? true : undefined;
+ window.location = QUnit.url(https://github.com/params);
+ });
+ toolbar.appendChild( urlConfigCheckboxesContainer );
+
+ if (numModules > 1) {
+ moduleFilter = document.createElement( "span" );
+ moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+ moduleFilter.innerHTML = moduleFilterHtml;
+ addEvent( moduleFilter.lastChild, "change", function() {
+ var selectBox = moduleFilter.getElementsByTagName("select")[0],
+ selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
+
+ window.location = QUnit.url({
+ module: ( selectedModule === "" ) ? undefined : selectedModule,
+ // Remove any existing filters
+ filter: undefined,
+ testNumber: undefined
+ });
+ });
+ toolbar.appendChild(moduleFilter);
+ }
+ }
+
+ // `main` initialized at top of scope
+ main = id( "qunit-fixture" );
+ if ( main ) {
+ config.fixture = main.innerHTML;
+ }
+
+ if ( config.autostart ) {
+ QUnit.start();
+ }
+};
+
+addEvent( window, "load", QUnit.load );
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will suppress the default browser handler,
+// returning false will let it run.
+window.onerror = function ( error, filePath, linerNr ) {
+ var ret = false;
+ if ( onErrorFnPrev ) {
+ ret = onErrorFnPrev( error, filePath, linerNr );
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not suppressed.
+ if ( ret !== true ) {
+ if ( QUnit.config.current ) {
+ if ( QUnit.config.current.ignoreGlobalErrors ) {
+ return true;
+ }
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ } else {
+ QUnit.test( "global failure", extend( function() {
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ }, { validTest: validTest } ) );
+ }
+ return false;
+ }
+
+ return ret;
+};
+
+function done() {
+ config.autorun = true;
+
+ // Log the last module results
+ if ( config.currentModule ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.currentModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ delete config.previousModule;
+
+ var i, key,
+ banner = id( "qunit-banner" ),
+ tests = id( "qunit-tests" ),
+ runtime = +new Date() - config.started,
+ passed = config.stats.all - config.stats.bad,
+ html = [
+ "Tests completed in ",
+ runtime,
+ " milliseconds. ",
+ "",
+ passed,
+ " assertions of ",
+ config.stats.all,
+ " passed, ",
+ config.stats.bad,
+ " failed."
+ ].join( "" );
+
+ if ( banner ) {
+ banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( config.altertitle && typeof document !== "undefined" && document.title ) {
+ // show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8-charset
+ document.title = [
+ ( config.stats.bad ? "\u2716" : "\u2714" ),
+ document.title.replace( /^[\u2714\u2716] /i, "" )
+ ].join( " " );
+ }
+
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+ // `key` & `i` initialized at top of scope
+ for ( i = 0; i < sessionStorage.length; i++ ) {
+ key = sessionStorage.key( i++ );
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
+ sessionStorage.removeItem( key );
+ }
+ }
+ }
+
+ // scroll back to top to show results
+ if ( window.scrollTo ) {
+ window.scrollTo(0, 0);
+ }
+
+ runLoggingCallbacks( "done", QUnit, {
+ failed: config.stats.bad,
+ passed: passed,
+ total: config.stats.all,
+ runtime: runtime
+ });
+}
+
+/** @return Boolean: true if this test should be ran */
+function validTest( test ) {
+ var include,
+ filter = config.filter && config.filter.toLowerCase(),
+ module = config.module && config.module.toLowerCase(),
+ fullName = (test.module + ": " + test.testName).toLowerCase();
+
+ // Internally-generated tests are always valid
+ if ( test.callback && test.callback.validTest === validTest ) {
+ delete test.callback.validTest;
+ return true;
+ }
+
+ if ( config.testNumber ) {
+ return test.testNumber === config.testNumber;
+ }
+
+ if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
+ return false;
+ }
+
+ if ( !filter ) {
+ return true;
+ }
+
+ include = filter.charAt( 0 ) !== "!";
+ if ( !include ) {
+ filter = filter.slice( 1 );
+ }
+
+ // If the filter matches, we need to honour include
+ if ( fullName.indexOf( filter ) !== -1 ) {
+ return include;
+ }
+
+ // Otherwise, do the opposite
+ return !include;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
+// Later Safari and IE10 are supposed to support error.stack as well
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+ offset = offset === undefined ? 3 : offset;
+
+ var stack, include, i;
+
+ if ( e.stacktrace ) {
+ // Opera
+ return e.stacktrace.split( "\n" )[ offset + 3 ];
+ } else if ( e.stack ) {
+ // Firefox, Chrome
+ stack = e.stack.split( "\n" );
+ if (/^error$/i.test( stack[0] ) ) {
+ stack.shift();
+ }
+ if ( fileName ) {
+ include = [];
+ for ( i = offset; i < stack.length; i++ ) {
+ if ( stack[ i ].indexOf( fileName ) !== -1 ) {
+ break;
+ }
+ include.push( stack[ i ] );
+ }
+ if ( include.length ) {
+ return include.join( "\n" );
+ }
+ }
+ return stack[ offset ];
+ } else if ( e.sourceURL ) {
+ // Safari, PhantomJS
+ // hopefully one day Safari provides actual stacktraces
+ // exclude useless self-reference for generated Error objects
+ if ( /qunit.js$/.test( e.sourceURL ) ) {
+ return;
+ }
+ // for actual exceptions, this is useful
+ return e.sourceURL + ":" + e.line;
+ }
+}
+function sourceFromStacktrace( offset ) {
+ try {
+ throw new Error();
+ } catch ( e ) {
+ return extractStacktrace( e, offset );
+ }
+}
+
+/**
+ * Escape text for attribute or text content.
+ */
+function escapeText( s ) {
+ if ( !s ) {
+ return "";
+ }
+ s = s + "";
+ // Both single quotes and double quotes (for attributes)
+ return s.replace( /['"<>&]/g, function( s ) {
+ switch( s ) {
+ case "'":
+ return "'";
+ case "\"":
+ return """;
+ case "<":
+ return "<";
+ case ">":
+ return ">";
+ case "&":
+ return "&";
+ }
+ });
+}
+
+function synchronize( callback, last ) {
+ config.queue.push( callback );
+
+ if ( config.autorun && !config.blocking ) {
+ process( last );
+ }
+}
+
+function process( last ) {
+ function next() {
+ process( last );
+ }
+ var start = new Date().getTime();
+ config.depth = config.depth ? config.depth + 1 : 1;
+
+ while ( config.queue.length && !config.blocking ) {
+ if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+ config.queue.shift()();
+ } else {
+ setTimeout( next, 13 );
+ break;
+ }
+ }
+ config.depth--;
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+ done();
+ }
+}
+
+function saveGlobal() {
+ config.pollution = [];
+
+ if ( config.noglobals ) {
+ for ( var key in window ) {
+ if ( hasOwn.call( window, key ) ) {
+ // in Opera sometimes DOM element ids show up here, ignore them
+ if ( /^qunit-test-output/.test( key ) ) {
+ continue;
+ }
+ config.pollution.push( key );
+ }
+ }
+ }
+}
+
+function checkPollution() {
+ var newGlobals,
+ deletedGlobals,
+ old = config.pollution;
+
+ saveGlobal();
+
+ newGlobals = diff( config.pollution, old );
+ if ( newGlobals.length > 0 ) {
+ QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
+ }
+
+ deletedGlobals = diff( old, config.pollution );
+ if ( deletedGlobals.length > 0 ) {
+ QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
+ }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+ var i, j,
+ result = a.slice();
+
+ for ( i = 0; i < result.length; i++ ) {
+ for ( j = 0; j < b.length; j++ ) {
+ if ( result[i] === b[j] ) {
+ result.splice( i, 1 );
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+function extend( a, b ) {
+ for ( var prop in b ) {
+ if ( hasOwn.call( b, prop ) ) {
+ // Avoid "Member not found" error in IE8 caused by messing with window.constructor
+ if ( !( prop === "constructor" && a === window ) ) {
+ if ( b[ prop ] === undefined ) {
+ delete a[ prop ];
+ } else {
+ a[ prop ] = b[ prop ];
+ }
+ }
+ }
+ }
+
+ return a;
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+ // Standards-based browsers
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, fn, false );
+ // IE
+ } else {
+ elem.attachEvent( "on" + type, fn );
+ }
+}
+
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+ var i = elems.length;
+ while ( i-- ) {
+ addEvent( elems[i], type, fn );
+ }
+}
+
+function hasClass( elem, name ) {
+ return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
+}
+
+function addClass( elem, name ) {
+ if ( !hasClass( elem, name ) ) {
+ elem.className += (elem.className ? " " : "") + name;
+ }
+}
+
+function removeClass( elem, name ) {
+ var set = " " + elem.className + " ";
+ // Class name may appear multiple times
+ while ( set.indexOf(" " + name + " ") > -1 ) {
+ set = set.replace(" " + name + " " , " ");
+ }
+ // If possible, trim it for prettiness, but not necessarily
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
+}
+
+function id( name ) {
+ return !!( typeof document !== "undefined" && document && document.getElementById ) &&
+ document.getElementById( name );
+}
+
+function registerLoggingCallback( key ) {
+ return function( callback ) {
+ config[key].push( callback );
+ };
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks( key, scope, args ) {
+ var i, callbacks;
+ if ( QUnit.hasOwnProperty( key ) ) {
+ QUnit[ key ].call(scope, args );
+ } else {
+ callbacks = config[ key ];
+ for ( i = 0; i < callbacks.length; i++ ) {
+ callbacks[ i ].call( scope, args );
+ }
+ }
+}
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé
+QUnit.equiv = (function() {
+
+ // Call the o related callback with the given arguments.
+ function bindCallbacks( o, callbacks, args ) {
+ var prop = QUnit.objectType( o );
+ if ( prop ) {
+ if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+ return callbacks[ prop ].apply( callbacks, args );
+ } else {
+ return callbacks[ prop ]; // or undefined
+ }
+ }
+ }
+
+ // the real equiv function
+ var innerEquiv,
+ // stack to decide between skip/abort functions
+ callers = [],
+ // stack to avoiding loops from circular referencing
+ parents = [],
+ parentsB = [],
+
+ getProto = Object.getPrototypeOf || function ( obj ) {
+ /*jshint camelcase:false */
+ return obj.__proto__;
+ },
+ callbacks = (function () {
+
+ // for string, boolean, number and null
+ function useStrictEquality( b, a ) {
+ /*jshint eqeqeq:false */
+ if ( b instanceof a.constructor || a instanceof b.constructor ) {
+ // to catch short annotation VS 'new' annotation of a
+ // declaration
+ // e.g. var i = 1;
+ // var j = new Number(1);
+ return a == b;
+ } else {
+ return a === b;
+ }
+ }
+
+ return {
+ "string": useStrictEquality,
+ "boolean": useStrictEquality,
+ "number": useStrictEquality,
+ "null": useStrictEquality,
+ "undefined": useStrictEquality,
+
+ "nan": function( b ) {
+ return isNaN( b );
+ },
+
+ "date": function( b, a ) {
+ return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+ },
+
+ "regexp": function( b, a ) {
+ return QUnit.objectType( b ) === "regexp" &&
+ // the regex itself
+ a.source === b.source &&
+ // and its modifiers
+ a.global === b.global &&
+ // (gmi) ...
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline &&
+ a.sticky === b.sticky;
+ },
+
+ // - skip when the property is a method of an instance (OOP)
+ // - abort otherwise,
+ // initial === would have catch identical references anyway
+ "function": function() {
+ var caller = callers[callers.length - 1];
+ return caller !== Object && typeof caller !== "undefined";
+ },
+
+ "array": function( b, a ) {
+ var i, j, len, loop, aCircular, bCircular;
+
+ // b could be an object literal here
+ if ( QUnit.objectType( b ) !== "array" ) {
+ return false;
+ }
+
+ len = a.length;
+ if ( len !== b.length ) {
+ // safe and faster
+ return false;
+ }
+
+ // track reference to avoid circular references
+ parents.push( a );
+ parentsB.push( b );
+ for ( i = 0; i < len; i++ ) {
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ aCircular = parents[j] === a[i];
+ bCircular = parentsB[j] === b[i];
+ if ( aCircular || bCircular ) {
+ if ( a[i] === b[i] || aCircular && bCircular ) {
+ loop = true;
+ } else {
+ parents.pop();
+ parentsB.pop();
+ return false;
+ }
+ }
+ }
+ if ( !loop && !innerEquiv(a[i], b[i]) ) {
+ parents.pop();
+ parentsB.pop();
+ return false;
+ }
+ }
+ parents.pop();
+ parentsB.pop();
+ return true;
+ },
+
+ "object": function( b, a ) {
+ /*jshint forin:false */
+ var i, j, loop, aCircular, bCircular,
+ // Default to true
+ eq = true,
+ aProperties = [],
+ bProperties = [];
+
+ // comparing constructors is more strict than using
+ // instanceof
+ if ( a.constructor !== b.constructor ) {
+ // Allow objects with no prototype to be equivalent to
+ // objects with Object as their constructor.
+ if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
+ ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
+ return false;
+ }
+ }
+
+ // stack constructor before traversing properties
+ callers.push( a.constructor );
+
+ // track reference to avoid circular references
+ parents.push( a );
+ parentsB.push( b );
+
+ // be strict: don't ensure hasOwnProperty and go deep
+ for ( i in a ) {
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ aCircular = parents[j] === a[i];
+ bCircular = parentsB[j] === b[i];
+ if ( aCircular || bCircular ) {
+ if ( a[i] === b[i] || aCircular && bCircular ) {
+ loop = true;
+ } else {
+ eq = false;
+ break;
+ }
+ }
+ }
+ aProperties.push(i);
+ if ( !loop && !innerEquiv(a[i], b[i]) ) {
+ eq = false;
+ break;
+ }
+ }
+
+ parents.pop();
+ parentsB.pop();
+ callers.pop(); // unstack, we are done
+
+ for ( i in b ) {
+ bProperties.push( i ); // collect b's properties
+ }
+
+ // Ensures identical properties name
+ return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+ }
+ };
+ }());
+
+ innerEquiv = function() { // can take multiple arguments
+ var args = [].slice.apply( arguments );
+ if ( args.length < 2 ) {
+ return true; // end transition
+ }
+
+ return (function( a, b ) {
+ if ( a === b ) {
+ return true; // catch the most you can
+ } else if ( a === null || b === null || typeof a === "undefined" ||
+ typeof b === "undefined" ||
+ QUnit.objectType(a) !== QUnit.objectType(b) ) {
+ return false; // don't lose time with error prone cases
+ } else {
+ return bindCallbacks(a, callbacks, [ b, a ]);
+ }
+
+ // apply transition with (1..n) arguments
+ }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) );
+ };
+
+ return innerEquiv;
+}());
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+ function quote( str ) {
+ return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
+ }
+ function literal( o ) {
+ return o + "";
+ }
+ function join( pre, arr, post ) {
+ var s = jsDump.separator(),
+ base = jsDump.indent(),
+ inner = jsDump.indent(1);
+ if ( arr.join ) {
+ arr = arr.join( "," + s + inner );
+ }
+ if ( !arr ) {
+ return pre + post;
+ }
+ return [ pre, inner + arr, base + post ].join(s);
+ }
+ function array( arr, stack ) {
+ var i = arr.length, ret = new Array(i);
+ this.up();
+ while ( i-- ) {
+ ret[i] = this.parse( arr[i] , undefined , stack);
+ }
+ this.down();
+ return join( "[", ret, "]" );
+ }
+
+ var reName = /^function (\w+)/,
+ jsDump = {
+ // type is used mostly internally, you can fix a (custom)type in advance
+ parse: function( obj, type, stack ) {
+ stack = stack || [ ];
+ var inStack, res,
+ parser = this.parsers[ type || this.typeOf(obj) ];
+
+ type = typeof parser;
+ inStack = inArray( obj, stack );
+
+ if ( inStack !== -1 ) {
+ return "recursion(" + (inStack - stack.length) + ")";
+ }
+ if ( type === "function" ) {
+ stack.push( obj );
+ res = parser.call( this, obj, stack );
+ stack.pop();
+ return res;
+ }
+ return ( type === "string" ) ? parser : this.parsers.error;
+ },
+ typeOf: function( obj ) {
+ var type;
+ if ( obj === null ) {
+ type = "null";
+ } else if ( typeof obj === "undefined" ) {
+ type = "undefined";
+ } else if ( QUnit.is( "regexp", obj) ) {
+ type = "regexp";
+ } else if ( QUnit.is( "date", obj) ) {
+ type = "date";
+ } else if ( QUnit.is( "function", obj) ) {
+ type = "function";
+ } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+ type = "window";
+ } else if ( obj.nodeType === 9 ) {
+ type = "document";
+ } else if ( obj.nodeType ) {
+ type = "node";
+ } else if (
+ // native arrays
+ toString.call( obj ) === "[object Array]" ||
+ // NodeList objects
+ ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+ ) {
+ type = "array";
+ } else if ( obj.constructor === Error.prototype.constructor ) {
+ type = "error";
+ } else {
+ type = typeof obj;
+ }
+ return type;
+ },
+ separator: function() {
+ return this.multiline ? this.HTML ? " " : "\n" : this.HTML ? " " : " ";
+ },
+ // extra can be a number, shortcut for increasing-calling-decreasing
+ indent: function( extra ) {
+ if ( !this.multiline ) {
+ return "";
+ }
+ var chr = this.indentChar;
+ if ( this.HTML ) {
+ chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
+ }
+ return new Array( this.depth + ( extra || 0 ) ).join(chr);
+ },
+ up: function( a ) {
+ this.depth += a || 1;
+ },
+ down: function( a ) {
+ this.depth -= a || 1;
+ },
+ setParser: function( name, parser ) {
+ this.parsers[name] = parser;
+ },
+ // The next 3 are exposed so you can use them
+ quote: quote,
+ literal: literal,
+ join: join,
+ //
+ depth: 1,
+ // This is the list of parsers, to modify them, use jsDump.setParser
+ parsers: {
+ window: "[Window]",
+ document: "[Document]",
+ error: function(error) {
+ return "Error(\"" + error.message + "\")";
+ },
+ unknown: "[Unknown]",
+ "null": "null",
+ "undefined": "undefined",
+ "function": function( fn ) {
+ var ret = "function",
+ // functions never have name in IE
+ name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
+
+ if ( name ) {
+ ret += " " + name;
+ }
+ ret += "( ";
+
+ ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
+ return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+ },
+ array: array,
+ nodelist: array,
+ "arguments": array,
+ object: function( map, stack ) {
+ /*jshint forin:false */
+ var ret = [ ], keys, key, val, i;
+ QUnit.jsDump.up();
+ keys = [];
+ for ( key in map ) {
+ keys.push( key );
+ }
+ keys.sort();
+ for ( i = 0; i < keys.length; i++ ) {
+ key = keys[ i ];
+ val = map[ key ];
+ ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+ }
+ QUnit.jsDump.down();
+ return join( "{", ret, "}" );
+ },
+ node: function( node ) {
+ var len, i, val,
+ open = QUnit.jsDump.HTML ? "<" : "<",
+ close = QUnit.jsDump.HTML ? ">" : ">",
+ tag = node.nodeName.toLowerCase(),
+ ret = open + tag,
+ attrs = node.attributes;
+
+ if ( attrs ) {
+ for ( i = 0, len = attrs.length; i < len; i++ ) {
+ val = attrs[i].nodeValue;
+ // IE6 includes all attributes in .attributes, even ones not explicitly set.
+ // Those have values like undefined, null, 0, false, "" or "inherit".
+ if ( val && val !== "inherit" ) {
+ ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
+ }
+ }
+ }
+ ret += close;
+
+ // Show content of TextNode or CDATASection
+ if ( node.nodeType === 3 || node.nodeType === 4 ) {
+ ret += node.nodeValue;
+ }
+
+ return ret + open + "/" + tag + close;
+ },
+ // function calls it internally, it's the arguments part of the function
+ functionArgs: function( fn ) {
+ var args,
+ l = fn.length;
+
+ if ( !l ) {
+ return "";
+ }
+
+ args = new Array(l);
+ while ( l-- ) {
+ // 97 is 'a'
+ args[l] = String.fromCharCode(97+l);
+ }
+ return " " + args.join( ", " ) + " ";
+ },
+ // object calls it internally, the key part of an item in a map
+ key: quote,
+ // function calls it internally, it's the content of the function
+ functionCode: "[code]",
+ // node calls it internally, it's an html attribute value
+ attribute: quote,
+ string: quote,
+ date: quote,
+ regexp: literal,
+ number: literal,
+ "boolean": literal
+ },
+ // if true, entities are escaped ( <, >, \t, space and \n )
+ HTML: false,
+ // indentation unit
+ indentChar: " ",
+ // if true, items in a collection, are separated by a \n, else just a space.
+ multiline: true
+ };
+
+ return jsDump;
+}());
+
+// from jquery.js
+function inArray( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Javascript Diff Algorithm
+ * By John Resig (http://ejohn.org/)
+ * Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ * http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over"
+ */
+QUnit.diff = (function() {
+ /*jshint eqeqeq:false, eqnull:true */
+ function diff( o, n ) {
+ var i,
+ ns = {},
+ os = {};
+
+ for ( i = 0; i < n.length; i++ ) {
+ if ( !hasOwn.call( ns, n[i] ) ) {
+ ns[ n[i] ] = {
+ rows: [],
+ o: null
+ };
+ }
+ ns[ n[i] ].rows.push( i );
+ }
+
+ for ( i = 0; i < o.length; i++ ) {
+ if ( !hasOwn.call( os, o[i] ) ) {
+ os[ o[i] ] = {
+ rows: [],
+ n: null
+ };
+ }
+ os[ o[i] ].rows.push( i );
+ }
+
+ for ( i in ns ) {
+ if ( hasOwn.call( ns, i ) ) {
+ if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
+ n[ ns[i].rows[0] ] = {
+ text: n[ ns[i].rows[0] ],
+ row: os[i].rows[0]
+ };
+ o[ os[i].rows[0] ] = {
+ text: o[ os[i].rows[0] ],
+ row: ns[i].rows[0]
+ };
+ }
+ }
+ }
+
+ for ( i = 0; i < n.length - 1; i++ ) {
+ if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
+ n[ i + 1 ] == o[ n[i].row + 1 ] ) {
+
+ n[ i + 1 ] = {
+ text: n[ i + 1 ],
+ row: n[i].row + 1
+ };
+ o[ n[i].row + 1 ] = {
+ text: o[ n[i].row + 1 ],
+ row: i + 1
+ };
+ }
+ }
+
+ for ( i = n.length - 1; i > 0; i-- ) {
+ if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
+ n[ i - 1 ] == o[ n[i].row - 1 ]) {
+
+ n[ i - 1 ] = {
+ text: n[ i - 1 ],
+ row: n[i].row - 1
+ };
+ o[ n[i].row - 1 ] = {
+ text: o[ n[i].row - 1 ],
+ row: i - 1
+ };
+ }
+ }
+
+ return {
+ o: o,
+ n: n
+ };
+ }
+
+ return function( o, n ) {
+ o = o.replace( /\s+$/, "" );
+ n = n.replace( /\s+$/, "" );
+
+ var i, pre,
+ str = "",
+ out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
+ oSpace = o.match(/\s+/g),
+ nSpace = n.match(/\s+/g);
+
+ if ( oSpace == null ) {
+ oSpace = [ " " ];
+ }
+ else {
+ oSpace.push( " " );
+ }
+
+ if ( nSpace == null ) {
+ nSpace = [ " " ];
+ }
+ else {
+ nSpace.push( " " );
+ }
+
+ if ( out.n.length === 0 ) {
+ for ( i = 0; i < out.o.length; i++ ) {
+ str += "" + out.o[i] + oSpace[i] + "";
+ }
+ }
+ else {
+ if ( out.n[0].text == null ) {
+ for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
+ str += "" + out.o[n] + oSpace[n] + "";
+ }
+ }
+
+ for ( i = 0; i < out.n.length; i++ ) {
+ if (out.n[i].text == null) {
+ str += "" + out.n[i] + nSpace[i] + " ";
+ }
+ else {
+ // `pre` initialized at top of scope
+ pre = "";
+
+ for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
+ pre += "" + out.o[n] + oSpace[n] + "";
+ }
+ str += " " + out.n[i].text + nSpace[i] + pre;
+ }
+ }
+ }
+
+ return str;
+ };
+}());
+
+// for CommonJS environments, export everything
+if ( typeof exports !== "undefined" ) {
+ extend( exports, QUnit.constructor.prototype );
+}
+
+// get at whatever the global object is, like window in browsers
+}( (function() {return this;}.call()) ));
\ No newline at end of file
diff --git a/reveal.js/test/simple.md b/reveal.js/test/simple.md
new file mode 100755
index 0000000..c72a440
--- /dev/null
+++ b/reveal.js/test/simple.md
@@ -0,0 +1,12 @@
+## Slide 1.1
+
+```js
+var a = 1;
+```
+
+
+## Slide 1.2
+
+
+
+## Slide 2
diff --git a/reveal.js/test/test-markdown-element-attributes.html b/reveal.js/test/test-markdown-element-attributes.html
new file mode 100755
index 0000000..6edf95e
--- /dev/null
+++ b/reveal.js/test/test-markdown-element-attributes.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+ reveal.js - Test Markdown Element Attributes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/test-markdown-element-attributes.js b/reveal.js/test/test-markdown-element-attributes.js
new file mode 100755
index 0000000..10a2503
--- /dev/null
+++ b/reveal.js/test/test-markdown-element-attributes.js
@@ -0,0 +1,46 @@
+
+
+Reveal.addEventListener( 'ready', function() {
+
+ QUnit.module( 'Markdown' );
+
+ test( 'Vertical separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 4, 'found four slides' );
+ });
+
+
+ test( 'Attributes on element header in vertical slides', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section>section h2.fragment.fade-out' ).length, 1, 'found one vertical slide with class fragment.fade-out on header' );
+ strictEqual( document.querySelectorAll( '.reveal .slides section>section h2.fragment.shrink' ).length, 1, 'found one vertical slide with class fragment.shrink on header' );
+ });
+
+ test( 'Attributes on element paragraphs in vertical slides', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section>section p.fragment.grow' ).length, 2, 'found a vertical slide with two paragraphs with class fragment.grow' );
+ });
+
+ test( 'Attributes on element list items in vertical slides', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section>section li.fragment.grow' ).length, 3, 'found a vertical slide with three list items with class fragment.grow' );
+ });
+
+ test( 'Attributes on element paragraphs in horizontal slides', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section p.fragment.highlight-red' ).length, 4, 'found a horizontal slide with four paragraphs with class fragment.grow' );
+ });
+ test( 'Attributes on element list items in horizontal slides', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section li.fragment.highlight-green' ).length, 5, 'found a horizontal slide with five list items with class fragment.roll-in' );
+ });
+ test( 'Attributes on element list items in horizontal slides', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section img.reveal.stretch' ).length, 1, 'found a horizontal slide with stretched image, class img.reveal.stretch' );
+ });
+
+ test( 'Attributes on elements in vertical slides with default element attribute separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section h2.fragment.highlight-red' ).length, 2, 'found two h2 titles with fragment highlight-red in vertical slides with default element attribute separator' );
+ });
+
+ test( 'Attributes on elements in single slides with default element attribute separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides section p.fragment.highlight-blue' ).length, 3, 'found three elements with fragment highlight-blue in single slide with default element attribute separator' );
+ });
+
+} );
+
+Reveal.initialize();
+
diff --git a/reveal.js/test/test-markdown-external.html b/reveal.js/test/test-markdown-external.html
new file mode 100755
index 0000000..859d0a1
--- /dev/null
+++ b/reveal.js/test/test-markdown-external.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ reveal.js - Test Markdown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/test-markdown-external.js b/reveal.js/test/test-markdown-external.js
new file mode 100755
index 0000000..cab85c6
--- /dev/null
+++ b/reveal.js/test/test-markdown-external.js
@@ -0,0 +1,24 @@
+
+
+Reveal.addEventListener( 'ready', function() {
+
+ QUnit.module( 'Markdown' );
+
+ test( 'Vertical separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 2, 'found two slides' );
+ });
+
+ test( 'Horizontal separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section' ).length, 2, 'found two slides' );
+ });
+
+ test( 'Language highlighter', function() {
+ strictEqual( document.querySelectorAll( '.hljs-keyword' ).length, 1, 'got rendered highlight tag.' );
+ strictEqual( document.querySelector( '.hljs-keyword' ).innerHTML, 'var', 'the same keyword: var.' );
+ });
+
+
+} );
+
+Reveal.initialize();
+
diff --git a/reveal.js/test/test-markdown-options.html b/reveal.js/test/test-markdown-options.html
new file mode 100755
index 0000000..5b3be97
--- /dev/null
+++ b/reveal.js/test/test-markdown-options.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+ reveal.js - Test Markdown Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/test-markdown-options.js b/reveal.js/test/test-markdown-options.js
new file mode 100755
index 0000000..3ae1350
--- /dev/null
+++ b/reveal.js/test/test-markdown-options.js
@@ -0,0 +1,26 @@
+Reveal.addEventListener( 'ready', function() {
+
+ QUnit.module( 'Markdown' );
+
+ test( 'Options are set', function() {
+ strictEqual( marked.defaults.smartypants, true );
+ });
+
+ test( 'Smart quotes are activated', function() {
+ var text = document.querySelector( '.reveal .slides>section>p' ).textContent;
+
+ strictEqual( /['"]/.test( text ), false );
+ strictEqual( /[“”‘’]/.test( text ), true );
+ });
+
+} );
+
+Reveal.initialize({
+ dependencies: [
+ { src: '../plugin/markdown/marked.js' },
+ { src: '../plugin/markdown/markdown.js' },
+ ],
+ markdown: {
+ smartypants: true
+ }
+});
diff --git a/reveal.js/test/test-markdown-slide-attributes.html b/reveal.js/test/test-markdown-slide-attributes.html
new file mode 100755
index 0000000..ab6ece4
--- /dev/null
+++ b/reveal.js/test/test-markdown-slide-attributes.html
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+ reveal.js - Test Markdown Attributes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/test-markdown-slide-attributes.js b/reveal.js/test/test-markdown-slide-attributes.js
new file mode 100755
index 0000000..3817fd3
--- /dev/null
+++ b/reveal.js/test/test-markdown-slide-attributes.js
@@ -0,0 +1,47 @@
+
+
+Reveal.addEventListener( 'ready', function() {
+
+ QUnit.module( 'Markdown' );
+
+ test( 'Vertical separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 6, 'found six vertical slides' );
+ });
+
+ test( 'Id on slide', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section#slide2' ).length, 1, 'found one slide with id slide2' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section a[href="#/slide2"]' ).length, 1, 'found one slide with a link to slide2' );
+ });
+
+ test( 'data-background attributes', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#A0C66B"]' ).length, 1, 'found one vertical slide with data-background="#A0C66B"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#ff0000"]' ).length, 1, 'found one vertical slide with data-background="#ff0000"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section[data-background="#C6916B"]' ).length, 1, 'found one slide with data-background="#C6916B"' );
+ });
+
+ test( 'data-transition attributes', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="zoom"]' ).length, 1, 'found one vertical slide with data-transition="zoom"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="fade"]' ).length, 1, 'found one vertical slide with data-transition="fade"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides section [data-transition="zoom"]' ).length, 1, 'found one slide with data-transition="zoom"' );
+ });
+
+ test( 'data-background attributes with default separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#A7C66B"]' ).length, 1, 'found one vertical slide with data-background="#A0C66B"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-background="#f70000"]' ).length, 1, 'found one vertical slide with data-background="#ff0000"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section[data-background="#C7916B"]' ).length, 1, 'found one slide with data-background="#C6916B"' );
+ });
+
+ test( 'data-transition attributes with default separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="concave"]' ).length, 1, 'found one vertical slide with data-transition="zoom"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section[data-transition="page"]' ).length, 1, 'found one vertical slide with data-transition="fade"' );
+ strictEqual( document.querySelectorAll( '.reveal .slides section [data-transition="concave"]' ).length, 1, 'found one slide with data-transition="zoom"' );
+ });
+
+ test( 'data-transition attributes with inline content', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section[data-background="#ff0000"]' ).length, 3, 'found three horizontal slides with data-background="#ff0000"' );
+ });
+
+} );
+
+Reveal.initialize();
+
diff --git a/reveal.js/test/test-markdown.html b/reveal.js/test/test-markdown.html
new file mode 100755
index 0000000..52b39ff
--- /dev/null
+++ b/reveal.js/test/test-markdown.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+ reveal.js - Test Markdown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/test-markdown.js b/reveal.js/test/test-markdown.js
new file mode 100755
index 0000000..d2bbba8
--- /dev/null
+++ b/reveal.js/test/test-markdown.js
@@ -0,0 +1,15 @@
+
+
+Reveal.addEventListener( 'ready', function() {
+
+ QUnit.module( 'Markdown' );
+
+ test( 'Vertical separator', function() {
+ strictEqual( document.querySelectorAll( '.reveal .slides>section>section' ).length, 2, 'found two slides' );
+ });
+
+
+} );
+
+Reveal.initialize();
+
diff --git a/reveal.js/test/test-pdf.html b/reveal.js/test/test-pdf.html
new file mode 100755
index 0000000..751ed26
--- /dev/null
+++ b/reveal.js/test/test-pdf.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+ reveal.js - Test PDF exports
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+ 3.3
+
+ 3.3.1
+ 3.3.2
+ 3.3.3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/test-pdf.js b/reveal.js/test/test-pdf.js
new file mode 100755
index 0000000..8ec34fd
--- /dev/null
+++ b/reveal.js/test/test-pdf.js
@@ -0,0 +1,15 @@
+
+Reveal.addEventListener( 'ready', function() {
+
+ // Only one test for now, we're mainly ensuring that there
+ // are no execution errors when running PDF mode
+
+ test( 'Reveal.isReady', function() {
+ strictEqual( Reveal.isReady(), true, 'returns true' );
+ });
+
+
+} );
+
+Reveal.initialize({ pdf: true });
+
diff --git a/reveal.js/test/test.html b/reveal.js/test/test.html
new file mode 100755
index 0000000..d08e4f0
--- /dev/null
+++ b/reveal.js/test/test.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+ reveal.js - Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3.3
+
+ 3.3.1
+ 3.3.2
+ 3.3.3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reveal.js/test/test.js b/reveal.js/test/test.js
new file mode 100755
index 0000000..a96b70b
--- /dev/null
+++ b/reveal.js/test/test.js
@@ -0,0 +1,597 @@
+
+// These tests expect the DOM to contain a presentation
+// with the following slide structure:
+//
+// 1
+// 2 - Three sub-slides
+// 3 - Three fragment elements
+// 3 - Two fragments with same data-fragment-index
+// 4
+
+
+Reveal.addEventListener( 'ready', function() {
+
+ // ---------------------------------------------------------------
+ // DOM TESTS
+
+ QUnit.module( 'DOM' );
+
+ test( 'Initial slides classes', function() {
+ var horizontalSlides = document.querySelectorAll( '.reveal .slides>section' )
+
+ strictEqual( document.querySelectorAll( '.reveal .slides section.past' ).length, 0, 'no .past slides' );
+ strictEqual( document.querySelectorAll( '.reveal .slides section.present' ).length, 1, 'one .present slide' );
+ strictEqual( document.querySelectorAll( '.reveal .slides>section.future' ).length, horizontalSlides.length - 1, 'remaining horizontal slides are .future' );
+
+ strictEqual( document.querySelectorAll( '.reveal .slides section.stack' ).length, 2, 'two .stacks' );
+
+ ok( document.querySelectorAll( '.reveal .slides section.stack' )[0].querySelectorAll( '.future' ).length > 0, 'vertical slides are given .future' );
+ });
+
+ // ---------------------------------------------------------------
+ // API TESTS
+
+ QUnit.module( 'API' );
+
+ test( 'Reveal.isReady', function() {
+ strictEqual( Reveal.isReady(), true, 'returns true' );
+ });
+
+ test( 'Reveal.isOverview', function() {
+ strictEqual( Reveal.isOverview(), false, 'false by default' );
+
+ Reveal.toggleOverview();
+ strictEqual( Reveal.isOverview(), true, 'true after toggling on' );
+
+ Reveal.toggleOverview();
+ strictEqual( Reveal.isOverview(), false, 'false after toggling off' );
+ });
+
+ test( 'Reveal.isPaused', function() {
+ strictEqual( Reveal.isPaused(), false, 'false by default' );
+
+ Reveal.togglePause();
+ strictEqual( Reveal.isPaused(), true, 'true after pausing' );
+
+ Reveal.togglePause();
+ strictEqual( Reveal.isPaused(), false, 'false after resuming' );
+ });
+
+ test( 'Reveal.isFirstSlide', function() {
+ Reveal.slide( 0, 0 );
+ strictEqual( Reveal.isFirstSlide(), true, 'true after Reveal.slide( 0, 0 )' );
+
+ Reveal.slide( 1, 0 );
+ strictEqual( Reveal.isFirstSlide(), false, 'false after Reveal.slide( 1, 0 )' );
+
+ Reveal.slide( 0, 0 );
+ strictEqual( Reveal.isFirstSlide(), true, 'true after Reveal.slide( 0, 0 )' );
+ });
+
+ test( 'Reveal.isFirstSlide after vertical slide', function() {
+ Reveal.slide( 1, 1 );
+ Reveal.slide( 0, 0 );
+ strictEqual( Reveal.isFirstSlide(), true, 'true after Reveal.slide( 1, 1 ) and then Reveal.slide( 0, 0 )' );
+ });
+
+ test( 'Reveal.isLastSlide', function() {
+ Reveal.slide( 0, 0 );
+ strictEqual( Reveal.isLastSlide(), false, 'false after Reveal.slide( 0, 0 )' );
+
+ var lastSlideIndex = document.querySelectorAll( '.reveal .slides>section' ).length - 1;
+
+ Reveal.slide( lastSlideIndex, 0 );
+ strictEqual( Reveal.isLastSlide(), true, 'true after Reveal.slide( '+ lastSlideIndex +', 0 )' );
+
+ Reveal.slide( 0, 0 );
+ strictEqual( Reveal.isLastSlide(), false, 'false after Reveal.slide( 0, 0 )' );
+ });
+
+ test( 'Reveal.isLastSlide after vertical slide', function() {
+ var lastSlideIndex = document.querySelectorAll( '.reveal .slides>section' ).length - 1;
+
+ Reveal.slide( 1, 1 );
+ Reveal.slide( lastSlideIndex );
+ strictEqual( Reveal.isLastSlide(), true, 'true after Reveal.slide( 1, 1 ) and then Reveal.slide( '+ lastSlideIndex +', 0 )' );
+ });
+
+ test( 'Reveal.getTotalSlides', function() {
+ strictEqual( Reveal.getTotalSlides(), 8, 'eight slides in total' );
+ });
+
+ test( 'Reveal.getIndices', function() {
+ var indices = Reveal.getIndices();
+
+ ok( indices.hasOwnProperty( 'h' ), 'h exists' );
+ ok( indices.hasOwnProperty( 'v' ), 'v exists' );
+ ok( indices.hasOwnProperty( 'f' ), 'f exists' );
+
+ Reveal.slide( 1, 0 );
+ strictEqual( Reveal.getIndices().h, 1, 'h 1' );
+ strictEqual( Reveal.getIndices().v, 0, 'v 0' );
+
+ Reveal.slide( 1, 2 );
+ strictEqual( Reveal.getIndices().h, 1, 'h 1' );
+ strictEqual( Reveal.getIndices().v, 2, 'v 2' );
+
+ Reveal.slide( 0, 0 );
+ strictEqual( Reveal.getIndices().h, 0, 'h 0' );
+ strictEqual( Reveal.getIndices().v, 0, 'v 0' );
+ });
+
+ test( 'Reveal.getSlide', function() {
+ equal( Reveal.getSlide( 0 ), document.querySelector( '.reveal .slides>section:first-child' ), 'gets correct first slide' );
+ equal( Reveal.getSlide( 1 ), document.querySelector( '.reveal .slides>section:nth-child(2)' ), 'no v index returns stack' );
+ equal( Reveal.getSlide( 1, 0 ), document.querySelector( '.reveal .slides>section:nth-child(2)>section:nth-child(1)' ), 'v index 0 returns first vertical child' );
+ equal( Reveal.getSlide( 1, 1 ), document.querySelector( '.reveal .slides>section:nth-child(2)>section:nth-child(2)' ), 'v index 1 returns second vertical child' );
+
+ strictEqual( Reveal.getSlide( 100 ), undefined, 'undefined when out of horizontal bounds' );
+ strictEqual( Reveal.getSlide( 1, 100 ), undefined, 'undefined when out of vertical bounds' );
+ });
+
+ test( 'Reveal.getSlideBackground', function() {
+ equal( Reveal.getSlideBackground( 0 ), document.querySelector( '.reveal .backgrounds>.slide-background:first-child' ), 'gets correct first background' );
+ equal( Reveal.getSlideBackground( 1 ), document.querySelector( '.reveal .backgrounds>.slide-background:nth-child(2)' ), 'no v index returns stack' );
+ equal( Reveal.getSlideBackground( 1, 0 ), document.querySelector( '.reveal .backgrounds>.slide-background:nth-child(2) .slide-background:nth-child(1)' ), 'v index 0 returns first vertical child' );
+ equal( Reveal.getSlideBackground( 1, 1 ), document.querySelector( '.reveal .backgrounds>.slide-background:nth-child(2) .slide-background:nth-child(2)' ), 'v index 1 returns second vertical child' );
+
+ strictEqual( Reveal.getSlideBackground( 100 ), undefined, 'undefined when out of horizontal bounds' );
+ strictEqual( Reveal.getSlideBackground( 1, 100 ), undefined, 'undefined when out of vertical bounds' );
+ });
+
+ test( 'Reveal.getSlideNotes', function() {
+ Reveal.slide( 0, 0 );
+ ok( Reveal.getSlideNotes() === 'speaker notes 1', 'works with ' );
+
+ Reveal.slide( 1, 0 );
+ ok( Reveal.getSlideNotes() === 'speaker notes 2', 'works with ' );
+ });
+
+ test( 'Reveal.getPreviousSlide/getCurrentSlide', function() {
+ Reveal.slide( 0, 0 );
+ Reveal.slide( 1, 0 );
+
+ var firstSlide = document.querySelector( '.reveal .slides>section:first-child' );
+ var secondSlide = document.querySelector( '.reveal .slides>section:nth-child(2)>section' );
+
+ equal( Reveal.getPreviousSlide(), firstSlide, 'previous is slide #0' );
+ equal( Reveal.getCurrentSlide(), secondSlide, 'current is slide #1' );
+ });
+
+ test( 'Reveal.getProgress', function() {
+ Reveal.slide( 0, 0 );
+ strictEqual( Reveal.getProgress(), 0, 'progress is 0 on first slide' );
+
+ var lastSlideIndex = document.querySelectorAll( '.reveal .slides>section' ).length - 1;
+
+ Reveal.slide( lastSlideIndex, 0 );
+ strictEqual( Reveal.getProgress(), 1, 'progress is 1 on last slide' );
+ });
+
+ test( 'Reveal.getScale', function() {
+ ok( typeof Reveal.getScale() === 'number', 'has scale' );
+ });
+
+ test( 'Reveal.getConfig', function() {
+ ok( typeof Reveal.getConfig() === 'object', 'has config' );
+ });
+
+ test( 'Reveal.configure', function() {
+ strictEqual( Reveal.getConfig().loop, false, '"loop" is false to start with' );
+
+ Reveal.configure({ loop: true });
+ strictEqual( Reveal.getConfig().loop, true, '"loop" has changed to true' );
+
+ Reveal.configure({ loop: false, customTestValue: 1 });
+ strictEqual( Reveal.getConfig().customTestValue, 1, 'supports custom values' );
+ });
+
+ test( 'Reveal.availableRoutes', function() {
+ Reveal.slide( 0, 0 );
+ deepEqual( Reveal.availableRoutes(), { left: false, up: false, down: false, right: true }, 'correct for first slide' );
+
+ Reveal.slide( 1, 0 );
+ deepEqual( Reveal.availableRoutes(), { left: true, up: false, down: true, right: true }, 'correct for vertical slide' );
+ });
+
+ test( 'Reveal.next', function() {
+ Reveal.slide( 0, 0 );
+
+ // Step through vertical child slides
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 1, v: 0, f: undefined } );
+
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 1, v: 1, f: undefined } );
+
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 1, v: 2, f: undefined } );
+
+ // Step through fragments
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: -1 } );
+
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 0 } );
+
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 1 } );
+
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 2 } );
+ });
+
+ test( 'Reveal.next at end', function() {
+ Reveal.slide( 3 );
+
+ // We're at the end, this should have no effect
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 3, v: 0, f: undefined } );
+
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 3, v: 0, f: undefined } );
+ });
+
+
+ // ---------------------------------------------------------------
+ // FRAGMENT TESTS
+
+ QUnit.module( 'Fragments' );
+
+ test( 'Sliding to fragments', function() {
+ Reveal.slide( 2, 0, -1 );
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: -1 }, 'Reveal.slide( 2, 0, -1 )' );
+
+ Reveal.slide( 2, 0, 0 );
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 0 }, 'Reveal.slide( 2, 0, 0 )' );
+
+ Reveal.slide( 2, 0, 2 );
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 2 }, 'Reveal.slide( 2, 0, 2 )' );
+
+ Reveal.slide( 2, 0, 1 );
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 1 }, 'Reveal.slide( 2, 0, 1 )' );
+ });
+
+ test( 'Hiding all fragments', function() {
+ var fragmentSlide = document.querySelector( '#fragment-slides>section:nth-child(1)' );
+
+ Reveal.slide( 2, 0, 0 );
+ strictEqual( fragmentSlide.querySelectorAll( '.fragment.visible' ).length, 1, 'one fragment visible when index is 0' );
+
+ Reveal.slide( 2, 0, -1 );
+ strictEqual( fragmentSlide.querySelectorAll( '.fragment.visible' ).length, 0, 'no fragments visible when index is -1' );
+ });
+
+ test( 'Current fragment', function() {
+ var fragmentSlide = document.querySelector( '#fragment-slides>section:nth-child(1)' );
+
+ Reveal.slide( 2, 0 );
+ strictEqual( fragmentSlide.querySelectorAll( '.fragment.current-fragment' ).length, 0, 'no current fragment at index -1' );
+
+ Reveal.slide( 2, 0, 0 );
+ strictEqual( fragmentSlide.querySelectorAll( '.fragment.current-fragment' ).length, 1, 'one current fragment at index 0' );
+
+ Reveal.slide( 1, 0, 0 );
+ strictEqual( fragmentSlide.querySelectorAll( '.fragment.current-fragment' ).length, 0, 'no current fragment when navigating to previous slide' );
+
+ Reveal.slide( 3, 0, 0 );
+ strictEqual( fragmentSlide.querySelectorAll( '.fragment.current-fragment' ).length, 0, 'no current fragment when navigating to next slide' );
+ });
+
+ test( 'Stepping through fragments', function() {
+ Reveal.slide( 2, 0, -1 );
+
+ // forwards:
+
+ Reveal.next();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 0 }, 'next() goes to next fragment' );
+
+ Reveal.right();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 1 }, 'right() goes to next fragment' );
+
+ Reveal.down();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 2 }, 'down() goes to next fragment' );
+
+ Reveal.down(); // moves to f #3
+
+ // backwards:
+
+ Reveal.prev();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 2 }, 'prev() goes to prev fragment' );
+
+ Reveal.left();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 1 }, 'left() goes to prev fragment' );
+
+ Reveal.up();
+ deepEqual( Reveal.getIndices(), { h: 2, v: 0, f: 0 }, 'up() goes to prev fragment' );
+ });
+
+ test( 'Stepping past fragments', function() {
+ var fragmentSlide = document.querySelector( '#fragment-slides>section:nth-child(1)' );
+
+ Reveal.slide( 0, 0, 0 );
+ equal( fragmentSlide.querySelectorAll( '.fragment.visible' ).length, 0, 'no fragments visible when on previous slide' );
+
+ Reveal.slide( 3, 0, 0 );
+ equal( fragmentSlide.querySelectorAll( '.fragment.visible' ).length, 3, 'all fragments visible when on future slide' );
+ });
+
+ test( 'Fragment indices', function() {
+ var fragmentSlide = document.querySelector( '#fragment-slides>section:nth-child(2)' );
+
+ Reveal.slide( 3, 0, 0 );
+ equal( fragmentSlide.querySelectorAll( '.fragment.visible' ).length, 2, 'both fragments of same index are shown' );
+
+ // This slide has three fragments, first one is index 0, second and third have index 1
+ Reveal.slide( 2, 2, 0 );
+ equal( Reveal.getIndices().f, 0, 'returns correct index for first fragment' );
+
+ Reveal.slide( 2, 2, 1 );
+ equal( Reveal.getIndices().f, 1, 'returns correct index for two fragments with same index' );
+ });
+
+ test( 'Index generation', function() {
+ var fragmentSlide = document.querySelector( '#fragment-slides>section:nth-child(1)' );
+
+ // These have no indices defined to start with
+ equal( fragmentSlide.querySelectorAll( '.fragment' )[0].getAttribute( 'data-fragment-index' ), '0' );
+ equal( fragmentSlide.querySelectorAll( '.fragment' )[1].getAttribute( 'data-fragment-index' ), '1' );
+ equal( fragmentSlide.querySelectorAll( '.fragment' )[2].getAttribute( 'data-fragment-index' ), '2' );
+ });
+
+ test( 'Index normalization', function() {
+ var fragmentSlide = document.querySelector( '#fragment-slides>section:nth-child(3)' );
+
+ // These start out as 1-4-4 and should normalize to 0-1-1
+ equal( fragmentSlide.querySelectorAll( '.fragment' )[0].getAttribute( 'data-fragment-index' ), '0' );
+ equal( fragmentSlide.querySelectorAll( '.fragment' )[1].getAttribute( 'data-fragment-index' ), '1' );
+ equal( fragmentSlide.querySelectorAll( '.fragment' )[2].getAttribute( 'data-fragment-index' ), '1' );
+ });
+
+ asyncTest( 'fragmentshown event', function() {
+ expect( 2 );
+
+ var _onEvent = function( event ) {
+ ok( true, 'event fired' );
+ }
+
+ Reveal.addEventListener( 'fragmentshown', _onEvent );
+
+ Reveal.slide( 2, 0 );
+ Reveal.slide( 2, 0 ); // should do nothing
+ Reveal.slide( 2, 0, 0 ); // should do nothing
+ Reveal.next();
+ Reveal.next();
+ Reveal.prev(); // shouldn't fire fragmentshown
+
+ start();
+
+ Reveal.removeEventListener( 'fragmentshown', _onEvent );
+ });
+
+ asyncTest( 'fragmenthidden event', function() {
+ expect( 2 );
+
+ var _onEvent = function( event ) {
+ ok( true, 'event fired' );
+ }
+
+ Reveal.addEventListener( 'fragmenthidden', _onEvent );
+
+ Reveal.slide( 2, 0, 2 );
+ Reveal.slide( 2, 0, 2 ); // should do nothing
+ Reveal.prev();
+ Reveal.prev();
+ Reveal.next(); // shouldn't fire fragmenthidden
+
+ start();
+
+ Reveal.removeEventListener( 'fragmenthidden', _onEvent );
+ });
+
+
+ // ---------------------------------------------------------------
+ // AUTO-SLIDE TESTS
+
+ QUnit.module( 'Auto Sliding' );
+
+ test( 'Reveal.isAutoSliding', function() {
+ strictEqual( Reveal.isAutoSliding(), false, 'false by default' );
+
+ Reveal.configure({ autoSlide: 10000 });
+ strictEqual( Reveal.isAutoSliding(), true, 'true after starting' );
+
+ Reveal.configure({ autoSlide: 0 });
+ strictEqual( Reveal.isAutoSliding(), false, 'false after setting to 0' );
+ });
+
+ test( 'Reveal.toggleAutoSlide', function() {
+ Reveal.configure({ autoSlide: 10000 });
+
+ Reveal.toggleAutoSlide();
+ strictEqual( Reveal.isAutoSliding(), false, 'false after first toggle' );
+ Reveal.toggleAutoSlide();
+ strictEqual( Reveal.isAutoSliding(), true, 'true after second toggle' );
+
+ Reveal.configure({ autoSlide: 0 });
+ });
+
+ asyncTest( 'autoslidepaused', function() {
+ expect( 1 );
+
+ var _onEvent = function( event ) {
+ ok( true, 'event fired' );
+ }
+
+ Reveal.addEventListener( 'autoslidepaused', _onEvent );
+ Reveal.configure({ autoSlide: 10000 });
+ Reveal.toggleAutoSlide();
+
+ start();
+
+ // cleanup
+ Reveal.configure({ autoSlide: 0 });
+ Reveal.removeEventListener( 'autoslidepaused', _onEvent );
+ });
+
+ asyncTest( 'autoslideresumed', function() {
+ expect( 1 );
+
+ var _onEvent = function( event ) {
+ ok( true, 'event fired' );
+ }
+
+ Reveal.addEventListener( 'autoslideresumed', _onEvent );
+ Reveal.configure({ autoSlide: 10000 });
+ Reveal.toggleAutoSlide();
+ Reveal.toggleAutoSlide();
+
+ start();
+
+ // cleanup
+ Reveal.configure({ autoSlide: 0 });
+ Reveal.removeEventListener( 'autoslideresumed', _onEvent );
+ });
+
+
+ // ---------------------------------------------------------------
+ // CONFIGURATION VALUES
+
+ QUnit.module( 'Configuration' );
+
+ test( 'Controls', function() {
+ var controlsElement = document.querySelector( '.reveal>.controls' );
+
+ Reveal.configure({ controls: false });
+ equal( controlsElement.style.display, 'none', 'controls are hidden' );
+
+ Reveal.configure({ controls: true });
+ equal( controlsElement.style.display, 'block', 'controls are visible' );
+ });
+
+ test( 'Progress', function() {
+ var progressElement = document.querySelector( '.reveal>.progress' );
+
+ Reveal.configure({ progress: false });
+ equal( progressElement.style.display, 'none', 'progress are hidden' );
+
+ Reveal.configure({ progress: true });
+ equal( progressElement.style.display, 'block', 'progress are visible' );
+ });
+
+ test( 'Loop', function() {
+ Reveal.configure({ loop: true });
+
+ Reveal.slide( 0, 0 );
+
+ Reveal.left();
+ notEqual( Reveal.getIndices().h, 0, 'looped from start to end' );
+
+ Reveal.right();
+ equal( Reveal.getIndices().h, 0, 'looped from end to start' );
+
+ Reveal.configure({ loop: false });
+ });
+
+
+ // ---------------------------------------------------------------
+ // LAZY-LOADING TESTS
+
+ QUnit.module( 'Lazy-Loading' );
+
+ test( 'img with data-src', function() {
+ strictEqual( document.querySelectorAll( '.reveal section img[src]' ).length, 1, 'Image source has been set' );
+ });
+
+ test( 'video with data-src', function() {
+ strictEqual( document.querySelectorAll( '.reveal section video[src]' ).length, 1, 'Video source has been set' );
+ });
+
+ test( 'audio with data-src', function() {
+ strictEqual( document.querySelectorAll( '.reveal section audio[src]' ).length, 1, 'Audio source has been set' );
+ });
+
+ test( 'iframe with data-src', function() {
+ Reveal.slide( 0, 0 );
+ strictEqual( document.querySelectorAll( '.reveal section iframe[src]' ).length, 0, 'Iframe source is not set' );
+ Reveal.slide( 2, 1 );
+ strictEqual( document.querySelectorAll( '.reveal section iframe[src]' ).length, 1, 'Iframe source is set' );
+ Reveal.slide( 2, 2 );
+ strictEqual( document.querySelectorAll( '.reveal section iframe[src]' ).length, 0, 'Iframe source is not set' );
+ });
+
+ test( 'background images', function() {
+ var imageSource1 = Reveal.getSlide( 0 ).getAttribute( 'data-background-image' );
+ var imageSource2 = Reveal.getSlide( 1, 0 ).getAttribute( 'data-background' );
+
+ // check that the images are applied to the background elements
+ ok( Reveal.getSlideBackground( 0 ).style.backgroundImage.indexOf( imageSource1 ) !== -1, 'data-background-image worked' );
+ ok( Reveal.getSlideBackground( 1, 0 ).style.backgroundImage.indexOf( imageSource2 ) !== -1, 'data-background worked' );
+ });
+
+
+ // ---------------------------------------------------------------
+ // EVENT TESTS
+
+ QUnit.module( 'Events' );
+
+ asyncTest( 'slidechanged', function() {
+ expect( 3 );
+
+ var _onEvent = function( event ) {
+ ok( true, 'event fired' );
+ }
+
+ Reveal.addEventListener( 'slidechanged', _onEvent );
+
+ Reveal.slide( 1, 0 ); // should trigger
+ Reveal.slide( 1, 0 ); // should do nothing
+ Reveal.next(); // should trigger
+ Reveal.slide( 3, 0 ); // should trigger
+ Reveal.next(); // should do nothing
+
+ start();
+
+ Reveal.removeEventListener( 'slidechanged', _onEvent );
+
+ });
+
+ asyncTest( 'paused', function() {
+ expect( 1 );
+
+ var _onEvent = function( event ) {
+ ok( true, 'event fired' );
+ }
+
+ Reveal.addEventListener( 'paused', _onEvent );
+
+ Reveal.togglePause();
+ Reveal.togglePause();
+
+ start();
+
+ Reveal.removeEventListener( 'paused', _onEvent );
+ });
+
+ asyncTest( 'resumed', function() {
+ expect( 1 );
+
+ var _onEvent = function( event ) {
+ ok( true, 'event fired' );
+ }
+
+ Reveal.addEventListener( 'resumed', _onEvent );
+
+ Reveal.togglePause();
+ Reveal.togglePause();
+
+ start();
+
+ Reveal.removeEventListener( 'resumed', _onEvent );
+ });
+
+
+} );
+
+Reveal.initialize();
+
diff --git a/session1.html b/session1.html
new file mode 100644
index 0000000..651ee8f
--- /dev/null
+++ b/session1.html
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+ CFG session 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session 1: Python basics
+
+ Welcome to the autumn/winter courses
+
+ Tania Allard, PhD
+
+
+ @ixek
+
+
+
+
+
+ First things first...
+ Make sure you have all the following components
+
+ Python3.x (3.6 preferably)
+ Git
+ A text editor: Atom, Sublime
+ GitKraken
+ pip
+
+
+
+
+
+
+ What is Python?
+
+ It is an open source programming language
+ with an emphasis on code readability...
+ which makes it great for beginners!!!
+
+
+
+
+
+ Using Python from the terminal
+ Open a terminal (Mac) or Git Bash (Windows) and open Python
+ in interactive mode by typing:
+ python -i
+
+ Your terminal will look similar to this:
+
+
+
+
+ The
+ Zen of Python summarises the language's core principles.
+
+ Type import this and see what you get!
+
+
+ Note: if you want to exit Python
+ type quit()
+
+
+
+
+ What will we be doing during this course?
+
+
+
+
+
+
+ Hands on: Creating your first script
+ Open your text editor and create a new script called
+ hello.py
+
+ Type the following command
+ print("Hello World!")
+ and save the script
+
+
+
+
+ Time to run your script
+
+ Open your terminal if needed
+ Make sure you are in the file location
+ (if you need to make sure type ls)
+ Type the following command
+ python hello.py
+
+
+
+
+ Let's add some code to our **hello.py** file to do some cool stuff
+ with strings
+
+ String: collection of characters denoted by " "
+
+ ```python
+ print('Bob'*3)
+ print('Bob'+3)
+ print('hello'.upper())
+ print('GOODBYE'.lower())
+ print("the lord of the rings".title())
+ ```
+ Now run your script from the terminal:
+ ```bash
+ python hello.py
+ ```
+
+
+
+
+ .upper() .lower() .title() are called
+ methods
+
+
+ When attached to a 'string' they tell Python to do some processing
+ on the strings
+
+
+
+
+
+ Names & Variables
+ You can assign a name to a value in Python e.g.
+ age = 25
+
+
+ you can change the name at any point
+
+ age ='still young'
+
+
+
+
+ Notes on naming variables
+
+ Start with lowercase letter:
+ chocolate ='yum'
+ Use underscore to separate words:
+ my_name = 'Tania'
+ Use meaningful names:
+ speed = 100 instead of x = 100
+
+
+
+
+ ### String formatting
+ When you place the value of a variable and place it
+ in a string:
+ ``` python
+ age = 25
+ hobby = "dancing"
+ description = "Her age is {} and she likes {}.".format(age, hobby)
+ print(description)
+ ```
+ ***
+ Add this to your script and run it!
+
+
+
+ ### Commenting
+
+ You can write comments for you or others using a `#` in Python
+ meaning that everything after this symbol will be ignored
+ ```python
+ greeting ="Hello World!" # This creates a variable
+ greeting.upper() # This converts the string to uppercase
+ print (greeting*3) # This prints the string 3 times
+ ```
+
+
+
+ Extra challenge!
+ You should all now have a fork of the GitHub repository
+
+ collaborative.py script in the file
+ session1
+
+
+
+ Extra challenge!
+
+ Add two variables: your name and your favourite food
+ Add a statement to print the variables in a sentence
+ Make sure this runs
+ Create a pull request to the original fork!!!
+
+
+
+
+
+
The end!
+
+
+
+
+
+
+
+
+
+
+
diff --git a/session2.html b/session2.html
new file mode 100644
index 0000000..6ccefda
--- /dev/null
+++ b/session2.html
@@ -0,0 +1,385 @@
+
+
+
+
+
+
+ CFG session 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session 2: User input, functions, loops, conditional statements
+
+ The session with the long title
+
+ Christopher McIntyre
+
+
+ @cgmcintyr
+
+
+
+
+
+
+
+
+ Recap: Using Python from the terminal
+ Open a terminal (Mac) or Git Bash (Windows) and open Python
+ in interactive mode by typing:
+ python -i
+
+ Your terminal will look similar to this:
+
+
+
+
+
+ Recap: Creating a script
+ Open your text editor and create a new file called
+ session2.py
+ that contains the following:
+ print("Hello World!") and save the file.
+
+
+
+
+ Recap: Running your script
+
+ Open your terminal if needed
+ Make sure you are in the file location
+ (if you need to make sure type ls)
+ Type the following command
+ python session2.py
+ Your script should reply with Hello world
+
+
+
+
+
+ Recap: Strings and Variables
+ String: a collection of characters denoted by " "
+ Variable: a named location that stores some data
+
+
+
+ Let's change our code to print a variable
+ that stores a string representation of your name.
+
+ ```python
+ name = "Your name here"
+ print("Hello {}!".format(name))
+ ```
+ Now run your script from the terminal:
+ ```bash
+ python session2.py
+ ```
+
+ Your script should reply with Hello Your name here
+
+
+
+
+ Recap: Changing variables
+ The contents of a variable can be changed any time after it has been declared
+
+ ```python
+ name = "Your name here"
+ name = name.upper()
+ print("Hello {}!".format(name))
+ ```
+ Now run your script from the terminal:
+ ```bash
+ python session2.py
+ ```
+
+ Your script should reply with Hello YOUR NAME HERE
+
+
+
+
+ Recap: Github
+ Lets push your code to github to keep a history of your changes.
+ We can use GitKraken to do this.
+
+
+
+
+
+ User input
+ A program is made much more useful by allowing a user to interact with it
+ We can use the input method to get a user's name.
+
+
+
+ Open your session2.py file and replace the contents with the following
+
+ ```python
+ print("What's your name?")
+ name = input()
+ print("Hello {}!".format(name))
+ ```
+ Now run your script from the terminal:
+ ```bash
+ python session2.py
+ ```
+
+
+
+
+ Functions
+ Programming the same task can get boring
+ A function allows us to reuse code by giving a
+ name to a block of code
+
+
+
+ Create a new file called my_first_function.py and enter the following
+
+ ```python
+ def hello_world():
+ print("Hello World!")
+
+ hello_world()
+ hello_world()
+ hello_world()
+ ```
+ Now run your script from the terminal:
+ ```bash
+ python my_first_function.py
+ ```
+
+ It should print out Hello World! 3 times
+
+
+
+
+ def tells python we are defining a function
+ hello_world is the unique name of our function*
+ () the brackets tell python this function has zero arguments
+ : the colon tells python the indented lines below this line are a code block
+ hello_world() is how we call our function
+
+
+
+ *be careful not to overwrite default python functions like
+ print or sum
+
+
+
+
+
+ Arguments
+ Arguments are values passed into a function
+
+ Sometimes we have a block of code we want customise when we call it
+ Arguments go in the brackets () when calling a function
+ e.g. print("hello") prints the value of its argument, which happens to be the string "hello"
+
+
+
+
+ Let's start some arguments!
+ Let's see how we can use arguments ourselves
+
+
+
+
+ Create a new file called more_arguments.py and enter the following
+
+ ```python
+ def add_two_numbers():
+ number1 =1
+ number2 =2
+ answer = number1 + number2
+ print("{} plus {} is {}".format(number1, number2, answer))
+
+ add_two_numbers()
+ ```
+
+ This is a function with zero arguments (the brackets are empty in the definition)
+
+
+
+
+ Edit more_arguments.py and replace the old function with the following
+
+ ```python
+ def add_two_numbers_from_args(number1, number2):
+ answer = number1 + number2
+ print"{} plus {} is {}".format(number1, number2, answer)
+
+ add_two_numbers_from_args(5,10)
+ add_two_numbers_from_args(1,11)
+ add_two_numbers_from_args(1,12)
+ ```
+
+ This is a function with two arguments
+ Arguments are named variables only available in the function
+
+
+
+ Returning stuff from functions
+ Functions do stuff like crunch numbers or manipulate data, and can then return the information they calculate
+
+
+
+
+ Create a new file called returning.py and enter the following
+
+ ```python
+ def add_two_numbers_and_return_value():
+ number1 =1
+ number2 =2
+ answer = number1 + number2
+ return answer
+
+ returned_value = add_two_numbers_and_return_value()
+ print (returned_value)
+ ```
+
+ Extra challenge change the function to return the result of adding two arguments together
+
+
+
+
+
+
+ Lists are used to store a list of items
+
+ ```python
+ empty_list = []
+ numbers = [0, 1, 2, 3, 4, 5]
+ fruits = ["pineapples", "oranges", "bananas"]
+ mixed = [15, "sunshine", "jumper", 4, "sky"]
+ ```
+
+
+
+
+ Lists are:
+
+ List items go between square brackets
+ Items in a list are separated by commas
+ Strings in a list must be in quotation marks
+
+
+
+
+
+ You can access an item in a list by using its name and its index number, like this:
+
+ ```python
+ print(fruits[0])
+ ```
+
+ In the world of programming, the first item in a list is always at position 0 (its "index" position). This means that [0] is the first item in the list, [1] is the second item, and so on.
+
+
+
+
+
+
+ For loops can be used to perform actions on each element in a list
+
+ ```python
+ my_shopping_cart = ["cake", "plates", "plastic forks", "juice", "cups"]
+
+ for item in my_shopping_cart:
+ print (item)
+
+ for x in my_shopping_cart:
+ print("hello!")
+ print(x)
+ ```
+
+
+
+
+ Programming with logic
+
+
+
+
+ Create a new file called logic.py and enter the following
+
+ ```python
+ number =input("Enter a number between 1 and 10: ")
+ number =int(number)
+
+ if number > 10:
+ print("Too high!")
+
+ if number <= 0:
+ print("Too low!")
+ ```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/session3.html b/session3.html
new file mode 100644
index 0000000..2247bb3
--- /dev/null
+++ b/session3.html
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+ CFG session 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session 3: Flask - using Python & HTML
+ together (Part 1)
+
+ Linking together the back-end with the front-end
+ Darren Vong
+
+
+ @mrdarrenv
+
+
+
+
+
+
+
+
+ Recap: Functions
+
+
+ A function is a named block of reusable code .
+
+ A function can have no arguments...
+
+
+ ... or it can take any number of arguments,
+ which goes inside the brackets () when we define
+ our function.
+
+
+ A function may optionally return some information
+
+
+ This could be some value from a result of a calculation, or
+ it can be another function !
+
+
+
+
+
+
+ Some words of caution about functions...
+
+
+ Be careful not to overwrite built-in Python functions like print !
+
+
+
+
+ Create a file named session2_recap.py and enter the following:
+
+ ```python
+ def print_hello():
+ print("Hello world!")
+
+ def add_one(number):
+ return number + 1
+
+ print_hello()
+ eight_plus_one = add_one(8)
+ print(eight_plus_one)
+ ```
+
+
+ It should print Hello world! followed by
+ 9 on the next line.
+
+
+
+
+
+ Recap: Lists
+
+
+ Lists are an ordered collection of items
+ List items go between square brackets [ ]
+
+ We can put any items in a list, each of which is separated by a comma
+
+ The first item starts at position (index) 0!
+
+
+
+
+ Replace the content of session2_recap.py with the following:
+
+ ```python
+ instructors = ["Chris", "Darren", "Nina", "Tania"]
+ print(f"The second instructor in the list is {instructors[1]}")
+ ```
+
+
+ It should print The second instructor in the list is Darren .
+
+
+
+
+
+ Recap: For loops
+
+
+ Commonly used to go through each item in a collection of items ,
+ such as a list or a dictionary .
+
+ Since we saw how to loop through a list last time, for completeness,
+ let's see an example on how to loop through a dictionary...
+
+
+
+ Replace the content of session2_recap.py with the following:
+
+ ```python
+ member = {"name": "Darren", "favourite_language": "Python"}
+ for key, value in member.items():
+ print(f"The member's {key.replace('_', ' ')} is {value}.")
+ ```
+
+
+ It should print The member's name is Darren. followed by
+ The member's favourite language is Python.
+
+
+
+
+
+ Recap: Logic
+
+
+ Logic allows our program to make decisions based on given conditions.
+
+ A condition tests whether something is True
+ or not
+
+
+ Let's see our final recap example before moving on...
+
+
+
+ Replace the content of session2_recap.py with the following:
+
+ ```python
+ age = 20
+ if age < 18:
+ print("You are too young to drink! Naughty!")
+ elif age > 65:
+ print("You're old... maybe you should chill out on drinking!")
+ else:
+ print("Drink responsibly (if you must)!")
+ ```
+
+
+ It should print Drink responsibly (if you must)!
+
+
+
+
+ And now...
+ Let's talk about Flask !
+
+
+
+
+
+
+ Alright, it's not quite the same kind of flask that Cumberbatch is holding...
+
+
+
+
+ A detour...
+
+
+ Before we cover Flask in more details, there are some important concepts we need to go
+ through first...
+
+ So for the time being:
+
+ Flask is a framework (more on what that means later)
+ which allows us to control our server using Python.
+
+
+
+
+
+ Why do we need/use a server?
+
+
+ A server is used so we can share our web application
+ with the world over the Internet.
+
+
+ But... how does it help make our application accessible over the Internet?
+
+ To understand that, we need to take a look at
+ what actually happens when we try to access our application
+ through our browser.
+
+
+
+
+
+
+
https://pawlean.com
+
+
<!DOCTYPE html>
+
<html>...</html>
+
+
+
+
+
+
+
+ When we type in a web address (URL) in our browser, we are in fact sending
+ a request to the server running the website we want to visit
+
+
+ Based on the URL we typed in, the server uses this information to generate the
+ correct HTML page to send back as a response .
+
+
+ Our browser then processes the received HTML, along with any CSS and JavaScript
+ on the page, and renders out the pretty web pages that we see!
+
+
+
+
+
+
+ If you did the Beginners course...
+
+
+ Your pretty HTML pages are actually being run (served) over the GitHub server, which
+ is what allowed you to see your projects publicly online!
+
+
+
+
+ And now...
+ Let's talk about Flask for real this time!
+
+
+
+
+ What is Flask?
+
+
+
+ Flask is a framework - a set of code written by some other clever
+ people that we can re-use
+
+
+ It's better to use Flask's code, rather than writing our own, to handle the requests
+ and responses we talked about earlier since it's quite complicated!
+
+
+ Re-using Flask's code deals with said complex tasks, allowing us to focus on the
+ application we want to build!
+
+
+
+
+
+ So... how does Flask work?
+
+
+
+
+ When we first start a Flask program, it starts up a server which we have
+ control over using Python. Users can then access our website in their browser!
+
+
+
+
+ Whenever a user type in the URL to go on our website, it goes through the exact journey
+ shown on the diagram earlier... with one exception
+
+
+ A diagram will hopefully clear this up...
+
+
+
+
+
+
+
+
+
+
+
+ The only key difference in this diagram from our previous one is that rather than returning the HTML
+ directly , Flask runs the HTML through a filter called a template engine (Jinja2).
+
+
+ This allows HTML to be generated dynamically
+ rather than having to write them all in full (by hand) in advance, which can get tedious quickly!
+
+
+
+
+
+
+ Hands on: our first Flask program
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/session4.html b/session4.html
new file mode 100644
index 0000000..20d9b95
--- /dev/null
+++ b/session4.html
@@ -0,0 +1,366 @@
+
+
+
+
+
+
+ CFG session 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session 4: Flask - using Python & HTML
+ together (Part 2)
+
+ Linking together the back-end with the front-end
+ Darren Vong
+
+
+ @mrdarrenv
+
+
+
+
+
+
+
+
+ Recap: Facebook Flask Challenge
+
+
+
+
+
+
+
+ The two bugs in the application highlight some commonly
+ made mistakes:
+
+
+
+ 1. Be careful not to name your Python file flask.py , or
+ for future references, any name that's identical to the library you are
+ going to import, as your copy of the code will hide
+ the actual flask library you want to import!
+
+
+
+
+
+
+ 2. The app.run(debug=True) line is
+ wrongly indented : unlike other languages which
+ mark a block of code with some form of brackets, Python relies on the
+ indentations to do the same job, so pay attention to
+ them in your code!
+
+
+
+
+
+ Recap: What happens when we visit a web page?
+
+
+
+
+
+
+
+ https://pawlean.com
+
+
+
+ <!DOCTYPE html>
+
+
+ <html>...</html>
+
+
+
+
+
+
+
+
+ Typing in the URL of the page sends a request to the server
+ running the website.
+
+
+ Using the information in the request with the URL, the server generates the
+ right HTML page as a response back to our browser.
+
+
+ Our browser processes the HTML response, along with any included CSS and
+ JavaScript, then renders it out nicely for us to see in our browser.
+
+
+
+
+
+
+
+
+ A framework containing code written by somebody else
+ which deals with the complexity of handling requests &
+ responses , so we can focus on writing the interesting part of
+ our application.
+
+
+
+
+
+ Before returning the HTML, Flask runs it through a
+ template engine
+ which generates most of the HTML
+ dynamically before returning it as a response to our browser.
+
+
+
+
+
+
+ Recap: Routing
+
+
+ Not explicitly mentioned in the last session, but the app.route(...)
+ "magic function" binds the URL specified to the standard Python
+ function which will then handle the requests from users.
+
+
+ Flask uses relative URLs - this is usually the string after the
+ first single slash.
+
+
+
+
+
+
+ Recap: Routing (cont.)
+
+
+ Relative URLs are used so that our application works in a consistent way ,
+ regardless of where we host it.
+
+
+ In our case where we have been developing locally, /hello is
+ a relative URL with respect to the host localhost:5000 , and so
+ can be accessed by visiting localhost:5000/hello
+
+
+
+
+
+
+
+ What is a template engine? Why do we use it?
+
+
+ To understand why we write HTML templates and use a
+ template engine to process them, we need to take a quick look
+ at how web pages were made before template engines existed .
+
+
+
+
+
+ Without templates, web pages have to be served as static HTML files
+ when the user visits the page. These have to be written up in full
+ before a user visit our site.
+
+
+ This is essentially what you did if you completed the Beginners course!
+
+ Revisiting a familiar diagram might make this clearer...
+
+
+
+
+
+
+ https://pawlean.com
+
+
+
+ <!DOCTYPE html>
+
+
+ <html>...</html>
+
+
+
+
+
+
+
+ Problems with static HTML pages ...
+
+
+ It gets tedious as we'd have to write the HTML for
+ each page we intend our users to visit from scratch
+ (or lots of copy and pasting of common parts), even though there are only
+ small differences between pages.
+
+ Repetition of code is (generally) a bad idea.
+
+
+
+
+ Problems with static HTML pages ... (cont.)
+
+
+ Writing the HTML ahead of the user's visit for dynamic pages
+ becomes tricky when we only know how to lay out & style the page, but we
+ don't know what the content looks like until they visit the page.
+
+
+
+
+
+ Facebook and Twitter are great examples: the feed is different for everyone visiting,
+ as it depends on many variable factors such as who they follow/are friends with.
+
+
+
+ Solution: write and use HTML templates!
+
+
+
+ How do templates solve our problems?
+
+ When writing templates, we only need to write in the HTML parts that
+ we know for definite .
+
+
+ For parts we don't know, the HTML is generated with some logics using
+ Python-like code with data from a user's request when they
+ visit our website.
+
+ Let's see how we can do this in Flask !
+
+
+
+
+
+ Hands on: a Flask program using templates
+
+
+
+
+ More about requests...
+
+ Hopefully by now, you'd have noticed that the word request
+ has been mentioned repeatedly.
+
+
+ There's just one more little detail that we need to
+ understand about requests before we can move forward... the
+ request type !
+
+
+
+
+ More about requests...
+ Earlier, we said:
+
+ "Typing in the URL of the page sends a request to the server
+ running the website."
+
+
+ In fact, to be more precise, we are actually sending a GET request
+ to the server running the website.
+
+
+
+
+ The main problem with GET requests
+
+ If we want the user to send some extra data when they visit a page on our website,
+ the data are visible at the end of the URL.
+ This is all well and good if the data is not sensitive !
+
+
+ To solve this problem, browsers allows data to be sent using a different kind of request
+ known as a POST request , which is only accessible on our server.
+
+
+
+
+
+
+ Hands on: handling POST requests in our Flask program
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/session5.html b/session5.html
new file mode 100644
index 0000000..916f6d3
--- /dev/null
+++ b/session5.html
@@ -0,0 +1,359 @@
+
+
+
+
+
+
+ CFG session 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session 5: APIs
+
+ Querying data efficiently from other websites
+
+
+ Tania Allard, PhD
+
+
+ @ixek
+
+
+
+
+
+
+
+ What am I going to learn today?
+
+
+
+
+
+ What is an API?
+ Aplication Programming Interfaces or API's act as an
+ interface between your Python code and other services
+ allowing you to collect data programatically
+
+
+
+
+
+ What do you mean by programatically?
+ It means that your Python code will communicate
+ with a service (Twitter for example) using their API, collect the
+ data and will posteriorly process it somehow.
+
+
+
+
+ Full disclose!
+ The regular curriculum focuses on using Mailgun and the weather app API...
+ but we thought we would make it more interesting by
+ focusing on: Twitter, Spotify, and NASA's
+ APIs.
+
+
+
+ So how can I talk to APIs?
+
+ Using
+ requests (HTTP for humans) and the API in question
+ Using a Python specific library (e.g.
+ Tweepy ) to access the API
+
+
+
+
+
+
+ Setting your first API in Python
+ We will use
+ Open Weather Map
+
+ You will need:
+
+ An app id... don't worry you can use mine
+ requests installed... you know the drill:
+ pip install requests
+
+
+
+
+ ```python
+ import requests
+
+ id = "84600bca7507293656495e8972aec659"
+ payload = {'q':'Sheffield, UK', 'units':'metric', 'appid':id}
+
+ def query_weather(payload):
+ endpoint = "http://api.openweathermap.org/data/2.5/weather"
+ response = requests.get(endpoint, params=payload)
+
+ return response
+
+ response = query_weather(payload)
+
+ ```
+
+
+
+ You might have noticed something familiar by now...
+ response = requests.get(endpoint, params=payload)
+
+
+ We are using HTTP GET requests to collect data!
+
+
+
+
+ New terms introduced here
+
+ endpoint : the location or destination of the data
+ payload : information sent to the endpoint... think of
+ them to be some sort of search terms
+
+ Add to your script: print(response.url)
+
+ Note the payload 'parameters' are defined by the API
+
+
+
+ Response content
+ We now have a response object from which we can extract all the
+ information we need
+
+ On your script add: print(response.status_code)
+
+ If you get 200 it means your request
+ was successful
+
+
+
+ Retrieving the content
+ You can check the content by adding a print(response.text)
+
+ I know it is not a very nice format
+
+
+
+ JSON
+ We can use JSON (JavaScript Object Notation),
+ which
+ has a human readable notation and can be easily
+ interpreted by computers.
+ JSON data consists of pairs of keys and values
+
+
+
+ Add the following to your script:
+ ```python
+ def jsonify(response):
+ json_response = response.json()
+ return json_response
+
+
+ json_response = jsonify(response)
+ print("\n")
+ print(json_response)
+ ```
+
+
+
+ That is much more understandable!
+
+ So if you wanted to get, for example, the temperature you'd query
+ your data as:
+ json_response['main']['temp']
+ How would you query the location,
+ and weather?
+
+
+
+
+ Having fun with Twitter
+
+
+
+ We will use Tweepy for the next exercises
+ so if you have not installed it yet do it now:
+ pip install tweepy
+
+
+
+ Authentication
+ We need to authenticate before accessing the
+ data from Twitter. We will need the
+ consumer_key, consumer_secret, set_access_token,
+ access_token_secret you got when you created
+ the app online.
+
+
+
+
+ Very important stuff
+ You have to be very careful with your keys.
+ Never share them on GitHub.
+
+ For apps deployment you'd normally have a separate
+ encrypted config file (config.ini, config.yml)
+ and usually additional authentication measures.
+
+
+
+ ```python
+ import tweepy
+
+ consumer_key = 'xxx'
+ consumer_secret = 'xxx'
+ access_token = 'xxx'
+ access_token_secret = 'xxx'
+
+ auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
+ auth.set_access_token(access_token, access_token_secret)
+ twitter_api = tweepy.API(auth)
+
+ print('Logged in as {}'.format(twitter_api.me().name))
+ ```
+
+
+
+
+
+
+
+
+
+ ### Let's start by checking our timeline
+ ```python
+ def see_timeline(no_tweets):
+ for tweet in tweepy.Cursor(twitter.home_timeline).items(no_tweets):
+ print("\n {} tweeted by {}".format(status.text, status.user.name))
+ ```
+
+
+
+ Other things to do
+ - Check someone else's timeline
+ - Collect tweets containing certain keywords
+ or hashtags
+ - Post a tweet using Python
+
+
+
+
+ So far all the examples are on the 'backend'
+ All the data has been collected using Python... so how do we combine
+ Flask, html/css, and the data?
+ Let's find out
+
+
+
+ Working on an app that integrates everything
+
+~/CFG_app
+ |-- config.py # or config.yml config.ini
+ |-- /env # Virtual Environment
+ |-- /app # Our Application Module
+ |-- /helpers
+ |-- __init__.py
+ |-- twitter.py
+ |-- spotify.py
+ |-- /templates
+ |--- index.html
+ |--- /partials
+ |-- artist.html
+ |-- /static
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/session6.html b/session6.html
new file mode 100644
index 0000000..bfade9f
--- /dev/null
+++ b/session6.html
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+ CFG session 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session 6: Deploying your code
+
+ Sharing your app with the rest of the world
+
+
+ Darren Vong
+
+
+ @mrdarrenv
+
+
+
+
+
+
+
+
+
+ Recap: What is an API?
+
+
+ A way which allows our Python code to access external services programmatically .
+ Think of it as the "browser" (interface) for our code to interact with a service!
+
+ This interface allows our Python code to collect and process data from these
+ services more easily .
+
+
+
+
+
+
+
+ Recap: communicating with API in Python
+
+ We can do that either using:
+
+ requests (HTTP for humans) with our chosen API, or;
+
+ a Python library (like tweepy ) which defines various functions
+ that we can use in our code to access the API.
+
+ Here's the familiar diagram again...
+
+
+
+
+
+
+
+
+ http://api.openweathermap.org/data/2.5/weather
+
+
+
+ ```json
+ {
+ "clouds": {"all": 92},
+ ...
+ }
+ ```
+
+
+
+
+
+
+
+
+ Recap: API keys
+
+
+
+
+ Used by most services that provide an API to identify who is accessing it.
+
+ Authenticates our app and gives us permission to access the API accordingly.
+
+ NEVER commit your keys on GitHub - it's the equivalent of giving away
+ your passwords to everyone!
+
+
+
+
+
+ And now...
+ Let's move on to the course's final major topic: deployment !
+
+
+
+
+
+ Why should we upload (deploy) our app?
+
+
+
+ Up until now, we've been working on and running the website locally
+ on our laptops.
+
+
+ This means the rest of the world won't be able to see the awesome work
+ we have done!
+
+
+ By deploying our code to a cloud-based platform (Heroku
+ is the one we'll be using), our app will be assigned a publicly accessible URL that others
+ can visit to see it!
+
+
+
+
+
+ What is Heroku?
+
+
+ A cloud-based platform where we can push our code online to get our
+ flask app up and running.
+
+
+ Since one of the main purposes of computers on a cloud-based platform is to listen
+ out to user requests at all times, it's on 24/7 unlike our laptops.
+
+
+ Using Heroku also means that if our app becomes super popular, we can
+ easily add more computing power to the platform our app runs on without the need to
+ switch to a different one.
+
+
+
+
+
+
+
+
+
+ More on API keys
+
+
+ Recall from earlier (and last session) that API keys should be stored in a
+ separate config file that should never be committed to GitHub.
+
+
+ But the only way to add
+ any files to our app on Heroku is by pushing them to GitHub...
+
+
+ Although using a config file for API keys is normally the best practice, we'll have to forget it
+ due to the quirks of Heroku.
+
+
+
+
+ Remember this minor modification we made earlier to our Flask code?
+
+ ```python
+ if "PORT" in os.environ:
+ app.run(host="0.0.0.0", port=int(os.environ["PORT"]))
+ else:
+ app.run(debug=True)
+ ```
+
+
+ This gives us a hint on how we might store and access our API keys:
+ in config (environment) variables !
+
+
+
+ Config (environment) variables
+
+ Instead of storing our API keys in a config file, Heroku lets us store our API keys in
+ config variables instead.
+
+
+ The values of these config variables can be set in our app on the Heroku website under
+ Settings > Config Variables , then click the Reveal Config Vars button.
+
+
+
+
+
+
+
+
+ And here's how we'd access the values in our Python code:
+
+ ```python
+ import os
+ import tweepy
+
+ consumer_key = os.environ["twitter_consumer_key"]
+ consumer_secret = os.environ["twitter_consumer_secret"]
+ auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
+ ...
+ ```
+
+
+
+
+
+
+
+
+
+
+
diff --git a/slide.html b/slide.html
new file mode 100644
index 0000000..2d30858
--- /dev/null
+++ b/slide.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ CFG session 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session name
+
+ Whatever you want to add
+
+ Tania
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/slide_template.html b/templates/slide_template.html
new file mode 100644
index 0000000..04bb883
--- /dev/null
+++ b/templates/slide_template.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+ CodeFirst: girls Sheffield
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Session name
+
+ Whatever you want to add
+
+ Tania
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/welcome_email.md b/templates/welcome_email.md
new file mode 100644
index 0000000..a25f3a9
--- /dev/null
+++ b/templates/welcome_email.md
@@ -0,0 +1,23 @@
+Hi all!
+
+First off - congratulations for obtaining a place at the Code First: Girls Python course here at Sheffield University. It’s quite competitive to get in so give yourselves a pat on the back!!
+
+We’re so excited to meet you all on our first session next week on Wednesday 18th of October, 6 - 8pm, Diamond Workroom 1! We’ll go more into depth with introductions in the first session but my name is Pauline. Along with Lakshika, we’ll be your ambassadors for this A/W course.
+
+But before then we’d like to invite you this Thursday to our install-a-thon run by your main instructor, Tania Allard (Sanchez), supported with other instructors, Darren Vong, Chris McIntyre and Nina Swansick. Details are as follows:
+
+**Thursday 12th October
+5-7:30pm, Diamond Workroom 2**
+
+This session is entirely to help you out with installing the correct programs before the course starts and avoid delays installing/sorting out software issues over the course. It would be ideal if you can make it, but if you cannot make the session we can find time to get up to speed!
+
+Additionally, it would be very helpful for all of you to go through the first 8 chapters of the book ‘Learn Python the Hard way’: https://learnpythonthehardway.org/book/preface.html
+prior to the commencement of the course. It would aid in providing an overview of some very basic Python commands.
+
+Also share with us how excited you are about the course, using our hashtag #shefcodefirst on social media.
+
+We’re all really looking to meeting you! If you have any questions, please do not hesitate to ask.
+
+Best wishes,
+Pauline & Lakshika
+CFG Python Ambassadors
diff --git a/theteam.html b/theteam.html
new file mode 100644
index 0000000..fb74848
--- /dev/null
+++ b/theteam.html
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+ Code First: Advanced Python material
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Meet your team
+
+
Instructors
+
+
+
+
+
+
+
Tania Allard
+ Lead instructor / Research Software Engineer
+
+
+ Passionate software engineer who loves all data engineering,
+ data science, working in the open and reproducibility in
+ science stuff. Diversity and inclusivity in STEM advocate.
+
+ I also love cakes, lifting, and horror films/books. 🎂🏋🏻👻
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Darren Vong
+ Lead instructor / Research Software Engineer
+
+
+ Research software engineer specialising in AR/VR at the AMRC.
+ Avid Traveller. ✈️🌎 Occasional tea drinker. ☕
+
+
+
+
+
+
+
+
+
+
+
+
Christopher McIntyre
+ Python instructor
+
+
3rd year Computer Science student. Linux and FOSS nerd 🐧
+
+
+
+
+
+
+
+
+
+
+
+
Ambassadors
+
+
+
+
+
+
+
Pauline Narvas
+ CFG WebDev Instructor
+ and Ambassador
+
+
+ Biomedical Sciences student, old-school blogger, a
+ front-end developer, former Comms intern, women in STEM
+ advocate, HackMed organiser and a coding instructor and
+ ambassador at Code First: Girls.
+ Also: Power-liftin'
+ meditater. 😆
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Lakshika
+ CFG ambassador
+
+
Coming soon ...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+