Advertisement
Guest User

Untitled

a guest
Nov 16th, 2016
166
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 190.90 KB | None | 0 0
  1. var 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"]);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement