From e5cfa147fa3f0d26aff3664053ac4d34d19cfb6a Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sat, 21 Feb 2015 14:22:46 -0800 Subject: [PATCH 1/8] attempt to use togetherjs --- app.js | 7 +- public/js/lib/together/togetherjs.js | 793 +++++++++++++++++++++++++++ views/layout-wide.jade | 1 + views/layout.jade | 1 + views/partials/footer.jade | 1 + views/partials/navbar.jade | 2 + 6 files changed, 804 insertions(+), 1 deletion(-) create mode 100644 public/js/lib/together/togetherjs.js diff --git a/app.js b/app.js index 602cd14e8c..106c3a6d6b 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/public/js/lib/together/togetherjs.js b/public/js/lib/together/togetherjs.js new file mode 100644 index 0000000000..51ec4634c0 --- /dev/null +++ b/public/js/lib/together/togetherjs.js @@ -0,0 +1,793 @@ +/* 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-35433268-28", + // The base URL of the hub (gets filled in below): + hubBase: null, + // 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"; + }; + + var defaultHubBase = "https://hub.togetherjs.com"; + if (defaultHubBase == "__" + "hubUrl"+ "__") { + // Substitution wasn't made + defaultHubBase = "https://hub.togetherjs.mozillalabs.com"; + } + defaultConfiguration.hubBase = defaultHubBase; + + 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 Date: Sat, 21 Feb 2015 14:49:24 -0800 Subject: [PATCH 2/8] refactor layout.jade to have universal head, and customize together.js --- public/js/application.js | 1 + public/js/lib/together/togetherjs.js | 13 +++---------- views/layout-wide.jade | 26 ++------------------------ views/layout.jade | 22 +--------------------- views/partials/universal-head.jade | 20 ++++++++++++++++++++ 5 files changed, 27 insertions(+), 55 deletions(-) create mode 100644 views/partials/universal-head.jade diff --git a/public/js/application.js b/public/js/application.js index 56fa90e6a3..f792928b2b 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/together/togetherjs.js b/public/js/lib/together/togetherjs.js index 51ec4634c0..5d8412c3b8 100644 --- a/public/js/lib/together/togetherjs.js +++ b/public/js/lib/together/togetherjs.js @@ -17,9 +17,9 @@ // of the hub analytics enableAnalytics: false, // The code to enable (this is defaulting to a Mozilla code): - analyticsCode: "UA-35433268-28", + analyticsCode: "UA-55446531-1", // The base URL of the hub (gets filled in below): - hubBase: null, + 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: @@ -70,7 +70,7 @@ 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"; @@ -487,13 +487,6 @@ return "TogetherJS"; }; - var defaultHubBase = "https://hub.togetherjs.com"; - if (defaultHubBase == "__" + "hubUrl"+ "__") { - // Substitution wasn't made - defaultHubBase = "https://hub.togetherjs.mozillalabs.com"; - } - defaultConfiguration.hubBase = defaultHubBase; - TogetherJS._configuration = {}; TogetherJS._defaultConfiguration = defaultConfiguration; TogetherJS._configTrackers = {}; diff --git a/views/layout-wide.jade b/views/layout-wide.jade index 64cf87de8b..89bfd95e5c 100644 --- a/views/layout-wide.jade +++ b/views/layout-wide.jade @@ -1,33 +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") - script(src="https://togetherjs.com/togetherjs-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 360dc4bd6c..a1fcddc371 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -1,19 +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") - script(src="https://togetherjs.com/togetherjs-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 @@ -22,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/universal-head.jade b/views/partials/universal-head.jade new file mode 100644 index 0000000000..4a959d3d2d --- /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 From ff4fbefc0a3e39d21e1380ffa27a0eb827cf3dd5 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Feb 2015 13:24:26 +0900 Subject: [PATCH 3/8] Cleaned up where do I belong challenge --- seed_data/bonfires.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index 2f9da459a1..04663879c8 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -239,15 +239,13 @@ "name": "Where do I belong", "difficulty": "1.61", "description": [ - "Return the lowest index at which a value (second argument) should be inserted into a sorted array (first argument)." + "Return the lowest index at which a value (second argument) should be inserted into a sorted array (first argument).", + "For example, where([1,2,3,4], 1.5) should return 1 because it is greater than 1 (0th index), but less than 2 (1st index)." ], "challengeSeed": "function where(arr, num) {\n // Find my place in this sorted array.\r\n return num;\r\n}\n\nwhere([40, 60], 50);", "tests": [ - "var numbers = [10, 20, 30, 40, 50], num = 35;", - "var indexForNum = where(numbers, num);", - "assert.equal(indexForNum, 3, '35 should be inserted at index 3');", - "var indexFor30 = where(numbers, 30);", - "assert.equal(indexFor30, 2, '30 should be inserted at index 2');" + "expect(where([10, 20, 30, 40, 50], 35)).to.eql(3);", + "expect(where([10, 20, 30, 40, 50], 30)).to.eql(2);" ] }, { From a087068022793acce071a5018247aa3082d284ab Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sun, 22 Feb 2015 13:55:27 +0900 Subject: [PATCH 4/8] Fix drop it like its hot challenge --- seed_data/bonfires.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index 04663879c8..b95c4ba83c 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -244,8 +244,8 @@ ], "challengeSeed": "function where(arr, num) {\n // Find my place in this sorted array.\r\n return num;\r\n}\n\nwhere([40, 60], 50);", "tests": [ - "expect(where([10, 20, 30, 40, 50], 35)).to.eql(3);", - "expect(where([10, 20, 30, 40, 50], 30)).to.eql(2);" + "expect(where([10, 20, 30, 40, 50], 35)).to.equal(3);", + "expect(where([10, 20, 30, 40, 50], 30)).to.equal(2);" ] }, { @@ -419,9 +419,9 @@ ], "challengeSeed": "function drop(arr, func) {\n // Drop them elements.\r\n return arr;\r\n}\n\ndrop([1, 2, 3], function(n) {return n < 3; });", "tests": [ - "assert.deepEqual(drop([1, 2, 3, 4], function(n) {return n < 3; }), [3, 4], 'should return remaining array');", - "assert.deepEqual(drop([1, 2, 3], function(n) {return n < 0; }), [1, 2, 3], 'should return complete array if predicate met in first element.');", - "assert.deepEqual(drop([1, 2, 3, 4], function(n) {return n < 5; }), [], 'should return an empty array if predicate does not return true');" + "expect(drop([1, 2, 3, 4], function(n) {return n >= 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([]);" ] }, { From 380e301a9a7cbfa35da12c82725e53d532771546 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 22 Feb 2015 08:05:32 -0700 Subject: [PATCH 5/8] Flip windows and mac gitter links --- views/resources/chat.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/resources/chat.jade b/views/resources/chat.jade index b300dcc3e7..9647804dea 100644 --- a/views/resources/chat.jade +++ b/views/resources/chat.jade @@ -6,9 +6,9 @@ block content a(href="http://github.com/join", target='_blank') here | . li Download the chat room app on   - a(href="https://update.gitter.im/osx/latest") Windows + a(href="https://update.gitter.im/win/latest") Windows | ,  - a(href="https://update.gitter.im/win/latest") Mac + a(href="https://update.gitter.im/osx/latest") Mac | ,  a(href="http://appstore.com/gitter") iPhone | ,  or   From bc2bf90d51d30f834d61c5dc8e1d0a753b745d25 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Feb 2015 15:00:05 -0800 Subject: [PATCH 6/8] fix a bug in a view --- views/resources/chat.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/resources/chat.jade b/views/resources/chat.jade index b300dcc3e7..9647804dea 100644 --- a/views/resources/chat.jade +++ b/views/resources/chat.jade @@ -6,9 +6,9 @@ block content a(href="http://github.com/join", target='_blank') here | . li Download the chat room app on   - a(href="https://update.gitter.im/osx/latest") Windows + a(href="https://update.gitter.im/win/latest") Windows | ,  - a(href="https://update.gitter.im/win/latest") Mac + a(href="https://update.gitter.im/osx/latest") Mac | ,  a(href="http://appstore.com/gitter") iPhone | ,  or   From 0674466f5fc4e1390a612c94599d9bce8d0d14cb Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Mon, 23 Feb 2015 08:01:55 +0900 Subject: [PATCH 7/8] Add bitly url shortening to twitter buttons --- app.js | 3 ++- views/bonfire/show.jade | 22 ++++++++++++++-------- views/challenges/show.jade | 15 +++++++++++++-- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/app.js b/app.js index 106c3a6d6b..74c920dcec 100644 --- a/app.js +++ b/app.js @@ -144,7 +144,8 @@ var trusted = [ 'https://*.togetherjs.com', 'wss://hub.togetherjs.com', '*.ytimg.com', - 'wss://fcctogether.herokuapp.com' + 'wss://fcctogether.herokuapp.com', + '*.bitly.com' ]; app.use(helmet.contentSecurityPolicy({ diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index bfa7fdbd77..01cbe87077 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -93,6 +93,7 @@ block content var challengeName = !{JSON.stringify(name)}; var started = Math.floor(Date.now() / 1000); var _ = R; + var dashed = !{JSON.stringify(dashedName)}; .col-xs-12.col-sm-12.col-md-8 #mainEditorPanel form.code @@ -127,17 +128,22 @@ block content - if (points && points > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank") + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") i.fa.fa-twitter   = phrase - else a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress - //#all-bonfires-dialog.modal(tabindex='-1') - // .modal-dialog.animated.fadeInUp.fast-animation - // .modal-content - // .modal-header.challenge-list-header Bonfires - // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - // .modal-body - // include ../partials/bonfires + script. + $.ajax({ + url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fbonfires%2F' + dashed + '&format=txt' + }) + .success( + function (data) { + console.log(data); + url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; + $('.btn-twitter').attr('href', url); + } + ); + diff --git a/views/challenges/show.jade b/views/challenges/show.jade index 9de6372f82..becbc7cdb8 100644 --- a/views/challenges/show.jade +++ b/views/challenges/show.jade @@ -31,7 +31,7 @@ block content - if (cc) a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-challenge-button(name='_csrf', value=_csrf, aria-hidden='true') Take me to my next challenge - if (points && points > 2) - a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge%20%23#{number}:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{number}&hashtags=LearnToCode, JavaScript" target="_blank") + a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(target="_blank") i.fa.fa-twitter   = phrase - else @@ -42,4 +42,15 @@ block content .modal-header.challenge-list-header Challenges a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body - include ../partials/challenges \ No newline at end of file + include ../partials/challenges + script. + $.ajax({ + url: 'https://api-ssl.bitly.com/v3/shorten?access_token=75e7931a19befaafcf108021b6d597e554b2c5c3&longUrl=http%3A%2F%2Ffreecodecamp.com%2Fchallenges%2F' + !{JSON.stringify(number)} + '&format=txt' + }) + .success( + function (data) { + console.log(data); + url = "https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=" + data + "&hashtags=LearnToCode, JavaScript"; + $('.btn-twitter').attr('href', url); + } + ); \ No newline at end of file From 19e87950558cfbe8d170dcf0fd1880da21d46586 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Sun, 22 Feb 2015 17:32:30 -0800 Subject: [PATCH 8/8] some minor ux improvements and use nice ghost-style buttons for more information and less information on challenges and bonfires --- public/css/main.less | 14 ++++++++++++++ seed_data/coursewares.json | 5 +++-- views/bonfire/show.jade | 14 ++++++-------- views/coursewares/showHTML.jade | 14 ++++++-------- views/coursewares/showJS.jade | 14 ++++++-------- views/resources/learn-to-code.jade | 5 ++++- 6 files changed, 39 insertions(+), 27 deletions(-) diff --git a/public/css/main.less b/public/css/main.less index cf8b467ed8..5996b2b858 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -709,6 +709,20 @@ iframe.iphone { margin-bottom: 50px; } +.btn-primary-ghost{ + + background: transparent; + color: @brand-primary; + + /* CSS Transition */ + -webkit-transition: background .2s ease-in-out, border .2s ease-in-out; + -moz-transition: background .2s ease-in-out, border .2s ease-in-out; + -ms-transition: background .2s ease-in-out, border .2s ease-in-out; + -o-transition: background .2s ease-in-out, border .2s ease-in-out; + transition: background .2s ease-in-out, border .2s ease-in-out; + +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 716f24acb2..969b39ab26 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -2605,11 +2605,12 @@ "challengeType" : 2 }, { - "_id": "bd7123c8c441eddfaeb5bdef", + "_id": "bd7123c9c441eddfaeb5bdef", "name": "Meet Booleans", "difficulty": "9.98", "description": [ - "Return true" + "Return true", + "Some additional directions" ], "tests": [ "expect(welcomeToBooleans()).to.be.a(\"boolean\");", diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 01cbe87077..77c5b61e0c 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -66,18 +66,16 @@ block content .bonfire-instructions p= brief #brief-instructions - .text-center - button#more-info.btn.btn-info - span.ion-help-circled - | More information + #more-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-down-b + | More information #long-instructions.row.hide .col-xs-12 for sentence in details p!= sentence - .text-center - button#less-info.btn.btn-info - span.ion-help-circled - | Less information + #less-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-up-b + | Less information #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) br form.code diff --git a/views/coursewares/showHTML.jade b/views/coursewares/showHTML.jade index 676aadf774..c094f534c3 100644 --- a/views/coursewares/showHTML.jade +++ b/views/coursewares/showHTML.jade @@ -27,18 +27,16 @@ block content .bonfire-instructions p!= brief #brief-instructions - .text-center - button#more-info.btn.btn-info - span.ion-help-circled - | More information + #more-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-down-b + | More information #long-instructions.row.hide .col-xs-12 for sentence in details p!= sentence - .text-center - button#less-info.btn.btn-info - span.ion-help-circled - | Less information + #less-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-up-b + | Less information br - if (cc) diff --git a/views/coursewares/showJS.jade b/views/coursewares/showJS.jade index 3570432d41..a8183dae10 100644 --- a/views/coursewares/showJS.jade +++ b/views/coursewares/showJS.jade @@ -24,18 +24,16 @@ block content .bonfire-instructions p= brief #brief-instructions - .text-center - button#more-info.btn.btn-info - span.ion-help-circled - | More information + #more-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-down-b + | More information #long-instructions.row.hide .col-xs-12 for sentence in details p!= sentence - .text-center - button#less-info.btn.btn-info - span.ion-help-circled - | Less information + #less-info.btn.btn-primary.btn-block.btn-primary-ghost + span.ion-arrow-up-b + | Less information #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) br form.code diff --git a/views/resources/learn-to-code.jade b/views/resources/learn-to-code.jade index 22e1ff5bbc..e040bce215 100644 --- a/views/resources/learn-to-code.jade +++ b/views/resources/learn-to-code.jade @@ -3,7 +3,10 @@ block content img.img-responsive.img-center(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') br .text-center - a.btn.btn-cta.signup-btn.next-challenge-button(href="/challenges") Take me to my next challenge + if (user) + a.btn.btn-cta.signup-btn.next-challenge-button(href="/challenges") Take me to my next challenge + else + a.btn.btn-cta.signup-btn.next-challenge-button(href="/signin") Start learning to code (it's free) br .row .col-xs-12.col-sm-12.col-md-4