diff --git a/app.js b/app.js index 8d3d8703e9a..bbc0e6192a8 100644 --- a/app.js +++ b/app.js @@ -139,7 +139,12 @@ var trusted = [ '*.ionicframework.com', 'https://syndication.twitter.com', '*.youtube.com', - '*.jsdelivr.net' + '*.jsdelivr.net', + '*.togetherjs.com', + 'https://*.togetherjs.com', + 'wss://hub.togetherjs.com', + '*.ytimg.com', + 'wss://fcctogether.herokuapp.com' ]; app.use(helmet.contentSecurityPolicy({ diff --git a/controllers/bonfire.js b/controllers/bonfire.js index d67edf35c4a..83431a390d4 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -222,7 +222,7 @@ exports.completedBonfire = function (req, res) { var isSolution = req.body.bonfireInfo.solution; if (isCompletedWith) { - var paired = User.find({"profile.username": isCompletedWith}).limit(1); + var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); paired.exec(function (err, pairedWith) { if (err) { return err; diff --git a/package.json b/package.json index 430ab671392..2e0986284d1 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "passport-local": "^1.0.0", "passport-oauth": "^1.0.0", "passport-twitter": "^1.0.2", + "ramda": "^0.10.0", "request": "^2.49.0", "sitemap": "^0.7.4", "uglify-js": "^2.4.15", diff --git a/public/js/application.js b/public/js/application.js index 56fa90e6a35..f792928b2b3 100644 --- a/public/js/application.js +++ b/public/js/application.js @@ -15,4 +15,5 @@ //= require lib/jquery-2.1.1.min //= require lib/bootstrap.min +//= require lib/together/togetherjs //= require main diff --git a/public/js/lib/bonfire/bonfireInit.js b/public/js/lib/bonfire/bonfireInit.js index ee885599f92..2b8d872e894 100644 --- a/public/js/lib/bonfire/bonfireInit.js +++ b/public/js/lib/bonfire/bonfireInit.js @@ -67,7 +67,7 @@ var requests; // (re)initializes the plugin var reset = function() { requests = 0; - plugin = new jailed.Plugin(path+'plugin_v0.1.2.js', api); + plugin = new jailed.Plugin(path+'plugin_v0.1.4.js', api); plugin.whenDisconnected( function() { // give some time to handle the last responce setTimeout( function() { diff --git a/public/js/lib/bonfire/plugin_v0.1.2.js b/public/js/lib/bonfire/plugin_v0.1.4.js similarity index 83% rename from public/js/lib/bonfire/plugin_v0.1.2.js rename to public/js/lib/bonfire/plugin_v0.1.4.js index 3c9942617b4..9aa5978c067 100644 --- a/public/js/lib/bonfire/plugin_v0.1.2.js +++ b/public/js/lib/bonfire/plugin_v0.1.4.js @@ -22,6 +22,16 @@ var run = function(code) { // protects even the worker scope from being accessed var runHidden = function(code) { + + var importScript = function(url) { + var error = null; + try { + importScripts(url); + } catch (e) { + error = e; + console.log('Unable to load ramda!'); + } + }; var indexedDB = null; var location = null; var navigator = null; @@ -44,7 +54,9 @@ var runHidden = function(code) { var dump = null; var onoffline = null; var ononline = null; - var importScripts = null; + importScript("https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js"); + var _ = R; + return eval(code); } diff --git a/public/js/lib/ramda/ramda.js b/public/js/lib/ramda/ramda.js new file mode 100644 index 00000000000..94149ef31d2 --- /dev/null +++ b/public/js/lib/ramda/ramda.js @@ -0,0 +1,4 @@ +/** + * Created by nathanleniz on 2/21/15. + */ +var R = require('ramda'); \ No newline at end of file diff --git a/public/js/lib/ramda/ramda.min.js b/public/js/lib/ramda/ramda.min.js new file mode 100644 index 00000000000..09afdfe60d1 --- /dev/null +++ b/public/js/lib/ramda/ramda.min.js @@ -0,0 +1,6 @@ +// Ramda v0.10.0 +// https://github.com/ramda/ramda +// (c) 2013-2015 Scott Sauyet and Michael Hurley +// Ramda may be freely distributed under the MIT license. + +(function(){"use strict";var n={ramda:"placeholder"},t=function(n,t){return n+t},r=function(n,t){for(var r=-1;++r0){for(var e,u=0,i=r[u],o=t(i);++ut},g=function(n,t,r){var e=0,u=n.length;for("number"==typeof r&&(e=0>r?Math.max(0,u+r):r);u>e;){if(n[e]===t)return e;++e}return-1},p=Array.isArray||function(n){return null!=n&&n.length>=0&&"[object Array]"===Object.prototype.toString.call(n)},m=Number.isInteger||function(n){return n<<0===n},y=function(n){return null!=n&&n===Object(n)&&"function"==typeof n.then},v=function(n,t,r){var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},d=function(n,t){return t>n},w=function(n,t){for(var r=-1,e=t.length,u=new Array(e);++rn?t[t.length+n]:t[n]},x=function(n){return function(t){return w(function(n){return[n,t[n]]},n(t))}},A=function(n,t){var r,e=-1,u=n.length;if(null!=t){for(r=t;null!=r&&++e0?n.hasOwnProperty(0)&&n.hasOwnProperty(n.length-1):!1:!1},D=function(n){return 0===Object(n).length},U=function(n){return null==n},_=function(n){for(var t=n.length,r=-1;++r=0)return!1;return!0},$=function(n){var t,r=[];for(t in n)r[r.length]=t;return r},G=function(n,t){switch(n){case 0:return function(){return t.call(this)};case 1:return function(n){return t.call(this,n)};case 2:return function(n,r){return t.call(this,n,r)};case 3:return function(n,r,e){return t.call(this,n,r,e)};case 4:return function(n,r,e,u){return t.call(this,n,r,e,u)};case 5:return function(n,r,e,u,i){return t.call(this,n,r,e,u,i)};case 6:return function(n,r,e,u,i,o){return t.call(this,n,r,e,u,i,o)};case 7:return function(n,r,e,u,i,o,c){return t.call(this,n,r,e,u,i,o,c)};case 8:return function(n,r,e,u,i,o,c,a){return t.call(this,n,r,e,u,i,o,c,a)};case 9:return function(n,r,e,u,i,o,c,a,f){return t.call(this,n,r,e,u,i,o,c,a,f)};case 10:return function(n,r,e,u,i,o,c,a,f,l){return t.call(this,n,r,e,u,i,o,c,a,f,l)};default:throw new Error("First argument to nAry must be a non-negative integer no greater than ten")}},H=function(n){return function(){return!n.apply(this,arguments)}},J=function(n){return function(){return j(n,arguments)}},K=function(n){return[n]},Q=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments))}},V=C(E),X=function(n,t){switch(arguments.length){case 0:throw O();case 1:return function(t){return t[n]}}return t[n]},Y=C(X),Z=function(n){return P(n).reverse()},nt=x($),tt=function(){var n=" \n \f\r   ᠎              \u2028\u2029",t="​",r="function"==typeof String.prototype.trim;return r&&!n.trim()&&t.trim()?function(n){return n.trim()}:function(t){var r=new RegExp("^["+n+"]["+n+"]*"),e=new RegExp("["+n+"]["+n+"]*$");return t.replace(r,"").replace(e,"")}}(),rt=function(n){return null===n?"Null":void 0===n?"Undefined":Object.prototype.toString.call(n).slice(8,-1)},et=function(n){if(0===arguments.length)throw O();return function(){return n(P(arguments))}},ut=function(n){return G(1,n)},it=function(n){var t,r=[];for(t in n)r[r.length]=n[t];return r},ot=T(!1),ct=F,at=T(!0),ft=function(n,t){return i(t,[n])},lt=function Cu(n,t,r){var e=function(e){for(var u=t.length,i=-1;++i=0},pt=function(n){return function(){switch(arguments.length){case 0:throw O();case 1:return arguments[0];default:for(var t=arguments.length-1,r=arguments[t],e=r.length;t--;)r=n(arguments[t],r);return S(e,r)}}},mt=function(n,t){return function(r){if(0===arguments.length)throw O();for(var e,u=-1,i=t;++u=n?t.apply(this,u):r(u)})}([])}),Bt=xt(-1),Ft=vt(function(n,t){return null==t?n:t}),zt=vt(function(n,t){for(var r=[],e=-1,u=n.length;++e=0?n:r.length,i(ft(t,P(r,0,n)),P(r,n))}),hr=dt(function(n,t,r){return n=n=0?n:r.length,i(i(P(r,0,n),t),P(r,n))}),gr=function(n,t){var r=P(arguments,2),e=n-r.length;return Lt(e+1,function(){var n=arguments[e],u=r.concat(P(arguments,0,e));return n[t].apply(n,u)})},pr=vt(function(n,t){return null!=t&&t.constructor===n||t instanceof n}),mr=gr(1,"join"),yr=function(){var n=!{toString:null}.propertyIsEnumerable("toString"),t=["constructor","valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];return function(r){if(Object(r)!==r)return[];if(Object.keys)return Object.keys(r);var e,u,i=[];for(e in r)or(e,r)&&(i[i.length]=e);if(n)for(u=t.length;u--;)e=t[u],or(e,r)&&!gt(e,i)&&(i[i.length]=e);return i}}(),vr=vt(function(n,t){return v(t,n)}),dr=function(n){return null!=n&&pr(Number,n.length)?n.length:0/0},wr=vt(function(n,t){var r=function(t){return n(t)};return r.set=vt(t),r.map=vt(function(r,e){return t(r(n(e)),e)}),r}),br=vt(st("map",w)),Or=dt(function(n,t,r){for(var e=-1,u=r.length,i=new Array(u),o=[t];++e=t)return[];for(var r=0,e=new Array(Math.floor(t)-Math.ceil(n));t>n;)e[r++]=n++;return e}),Zr=dt(k),ne=dt(function(n,t,r){for(var e=-1,u=r.length;++ee?-1:e>u?1:0})}),se=gr(1,"split"),he=vt(function(n,t){return t.indexOf(n)}),ge=vt(function(n,t){return t.lastIndexOf(n)}),pe=gr(2,"substring"),me=C(pe)(void 0),ye=pe(0),ve=Zr(t,0),de=st("tail",function(n){return P(n,1)}),we=vt(st("take",function(n,t){return P(t,0,Math.min(n,t.length))})),be=vt(st("takeWhile",function(n,t){for(var r=-1,e=t.length;++r1?r.apply(null,P(arguments,1)):S(kr(jt("length",t)),r)}},De=ze(r),Ue=ze(e),_e=vt(Le),$e=dt(function(n,t,r){return Fe(L(w(function(n){return[n,r[n]]},$(r))),Ct(n,t))}),Ge=function(){var n=function(t,r,e){if(1===t.length)return $e(t[0],r,e);var u=e[t[0]];return $e(t[0],n(P(t,1),r,pr(Object,u)?u:{}),e)};return function(t,r,e){var u=arguments.length;if(0===u)throw O();var i=se(".",t),o=vt(function(t,r){return n(i,t,r)});switch(u){case 1:return o;case 2:return o(r);default:return o(r,e)}}}(),He=vt(st("chain",function(n,t){return Pe(w(n,t))})),Je=gr(1,"charAt"),Ke=gr(1,"charCodeAt"),Qe=dt(function(n,t,r){function e(t,r){return Le(w(kt,n(r)),t)}return k(e,t([]),r)}),Ve=vt(function(n,t){for(var r={},e=t.length,u=-1;++u10)throw new Error("Constructor with greater than ten arguments");return 0===n?function(){return new t}:Xe(G(n,function(n,r,e,u,i,o,c,a,f,l){switch(arguments.length){case 1:return new t(n);case 2:return new t(n,r);case 3:return new t(n,r,e);case 4:return new t(n,r,e,u);case 5:return new t(n,r,e,u,i);case 6:return new t(n,r,e,u,i,o);case 7:return new t(n,r,e,u,i,o,c);case 8:return new t(n,r,e,u,i,o,c,a);case 9:return new t(n,r,e,u,i,o,c,a,f);case 10:return new t(n,r,e,u,i,o,c,a,f,l)}}))}),Ou=lu(gt),ju=lu(function(n,t){return n/t}),xu=lu(h),Au=lu(function(n,t){return n>=t}),Iu=function(n){if(0===arguments.length)throw O();return fu(n.length,n)},Eu=lu(d),ku=lu(function(n,t){return t>=n}),Mu=lu(function(n,t){return m(n)?!m(t)||1>t?0/0:(n%t+t)%t:0/0}),Pu=lu(function(n,t){return Fe(Fe({},n),t)}),Tu=Zr(Pu,{}),Su=lu(function(n,t){return n%t}),Wu=vu(w,_r,F),Nu=function(n){return bu(n.length,n)},Ru={F:ot,I:ct,T:at,__:n,add:xt,all:At,allPass:De,always:T,and:It,any:Et,anyPass:Ue,ap:_e,append:kt,appendTo:Mt,apply:Pt,arity:S,assoc:$e,assocPath:Ge,binary:Tt,bind:St,call:W,chain:He,charAt:Je,charCodeAt:Ke,clone:Wt,commute:du,commuteMap:Qe,comparator:N,compose:Nt,composeP:Rt,concat:wu,cond:R,construct:Nu,constructN:bu,contains:Ou,containsWith:qt,converge:q,countBy:Ve,createMapEntry:Ct,curry:Xe,curryN:Lt,dec:Bt,defaultTo:Ft,difference:zt,differenceWith:Dt,dissoc:Ut,divide:ju,drop:_t,dropWhile:$t,empty:Gt,eq:Ht,eqDeep:Ye,eqProps:Jt,evolve:Ze,filter:Kt,filterIndexed:Qt,find:Vt,findIndex:Xt,findLast:Yt,findLastIndex:Zt,flatten:nr,flip:C,forEach:tr,forEachIndexed:rr,fromPairs:L,func:B,functions:nu,functionsIn:er,get:ur,groupBy:ir,gt:xu,gte:Au,has:or,hasIn:cr,head:tu,identity:F,ifElse:ar,inc:fr,indexOf:lr,init:ru,insert:sr,insertAll:hr,installTo:eu,intersection:uu,intersectionWith:iu,invert:ou,invertObj:cu,invoker:gr,is:pr,isArrayLike:z,isEmpty:D,isNil:U,isSet:_,join:mr,keys:yr,keysIn:$,last:au,lastIndexOf:vr,length:dr,lens:wr,lift:Iu,liftN:fu,lt:Eu,lte:ku,map:br,mapAccum:Or,mapAccumRight:jr,mapIndexed:xr,mapObj:Ar,mapObjIndexed:Ir,match:Er,mathMod:Mu,max:kr,maxBy:Mr,memoize:Pr,merge:Pu,mergeAll:Tu,min:Tr,minBy:Sr,modulo:Su,multiply:Wr,nAry:G,negate:Nr,not:H,nth:Rr,nthArg:J,of:K,omit:qr,once:Q,op:lu,or:Cr,partial:Lr,partialRight:Br,partition:Fr,path:su,pathEq:zr,pathOn:Dr,pick:Ur,pickAll:_r,pickBy:$r,pipe:Gr,pipeP:Hr,pluck:Jr,prepend:Kr,prependTo:V,product:hu,project:Wu,prop:X,propEq:Qr,propOf:Y,propOr:Vr,props:Xr,range:Yr,reduce:Zr,reduceIndexed:ne,reduceRight:te,reduceRightIndexed:re,reject:ee,rejectIndexed:ue,remove:ie,repeat:gu,replace:oe,reverse:Z,scan:ce,slice:ae,sort:fe,sortBy:le,split:se,strIndexOf:he,strLastIndexOf:ge,substring:pe,substringFrom:me,substringTo:ye,subtract:pu,sum:ve,tail:de,take:we,takeWhile:be,tap:Oe,times:je,toLower:xe,toPairs:Ae,toPairsIn:nt,toUpper:Ie,trim:tt,type:rt,unapply:et,unary:ut,unfold:Ee,union:mu,unionWith:yu,uniq:ke,uniqWith:Me,unnest:Pe,useWith:vu,values:Te,valuesIn:it,where:Se,wrap:We,xprod:Ne,zip:Re,zipObj:qe,zipWith:Ce};"object"==typeof exports?module.exports=Ru:"function"==typeof define&&define.amd?define(function(){return Ru}):this.R=Ru}).call(this); \ No newline at end of file diff --git a/public/js/lib/together/togetherjs.js b/public/js/lib/together/togetherjs.js new file mode 100644 index 00000000000..5d8412c3b8c --- /dev/null +++ b/public/js/lib/together/togetherjs.js @@ -0,0 +1,786 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/*jshint scripturl:true */ +(function () { + + var defaultConfiguration = { + // Disables clicks for a certain element. + // (e.g., 'canvas' would not show clicks on canvas elements.) + // Setting this to true will disable clicks globally. + dontShowClicks: false, + // Experimental feature to echo clicks to certain elements across clients: + cloneClicks: false, + // Enable Mozilla or Google analytics on the page when TogetherJS is activated: + // FIXME: these don't seem to be working, and probably should be removed in favor + // of the hub analytics + enableAnalytics: false, + // The code to enable (this is defaulting to a Mozilla code): + analyticsCode: "UA-55446531-1", + // The base URL of the hub (gets filled in below): + hubBase: "https://fcctogether.herokuapp.com", + // A function that will return the name of the user: + getUserName: null, + // A function that will return the color of the user: + getUserColor: null, + // A function that will return the avatar of the user: + getUserAvatar: null, + // The siteName is used in the walkthrough (defaults to document.title): + siteName: null, + // Whether to use the minimized version of the code (overriding the built setting) + useMinimizedCode: undefined, + // Any events to bind to + on: {}, + // Hub events to bind to + hub_on: {}, + // Enables the alt-T alt-T TogetherJS shortcut; however, this setting + // must be enabled early as TogetherJSConfig_enableShortcut = true; + enableShortcut: false, + // The name of this tool as provided to users. The UI is updated to use this. + // Because of how it is used in text it should be a proper noun, e.g., + // "MySite's Collaboration Tool" + toolName: null, + // Used to auto-start TogetherJS with a {prefix: pageName, max: participants} + // Also with findRoom: "roomName" it will connect to the given room name + findRoom: null, + // If true, starts TogetherJS automatically (of course!) + autoStart: false, + // If true, then the "Join TogetherJS Session?" confirmation dialog + // won't come up + suppressJoinConfirmation: false, + // If true, then the "Invite a friend" window won't automatically come up + suppressInvite: false, + // A room in which to find people to invite to this session, + inviteFromRoom: null, + // This is used to keep sessions from crossing over on the same + // domain, if for some reason you want sessions that are limited + // to only a portion of the domain: + storagePrefix: "togetherjs", + // When true, we treat the entire URL, including the hash, as the identifier + // of the page; i.e., if you one person is on `http://example.com/#view1` + // and another person is at `http://example.com/#view2` then these two people + // are considered to be at completely different URLs + includeHashInUrl: false, + // When true, the WebRTC-based mic/chat will be disabled + disableWebRTC: false, + // When true, youTube videos will synchronize + youtube: true, + // Ignores the following console messages, disables all messages if set to true + ignoreMessages: ["cursor-update", "keydown", "scroll-update"], + // Ignores the following forms (will ignore all forms if set to true): + ignoreForms: [":password"] +}; + + var styleSheet = "/togetherjs/togetherjs.css"; + + var baseUrl = "https://togetherjs.com"; + if (baseUrl == "__" + "baseUrl__") { + // Reset the variable if it doesn't get substituted + baseUrl = ""; + } + // True if this file should use minimized sub-resources: + var min = "no" == "__" + "min__" ? false : "no" == "yes"; + + var baseUrlOverride = localStorage.getItem("togetherjs.baseUrlOverride"); + if (baseUrlOverride) { + try { + baseUrlOverride = JSON.parse(baseUrlOverride); + } catch (e) { + baseUrlOverride = null; + } + if ((! baseUrlOverride) || baseUrlOverride.expiresAt < Date.now()) { + // Ignore because it has expired + localStorage.removeItem("togetherjs.baseUrlOverride"); + } else { + baseUrl = baseUrlOverride.baseUrl; + var logger = console.warn || console.log; + logger.call(console, "Using TogetherJS baseUrlOverride:", baseUrl); + logger.call(console, "To undo run: localStorage.removeItem('togetherjs.baseUrlOverride')"); + } + } + + var configOverride = localStorage.getItem("togetherjs.configOverride"); + if (configOverride) { + try { + configOverride = JSON.parse(configOverride); + } catch (e) { + configOverride = null; + } + if ((! configOverride) || configOverride.expiresAt < Date.now()) { + localStorage.removeItem("togetherjs.configOverride"); + } else { + var shownAny = false; + for (var attr in configOverride) { + if (attr == "expiresAt" || ! configOverride.hasOwnProperty(attr)) { + continue; + } + if (! shownAny) { + console.warn("Using TogetherJS configOverride"); + console.warn("To undo run: localStorage.removeItem('togetherjs.configOverride')"); + } + window["TogetherJSConfig_" + attr] = configOverride[attr]; + console.log("Config override:", attr, "=", configOverride[attr]); + } + } + } + + var version = "unknown"; + // FIXME: we could/should use a version from the checkout, at least + // for production + var cacheBust = ""; + if ((! cacheBust) || cacheBust == "") { + cacheBust = Date.now() + ""; + } else { + version = cacheBust; + } + + // Make sure we have all of the console.* methods: + if (typeof console == "undefined") { + console = {}; + } + if (! console.log) { + console.log = function () {}; + } + ["debug", "info", "warn", "error"].forEach(function (method) { + if (! console[method]) { + console[method] = console.log; + } + }); + + if (! baseUrl) { + var scripts = document.getElementsByTagName("script"); + for (var i=0; i with togetherjs.js and togetherjs-min.js)"); + } + + function addStyle() { + var existing = document.getElementById("togetherjs-stylesheet"); + if (! existing) { + var link = document.createElement("link"); + link.id = "togetherjs-stylesheet"; + link.setAttribute("rel", "stylesheet"); + link.href = baseUrl + styleSheet + "?bust=" + cacheBust; + document.head.appendChild(link); + } + } + + function addScript(url) { + var script = document.createElement("script"); + script.src = baseUrl + url + "?bust=" + cacheBust; + document.head.appendChild(script); + } + + var TogetherJS = window.TogetherJS = function TogetherJS(event) { + if (TogetherJS.running) { + var session = TogetherJS.require("session"); + session.close(); + return; + } + TogetherJS.startup.button = null; + try { + if (event && typeof event == "object") { + if (event.target && typeof event) { + TogetherJS.startup.button = event.target; + } else if (event.nodeType == 1) { + TogetherJS.startup.button = event; + } else if (event[0] && event[0].nodeType == 1) { + // Probably a jQuery element + TogetherJS.startup.button = event[0]; + } + } + } catch (e) { + console.warn("Error determining starting button:", e); + } + if (window.TowTruckConfig) { + console.warn("TowTruckConfig is deprecated; please use TogetherJSConfig"); + if (window.TogetherJSConfig) { + console.warn("Ignoring TowTruckConfig in favor of TogetherJSConfig"); + } else { + window.TogetherJSConfig = TowTruckConfig; + } + } + if (window.TogetherJSConfig && (! window.TogetherJSConfig.loaded)) { + TogetherJS.config(window.TogetherJSConfig); + window.TogetherJSConfig.loaded = true; + } + + // This handles loading configuration from global variables. This + // includes TogetherJSConfig_on_*, which are attributes folded into + // the "on" configuration value. + var attr; + var attrName; + var globalOns = {}; + for (attr in window) { + if (attr.indexOf("TogetherJSConfig_on_") === 0) { + attrName = attr.substr(("TogetherJSConfig_on_").length); + globalOns[attrName] = window[attr]; + } else if (attr.indexOf("TogetherJSConfig_") === 0) { + attrName = attr.substr(("TogetherJSConfig_").length); + TogetherJS.config(attrName, window[attr]); + } else if (attr.indexOf("TowTruckConfig_on_") === 0) { + attrName = attr.substr(("TowTruckConfig_on_").length); + console.warn("TowTruckConfig_* is deprecated, please rename", attr, "to TogetherJSConfig_on_" + attrName); + globalOns[attrName] = window[attr]; + } else if (attr.indexOf("TowTruckConfig_") === 0) { + attrName = attr.substr(("TowTruckConfig_").length); + console.warn("TowTruckConfig_* is deprecated, please rename", attr, "to TogetherJSConfig_" + attrName); + TogetherJS.config(attrName, window[attr]); + } + + + } + // FIXME: copy existing config? + // FIXME: do this directly in TogetherJS.config() ? + // FIXME: close these configs? + var ons = TogetherJS.config.get("on"); + for (attr in globalOns) { + if (globalOns.hasOwnProperty(attr)) { + // FIXME: should we avoid overwriting? Maybe use arrays? + ons[attr] = globalOns[attr]; + } + } + TogetherJS.config("on", ons); + for (attr in ons) { + TogetherJS.on(attr, ons[attr]); + } + var hubOns = TogetherJS.config.get("hub_on"); + if (hubOns) { + for (attr in hubOns) { + if (hubOns.hasOwnProperty(attr)) { + TogetherJS.hub.on(attr, hubOns[attr]); + } + } + } + + if (! TogetherJS.startup.reason) { + // Then a call to TogetherJS() from a button must be started TogetherJS + TogetherJS.startup.reason = "started"; + } + + // FIXME: maybe I should just test for TogetherJS.require: + if (TogetherJS._loaded) { + var session = TogetherJS.require("session"); + addStyle(); + session.start(); + return; + } + // A sort of signal to session.js to tell it to actually + // start itself (i.e., put up a UI and try to activate) + TogetherJS.startup._launch = true; + + addStyle(); + var minSetting = TogetherJS.config.get("useMinimizedCode"); + TogetherJS.config.close("useMinimizedCode"); + if (minSetting !== undefined) { + min = !! minSetting; + } + var requireConfig = TogetherJS._extend(TogetherJS.requireConfig); + var deps = ["session", "jquery"]; + function callback(session, jquery) { + TogetherJS._loaded = true; + if (! min) { + TogetherJS.require = require.config({context: "togetherjs"}); + TogetherJS._requireObject = require; + } + } + if (! min) { + if (typeof require == "function") { + if (! require.config) { + console.warn("The global require (", require, ") is not requirejs; please use togetherjs-min.js"); + throw new Error("Conflict with window.require"); + } + TogetherJS.require = require.config(requireConfig); + } + } + if (typeof TogetherJS.require == "function") { + // This is an already-configured version of require + TogetherJS.require(deps, callback); + } else { + requireConfig.deps = deps; + requireConfig.callback = callback; + if (! min) { + window.require = requireConfig; + } + } + if (min) { + addScript("/togetherjs/togetherjsPackage.js"); + } else { + addScript("/togetherjs/libs/require.js"); + } + }; + + TogetherJS.pageLoaded = Date.now(); + + TogetherJS._extend = function (base, extensions) { + if (! extensions) { + extensions = base; + base = {}; + } + for (var a in extensions) { + if (extensions.hasOwnProperty(a)) { + base[a] = extensions[a]; + } + } + return base; + }; + + TogetherJS._startupInit = { + // What element, if any, was used to start the session: + button: null, + // The startReason is the reason TogetherJS was started. One of: + // null: not started + // started: hit the start button (first page view) + // joined: joined the session (first page view) + reason: null, + // Also, the session may have started on "this" page, or maybe is continued + // from a past page. TogetherJS.continued indicates the difference (false the + // first time TogetherJS is started or joined, true on later page loads). + continued: false, + // This is set to tell the session what shareId to use, if the boot + // code knows (mostly because the URL indicates the id). + _joinShareId: null, + // This tells session to start up immediately (otherwise it would wait + // for session.start() to be run) + _launch: false + }; + TogetherJS.startup = TogetherJS._extend(TogetherJS._startupInit); + TogetherJS.running = false; + + TogetherJS.requireConfig = { + context: "togetherjs", + baseUrl: baseUrl + "/togetherjs", + urlArgs: "bust=" + cacheBust, + paths: { + jquery: "libs/jquery-1.8.3.min", + walkabout: "libs/walkabout/walkabout", + esprima: "libs/walkabout/lib/esprima", + falafel: "libs/walkabout/lib/falafel", + tinycolor: "libs/tinycolor", + whrandom: "libs/whrandom/random" + } + }; + + TogetherJS._mixinEvents = function (proto) { + proto.on = function on(name, callback) { + if (typeof callback != "function") { + console.warn("Bad callback for", this, ".once(", name, ", ", callback, ")"); + throw "Error: .once() called with non-callback"; + } + if (name.search(" ") != -1) { + var names = name.split(/ +/g); + names.forEach(function (n) { + this.on(n, callback); + }, this); + return; + } + if (this._knownEvents && this._knownEvents.indexOf(name) == -1) { + var thisString = "" + this; + if (thisString.length > 20) { + thisString = thisString.substr(0, 20) + "..."; + } + console.warn(thisString + ".on('" + name + "', ...): unknown event"); + if (console.trace) { + console.trace(); + } + } + if (! this._listeners) { + this._listeners = {}; + } + if (! this._listeners[name]) { + this._listeners[name] = []; + } + if (this._listeners[name].indexOf(callback) == -1) { + this._listeners[name].push(callback); + } + }; + proto.once = function once(name, callback) { + if (typeof callback != "function") { + console.warn("Bad callback for", this, ".once(", name, ", ", callback, ")"); + throw "Error: .once() called with non-callback"; + } + var attr = "onceCallback_" + name; + // FIXME: maybe I should add the event name to the .once attribute: + if (! callback[attr]) { + callback[attr] = function onceCallback() { + callback.apply(this, arguments); + this.off(name, onceCallback); + delete callback[attr]; + }; + } + this.on(name, callback[attr]); + }; + proto.off = proto.removeListener = function off(name, callback) { + if (this._listenerOffs) { + // Defer the .off() call until the .emit() is done. + this._listenerOffs.push([name, callback]); + return; + } + if (name.search(" ") != -1) { + var names = name.split(/ +/g); + names.forEach(function (n) { + this.off(n, callback); + }, this); + return; + } + if ((! this._listeners) || ! this._listeners[name]) { + return; + } + var l = this._listeners[name], _len = l.length; + for (var i=0; i<_len; i++) { + if (l[i] == callback) { + l.splice(i, 1); + break; + } + } + }; + proto.emit = function emit(name) { + var offs = this._listenerOffs = []; + if ((! this._listeners) || ! this._listeners[name]) { + return; + } + var args = Array.prototype.slice.call(arguments, 1); + var l = this._listeners[name]; + l.forEach(function (callback) { + + callback.apply(this, args); + }, this); + delete this._listenerOffs; + if (offs.length) { + offs.forEach(function (item) { + this.off(item[0], item[1]); + }, this); + } + + }; + return proto; + }; + + /* This finalizes the unloading of TogetherJS, including unloading modules */ + TogetherJS._teardown = function () { + var requireObject = TogetherJS._requireObject || window.require; + // FIXME: this doesn't clear the context for min-case + if (requireObject.s && requireObject.s.contexts) { + delete requireObject.s.contexts.togetherjs; + } + TogetherJS._loaded = false; + TogetherJS.startup = TogetherJS._extend(TogetherJS._startupInit); + TogetherJS.running = false; + }; + + TogetherJS._mixinEvents(TogetherJS); + TogetherJS._knownEvents = ["ready", "close"]; + TogetherJS.toString = function () { + return "TogetherJS"; + }; + + TogetherJS._configuration = {}; + TogetherJS._defaultConfiguration = defaultConfiguration; + TogetherJS._configTrackers = {}; + TogetherJS._configClosed = {}; + + /* TogetherJS.config(configurationObject) + or: TogetherJS.config(configName, value) + + Adds configuration to TogetherJS. You may also set the global variable TogetherJSConfig + and when TogetherJS is started that configuration will be loaded. + + Unknown configuration values will lead to console error messages. + */ + TogetherJS.config = function (name, maybeValue) { + var settings; + if (arguments.length == 1) { + if (typeof name != "object") { + throw new Error('TogetherJS.config(value) must have an object value (not: ' + name + ')'); + } + settings = name; + } else { + settings = {}; + settings[name] = maybeValue; + } + var i; + var tracker; + for (var attr in settings) { + if (settings.hasOwnProperty(attr)) { + if (TogetherJS._configClosed[attr] && TogetherJS.running) { + throw new Error("The configuration " + attr + " is finalized and cannot be changed"); + } + } + } + for (var attr in settings) { + if (! settings.hasOwnProperty(attr)) { + continue; + } + if (attr == "loaded" || attr == "callToStart") { + continue; + } + if (! TogetherJS._defaultConfiguration.hasOwnProperty(attr)) { + console.warn("Unknown configuration value passed to TogetherJS.config():", attr); + } + var previous = TogetherJS._configuration[attr]; + var value = settings[attr]; + TogetherJS._configuration[attr] = value; + var trackers = TogetherJS._configTrackers[name] || []; + var failed = false; + for (i=0; i= 3; })).to.eqls([3, 4]);", + "expect(drop([1, 2, 3], function(n) {return n > 0; })).to.eqls([1, 2, 3]);", + "expect(drop([1, 2, 3, 4], function(n) {return n > 5; })).to.eqls([]);" ] }, { @@ -497,6 +495,22 @@ "bob.setFullName('Bob Ross');" ] }, + { + "_id": "af4afb223120f7348cdfc9fd", + "name": "Map the Debris", + "difficulty": "3.50", + "description": [ + "Return a new array that transforms the element's average altitude into their orbital periods.", + "The array will contain objects in the format {name: 'name', avgAlt: avgAlt}.", + "You can read about orbital periods on wikipedia.", + "The values should be rounded to the nearest whole number. Assume the body being orbited is Earth." + ], + "challengeSeed": "function orbitalPeriod(arr) {\n return arr;\r\n}\r\n\r\norbitalPeriod([{name : \"sputkin\", avgAlt : 35873.5553}]);", + "tests": [ + "expect(orbitalPeriod([{name : \"sputkin\", avgAlt : 35873.5553}])).to.eqls([{name: \"sputkin\", orbitalPeriod: 86400}]);", + "expect(orbitalPeriod([{name: \"iss\", avgAlt: 413.6}, {name: \"hubble\", avgAlt: 556.7}, {name: \"moon\", avgAlt: 378632.553}])).to.eqls([{name : \"iss\", orbitalPeriod: 5557}, {name: \"hubble\", orbitalPeriod: 5734}, {name: \"moon\", orbitalPeriod: 2377399}]);" + ] + }, { "_id": "aff0395860f5d3034dc0bfc9", "name": "Validate US Telephone Numbers", diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index ef53cdf446c..bfa7fdbd77b 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -15,6 +15,7 @@ block content script(src='/js/lib/codemirror/mode/javascript/javascript.js') script(src='/js/lib/jailed/jailed.js') script(src='/js/lib/bonfire/bonfireInit.js') + script(src="https://cdn.jsdelivr.net/ramda/0.10.0/ramda.min.js") .row @@ -91,6 +92,7 @@ block content var passedBonfireHash = !{JSON.stringify(bonfireHash)}; var challengeName = !{JSON.stringify(name)}; var started = Math.floor(Date.now() / 1000); + var _ = R; .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel form.code diff --git a/views/layout-wide.jade b/views/layout-wide.jade index 33873b2bf21..89bfd95e5ca 100644 --- a/views/layout-wide.jade +++ b/views/layout-wide.jade @@ -1,32 +1,11 @@ doctype html html(ng-app='profileValidation', lang='en') head - script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") - script(src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.11/angular.min.js") - script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") - link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicon.ico') - link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') - link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css') - link(rel='stylesheet', href='//code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css') - include partials/meta - title #{title} | Free Code Camp - meta(charset='utf-8') - meta(http-equiv='X-UA-Compatible', content='IE=edge') - meta(name='viewport', content='width=device-width, initial-scale=1.0') - meta(name='csrf-token', content=_csrf) - != css('main') + include partials/universal-head body.no-top-and-bottom-margins.full-screen-body-background include partials/navbar-wide include partials/flash block content include partials/footer - != js('application') -script. - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', 'UA-55446531-1', 'auto'); - ga('require', 'displayfeatures'); - ga('send', 'pageview'); \ No newline at end of file + != js('application') \ No newline at end of file diff --git a/views/layout.jade b/views/layout.jade index 6e5cadeefd3..a1fcddc3714 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -1,18 +1,7 @@ doctype html html(ng-app='profileValidation', lang='en') head - script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") - script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") - script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") - link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') - link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css') - include partials/meta - title #{title} | Free Code Camp - meta(charset='utf-8') - meta(http-equiv='X-UA-Compatible', content='IE=edge') - meta(name='viewport', content='width=device-width, initial-scale=1.0') - meta(name='csrf-token', content=_csrf) - != css('main') + include partials/universal-head body.top-and-bottom-margins include partials/navbar-narrow @@ -21,11 +10,3 @@ html(ng-app='profileValidation', lang='en') block content include partials/footer != js('application') -script. - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', 'UA-55446531-1', 'auto'); - ga('require', 'displayfeatures'); - ga('send', 'pageview'); \ No newline at end of file diff --git a/views/partials/footer.jade b/views/partials/footer.jade index f5e6b365231..95bb374ac0b 100644 --- a/views/partials/footer.jade +++ b/views/partials/footer.jade @@ -7,6 +7,7 @@ a.ion-social-facebook(href="http://facebook.com/freecodecamp", target='_blank')  Facebook   a.ion-information-circled(href="/learn-to-code")  About   a.ion-locked(href="/privacy")  Privacy   + a(href="#" onclick="TogetherJS(this); return false;")   .col-xs-12.visible-xs.visible-sm a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank') span.sr-only Free Code Camp's Blog diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 5b895c57ba3..addf25e8238 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -16,6 +16,8 @@ a(href='http://forum.freecodecamp.com' target='_blank') Forum li a(href='/bonfires') Bonfires + //li + // a(href="#" onclick="TogetherJS(this); return false;") Pair if !user li       li diff --git a/views/partials/universal-head.jade b/views/partials/universal-head.jade new file mode 100644 index 00000000000..4a959d3d2de --- /dev/null +++ b/views/partials/universal-head.jade @@ -0,0 +1,20 @@ +script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") +script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js") +script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") +link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') +link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css') +include meta +title #{title} | Free Code Camp +meta(charset='utf-8') +meta(http-equiv='X-UA-Compatible', content='IE=edge') +meta(name='viewport', content='width=device-width, initial-scale=1.0') +meta(name='csrf-token', content=_csrf) +!= css('main') +script. + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + ga('create', 'UA-55446531-1', 'auto'); + ga('require', 'displayfeatures'); + ga('send', 'pageview'); \ No newline at end of file