· 9 years ago · Nov 16, 2016, 06:42 PM
1var requirejs, require, define;
2! function(a) {
3 function b(a, b) {
4 return s.call(a, b)
5 }
6
7 function c(a, b) {
8 var c, d, e, f, g, h, i, j, k, l, m, n, o = b && b.split("/"),
9 p = q.map,
10 r = p && p["*"] || {};
11 if (a) {
12 for (a = a.split("/"), g = a.length - 1, q.nodeIdCompat && u.test(a[g]) && (a[g] = a[g].replace(u, "")), "." === a[0].charAt(0) && o && (n = o.slice(0, o.length - 1), a = n.concat(a)), k = 0; k < a.length; k++)
13 if (m = a[k], "." === m) a.splice(k, 1), k -= 1;
14 else if (".." === m) {
15 if (0 === k || 1 === k && ".." === a[2] || ".." === a[k - 1]) continue;
16 k > 0 && (a.splice(k - 1, 2), k -= 2)
17 }
18 a = a.join("/")
19 }
20 if ((o || r) && p) {
21 for (c = a.split("/"), k = c.length; k > 0; k -= 1) {
22 if (d = c.slice(0, k).join("/"), o)
23 for (l = o.length; l > 0; l -= 1)
24 if (e = p[o.slice(0, l).join("/")], e && (e = e[d])) {
25 f = e, h = k;
26 break
27 }
28 if (f) break;
29 !i && r && r[d] && (i = r[d], j = k)
30 }!f && i && (f = i, h = j), f && (c.splice(0, h, f), a = c.join("/"))
31 }
32 return a
33 }
34
35 function d(b, c) {
36 return function() {
37 var d = t.call(arguments, 0);
38 return "string" != typeof d[0] && 1 === d.length && d.push(null), l.apply(a, d.concat([b, c]))
39 }
40 }
41
42 function e(a) {
43 return function(b) {
44 return c(b, a)
45 }
46 }
47
48 function f(a) {
49 return function(b) {
50 o[a] = b
51 }
52 }
53
54 function g(c) {
55 if (b(p, c)) {
56 var d = p[c];
57 delete p[c], r[c] = !0, k.apply(a, d)
58 }
59 if (!b(o, c) && !b(r, c)) throw new Error("No " + c);
60 return o[c]
61 }
62
63 function h(a) {
64 var b, c = a ? a.indexOf("!") : -1;
65 return c > -1 && (b = a.substring(0, c), a = a.substring(c + 1, a.length)), [b, a]
66 }
67
68 function i(a) {
69 return a ? h(a) : []
70 }
71
72 function j(a) {
73 return function() {
74 return q && q.config && q.config[a] || {}
75 }
76 }
77 var k, l, m, n, o = {},
78 p = {},
79 q = {},
80 r = {},
81 s = Object.prototype.hasOwnProperty,
82 t = [].slice,
83 u = /\.js$/;
84 m = function(a, b) {
85 var d, f = h(a),
86 i = f[0],
87 j = b[1];
88 return a = f[1], i && (i = c(i, j), d = g(i)), i ? a = d && d.normalize ? d.normalize(a, e(j)) : c(a, j) : (a = c(a, j), f = h(a), i = f[0], a = f[1], i && (d = g(i))), {
89 f: i ? i + "!" + a : a,
90 n: a,
91 pr: i,
92 p: d
93 }
94 }, n = {
95 require: function(a) {
96 return d(a)
97 },
98 exports: function(a) {
99 var b = o[a];
100 return "undefined" != typeof b ? b : o[a] = {}
101 },
102 module: function(a) {
103 return {
104 id: a,
105 uri: "",
106 exports: o[a],
107 config: j(a)
108 }
109 }
110 }, k = function(c, e, h, j) {
111 var k, l, q, s, t, u, v, w = [],
112 x = typeof h;
113 if (j = j || c, u = i(j), "undefined" === x || "function" === x) {
114 for (e = !e.length && h.length ? ["require", "exports", "module"] : e, t = 0; t < e.length; t += 1)
115 if (s = m(e[t], u), l = s.f, "require" === l) w[t] = n.require(c);
116 else if ("exports" === l) w[t] = n.exports(c), v = !0;
117 else if ("module" === l) k = w[t] = n.module(c);
118 else if (b(o, l) || b(p, l) || b(r, l)) w[t] = g(l);
119 else {
120 if (!s.p) throw new Error(c + " missing " + l);
121 s.p.load(s.n, d(j, !0), f(l), {}), w[t] = o[l]
122 }
123 q = h ? h.apply(o[c], w) : void 0, c && (k && k.exports !== a && k.exports !== o[c] ? o[c] = k.exports : q === a && v || (o[c] = q))
124 } else c && (o[c] = h)
125 }, requirejs = require = l = function(b, c, d, e, f) {
126 if ("string" == typeof b) return n[b] ? n[b](c) : g(m(b, i(c)).f);
127 if (!b.splice) {
128 if (q = b, q.deps && l(q.deps, q.callback), !c) return;
129 c.splice ? (b = c, c = d, d = null) : b = a
130 }
131 return c = c || function() {}, "function" == typeof d && (d = e, e = f), e ? k(a, b, c, d) : setTimeout(function() {
132 k(a, b, c, d)
133 }, 4), l
134 }, l.config = function(a) {
135 return l(a)
136 }, requirejs._defined = o, define = function(a, c, d) {
137 if ("string" != typeof a) throw new Error("See almond README: incorrect module build, no module name");
138 c.splice || (d = c, c = []), b(o, a) || b(p, a) || (p[a] = [a, c, d])
139 }, define.amd = {
140 jQuery: !0
141 }
142}(), define("../../../bower_components/almond/almond", function() {}), define("templates", ["require"], function(a) {
143 "use strict";
144 return function(a, b) {
145 return window.FTWTEMPLATES[a + "_" + b] ? window.FTWTEMPLATES[a + "_" + b] : (console.error("Unable to find template: " + a + "_" + b), function() {
146 return "Unable to find template: " + a + "_" + b
147 })
148 }
149}), define("registry", ["require"], function(a) {
150 "use strict";
151 var b = [];
152 return {
153 get: function(a) {
154 return b[a] || void 0
155 },
156 set: function(a, c) {
157 b[a] = c
158 }
159 }
160}), define("shared/scoring", ["require"], function(a) {
161 "use strict";
162 return {
163 speed: function(a, b, c, d) {
164 "object" == typeof a && (b = a.seconds, c = a.errors, d = a.speedType, a = a.typed), d = d || "wpm", c = c || 0;
165 var e, f, g = b / 60;
166 return "kph" === d ? f = Math.round(a / b * 3600) : "cpm" === d ? (e = a, f = Math.max(Math.round(e / g), 0)) : (e = a / 5, f = Math.max(Math.round(e / g), 0)), f === 1 / 0 || isNaN(f) ? 0 : f
167 },
168 accuracy: function(a, b) {
169 "object" == typeof a && (b = a.errors, a = a.typed);
170 var c;
171 return a = a || 1, c = 100 - Math.round(b / a * 100), isNaN(c) ? 0 : c
172 },
173 stars: function(a, b, c) {
174 return b = b || 90, c = c || 95, b > a ? 1 : c > a ? 2 : 3
175 }
176 }
177}), define("global/views/confirm", ["require", "templates"], function(a) {
178 "use strict";
179 var b = a("templates");
180 return Backbone.View.extend({
181 className: "popup-wrap",
182 events: {
183 "click .close": "cancel",
184 "click .ok": "ok"
185 },
186 initialize: function(a) {
187 this.options = a || {}, this.options.position = "fixed", this.template = b("global", "confirm"), this.render()
188 },
189 render: function() {
190 return this.$el.html(this.template({
191 title: this.options.title,
192 text: this.options.text,
193 ok: this.options.ok || "OK",
194 cancel: this.options.cancel || "Cancel"
195 })).fastHide(), $("body").append(this.el), this
196 },
197 cancel: function() {
198 return this.trigger("close", !1), this.close(), !1
199 },
200 ok: function() {
201 return this.trigger("close", !0), this.options.doNotClose ? this.$(".ok").addClass("pending") : this.close(), !1
202 },
203 close: function() {
204 return this.$el.velocity("fadeOut", this.remove.bind(this)), !1
205 },
206 show: function() {
207 return this.render(), this.$el.velocity("fadeIn"), this
208 }
209 })
210}), define("global/collections/badges", ["require", "templates"], function(a) {
211 "use strict";
212 var b = a("templates"),
213 c = Backbone.Model.extend({});
214 return Backbone.Collection.extend({
215 model: c,
216 totalBadges: 12,
217 initialize: function() {
218 for (var a = [], c = 1; c <= this.totalBadges; c++) a.push({
219 id: c,
220 template: b("badges", "badge" + c)
221 });
222 this.reset(a)
223 }
224 })
225}), define("global/models/lesson", ["require"], function(a) {
226 "use strict";
227 var b = Backbone.Model.extend({
228 idAttribute: "lesson_id",
229 defaults: {
230 name: "",
231 course: "",
232 intro: "",
233 seconds: 0,
234 typed: 0,
235 errors: 0,
236 congrats: "",
237 progress: 0,
238 max_progress: 0
239 }
240 });
241 return b
242}), define("global/collections/lessons", ["require", "global/models/lesson", "shared/scoring"], function(a) {
243 "use strict";
244 var b = a("global/models/lesson"),
245 c = a("shared/scoring"),
246 d = Backbone.Collection.extend({
247 model: b,
248 comparator: "sort_order",
249 setProgress: function(a) {
250 a.forEach(function(a) {
251 var b = this.get(a.id);
252 b && "test" != b.get("course") && "problemkeys" != b.get("course") && this.get(a.id).set({
253 progress: a.get("progress"),
254 max_progress: Math.max(a.get("progress"), a.get("max_progress")),
255 speed: c.speed(a.get("typed"), a.get("seconds"), a.get("errors")),
256 accuracy: c.accuracy(a.get("typed"), a.get("errors")),
257 stars: a.get("stars"),
258 updated_at: a.get("updated_at")
259 })
260 }.bind(this))
261 }
262 });
263 return d
264}), define("index/views/dashboard", ["require", "templates", "shared/scoring", "registry", "global/views/confirm", "global/collections/badges", "global/collections/lessons"], function(a) {
265 "use strict";
266 var b = a("templates"),
267 c = a("shared/scoring"),
268 d = a("registry"),
269 e = a("global/views/confirm"),
270 f = a("global/collections/badges"),
271 g = a("global/collections/lessons");
272 return Backbone.View.extend({
273 daysBack: 1,
274 events: {
275 "click .clear": "clearProblemKeys",
276 "click .filter span": "changeTime",
277 "mouseenter .badge": "hoverBadge",
278 "mouseleave .badge": "hoverBadge"
279 },
280 initialize: function(a) {
281 this.options = a, void 0 === this.template && (this.template = b("index", "dashboard")), this.userBadges = d.get("userBadges"), this.user = d.get("user"), this.userLettersTyped = d.get("userLettersTyped"), this.userActivity = d.get("userActivity"), this.lessons = new g(FTWGLOBALS("lessons")), this.badges = new f, this.render()
282 },
283 render: function() {
284 var a = this.userActivity.getCompiled(this.daysBack),
285 b = this.userBadges.last(),
286 d = this.userLettersTyped.topProblemKeys(3).map(function(a) {
287 return a.id.toUpperCase()
288 }),
289 e = this.badges.get(1).get("template");
290 if (b) {
291 b = b.toJSON();
292 var f = this.lessons.get(b.lesson_id);
293 b.lesson = f.get("name"), b.speed = c.speed(b), b.accuracy = c.accuracy(b), b.course = f.get("course"), e = this.badges.get(f.get("badge") || 1).get("template")
294 }
295 var g = d.join("");
296 return "FUK" == g && (g = "UKF"), this.$el.html(this.template({
297 zeroState: 0 === this.userActivity.length && FTWGLOBALS("loggedIn"),
298 daysBack: this.daysBack,
299 user: this.user.toJSON(),
300 speed: c.speed(a.typed, a.seconds, a.errors),
301 accuracy: c.accuracy(a.typed, a.errors),
302 time: a.seconds,
303 keys: g,
304 loggedIn: FTWGLOBALS("loggedIn"),
305 badge: b,
306 badgeTemplate: e
307 })), this
308 },
309 changeTime: function(a) {
310 this.daysBack = $(a.currentTarget).data("id");
311 var b = this.userActivity.getCompiled(this.daysBack);
312 this.$("#dashboard-speed").countdown(c.speed(b.typed, b.seconds, b.errors), null, 400).parent().velocity("fadeIn"), this.$("#dashboard-accuracy").countdown(c.accuracy(b.typed, b.errors), null, 400).parent().velocity("fadeIn");
313 var d = this.$("#dashboard-time"),
314 e = d.data("data");
315 return d.data("data", b.seconds), d.countdown([b.seconds, e], {
316 formatter: function(a) {
317 return a.countdownSeconds()
318 }
319 }, 400).parent().velocity("fadeIn"), this.$(".filter span").removeClass("active"), this.$(".filter span[data-id=" + this.daysBack + "]").addClass("active"), !1
320 },
321 clearProblemKeys: function() {
322 return new e({
323 title: "Clear Your Problem Keys?",
324 text: "Are you sure you would like to clear your Problem Keys?<br /><br />Problem Keys will begin tracking again moving forward."
325 }).show().once("close", function(a) {
326 a && (this.userLettersTyped.clear(), this.render())
327 }.bind(this)), !1
328 },
329 hoverBadge: function(a) {
330 "mouseenter" == a.type ? $(a.currentTarget).velocity("stop", !0).velocity({
331 scale: [1.2, 1]
332 }, 100) : $(a.currentTarget).velocity("stop", !0).velocity("reverse", 50)
333 }
334 })
335}), define("index/views/migration", ["require", "templates", "shared/scoring", "registry"], function(a) {
336 "use strict";
337 var b = a("templates"),
338 c = (a("shared/scoring"), a("registry"));
339 return Backbone.View.extend({
340 events: {
341 "click .close-button": "hide"
342 },
343 initialize: function(a) {
344 this.options = a, void 0 === this.template && (this.template = b("index", "migration")), this.user = c.get("user"), this.render()
345 },
346 render: function() {
347 return this.$el.html(this.template({
348 teacherId: this.user.get("teacher_id")
349 })), this
350 },
351 hide: function() {
352 var a = this.$("input[type=checkbox]");
353 a.prop("checked") && $.post("/apiv1/student/user/account", {
354 migrated: 2
355 }), this.user.set({
356 migrated: 0
357 }), this.$el.velocity("slideUp")
358 }
359 })
360}), define("global/collections/courses", ["require"], function(a) {
361 "use strict";
362 var b = Backbone.Model.extend({
363 idAttribute: "course",
364 defaults: {
365 course: "",
366 sort_order: 0
367 }
368 });
369 return Backbone.Collection.extend({
370 model: b,
371 sort: "sort_order",
372 setProgress: function(a) {
373 var b, c, d = a.toJSON();
374 this.forEach(function(a) {
375 if ("test" != a.get("type") && "problemkeys" != a.get("type")) {
376 c = _.where(d, {
377 course: a.id
378 });
379 var e = c.reduce(function(a, b) {
380 return a + b.max_progress
381 }, 0);
382 if (e > 0) {
383 var f = _.indexOf(c, _.max(c, function(a) {
384 return a.max_progress == a.screens ? a.updated_at : 0
385 })),
386 g = _.max(c, function(a) {
387 return a.max_progress == a.screens ? 0 : a.updated_at
388 }),
389 h = _.max(c, function(a) {
390 return a.updated_at
391 });
392 if (g.max_progress == g.screens && (g = null), -1 == f) b = _.max(c, function(a) {
393 return a.updated_at
394 }), b.max_progress == b.screens && (b = null);
395 else if (h.max_progress != h.screens) b = h;
396 else if (f == c.length - 1) b = null;
397 else {
398 var i = c.slice(f).filter(function(a) {
399 return a.max_progress < a.screens
400 });
401 b = i.length ? i[0] : null
402 }
403 if (!b)
404 if (g) b = g;
405 else {
406 var j = c.filter(function(a) {
407 return a.max_progress < a.screens
408 });
409 b = j.length ? j[0] : null
410 }
411 } else b = _.where(d, {
412 course: a.id
413 })[0];
414 a.set({
415 progress: e,
416 active_lesson: b ? b.lesson_id : null,
417 screens: c.reduce(function(a, b) {
418 return a + b.screens
419 }, 0)
420 })
421 }
422 })
423 }
424 })
425}), define("global/collections/user_lesson_screens", ["require"], function(a) {
426 "use strict";
427 var b = Backbone.Model.extend({
428 idAttribute: "lesson_screen_id",
429 defaults: {
430 lesson_id: 0,
431 lesson_screen_id: 0,
432 seconds: 0,
433 errors: 0,
434 typed: 0,
435 stars: 0,
436 created_at: 0
437 }
438 });
439 return Backbone.Collection.extend({
440 model: b,
441 url: function() {
442 return "/apiv1/student/lessons/" + this.options.lesson_id + "/screens"
443 },
444 initialize: function(a, b) {
445 this.options = b
446 }
447 })
448}), define("shared/model_backends/localstorage", ["require"], function(a) {
449 "use strict";
450 var b = [],
451 c = function(a) {
452 this.table = a
453 };
454 return c.prototype.setModel = function(a) {
455 b.push(a), this.model = a
456 }, c.prototype.getData = function() {
457 return "undefined" == typeof window.localStorage[rot47(this.table)] ? null : JSON.parse(rot47(window.localStorage[rot47(this.table)]))
458 }, c.prototype.setData = function(a) {
459 window.localStorage[rot47(this.table)] = rot47(JSON.stringify(a))
460 }, c.prototype.change = function() {
461 this.setData(this.model.toJSON())
462 }, c.prototype.add = c.prototype.change, c.prototype.remove = c.prototype.change, c.prototype.reset = c.prototype.change, c.prototype.sort = c.prototype.change, c.clear = function() {
463 b.forEach(function(a) {
464 a.off("change")
465 }), localStorage.clear()
466 }, c
467}), define("index/index", ["require", "templates", "registry", "shared/scoring", "index/views/dashboard", "index/views/migration", "global/collections/courses", "global/collections/lessons", "global/collections/user_lesson_screens", "shared/model_backends/localstorage", "global/views/confirm"], function(a) {
468 "use strict";
469 var b = a("templates"),
470 c = a("registry"),
471 d = (a("shared/scoring"), a("index/views/dashboard")),
472 e = a("index/views/migration"),
473 f = a("global/collections/courses"),
474 g = a("global/collections/lessons"),
475 h = a("global/collections/user_lesson_screens"),
476 i = a("shared/model_backends/localstorage"),
477 j = a("global/views/confirm");
478 return Backbone.View.extend({
479 el: "#js-content",
480 events: {
481 "mouseenter .path": "showCourse",
482 "mouseenter .lesson": "hoverLesson",
483 "mouseleave .lesson": "hoverLesson",
484 "mouseenter .path .button": "hoverButton",
485 "mouseleave .path .button": "hoverButton",
486 "click .restart": "restartLesson"
487 },
488 initialize: function(a) {
489 this.options = a || {}, this.user = c.get("user"), this.courses = new f(FTWGLOBALS("courses")), this.lessons = new g(FTWGLOBALS("lessons")), this.userLessons = c.get("userLessons"), FTWGLOBALS("loggedIn") || (this.userLessons.forEach(function(a) {
490 new i("userLessonScreens" + a.id).setData(null)
491 }), this.userLessons.reset(), this.user.clearLocalProgress()), this.dashboardView = new d({}), this.updateModelsWithProgress(), void 0 === this.template && (this.template = b("index", "index")), this.render()
492 },
493 restartLesson: function(a) {
494 var b = $(a.currentTarget),
495 c = b.data("id"),
496 d = this.userLessons.get(c),
497 e = new j({
498 title: "Restart Lesson: " + this.lessons.get(c).get("name"),
499 text: "Are you sure you would like to restart the lesson?",
500 doNotClose: !0
501 });
502 return e.show().once("close", function(a) {
503 a ? d.restart().done(function() {
504 d.set({
505 progress: 0,
506 max_progress: 0,
507 stars: 0,
508 typed: 0,
509 seconds: 0,
510 errors: 0
511 });
512 var a = new h(null, {
513 lesson_id: d.id
514 });
515 a.setBackend(new i("userLessonScreens" + d.id), "backend"), a.reset(), location.href = "/student/lessons/" + d.id
516 }.bind(this)).fail(function() {
517 alert("There was an error resetting your lesson!")
518 }) : e.close()
519 }.bind(this)), !1
520 },
521 updateModelsWithProgress: function() {
522 this.lessons.setProgress(this.userLessons), this.courses.setProgress(this.lessons)
523 },
524 createPie: function(a) {
525 var b, c, d, e, f, g, h, i = this.lessons.where({
526 course: a
527 }),
528 j = i.length,
529 k = 360 * (1 / j),
530 l = 0,
531 m = -90;
532 b = this.$("#course-pie-" + a)[0], b && _.sortBy(i, function(a) {
533 return a.get("max_progress") == a.get("screens") ? 0 : 1
534 }).forEach(function(a, i) {
535 l = m, m = l + k, c = parseInt(Math.round(200 + 195 * Math.cos(Math.PI * l / 180))), e = parseInt(Math.round(200 + 195 * Math.sin(Math.PI * l / 180))), d = parseInt(Math.round(200 + 195 * Math.cos(Math.PI * m / 180))), f = parseInt(Math.round(200 + 195 * Math.sin(Math.PI * m / 180))), g = "M200,200 L" + c + "," + e + " A195,195 0 " + (m - l > 180 ? 1 : 0) + ",1 " + d + "," + f + " z", h = document.createElementNS("http://www.w3.org/2000/svg", "path"), h.setAttribute("d", g), h.setAttribute("class", a.get("max_progress") == a.get("screens") ? "complete" : ""), b.appendChild(h)
536 })
537 },
538 render: function() {
539 var a = this.userLessons.sortBy(function(a) {
540 return -a.get("updated_at")
541 })[0],
542 b = a && this.lessons.get(a.id) ? this.lessons.get(a.id).get("course") : "beginner";
543 if ("test" == b && (b = "beginner"), this.$el.append(this.template({
544 loggedIn: FTWGLOBALS("loggedIn"),
545 currentCourse: b,
546 courses: this.courses.toJSON(),
547 lessons: this.lessons.toJSON(),
548 user: this.user.toJSON()
549 })), 1 == this.user.get("migrated")) {
550 new e({
551 el: this.$("#migrationContent")
552 })
553 }
554 this.dashboardView && this.$("#dashboardContent").html(this.dashboardView.el), this.courses.forEach(function(a) {
555 this.createPie(a.id)
556 }.bind(this))
557 },
558 showCourse: function(a) {
559 var b = $(a.currentTarget);
560 if (b.hasClass("active")) return !1;
561 var c = this.$("#lessons-course-" + $(a.currentTarget).data("id")),
562 d = this.$(".path.active"),
563 e = this.$("#lessons-course-" + d.data("id"));
564 d.removeClass("active"), e.fastHide(), b.addClass("active"), c.fastShow()
565 },
566 hoverLesson: function(a) {
567 "mouseenter" == a.type ? $(a.currentTarget).velocity("stop", !0).velocity({
568 scale: [1.02, 1]
569 }, 80, "ease-in") : $(a.currentTarget).velocity("stop", !0).velocity("reverse", 50)
570 },
571 hoverButton: function(a) {
572 "mouseenter" == a.type ? $(a.currentTarget).velocity("stop", !0).velocity({
573 scale: [1.1, 1]
574 }, 150) : $(a.currentTarget).velocity("stop", !0).velocity("reverse")
575 }
576 })
577}), define("oauth/index", ["require", "registry"], function(a) {
578 "use strict";
579 var b = a("registry");
580 return Backbone.View.extend({
581 initialize: function() {
582 var a = {
583 tz: jstz.determine().name(),
584 screenWidth: screen.width
585 };
586 $.post("/apiv1/student/auth/oauth-login", a).done(function(a) {
587 b.get("user").loginWithData(a.data), window.location.href = "/student"
588 }).fail(function(a) {
589 a.responseJSON && a.responseJSON.data.message ? this.showError("Uh oh, an error occurred", a.responseJSON.data.message) : this.showError("Uh oh, an error occurred", a.responseText)
590 }.bind(this))
591 },
592 showError: function(a, b) {
593 $("#title").velocity({
594 opacity: [0, 1]
595 }, function() {
596 $(this).html(a).velocity({
597 opacity: [1, 0]
598 }), $("#message").html(b), $(".loader").fastHide()
599 })
600 }
601 })
602}), define("shared/analytics", ["require"], function(a) {
603 "use strict";
604 return {
605 trackEvent: function(a, b, c, d) {
606 ga("send", "event", a, b, c, d)
607 },
608 trackPage: function(a) {
609 ga("send", "pageview", a)
610 },
611 customDimension: function(a, b) {
612 ga("set", "dimension" + a, b)
613 },
614 customMetric: function(a, b) {
615 ga("set", "metric" + a, b)
616 },
617 trackSale: function(a, b) {
618 ga("require", "ecommerce"), ga("ecommerce:addTransaction", {
619 id: a.transaction_id,
620 affiliation: a.store,
621 revenue: a.total,
622 shipping: 0,
623 tax: 0
624 }), b && b.forEach(function(b) {
625 ga("ecommerce:addItem", {
626 id: a.transaction_id,
627 name: b.product,
628 sku: b.sku,
629 category: b.category,
630 price: b.price,
631 quantity: b.quantity || 1
632 })
633 }), ga("ecommerce:send")
634 }
635 }
636}), define("shared/form_validation", ["require"], function(a) {
637 "use strict";
638 var b = function(a, b, c, d, e, f, g) {
639 1 == arguments.length ? (_.extend(this, arguments[0]), this.bindTo && this.successCallback && (this.successCallback = this.successCallback.bind(this.bindTo))) : (this.form = a, this.button = b, this.model = c, e && (d = d.bind(e)), this.successCallback = d || $.noop, this.remainDisabled = f), g && (c.fileUpload = a), this.setup()
640 };
641 return b.prototype.setup = function() {
642 this.form.validate({
643 submitHandler: _.bind(this.submitHandler, this),
644 rules: this.model.validationRules || {},
645 messages: this.model.validationMessages || {},
646 invalidHandler: function(a, b) {
647 var c = b.errorList.map(function(a) {
648 return a.element
649 });
650 $.Velocity(c, "ftw.miniShake")
651 }.bind(this)
652 });
653 var a = this;
654 this.button.click(function(b) {
655 b.preventDefault(), a.form.submit()
656 })
657 }, b.prototype.submitHandler = function(a) {
658 if (this.options = a || {
659 delay: 300
660 }, !this.submitted) {
661 this.disableButton();
662 var b = {},
663 c = this.model.toJSON();
664 _.each(this.form.serializeArray(), function(a) {
665 a.value != c[a.name] && (b[a.name] = a.value)
666 }), setTimeout(function() {
667 this.save(b, {
668 patch: !0,
669 silent: !1,
670 wait: !0
671 })
672 }.bind(this), this.options.delay || 0)
673 }
674 }, b.prototype.saveSuccess = function(a) {
675 this.successCallback(a.data, this.model, this.form), this.remainDisabled || this.enableButton()
676 }, b.prototype.saveError = function(a) {
677 if (a = a || {}, $._lastXhr = {
678 response: JSON.stringify(a)
679 }, "object" == typeof a && (a = a.responseJSON || a.responseText || a.data), this.failureCallback && this.failureCallback(a), a) {
680 var b = this.model.validationMessages || {};
681 if ("object" == typeof a) {
682 _.each(a, function(c, d) {
683 b[d] && b[d][c] && (a[d] = b[d][c])
684 }.bind(this)), this.form.validate().showErrors(a), $.Velocity($(".error", this.form), "ftw.miniShake");
685 var c = Object.keys(a)[0];
686 this.form.find("[name=" + c + "]").focus()
687 } else alert("Response from the server was invalid. Response: " + JSON.stringify(a))
688 } else alert("There was an error communicating with the server: No response was returned. Please check your internet connection and try again.");
689 this.enableButton()
690 }, b.prototype.disableButton = function() {
691 this.button.addClass("pending"), this.submitted = !0
692 }, b.prototype.enableButton = function() {
693 this.button.removeClass("pending"), this.submitted = !1
694 }, b.prototype.save = function(a, b) {
695 this.model.save(a, b).done(function(a) {
696 a.success ? this.saveSuccess(a) : this.saveError(a)
697 }.bind(this)).fail(function(a) {
698 var b = a.responseJSON || a.responseText;
699 this.saveError(b)
700 }.bind(this))
701 }, b
702}), define("shared/form_model", ["require"], function(a) {
703 "use strict";
704 var b = Backbone.Model.extend({
705 initialize: function() {
706 $.validator.addMethod("alphanum", function(a) {
707 return /^[a-zA-Z0-9]+$/.test(a)
708 }, "Can only contain letters and numbers")
709 },
710 validationRules: {},
711 validationMessages: {},
712 sync: function(a) {
713 if ("read" == a || "delete" == a || !this.fileUpload) return Backbone.Model.prototype.sync.apply(this, arguments);
714 var b = this.id ? this.urlRoot + "/" + this.id : this.urlRoot,
715 c = this.id ? "patch" : "post";
716 return $.ajax({
717 url: b,
718 method: "POST",
719 data: new FormData($(this.fileUpload)[0]),
720 dataType: "json",
721 processData: !1,
722 contentType: !1,
723 headers: {
724 "X-HTTP-METHOD-OVERRIDE": c
725 }
726 }).done(function(a) {
727 this.set(a.data)
728 }.bind(this))
729 }
730 });
731 return b
732}), define("global/models/signup", ["require", "shared/form_model"], function(a) {
733 "use strict";
734 var b = a("shared/form_model");
735 return b.extend({
736 initialize: function() {},
737 url: "/apiv1/student/auth/signup",
738 defaults: {
739 username: "",
740 password: "",
741 password2: "",
742 email: "",
743 tz: jstz.determine().name()
744 },
745 validationRules: {
746 username: {
747 minlength: 4,
748 maxlength: 30,
749 username: !0,
750 required: !0,
751 remote: {
752 url: "/apiv1/student/auth/username",
753 type: "post"
754 }
755 },
756 email: {
757 require: !1,
758 email: !0,
759 emailExtended: !0,
760 remote: {
761 url: "/apiv1/student/auth/email",
762 type: "post"
763 }
764 },
765 password: {
766 required: !0,
767 minlength: 4,
768 notMatchUsername: !0
769 },
770 password2: {
771 required: !0,
772 minlength: 4,
773 equalTo: "#reg-form input[name=password]"
774 }
775 },
776 validationMessages: {
777 username: {
778 remote: "This username already exists. Please try a different username.",
779 EXISTS: "This username already exists. Please try a different username.",
780 minlength: "Username must be at between 4 and 30 characters in length",
781 maxlength: "Username must be at between 4 and 30 characters in length",
782 INVALID_LENGTH: "Username must be at between 4 and 30 characters in length",
783 INVALID_CHARS: "Can only contain letters, numbers, and underscores",
784 INVALID_IP: "Your teacher has restricted access to only specific locations. Your IP address does not match the list of allowed IPs.",
785 NO_MOBILE_ACCESS: "Your teacher has restricted access form small/mobile devices. Please log in from a computer."
786 },
787 email: {
788 email: "Invalid email address. Keep in mind, your email is optional.",
789 remote: 'An account with this email already exists. If you lost your password, you should use the "I forgot my login" feature to recover it.',
790 EXISTS: 'An account with this email already exists. If you lost your password, you should use the "I forgot my login" feature to recover it.',
791 INVALID: "Invalid email address, please double check. Keep in mind, your email is optional."
792 },
793 password: {
794 notMatchUsername: "Password can not be the same as your username",
795 NO_MATCH_USERNAME: "Password can not be the same as your username",
796 minlength: "Password must be at least 4 characters long",
797 INVALID_LENGTH: "Password must be at least 4 characters long"
798 },
799 password2: {
800 required: "Please confirm your password",
801 equalTo: "Passwords do not match",
802 NO_MATCH: "Passwords do not match"
803 },
804 section_id: {
805 INVALID: "Invalid Section ID or code"
806 }
807 }
808 })
809}), define("global/views/signup_form", ["require", "templates", "shared/analytics", "registry", "shared/form_validation", "global/models/signup"], function(a) {
810 "use strict";
811 var b = a("templates"),
812 c = a("shared/analytics"),
813 d = a("registry"),
814 e = a("shared/form_validation"),
815 f = a("global/models/signup");
816 return Backbone.View.extend({
817 events: {
818 "keypress #reg-form input": "submitOnEnter"
819 },
820 initialize: function(a) {
821 a.template ? this.template = a.template : this.template = b("global", "signup_form"), a.classes && (this.classes = a.classes), a.joinCode && (this.joinCode = a.joinCode), this.model = new f, this.render(), this.form = this.$("#reg-form");
822 var c = this.$("#reg-form .submit");
823 new e({
824 form: this.form,
825 button: c,
826 model: this.model,
827 successCallback: this.successCallback.bind(this),
828 remainDisabled: !0
829 })
830 },
831 render: function() {
832 this.$el.html(this.template({
833 classes: this.classes,
834 joinCode: this.joinCode
835 }))
836 },
837 successCallback: function(a) {
838 c.trackPage("/student/signup/success"), d.get("user").loginWithData(a), window.location.href = "/student"
839 },
840 submitOnEnter: function(a) {
841 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
842 }
843 })
844}), define("global/views/modal", ["require", "templates"], function(a) {
845 "use strict";
846 var b = a("templates");
847 return Backbone.View.extend({
848 contentView: null,
849 className: "popup-wrap",
850 events: {
851 "click .close": "hide",
852 "click .ok": "ok"
853 },
854 initialize: function(a) {
855 if (this.options = a || {}, this.template = b("global", "modal"), !a || !_.isObject(a.contentView)) throw new Error("Cannot create a modal without a contentView");
856 this.contentView = a.contentView, this.listenTo(this.contentView, "complete", function(a) {
857 this.trigger("complete", a), this.hide()
858 }), this.render()
859 },
860 render: function() {
861 return this.$el.html(this.template({
862 title: this.options.title,
863 okButton: this.options.okButton,
864 cancelButton: this.options.cancelButton,
865 closeButton: void 0 === this.options.closeButton ? !0 : this.options.closeButton,
866 width: this.options.width || null
867 })).fastHide(), this.contentView.button = this.$(".ok"), this.$(".popup-content").html(this.contentView.render().el), this
868 },
869 ok: function() {
870 return this.contentView.ok(), !1
871 },
872 show: function() {
873 var a;
874 if ("fixed" == this.options.position) $("body").append(this.el), a = this.$el;
875 else {
876 var b = $("<div>"),
877 c = this.$el,
878 d = this.$(".popup");
879 d.addClass("popup-absolute"), $("body").append(b), this.setElement(b), b.append(c), b.append(d), d.css({
880 top: $(window).scrollTop() + 50,
881 left: $(window).width() / 2 - d.outerWidth() / 2
882 }), a = c
883 }
884 a.velocity("fadeIn", function() {
885 this.$el.find("input[type=text]:eq(0)").focus()
886 }.bind(this))
887 },
888 hide: function() {
889 return this.$el.velocity("fadeOut", this.remove.bind(this)), !1
890 }
891 })
892}), define("global/views/notice", ["require", "templates"], function(a) {
893 "use strict";
894 var b = a("templates");
895 return Backbone.View.extend({
896 events: {
897 "click span": "close",
898 mouseover: "hover"
899 },
900 initialize: function(a) {
901 this.options = a || {}, this.template = b("global", "notice")
902 },
903 render: function() {
904 var a = $("#notices");
905 return a.length || (a = $('<div id="notices"></div>'), $("#js-content").prepend(a)), this.$el.html(this.template({
906 error: this.options.error,
907 text: this.options.text
908 })).fastHide().appendTo(a), this
909 },
910 close: function() {
911 return clearTimeout(this.timeout), this.$el.velocity("fadeOut", this.remove.bind(this)), !1
912 },
913 show: function() {
914 this.render(), this.$el.velocity("fadeIn"), this.timeout = setTimeout(this.close.bind(this), 5e3)
915 },
916 hover: function() {
917 clearTimeout(this.timeout)
918 }
919 })
920}), define("global/views/alert", ["require", "templates"], function(a) {
921 "use strict";
922 var b = a("templates");
923 return Backbone.View.extend({
924 events: {
925 "click .ok": "close"
926 },
927 initialize: function(a) {
928 this.options = a || {}, this.options.position = "fixed", this.template = b("global", "alert")
929 },
930 render: function() {
931 return this.setElement(this.template({
932 title: this.options.title,
933 text: this.options.text,
934 ok: this.options.ok
935 })), this.$el.fastHide(), $("body").append(this.el), this
936 },
937 close: function() {
938 return this.trigger("close"), this.$el.velocity("fadeOut", this.remove.bind(this)), !1
939 },
940 show: function() {
941 return this.render(), this.$el.velocity("fadeIn", function() {
942 this.$el.find("input:eq(0)").focus()
943 }.bind(this)), this
944 }
945 })
946}), define("global/models/login", ["require", "shared/form_model"], function(a) {
947 "use strict";
948 var b = a("shared/form_model");
949 return b.extend({
950 url: "/apiv1/student/auth/login",
951 defaults: {
952 username: null,
953 password: null,
954 tz: jstz.determine().name(),
955 screenWidth: screen.width,
956 adb: !1
957 },
958 validationRules: {
959 username: {
960 required: !0
961 },
962 password: {
963 required: !0
964 }
965 },
966 validationMessages: {
967 username: {
968 INVALID_USERNAME: "This username does not exist",
969 CLASS_MIGRATION: "This account has not been migrated",
970 INVALID_IP: "Your teacher has restricted access to only specific locations. Your IP address does not match the list of allowed IPs.",
971 NO_MOBILE_ACCESS: "Your teacher has restricted access form small/mobile devices. Please log in from a computer."
972 },
973 password: {
974 INCORRECT_PASSWORD: "Your password is incorrect for this username, please try again"
975 }
976 }
977 })
978}), define("global/views/forgot_password", ["require", "templates", "shared/form_model", "shared/form_validation"], function(a) {
979 "use strict";
980 var b = a("templates"),
981 c = a("shared/form_model"),
982 d = a("shared/form_validation"),
983 e = c.extend({
984 url: "/apiv1/student/auth/password-reset",
985 defaults: {
986 email: ""
987 },
988 validationRules: {
989 email: {
990 email: !0,
991 emailExtended: !0,
992 required: !0
993 }
994 }
995 });
996 return Backbone.View.extend({
997 events: {
998 "keypress input": "submitOnEnter"
999 },
1000 baseModel: null,
1001 initialize: function(a) {
1002 this.options = a, this.template = b("global", "forgot_password"), this.templateNoMigrate = b("global", "forgot_password_no_migration"), this.model = new e
1003 },
1004 render: function() {
1005 return this.$el.html(this.template({})), this.form = this.$("form"), new d({
1006 form: this.form,
1007 button: this.button,
1008 model: this.model,
1009 successCallback: this.successCallback.bind(this),
1010 failureCallback: this.failureCallback.bind(this),
1011 bindTo: this,
1012 remainDisabled: !0
1013 }), this
1014 },
1015 ok: function() {
1016 this.notMigrated ? location.href = "https://classic.typing.com/tutor/" : this.form.submit()
1017 },
1018 successCallback: function(a) {
1019 this.trigger("complete", a)
1020 },
1021 failureCallback: function(a) {
1022 a && "NOT_MIGRATED" == a.email && (this.notMigrated = !0, this.$el.html(this.templateNoMigrate()))
1023 },
1024 submitOnEnter: function(a) {
1025 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
1026 }
1027 })
1028}), define("global/views/login_form", ["require", "templates", "registry", "shared/form_validation", "global/views/modal", "global/views/notice", "global/views/alert", "global/models/login", "global/views/forgot_password"], function(a) {
1029 "use strict";
1030 var b = a("templates"),
1031 c = a("registry"),
1032 d = a("shared/form_validation"),
1033 e = a("global/views/modal"),
1034 f = a("global/views/notice"),
1035 g = a("global/views/alert"),
1036 h = a("global/models/login"),
1037 i = a("global/views/forgot_password");
1038 return Backbone.View.extend({
1039 events: {
1040 "keypress #login-form input": "submitOnEnter",
1041 "click .forgot-password-callout": "forgotPassword"
1042 },
1043 initialize: function() {
1044 this.template = b("global", "login_form"), this.model = new h, this.render(), this.form = this.$("#login-form");
1045 var a = this.$("#login-form .submit");
1046 new d({
1047 form: this.form,
1048 button: a,
1049 model: this.model,
1050 failureCallback: this.failureCallback.bind(this),
1051 successCallback: this.successCallback.bind(this),
1052 remainDisabled: !0
1053 })
1054 },
1055 render: function() {
1056 this.$el.html(this.template({
1057 username: location.hash.replace("#", "") || ""
1058 }))
1059 },
1060 successCallback: function(a) {
1061 c.get("user").loginWithData(a), window.location.href = "/student"
1062 },
1063 failureCallback: function(a) {
1064 a && "CLASS_MIGRATION" == a.username && new g({
1065 title: "Oh no!",
1066 text: 'Your teacher has not yet migrated to the new version of Typing.com.</p><p>To continue your lessons, please go to <a href="http://classic.typing.com/tutor/">http://classic.typing.com/tutor</a> and log in.'
1067 }).show()
1068 },
1069 submitOnEnter: function(a) {
1070 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
1071 },
1072 forgotPassword: function() {
1073 var a = new e({
1074 title: "I forgot my login!",
1075 okButton: "Continue",
1076 cancelButton: "Cancel",
1077 contentView: new i,
1078 width: 500
1079 });
1080 return a.on("complete", function(a) {
1081 a && a.bounce ? new f({
1082 error: !0,
1083 text: "An email has been sent with instructions for changing your password.<br /><br />However, the last email we sent you was undeliverable. Your mail service returned the error: " + a.bounce + "<br /><br />Because of this, it is possible you will not receive this email."
1084 }).show() : new f({
1085 error: !1,
1086 text: "An email has been sent with instructions for changing your password."
1087 }).show()
1088 }.bind(this)), a.show(), !1
1089 }
1090 })
1091}), define("global/views/login", ["require", "templates", "global/views/signup_form", "global/views/login_form"], function(a) {
1092 "use strict";
1093 var b = a("templates"),
1094 c = a("global/views/signup_form"),
1095 d = a("global/views/login_form");
1096 return Backbone.View.extend({
1097 events: {
1098 "click #cleverLogin": "cleverLogin",
1099 "click #googleLogin": "googleLogin"
1100 },
1101 initialize: function(a) {
1102 this.options = a || {}, void 0 === this.template && (this.template = b("global", "login")), this.render(), new c({
1103 el: this.$("#signupContent")
1104 }), new d({
1105 el: this.$("#loginContent")
1106 }), this.focus()
1107 },
1108 render: function() {
1109 return this.$el.html(this.template({
1110 hideTeacherCallout: this.options.hideTeacherCallout
1111 })), this
1112 },
1113 focus: function() {
1114 "/student/signup" == location.pathname ? $("#reg-form input[name=username]").focus() : $("#login-form input[name=username]").focus()
1115 },
1116 getState: function() {
1117 var a = _.random(1e4, 99999);
1118 return $.cookie("state", a, {
1119 path: "/"
1120 }), a
1121 },
1122 cleverLogin: function() {
1123 return location.href = "https://clever.com/oauth/authorize?client_id=" + FTWGLOBALS("cleverClientId") + "&redirect_uri=" + encodeURIComponent(FTWGLOBALS("cleverRedirectUri")) + "&response_type=code&state=" + this.getState(), !1
1124 },
1125 googleLogin: function() {
1126 return location.href = "https://accounts.google.com/o/oauth2/auth?client_id=" + FTWGLOBALS("googleClientId") + "&redirect_uri=" + encodeURIComponent(FTWGLOBALS("googleRedirectUri")) + "&response_type=code&fetch_basic_profile=true&scope=openid%20email%20profile&state=" + this.getState(), !1
1127 }
1128 })
1129}), define("index/login", ["require", "global/views/login"], function(a) {
1130 "use strict";
1131 var b = a("global/views/login");
1132 return Backbone.View.extend({
1133 el: "#js-content",
1134 initialize: function() {
1135 new b({
1136 el: this.el
1137 })
1138 }
1139 })
1140}), define("join/views/classes_modal", ["require", "templates"], function(a) {
1141 "use strict";
1142 var b = a("templates");
1143 return Backbone.View.extend({
1144 initialize: function(a) {
1145 this.options = a, this.template = b("join", "classes_modal")
1146 },
1147 render: function() {
1148 return this.$el.html(this.template({
1149 classes: this.options.classes
1150 })), this
1151 },
1152 ok: function() {
1153 this.successCallback()
1154 },
1155 successCallback: function() {
1156 this.trigger("complete", this.$("select[name=section_id]").val())
1157 }
1158 })
1159}), define("join/index", ["require", "templates", "global/views/signup_form", "global/views/modal", "join/views/classes_modal"], function(a) {
1160 "use strict";
1161 var b = a("templates"),
1162 c = a("global/views/signup_form"),
1163 d = a("global/views/modal"),
1164 e = a("join/views/classes_modal");
1165 return Backbone.View.extend({
1166 el: "#js-content",
1167 events: {
1168 "click #cleverLogin": "cleverLogin",
1169 "click #googleLogin": "googleLogin"
1170 },
1171 initialize: function(a) {
1172 this.options = a, this.template = b("join", "index"), this.formTemplate = b("join", "form"), this.failedTemplate = b("join", "failed"), $.post("/apiv1/student/auth/join-validate", {
1173 join_code: a.code
1174 }).done(function(a) {
1175 this.classes = a.data, this.render()
1176 }.bind(this)).fail(this.renderFailed.bind(this))
1177 },
1178 render: function() {
1179 this.$el.html(this.template()), new c({
1180 el: this.$("#signupContent"),
1181 template: this.formTemplate,
1182 classes: this.classes,
1183 joinCode: this.options.code
1184 })
1185 },
1186 renderFailed: function() {
1187 this.$el.html(this.failedTemplate())
1188 },
1189 getState: function() {
1190 var a = _.random(1e4, 99999);
1191 return $.cookie("state", a, {
1192 path: "/"
1193 }), a
1194 },
1195 cleverLogin: function() {
1196 return location.href = "https://clever.com/oauth/authorize?client_id=" + FTWGLOBALS("cleverClientId") + "&redirect_uri=" + encodeURIComponent(FTWGLOBALS("cleverRedirectUri")) + "&response_type=code&state=" + this.getState(), !1
1197 },
1198 googleLogin: function() {
1199 if ($.cookie("_joinCode", this.options.code, {
1200 path: "/"
1201 }), 1 == this.classes.length) return $.cookie("_joinClassId", this.classes[0].section_id, {
1202 path: "/"
1203 }), this.completeGoogleLogin();
1204 var a = new d({
1205 width: 450,
1206 title: "Select Your Class",
1207 okButton: "Continue",
1208 cancelButton: "Cancel",
1209 contentView: new e({
1210 classes: this.classes
1211 })
1212 });
1213 return a.on("complete", function(b) {
1214 $.cookie("_joinClassId", b, {
1215 path: "/"
1216 }), a.hide(), this.completeGoogleLogin()
1217 }.bind(this)), a.show(), !1
1218 },
1219 completeGoogleLogin: function() {
1220 return location.href = "https://accounts.google.com/o/oauth2/auth?client_id=" + FTWGLOBALS("googleClientId") + "&redirect_uri=" + encodeURIComponent(FTWGLOBALS("googleRedirectUri")) + "&response_type=code&scope=openid&state=" + this.getState(), !1
1221 }
1222 })
1223}), define("password/index", ["require", "templates", "shared/form_model", "shared/form_validation", "global/views/notice"], function(a) {
1224 "use strict";
1225 var b = a("templates"),
1226 c = a("shared/form_model"),
1227 d = a("shared/form_validation"),
1228 e = (a("global/views/notice"), c.extend({
1229 initialize: function() {},
1230 url: "/apiv1/student/auth/password-update",
1231 defaults: {
1232 hash: "",
1233 user_id: "",
1234 username: "",
1235 password: "",
1236 password2: ""
1237 },
1238 validationRules: {
1239 password: {
1240 required: !0,
1241 minlength: 4
1242 },
1243 password2: {
1244 required: !0,
1245 minlength: 4,
1246 equalTo: "#password-form input[name=password]"
1247 }
1248 },
1249 validationMessages: {
1250 password: {
1251 required: "Please enter a password"
1252 },
1253 password2: {
1254 required: "Please confirm your password",
1255 equalTo: "Passwords do not match"
1256 }
1257 }
1258 })),
1259 f = Backbone.View.extend({
1260 events: {
1261 "keypress #password-form input": "submitOnEnter"
1262 },
1263 initialize: function(a) {
1264 this.model = new e(a.data), this.form = this.$("#password-form");
1265 var b = this.$("#password-form .submit");
1266 new d(this.form, b, this.model, this.successCallback, this, !0)
1267 },
1268 successCallback: function(a) {
1269 this.trigger("complete")
1270 },
1271 submitOnEnter: function(a) {
1272 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
1273 }
1274 });
1275 return Backbone.View.extend({
1276 el: "#js-content",
1277 initialize: function(a) {
1278 this.options = a || {}, this.template = b("password", "index"), this.failedTemplate = b("password", "failed"), this.completeTemplate = b("password", "complete");
1279 var c = location.hash.replace("#", "").split("!"),
1280 d = c[0],
1281 e = c[1];
1282 $.post("/apiv1/student/auth/password-validate", {
1283 hash: d,
1284 user_id: e
1285 }).done(function(a) {
1286 this.data = {
1287 username: a.data.username,
1288 hash: d,
1289 user_id: e
1290 }, this.render()
1291 }.bind(this)).fail(this.renderFailed.bind(this))
1292 },
1293 render: function() {
1294 this.$el.html(this.template(this.data));
1295 var a = new f({
1296 el: this.el,
1297 data: this.data
1298 });
1299 return a.on("complete", this.renderComplete.bind(this)), this.$("#password-form input[name=password]").focus(), this
1300 },
1301 renderFailed: function() {
1302 this.$el.html(this.failedTemplate())
1303 },
1304 renderComplete: function() {
1305 this.$el.velocity("slideUp", function() {
1306 this.$el.html(this.completeTemplate()).velocity("slideDown")
1307 }.bind(this))
1308 }
1309 })
1310}), define("scoreboard/collections/scoreboard", ["require", "shared/scoring"], function(a) {
1311 "use strict";
1312 var b = a("shared/scoring");
1313 return Backbone.Collection.extend({
1314 model: Backbone.Model.extend({
1315 idAttribute: "user_id"
1316 }),
1317 url: "/apiv1/student/scoreboard",
1318 parse: function(a) {
1319 var c = a.data;
1320 return _.isArray(c) && c.forEach(function(a) {
1321 a.speed = b.speed(a.typed, a.seconds, a.errors), a.accuracy = b.accuracy(a.typed, a.errors)
1322 }), Backbone.Collection.prototype.parse.call(this, a)
1323 },
1324 comparatorKey: "speed",
1325 comparator: function(a) {
1326 return -a.get(this.comparatorKey)
1327 }
1328 })
1329}), define("scoreboard/index", ["require", "templates", "registry", "scoreboard/collections/scoreboard"], function(a) {
1330 "use strict";
1331 var b = a("templates"),
1332 c = a("registry"),
1333 d = a("scoreboard/collections/scoreboard");
1334 return Backbone.View.extend({
1335 el: "#js-content",
1336 events: {
1337 "click .type": "changeType"
1338 },
1339 initialize: function() {
1340 this.template = b("scoreboard", "index"), this.collection = new d, this.collection.fetch({
1341 reset: !0
1342 }), this.listenTo(this.collection, "reset", this.render), this.user = c.get("user")
1343 },
1344 render: function() {
1345 this.$el.html(this.template({
1346 type: this.collection.comparatorKey,
1347 data: this.collection.toJSON()
1348 }))
1349 },
1350 changeType: function(a) {
1351 var b = $(a.currentTarget);
1352 return b.hasClass("active") ? !1 : (this.collection.comparatorKey = b.data("id"), this.collection.sort(), this.render(), !1)
1353 }
1354 })
1355}), define("verify/models/test", ["require", "shared/form_model"], function(a) {
1356 "use strict";
1357 var b = a("shared/form_model");
1358 return b.extend({
1359 idAttribute: "user_test_id",
1360 url: function() {
1361 return "/apiv1/student/tests/" + this.id + "/verify?user_id=" + this.get("user_id")
1362 }
1363 })
1364}), define("verify/index", ["require", "templates", "registry", "verify/models/test"], function(a) {
1365 "use strict";
1366 var b = a("templates"),
1367 c = (a("registry"), a("verify/models/test"));
1368 return Backbone.View.extend({
1369 el: "#js-content",
1370 initialize: function(a) {
1371 this.template = b("verify", "index"), this.model = new c(a), this.model.fetch().done(this.render.bind(this))
1372 },
1373 render: function(a) {
1374 this.$el.html(this.template({
1375 data: this.model.toJSON()
1376 }))
1377 }
1378 })
1379}), define("shared/radar_tracker", ["require"], function(a) {
1380 "use strict";
1381 return {
1382 track: function(a, b) {
1383 if (a.get("teacher_id")) {
1384 var c = {
1385 t: a.get("teacher_id"),
1386 u: a.id
1387 };
1388 b && (c.l = {
1389 t: b.typed,
1390 s: b.seconds,
1391 e: b.errors,
1392 p: b.progress
1393 }), $.get("/apiv1/student/radar/" + window.btoa(JSON.stringify(c)))
1394 }
1395 }
1396 }
1397}), define("lessons/models/screen", ["require"], function(a) {
1398 "use strict";
1399 return Backbone.Model.extend({
1400 idAttribute: "lesson_screen_id",
1401 defaults: {
1402 content: "",
1403 formatted_content: "",
1404 lastKey: "",
1405 line: 0,
1406 typed: 0,
1407 errors: 0,
1408 seconds: 0,
1409 lastKeyCode: 0,
1410 lastKeyError: !1
1411 },
1412 formatContent: function(a, b) {
1413 var c = this.get("content"),
1414 d = this.get("title"),
1415 e = this.get("intro");
1416 c = c.replace(/\r/g, "").replace(/\s+\n/g, "\n").trim(), "speed" == this.get("screen_type") && (c = c + "\n" + c + "\n" + c + "\n" + c + "\n"), c = this.periodSpacing(c, b.get("spaces"));
1417 var f = c;
1418 c = c.replace(/âÂŽ\n/g, "âÂŽ"), this.set({
1419 title: d,
1420 intro: e,
1421 content: c,
1422 formatted_content: f
1423 })
1424 },
1425 getContentByLine: function() {
1426 return _.compact(this.get("formatted_content").split(/\n/)).map(function(a, b, c) {
1427 return a = a.split(""), b < c.length && "âÂŽ" != a[a.length - 1] && a.push("\n"), a
1428 })
1429 },
1430 charAt: function(a) {
1431 var b = this.get("content") || "";
1432 return b[a]
1433 },
1434 handleInput: function(a) {
1435 var b = this.get("typed"),
1436 c = this.get("screen_type"),
1437 d = this.get("errors"),
1438 e = this.get("line"),
1439 f = b,
1440 g = a.key,
1441 h = this.charAt(f),
1442 i = "\n" == h || "âÂŽ" == h,
1443 j = !1;
1444 if ("\n" == h && " " == g && "speed" != c && (h = " "), "âÂŽ" == h && "\n" == g && (h = "\n"), !(f >= this.get("content").length)) {
1445 if ("BACKSPACE" == g) {
1446 if ("falling" == c) return;
1447 if ("speed" == c && ("\n" == this.charAt(f - 1) || "âÂŽ" == this.charAt(f - 1))) return;
1448 "\n" != this.charAt(f - 1) && "âÂŽ" != this.charAt(f - 1) || e--, f > 0 && (f -= 1)
1449 } else {
1450 if (a.special) return;
1451 h == g ? (f += 1, i && e++) : h != g && (j = !0, this.get("lastKeyError") || (f += "falling" == this.get("screen_type") ? 0 : 1, d += 1, i && e++))
1452 }
1453 this.set({
1454 typed: f,
1455 nextKey: this.charAt(f),
1456 letterTyped: g,
1457 lastKeyError: j,
1458 errors: d,
1459 correctLetter: h,
1460 line: e
1461 }), f >= this.get("content").length && this.trigger("complete")
1462 }
1463 },
1464 periodSpacing: function(a, b) {
1465 if (b = b || 1, 1 > b || b > 2) throw new Error("Only 1 or 2 spaces after a period");
1466 var c = 1 == b ? " " : " ";
1467 return a.replace(/(\.|\?|!) +/g, "$1" + c).replace(/(mrs\.|mr\.|sr\.|st\.|ms\.|\s\w\.|inc\.|lt\.|jr\.|dr\.|\sgt\.|ft\.|st\.|ave\.|rd\.|in\.|hrs\.|e\.g\.|ot\.|lol\.|ed\.|fl\.|off\.|ord\.|rt\.|rds\.|asstd\.|ea\.|ltd\.|ins\.|reg\.|c\.o\.d\.) /gi, "$1 ")
1468 },
1469 metMinimums: function(a, b) {
1470 return a > 0 && this.speed() < a ? !1 : !(b > 0 && this.accuracy() < b)
1471 },
1472 quickComplete: function() {
1473 this.set({
1474 typed: 1
1475 }), this.trigger("complete")
1476 },
1477 setPeriodSpacing: function(a) {
1478 this.set({
1479 content: this.periodSpacing(this.get("content"), a),
1480 period_spacing: a
1481 })
1482 }
1483 })
1484}), define("lessons/collections/screens", ["require", "lessons/models/screen"], function(a) {
1485 "use strict";
1486 var b = a("lessons/models/screen");
1487 return Backbone.Collection.extend({
1488 model: b,
1489 comparator: "display_order"
1490 })
1491}), define("lessons/collections/problemkey_screens", ["require", "lessons/models/screen"], function(a) {
1492 "use strict";
1493 var b = a("lessons/models/screen");
1494 return Backbone.Collection.extend({
1495 constructor: function(a, b) {
1496 for (var c = b.letters.map(function(a) {
1497 return a.id.toUpperCase()
1498 }), d = _.shuffle(_.flatten(_.map(c, function(a) {
1499 return b.words[a]
1500 }))), e = [], f = 0, g = 0;;) {
1501 for (e[f] || (e[f] = "");;) {
1502 if ((e[f] + " " + d[g]).length >= 50) {
1503 e[f] = e[f].trim(), f++;
1504 break
1505 }
1506 e[f] += " " + d[g], g++
1507 }
1508 if (f > 100) break
1509 }
1510 return a[0].intro = a[0].intro.format(c.join(", ")), a[0].title += c.join(", "), a[0].content = e.join("\n"), Backbone.Collection.call(this, a, b)
1511 },
1512 model: b,
1513 comparator: "display_order"
1514 })
1515}), define("lessons/views/nav", ["require", "templates"], function(a) {
1516 "use strict";
1517 var b = a("templates");
1518 return Backbone.View.extend({
1519 initialize: function(a) {
1520 void 0 === this.template && (this.template = b("lessons", "nav")), this.lesson = a.lesson, this.screen = a.screen
1521 },
1522 render: function() {
1523 return this.$el.html(this.template({
1524 lesson: this.lesson.toJSON(),
1525 screen: this.screen ? this.screen.toJSON() : {}
1526 })), this
1527 }
1528 })
1529}), define("global/views/keyboard", ["require", "templates", "registry"], function(a) {
1530 "use strict";
1531 var b = a("templates"),
1532 c = a("registry");
1533 return Backbone.View.extend({
1534 highlighted: [],
1535 lastFingers: [],
1536 events: {
1537 "click .hide-keyboard": "toggle"
1538 },
1539 initialize: function(a) {
1540 void 0 === this.template && (this.template = b("global", "keyboard")), this.user = c.get("user"), this.setKeyboard(a)
1541 },
1542 setKeyboard: function(a) {
1543 return this.options = a || {
1544 type: "small",
1545 noHands: !1,
1546 noControls: !1
1547 }, a.model && (this.model = a.model), this
1548 },
1549 toggleCapsLockWarning: function(a) {
1550 this.capsLockWarning && (a && this.capsLockWarning.is(":hidden") ? this.capsLockWarning.velocity("fadeIn") : !a && this.capsLockWarning.is(":visible") && this.capsLockWarning.velocity("fadeOut"))
1551 },
1552 render: function() {
1553 return this.$el.html(this.template({
1554 format: this.model.get("type"),
1555 structure: this.model.get("structure"),
1556 type: this.options.type,
1557 noHands: this.options.noHands,
1558 noControls: this.options.noControls,
1559 hideKeyboard: this.user.get("hideKeyboard")
1560 })), "large" == this.options.type && (this.hands = this.$("#handsImg")[0]), this.capsLockWarning = this.$(".capslock-warning"), this
1561 },
1562 highlightKey: function(a) {
1563 a = a || "", "\n" == a && (a = "âÂŽ");
1564 var b, c, d = "âÂŽ" == a ? 13 : a.toLowerCase().charCodeAt(0),
1565 e = this.$(".key-" + d),
1566 f = a != a.toLowerCase() || e.data("shifted") == d,
1567 g = e.data("finger");
1568 f && (5 >= g ? (b = this.rightCaps || this.$(".key-16.right"), this.rightCaps = b) : (b = this.leftCaps || this.$(".key-16.left"), this.leftCaps = b)), this.highlighted.forEach(function(a) {
1569 a[0].classList.remove("active")
1570 }), this.highlighted = [], "small" == this.options.type ? (this.lastFingers.forEach(function(a) {
1571 this.el.classList.remove("finger-" + a)
1572 }.bind(this)), this.lastFingers = []) : (this.lastFingers.forEach(function(a) {
1573 a.setAttribute("class", a.className.baseVal.replace("is-active", ""))
1574 }), this.lastFingers = []), a && (g && ("small" == this.options.type ? (this.$el[0].classList.add("finger-" + g), this.lastFingers.push(g), f && (5 >= g ? (this.$el[0].classList.add("finger-10"), this.lastFingers.push(10)) : (this.$el[0].classList.add("finger-1"), this.lastFingers.push(1)))) : (c = this.hands.querySelector("path.finger-" + g), this.lastFingers.push(c), c.setAttribute("class", c.className.baseVal + " is-active"), f && (c = 5 >= g ? this.hands.querySelector("path.finger-10") : this.hands.querySelector("path.finger-1"), this.lastFingers.push(c), c.setAttribute("class", c.className.baseVal + " is-active")))), f && (this.highlighted.push(b), b[0].classList.add("active")), e.length && (this.highlighted.push(e), e[0].classList.add("active")))
1575 },
1576 toggle: function() {
1577 this.$(".keyboard-container").toggleClass("hidden"), this.user.set({
1578 hideKeyboard: this.$(".keyboard-container").hasClass("hidden")
1579 })
1580 }
1581 })
1582}), define("lessons/views/intro", ["require", "templates", "global/views/keyboard"], function(a) {
1583 "use strict";
1584 var b = a("templates"),
1585 c = a("global/views/keyboard");
1586 return Backbone.View.extend({
1587 events: {
1588 "click .begin-button": "begin"
1589 },
1590 animatedKeyId: 0,
1591 animationSpeed: 1e3,
1592 initialize: function(a) {
1593 this.options = a, void 0 === this.template && (this.template = b("lessons", this.options.test ? "test_intro" : "intro")), a.contents.match("virtual-keyboard") && (this.keyboard = new c({
1594 type: 29 == a.keyboard.id ? "small" : "large",
1595 noControls: !0,
1596 model: a.keyboard
1597 })), this.input = a.input
1598 },
1599 render: function() {
1600 if (this.listenTo(this.input, "keypress", this.handleKeypress), this.$el.html(this.template({
1601 contents: this.options.contents
1602 })), this.keyboard) {
1603 var a = this.$(".virtual-keyboard");
1604 29 == this.options.keyboard && (this.keyboard.type = "small"), this.animationSpeed = a.data("keys-speed") || 1e3, this.keysToAnimate = String(a.data("keys")).split(" ").map(function(a) {
1605 return "space" == a ? " " : "quote" == a ? '"' : a
1606 }), a.html(this.keyboard.render().el), this.animateKeys()
1607 }
1608 return this
1609 },
1610 begin: function() {
1611 return this.trigger("begin"), !1
1612 },
1613 handleKeypress: function(a) {
1614 "\n" == a.key && this.begin()
1615 },
1616 animateKeys: function() {
1617 this.animatedKeyId >= this.keysToAnimate.length && (this.animatedKeyId = 0), this.keyboard.highlightKey(this.keysToAnimate[this.animatedKeyId]), this.animatedKeyId++, this.animationTimeout = setTimeout(this.animateKeys.bind(this), this.animationSpeed)
1618 },
1619 hide: function() {
1620 this.stopListening(this.input), clearTimeout(this.animationTimeout), this.$("#handsImg").remove(), this.$el.fastHide()
1621 }
1622 })
1623}), define("lessons/views/congrats", ["require", "templates", "shared/scoring", "registry", "global/collections/badges", "global/collections/courses", "global/collections/lessons"], function(a) {
1624 "use strict";
1625 var b = a("templates"),
1626 c = a("shared/scoring"),
1627 d = a("registry"),
1628 e = a("global/collections/badges"),
1629 f = a("global/collections/courses"),
1630 g = a("global/collections/lessons");
1631 return Backbone.View.extend({
1632 initialize: function(a) {
1633 void 0 === this.template && (this.template = b("lessons", "congrats")), this.userLessons = d.get("userLessons"), this.lessons = new g(FTWGLOBALS("lessons")), this.courses = new f(FTWGLOBALS("courses")), this.badges = new e, this.screen = a.screen, this.userLesson = a.userLesson, this.userLessonScreens = a.userLessonScreens, this.lesson = a.lesson
1634 },
1635 render: function() {
1636 this.lessons.setProgress(this.userLessons), this.courses.setProgress(this.lessons);
1637 var a = this.courses.get(this.lesson.get("course")).get("active_lesson"),
1638 b = this.lessons.get(a),
1639 d = [];
1640 return this.userLessonScreens.forEach(function(a) {
1641 for (var b = a.get("stars"), c = 0; b > c; c++) d.push(1);
1642 for (c = 0; 3 - b > c; c++) d.push(0)
1643 }), this.$el.html(this.template({
1644 lesson: this.lesson.toJSON(),
1645 starList: d,
1646 stars: this.userLesson.get("stars"),
1647 totalStars: 3 * this.lesson.get("screens"),
1648 accuracy: c.accuracy(this.userLesson.get("typed"), this.userLesson.get("errors")),
1649 speed: c.speed(this.userLesson.get("typed"), this.userLesson.get("seconds"), this.userLesson.get("errors")),
1650 time: (this.userLesson.get("seconds") || 0).countdownSeconds(),
1651 activeLesson: b ? b.toJSON() : null,
1652 badge: this.badges.get(this.lesson.get("badge") || 1).get("template")
1653 })), this
1654 }
1655 })
1656}), define("lessons/views/screen_complete", ["require", "templates", "registry", "shared/scoring"], function(a) {
1657 "use strict";
1658 var b = a("templates"),
1659 c = a("registry"),
1660 d = a("shared/scoring");
1661 return Backbone.View.extend({
1662 saved: !1,
1663 events: {
1664 "click .continue-button": "continue",
1665 "click .try-again": "restart",
1666 "click .saving-failed a": "saveTryAgain"
1667 },
1668 initialize: function(a) {
1669 void 0 === this.template && (this.template = b("lessons", "screen_complete")), this.user = c.get("user"), this.screen = a.screen, this.screenType = a.screenType, this.userLesson = a.userLesson, this.userLessonScreens = a.userLessonScreens, this.lesson = a.lesson, this.lettersTyped = a.lettersTyped, this.input = a.input, this.typingView = a.typingView, this.problemKeysLesson = a.problemKeysLesson
1670 },
1671 render: function() {
1672 this.listenTo(this.input, "keypress", this.handleKeyPress);
1673 var a = d.accuracy(this.screen.get("typed"), this.screen.get("errors")),
1674 b = 0,
1675 c = 0,
1676 e = 0;
1677 this.userLesson.get("progress") > 0 && (b = d.accuracy(this.userLesson.get("typed") + this.screen.get("typed"), this.userLesson.get("errors") + this.screen.get("errors")), c = d.speed(this.userLesson.get("typed") + this.screen.get("typed"), this.userLesson.get("seconds") + this.screen.get("seconds"), this.userLesson.get("errors") + this.screen.get("errors")), e = (this.userLesson.get("seconds") + this.screen.get("seconds") || 0).countdownSeconds()), this.$el.html(this.template({
1678 loggedIn: FTWGLOBALS("loggedIn"),
1679 lesson: this.lesson.toJSON(),
1680 two_stars: this.user.get("two_stars"),
1681 three_stars: this.user.get("three_stars"),
1682 stars: d.stars(a, this.user.get("two_stars"), this.user.get("three_stars")),
1683 accuracy: 0,
1684 speed: 0,
1685 problemKeys: _.pluck(this.lettersTyped.topProblemKeys(3), "id"),
1686 time: 0,
1687 lessonAccuracy: b,
1688 lessonSpeed: c,
1689 lessonTime: e,
1690 screenType: this.screenType,
1691 problemKeysLesson: this.problemKeysLesson,
1692 lineSpeeds: this.calculateLineSpeeds()
1693 }));
1694 var f = this.$(".stars span").css({
1695 visibility: "hidden"
1696 }),
1697 g = "easeOutQuint",
1698 h = 200,
1699 i = 400,
1700 j = 1e3;
1701 $.Velocity(f[0], {
1702 opacity: [1, 0],
1703 rotateZ: ["-5deg", 0],
1704 scale: [1, 15]
1705 }, {
1706 delay: h + i,
1707 display: "inline-block",
1708 visibility: "visible",
1709 duration: j,
1710 easing: g
1711 }), $.Velocity(f[1], {
1712 opacity: [1, 0],
1713 scale: [1.2, 15]
1714 }, {
1715 delay: 2 * h + i,
1716 display: "inline-block",
1717 visibility: "visible",
1718 duration: j,
1719 easing: g
1720 }), $.Velocity(f[2], {
1721 opacity: [1, 0],
1722 rotateZ: ["5deg", 0],
1723 scale: [1, 15]
1724 }, {
1725 delay: 3 * h + i,
1726 display: "inline-block",
1727 visibility: "visible",
1728 duration: j,
1729 easing: g
1730 });
1731 var k = function() {
1732 this.$("#screen-time").countdown([this.screen.get("seconds") || 0, 0], {
1733 formatter: function(a) {
1734 return a.countdownSeconds()
1735 }
1736 }, 500)
1737 }.bind(this),
1738 l = function() {
1739 this.$("#screen-accuracy").countdown([a, 0], null, 500, k)
1740 }.bind(this);
1741 return this.$("#screen-speed").countdown([d.speed(this.screen.get("typed"), this.screen.get("seconds"), this.screen.get("errors")), 0], null, 500, l), this
1742 },
1743 "continue": function() {
1744 return this.saved ? (this.$(".continue-button").addClass("pending"), this.trigger("continue"), location.softReload(), !1) : !1
1745 },
1746 restart: function() {
1747 return this.saved && this.userLesson.inc({
1748 progress: -1
1749 }), location.href = location.href, !1
1750 },
1751 handleKeyPress: function(a) {
1752 "\n" == a.key && this["continue"]()
1753 },
1754 showSaved: function() {
1755 this.saved = !0, this.$(".saving-spinner").hide(), this.$(".saving-failed").hide(), this.$(".continue-button").show()
1756 },
1757 showSaveError: function() {
1758 this.$(".saving-spinner").hide(), this.$(".saving-failed").show()
1759 },
1760 saveTryAgain: function() {
1761 return this.$(".saving-failed").hide(), this.$(".saving-spinner").show(), this.trigger("try-again"), !1
1762 },
1763 calculateLineSpeeds: function() {
1764 var a = [];
1765 return "speed" == this.screenType && this.typingView.lineScores.forEach(function(b, c) {
1766 var e = d.speed(b.typed, b.seconds, b.errors),
1767 f = 0;
1768 c > 0 && (f = d.speed(this.typingView.lineScores[c - 1].typed, this.typingView.lineScores[c - 1].seconds, this.typingView.lineScores[c - 1].errors)), a[c] = {
1769 speed: e,
1770 improvement: f > 0 ? Math.round((e - f) / f * 100) : 0
1771 }
1772 }.bind(this)), a
1773 }
1774 })
1775}), define("lessons/views/screen_failed", ["require", "templates", "shared/scoring"], function(a) {
1776 "use strict";
1777 var b = a("templates"),
1778 c = a("shared/scoring");
1779 return Backbone.View.extend({
1780 saved: !1,
1781 events: {
1782 "click .continue-button": "restart"
1783 },
1784 initialize: function(a) {
1785 void 0 === this.template && (this.template = b("lessons", "screen_failed")), this.screen = a.screen, this.screenType = a.screenType, this.userLesson = a.userLesson, this.userLessonScreens = a.userLessonScreens, this.lesson = a.lesson, this.lettersTyped = a.lettersTyped, this.input = a.input, this.typingView = a.typingView
1786 },
1787 render: function() {
1788 this.listenTo(this.input, "keypress", this.handleKeyPress);
1789 var a = c.accuracy(this.screen.get("typed"), this.screen.get("errors"));
1790 return this.$el.html(this.template({
1791 lesson: this.lesson.toJSON(),
1792 accuracy: a,
1793 speed: c.speed(this.screen.get("typed"), this.screen.get("seconds"), this.screen.get("errors")),
1794 problemKeys: _.pluck(this.lettersTyped.topProblemKeys(3), "id"),
1795 time: (parseInt(this.screen.get("seconds")) || 0).countdownSeconds(),
1796 screenType: this.screenType
1797 })), this
1798 },
1799 restart: function() {
1800 return location.softReload(), !1
1801 },
1802 handleKeyPress: function(a) {
1803 "\n" == a.key && this.restart()
1804 }
1805 })
1806}), define("test/views/name_modal", ["require", "templates", "shared/form_model", "registry", "shared/form_validation"], function(a) {
1807 "use strict";
1808 var b = a("templates"),
1809 c = a("shared/form_model"),
1810 d = a("registry"),
1811 e = a("shared/form_validation"),
1812 f = c.extend({
1813 url: "/apiv1/student/user/account",
1814 defaults: {
1815 first_name: "",
1816 last_name: ""
1817 },
1818 validationRules: {
1819 first_name: {
1820 required: !0
1821 },
1822 last_name: {
1823 required: !0
1824 }
1825 },
1826 validationMessages: {}
1827 });
1828 return Backbone.View.extend({
1829 events: {
1830 "keypress input": "submitOnEnter"
1831 },
1832 baseModel: null,
1833 initialize: function(a) {
1834 this.options = a, this.template = b("test", "name_modal"), this.model = new f
1835 },
1836 render: function() {
1837 return this.$el.html(this.template({})), this.form = this.$("form"), new e(this.form, this.button, this.model, this.successCallback, this), this
1838 },
1839 ok: function() {
1840 this.form.submit()
1841 },
1842 successCallback: function(a) {
1843 d.get("user").set(a), this.trigger("complete")
1844 },
1845 submitOnEnter: function(a) {
1846 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
1847 }
1848 })
1849}), define("lessons/views/test_complete", ["require", "templates", "shared/scoring", "registry", "global/views/notice", "global/views/modal", "test/views/name_modal", "lessons/views/screen_complete"], function(a) {
1850 "use strict";
1851 var b = a("templates"),
1852 c = (a("shared/scoring"), a("registry")),
1853 d = a("global/views/notice"),
1854 e = a("global/views/modal"),
1855 f = a("test/views/name_modal"),
1856 g = a("lessons/views/screen_complete");
1857 return g.extend({
1858 initialize: function(a) {
1859 g.prototype.initialize.apply(this, arguments), this.template = b("lessons", "test_complete"), this.user = c.get("user")
1860 },
1861 render: function() {
1862 return g.prototype.render.apply(this, arguments), this
1863 },
1864 "continue": function() {
1865 if (!this.saved) return !1;
1866 var a = this.userLesson.get("last_save_response").id;
1867 return this.user.get("first_name") || this.user.get("last_name") ? window.open("/apiv1/student/tests/" + a + "/certificate") : this.requestName(), !1
1868 },
1869 restart: function() {
1870 return location.softReload(), !1
1871 },
1872 requestName: function() {
1873 var a = new e({
1874 title: "Enter Your Name",
1875 okButton: "Continue",
1876 cancelButton: "Cancel",
1877 contentView: new f,
1878 width: 500
1879 });
1880 return a.on("complete", function() {
1881 new d({
1882 error: !1,
1883 text: "Your name has been updated. You may now print your certificate."
1884 }).show()
1885 }.bind(this)), a.show(), !1
1886 },
1887 handleKeyPress: function(a) {
1888 return !1
1889 }
1890 })
1891}), define("lessons/views/progress", ["require", "templates", "shared/scoring"], function(a) {
1892 "use strict";
1893 var b = 200,
1894 c = 20,
1895 d = a("templates"),
1896 e = a("shared/scoring");
1897 return Backbone.View.extend({
1898 events: {
1899 "mouseover .progress-screens span": "showDetails",
1900 "mouseout .progress-screens span": "hideDetails",
1901 "click .progress-screens span": "goToScreen"
1902 },
1903 initialize: function(a) {
1904 void 0 === this.template && (this.template = d("lessons", "progress"), this.hoverTemplate = d("lessons", "progress_hover")), this.userLesson = a.userLesson, this.userLessonScreens = a.userLessonScreens, this.lesson = a.lesson, this.screens = a.screens
1905 },
1906 render: function() {
1907 return this.$el.html(this.template({
1908 lesson: this.lesson.toJSON(),
1909 screens: this.screens.toJSON(),
1910 userLesson: this.userLesson.toJSON(),
1911 userLessonScreens: this.userLessonScreens
1912 })), this.hover = this.$("#progressHover"), this
1913 },
1914 goToScreen: function(a) {
1915 var b = $(a.currentTarget),
1916 c = b.data("id"),
1917 d = this.screens.indexOf(this.screens.get(c));
1918 d <= this.userLessonScreens.length && (this.userLesson.set({
1919 progress: d
1920 }), location.softReload())
1921 },
1922 showDetails: function(a) {
1923 var d = $(a.currentTarget),
1924 f = d.data("id"),
1925 g = d.width(),
1926 h = d.position().left,
1927 i = this.screens.get(f),
1928 j = this.userLessonScreens.get(f),
1929 k = 0,
1930 l = 0,
1931 m = 0,
1932 n = !1;
1933 j && (k = j.get("stars"), l = e.speed(j.get("typed"), j.get("seconds"), j.get("errors")), m = e.accuracy(j.get("typed"), j.get("errors")), n = !0), this.hover.css({
1934 left: h + g / 2 - b / 2 + c - 4
1935 }).html(this.hoverTemplate({
1936 title: i.get("title"),
1937 stars: k,
1938 speed: l,
1939 accuracy: m,
1940 completed: n
1941 })).velocity("stop", !0).velocity("ftw.perspectiveShake", {
1942 display: "block"
1943 })
1944 },
1945 hideDetails: function(a) {
1946 return $(a.relatedTarget).parent().hasClass("progress-screens") ? !1 : (this.hover.fastHide(), !1)
1947 }
1948 })
1949}), define("shared/sounds", ["require"], function(a) {
1950 "use strict";
1951 return {
1952 sounds: {},
1953 play: function(a) {
1954 "string" == typeof a && (a = {
1955 id: a
1956 });
1957 try {} catch (b) {}
1958 a.loop = a.loop || !1, a.volume = a.volume || 1, a.delay = a.delay || 0, this.sounds[a.id] && (this.sounds[a.id].volume = a.volume, this.sounds[a.id].loop = a.loop, setTimeout(function() {
1959 try {
1960 this.sounds[a.id].play()
1961 } catch (b) {}
1962 }.bind(this), a.delay))
1963 },
1964 stop: function(a, b) {
1965 var c = this.sounds[a];
1966 c && (c.pause(), c.currentTime = 0)
1967 },
1968 preload: function(a, b, c) {
1969 b = b || "/dist/site/images/sounds/", c = c || a;
1970 try {
1971 this.sounds[a] = new Audio(b + c + ".mp3")
1972 } catch (d) {}
1973 }
1974 }
1975}), define("shared/tooltip", ["require", "templates"], function(a) {
1976 "use strict";
1977 var b = (a("templates"), Backbone.View.extend({
1978 className: "tooltip",
1979 initialize: function() {
1980 $("body").append(this.$el)
1981 },
1982 init: function() {
1983 $(".tooltip-trigger").off("mouseover.tooltip").off("mouseout.tooltip").on("mouseover.tooltip", function(a) {
1984 this.show(a.currentTarget, $(a.currentTarget).data("tooltip"))
1985 }.bind(this)).on("mouseout.tooltip", function() {
1986 this.hide()
1987 }.bind(this))
1988 },
1989 show: function(a, b) {
1990 a = $(a), this.$el.html(b).css({
1991 left: a.offset().left - this.$el.outerWidth() / 2 + a.outerWidth() / 2 - 3,
1992 top: a.offset().top - this.$el.outerHeight() - 10
1993 }), this.$el.velocity("ftw.growUp", {
1994 display: "block"
1995 })
1996 },
1997 hide: function() {
1998 this.$el.fastHide()
1999 }
2000 }));
2001 return new b
2002}), define("lessons/views/typing", ["require", "templates", "registry", "shared/sounds", "shared/scoring", "shared/tooltip", "global/views/keyboard"], function(a) {
2003 "use strict";
2004 var b = a("templates"),
2005 c = a("registry"),
2006 d = a("shared/sounds"),
2007 e = (a("shared/scoring"), a("shared/tooltip")),
2008 f = a("global/views/keyboard");
2009 return Backbone.View.extend({
2010 keyboardType: null,
2011 templateName: null,
2012 lettersCache: null,
2013 activeLetter: null,
2014 events: {
2015 "click .button-restart": "restart"
2016 },
2017 initialize: function(a) {
2018 void 0 === this.template && (this.baseTemplate = b("lessons", "typing"), this.template = b("lessons", this.templateName)), this.lettersTyped = a.lettersTyped, this.user = c.get("user"), this.input = a.input, this.timer = a.timer, this.keyboardType && (this.keyboard = new f({
2019 type: this.keyboardType,
2020 model: a.keyboard
2021 }), this.input.on("capslock", this.keyboard.toggleCapsLockWarning.bind(this.keyboard))), d.preload("error"), this.userLesson = a.userLesson, this.userLessonScreens = a.userLessonScreens, this.lesson = a.lesson, this.screens = a.screens, this.screen = a.screen, this.screen.on("change:typed", this.handleKeyTyped, this), this.screen.on("change:errors", this.handleErrorTyped, this), this.screen.once("complete", this.handleComplete.bind(this))
2022 },
2023 render: function() {
2024 return e.hide(), this.$el.html(this.baseTemplate()), this.listenTo(this.input, "keypress", this.handleInput), this.keyboard && (this.$("#keyboard-content").append(this.keyboard.render().el), this.keyboard.highlightKey(this.screen.charAt(0))), c.get("isMobile") && "software" == this.user.get("_mobileKeyboard") && $("#ios-overlay").fastShow(), this.setPosition(0), this
2025 },
2026 handleComplete: function() {
2027 this.screen.off("complete", this.handleComplete.bind(this)), this.timer.stop();
2028 var a = -1;
2029 this.keyTimes.length > 20 && (a = Math.round(this.std(this.keyTimes))), this.screen.set({
2030 seconds: this.timer.get("seconds"),
2031 std: a
2032 }), this.trigger("complete"), e.init()
2033 },
2034 startCursorAnimation: function() {
2035 c.get("userActivity").length <= 1 && 0 === this.userLesson.get("progress") && (this.startCursorTimeout = setTimeout(function() {
2036 e.show(this.$(".letter.active"), "Start Typing!")
2037 }.bind(this), 3e3))
2038 },
2039 stopCursorAnimation: function() {
2040 clearTimeout(this.startCursorTimeout), e.hide()
2041 },
2042 keyTimes: [],
2043 lastKeyTime: 0,
2044 handleInput: function(a) {
2045 if (this.lastKeyTime) {
2046 var b = Date.now();
2047 this.keyTimes.push(Date.now() - this.lastKeyTime), this.lastKeyTime = b
2048 }
2049 0 === this.screen.get("typed") && (this.timer.start(), this.lastKeyTime = Date.now(), this.stopCursorAnimation()), "NEXT" == a.key ? (this.timer.stop().set({
2050 seconds: 10
2051 }), this.screen.quickComplete()) : this.screen.handleInput(a)
2052 },
2053 handleKeyTyped: function(a) {
2054 a.changed.lastKeyError ? this.lettersTyped.trackError(a.get("correctLetter")) : this.lettersTyped.trackCorrect(a.get("letterTyped")), this.keyboard && this.keyboard.highlightKey(a.get("nextKey")), this.setPosition(a.changed.typed, a.get("lastKeyError"))
2055 },
2056 restart: function() {
2057 return location.softReload(), !1
2058 },
2059 handleErrorTyped: function(a) {
2060 if (this.activeLetter) {
2061 var b = $("<div>").addClass("error-letter").css({
2062 color: "red"
2063 }).html(a.get("letterTyped"));
2064 this.$(this.activeLetter).prev().append(b), b.velocity("transition.slideDownOut")
2065 }
2066 d.play("error")
2067 },
2068 setPosition: function(a, b) {
2069 if (!this.lettersCache) {
2070 var c = this.$(".screen span");
2071 if (0 === c.length) return;
2072 this.lettersCache = c
2073 }
2074 var d = a - 1 >= 0 ? this.lettersCache[a - 1] : null,
2075 e = this.lettersCache[a] || null,
2076 f = this.lettersCache.length > a + 1 ? this.lettersCache[a + 1] : null;
2077 e && (e.classList.add("active"), e.classList.remove("error"), e.classList.remove("typed"), this.activeLetter = e), f && (f.classList.remove("error"), f.classList.remove("typed"), f.classList.remove("active")), d && a > 0 && (d.classList.remove("active"), d.classList.add("typed"), b && d.classList.add("error"))
2078 },
2079 hide: function() {
2080 this.stopListening(this.input), this.remove()
2081 },
2082 std: function(a) {
2083 a.sort(function(a, b) {
2084 return a - b
2085 }), a.pop(), a.pop(), a.pop(), a.pop(), a.shift(), a.shift(), a.shift(), a.shift();
2086 var b = function(a) {
2087 var b = a.reduce(function(a, b) {
2088 return a + b
2089 }, 0),
2090 c = b / a.length;
2091 return c
2092 },
2093 c = b(a),
2094 d = a.map(function(a) {
2095 var b = a - c,
2096 d = b * b;
2097 return d
2098 }),
2099 e = b(d),
2100 f = Math.sqrt(e);
2101 return f
2102 }
2103 })
2104}), define("lessons/views/typing_standard", ["require", "templates", "registry", "lessons/views/typing"], function(a) {
2105 "use strict";
2106 var b = (a("templates"), a("registry")),
2107 c = a("lessons/views/typing");
2108 return c.extend({
2109 keyboardType: "small",
2110 templateName: "typing_standard",
2111 initialize: function(a) {
2112 return c.prototype.initialize.call(this, a)
2113 },
2114 render: function() {
2115 return c.prototype.render.call(this), this.$("#lesson-content").append(this.template({
2116 minimumLines: 4,
2117 lesson: this.lesson.toJSON(),
2118 typingContent: this.screen.getContentByLine(),
2119 user: b.get("user").toJSON()
2120 })), this.startCursorAnimation(), this
2121 }
2122 })
2123}), define("lessons/views/typing_falling", ["require", "templates", "registry", "lessons/views/typing"], function(a) {
2124 "use strict";
2125 var b = a("templates"),
2126 c = a("registry"),
2127 d = a("lessons/views/typing"),
2128 e = 52,
2129 f = 40,
2130 g = 10,
2131 h = 270,
2132 i = -20,
2133 j = 700,
2134 k = 70,
2135 l = 880;
2136 return d.extend({
2137 keyboardType: "large",
2138 templateName: "typing_falling",
2139 initialize: function(a) {
2140 return this.lettersTemplate = b("lessons", "typing_falling_letters"), this.lessonLength = a.screen.get("content").length, d.prototype.initialize.call(this, a)
2141 },
2142 render: function() {
2143 return d.prototype.render.call(this), this.$("#lesson-content").append(this.template({
2144 lesson: this.lesson.toJSON(),
2145 user: c.get("user").toJSON()
2146 })), setTimeout(this.renderLetters.bind(this), 0), this
2147 },
2148 renderLetters: function() {
2149 var a, b = {},
2150 c = this.$(".keyboard"),
2151 d = Math.ceil(this.$(".screen").css("border-left-width").replace("px", "")) || 0;
2152 j -= 2 * d, l -= 2 * d, i -= d, k -= d;
2153 var m = $("body").hasClass("compact"),
2154 n = m ? j : l;
2155 this.screen.get("content").split("").forEach(function(d) {
2156 d = d.toLowerCase(), a = d.charCodeAt(0), "âÂŽ" == d && (a = 13), b[a] || (b[a] = Math.min(n - g, c.find(".key-" + a).position().left))
2157 }.bind(this)), this.$(".screen").html(this.lettersTemplate({
2158 lettersPos: b,
2159 typingContent: this.screen.get("content"),
2160 rowHeight: e,
2161 letterPadding: g,
2162 letterWidth: f,
2163 rowWidth: m ? j : l,
2164 viewportHeight: h,
2165 viewportLeftOffset: m ? i : k
2166 })), this.lettersContent = this.$("#lettersContent"), this.progressMeter = this.$("#screenProgress"), this.setPosition(0)
2167 },
2168 handleErrorTyped: function(a) {
2169 this.lettersTyped.trackError(a.get("correctLetter")), this.setPosition(a.get("typed"), !0), d.prototype.handleErrorTyped.call(this, a)
2170 },
2171 setPosition: function(a, b) {
2172 if (!this.lettersCache) {
2173 var c = this.$("#lettersContent .letter-line");
2174 if (0 === c.length) return;
2175 this.lettersCache = c, this.keysCache = this.$("#lettersContent .key-letter");
2176 }
2177 var d = this.lessonLength - a - 1,
2178 e = this.lettersCache[d],
2179 f = this.keysCache[d],
2180 g = this.lettersCache[d + 1];
2181 return b && e ? (e.classList.add("error"), void $.Velocity(f, "ftw.miniShake", {
2182 duration: 300
2183 })) : (e && e.classList.add("active"), g && (g.classList.remove("active"), g.classList.add("complete")), void(a > 0 && (this.animate(a), this.updateProgress(a))))
2184 },
2185 updateProgress: function(a) {
2186 var b = Math.round(a / this.lessonLength * 100);
2187 $(this.progressObject).stop().animate({
2188 progress: b
2189 }, {
2190 step: function(a) {
2191 this.progressMeter.css({
2192 "background-size": a + "% 100%"
2193 })
2194 }.bind(this)
2195 }), this.progressObject.progress = b
2196 },
2197 progressObject: {
2198 progress: 0
2199 },
2200 animate: function(a) {
2201 var b = this.lessonLength - a,
2202 c = e * b - h;
2203 this.lettersContent.velocity("stop").velocity({
2204 top: -1 * c
2205 })
2206 }
2207 })
2208}), define("lessons/views/typing_speed", ["require", "templates", "shared/scoring", "registry", "lessons/views/typing"], function(a) {
2209 "use strict";
2210 var b = a("templates"),
2211 c = a("shared/scoring"),
2212 d = a("registry"),
2213 e = a("lessons/views/typing");
2214 return e.extend({
2215 keyboardType: "small",
2216 templateName: "typing_speed",
2217 initialize: function(a) {
2218 this.statsTemplate = b("lessons", "typing_speed_stats"), this.currentLine = 0, e.prototype.initialize.call(this, a), this.listenTo(this.screen, "change:line", this.completeLine)
2219 },
2220 render: function() {
2221 return e.prototype.render.call(this), this.$("#lesson-content").append(this.template({
2222 minimumLines: 4,
2223 lesson: this.lesson.toJSON(),
2224 typingContent: this.screen.getContentByLine(),
2225 user: d.get("user").toJSON()
2226 })), this.lines = this.$(".line"), this.lineScores = [], this.startCursorAnimation(), this
2227 },
2228 completeLine: function() {
2229 var a, b, d = "";
2230 0 === this.currentLine ? a = {
2231 typed: this.screen.get("typed"),
2232 seconds: this.timer.get("seconds"),
2233 errors: this.screen.get("errors"),
2234 totalSeconds: this.timer.get("seconds"),
2235 totalErrors: this.screen.get("errors")
2236 } : (b = this.lineScores[this.currentLine - 1], a = {
2237 typed: b.typed,
2238 seconds: this.timer.get("seconds") - b.totalSeconds,
2239 errors: this.screen.get("errors") - b.totalErrors,
2240 totalSeconds: this.timer.get("seconds"),
2241 totalErrors: this.screen.get("errors")
2242 }, c.speed(a.typed, a.seconds, a.errors) > c.speed(b.typed, b.seconds, b.errors) ? d = "up" : c.speed(a.typed, a.seconds, a.errors) < c.speed(b.typed, b.seconds, b.errors) && (d = "down")), this.lineScores[this.currentLine] = a;
2243 var e = c.speed(a.typed, a.seconds, a.errors),
2244 f = 0;
2245 this.currentLine > 0 && (f = c.speed(this.lineScores[this.currentLine - 1].typed, this.lineScores[this.currentLine - 1].seconds, this.lineScores[this.currentLine - 1].errors)), $(this.lines[this.currentLine]).addClass("complete " + d).find(".repeat-stats").html(this.statsTemplate({
2246 speed: e,
2247 improvement: Math.round((e - f) / f * 100)
2248 })).velocity("fadeIn"), this.currentLine++
2249 }
2250 })
2251}), define("lessons/views/typing_test", ["require", "templates", "registry", "lessons/views/typing"], function(a) {
2252 "use strict";
2253 var b = (a("templates"), a("registry")),
2254 c = a("lessons/views/typing"),
2255 d = 50;
2256 return c.extend({
2257 keyboardType: !1,
2258 templateName: "typing_test",
2259 initialize: function(a) {
2260 this.options = a, this.options.problemKeysLesson && (this.keyboardType = "small"), c.prototype.initialize.call(this, a), this.listenTo(this.screen, "change:line", this.animate), this.listenTo(this.timer, "change:startTime", this.beginProgress)
2261 },
2262 render: function() {
2263 return c.prototype.render.call(this), this.$("#lesson-content").append(this.template({
2264 lesson: this.lesson.toJSON(),
2265 typingContent: this.screen.getContentByLine(),
2266 minutes: this.options.minutes,
2267 user: b.get("user").toJSON()
2268 })), this.typingContent = this.$("#typingContent"), this.progressMeter = this.$("#screenProgress"), this.startCursorAnimation(), this
2269 },
2270 handleComplete: function() {
2271 return this.timer.stop(), this.timer.set({
2272 seconds: 60 * this.options.minutes
2273 }), c.prototype.handleComplete.call(this)
2274 },
2275 beginProgress: function() {
2276 var a = 60 * this.options.minutes,
2277 b = function(b) {
2278 return (a - Math.floor(b / 100 * a)).countdownSeconds()
2279 };
2280 $({
2281 progress: 0
2282 }).animate({
2283 progress: 100
2284 }, {
2285 step: function(a) {
2286 this.progressMeter.html("<span>" + b(a) + "</span>").css({
2287 "background-size": a + "% 100%"
2288 })
2289 }.bind(this),
2290 easing: "linear",
2291 duration: 60 * this.options.minutes * 1e3,
2292 complete: this.handleComplete.bind(this)
2293 })
2294 },
2295 animate: function(a, b) {
2296 this.typingContent.velocity("stop").velocity({
2297 top: -1 * (d * b)
2298 }, "slow", "linear")
2299 }
2300 })
2301}), define("shared/keyboard_input", ["require", "registry"], function(a) {
2302 "use strict";
2303 var b = a("registry"),
2304 c = function() {};
2305 return _.extend(c.prototype, Backbone.Events, {
2306 boundElement: null,
2307 capsLockWarningOn: !1,
2308 initialize: function() {
2309 b.get("isMobile") && "software" == b.get("user").get("_mobileKeyboard") && (this.boundElement = $("#mobile-input").fastShow(), this.boundElement.on("focus", this.hideMobileFocus.bind(this)), this.boundElement.on("blur", this.showMobileFocus.bind(this)), this.trigger("input-blur")), $(document).on("keypress", this.boundElement, _.bind(this.handleKeyPress, this)), $(document).on("keydown", this.boundElement, _.bind(this.handleKeyDown, this)), $(document).on("blur", this.boundElement, _.bind(this.handleBlur, this)), $(document).on("focus", this.boundElement, _.bind(this.handleFocus, this))
2310 },
2311 showMobileFocus: function() {
2312 $("#ios-overlay").velocity("fadeIn")
2313 },
2314 hideMobileFocus: function() {
2315 $("#ios-overlay").fastHide()
2316 },
2317 stop: function() {
2318 $(document).off("keypress keydown blur focus")
2319 },
2320 prevKeyPress: null,
2321 handleKeyPress: function(a) {
2322 if (this.prevKeyPress && this.prevKeyPress.timeStamp === a.timeStamp) return !1;
2323 this.prevKeyPress = a;
2324 var b = null;
2325 if (a.target.form && ("contactForm" == a.target.form.id || "nameForm" == a.target.form.id)) return !0;
2326 switch (a.which = a.which || a.keyCode, a.which) {
2327 case 13:
2328 b = "\n"
2329 }
2330 return b || (b = String.fromCharCode(a.which)), !a.shiftKey && b.match(/[a-zA-Z]/) && (b !== b.toUpperCase() || this.capsLockWarningOn ? b !== b.toUpperCase() && this.capsLockWarningOn && (this.trigger("capslock", !1), this.capsLockWarningOn = !1) : (this.trigger("capslock", !0), this.capsLockWarningOn = !0)), this.trigger("keypress", {
2331 keyCode: a.which,
2332 key: b,
2333 special: !1
2334 }), !1
2335 },
2336 prevKeyDown: null,
2337 handleKeyDown: function(a) {
2338 if (this.prevKeyDown && this.prevKeyDown.timeStamp === a.timeStamp) return !1;
2339 if (this.prevKeyDown = a, a.target.form && ("contactForm" == a.target.form.id || "nameForm" == a.target.form.id)) return !0;
2340 var b = null;
2341 switch (a.which = a.which || a.keyCode, a.which) {
2342 case 9:
2343 b = "TAB";
2344 break;
2345 case 20:
2346 b = "CAPSLOCK";
2347 break;
2348 case 91:
2349 b = "CMD";
2350 break;
2351 case 18:
2352 b = "ALT";
2353 break;
2354 case 16:
2355 b = "SHIFT";
2356 break;
2357 case 17:
2358 b = "CTRL";
2359 break;
2360 case 8:
2361 b = "BACKSPACE";
2362 break;
2363 case 46:
2364 b = "DELETE";
2365 break;
2366 case 12:
2367 b = "NUMLOCK";
2368 break;
2369 case 27:
2370 b = "ESC";
2371 break;
2372 case 32:
2373 return this.handleKeyPress(a)
2374 }
2375 return "CAPSLOCK" !== b || this.capsLockWarningOn || (this.trigger("capslock", !0), this.capsLockWarningOn = !0), 192 == a.which && a.shiftKey && a.ctrlKey && (b = "NEXT"), null !== b ? (this.trigger("keypress", {
2376 keyCode: a.which,
2377 key: b,
2378 special: !0
2379 }), !1) : void 0
2380 },
2381 handleFocus: function(a) {
2382 this.trigger("focus", a)
2383 },
2384 handleBlur: function(a) {
2385 this.trigger("blur", a)
2386 }
2387 }), c
2388}), define("global/models/timer", ["require"], function(a) {
2389 "use strict";
2390 return Backbone.Model.extend({
2391 "default": {
2392 startTime: 0,
2393 seconds: 0
2394 },
2395 initialize: function() {},
2396 start: function(a, b) {
2397 return this.get("startTime") ? this : (this.set({
2398 startTime: this.getTime(b),
2399 pauseTime: 0
2400 }), this.interval = setInterval(this._updateSeconds.bind(this), 100), this)
2401 },
2402 pause: function() {
2403 return this.set({
2404 pauseTime: this.getTime()
2405 }), this
2406 },
2407 unpause: function() {
2408 return this.inc({
2409 startTime: this.getTime() - this.get("pauseTime")
2410 }), this.set({
2411 pauseTime: 0
2412 }), this
2413 },
2414 stop: function() {
2415 return clearInterval(this.interval), this
2416 },
2417 getTime: function(a) {
2418 return a = a || new Date, a.getTime() / 1e3
2419 },
2420 _updateSeconds: function() {
2421 var a = this.get("pauseTime") || this.getTime();
2422 this.set({
2423 seconds: a - this.get("startTime")
2424 })
2425 }
2426 })
2427}), define("global/collections/keyboards", ["require"], function(a) {
2428 "use strict";
2429 var b = Backbone.Model.extend({
2430 idAttribute: "keyboard_id"
2431 });
2432 return Backbone.Collection.extend({
2433 model: b,
2434 comparator: "sort_order"
2435 })
2436}), define("global/collections/letters_typed", ["require"], function(a) {
2437 "use strict";
2438 var b = Backbone.Model.extend({
2439 defaults: {
2440 id: "",
2441 typed: 0,
2442 errors: 0
2443 }
2444 }),
2445 c = Backbone.Collection.extend({
2446 model: b,
2447 trackCorrect: function(a) {
2448 if (this.valid(a)) {
2449 a = a.toLowerCase();
2450 var b = this.get(a);
2451 b ? b.inc({
2452 typed: 1
2453 }) : this.add({
2454 id: a,
2455 typed: 1
2456 })
2457 }
2458 },
2459 trackError: function(a) {
2460 if (this.valid(a)) {
2461 a = a.toLowerCase();
2462 var b = this.get(a);
2463 b ? b.inc({
2464 errors: 1
2465 }) : this.add({
2466 id: a,
2467 errors: 1
2468 })
2469 }
2470 this.trackCorrect(a)
2471 },
2472 valid: function(a) {
2473 return a ? a.length > 1 ? !1 : "" === $.trim(a) ? !1 : !!a.match(/[A-Za-z]/) : void 0
2474 },
2475 topProblemKeys: function(a) {
2476 a = a || 4;
2477 var b = this.map(function(a) {
2478 return {
2479 id: a.id,
2480 percent: a.attributes.errors / a.attributes.typed
2481 }
2482 });
2483 return _.chain(b).sortBy(function(a) {
2484 return -1 * a.percent
2485 }).filter(function(a) {
2486 return a.percent > 0
2487 }).sortBy(function(a) {
2488 return -a.percent
2489 }).first(a).value()
2490 },
2491 merge: function(a) {
2492 var b;
2493 a.forEach(function(a) {
2494 b = this.get(a.id), b ? b.inc({
2495 typed: a.typed,
2496 errors: a.errors
2497 }) : this.add(a)
2498 }.bind(this))
2499 },
2500 clear: function() {
2501 this.reset(), $.postJSON("/apiv1/student/user/reset-problem-keys")
2502 }
2503 });
2504 return c
2505}), define("lessons/views/mobile_keyboard_modal", ["require", "templates"], function(a) {
2506 "use strict";
2507 var b = a("templates");
2508 return Backbone.View.extend({
2509 events: {
2510 "click .keyboard-button": "clickButton"
2511 },
2512 initialize: function(a) {
2513 this.template = b("lessons", "mobile_keyboard_modal")
2514 },
2515 render: function() {
2516 return this.$el.html(this.template({})), this
2517 },
2518 clickButton: function(a) {
2519 $(a.currentTarget).hasClass("software") ? this.trigger("complete", "software") : this.trigger("complete", "hardware")
2520 }
2521 })
2522}), define("lessons/index", ["require", "templates", "registry", "global/views/modal", "shared/scoring", "shared/radar_tracker", "global/models/lesson", "lessons/collections/screens", "lessons/collections/problemkey_screens", "lessons/views/nav", "lessons/views/intro", "lessons/views/congrats", "lessons/views/screen_complete", "lessons/views/screen_failed", "lessons/views/test_complete", "lessons/views/progress", "lessons/views/typing_standard", "lessons/views/typing_falling", "lessons/views/typing_speed", "lessons/views/typing_test", "shared/keyboard_input", "global/models/timer", "shared/tooltip", "global/collections/keyboards", "global/collections/user_lesson_screens", "global/collections/letters_typed", "lessons/views/mobile_keyboard_modal", "shared/model_backends/localstorage"], function(a) {
2523 "use strict";
2524 var b = a("templates"),
2525 c = a("registry"),
2526 d = a("global/views/modal"),
2527 e = a("shared/scoring"),
2528 f = a("shared/radar_tracker"),
2529 g = a("global/models/lesson"),
2530 h = a("lessons/collections/screens"),
2531 i = a("lessons/collections/problemkey_screens"),
2532 j = a("lessons/views/nav"),
2533 k = a("lessons/views/intro"),
2534 l = a("lessons/views/congrats"),
2535 m = a("lessons/views/screen_complete"),
2536 n = a("lessons/views/screen_failed"),
2537 o = a("lessons/views/test_complete"),
2538 p = a("lessons/views/progress"),
2539 q = a("lessons/views/typing_standard"),
2540 r = a("lessons/views/typing_falling"),
2541 s = a("lessons/views/typing_speed"),
2542 t = a("lessons/views/typing_test"),
2543 u = a("shared/keyboard_input"),
2544 v = a("global/models/timer"),
2545 w = a("shared/tooltip"),
2546 x = a("global/collections/keyboards"),
2547 y = a("global/collections/user_lesson_screens"),
2548 z = a("global/collections/letters_typed"),
2549 A = a("lessons/views/mobile_keyboard_modal"),
2550 B = a("shared/model_backends/localstorage");
2551 return Backbone.View.extend({
2552 el: "#js-content",
2553 events: {},
2554 initialize: function(a) {
2555 this.options = a, void 0 === this.template && (this.template = b("lessons", "index")), this.screenTypes = {
2556 standard: q,
2557 speed: s,
2558 falling: r,
2559 test: t
2560 }, this.user = c.get("user"), this.userBadges = c.get("userBadges"), this.userLettersTyped = c.get("userLettersTyped"), this.userActivity = c.get("userActivity"), this.userTests = c.get("userTests"), this.input = new u, this.timer = new v, this.lesson = new g(FTWGLOBALS("lesson"));
2561 var d = new x(FTWKEYBOARDS),
2562 e = 380 == this.lesson.id || 351 == this.lesson.id ? 29 : this.user.get("keyboard_id");
2563 if (this.keyboard = d.get(e), this.options.problemkeys) this.options.test = !0, this.options.minutes = 1, this.screens = new i(FTWGLOBALS("screens"), {
2564 words: FTWGLOBALS("words"),
2565 letters: this.userLettersTyped.topProblemKeys(3)
2566 });
2567 else if (this.options.test) {
2568 this.lesson.set({
2569 screens: 1
2570 });
2571 var w = FTWGLOBALS("screens")[0];
2572 w.title = this.options.minutes + ":00 Typing Test", w.content = _.shuffle(FTWGLOBALS("screens")).map(function(a) {
2573 return a.content.replace(/\n+/g, "\n").trim()
2574 }).join("\n").substr(0, 750 * this.options.minutes), this.screens = new h([w])
2575 } else this.screens = new h(FTWGLOBALS("screens"));
2576 this.lettersTyped = new z;
2577 var A = c.get("userLessons");
2578 if (this.userLesson = A.get(this.lesson.id), this.userLesson || (this.userLesson = A.add({
2579 lesson_id: this.lesson.id
2580 })), this.options.test && this.userLesson.get("progress") > 0 && this.userLesson.set({
2581 progress: 0
2582 }), this.userLessonScreens = new y(null, {
2583 lesson_id: this.lesson.id
2584 }), this.userLessonScreens.setBackend(new B("userLessonScreens" + this.lesson.id), "backend"), this.userLessonScreens.length > 0 && this.userLessonScreens.at(0).get("user_id") != this.user.id && this.userLessonScreens.reset(), this.options.test || this.options.problemkeys || document.referrer.match(/\/student\/lessons/) || f.track(c.get("user"), {
2585 seconds: this.userLesson.get("seconds"),
2586 errors: this.userLesson.get("errors"),
2587 typed: this.userLesson.get("typed"),
2588 progress: this.userLesson.get("progress")
2589 }), this.screen = this.screens.at(this.userLesson.get("progress")), this.screen) {
2590 this.screen.formatContent(this.keyboard, this.user), this.screen.get("intro") && (this.intro = new k({
2591 test: this.options.test && !this.options.problemkeys,
2592 keyboard: this.keyboard,
2593 contents: this.screen.get("intro"),
2594 input: this.input
2595 }), this.intro.once("begin", this.showScreen.bind(this)));
2596 var C = this.options.test ? "test" : this.screen.get("screen_type");
2597 this.typing = new this.screenTypes[C]({
2598 keyboard: this.keyboard,
2599 lesson: this.lesson,
2600 screens: this.screens,
2601 screen: this.screen,
2602 input: this.input,
2603 timer: this.timer,
2604 userLesson: this.userLesson,
2605 userLessonScreens: this.userLessonScreens,
2606 lettersTyped: this.lettersTyped,
2607 problemKeysLesson: this.options.problemkeys,
2608 minutes: this.options.minutes
2609 }), this.typing.once("complete", this.handleScreenComplete.bind(this)), this.options.test ? this.screenComplete = new o({
2610 lesson: this.lesson,
2611 screens: this.screens,
2612 screen: this.screen,
2613 input: this.input,
2614 timer: this.timer,
2615 typingView: this.typing,
2616 userLesson: this.userLesson,
2617 userLessonScreens: this.userLessonScreens,
2618 lettersTyped: this.lettersTyped,
2619 problemKeysLesson: this.options.problemkeys,
2620 screenType: C
2621 }) : this.screenComplete = new m({
2622 lesson: this.lesson,
2623 screens: this.screens,
2624 screen: this.screen,
2625 input: this.input,
2626 timer: this.timer,
2627 typingView: this.typing,
2628 userLesson: this.userLesson,
2629 userLessonScreens: this.userLessonScreens,
2630 lettersTyped: this.lettersTyped,
2631 screenType: C
2632 }), this.screenFailed = new n({
2633 lesson: this.lesson,
2634 screens: this.screens,
2635 screen: this.screen,
2636 input: this.input,
2637 timer: this.timer,
2638 typingView: this.typing,
2639 userLesson: this.userLesson,
2640 userLessonScreens: this.userLessonScreens,
2641 lettersTyped: this.lettersTyped,
2642 screenType: C
2643 })
2644 } else this.congrats = new l({
2645 lesson: this.lesson,
2646 lettersTyped: this.lettersTyped,
2647 userLesson: this.userLesson,
2648 userLessonScreens: this.userLessonScreens
2649 });
2650 this.navigation = new j({
2651 lesson: this.lesson,
2652 screen: this.screen,
2653 timer: this.timer,
2654 userLesson: this.userLesson
2655 }), this.options.test || (this.progress = new p({
2656 lesson: this.lesson,
2657 screens: this.screens,
2658 screen: this.screen,
2659 userLesson: this.userLesson,
2660 userLessonScreens: this.userLessonScreens
2661 })), this.userLesson.get("progress") > 0 && 0 === this.userLessonScreens.length ? (this.userLessonScreens.once("reset", this.render.bind(this)), this.userLessonScreens.fetch({
2662 reset: !0
2663 })) : this.render()
2664 },
2665 render: function() {
2666 var a = $(this.template({
2667 loading: !1
2668 }));
2669 a.find("#typing-nav").append(this.navigation.render().el), this.userLesson.get("progress") == this.lesson.get("screens") ? a.find("#typing-contents").append(this.congrats.render().el) : this.intro ? a.find("#typing-contents").append(this.intro.render().el) : a.find("#typing-contents").append(this.typing.render().el), this.progress && a.find("#typing-progress").append(this.progress.render().el), 0 === this.userLesson.get("progress") && this.user.unset("_mobileKeyboard"), this.$el.html(a), c.get("isMobile") && !this.user.get("_mobileKeyboard") ? this.showKeyboardSelectModal() : this.input.initialize(), w.init()
2670 },
2671 showKeyboardSelectModal: function() {
2672 var a = new d({
2673 title: "Select Tablet/Mobile Keyboard Type",
2674 okButton: "",
2675 cancelButton: "",
2676 closeButton: !1,
2677 contentView: new A
2678 });
2679 a.show(), a.on("complete", function(a) {
2680 this.user.set({
2681 _mobileKeyboard: a
2682 }), this.input.initialize()
2683 }.bind(this))
2684 },
2685 showScreen: function() {
2686 this.intro.hide(), this.userLesson.get("progress") == this.lesson.get("screens") ? this.$("#typing-contents").append(this.congrats.render().el) : this.$("#typing-contents").append(this.typing.render().el), w.init()
2687 },
2688 handleScreenComplete: function() {
2689 if (this.typing.hide(), e.accuracy(this.screen.get("typed"), this.screen.get("errors")) < this.lesson.get("min_accuracy")) return void this.$("#typing-contents").append(this.screenFailed.render().el);
2690 this.$("#typing-contents").append(this.screenComplete.render().el), this.screenStats = {
2691 lesson_id: this.lesson.id,
2692 lesson_screen_id: parseInt(this.screen.id),
2693 seconds: this.screen.get("seconds"),
2694 errors: this.screen.get("errors"),
2695 typed: this.screen.get("typed"),
2696 std: this.screen.get("std"),
2697 stars: e.stars(e.accuracy(this.screen.get("typed"), this.screen.get("errors")), this.user.get("two_stars"), this.user.get("three_stars")),
2698 completed: this.userLesson.get("progress") + 1 == this.lesson.get("screens") ? 1 : 0,
2699 progress: this.userLesson.get("progress") + 1,
2700 created_at: Date.getUnixTime(),
2701 test: this.options.test ? 1 : 0,
2702 now: Math.floor(Date.now() / 1e3)
2703 };
2704 var a = this.userLessonScreens.get(parseInt(this.screen.id));
2705 a && (this.screenStats.prev_seconds = a.get("seconds"), this.screenStats.prev_errors = a.get("errors"), this.screenStats.prev_typed = a.get("typed"), this.screenStats.prev_stars = a.get("stars"), this.screenStats.prev_completed = a.get("completed")), this.saveStats()
2706 },
2707 saveStats: function() {
2708 this.screenComplete.once("try-again", this.saveStats, this), this.userLesson.saveStats(this.screenStats, this.lettersTyped.toJSON()).done(function() {
2709 this.screenComplete.showSaved(), this.updateLocalStats()
2710 }.bind(this)).fail(function() {
2711 this.screenComplete.showSaveError()
2712 }.bind(this))
2713 },
2714 updateLocalStats: function() {
2715 var a = this.screenStats;
2716 if (!this.options.problemkeys)
2717 if (this.options.test) this.userTests.reset();
2718 else {
2719 var b = {
2720 progress: 1,
2721 typed: a.typed,
2722 errors: a.errors,
2723 seconds: a.seconds,
2724 stars: a.stars,
2725 completed: a.completed
2726 },
2727 c = this.userLessonScreens.get(a.lesson_screen_id);
2728 if (c && (b.typed -= c.get("typed"), b.seconds -= c.get("seconds"), b.errors -= c.get("errors"), b.stars -= c.get("stars"), b.completed -= c.get("completed")), this.userLesson.inc(b), this.userLesson.set({
2729 max_progress: Math.max(this.userLesson.get("progress"), this.userLesson.get("max_progress")),
2730 updated_at: a.created_at
2731 }), this.userLessonScreens.add(a, {
2732 merge: !0
2733 }), FTWGLOBALS("loggedIn") && this.screenStats.completed && !this.userBadges.get(this.lesson.id)) {
2734 var d = this.userLesson.toJSON();
2735 d.created_at = Math.round(Date.now() / 1e3), this.userBadges.add(d)
2736 }
2737 }
2738 this.userActivity.merge(this.screenStats), this.userLettersTyped.merge(this.lettersTyped.toJSON())
2739 }
2740 })
2741}), define("global/views/chart", ["require", "templates"], function(a) {
2742 "use strict";
2743 var b = a("templates");
2744 return Backbone.View.extend({
2745 options: {
2746 scaleGridLineColor: "rgba(234,234,234,1)",
2747 scaleShowLabels: !1,
2748 pointDotRadius: 8,
2749 pointDotStrokeWidth: 3,
2750 pointHitDetectionRadius: 0,
2751 datasetStrokeWidth: 1,
2752 bezierCurve: !1,
2753 showTooltips: !0,
2754 tooltipFillColor: "#000000",
2755 tooltipFontColor: "#ffffff",
2756 showLabels: !1
2757 },
2758 dataset: {
2759 labels: [],
2760 data: []
2761 },
2762 initialize: function(a) {
2763 void 0 === this.template && (this.template = b("global", "chart")), a.width = a.width || this.width || 548, a.height = a.height || this.height || 100, a.type = (a.type || this.type || "line").ucFirst(), this.setOptions(a)
2764 },
2765 setData: function(a, b) {
2766 b = b || {}, b.data = a, this.dataset = b
2767 },
2768 setOptions: function(a) {
2769 _.extend(this.options, a)
2770 },
2771 render: function() {
2772 if (this.$el.html(this.template({
2773 options: this.options,
2774 data: this.dataset.data
2775 })), this.dataset.data.length > 0) {
2776 var a = this.$("canvas")[0],
2777 b = a.getContext("2d"),
2778 c = _.pluck(this.dataset.data, "label");
2779 window.devicePixelRatio > 1 && (a.width = this.options.width, a.height = this.options.height, a.style.width = this.options.width / window.devicePixelRatio + "px", a.style.height = this.options.height / window.devicePixelRatio + "px", b.scale(window.devicePixelRatio, window.devicePixelRatio)), new Chart(b)[this.options.type]({
2780 labels: c,
2781 datasets: [this.dataset]
2782 }, this.options)
2783 }
2784 }
2785 })
2786}), define("test/views/graph", ["require", "global/views/chart", "templates", "shared/scoring"], function(a) {
2787 "use strict";
2788 var b = a("global/views/chart"),
2789 c = a("templates"),
2790 d = a("shared/scoring");
2791 return b.extend({
2792 sampleData: [],
2793 initialize: function(a) {
2794 a = a || {}, this.waitingTemplate = c("test", "graph_waiting"), a.tooltipTemplate = function(a) {
2795 var b = this.dataset.data[a.index];
2796 return moment(1e3 * b.created_at).format("lll") + " " + Math.round(b.seconds / 60) + " minute " + b.value + " WPM " + d.accuracy(b) + "% acc"
2797 }.bind(this), this.renderWaiting(), this.listenTo(this.collection, "reset", this.setData), b.prototype.initialize.call(this, a)
2798 },
2799 renderWaiting: function() {
2800 this.$el.html(this.waitingTemplate())
2801 },
2802 setData: function() {
2803 if (this.collection.length < 2) {
2804 this.$el.addClass("sample");
2805 var a = [{
2806 user_test_id: 6,
2807 seconds: 60,
2808 errors: 0,
2809 typed: 266,
2810 created_at: 1426181915
2811 }, {
2812 user_test_id: 5,
2813 seconds: 60,
2814 errors: 10,
2815 typed: 285,
2816 created_at: 1421794858
2817 }, {
2818 user_test_id: 4,
2819 seconds: 60,
2820 errors: 12,
2821 typed: 330,
2822 created_at: 1421793538
2823 }, {
2824 user_test_id: 3,
2825 seconds: 60,
2826 errors: 12,
2827 typed: 360,
2828 created_at: 1421791395
2829 }, {
2830 user_test_id: 2,
2831 seconds: 60,
2832 errors: 0,
2833 typed: 380,
2834 created_at: 1421791394
2835 }];
2836 a.reverse(), this.collection = new Backbone.Collection(a)
2837 }
2838 var c = this.collection.toJSON().reverse(),
2839 e = {
2840 fillColor: "transparent",
2841 strokeColor: "#3295db",
2842 pointStrokeColor: "#ffffff",
2843 pointColor: "#3295db"
2844 };
2845 c = _.map(c, function(a) {
2846 return a.date = a.label, a.label = "", a.pointLabel = Math.round(a.seconds / 60), a.value = d.speed(a), a
2847 }, this), b.prototype.setData.call(this, c, e), this.render()
2848 }
2849 })
2850}), define("test/views/table", ["require", "templates", "shared/scoring", "global/views/notice", "global/views/modal", "test/views/name_modal", "registry"], function(a) {
2851 "use strict";
2852 var b = a("templates"),
2853 c = a("shared/scoring"),
2854 d = a("global/views/notice"),
2855 e = a("global/views/modal"),
2856 f = a("test/views/name_modal"),
2857 g = a("registry");
2858 return Backbone.View.extend({
2859 events: {
2860 "click .print": "clickPrint"
2861 },
2862 initialize: function() {
2863 this.template = b("test", "table"), this.user = g.get("user"), this.collection.length || !FTWGLOBALS("loggedIn") ? this.render() : (this.renderLoading(), this.collection.on("reset", this.render.bind(this)))
2864 },
2865 renderLoading: function() {
2866 return this.$el.html(this.template({
2867 loading: !0
2868 })), this
2869 },
2870 render: function() {
2871 var a = this.collection.toJSON().map(function(a) {
2872 return a.minutes = Math.round(a.seconds / 60) + " minute" + Math.round(a.seconds / 60).pluralize(), a.speed = c.speed(a.typed, a.seconds, a.errors), a.accuracy = c.accuracy(a.typed, a.errors), a
2873 });
2874 return this.$el.html(this.template({
2875 loading: !1,
2876 loggedIn: FTWGLOBALS("loggedIn"),
2877 data: a
2878 })), this
2879 },
2880 clickPrint: function(a) {
2881 var b = $(a.currentTarget).data("id");
2882 return this.user.get("first_name") || this.user.get("last_name") ? window.open("/apiv1/student/tests/" + b + "/certificate") : this.requestName(), !1
2883 },
2884 requestName: function() {
2885 var a = new e({
2886 title: "Enter Your Name",
2887 okButton: "Continue",
2888 cancelButton: "Cancel",
2889 contentView: new f,
2890 width: 500
2891 });
2892 return a.on("complete", function() {
2893 new d({
2894 error: !1,
2895 text: "Your name has been updated. You may now print your certificate."
2896 }).show()
2897 }.bind(this)), a.show(), !1
2898 }
2899 })
2900}), define("test/index", ["require", "templates", "registry", "shared/scoring", "test/views/graph", "test/views/table"], function(a) {
2901 "use strict";
2902 var b = a("templates"),
2903 c = a("registry"),
2904 d = (a("shared/scoring"), a("test/views/graph")),
2905 e = a("test/views/table");
2906 return Backbone.View.extend({
2907 el: "#js-content",
2908 initialize: function() {
2909 this.template = b("test", "index"), this.tests = c.get("userTests"), FTWGLOBALS("loggedIn") || this.tests.reset(), this.table = new e({
2910 collection: this.tests
2911 });
2912 var a = $("body").hasClass("compact") ? 394 : 574;
2913 this.graph = new d({
2914 width: a,
2915 height: 144,
2916 collection: this.tests
2917 }), this.render(), FTWGLOBALS("loggedIn") && 0 === this.tests.length ? this.tests.fetch({
2918 reset: !0
2919 }) : this.tests.trigger("reset")
2920 },
2921 render: function() {
2922 this.$el.append(this.template()), this.$("#testGraph").html(this.graph.el), this.$("#tableContents").append(this.table.el)
2923 }
2924 })
2925}), define("games/views/scoreboard", ["require", "templates", "registry"], function(a) {
2926 "use strict";
2927 var b = a("templates"),
2928 c = a("registry");
2929 return Backbone.View.extend({
2930 events: {},
2931 initialize: function(a) {
2932 this.options = a, void 0 === this.template && (this.template = b("games", "scoreboard")), this.user = c.get("user")
2933 },
2934 render: function() {
2935 return this.$el.html(this.template({
2936 loggedIn: loggedIn(),
2937 recent: this.options.recentScores.toJSON(),
2938 high: this.options.highScores.toJSON()
2939 })), this
2940 },
2941 hide: function() {
2942 var a = this.$("input[type=checkbox]");
2943 a.prop("checked") && $.post("/apiv1/student/user/account", {
2944 migrated: 2
2945 }), this.user.set({
2946 migrated: 0
2947 }), this.$el.velocity("slideUp")
2948 }
2949 })
2950}), define("games/models/score", ["require"], function(a) {
2951 "use strict";
2952 return Backbone.Model.extend({
2953 initialize: function() {},
2954 urlRoot: "/apiv1/student/games",
2955 defaults: {
2956 game_id: 0,
2957 score: 0,
2958 seconds: 0,
2959 created_at: 0
2960 },
2961 saveScore: function() {
2962 var a = rot47(JSON.stringify(this.toJSON()));
2963 return $.post(this.urlRoot, {
2964 data: a
2965 })
2966 }
2967 })
2968}), define("games/collections/scores", ["require", "games/models/score"], function(a) {
2969 "use strict";
2970 var b = a("games/models/score");
2971 return Backbone.Collection.extend({
2972 model: b,
2973 url: "/apiv1/student/games",
2974 getScores: function(a) {
2975 return $.get(this.url + "?id=" + a)
2976 }
2977 })
2978}), define("global/views/video_ad", ["require"], function(a) {
2979 "use strict";
2980 return Backbone.View.extend({
2981 adDisplayContainer: null,
2982 adsLoader: null,
2983 adsRequest: null,
2984 initialize: function(a) {
2985 this.options = a, this.game = a.game, this.adDisplayContainer = new google.ima.AdDisplayContainer(this.el, this.game[0]), this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer), this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, this.managerLoaded.bind(this), !1), this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, this.error.bind(this), !1), this.adsRequest = new google.ima.AdsRequest, this.adsRequest.adTagUrl = a.adUrl, this.adsRequest.linearAdSlotWidth = 640, this.adsRequest.linearAdSlotHeight = 400, this.adsRequest.nonLinearAdSlotWidth = 640, this.adsRequest.nonLinearAdSlotHeight = 150, this.adsLoader.requestAds(this.adsRequest)
2986 },
2987 error: function(a) {
2988 console.error("Video Ad Loading Error"), console.error(a.getError()), this.adsManager && this.adsManager.destroy(), this.trigger("complete")
2989 },
2990 managerLoaded: function(a) {
2991 var b = new google.ima.AdsRenderingSettings;
2992 b.restoreCustomPlaybackStateOnAdBreakComplete = !0, this.adsManager = a.getAdsManager(this.game[0], b), this.adDisplayContainer.initialize(), this.adsManager.init(this.options.width, this.options.height, google.ima.ViewMode.NORMAL), this.adsManager.start(), this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, this.error.bind(this)), this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, this.adEvent.bind(this)), this.adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, this.adEvent.bind(this)), this.adsManager.addEventListener(google.ima.AdEvent.Type.USER_CLOSE, this.adEvent.bind(this)), this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, this.adEvent.bind(this)), this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, this.adEvent.bind(this)), this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, this.adEvent.bind(this))
2993 },
2994 adEvent: function(a) {
2995 switch (a.type) {
2996 case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
2997 case google.ima.AdEvent.Type.COMPLETE:
2998 case google.ima.AdEvent.Type.SKIPPED:
2999 case google.ima.AdEvent.Type.USER_CLOSE:
3000 this.trigger("complete")
3001 }
3002 }
3003 })
3004}), define("games/play", ["require", "templates", "registry", "games/views/scoreboard", "games/models/score", "games/collections/scores", "global/views/video_ad"], function(a) {
3005 "use strict";
3006 var b = (a("templates"), a("registry")),
3007 c = a("games/views/scoreboard"),
3008 d = a("games/models/score"),
3009 e = a("games/collections/scores"),
3010 f = a("global/views/video_ad");
3011 return Backbone.View.extend({
3012 el: "#js-content",
3013 initialize: function(a) {
3014 this.options = a || {}, this.user = b.get("user"), this.highScores = new e, this.recentScores = new e, this.highScores.comparator = function(a) {
3015 return -a.get("score")
3016 }, this.scoreboardView = new c({
3017 el: this.$(".js-scoreboard"),
3018 highScores: this.highScores,
3019 recentScores: this.recentScores
3020 }), window.submitGameScoreboard = this.gameOver.bind(this), loggedIn() && FTWGLOBALS("game").scoreboard ? this.highScores.getScores(FTWGLOBALS("game").game_id).done(this.update.bind(this)) : this.scoreboardView.render(), paid() ? this.startGame() : this.showAd()
3021 },
3022 showAd: function() {
3023 this.ad = new f({
3024 el: this.$("#ad-content"),
3025 game: this.$("#game-content"),
3026 adUrl: FTWGLOBALS("adUrl"),
3027 width: 916,
3028 height: 400
3029 }), this.listenTo(this.ad, "complete", this.startGame)
3030 },
3031 startGame: function() {
3032 var a = FTWGLOBALS("game");
3033 "javascript" == a.type && (Game.inited || (new Game(a.width, a.height, "/game_files/" + a.folder, this.$("#game")[0]), Game.inited = !0)), this.$("#ad-content").fastHide(), this.$("#game-content").fastShow()
3034 },
3035 update: function(a) {
3036 this.highScores.reset(a.data.high, {
3037 reset: !0
3038 }), this.recentScores.reset(a.data.recent, {
3039 reset: !0
3040 }), this.scoreboardView.render()
3041 },
3042 gameOver: function(a) {
3043 if (loggedIn() && !(a.seconds <= 0)) {
3044 a.game_id = FTWGLOBALS("game").game_id, a.created_at = Date.getUnixTime();
3045 var b = new d(a);
3046 b.saveScore(), "hard" == a.difficulty && "hard" == a.level && (this.recentScores.unshift(b), this.highScores.add(b), this.highScores.length > 3 && this.highScores.pop(), this.scoreboardView.render())
3047 }
3048 }
3049 })
3050}), define("skins/index", ["require", "templates", "registry"], function(a) {
3051 "use strict";
3052 var b = (a("templates"), a("registry"));
3053 return Backbone.View.extend({
3054 el: "#js-content",
3055 events: {
3056 "click .skin": "changeSkin"
3057 },
3058 initialize: function() {
3059 this.user = b.get("user")
3060 },
3061 changeSkin: function(a) {
3062 var b = $(a.currentTarget).data("id"),
3063 c = $.cookie("tc_skin") || "0";
3064 return this.user.changeSkin(b), $("body").removeClass("skin" + c).addClass("skin" + b), !1
3065 }
3066 })
3067}), define("badges/index", ["require", "templates", "registry", "shared/scoring", "global/collections/badges", "global/collections/courses", "global/collections/lessons"], function(a) {
3068 "use strict";
3069 var b = a("templates"),
3070 c = a("registry"),
3071 d = a("shared/scoring"),
3072 e = a("global/collections/badges"),
3073 f = a("global/collections/courses"),
3074 g = a("global/collections/lessons");
3075 return Backbone.View.extend({
3076 el: "#js-content",
3077 events: {
3078 "mouseenter .badge": "hoverBadge",
3079 "mouseleave .badge": "hoverBadge"
3080 },
3081 initialize: function() {
3082 this.template = b("badges", "index"), this.badges = new e, this.courses = new f(FTWGLOBALS("courses")), this.lessons = new g(FTWGLOBALS("lessons")), this.userBadges = c.get("userBadges"), this.render()
3083 },
3084 render: function() {
3085 return this.$el.html(this.template({
3086 courses: this.courses.toJSON(),
3087 lessons: this.lessons.toJSON(),
3088 userBadges: this.userBadges,
3089 badges: this.badges,
3090 scoring: d
3091 })), this
3092 },
3093 hoverBadge: function(a) {
3094 "mouseenter" == a.type ? $(a.currentTarget).velocity("stop", !0).velocity({
3095 scale: [1.2, 1]
3096 }, 100) : $(a.currentTarget).velocity("stop", !0).velocity("reverse", 50)
3097 }
3098 })
3099}), define("settings/views/info", ["require", "templates", "registry"], function(a) {
3100 "use strict";
3101 var b = a("templates"),
3102 c = a("registry");
3103 return Backbone.View.extend({
3104 initialize: function() {
3105 this.template = b("settings", "info"), this.render()
3106 },
3107 render: function() {
3108 return this.$el.html(this.template({
3109 user: c.get("user").toJSON()
3110 })), this
3111 }
3112 })
3113}), define("settings/views/account", ["require", "templates", "registry", "shared/form_model", "shared/form_validation"], function(a) {
3114 "use strict";
3115 var b = a("templates"),
3116 c = a("registry"),
3117 d = a("shared/form_model"),
3118 e = a("shared/form_validation"),
3119 f = d.extend({
3120 url: "/apiv1/student/user/account",
3121 defaults: {
3122 email: "",
3123 first_name: "",
3124 last_name: "",
3125 country: "",
3126 allow_contact: 0,
3127 spaces: 1,
3128 speed: "wpm",
3129 sounds: 1
3130 },
3131 validationRules: {
3132 email: {
3133 email: !0
3134 }
3135 },
3136 validationMessages: {
3137 email: {
3138 email: "Invalid email address",
3139 INVALID_EMAIL: "Invalid email address",
3140 EMAIL_IN_USE: "This email is in use by a different account"
3141 }
3142 }
3143 });
3144 return Backbone.View.extend({
3145 events: {
3146 "keypress input": "submitOnEnter"
3147 },
3148 initialize: function() {
3149 this.template = b("settings", "account"), this.render(), this.model = new f, this.form = this.$("form");
3150 var a = this.$("form .submit");
3151 new e(this.form, a, this.model, this.successCallback, this, !0)
3152 },
3153 render: function() {
3154 return this.$el.html(this.template({
3155 user: c.get("user").toJSON(),
3156 countries: FTWGLOBALS("countries")
3157 })), this
3158 },
3159 successCallback: function(a) {
3160 c.get("user").set(a), this.form.velocity("slideUp", function() {
3161 this.$(".confirm").slideDown()
3162 }.bind(this))
3163 },
3164 submitOnEnter: function(a) {
3165 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
3166 }
3167 })
3168}), define("settings/views/password", ["require", "templates", "registry", "shared/form_model", "shared/form_validation"], function(a) {
3169 "use strict";
3170 var b = a("templates"),
3171 c = a("registry"),
3172 d = a("shared/form_model"),
3173 e = a("shared/form_validation"),
3174 f = d.extend({
3175 initialize: function() {
3176 $.validator.addMethod("notMatchUsername", function(a) {
3177 return a != c.get("user").get("username")
3178 }, "Can not match username");
3179 },
3180 url: "/apiv1/student/user/password",
3181 defaults: {
3182 oldPassword: "",
3183 password: "",
3184 password2: ""
3185 },
3186 validationRules: {
3187 oldPassword: {
3188 required: !0
3189 },
3190 password: {
3191 notMatchUsername: !0,
3192 required: !0,
3193 minlength: 4
3194 },
3195 password2: {
3196 required: !0,
3197 minlength: 4,
3198 equalTo: "input[name=password]"
3199 }
3200 },
3201 validationMessages: {
3202 password2: {
3203 equalTo: "Passwords do not match"
3204 },
3205 oldPassword: {
3206 INCORRECT_PASSWORD: "Incorrect password"
3207 }
3208 }
3209 });
3210 return Backbone.View.extend({
3211 events: {
3212 "keypress input": "submitOnEnter"
3213 },
3214 initialize: function() {
3215 this.template = b("settings", "password"), this.render(), this.model = new f, this.form = this.$("form");
3216 var a = this.$("form .submit");
3217 new e(this.form, a, this.model, this.successCallback, this, !0)
3218 },
3219 render: function() {
3220 return this.$el.html(this.template()), this
3221 },
3222 successCallback: function(a) {
3223 this.form.velocity("slideUp", function() {
3224 this.$(".confirm").slideDown()
3225 }.bind(this))
3226 },
3227 submitOnEnter: function(a) {
3228 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
3229 }
3230 })
3231}), define("settings/views/keyboard", ["require", "templates", "registry", "shared/form_model", "shared/form_validation", "global/views/keyboard", "global/collections/keyboards"], function(a) {
3232 "use strict";
3233 var b = a("templates"),
3234 c = a("registry"),
3235 d = a("shared/form_model"),
3236 e = a("shared/form_validation"),
3237 f = a("global/views/keyboard"),
3238 g = a("global/collections/keyboards"),
3239 h = d.extend({
3240 events: {
3241 "keypress input": "submitOnEnter"
3242 },
3243 url: "/apiv1/student/user/account",
3244 defaults: {
3245 keyboard_id: 1
3246 },
3247 validationRules: {
3248 keyboard_id: {
3249 required: !0
3250 }
3251 }
3252 });
3253 return Backbone.View.extend({
3254 events: {
3255 "change select[name=keyboard_id]": "changeKeyboard",
3256 "keypress input": "submitOnEnter"
3257 },
3258 initialize: function() {
3259 this.template = b("settings", "keyboard"), this.keyboards = new g(FTWKEYBOARDS), this.keyboard = new f({
3260 type: "large",
3261 noHands: !0,
3262 noControls: !0,
3263 model: this.keyboards.get(c.get("user").get("keyboard_id"))
3264 }), this.render(), this.model = new h, this.form = this.$("form");
3265 var a = this.$("form .submit");
3266 new e(this.form, a, this.model, this.successCallback, this, !0)
3267 },
3268 render: function() {
3269 return this.$el.html(this.template({
3270 user: c.get("user").toJSON(),
3271 keyboards: this.keyboards.toJSON()
3272 })), this.$("#keyboardContainer").append(this.keyboard.render().el), this
3273 },
3274 changeKeyboard: function(a) {
3275 var b = $(a.target).val();
3276 this.keyboard.setKeyboard({
3277 type: "large",
3278 noHands: !0,
3279 noControls: !0,
3280 model: this.keyboards.get(parseInt(b, 10))
3281 }).render()
3282 },
3283 successCallback: function(a) {
3284 c.get("user").set(a), this.form.velocity("slideUp", function() {
3285 this.$(".confirm").slideDown()
3286 }.bind(this))
3287 },
3288 submitOnEnter: function(a) {
3289 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
3290 }
3291 })
3292}), define("settings/views/join", ["require", "templates", "registry", "shared/form_model", "shared/form_validation"], function(a) {
3293 "use strict";
3294 var b = a("templates"),
3295 c = a("registry"),
3296 d = a("shared/form_model"),
3297 e = a("shared/form_validation"),
3298 f = d.extend({
3299 url: "/apiv1/student/user/join",
3300 defaults: {
3301 join_code: "",
3302 section_id: ""
3303 },
3304 validationRules: {
3305 join_code: {
3306 required: !0
3307 }
3308 },
3309 validationMessages: {
3310 join_code: {
3311 INVALID: "Invalid Code. Please make sure you typed it correctly."
3312 }
3313 }
3314 });
3315 return Backbone.View.extend({
3316 events: {
3317 "keypress input": "submitOnEnter"
3318 },
3319 initialize: function() {
3320 this.template = b("settings", "join"), this.model = new f, this.render()
3321 },
3322 render: function() {
3323 this.$el.html(this.template({
3324 user: c.get("user").toJSON(),
3325 joinCode: this.model.get("join_code"),
3326 sections: this.sections ? this.sections.toJSON() : []
3327 })), this.form = this.$("form");
3328 var a = this.$("form .submit");
3329 return new e(this.form, a, this.model, this.successCallback, this, !0), this
3330 },
3331 successCallback: function(a) {
3332 if (a.teacher_id) c.get("user").set(a), this.form.velocity("slideUp", function() {
3333 this.$(".confirm").slideDown()
3334 }.bind(this));
3335 else {
3336 var b = this.model.toJSON();
3337 this.model.clear(), this.model.set({
3338 join_code: b.join_code,
3339 section_id: b.section_id
3340 }), this.sections = new Backbone.Collection(a), this.form.velocity("fadeOut", function() {
3341 this.render()
3342 }.bind(this), {
3343 visibility: "hidden"
3344 })
3345 }
3346 },
3347 submitOnEnter: function(a) {
3348 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
3349 }
3350 })
3351}), define("settings/index", ["require", "templates", "registry", "settings/views/info", "settings/views/account", "settings/views/password", "settings/views/keyboard", "settings/views/join"], function(a) {
3352 "use strict";
3353 var b = a("templates"),
3354 c = a("registry"),
3355 d = a("settings/views/info"),
3356 e = a("settings/views/account"),
3357 f = a("settings/views/password"),
3358 g = a("settings/views/keyboard"),
3359 h = a("settings/views/join");
3360 return Backbone.View.extend({
3361 el: "#js-content",
3362 initialize: function() {
3363 return this.template = b("settings", "index"), this.user = c.get("user"), loggedIn() ? void this.render() : void(location.href = "/student")
3364 },
3365 render: function() {
3366 this.$el.html(this.template());
3367 var a = this.$(".page-settings");
3368 return a.append((new d).el), this.user.get("no_account_change") || "clever" == this.user.get("login_type") || a.append((new e).el), this.user.get("no_password_change") || "username" != this.user.get("login_type") || a.append((new f).el), this.user.get("no_account_change") || a.append((new g).el), "clever" != c.get("user").get("login_type") && a.append((new h).el), this
3369 }
3370 })
3371}), define("upgrade/models/upgrade", ["require"], function(a) {
3372 "use strict";
3373 return Backbone.Model.extend({
3374 url: "/apiv1/student/upgrade"
3375 })
3376}), define("upgrade/index", ["require", "templates", "registry", "shared/analytics", "upgrade/models/upgrade", "global/views/notice"], function(a) {
3377 "use strict";
3378 var b = a("templates"),
3379 c = a("registry"),
3380 d = a("shared/analytics"),
3381 e = a("upgrade/models/upgrade"),
3382 f = a("global/views/notice");
3383 return Backbone.View.extend({
3384 events: {
3385 "click .purchase-box .button": "upgrade"
3386 },
3387 el: "#js-content",
3388 initialize: function() {
3389 return this.template = b("upgrade", "index"), this.templateComplete = b("upgrade", "complete"), this.templateWaiting = b("upgrade", "waiting"), this.user = c.get("user"), this.model = new e, "object" != typeof StripeCheckout ? void alert('Uh oh! It looks like your network is blocking our payment gateway. To upgrade, please allow "stripe.com" in your firewall, or upgrade using a different internet connection.') : (this.stripeHandler = StripeCheckout.configure({
3390 key: FTWGLOBALS("stripeKey"),
3391 token: this.confirmOrder.bind(this),
3392 bitcoin: !0
3393 }), $(window).on("popstate", this.stripeHandler.close.bind(this.stripeHandler)), void this.render())
3394 },
3395 render: function() {
3396 this.$el.velocity("stop").html(this.template({
3397 user: this.user.toJSON(),
3398 price: FTWGLOBALS("product").price
3399 }))
3400 },
3401 renderWaiting: function() {
3402 this.$el.velocity("slideUp", function() {
3403 this.$el.html(this.templateWaiting({})).slideDown()
3404 }.bind(this))
3405 },
3406 renderComplete: function(a) {
3407 this.$el.html(this.templateComplete({
3408 data: a
3409 }))
3410 },
3411 upgrade: function() {
3412 return FTWGLOBALS("loggedIn") ? paid() ? void new f({
3413 error: !0,
3414 text: "Your account is already upgraded! If you are still seeing ads, try logging out and back in."
3415 }).show() : (this.stripeHandler.open({
3416 name: "Typing.com",
3417 description: " Account Upgrade for " + this.user.get("username"),
3418 amount: Math.round(100 * FTWGLOBALS("product").price),
3419 zipCode: !0,
3420 email: this.user.get("email") || null,
3421 allowRememberMe: !1
3422 }), !1) : (new f({
3423 error: !0,
3424 text: "You must log in to upgrade an account!"
3425 }).show(), !1)
3426 },
3427 confirmOrder: function(a) {
3428 this.renderWaiting(), this.model.save(a).done(function(a) {
3429 var b = a.data;
3430 b.total = b.price, b.store = "Typing.com";
3431 var c = [{
3432 product: "Premium Upgrade",
3433 price: b.total,
3434 quantity: 1,
3435 category: "Student"
3436 }];
3437 this.user.refreshCookie(2, !0), this.renderComplete(b), d.trackSale(b, c)
3438 }.bind(this)).fail(function(a) {
3439 this.render(), a.responseJSON ? a.responseJSON.data.declined ? new f({
3440 error: !0,
3441 text: "Your credit card was declined: " + a.responseJSON.data.declined + ". Please try again!"
3442 }).show() : new f({
3443 error: !0,
3444 text: "Uh oh! An error occurred: " + a.responseJSON.data.error + " Please try again!"
3445 }).show() : new f({
3446 error: !0,
3447 text: "An unknown error occurred! Please try again"
3448 }).show()
3449 }.bind(this))
3450 }
3451 })
3452}), define("router", ["require", "index/index", "oauth/index", "index/index", "index/login", "join/index", "index/login", "password/index", "scoreboard/index", "verify/index", "lessons/index", "lessons/index", "lessons/index", "test/index", "games/play", "skins/index", "badges/index", "settings/index", "upgrade/index"], function(a) {
3453 "use strict";
3454 var b = Backbone.Router.extend({
3455 initialize: function() {},
3456 routes: {
3457 "": function() {
3458 new(a("index/index"))
3459 },
3460 oauth: function() {
3461 new(a("oauth/index"))
3462 },
3463 start: function() {
3464 new(a("index/index"))({
3465 start: !0
3466 })
3467 },
3468 login: function() {
3469 new(a("index/login"))
3470 },
3471 join: function() {
3472 new(a("join/index"))({
3473 code: location.hash.replace("#", "")
3474 })
3475 },
3476 signup: function() {
3477 new(a("index/login"))
3478 },
3479 password: function() {
3480 new(a("password/index"))
3481 },
3482 scoreboard: function() {
3483 new(a("scoreboard/index"))
3484 },
3485 verify: function() {
3486 var b;
3487 b = location.hash.match(/\|/) ? location.hash.replace("#", "").split("|") : location.hash.replace("#", "").split("-"), new(a("verify/index"))({
3488 user_test_id: parseInt(b[0]),
3489 user_id: parseInt(b[1])
3490 })
3491 },
3492 "lessons/problemkeys": function() {
3493 new(a("lessons/index"))({
3494 problemkeys: !0
3495 })
3496 },
3497 "lessons/:id(/:slug)(/:index)": function(b) {
3498 new(a("lessons/index"))({
3499 lessonId: b
3500 })
3501 },
3502 "test/:minutes": function(b) {
3503 new(a("lessons/index"))({
3504 test: !0,
3505 minutes: b
3506 })
3507 },
3508 test: function(b) {
3509 new(a("test/index"))
3510 },
3511 "games/play/:slug": function(b) {
3512 new(a("games/play"))
3513 },
3514 skins: function() {
3515 new(a("skins/index"))
3516 },
3517 badges: function() {
3518 new(a("badges/index"))
3519 },
3520 settings: function() {
3521 new(a("settings/index"))
3522 },
3523 upgrade: function() {
3524 new(a("upgrade/index"))
3525 }
3526 }
3527 });
3528 return {
3529 initialize: function() {
3530 new b, Backbone.history.start({
3531 pushState: !0,
3532 root: "/student/"
3533 })
3534 }
3535 }
3536}), define("global/models/user", ["require", "shared/model_backends/localstorage", "registry"], function(a) {
3537 "use strict";
3538 var b = a("shared/model_backends/localstorage"),
3539 c = a("registry");
3540 return Backbone.Model.extend({
3541 idAttribute: "user_id",
3542 defaults: {
3543 user_id: 0,
3544 user_session_id: 0,
3545 teacher_id: null,
3546 section_id: null,
3547 username: "",
3548 first_name: "",
3549 last_name: "",
3550 country: "",
3551 email: "",
3552 allow_contact: 1,
3553 membership: "free",
3554 last_login: 0,
3555 speed: "wpm",
3556 spaces: 1,
3557 keyboard_id: 1,
3558 skin_id: 0,
3559 language: "en",
3560 scoreboard: 1,
3561 two_stars: 90,
3562 three_stars: 95,
3563 _mobileKeyboard: ""
3564 },
3565 initialize: function() {},
3566 refreshCookie: function(a, b) {
3567 a = a || 2;
3568 var c = $.cookie("userrem");
3569 if (b) {
3570 var d = c.split("<");
3571 d[2] = 2, c = d.join("<")
3572 }
3573 $.cookie("userrem", c, {
3574 expires: 1 / 24 * a,
3575 path: "/"
3576 })
3577 },
3578 loginWithData: function(a) {
3579 for (var d, e = [], f = 97; 122 >= f; f++) d = {
3580 id: String.fromCharCode(f),
3581 typed: 0,
3582 errors: 0
3583 }, a.letters_typed[String.fromCharCode(f) + "typed"] && (d.typed = a.letters_typed[String.fromCharCode(f) + "typed"]), a.letters_typed[String.fromCharCode(f) + "errors"] && (d.errors = a.letters_typed[String.fromCharCode(f) + "errors"]), e.push(d);
3584 var g = c.get("userLettersTyped");
3585 g.reset(e, {
3586 silent: !0
3587 }), g.setBackend(new b("userLettersTyped"), "model"), delete a.letters_typed;
3588 var h = c.get("userActivity");
3589 h.reset(a.activity, {
3590 silent: !0
3591 }), h.setBackend(new b("userActivity"), "model"), delete a.activity;
3592 var i = c.get("userLessons");
3593 i.reset(a.lessons, {
3594 silent: !0
3595 }), i.setBackend(new b("userLessons"), "model"), delete a.lessons;
3596 var j = c.get("userBadges");
3597 j.reset(a.badges, {
3598 silent: !0
3599 }), j.setBackend(new b("userBadges"), "model"), delete a.badges, a._lastSessionUpdate = Date.getUnixTime(), this.set(a, {
3600 silent: !0
3601 }), this.setBackend(new b("user"), "model")
3602 },
3603 clearLocalProgress: function() {
3604 c.get("userLettersTyped").reset(), c.get("userActivity").reset(), c.get("userLessons").reset(), c.get("userBadges").reset()
3605 },
3606 logOut: function(a) {
3607 a = a || {};
3608 var c = function() {
3609 $.removeCookie("userrem", {
3610 path: "/"
3611 }), $.removeCookie("teacherrem", {
3612 path: "/"
3613 }), $.removeCookie("tc_skin", {
3614 path: "/"
3615 }), b.clear()
3616 }.bind(this);
3617 return a.ignoreServer ? void c() : $.post("/apiv1/student/auth/logout").done(c)
3618 },
3619 changeSkin: function(a) {
3620 this.set({
3621 skin_id: a
3622 }), $.cookie("tc_skin", String(a), {
3623 expires: 999,
3624 path: "/"
3625 }), FTWGLOBALS("loggedIn") && $.post("/apiv1/student/user/account", {
3626 skin_id: a
3627 })
3628 }
3629 })
3630}), define("global/views/header", ["require", "templates", "registry"], function(a) {
3631 "use strict";
3632 var b = a("templates"),
3633 c = a("registry");
3634 return Backbone.View.extend({
3635 el: "#header",
3636 events: {
3637 "click #logOutLink": "logOut"
3638 },
3639 initialize: function() {
3640 this.user = c.get("user"), this.template = b("global", "header"), this.render()
3641 },
3642 render: function() {
3643 var a = this.user.get("first_name");
3644 return a || "username" != this.user.get("login_type") || (a = this.user.get("username")), this.$el.html(this.template({
3645 data: this.user.toJSON(),
3646 page: FTWGLOBALS("page") || "",
3647 loggedIn: FTWGLOBALS("loggedIn"),
3648 displayName: a
3649 })), this
3650 },
3651 logOut: function() {
3652 return c.get("user").logOut().done(function() {
3653 location.href = "/student/start"
3654 }), !1
3655 }
3656 })
3657}), define("global/collections/user_lessons", ["require", "registry"], function(a) {
3658 "use strict";
3659 var b = a("registry"),
3660 c = Backbone.Model.extend({
3661 idAttribute: "lesson_id",
3662 defaults: {
3663 progress: 0,
3664 max_progress: 0,
3665 typed: 0,
3666 errors: 0,
3667 seconds: 0,
3668 times_completed: 0,
3669 updated_at: 0,
3670 created_at: 0,
3671 last_save_response: null
3672 },
3673 saveStats: function(a, c) {
3674 return a.user_id = b.get("user").id, a.user_session_id = b.get("user").get("user_session_id"), b.get("user").get("section_id") && b.get("user").get("scoreboard") && (a.section_id = b.get("user").get("section_id")), b.get("user").get("teacher_id") && (a.teacher_id = b.get("user").get("teacher_id")), FTWGLOBALS("loggedIn") ? $.postJSON("/apiv1/student/stats", {
3675 stats: a,
3676 keys: c
3677 }).done(function(a) {
3678 a.data && this.set({
3679 last_save_response: a.data
3680 })
3681 }.bind(this)) : $.Deferred().resolve()
3682 },
3683 restart: function() {
3684 return FTWGLOBALS("loggedIn") ? $.postJSON("/apiv1/student/lessons/restart", {
3685 lesson_id: this.id
3686 }) : $.Deferred().resolve()
3687 }
3688 });
3689 return Backbone.Collection.extend({
3690 model: c
3691 })
3692}), define("global/collections/user_activity", ["require"], function(a) {
3693 "use strict";
3694 var b = Backbone.Model.extend({
3695 idAttribute: "hour",
3696 defaults: {
3697 hour: 0,
3698 typed: 0,
3699 seconds: 0,
3700 errors: 0,
3701 screens: 0,
3702 stars: 0
3703 }
3704 });
3705 return Backbone.Collection.extend({
3706 model: b,
3707 comparator: "hour",
3708 merge: function(a) {
3709 a.hour = a.created_at - a.created_at % 3600, a.screens = 1, a = _.mapObject(a, parseInt);
3710 var b = this.get(a.hour);
3711 b ? b.inc({
3712 typed: a.typed,
3713 seconds: a.seconds,
3714 errors: a.errors,
3715 screens: a.screens,
3716 stars: a.stars
3717 }) : this.add(a), a.hour = 0, b = this.get(0), b ? b.inc({
3718 typed: a.typed,
3719 seconds: a.seconds,
3720 errors: a.errors,
3721 screens: a.screens,
3722 stars: a.stars
3723 }) : this.add(a)
3724 },
3725 getCompiled: function(a) {
3726 if (0 === a) return this.get(0) ? this.get(0).toJSON() : (new b).toJSON();
3727 var c = Date.getUnixTime() - 86400 * a;
3728 return this.toJSON().reduce(function(a, b) {
3729 return b.hour >= c && (a.typed += parseInt(b.typed), a.seconds += parseInt(b.seconds), a.errors += parseInt(b.errors), a.screens += parseInt(b.screens), a.stars += parseInt(b.stars)), a
3730 }, {
3731 typed: 0,
3732 seconds: 0,
3733 errors: 0,
3734 screens: 0,
3735 stars: 0
3736 })
3737 }
3738 })
3739}), define("global/collections/user_tests", ["require"], function(a) {
3740 "use strict";
3741 var b = Backbone.Model.extend({
3742 idAttribute: "user_test_id",
3743 defaults: {
3744 seconds: 0,
3745 errors: 0,
3746 typed: 0,
3747 created_at: 0
3748 }
3749 });
3750 return Backbone.Collection.extend({
3751 model: b,
3752 comparator: function(a) {
3753 return -a.get("created_at")
3754 },
3755 url: function() {
3756 return "/apiv1/student/tests"
3757 }
3758 })
3759}), define("global/collections/user_badges", ["require"], function(a) {
3760 "use strict";
3761 var b = Backbone.Model.extend({
3762 idAttribute: "lesson_id",
3763 defaults: {
3764 typed: 0,
3765 errors: 0,
3766 seconds: 0,
3767 created_at: 0
3768 }
3769 });
3770 return Backbone.Collection.extend({
3771 model: b,
3772 comparator: "created_at"
3773 })
3774}), define("global/views/support", ["require", "templates", "shared/form_model", "shared/form_validation"], function(a) {
3775 "use strict";
3776 var b = a("templates"),
3777 c = a("shared/form_model"),
3778 d = a("shared/form_validation"),
3779 e = c.extend({
3780 url: "/apiv1/student/auth/contact",
3781 defaults: {
3782 from: "student",
3783 name: "",
3784 email: "",
3785 message: "",
3786 reason: "",
3787 phone: "",
3788 page: ""
3789 },
3790 validationRules: {
3791 name: {
3792 required: !0
3793 },
3794 email: {
3795 email: !0,
3796 required: !0
3797 },
3798 message: {
3799 required: !0
3800 },
3801 reason: {
3802 required: !0
3803 }
3804 }
3805 });
3806 return Backbone.View.extend({
3807 events: {
3808 "keypress input": "submitOnEnter"
3809 },
3810 baseModel: null,
3811 initialize: function(a) {
3812 this.options = a, this.template = b("global", "support"), this.model = new e({
3813 page: location.href
3814 })
3815 },
3816 render: function() {
3817 return this.$el.html(this.template({})), this.form = this.$("form"), new d(this.form, this.button, this.model, this.successCallback, this), this
3818 },
3819 ok: function() {
3820 this.form.submit()
3821 },
3822 successCallback: function() {
3823 this.trigger("complete")
3824 },
3825 submitOnEnter: function(a) {
3826 13 === a.keyCode && (a.preventDefault(), $(a.currentTarget).blur(), this.form.submit())
3827 }
3828 })
3829}), define("shared/helpers", ["require"], function(a) {
3830 "use strict";
3831 $(document).ajaxComplete(function(a, b, c) {
3832 $._lastXhr = {
3833 url: c.url,
3834 response: b.responseText
3835 }
3836 }), $.Velocity.RegisterEffect("ftw.miniShake", {
3837 defaultDuration: 300,
3838 calls: [
3839 [{
3840 translateX: -6
3841 }, .25],
3842 [{
3843 translateX: 6
3844 }, .25],
3845 [{
3846 translateX: -9
3847 }, .25],
3848 [{
3849 translateX: 0
3850 }, .25]
3851 ],
3852 reset: {
3853 translateX: 0
3854 }
3855 }), $.Velocity.RegisterEffect("ftw.bigShrinkIn", {
3856 defaultDuration: 750,
3857 calls: [
3858 [{
3859 opacity: [1, 1],
3860 scale: [1, 5]
3861 }]
3862 ]
3863 }), $.Velocity.RegisterEffect("ftw.perspectiveShake", {
3864 defaultDuration: 300,
3865 calls: [
3866 [{
3867 opacity: [1, 0],
3868 transformPerspective: [800, 800],
3869 rotateY: [15, -20]
3870 }, null, {
3871 easing: "easeInOutQuad"
3872 }],
3873 [{
3874 transformPerspective: [800, 800],
3875 rotateY: -15
3876 }, null, {
3877 easing: "easeInOutQuad"
3878 }],
3879 [{
3880 transformPerspective: [800, 800],
3881 rotateY: 0
3882 }, null, {
3883 easing: "easeInOutQuad"
3884 }]
3885 ],
3886 reset: {
3887 transformPerspective: 0,
3888 rotateY: 0
3889 }
3890 }), $.Velocity.RegisterEffect("ftw.growUp", {
3891 defaultDuration: 200,
3892 calls: [
3893 [{
3894 opacity: [1, 0],
3895 scale: [1, 0],
3896 transformOriginY: ["100%", "100%"]
3897 }, 1]
3898 ]
3899 }), $.Velocity.RegisterEffect("ftw.fallIn", {
3900 defaultDuration: 800,
3901 calls: [
3902 [{
3903 opacity: [1, 0],
3904 translateY: [0, -75],
3905 translateZ: 0
3906 }, 1, {
3907 easing: "spring"
3908 }]
3909 ]
3910 }), String.prototype.format || (String.prototype.format = function() {
3911 var a = arguments;
3912 return this.replace(/{(\d+)}/g, function(b, c) {
3913 return "undefined" != typeof a[c] ? a[c] : b
3914 })
3915 }), $.fn.fastHide = function() {
3916 return this.each(function(a, b) {
3917 b.style.display = "none"
3918 }), this
3919 }, $.fn.fastShow = function() {
3920 return this.each(function(a, b) {
3921 b.style.display = "block"
3922 }), this
3923 }, $.postJSON = function(a, b) {
3924 return $.ajax({
3925 type: "POST",
3926 url: a,
3927 dataType: "json",
3928 contentType: "application/json; charset=utf-8",
3929 data: JSON.stringify(b)
3930 })
3931 }, String.prototype.ucFirst = function() {
3932 return this.substr(0, 1).toUpperCase() + this.substr(1)
3933 }, $.validator.addMethod("username", function(a, b) {
3934 return a.toLowerCase().trim().match(/^[a-z0-9\-\._@]+$/i)
3935 }, "Usernames can only contain letters, numbers, underscores, dashes, and dots."), $.validator.ignoreEmailExtended = function(a, b) {
3936 var c = $(a).closest("form"),
3937 d = c.find("input[name=" + b + "]");
3938 return d.data("emailExtended", d.val()), c.validate().element("input[name=" + b + "]"), !1
3939 }, $.validator.addMethod("emailExtended", function(a, b) {
3940 var c = Mailcheck.run({
3941 email: a
3942 });
3943 return c && $(b).data("emailExtended") != a ? ($.validator.messages.emailExtended = 'This email might contain a typo or mistake. <a href="#" onclick="$.validator.ignoreEmailExtended(this, \'' + b.name + "'); return false;\">No, this is correct.</a>", !1) : !0
3944 }, "This email looks possibly invalid. Please double check."), $.validator.addMethod("notMatchUsername", function(a) {
3945 return a != this.findByName("username").val()
3946 }, "Can not match username"), $.validator.addMethod("ips", function(a) {
3947 return a.trim() ? !a.trim().split("\n").filter(function(a) {
3948 return !a.trim().match(/^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$/)
3949 }).length : !0
3950 }, "Contains at least one invalid IP address. IPs are in the format A.B.C.D, for example 192.168.100.50."), $.validator.defaults.errorPlacement = function(a, b) {
3951 b.parent().is("label") ? b.parent().after(a) : a.insertAfter(b)
3952 }, Number.addCommas = function(a, b) {
3953 void 0 !== b && (a = Number(a).toFixed(b) || 0);
3954 var c = a.toString().split(".");
3955 c[0] = c[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3956 var d = c.join(".");
3957 return d
3958 }, Number.prototype.addCommas = function(a) {
3959 return Number.addCommas(this, a)
3960 }, Number.prototype.countdownSeconds = function(a) {
3961 a = a || !1;
3962 var b = Math.floor(this / 60 / 60),
3963 c = Math.floor((this - 60 * b * 60) / 60),
3964 d = Math.floor(this - 60 * b * 60 - 60 * c),
3965 e = "";
3966 return e = String(c).pad(b > 0 || a ? 2 : 1, "0", "STR_PAD_LEFT") + ":" + String(d).pad(2, "0", "STR_PAD_LEFT"), (b > 0 || a) && (e = String(b).pad(2, "0", "STR_PAD_LEFT") + ":" + e), e
3967 }, String.prototype.nextLesson = function() {
3968 var a = this.lastIndexOf("/"),
3969 b = parseInt(this.substr(a + 1));
3970 return !this.match(/\/student\/lessons\/\d+\/\w+/) || 1 != b && 2 != b ? this : this.substr(0, a + 1) + (1 == b ? 2 : 1)
3971 }, String.prototype.slug = function() {
3972 return this.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
3973 }, String.prototype.pad = function(a, b, c) {
3974 var d, e = this,
3975 f = "",
3976 g = function(a, b) {
3977 for (var c = ""; c.length < b;) c += a;
3978 return c = c.substr(0, b)
3979 };
3980 return e += "", b = void 0 !== b ? b : " ", "STR_PAD_LEFT" !== c && "STR_PAD_RIGHT" !== c && "STR_PAD_BOTH" !== c && (c = "STR_PAD_RIGHT"), (d = a - e.length) > 0 && ("STR_PAD_LEFT" === c ? e = g(b, d) + e : "STR_PAD_RIGHT" === c ? e += g(b, d) : "STR_PAD_BOTH" === c && (f = g(b, Math.ceil(d / 2)), e = f + e + f, e = e.substr(0, a))), e
3981 }, Number.prototype.ordinal = function() {
3982 var a = ["th", "st", "nd", "rd"],
3983 b = this % 100;
3984 return a[(b - 20) % 10] || a[b] || a[0]
3985 }, Date.getUnixTime = function() {
3986 return Math.floor(Date.now() / 1e3)
3987 }, Number.prototype.pluralize = function(a) {
3988 return a = a || !1, a ? 1 == this ? "" : "es" : 1 == this ? "" : "s"
3989 }, Math.long2ip = function(a) {
3990 for (var b = a % 256, c = 1; 3 >= c; c++) a = Math.floor(a / 256), b = a % 256 + "." + b;
3991 return b
3992 }, $.getQueryParam = function(a) {
3993 a = a.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
3994 var b = new RegExp("[\\?&]" + a + "=([^&#]*)"),
3995 c = b.exec(location.search);
3996 return null === c ? "" : decodeURIComponent(c[1].replace(/\+/g, " "))
3997 };
3998 var b = function(a, b) {
3999 var c, d, e, f = new String,
4000 g = b.length;
4001 for (c = 0; c < a.length; c++) e = a.charAt(c), d = b.indexOf(e), d >= 0 && (e = b.charAt((d + g / 2) % g)), f += e;
4002 return f
4003 };
4004 window.rot47 = function(a) {
4005 var c = new String;
4006 return c = b(a, "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")
4007 }, location.softReload = function() {
4008 try {
4009 location.href = location.href
4010 } catch (a) {
4011 location.reload(!1)
4012 }
4013 };
4014 var c = {};
4015 c.PADCHAR = "=", c.ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", c.makeDOMException = function() {
4016 try {
4017 return new DOMException(DOMException.INVALID_CHARACTER_ERR)
4018 } catch (a) {
4019 var b = new Error("DOM Exception 5");
4020 return b.code = b.number = 5, b.name = b.description = "INVALID_CHARACTER_ERR", b.toString = function() {
4021 return "Error: " + b.name + ": " + b.message
4022 }, b
4023 }
4024 }, c.getbyte64 = function(a, b) {
4025 var d = c.ALPHA.indexOf(a.charAt(b));
4026 if (-1 === d) throw c.makeDOMException();
4027 return d
4028 }, c.decode = function(a) {
4029 a = "" + a;
4030 var b, d, e, f = c.getbyte64,
4031 g = a.length;
4032 if (0 === g) return a;
4033 if (g % 4 !== 0) throw c.makeDOMException();
4034 b = 0, a.charAt(g - 1) === c.PADCHAR && (b = 1, a.charAt(g - 2) === c.PADCHAR && (b = 2), g -= 4);
4035 var h = [];
4036 for (d = 0; g > d; d += 4) e = f(a, d) << 18 | f(a, d + 1) << 12 | f(a, d + 2) << 6 | f(a, d + 3), h.push(String.fromCharCode(e >> 16, e >> 8 & 255, 255 & e));
4037 switch (b) {
4038 case 1:
4039 e = f(a, d) << 18 | f(a, d + 1) << 12 | f(a, d + 2) << 6, h.push(String.fromCharCode(e >> 16, e >> 8 & 255));
4040 break;
4041 case 2:
4042 e = f(a, d) << 18 | f(a, d + 1) << 12, h.push(String.fromCharCode(e >> 16))
4043 }
4044 return h.join("")
4045 }, c.getbyte = function(a, b) {
4046 var d = a.charCodeAt(b);
4047 if (d > 255) throw c.makeDOMException();
4048 return d
4049 }, c.encode = function(a) {
4050 if (1 !== arguments.length) throw new SyntaxError("Not enough arguments");
4051 var b, d, e = c.PADCHAR,
4052 f = c.ALPHA,
4053 g = c.getbyte,
4054 h = [];
4055 a = "" + a;
4056 var i = a.length - a.length % 3;
4057 if (0 === a.length) return a;
4058 for (b = 0; i > b; b += 3) d = g(a, b) << 16 | g(a, b + 1) << 8 | g(a, b + 2), h.push(f.charAt(d >> 18)), h.push(f.charAt(d >> 12 & 63)), h.push(f.charAt(d >> 6 & 63)), h.push(f.charAt(63 & d));
4059 switch (a.length - i) {
4060 case 1:
4061 d = g(a, b) << 16, h.push(f.charAt(d >> 18) + f.charAt(d >> 12 & 63) + e + e);
4062 break;
4063 case 2:
4064 d = g(a, b) << 16 | g(a, b + 1) << 8, h.push(f.charAt(d >> 18) + f.charAt(d >> 12 & 63) + f.charAt(d >> 6 & 63) + e)
4065 }
4066 return h.join("")
4067 }, window.btoa || (window.btoa = c.encode), window.atob || (window.atob = c.decode)
4068}), define("shared/model", ["require"], function(a) {
4069 "use strict";
4070 Backbone.Model.prototype.parse = function(a) {
4071 return _.isObject(a.data) && _.isBoolean(a.success) ? a.data : a
4072 }, Backbone.Model.prototype.setBackend = function(a, b) {
4073 return this.backend && this.off("change", this.backend.change), a.setModel(this), this.on("change", a.change, a), "backend" == b ? this.set(a.getData()) : "model" == b && a.setData(this.toJSON()), this.backend = a, this
4074 }, Backbone.Model.prototype.inc = function() {
4075 if ("object" == typeof arguments[0]) {
4076 var a = null,
4077 b = {};
4078 _.each(arguments[0], function(c, d) {
4079 a = this.get(d) || 0, b[d] = a + c
4080 }, this), this.set(b, arguments[1])
4081 } else if ("string" == typeof arguments[0] && arguments[1]) {
4082 var a = this.get(arguments[0]) || 0,
4083 b = {};
4084 return b[arguments[0]] = a + arguments[1], this.set(b, arguments[3])
4085 }
4086 }
4087}), define("shared/collection", ["require"], function(a) {
4088 "use strict";
4089 Backbone.Collection.prototype.parse = function(a) {
4090 return _.isObject(a.data) && _.isBoolean(a.success) ? a.data : a
4091 }, Backbone.Collection.prototype.setBackend = function(a, b) {
4092 return a.setModel(this), this.on("change", a.change, a), this.on("add", a.add, a), this.on("remove", a.remove, a), this.on("reset", a.reset, a), this.on("sort", a.sort, a), "backend" == b ? this.reset(a.getData()) : "model" == b && a.setData(this.toJSON()), this
4093 }, Backbone.Collection.prototype.sortField = "", Backbone.Collection.prototype.sortDir = "asc";
4094 var b = function(a, b) {
4095 var c = a.get(this.sortField),
4096 d = b.get(this.sortField),
4097 e = "asc" == this.sortDir ? 1 : -1;
4098 return d > c ? -1 * e : c > d ? 1 * e : 0
4099 },
4100 c = function(a, c) {
4101 var d = String(a.get(this.sortField)),
4102 e = String(c.get(this.sortField)),
4103 f = "asc" == this.sortDir ? 1 : -1;
4104 if ("string" != typeof d && "string" != typeof e) return b.call(this, a, c);
4105 var g, h, i, j, k, l, m = 0,
4106 n = /(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.\D+)|(\.$)/g;
4107 if (d === e) return 0;
4108 for (g = d.toLowerCase().match(n) || "", h = e.toLowerCase().match(n) || "", l = g.length; l > m;) {
4109 if (!h[m]) return 1 * f;
4110 if (i = g[m], j = h[m++], i !== j) return k = i - j, isNaN(k) ? (i > j ? 1 : -1) * f : k * f
4111 }
4112 return (h[m] ? -1 : 0) * f
4113 };
4114 Backbone.Collection.prototype.reSort = function(a, b) {
4115 this.sortDir = b || ("asc" == this.sortDir && this.sortField == a ? "desc" : "asc"), this.sortField = a, this.comparator !== c && (this.comparator = c), this.sort()
4116 }
4117}), define("app", ["require", "router", "registry", "shared/analytics", "shared/scoring", "shared/radar_tracker", "global/models/user", "global/views/header", "global/collections/user_lessons", "global/collections/letters_typed", "global/collections/user_activity", "global/collections/user_tests", "global/collections/user_badges", "shared/model_backends/localstorage", "shared/tooltip", "global/views/notice", "global/views/alert", "global/views/modal", "global/views/support", "shared/helpers", "shared/model", "shared/collection"], function(a) {
4118 "use strict";
4119 var b = a("router"),
4120 c = a("registry"),
4121 d = a("shared/analytics"),
4122 e = a("shared/scoring"),
4123 f = a("shared/radar_tracker"),
4124 g = a("global/models/user"),
4125 h = a("global/views/header"),
4126 i = a("global/collections/user_lessons"),
4127 j = a("global/collections/letters_typed"),
4128 k = a("global/collections/user_activity"),
4129 l = a("global/collections/user_tests"),
4130 m = a("global/collections/user_badges"),
4131 n = a("shared/model_backends/localstorage"),
4132 o = a("shared/tooltip"),
4133 p = a("global/views/notice"),
4134 q = a("global/views/alert"),
4135 r = a("global/views/modal"),
4136 s = a("global/views/support");
4137 return a("shared/helpers"), a("shared/model"), a("shared/collection"), {
4138 initialize: function() {
4139 this.browserCheck() && (window.__NO_SCRIPTS || (this.globals(), "production" == FTWGLOBALS("env") && this.analytics(), this.authentication({
4140 cookieName: "userrem"
4141 }), this.views(), this.support(), this.pageTrack(), this.router(), this.tracker(), this.tooltips()))
4142 },
4143 browserCheck: function() {
4144 var a = navigator.userAgent;
4145 if (a.match(/MSIE/) && parseInt(a.match(/MSIE (\d+)/)[1], 10) < 10) return location.href = "/failbrowser", !1;
4146 if (a.match(/Firefox/) && parseInt(a.match(/Firefox\/(\d+)/)[1], 10) < 29) return location.href = "/failbrowser", !1;
4147 if (!a.match("Chrome") && a.match("Safari") && a.match(/Version\/(\d+.\d+)/)) {
4148 var b = parseFloat(a.match(/Version\/(\d+.\d+)/)[1]);
4149 if (5.1 > b) return location.href = "/failbrowser", !1
4150 }
4151 if (!window.__NO_SCRIPTS) {
4152 try {
4153 window.localStorage.setItem("test", "test")
4154 } catch (c) {
4155 return navigator.userAgent.match(/safari/i) ? alert("Typing.com can not be used in Safari while in Private Browsing Mode due to a bug in Safari. Please switch to normal browsing mode to use Typing.com, or switch to a different browser.") : alert("Error: Your browser does not support Local Storage, or your Local Storage is full. You will not be able to use Typing.com until this is resolved. Try updating your browser, or if you are in Private Browsing mode try normal mode."), !1
4156 }
4157 return !0
4158 }
4159 },
4160 globals: function() {
4161 c.set("data", window.FTW), c.set("isMobile", navigator.userAgent.match(/iPad|iPhone|Android/));
4162 var a = _.extend({}, Backbone.Events);
4163 c.set("pubSub", a);
4164 var b = new g;
4165 b.setBackend(new n("user"), "backend"), c.set("user", b);
4166 var d = new i;
4167 d.setBackend(new n("userLessons"), "backend"), c.set("userLessons", d);
4168 var e = new k;
4169 e.setBackend(new n("userActivity"), "backend"), c.set("userActivity", e);
4170 var f = new m;
4171 f.setBackend(new n("userBadges"), "backend"), c.set("userBadges", f);
4172 var h = new l;
4173 h.setBackend(new n("userTests"), "backend"), c.set("userTests", h);
4174 var o = new j;
4175 o.setBackend(new n("userLettersTyped"), "backend"), c.set("userLettersTyped", o)
4176 },
4177 authentication: function(a) {
4178 $.ajaxSetup({
4179 headers: {
4180 "X-CSRFToken": c.get("user").get("token"),
4181 "X-User-Id": c.get("user").id
4182 }
4183 });
4184 var b = this;
4185 if ($(document).ajaxError(function(a, d) {
4186 if (d.responseJSON && d.responseJSON.data) {
4187 var e = d.responseJSON.data.error;
4188 !FTWGLOBALS("loggedIn") || "LOGIN_REQUIRED" != e && "TOKEN_MISMATCH" != e || (console.error("Invalid token or session"), c.get("user").logOut({
4189 ignoreServer: !0
4190 }), "production" == FTWGLOBALS("env") ? location.href = "/student" : b.devLoggedOutNotice("Bad AJAX response: " + JSON.stringify({
4191 error: e,
4192 response: d
4193 })))
4194 }
4195 }), location.pathname.match(/^\/student\/oauth/)) return !0;
4196 var d = FTWGLOBALS("loggedIn"),
4197 e = c.get("user"),
4198 f = $.cookie(a.cookieName) || "",
4199 g = f.split("<"),
4200 h = g[1];
4201 if (d) {
4202 if (!e.id || h != e.id) return e.logOut().done(function() {
4203 "production" == FTWGLOBALS("env") ? location.reload() : b.devLoggedOutNotice("LocalStorage ID does not match cookieUserId: " + JSON.stringify({
4204 "user.id": e.id,
4205 cookieUserId: h
4206 }))
4207 }), !1;
4208 var i = 2;
4209 e.refreshCookie(i), setTimeout(this.loggedOutNotice.bind(this), 3600 * i * 1e3)
4210 } else e.id && (e.logOut({
4211 ignoreServer: !0
4212 }), "local" == FTWGLOBALS("env") && b.devLoggedOutNotice("Not logged in, but has local storage: " + JSON.stringify({
4213 cookieParts: g,
4214 cookieUserId: h
4215 })), this.globals());
4216 return !0
4217 },
4218 loggedOutNotice: function() {
4219 new q({
4220 title: "Logged Out",
4221 text: "This account has been logged out due to inactivity. You must log back in to continue"
4222 }).show().on("close", function() {
4223 location.href = "/student/login"
4224 })
4225 },
4226 views: function() {
4227 new h({
4228 model: c.get("user")
4229 })
4230 },
4231 support: function() {
4232 FTWGLOBALS("loggedIn") && c.get("user").get("teacher_id") ? $("#supportTab").remove() : $("#supportTab").fastShow().click(function() {
4233 var a = new r({
4234 title: "Contact Support",
4235 okButton: "Send",
4236 cancelButton: "Cancel",
4237 contentView: new s,
4238 position: "absolute"
4239 });
4240 return a.on("complete", function() {
4241 new p({
4242 error: !1,
4243 text: "Your support request has been sent. We will get back to you shortly!"
4244 }).show()
4245 }.bind(this)), a.show(), !1
4246 })
4247 },
4248 devLoggedOutNotice: function(a) {
4249 console.trace(), new q({
4250 title: "DEBUGGING NOTICE: Logged Out",
4251 text: "This account was just logged out. Look at the console. " + a
4252 }).show()
4253 },
4254 router: function() {
4255 b.initialize()
4256 },
4257 tracker: function() {
4258 location.href.match(/lessons\/[0-9]+/) || f.track(c.get("user"))
4259 },
4260 pageTrack: function() {
4261 var a = rot47("student_pages"),
4262 b = localStorage.getItem(a) || 0;
4263 b++, localStorage.setItem(a, b)
4264 },
4265 analytics: function() {
4266 var a = c.get("user");
4267 if (d.customDimension(1, FTWGLOBALS("loggedIn") ? "yes" : "no"), FTWGLOBALS("loggedIn")) {
4268 a.get("teacher_id") ? d.customDimension(2, "in class") : d.customDimension(2, "individual"), d.customDimension(3, a.get("login_type")), d.customDimension(4, a.get("membership")), d.customDimension(5, Math.floor(Math.floor(Date.now() / 1e3 - a.get("created_at")) / 60 / 60 / 24));
4269 var b = c.get("userActivity").getCompiled(999),
4270 f = e.speed(b.typed, b.seconds);
4271 f > 0 && (f = 10 * Math.floor(f / 10), d.customDimension(6, f + "-" + (f + 9)));
4272 var g = c.get("userLessons").toJSON().filter(function(a) {
4273 return a.progress > 0
4274 }).length;
4275 d.customDimension(7, g)
4276 } else d.customDimension(2, "anon");
4277 ga("send", "pageview")
4278 },
4279 tooltips: function() {
4280 o.init()
4281 }
4282 }
4283}), require.config({
4284 urlArgs: "bust=" + (new Date).getTime(),
4285 baseUrl: "/src/student/js",
4286 paths: {
4287 shared: "../../shared",
4288 analytics: "../../shared/analytics",
4289 registry: "../../shared/registry",
4290 templates: "../../shared/templates"
4291 }
4292}), require(["app"], function(a) {
4293 $(function() {
4294 a.initialize()
4295 })
4296}), define("main", function() {}), require(["app"]);