Merge remote-tracking branch 'codecombat/master' into flip-hero-logic

This commit is contained in:
Cat Sync 2016-05-13 13:32:46 -04:00
commit 59408c0de2
36 changed files with 1091 additions and 559 deletions

65
Vagrantfile vendored
View file

@ -1,32 +1,33 @@
# -*- mode: ruby -*- # -*- mode: ruby -*-
# vi: set ft=ruby : # vi: set ft=ruby :
# Original content copyright (c) 2014 dpen2000 licensed under the MIT license # Original content copyright (c) 2014 dpen2000 licensed under the MIT license
VAGRANTFILE_API_VERSION = "2" VAGRANTFILE_API_VERSION = "2"
Vagrant.require_version ">= 1.5.0" Vagrant.require_version ">= 1.5.0"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# Ubuntu 14.04 compatible with both VirtualBox and VMWare Fusion # Ubuntu 14.04 compatible with both VirtualBox and VMWare Fusion
# see https://github.com/phusion/open-vagrant-boxes#readme # see https://github.com/phusion/open-vagrant-boxes#readme
config.vm.box = "phusion/ubuntu-14.04-amd64" config.vm.box = "phusion/ubuntu-14.04-amd64"
config.vm.hostname = "coco-dev" config.vm.hostname = "coco-dev"
config.vm.network "forwarded_port", guest: 3000, host: 3000 config.vm.network "forwarded_port", guest: 3000, host: 13000
config.vm.network "forwarded_port", guest: 9485, host: 9485 config.vm.network "forwarded_port", guest: 9485, host: 19485
config.vm.provision "shell", path: "scripts/vagrant/provision.sh", privileged: false config.vm.provision "shell", path: "scripts/vagrant/provision.sh", privileged: false
config.vm.provider "virtualbox" do |v| config.vm.provider "virtualbox" do |v|
v.memory = 2048 v.memory = 2048
v.cpus = 2 v.cpus = 2
end #v.gui = true
end
config.vm.provider "vmware_fusion" do |v|
v.vmx["memsize"] = "2048" config.vm.provider "vmware_fusion" do |v|
v.vmx["numvcpus"] = 2 v.vmx["memsize"] = "2048"
end v.vmx["numvcpus"] = 2
end
end
end

View file

@ -37,6 +37,7 @@ module.exports = class CocoRouter extends Backbone.Router
'admin/level-sessions': go('admin/LevelSessionsView') 'admin/level-sessions': go('admin/LevelSessionsView')
'admin/users': go('admin/UsersView') 'admin/users': go('admin/UsersView')
'admin/base': go('admin/BaseView') 'admin/base': go('admin/BaseView')
'admin/demo-requests': go('admin/DemoRequestsView')
'admin/trial-requests': go('admin/TrialRequestsView') 'admin/trial-requests': go('admin/TrialRequestsView')
'admin/user-code-problems': go('admin/UserCodeProblemsView') 'admin/user-code-problems': go('admin/UserCodeProblemsView')
'admin/pending-patches': go('admin/PendingPatchesView') 'admin/pending-patches': go('admin/PendingPatchesView')

View file

@ -8,12 +8,12 @@ module.exports.createAetherOptions = (options) ->
throw new Error 'Specify a code language to create an Aether instance' unless options.codeLanguage throw new Error 'Specify a code language to create an Aether instance' unless options.codeLanguage
useInterpreter = options.useInterpreter useInterpreter = options.useInterpreter
defaultToEsper = switch options.codeLanguage defaultToEsper = true #switch options.codeLanguage
when 'python' then me.level() < 15 # Esper currently works well until using range() # when 'python' then me.level() < 15 # Esper currently works well until using range()
when 'javascript' then me.level() < 22 # Esper currently works well until using hero.myFn = function() pattern # when 'javascript' then me.level() < 22 # Esper currently works well until using hero.myFn = function() pattern
when 'lua' then me.level() < 10 # Functions don't work in Esper yet, can't play forest function levels # when 'lua' then me.level() < 10 # Functions don't work in Esper yet, can't play forest function levels
when 'coffeescript' then false # CoffeeScript has a toNative error if it ever finishes plan(), and also @fn = -> pattern doesn't work # when 'coffeescript' then false # CoffeeScript has a toNative error if it ever finishes plan(), and also @fn = -> pattern doesn't work
when 'clojure' then false # No Clojure support # when 'clojure' then false # No Clojure support
useInterpreter ?= !!utils.getQueryVariable 'esper', defaultToEsper useInterpreter ?= !!utils.getQueryVariable 'esper', defaultToEsper
aetherOptions = aetherOptions =
functionName: options.functionName functionName: options.functionName

View file

@ -1,4 +1,4 @@
module.exports = nativeDescription: "English", englishDescription: "English", translation: module.exports = nativeDescription: "English", englishDescription: "English", translation:
home: home:
slogan: "Learn to Code by Playing a Game" slogan: "Learn to Code by Playing a Game"
no_ie: "CodeCombat does not run in Internet Explorer 8 or older. Sorry!" # Warning that only shows up in IE8 and older no_ie: "CodeCombat does not run in Internet Explorer 8 or older. Sorry!" # Warning that only shows up in IE8 and older
@ -57,15 +57,11 @@
real_game: "A real game, played with real coding." real_game: "A real game, played with real coding."
great_game: "A great game is more than just badges and achievements - its about a players journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence." great_game: "A great game is more than just badges and achievements - its about a players journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence."
agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code." agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code."
curious: "Curious? Request a demo and we'll show you the ropes"
request_demo_title: "Get your students started today!" request_demo_title: "Get your students started today!"
request_demo_subtitle: "Request a demo and get your students started in less than an hour." request_demo_subtitle: "Request a demo and get your students started in less than an hour."
get_started_title: "Set up your class today" get_started_title: "Set up your class today"
get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science." get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science."
create_class: "Or create a class and see it for yourself!"
teacher_screenshots_hint: "Students write code and see their changes update in real-time"
request_demo: "Request a Demo" request_demo: "Request a Demo"
create_a_class: "Create a Class"
setup_a_class: "Set Up a Class" setup_a_class: "Set Up a Class"
have_an_account: "Have an account?" have_an_account: "Have an account?"
logged_in_as: "You are currently logged in as" logged_in_as: "You are currently logged in as"
@ -91,10 +87,9 @@
start_playing_for_free: "Start Playing for Free!" start_playing_for_free: "Start Playing for Free!"
students_and_players: "Students & Players" students_and_players: "Students & Players"
goto_classes: "Go to My Classes" goto_classes: "Go to My Classes"
educator_wiki: "Educator wiki"
view_profile: "View My Profile" view_profile: "View My Profile"
view_progress: "View Progress" view_progress: "View Progress"
check_out_wiki: "Check out our new educator Wiki" check_out_wiki: "Check out our new Educator Wiki"
want_coco: "Want CodeCombat at your school?" want_coco: "Want CodeCombat at your school?"
form_select_role: "Select primary role" form_select_role: "Select primary role"
form_select_range: "Select class size" form_select_range: "Select class size"
@ -190,20 +185,52 @@
code: code:
if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.) if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
else: "else" else: "else"
elif: "elif" elif: "else if"
while: "while" while: "while"
loop: "loop" loop: "loop"
for: "for" for: "for"
break: "break" break: "break"
continue: "continue" continue: "continue"
pass: "pass"
return: "return"
then: "then" then: "then"
do: "do" do: "do"
end: "end" end: "end"
function: "function" function: "function"
def: "def" # (short for "define") def: "define"
var: "variable"
self: "self" self: "self"
hero: "hero" hero: "hero"
this: "this" this: "this"
or: "or"
"||": "or"
and: "and"
"&&": "and"
not: "not"
"!": "not"
"=": "assign" # For this section, conjugate it like it's the verb part of a sentence when possible
"==": "equals"
"===": "strictly equals"
"!=": "does not equal"
"!==": "does not strictly equal"
">": "is greater than"
">=": "is greater than or equal"
"<": "is less than"
"<=": "is less than or equal"
"*": "multiplied by"
"/": "divided by"
"+": "plus"
"-": "minus"
"+=": "add and assign"
"-=": "subtract and assign"
True: "True"
true: "true"
False: "False"
false: "false"
undefined: "undefined"
null: "null"
nil: "nil"
None: "None"
share_progress_modal: share_progress_modal:
blurb: "Youre making great progress! Tell your parent how much you've learned with CodeCombat." blurb: "Youre making great progress! Tell your parent how much you've learned with CodeCombat."
@ -755,9 +782,6 @@
jobs_title: "Come work with us and help write CodeCombat history!" jobs_title: "Come work with us and help write CodeCombat history!"
jobs_subtitle: """Don't see a good fit but interested in keeping in touch? See our "Create Your Own" listing.""" jobs_subtitle: """Don't see a good fit but interested in keeping in touch? See our "Create Your Own" listing."""
jobs_benefits: "Employee Benefits" jobs_benefits: "Employee Benefits"
jobs_benefit_1: "Competitive salary and options"
jobs_benefit_2: "15 day minimum vacation policy, excluding company holidays"
jobs_benefit_3: "Flex time and flexible work-from-home"
jobs_benefit_4: "Unlimited vacation" jobs_benefit_4: "Unlimited vacation"
jobs_benefit_5: "Professional development and continuing education support free books and games!" jobs_benefit_5: "Professional development and continuing education support free books and games!"
jobs_benefit_6: "Medical (gold), dental, vision" jobs_benefit_6: "Medical (gold), dental, vision"

View file

@ -14,90 +14,90 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
for_developers: "Voor ontwikkelaars" # Not currently shown on home page. for_developers: "Voor ontwikkelaars" # Not currently shown on home page.
or_ipad: "Of download voor iPad" or_ipad: "Of download voor iPad"
# new_home: new_home:
# slogan: "The most engaging game for learning programming." slogan: "Het meest uitdagende spel om mee te leren programmeren."
# classroom_edition: "Classroom Edition:" classroom_edition: "Klas versie:"
# learn_to_code: "Learn to code:" learn_to_code: "Leer programmeren:"
# teacher: "Teacher" teacher: "Leraar"
# student: "Student" student: "Leerling"
# play_now: "Play Now" play_now: "Speel"
# im_a_teacher: "I'm a Teacher" im_a_teacher: "Ik ben een leraar"
# im_a_student: "I'm a Student" im_a_student: "Ik ben een leerling"
# learn_more: "Learn more" learn_more: "Lees verder"
# classroom_in_a_box: "A classroom in-a-box for teaching computer science." classroom_in_a_box: "Een kant-en-klare digitale klas voor programmeerlessen."
# codecombat_is: "CodeCombat is a platform <strong>for students</strong> to learn computer science while playing through a real game." codecombat_is: "CodeCombat is een platform waarmee leerlingen leren programmeren door het spelen van een spel." # {change}
# our_courses: "Our courses have been specifically playtested to <strong>excel in the classroom</strong>, even by teachers with little to no prior programming experience." our_courses: "Onze lessen zijn specifiek ontwikkeld voor een klasomgeving, zelfs voor leraren zonder programmeerervaring." # {change}
# top_screenshots_hint: "Students write code and see their changes update in real-time" top_screenshots_hint: "Leerlingen schrijven code en zien direct het resultaat van de verandering."
# designed_with: "Designed with teachers in mind" designed_with: "Gemaakt voor leraren"
# real_code: "Real, typed code" real_code: "Echte, getypte code"
# from_the_first_level: "from the first level" from_the_first_level: "vanaf het eerste level"
# getting_students: "Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure." getting_students: "Leerlingen zo snel mogelijk echte code laten schrijven is noodzakelijk voor het leren van programmeer syntax en correcte structuur."
# educator_resources: "Educator resources" educator_resources: "Lesbrieven voor docenten"
# course_guides: "and course guides" course_guides: "en ondersteuningsmateriaal"
# teaching_computer_science: "Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds." teaching_computer_science: "Je hebt geen informatica diploma nodig om te kunnen programmeren, wij verschaffen de materialen waarmee elke docent programmeerles kan geven."
# accessible_to: "Accessible to" accessible_to: "Bereikbaar voor"
# everyone: "everyone" everyone: "iedereen"
# democratizing: "Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code." democratizing: "Programmeerles toegankelijk maken is onze filosofie. Iedereen moet de kans krijgen om te leren programmeren."
# forgot_learning: "I think they actually forgot that they were actually learning something." forgot_learning: "Volgens mij hadden ze niet meer door dat ze eigenlijk bezig waren met leren."
# wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school." wanted_to_do: " Ik wilde altijd al leren programmeren, maar op school was hier nooit aandacht voor."
# why_games: "Why is learning through games important?" why_games: "Waarom is spelenderwijs leren belangrijk?"
# games_reward: "Games reward the productive struggle." games_reward: "Games vergroten de productiviteit."
# encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn." encourage: "Gaming is een middel dat interactie, nieuwschierigheid, en trial-and-error aanmoedigt. Een goed spel daagt de speler uit zijn vaardigheden te perfectioneren, wat hetzelfde noodzakelijke proces is waar leerlingen doorheen gaan wanneer zij iets leren."
# excel: "Games excel at rewarding" excel: "Games helpen bij de"
# struggle: "productive struggle" struggle: "productiviteit-strijd"
# kind_of_struggle: "the kind of struggle that results in learning thats engaging and" kind_of_struggle: "het soort worsteling dat uitmondt in een leerproces dat uitdagend is en "
# motivating: "motivating" motivating: "motiveert"
# not_tedious: "not tedious." not_tedious: "niet vervelend."
# gaming_is_good: "Studies suggest gaming is good for childrens brains. (its true!)" gaming_is_good: "Studies geven aan dat speels leren goed is voor de hersenen van kinderen. (En dat klopt!)"
# game_based: "When game-based learning systems are" game_based: "Wanneer spel-gebaseerde leersystemen worden"
# compared: "compared" compared: "vergeleken"
# conventional: "against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and" conventional: "met conventionele assessment methodes, is het verschil duidelijk: games zijn beter in het stimuleren van leerlingen bij het onthouden van kennis, concentratie en het"
# perform_at_higher_level: "perform at a higher level of achievement" perform_at_higher_level: "niveau van hun prestaties."
# feedback: "Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers." feedback: "Games verschaffen directe feedback, wat leerlingen in staat stelt hun oplossingen te verbeteren en zij een holistisch begrip van de concepten krijgen, in plaats van beperkt zijn tot antwoorden als “correct” of “incorrect”."
# real_game: "A real game, played with real coding." real_game: "Een echt spel, wat je speelt met echte programmeertaal."
# great_game: "A great game is more than just badges and achievements - its about a players journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence." great_game: "Een goed spel is meer dan alleen medailles en prestaties - het gaat om een reis, nauwkeurig ontworpen puzzels, en de mogelijkheid om uitdagingen vol zelfvertrouwen aan te pakken."
# agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code." agency: "CodeCombat is een game die de mogelijkheid en het zelfvertrouwen geeft om met echte code te werken, wat zowel beginners als gevorderde helpt bij het schrijven van goede, valide code."
# curious: "Curious? Request a demo and we'll show you the ropes" curious: "Nieuwsgierig? Vraag een demo aan en we laten je zien hoe het werkt."
# request_demo_title: "Get your students started today!" request_demo_title: "Laat je leerlingen vandaag nog starten!"
# request_demo_subtitle: "Request a demo and get your students started in less than an hour." request_demo_subtitle: "Vraag een demo aan en start binnen een uur met programmeerlessen."
# get_started_title: "Set up your class today" get_started_title: "Maak vandaag nog een klas aan!"
# get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science." get_started_subtitle: "Maak een klas aan, voeg je leerlingen toe, en monitor hun vooruitgang."
# create_class: "Or create a class and see it for yourself!" create_class: "Of maak een klas aan en ervaar het zelf!"
# teacher_screenshots_hint: "Students write code and see their changes update in real-time" teacher_screenshots_hint: "Kinderen schrijven code en zien hun code live uitgevoerd worden."
# request_demo: "Request a Demo" request_demo: "Vraag een demo aan"
# create_a_class: "Create a Class" create_a_class: "Maak een klas aan"
# setup_a_class: "Set Up a Class" setup_a_class: "Maak een klas aan"
# have_an_account: "Have an account?" have_an_account: "Heb je al een account?" # {change}
# logged_in_as: "You are currently logged in as" logged_in_as: "Je bent ingelogd als"
# view_my_classes: "View my classes" view_my_classes: "Bekijk mijn klassen"
# computer_science: "Computer science courses for all ages" computer_science: "Informatica lessen voor alle leeftijden"
# show_me_lesson_time: "Show me lesson time estimates for:" show_me_lesson_time: "Geef geschatte lesduur weer:"
# curriculum: "Total curriculum hours:" curriculum: "Totaal aantal lesuren:"
# ffa: "Free for all students" ffa: "Gratis voor alle leerlingen"
# lesson_time: "Lesson time:" lesson_time: "Lesduur:"
# coming_soon: "Coming soon!" coming_soon: "Binnenkort beschikbaar!"
# courses_available_in: "Courses are available in JavaScript, Python, and Java (coming soon!)" courses_available_in: "Lessen zijn beschikbaar in JavaScript, Python, en Java (Java is binnenkort beschikbaar!)"
# boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike." boast: "Uitdagende raadsels die zowel gamers als fanatieke programmeurs weten te prikkelen."
# winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable." winning: "Een gouden combinatie van spel-elementen en programmeerhuiswerk, dat samen zorgt voor kind-vriendelijk en oprecht aangenaam onderwijs."
# run_class: "Everything you need to run a computer science class in your school today, no CS background required." run_class: "Alles wat je nodig hebt om vandaag nog programmeerlessen in jouw klas te geven, geen voorkennis vereist."
# teachers: "Teachers!" teachers: "Docenten!"
# teachers_and_educators: "Teachers & Educators" teachers_and_educators: "Docenten & Mentoren"
# class_in_box: "Learn how our classroom-in-a-box platform fits into your curriculum." class_in_box: "Lees hoe ons digitale lesplatform in uw curriculum past."
# get_started: "Get Started" get_started: "Start nu"
# students: "Students:" students: "Leerlingen:"
# join_class: "Join Class" join_class: "Inschrijven bij Klas"
# role: "Your role:" role: "Uw rol:"
# student_count: "Number of students:" student_count: "Aantal leerlingen:"
# start_playing_for_free: "Start Playing for Free!" start_playing_for_free: "Begin met gratis spelen!"
# students_and_players: "Students & Players" students_and_players: "Leerlingen & Spelers"
# goto_classes: "Go to My Classes" goto_classes: "Ga naar mijn klassen"
# educator_wiki: "Educator wiki" educator_wiki: "Leraren wiki"
# view_profile: "View My Profile" view_profile: "Mijn Profiel"
# view_progress: "View Progress" view_progress: "Bekijk voortgang"
# check_out_wiki: "Check out our new educator Wiki" check_out_wiki: "Bekijk onze nieuwe leraren Wiki"
# want_coco: "Want CodeCombat at your school?" want_coco: "Wil je CodeCombat op jouw school?"
# form_select_role: "Select primary role" form_select_role: "Selecteer je rol"
# form_select_range: "Select class size" form_select_range: "Selecteer klassengrootte"
nav: nav:
play: "Levels" # The top nav bar entry where players choose which levels to play play: "Levels" # The top nav bar entry where players choose which levels to play
@ -113,28 +113,28 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
code: "Code" code: "Code"
home: "Home" home: "Home"
contribute: "Bijdragen" contribute: "Bijdragen"
legal: "Legaal" legal: "Juridisch"
about: "Over Ons" about: "Over Ons"
contact: "Contact" contact: "Contact"
twitter_follow: "Volgen" twitter_follow: "Volgen"
# students: "Students" students: "Leerlingen"
teachers: "Docenten" teachers: "Docenten"
careers: "Carrière" careers: "Banen"
# facebook: "Facebook" facebook: "Facebook"
# twitter: "Twitter" twitter: "Twitter"
# create_a_class: "Create a Class" create_a_class: "Maak een Klas"
# other: "Other" other: "Andere"
# learn_to_code: "Learn to Code!" learn_to_code: "Leer te programmeren!"
# toggle_nav: "Toggle navigation" toggle_nav: "Toggle navigatie"
# jobs: "Jobs" jobs: "Banen"
# schools: "Schools" schools: "Scholen"
# educator_wiki: "Educator Wiki" educator_wiki: "Leraren Wiki"
# get_involved: "Get Involved" get_involved: "Help Mee"
# open_source: "Open source (GitHub)" open_source: "Open source (GitHub)"
# support: "Support" support: "Hulp / ondersteuning"
# faqs: "FAQs" faqs: "FAQs"
# help_pref: "Need help? Email" help_pref: "Hulp nodig? E-mail ons"
# help_suff: "and we'll get in touch!" help_suff: "en we nemen contact op!"
modal: modal:
close: "Sluiten" close: "Sluiten"
@ -146,22 +146,22 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
diplomat_suggestion: diplomat_suggestion:
title: "Help CodeCombat vertalen!" # This shows up when a player switches to a non-English language using the language selector. title: "Help CodeCombat vertalen!" # This shows up when a player switches to a non-English language using the language selector.
sub_heading: "We hebben je taalvaardigheden nodig." sub_heading: "We hebben je taalvaardigheden nodig."
pitch_body: "We ontwikkelen CodeCombat in het Engels, maar we hebben al spelers van over de hele wereld. Veel van hen willen in het Nederlands spelen, maar kunnen geen Engels. Dus als je beiden spreekt, overweeg a.u.b. om je aan te melden als Diplomaat en help zowel de CodeCombat website als alle levels te vertalen naar het Nederlands." pitch_body: "We ontwikkelen CodeCombat in het Engels, maar we hebben al spelers van over de hele wereld. Veel van hen willen in het Nederlands spelen, maar kunnen geen Engels. Dus als je beiden talen spreekt, overweeg a.u.b. om je aan te melden als Diplomaat en help zowel de CodeCombat website als alle levels te vertalen naar het Nederlands."
missing_translations: "Totdat we alles hebben vertaald naar het Nederlands zul je Engels zien waar Nederlands niet beschikbaar is." missing_translations: "Totdat we alles hebben vertaald naar het Nederlands zul je Engels zien waar Nederlands niet beschikbaar is."
learn_more: "Meer informatie over het zijn van een Diplomaat" learn_more: "Meer informatie over het zijn van een Diplomaat"
subscribe_as_diplomat: "Abonneren als Diplomaat" subscribe_as_diplomat: "Abonneren als Diplomaat"
play: play:
play_as: "Speel als " # Ladder page play_as: "Speel als " # Ladder page
compete: "Strijd!" # Course details page compete: "Compleet!" # Course details page
spectate: "Toeschouwen" # Ladder page spectate: "Toeschouwen" # Ladder page
players: "Spelers" # Hover over a level on /play players: "Spelers" # Hover over a level on /play
hours_played: "Gespeelde uren" # Hover over a level on /play hours_played: "Speeltijd" # Hover over a level on /play
items: "Items" # Tooltip on item shop button from /play items: "Items" # Tooltip on item shop button from /play
unlock: "Ontsluit" # For purchasing items and heroes unlock: "Koop" # For purchasing items and heroes
confirm: "Bevestig" confirm: "Bevestigen"
owned: "In bezit" # For items you own owned: "In bezit" # For items you own
locked: "Gesloten" locked: "Vergrendeld"
purchasable: "Te koop" # For a hero you unlocked but haven't purchased purchasable: "Te koop" # For a hero you unlocked but haven't purchased
available: "Beschikbaar" available: "Beschikbaar"
skills_granted: "Verleende vaardigheden" # Property documentation details skills_granted: "Verleende vaardigheden" # Property documentation details
@ -171,46 +171,46 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
settings: "Instellingen" # Tooltip on settings button from /play settings: "Instellingen" # Tooltip on settings button from /play
poll: "Stemming" # Tooltip on poll button from /play poll: "Stemming" # Tooltip on poll button from /play
next: "Volgende" # Go from choose hero to choose inventory before playing a level next: "Volgende" # Go from choose hero to choose inventory before playing a level
change_hero: "Held wisselen" # Go back from choose inventory to choose hero change_hero: "Verander held" # Go back from choose inventory to choose hero
buy_gems: "Koop edelstenen" buy_gems: "Edelstenen kopen"
subscription_required: "Abbonement vereist" subscription_required: "Abonnement nodig"
anonymous: "Anonieme speler" anonymous: "Anonieme Speler"
level_difficulty: "Moeilijkheidsgraad: " level_difficulty: "Moeilijkheidsgraad: "
play_classroom_version: "Speel de klassikale versie" # Choose a level in campaign version that you also can play in one of your courses play_classroom_version: "Speel de klassikale versie" # Choose a level in campaign version that you also can play in one of your courses
campaign_beginner: "Beginnercampagne" campaign_beginner: "Beginnerscampagne"
awaiting_levels_adventurer_prefix: "Iedere week komen er nieuwe levels bij." awaiting_levels_adventurer_prefix: "We brengen 5 nieuwe levels per week uit." # {change}
awaiting_levels_adventurer: "Registreer je als Avonturier" awaiting_levels_adventurer: "Schrijf je in als Avonturier"
awaiting_levels_adventurer_suffix: "en wees de eerste om de nieuwe levels te spelen." awaiting_levels_adventurer_suffix: "om de eerste te zijn die nieuwe levels speelt."
adjust_volume: "Volume aanpassen" adjust_volume: "Volume aanpassen"
campaign_multiplayer: "Multiplayer Arena's" campaign_multiplayer: "Multiplayer Arena's"
campaign_multiplayer_description: "... waarin je direct tegen andere spelers speelt." campaign_multiplayer_description: "... waarin je direct tegen andere spelers speelt."
campaign_old_multiplayer: "(Afgekeurd) Oude multiplayer Arenas" campaign_old_multiplayer: "(Verouderde) Oude Multiplayer Arenas"
campaign_old_multiplayer_description: "Overblijfselen van een meer geciviliseerde tijd. Er zijn geen simulaties voor deze oudere, heldenloze multiplayer arenas." campaign_old_multiplayer_description: "Antieke overblijvselen van een beschaafder tijdperk. Deze oudere held-loze multiplayer arenas worden niet gesimuleert."
# code: code:
# if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.) if: "als" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
# else: "else" else: "anders"
# elif: "elif" elif: "anders als"
# while: "while" while: "wanneer"
# loop: "loop" loop: "lus"
# for: "for" for: "voor"
# break: "break" break: "breek"
# continue: "continue" continue: "doorgaan"
# then: "then" then: "daarna"
# do: "do" do: "doe"
# end: "end" end: "einde"
# function: "function" function: "functie"
# def: "def" def: "def"
# self: "self" self: "zelf"
# hero: "hero" hero: "held"
# this: "this" this: "dit"
share_progress_modal: share_progress_modal:
blurb: "Je gaat snel vooruit! Vertel aan je ouders hoeveel je met CodeCombat geleerd hebt." blurb: "Je gaat snel vooruit! Vertel aan je ouders hoeveel je geleerd hebt met CodeCombat."
email_invalid: "Emailaddress klopt niet." email_invalid: "Emailaddress klopt niet."
form_blurb: "Vul het emailadres van je ouders hieronder in en we zullen het ze laten zien!" form_blurb: "Vul het emailadres van je ouders hieronder in en we zullen het ze laten zien!"
form_label: "Emailaddress" form_label: "E-mailaddress"
placeholder: "emailaddress" placeholder: "e-mailaddress"
title: "Goed werk, leerling" title: "Goed werk, leerling"
login: login:
@ -236,15 +236,15 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
school_name: "Schoolnaam en stad" school_name: "Schoolnaam en stad"
optional: "optioneel" optional: "optioneel"
school_name_placeholder: "Sint-Jan Berchmanscollege, Brussel" school_name_placeholder: "Sint-Jan Berchmanscollege, Brussel"
# or_sign_up_with: "or sign up with" or_sign_up_with: "of log in met"
# connected_gplus_header: "You've successfully connected with Google+!" connected_gplus_header: "Je bent ingelogd met Google+!"
# connected_gplus_p: "Finish signing up so you can log in with your Google+ account." connected_gplus_p: "Maak je inschrijving compleet zodat je in kan loggen met je Google+ account."
# gplus_exists: "You already have an account associated with Google+!" gplus_exists: "Jouw Google+ account is al gekoppeld!"
# connected_facebook_header: "You've successfully connected with Facebook!" connected_facebook_header: "Je bent ingelogd met Facebook!"
# connected_facebook_p: "Finish signing up so you can log in with your Facebook account." connected_facebook_p: "Maak je inschrijving compleet zodat je in kan loggen met je Facebook account."
# facebook_exists: "You already have an account associated with Facebook!" facebook_exists: "Jouw Facebook account is al gekoppeld!"
# hey_students: "Students, enter the class code from your teacher." hey_students: "Leerlingen, voer hier de klassencode van je docent in."
# birthday: "Birthday" birthday: "Verjaardag"
recover: recover:
recover_account_title: "Herstel Account" recover_account_title: "Herstel Account"
@ -314,7 +314,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
subject: "Onderwerp" subject: "Onderwerp"
email: "Email" email: "Email"
password: "Wachtwoord" password: "Wachtwoord"
# confirm_password: "Confirm Password" confirm_password: "Bevestig wachtwoord"
message: "Bericht" message: "Bericht"
code: "Code" code: "Code"
ladder: "Ladder" ladder: "Ladder"
@ -333,9 +333,9 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
warrior: "Krijger" warrior: "Krijger"
ranger: "Boogschutter" ranger: "Boogschutter"
wizard: "Tovenaar" wizard: "Tovenaar"
# first_name: "First Name" first_name: "Voornaam"
# last_name: "Last Name" last_name: "Achternaam"
# username: "Username" username: "Gebruikersnaam"
units: units:
second: "seconde" second: "seconde"
@ -354,7 +354,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
years: "jaren" years: "jaren"
play_level: play_level:
# level_complete: "Level Complete" level_complete: "Level Voltooid"
completed_level: "Voltooid Level:" completed_level: "Voltooid Level:"
course: "Les:" course: "Les:"
done: "Klaar" done: "Klaar"

View file

@ -24,22 +24,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
im_a_teacher: "Sou um Professor" im_a_teacher: "Sou um Professor"
im_a_student: "Sou um Estudante" im_a_student: "Sou um Estudante"
learn_more: "Saber mais" learn_more: "Saber mais"
# classroom_in_a_box: "A classroom in-a-box for teaching computer science." classroom_in_a_box: "Uma sala de aula num pacote para ensinar ciência da computação."
# codecombat_is: "CodeCombat is a platform <strong>for students</strong> to learn computer science while playing through a real game." codecombat_is: "O CodeCombat é uma plataforma <strong>para estudantes</strong> para aprender ciência da computação enquanto se joga um jogo real."
# our_courses: "Our courses have been specifically playtested to <strong>excel in the classroom</strong>, even by teachers with little to no prior programming experience." our_courses: "Os nossos cursos foram especificamente testados para <strong>terem sucesso na sala de aula</strong>, até para professores com pouca ou nenhuma experiência anterior de programação."
top_screenshots_hint: "Os estudantes escrevem código e veem as alterações deles atualizarem em tempo real" top_screenshots_hint: "Os estudantes escrevem código e veem as alterações deles atualizarem em tempo real"
# designed_with: "Designed with teachers in mind" designed_with: "Desenhado a pensar nos professores"
# real_code: "Real, typed code" real_code: "Código real e escrito"
# from_the_first_level: "from the first level" from_the_first_level: "desde o primeiro nível"
# getting_students: "Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure." # getting_students: "Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure."
# educator_resources: "Educator resources" educator_resources: "Recursos para educadores"
# course_guides: "and course guides" course_guides: "e guias dos cursos"
# teaching_computer_science: "Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds." # teaching_computer_science: "Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds."
# accessible_to: "Accessible to" accessible_to: "Acessível a"
# everyone: "everyone" everyone: "todos"
# democratizing: "Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code." # democratizing: "Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code."
# forgot_learning: "I think they actually forgot that they were actually learning something." forgot_learning: "Acho que eles se esqueceram que estavam a aprender alguma coisa."
# wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school." wanted_to_do: "Programar é algo que sempre quis fazer e nunca pensei que poderia aprender isso na escola."
why_games: "Porque é que aprender através de jogos é importante?" why_games: "Porque é que aprender através de jogos é importante?"
# games_reward: "Games reward the productive struggle." # games_reward: "Games reward the productive struggle."
# encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn." # encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn."
@ -68,18 +68,18 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# create_a_class: "Create a Class" # create_a_class: "Create a Class"
# setup_a_class: "Set Up a Class" # setup_a_class: "Set Up a Class"
have_an_account: "Tens uma conta?" have_an_account: "Tens uma conta?"
# logged_in_as: "You are currently logged in as" logged_in_as: "Atualmente tens sessão iniciada como"
# view_my_classes: "View my classes" # view_my_classes: "View my classes"
# computer_science: "Computer science courses for all ages" computer_science: "Cursos de ciência da computação para todas as idades"
# show_me_lesson_time: "Show me lesson time estimates for:" show_me_lesson_time: "Mostrar estimativas do tempo de aula para:"
# curriculum: "Total curriculum hours:" curriculum: "Horas totais do currículo:"
# ffa: "Free for all students" ffa: "Grátis para todos os estudantes"
# lesson_time: "Lesson time:" lesson_time: "Tempo de aula:"
# coming_soon: "Coming soon!" coming_soon: "Brevemente!"
# courses_available_in: "Courses are available in JavaScript, Python, and Java (coming soon!)" courses_available_in: "Os cursos estão disponíveis em JavaScript, Python, e Java (brevemente!)"
# boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike." # boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
# winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable." # winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
# run_class: "Everything you need to run a computer science class in your school today, no CS background required." run_class: "Tudo o que precisas para teres uma turma de ciência da computação na tua escola hoje, sem serem necessárias bases de CC."
# teachers: "Teachers!" # teachers: "Teachers!"
# teachers_and_educators: "Teachers & Educators" # teachers_and_educators: "Teachers & Educators"
# class_in_box: "Learn how our classroom-in-a-box platform fits into your curriculum." # class_in_box: "Learn how our classroom-in-a-box platform fits into your curriculum."
@ -93,7 +93,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# goto_classes: "Go to My Classes" # goto_classes: "Go to My Classes"
# educator_wiki: "Educator wiki" # educator_wiki: "Educator wiki"
# view_profile: "View My Profile" # view_profile: "View My Profile"
# view_progress: "View Progress" view_progress: "Ver Progresso"
# check_out_wiki: "Check out our new educator Wiki" # check_out_wiki: "Check out our new educator Wiki"
# want_coco: "Want CodeCombat at your school?" # want_coco: "Want CodeCombat at your school?"
# form_select_role: "Select primary role" # form_select_role: "Select primary role"
@ -107,7 +107,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
blog: "Blog" blog: "Blog"
forum: "Fórum" forum: "Fórum"
account: "Conta" account: "Conta"
# my_account: "My Account" my_account: "A Minha Conta"
profile: "Perfil" profile: "Perfil"
stats: "Estatísticas" stats: "Estatísticas"
code: "Código" code: "Código"
@ -621,7 +621,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
clojure_blurb: "Um Lisp moderno." clojure_blurb: "Um Lisp moderno."
lua_blurb: "Linguagem para scripts de jogos." lua_blurb: "Linguagem para scripts de jogos."
io_blurb: "Simples mas obscuro." io_blurb: "Simples mas obscuro."
# java_blurb: "(Subscriber Only) Android and enterprise." java_blurb: "(Apenas para Subscritores) Android e empresas."
status: "Estado" status: "Estado"
hero_type: "Tipo" hero_type: "Tipo"
weapons: "Armas" weapons: "Armas"
@ -1159,9 +1159,9 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# last_level: "Last Level" # last_level: "Last Level"
# welcome_to_hoc: "Adventurers, welcome to our Hour of Code!" # welcome_to_hoc: "Adventurers, welcome to our Hour of Code!"
# logged_in_as: "Logged in as:" # logged_in_as: "Logged in as:"
# not_you: "Not you?" not_you: "Não és tu?"
# welcome_back: "Hi adventurer, welcome back!" # welcome_back: "Hi adventurer, welcome back!"
# continue_playing: "Continue Playing" continue_playing: "Continuar a Jogar"
# more_options: "More options:" # more_options: "More options:"
# option1_header: "Option 1: Invite students via email" # option1_header: "Option 1: Invite students via email"
# option1_body: "Students will automatically be sent an invitation to join this class, and will need to create an account with a username and password." # option1_body: "Students will automatically be sent an invitation to join this class, and will need to create an account with a username and password."

View file

@ -14,24 +14,24 @@ module.exports = nativeDescription: "Українська", englishDescription:
for_developers: "Для розробників" # Not currently shown on home page. for_developers: "Для розробників" # Not currently shown on home page.
or_ipad: "Або завантажте на iPad" or_ipad: "Або завантажте на iPad"
# new_home: new_home:
# slogan: "The most engaging game for learning programming." slogan: "Найбільш захоплююча гра для вивчення програмування." # The most engaging game for learning programming
# classroom_edition: "Classroom Edition:" classroom_edition: "Класна версія:" # Classroom Edition:
# learn_to_code: "Learn to code:" learn_to_code: "Вчитися кодувати:" # Learn to code:
# teacher: "Teacher" teacher: "Вчитель" # Teacher
# student: "Student" student: "Учень" # Student
# play_now: "Play Now" play_now: "Грати Зараз" # Play Now
# im_a_teacher: "I'm a Teacher" im_a_teacher: "Я Вчитель" # I'm a Teacher
# im_a_student: "I'm a Student" im_a_student: "Я Учень" # I'm a Student
# learn_more: "Learn more" learn_more: "Дізнатися більше" # Learn more
# classroom_in_a_box: "A classroom in-a-box for teaching computer science." # classroom_in_a_box: "A classroom in-a-box for teaching computer science."
# codecombat_is: "CodeCombat is a platform <strong>for students</strong> to learn computer science while playing through a real game." codecombat_is: "CodeCombat - це платформа <strong>для учнів</strong>, створена, щоб опановувати комп'ютерні науки під час захоплюючої гри." # CodeCombat is a platform <strong>for students</strong> to learn computer science while playing through a real game.
# our_courses: "Our courses have been specifically playtested to <strong>excel in the classroom</strong>, even by teachers with little to no prior programming experience." our_courses: "Наша програма була протестована, щоб бути <strong>ефективною в класі</strong>, навіть для вчителів з мінімальним досвідом програмування." # Our courses have been specifically playtested to <strong>excel in the classroom</strong>, even by teachers with little to no prior programming experience.
# top_screenshots_hint: "Students write code and see their changes update in real-time" top_screenshots_hint: "Учні пишуть код і бачать усі зміни наживо" # Students write code and see their changes update in real-time
# designed_with: "Designed with teachers in mind" designed_with: "Зважаючи на потреби вчителя" # Designed with teachers in mind
# real_code: "Real, typed code" real_code: "Кодування" # Real, typed code
# from_the_first_level: "from the first level" from_the_first_level: "з першого рівня" # from the first level
# getting_students: "Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure." getting_students: "Написання коду самостійно сприяє вивченню синтаксису та структури програми." # Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure.
# educator_resources: "Educator resources" # educator_resources: "Educator resources"
# course_guides: "and course guides" # course_guides: "and course guides"
# teaching_computer_science: "Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds." # teaching_computer_science: "Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds."

View file

@ -55,9 +55,9 @@ _.extend LevelSessionSchema.properties,
title: 'Changed' title: 'Changed'
readOnly: true readOnly: true
dateFirstCompleted: c.stringDate dateFirstCompleted: {} # c.stringDate
title: 'Completed' # title: 'Completed'
readOnly: true # readOnly: true
team: c.shortString() team: c.shortString()
level: LevelSessionLevelSchema level: LevelSessionLevelSchema

View file

@ -0,0 +1,23 @@
#admin-demo-requests-view
#site-content-area
width: 100%
td
max-width: 120px
overflow: hidden
.btn-deny
float: right
.status-cell
width: 120px
td.created
min-width: 90px
td.reviewed
min-width: 90px
th.number
max-width: 50px

View file

@ -1,2 +0,0 @@
#blog-view
@import "bootstrap/variables"

View file

@ -1,2 +0,0 @@
#contact-view
@import "bootstrap/variables"

View file

@ -60,10 +60,8 @@
a[disabled] .level a[disabled] .level
opacity: 0.7 opacity: 0.7
a.complete .level-difficulty:after a .level-difficulty .level-status-complete
content: " - Complete!"
color: $yellow color: $yellow
a.started .level-difficulty:after a .level-difficulty .level-status-started
content: " - Started"
color: desaturate($yellow, 50%) color: desaturate($yellow, 50%)

View file

@ -39,8 +39,17 @@ block content
li li
a(href="/admin/users") Users a(href="/admin/users") Users
h4 Other if me.isAdmin()
h4 Analytics
ul
li
a(href="/admin/analytics") Dashboard
li
a(href="/admin/analytics/subscriptions") Subscriptions
li
a(href="/admin/demo-requests") Teacher Demo Requests
h4 Other
ul ul
li li
a(href="/admin/base") Base (for debugging base.jade) a(href="/admin/base") Base (for debugging base.jade)
@ -48,15 +57,7 @@ block content
a(href="/admin/clas") CLAs a(href="/admin/clas") CLAs
li li
a(href="/admin/pending-patches") Patches a(href="/admin/pending-patches") Patches
if me.isAdmin()
li
a(href="/admin/analytics") Analytics
ul
li
a(href="/admin/analytics/subscriptions") Subscriptions
li
a(href="/admin/design-elements") Design Elements
if me.isAdmin() if me.isAdmin()
hr hr
h3 Prepaids h3 Prepaids

View file

@ -0,0 +1,54 @@
extends /templates/base
block content
if !me.isAdmin()
div You must be logged in as an admin to view this page.
else
h2 Teacher Demo Requests
if view.trialRequests.models.length < 1
h4 Fetching trial requests...
else
h3 Incoming Rate
table.table.table-condensed
thead
tr
th Day
th Count
th 7-day Average
tbody
each dayCount in view.dayCounts
tr
td= dayCount.day
td= dayCount.count
td= dayCount.sevenAverage
h3 Student Counts
table.table.table-condensed
thead
tr
th Created
th NCES District
th School Name
th.number NCES District Schools
th.number NCES District Students
th.number NCES School Students
th.number School Students
th.number Teacher Students
th Site Origin
tbody
each trialRequest in view.trialRequests.models
if trialRequest.get('type') !== 'course'
- continue;
tr
td.created= trialRequest.get('created').substring(0, 10)
td= trialRequest.get('properties').nces_district || ''
td= trialRequest.get('properties').organization || ''
td= trialRequest.get('properties').nces_district_schools || ''
td= trialRequest.get('properties').nces_district_students || ''
td= trialRequest.get('properties').nces_students || ''
td= trialRequest.get('properties').numStudentsTotal || ''
td= trialRequest.get('properties').numStudents || ''
td= trialRequest.get('properties').siteOrigin

View file

@ -80,7 +80,7 @@ mixin box
div div
if view.isTeacherWithDemo if view.isTeacherWithDemo
h6(data-i18n="new_home.check_out_wiki") h6(data-i18n="new_home.check_out_wiki")
a.btn.btn-primary.btn-lg.btn-block(href="https://sites.google.com/a/codecombat.com/teacher-guides/course-guides", data-i18n="new_home.educator_wiki") a.btn.btn-primary.btn-lg.btn-block(href="https://sites.google.com/a/codecombat.com/teacher-guides/course-guides", data-i18n="nav.educator_wiki")
else else
h6(data-i18n="new_home.want_coco") h6(data-i18n="new_home.want_coco")
a.btn.btn-primary.btn-lg.btn-block(href=view.demoRequestURL, data-i18n="new_home.get_started") a.btn.btn-primary.btn-lg.btn-block(href=view.demoRequestURL, data-i18n="new_home.get_started")
@ -267,7 +267,7 @@ block content
.teacher-screenshots .teacher-screenshots
.screenshots .screenshots
.hidden-sm.hidden-md.hidden-lg .hidden-sm.hidden-md.hidden-lg
small(data-i18n="new_home.teacher_screenshots_hint") small(data-i18n="new_home.top_screenshots_hint")
.screenshot-grid(title='Click to view full size') .screenshot-grid(title='Click to view full size')
a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='1') a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='1')
img(src="/images/pages/about/forest.png") img(src="/images/pages/about/forest.png")
@ -276,7 +276,7 @@ block content
a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='3') a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='3')
img(src="/images/pages/about/glacier.png") img(src="/images/pages/about/glacier.png")
.clearfix.hidden-xs .clearfix.hidden-xs
small(data-i18n="new_home.teacher_screenshots_hint") small(data-i18n="new_home.top_screenshots_hint")
if view.isTeacherWithDemo if view.isTeacherWithDemo
h4(data-i18n="new_home.get_started_subtitle") h4(data-i18n="new_home.get_started_subtitle")

View file

@ -23,5 +23,11 @@ block content
if playCount if playCount
span.spl.spr - #{playCount.sessions} span.spl.spr - #{playCount.sessions}
span(data-i18n="play.players") players span(data-i18n="play.players") players
if (view.levelStatusMap[level.id]=='complete')
span.spl.spr -
span(class="level-status-#{view.levelStatusMap[level.id]}", data-i18n="clans.complete_2")
else if (view.levelStatusMap[level.id]=='started')
span.spl.spr -
span(class="level-status-#{view.levelStatusMap[level.id]}", data-i18n="clans.started_2")
.play-text-container .play-text-container
.overlay-text.play-text(data-i18n="common.play") Play .overlay-text.play-text(data-i18n="common.play") Play

View file

@ -0,0 +1,36 @@
RootView = require 'views/core/RootView'
template = require 'templates/admin/demo-requests'
CocoCollection = require 'collections/CocoCollection'
TrialRequest = require 'models/TrialRequest'
module.exports = class DemoRequestsView extends RootView
id: 'admin-demo-requests-view'
template: template
constructor: (options) ->
super options
return unless me.isAdmin()
@trialRequests = new CocoCollection([], { url: '/db/trial.request?conditions[sort]="-created"&conditions[limit]=5000', model: TrialRequest })
@supermodel.loadCollection(@trialRequests, 'trial-requests', {cache: false})
@dayCounts = []
onLoaded: ->
return super() unless me.isAdmin()
dayCountMap = {}
for trialRequest in @trialRequests.models
day = trialRequest.get('created').substring(0, 10)
dayCountMap[day] ?= 0
dayCountMap[day]++
@dayCounts = []
for day, count of dayCountMap
@dayCounts.push(day: day, count: count)
@dayCounts.sort((a, b) -> b.day.localeCompare(a.day))
sevenCounts = []
for i in [@dayCounts.length - 1..0]
dayCount = @dayCounts[i]
sevenCounts.push(dayCount.count)
while sevenCounts.length > 7
sevenCounts.shift()
if sevenCounts.length is 7
dayCount.sevenAverage = Math.round(sevenCounts.reduce(((a, b) -> a + b), 0) / 7)
super()

View file

@ -146,10 +146,11 @@ module.exports = class CreateAccountModal extends ModalView
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'CodeCombat' window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'CodeCombat'
if @classCode if @classCode
url = "/courses?_cc="+@classCode url = "/courses?_cc="+@classCode
application.router.navigate(url) location.href = url
window.location.reload() else
window.location.reload()
# Google Plus # Google Plus
onClickGPlusSignupButton: -> onClickGPlusSignupButton: ->

View file

@ -69,7 +69,7 @@ module.exports = class CoursesView extends RootView
application.tracker?.trackEvent 'Started Student Login', category: 'Courses' application.tracker?.trackEvent 'Started Student Login', category: 'Courses'
openSignUpModal: -> openSignUpModal: ->
modal = new CreateAccountModal() modal = new CreateAccountModal({ initialValues: { classCode: utils.getQueryVariable('_cc', "") } })
@openModalView(modal) @openModalView(modal)
application.tracker?.trackEvent 'Started Student Signup', category: 'Courses' application.tracker?.trackEvent 'Started Student Signup', category: 'Courses'

View file

@ -1,3 +1,5 @@
utils = require 'core/utils'
RootView = require 'views/core/RootView' RootView = require 'views/core/RootView'
template = require 'templates/editor/verifier/verifier-view' template = require 'templates/editor/verifier/verifier-view'
VerifierTest = require './VerifierTest' VerifierTest = require './VerifierTest'
@ -52,14 +54,14 @@ module.exports = class VerifierView extends RootView
#testLevels = testLevels.slice 0, 15 #testLevels = testLevels.slice 0, 15
@linksQueryString = window.location.search @linksQueryString = window.location.search
@levelIDs = if @levelID then [@levelID] else testLevels @levelIDs = if @levelID then [@levelID] else testLevels
languages = utils.getQueryVariable 'languages', 'python,javascript'
#supermodel = if @levelID then @supermodel else undefined #supermodel = if @levelID then @supermodel else undefined
@tests = [] @tests = []
@taskList = [] @taskList = []
@tasksList = _.flatten _.map @levelIDs, (v) -> @tasksList = _.flatten _.map @levelIDs, (v) ->
# TODO: offer good interface for choosing which languages, better performance for skipping missing solutions # TODO: offer good interface for choosing which languages, better performance for skipping missing solutions
#_.map ['python', 'javascript', 'coffeescript', 'lua'], (l) -> #_.map ['python', 'javascript', 'coffeescript', 'lua'], (l) ->
_.map ['python', 'javascript'], (l) -> _.map languages.split(','), (l) ->
#_.map ['javascript'], (l) -> #_.map ['javascript'], (l) ->
level: v, language: l level: v, language: l

View file

@ -362,6 +362,15 @@ module.exports = class PlayLevelView extends RootView
onLevelStarted: -> onLevelStarted: ->
return unless @surface? return unless @surface?
#TODO: Remove this at some point
if @session.get('codeLanguage') in ['clojure', 'io']
problem =
aetherProblem:
message: "Sorry, support for #{@session.get('codeLanguage')} has been removed."
Backbone.Mediator.publish 'tome:show-problem-alert', problem: problem
@loadingView.showReady() @loadingView.showReady()
@trackLevelLoadEnd() @trackLevelLoadEnd()
if window.currentModal and not window.currentModal.destroyed and window.currentModal.constructor isnt VictoryModal if window.currentModal and not window.currentModal.destroyed and window.currentModal.constructor isnt VictoryModal

View file

@ -37,7 +37,7 @@ module.exports = class SpellTranslationView extends CocoView
@$el.show().css(@pos) @$el.show().css(@pos)
isIdentifier: (t) -> isIdentifier: (t) ->
t and (t.type in ['identifier', 'keyword'] or t.value is 'this') t and (_.any([/identifier/, /keyword/], (regex) -> regex.test(t.type)) or t.value is 'this')
onMouseMove: (e) => onMouseMove: (e) =>
return if @destroyed return if @destroyed

View file

@ -73,11 +73,11 @@ if cluster.isMaster
for i in [0...numCPUs] for i in [0...numCPUs]
cluster.fork() cluster.fork()
cluster.on 'exit', (worker, code, signal) -> cluster.on 'exit', (worker, code, signal) ->
message = "Worker #{worker.id} died! #{deaths[Math.floor Math.random() * deaths.length]}" message = "Worker #{worker.id} died!"
console.log message console.log message
try try
slack = require './server/slack' slack = require './server/slack'
slack.sendSlackMessage(message, ['ops'], {papertrail: true}) slack.sendSlackMessage(message, ['eng'], {papertrail: true})
catch error catch error
console.log "Couldn't send Slack message on server death:", error console.log "Couldn't send Slack message on server death:", error
cluster.fork() cluster.fork()

View file

@ -1,13 +1,13 @@
{ {
"verbose": "true", "verbose": "true",
"ignore": [], "ignore": [],
"events": { "events": {
}, },
"watch": [ "watch": [
"server_config.js", "server_config.js",
"server_setup.coffee", "server_setup.coffee",
"app/schemas", "app/schemas",
"./server" "./server"
], ],
"ext":"js coffee" "ext":"js coffee"
} }

View file

@ -36,7 +36,7 @@
"postinstall": "bower install && brunch build --env fast", "postinstall": "bower install && brunch build --env fast",
"brunch": "brunch", "brunch": "brunch",
"bower": "bower", "bower": "bower",
"dev": "brunch watch --server", "dev": "brunch watch --server --env fast",
"nodemon": "nodemon", "nodemon": "nodemon",
"jasmine-node": "jasmine-node", "jasmine-node": "jasmine-node",
"multicore": "coffee multicore.coffee", "multicore": "coffee multicore.coffee",
@ -93,7 +93,7 @@
"devDependencies": { "devDependencies": {
"after-brunch": "0.0.5", "after-brunch": "0.0.5",
"assetsmanager-brunch": "^1.8.1", "assetsmanager-brunch": "^1.8.1",
"auto-reload-brunch": "> 1.0 < 1.8", "auto-reload-brunch": "^1.8.1",
"bower": "~1.6.4", "bower": "~1.6.4",
"brunch": "^1.8.5", "brunch": "^1.8.5",
"coffee-script-brunch": "^1.8.3", "coffee-script-brunch": "^1.8.3",

View file

@ -0,0 +1,97 @@
// Find users that may have incorrect roles based on classroom ownership and membership
// Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// User buckets
// Classroom owner, no role
// Classroom owner, student role
// Classroom owner, teacher role (GOOD)
// Classroom member, no role
// Classroom member, teacher role
// Classroom member, student role (GOOD)
'use strict';
const scriptStartTime = new Date();
const classrooms = db.classrooms.find({}, {ownerID: 1, members: 1}).toArray();
print(classrooms.length, "classrooms");
const userIds = [];
const teacherIds = {};
const studentIds = {};
for (var classroom of classrooms) {
teacherIds[classroom.ownerID.valueOf()] = true;
userIds.push(classroom.ownerID);
for (var memberId of classroom.members) {
studentIds[memberId.valueOf()] = true;
userIds.push(memberId);
}
}
print(Object.keys(teacherIds).length, "users own classrooms");
print(Object.keys(studentIds).length, "users in classrooms");
print(Object.keys(teacherIds).length + Object.keys(studentIds).length, "total");
const users = db.users.find({$and: [{_id: {$in: userIds}}, {anonymous: false}]}, {email:1, role: 1}).toArray();
const studentOwnsClassroom = [];
const teacherInClassroom = [];
const individualInClassroom = [];
const individualOwnsClassroom = [];
for (var user of users) {
const userId = user._id.valueOf();
if (!user.email) {
printjson(user);
continue;
}
if (hasStudentRole(user.role)) {
if (teacherIds[userId]) {
studentOwnsClassroom.push(user.email);
}
}
else if (hasTeacherRole(user.role)) {
if (studentIds[userId]) {
teacherInClassroom.push(user.email);
}
}
else {
if (studentIds[userId]) {
individualInClassroom.push(user.email);
}
else if (teacherIds[userId]) {
individualOwnsClassroom.push(user.email);
}
else {
print("ERROR?", userId);
break;
}
}
}
print(studentOwnsClassroom.length, "Students own classroom:");
for (var email of studentOwnsClassroom) {
print(email);
}
print(teacherInClassroom.length, "Teachers in classroom:");
for (var email of teacherInClassroom) {
print(email);
}
print(individualInClassroom.length, "Individuals in classroom:");
for (var email of individualInClassroom) {
print(email);
}
print(individualOwnsClassroom.length, "Individuals own classroom:");
for (var email of individualOwnsClassroom) {
print(email);
}
print("Script runtime: " + (new Date() - scriptStartTime));
function hasTeacherRole(role) {
return ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent'].indexOf(role) >= 0;
}
function hasStudentRole(role) {
return ['student'].indexOf(role) >= 0;
}

View file

@ -11,6 +11,7 @@ if (process.argv.length !== 6) {
// TODO: 2nd follow up email activity does not handle paged activity results // TODO: 2nd follow up email activity does not handle paged activity results
// TODO: sendMail copied from updateCloseIoLeads.js // TODO: sendMail copied from updateCloseIoLeads.js
// TODO: template values copied from updateCloseIoLeads.js // TODO: template values copied from updateCloseIoLeads.js
// TODO: status change is not related to specific lead contacts
const createTeacherEmailTemplatesAuto1 = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G']; const createTeacherEmailTemplatesAuto1 = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G'];
const demoRequestEmailTemplatesAuto1 = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf']; const demoRequestEmailTemplatesAuto1 = ['tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm', 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf'];
@ -31,7 +32,9 @@ earliestDate.setUTCDate(earliestDate.getUTCDate() - 10);
// ** Main program // ** Main program
async.series([ async.series([
sendSecondFollowupMails sendSecondFollowupMails,
addCallTasks
// TODO: Cancel call tasks
], ],
(err, results) => { (err, results) => {
if (err) console.error(err); if (err) console.error(err);
@ -69,6 +72,14 @@ function isCreateTeacherTemplateAuto1(template) {
return createTeacherEmailTemplatesAuto1.indexOf(template) >= 0; return createTeacherEmailTemplatesAuto1.indexOf(template) >= 0;
} }
function isDemoRequestTemplateAuto2(template) {
return demoRequestEmailTemplatesAuto2.indexOf(template) >= 0;
}
function isCreateTeacherTemplateAuto2(template) {
return createTeacherEmailTemplatesAuto2.indexOf(template) >= 0;
}
function log(str) { function log(str) {
console.log(new Date().toISOString() + " " + str); console.log(new Date().toISOString() + " " + str);
} }
@ -325,7 +336,9 @@ function sendSecondFollowupMails(done) {
if (error) return done(error); if (error) return done(error);
try { try {
const results = JSON.parse(body); const results = JSON.parse(body);
console.log(`sendSecondFollowupMails total num leads ${results.total_results} has_more=${results.has_more}`); if (skip === 0) {
console.log(`sendSecondFollowupMails total num leads ${results.total_results} has_more=${results.has_more}`);
}
has_more = results.has_more; has_more = results.has_more;
const tasks = []; const tasks = [];
for (const lead of results.data) { for (const lead of results.data) {
@ -357,3 +370,227 @@ function sendSecondFollowupMails(done) {
nextPage(0); nextPage(0);
}); });
} }
function createAddCallTaskFn(userApiKeyMap, latestDate, lead, email) {
// Check for activity since second auto mail and status update
// Add call task
// TODO: Very similar function to createSendFollowupMailFn
const auto1Statuses = ["Auto Attempt 1", "New US Schools Auto Attempt 1"];
const auto2Statuses = ["Auto Attempt 2", "New US Schools Auto Attempt 2"];
return (done) => {
// console.log("DEBUG: addCallTask", lead.id);
// Skip leads with tasks
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/task/?lead_id=${lead.id}`;
request.get(url, (error, response, body) => {
if (error) {
console.log(error);
return done();
}
try {
const results = JSON.parse(body);
if (results.total_results > 0) {
// console.log(`DEBUG: ${lead.id} has ${results.total_results} tasks`);
return done();
}
}
catch (err) {
return done(err);
}
// Find all lead activities
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/activity/?lead_id=${lead.id}`;
request.get(url, (error, response, body) => {
if (error) {
console.log(error);
return done();
}
try {
const results = JSON.parse(body);
if (results.has_more) {
console.log(`ERROR: ${lead.id} has more activities than returned!`);
return done();
}
// Find second auto mail and status change
let sentSecondCreateTeacherEmail = false;
let sentSecondDemoRequestEmail = false;
let secondMailActivity;
let statusUpdateActivity;
let contactReplyMail;
for (const activity of results.data) {
if (activity._type === 'Email' && activity.to[0] === email) {
if (isCreateTeacherTemplateAuto2(activity.template_id)) {
if (sentSecondCreateTeacherEmail || sentSecondDemoRequestEmail) {
console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`);
return done();
}
sentSecondCreateTeacherEmail = true;
secondMailActivity = activity;
}
else if (isDemoRequestTemplateAuto2(activity.template_id)) {
if (sentSecondCreateTeacherEmail || sentSecondDemoRequestEmail) {
console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`);
return done();
}
sentSecondDemoRequestEmail = true;
secondMailActivity = activity;
}
}
else if (activity._type === 'LeadStatusChange' && auto1Statuses.indexOf(activity.old_status_label) >= 0
&& auto2Statuses.indexOf(activity.new_status_label) >= 0) {
statusUpdateActivity = activity;
}
}
if (!secondMailActivity) {
// console.log(`DEBUG: No auto2 mail sent for ${lead.id} ${email}`);
return done();
}
if (!statusUpdateActivity) {
console.log(`ERROR: No status update for ${lead.id} ${email}`);
return done();
}
if (new Date(secondMailActivity.date_created) > latestDate) {
// console.log(`DEBUG: Second auto mail too recent ${secondMailActivity.date_created} ${lead.id}`);
return done();
}
if (sentSecondCreateTeacherEmail && sentSecondDemoRequestEmail) {
console.log(`ERROR: ${lead.id} ${email} sent multiple auto2 emails!? ${sentSecondCreateTeacherEmail} ${sentSecondDemoRequestEmail}`);
return done();
}
// console.log(secondMailActivity);
// Find activity since second auto mail and status update
// Skip email to a different contact's email
// Skip note about different contact
let recentActivity;
for (const activity of results.data) {
if (activity.id === secondMailActivity.id) continue;
if (activity.id === statusUpdateActivity.id) continue;
if (new Date(secondMailActivity.date_created) > new Date(activity.date_created)) continue;
if (new Date(statusUpdateActivity.date_created) > new Date(activity.date_created)) continue;
if (activity._type === 'Note' && activity.note
&& activity.note.indexOf('demo_email') >= 0 && activity.note.indexOf(email) < 0) {
// console.log(`DEBUG: Skipping ${lead.id} ${email} auto import note for different contact`);
// console.log(activity.note);
continue;
}
recentActivity = activity;
break;
}
// Create call task
if (!recentActivity) {
console.log(`DEBUG: adding call task for ${lead.id} ${email}`);
const postData = {
_type: "lead",
lead_id: lead.id,
assigned_to: secondMailActivity.user_id,
text: `Call ${email}`,
is_complete: false
};
const options = {
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/task/`,
body: JSON.stringify(postData)
};
request.post(options, (error, response, body) => {
if (error) return done(error);
const result = JSON.parse(body);
if (result.errors || result['field-errors']) {
const errorMessage = `Create call task POST error for ${email} ${lead.id}`;
console.error(errorMessage);
// console.error(body);
// console.error(postData);
return done(errorMessage);
}
return done();
});
}
else {
// console.log(`DEBUG: Found recent activity after auto2 mail for ${lead.id} ${email}`);
// console.log(recentActivity);
return done();
}
}
catch (err) {
console.log(err);
console.log(body);
return done();
}
});
});
};
}
function addCallTasks(done) {
// Find all leads with auto 2 status, created since earliestDate
// TODO: Very similar function to sendSecondFollowupMails
// console.log("DEBUG: addCallTasks");
const userApiKeyMap = {};
let createGetUserFn = (apiKey) => {
return (done) => {
const url = `https://${apiKey}:X@app.close.io/api/v1/me/`;
request.get(url, (error, response, body) => {
if (error) return done();
const results = JSON.parse(body);
userApiKeyMap[results.id] = apiKey;
return done();
});
};
}
const tasks = [];
for (const apiKey of closeIoMailApiKeys) {
tasks.push(createGetUserFn(apiKey));
}
async.parallel(tasks, (err, results) => {
if (err) console.log(err);
const latestDate = new Date();
latestDate.setUTCDate(latestDate.getUTCDate() - 3);
const query = `date_created > ${earliestDate.toISOString().substring(0, 19)} (lead_status:"Auto Attempt 2" or lead_status:"New US Schools Auto Attempt 2")"`;
const limit = 100;
const nextPage = (skip) => {
let has_more = false;
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?_skip=${skip}&_limit=${limit}&query=${encodeURIComponent(query)}/`;
request.get(url, (error, response, body) => {
if (error) return done(error);
try {
const results = JSON.parse(body);
if (skip === 0) {
console.log(`addCallTasks total num leads ${results.total_results} has_more=${results.has_more}`);
}
has_more = results.has_more;
const tasks = [];
for (const lead of results.data) {
// console.log(`${lead.id}\t${lead.status_label}\t${lead.name}`);
// if (lead.id !== 'lead_foo') continue;
const existingContacts = lead.contacts || [];
for (const contact of existingContacts) {
if (contact.emails && contact.emails.length > 0) {
if (contact.phones && contact.phones.length > 0) {
tasks.push(createAddCallTaskFn(userApiKeyMap, latestDate, lead, contact.emails[0].email.toLowerCase()));
}
}
else {
console.log(`ERROR: lead ${lead.id} contact has non-1 emails`);
}
}
// if (tasks.length > 10) break;
}
async.series(tasks, (err, results) => {
if (err) return done(err);
if (has_more) {
return nextPage(skip + limit);
}
return done(err);
});
}
catch (err) {
return done(err);
}
});
};
nextPage(0);
});
}

View file

@ -14,7 +14,7 @@ if (process.argv.length !== 7) {
// TODO: Reduce response data via _fields param // TODO: Reduce response data via _fields param
// TODO: Assumes 1:1 contact:email relationship (Close.io supports multiple emails for a single contact) // TODO: Assumes 1:1 contact:email relationship (Close.io supports multiple emails for a single contact)
// Save as custom fields instead of user-specific lead notes // Save as custom fields instead of user-specific lead notes (also saving nces_ props)
const commonTrialProperties = ['organization', 'city', 'state', 'country']; const commonTrialProperties = ['organization', 'city', 'state', 'country'];
// Old properties which are deprecated or moved // Old properties which are deprecated or moved
@ -90,13 +90,14 @@ function upsertLeads(done) {
// ** Utilities // ** Utilities
function getInitialLeadStatusViaCountry(country) { function getInitialLeadStatusViaCountry(country, trialRequests) {
if (/usa|america|united states/ig.test(country)) { if (/usa|america|united states/ig.test(country)) {
return 'New US Schools Auto Attempt 1'; const status = 'New US Schools Auto Attempt 1'
return isLowValueLead(status, trialRequests) ? `${status} Low` : status;
} }
} }
function getInitialLeadStatusViaEmails(emails) { function getInitialLeadStatusViaEmails(emails, trialRequests) {
let currentStatus = null; let currentStatus = null;
let currentRank = closeIoInitialLeadStatuses.length; let currentRank = closeIoInitialLeadStatuses.length;
for (const email of emails) { for (const email of emails) {
@ -109,7 +110,25 @@ function getInitialLeadStatusViaEmails(emails) {
} }
} }
} }
return currentStatus ? currentStatus : closeIoInitialLeadStatuses[closeIoInitialLeadStatuses.length - 1].status; currentStatus = currentStatus ? currentStatus : closeIoInitialLeadStatuses[closeIoInitialLeadStatuses.length - 1].status;
return isLowValueLead(currentStatus, trialRequests) ? `${currentStatus} Low` : currentStatus;
}
function isLowValueLead(status, trialRequests) {
if (['Auto Attempt 1', 'New US Schools Auto Attempt 1'].indexOf(status) >= 0) {
for (const trialRequest of trialRequests) {
if (parseInt(trialRequest.properties.nces_district_students) < 5000) {
return true;
}
}
for (const trialRequest of trialRequests) {
// Must match these values: https://github.com/codecombat/codecombat/blob/master/app/templates/teachers/request-quote-view.jade#L159
if (['1-500', '500-1,000'].indexOf(trialRequest.properties.numStudentsTotal) >= 0) {
return true;
}
}
}
return false;
} }
function getRandomEmailApiKey() { function getRandomEmailApiKey() {
@ -246,6 +265,7 @@ class CocoLead {
this.contacts = {}; this.contacts = {};
this.custom = {}; this.custom = {};
this.name = name; this.name = name;
this.trialRequests = [];
} }
addClassroom(email, classroom) { addClassroom(email, classroom) {
if (!this.contacts[email.toLowerCase()]) this.contacts[email.toLowerCase()] = {}; if (!this.contacts[email.toLowerCase()]) this.contacts[email.toLowerCase()] = {};
@ -270,6 +290,7 @@ class CocoLead {
this.contacts[email.toLowerCase()].name = trial.properties.name; this.contacts[email.toLowerCase()].name = trial.properties.name;
} }
this.contacts[email.toLowerCase()].trial = trial; this.contacts[email.toLowerCase()].trial = trial;
this.trialRequests.push(trial);
} }
addUser(email, user) { addUser(email, user) {
this.contacts[email.toLowerCase()].user = user; this.contacts[email.toLowerCase()].user = user;
@ -278,11 +299,11 @@ class CocoLead {
for (const email in this.contacts) { for (const email in this.contacts) {
const props = this.contacts[email].trial.properties; const props = this.contacts[email].trial.properties;
if (props && props['country']) { if (props && props['country']) {
const status = getInitialLeadStatusViaCountry(props['country']); const status = getInitialLeadStatusViaCountry(props['country'], this.trialRequests);
if (status) return status; if (status) return status;
} }
} }
return getInitialLeadStatusViaEmails(Object.keys(this.contacts)); return getInitialLeadStatusViaEmails(Object.keys(this.contacts), this.trialRequests);
} }
getLeadPostData() { getLeadPostData() {
const postData = { const postData = {
@ -299,7 +320,7 @@ class CocoLead {
const props = this.contacts[email].trial.properties; const props = this.contacts[email].trial.properties;
if (props) { if (props) {
for (const prop in props) { for (const prop in props) {
if (commonTrialProperties.indexOf(prop) >= 0) { if (commonTrialProperties.indexOf(prop) >= 0 || /nces_/ig.test(prop)) {
postData.custom[`demo_${prop}`] = props[prop]; postData.custom[`demo_${prop}`] = props[prop];
} }
} }
@ -318,8 +339,17 @@ class CocoLead {
for (const email in this.contacts) { for (const email in this.contacts) {
const props = this.contacts[email].trial.properties; const props = this.contacts[email].trial.properties;
if (props) { if (props) {
let haveNcesData = false;
for (const prop in props) { for (const prop in props) {
if (commonTrialProperties.indexOf(prop) >= 0 && currentCustom[`demo_${prop}`] !== props[prop] && currentCustom[`demo_${prop}`].indexOf(props[prop]) < 0) { if (/nces_/ig.test(prop)) {
haveNcesData = true;
putData[`custom.demo_${prop}`] = props[prop];
}
}
for (const prop in props) {
// Always overwrite common props if we have NCES data, because other fields more likely to be accurate
if (commonTrialProperties.indexOf(prop) >= 0
&& (haveNcesData || currentCustom[`demo_${prop}`] !== props[prop] && currentCustom[`demo_${prop}`].indexOf(props[prop]) < 0)) {
putData[`custom.demo_${prop}`] = props[prop]; putData[`custom.demo_${prop}`] = props[prop];
} }
} }
@ -493,8 +523,8 @@ function updateExistingLead(lead, existingLead, done) {
} }
function saveNewLead(lead, done) { function saveNewLead(lead, done) {
// console.log('DEBUG: saveNewLead', lead.name);
const postData = lead.getLeadPostData(); const postData = lead.getLeadPostData();
// console.log(`DEBUG: saveNewLead ${lead.name} ${postData.status}`);
const options = { const options = {
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/`, uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/`,
body: JSON.stringify(postData) body: JSON.stringify(postData)

View file

@ -2,8 +2,7 @@
# Original content copyright (c) 2014 dpen2000 licensed under the MIT license # Original content copyright (c) 2014 dpen2000 licensed under the MIT license
# some defaults # some defaults
DISTRO="trusty" NODE_VERSION="5.x" # 0.10 | 0.12 | 4.x | 5.x | 6.x
NODE_VERSION="0.10" # 0.10 | 0.12 | 4.x | 5.x
# inform apt that there's no user to answer interactive questions # inform apt that there's no user to answer interactive questions
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
@ -17,42 +16,51 @@ sudo mv /tmp/limits.conf /etc/security/limits.conf
sudo chown root:root /etc/security/limits.conf sudo chown root:root /etc/security/limits.conf
# install prerequisites # install prerequisites
curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add - curl -sL https://deb.nodesource.com/setup_${NODE_VERSION} | sudo -E bash -
echo "deb https://deb.nodesource.com/node_${NODE_VERSION} ${DISTRO} main
deb-src https://deb.nodesource.com/node_${NODE_VERSION} ${DISTRO} main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
echo "updating apt sources..." echo "updating apt sources..."
sudo apt-get -qq update sudo apt-get update
echo "installing prerequisites..." echo "installing prerequisites..."
sudo apt-get -qqy install --no-install-recommends git g++ make curl wget sudo apt-get -y install --no-install-recommends build-essential git g++ make curl wget python2.7 dos2unix
# install node.js # install node.js
echo "installing node.js..." echo "installing node.js..."
sudo apt-get -qqy install nodejs sudo apt-get -y install nodejs
npm config set python `which python2.7`
echo "upgrading npm..." echo "upgrading npm..."
sudo npm install -g npm@latest # upgrade npm sudo npm install -g npm@latest # upgrade npm
sudo npm install -g geoip-lite
sudo npm install -g bower sudo npm install -g bower
sudo npm install -g brunch
sudo npm install -g geoip-lite
sudo npm install -g nodemon
# bind /vagrant/node_modules so that it does not leak through to the host file system # bind /vagrant/node_modules so that it does not leak through to the host file system
# which triggers symlink and path size issues on Windows hosts # which triggers symlink and path size issues on Windows hosts
mkdir -p /vagrant/node_modules mkdir -p /vagrant/node_modules
sudo mkdir -p /node_modules sudo mkdir -p /node_modules
sudo chown vagrant:vagrant /node_modules sudo chown -R vagrant:vagrant /node_modules
sudo mount --bind /node_modules /vagrant/node_modules sudo mount --bind /node_modules /vagrant/node_modules
cd /vagrant
# prepare
find /vagrant/app -type f -exec dos2unix {} \;
find /vagrant/vendor -type f -exec dos2unix {} \;
sudo chown -R vagrant:vagrant /home/vagrant
# install npm modules # install npm modules
echo "installing modules..." echo "installing modules..."
cd /vagrant
npm install npm install
bower install
# install mongo # install mongo
echo "installing mongodb..." echo "installing mongodb..."
sudo apt-get -qqy install --no-install-recommends mongodb-org sudo apt-get -y install --no-install-recommends mongodb-org
# start mongodb
sudo service mongod start
# populate mongo # populate mongo
echo "populating mongodb..." echo "populating mongodb..."

View file

@ -104,13 +104,15 @@ ClassroomHandler = class ClassroomHandler extends Handler
return @sendForbiddenError(res) unless classroom.get('ownerID').equals(req.user.get('_id')) return @sendForbiddenError(res) unless classroom.get('ownerID').equals(req.user.get('_id'))
for email in req.body.emails for email in req.body.emails
joinCode = (classroom.get('codeCamel') or classroom.get('code'))
context = context =
email_id: sendwithus.templates.course_invite_email email_id: sendwithus.templates.course_invite_email
recipient: recipient:
address: email address: email
email_data: email_data:
class_name: classroom.get('name') class_name: classroom.get('name')
join_link: "https://codecombat.com/courses?_cc=" + (classroom.get('codeCamel') or classroom.get('code')) join_link: "https://codecombat.com/courses?_cc=" + joinCode
join_code: joinCode
sendwithus.api.send context, _.noop sendwithus.api.send context, _.noop
return @sendSuccess(res, {}) return @sendSuccess(res, {})

View file

@ -401,7 +401,7 @@ UserHandler = class UserHandler extends Handler
name: sponsor.get('name') name: sponsor.get('name')
# Get recipient subscription info # Get recipient subscription info
findStripeSubscription sponsor.get('stripe').customerID, userID: req.user.id, (subscription) => findStripeSubscription sponsor.get('stripe')?.customerID, userID: req.user.id, (subscription) =>
info.subscription = subscription info.subscription = subscription
@sendDatabaseError(res, 'No sponsored subscription found') unless info.subscription? @sendDatabaseError(res, 'No sponsored subscription found') unless info.subscription?
@sendSuccess(res, info) @sendSuccess(res, info)

View file

@ -27,19 +27,24 @@ createMailContext = (req, done) ->
level = if user?.get('points') > 0 then Math.floor(5 * Math.log((1 / 100) * (user.get('points') + 100))) + 1 else 0 level = if user?.get('points') > 0 then Math.floor(5 * Math.log((1 / 100) * (user.get('points') + 100))) + 1 else 0
premium = user?.isPremium() premium = user?.isPremium()
teacher = user?.isTeacher()
content = """ content = """
#{message} #{message}
-- --
<a href='http://codecombat.com/user/#{user.get('slug') or user.get('_id')}'>#{user.get('name') or 'Anonymous'}</a> - Level #{level}#{if premium then ' - Subscriber' else ''}#{if country then ' - ' + country else ''} <a href='http://codecombat.com/user/#{user.get('slug') or user.get('_id')}'>#{user.get('name') or 'Anonymous'}</a> - Level #{level}#{if teacher then ' - Teacher' else ''}#{if premium then ' - Subscriber' else ''}#{if country then ' - ' + country else ''}
""" """
if req.body.browser if req.body.browser
content += "\n#{req.body.browser} - #{req.body.screenSize}" content += "\n#{req.body.browser} - #{req.body.screenSize}"
address = switch
when teacher then config.mail.supportSchools
when premium then config.mail.supportPremium
else config.mail.supportPrimary
context = context =
email_id: sendwithus.templates.plain_text_email email_id: sendwithus.templates.plain_text_email
recipient: recipient:
address: if premium then config.mail.supportPremium else config.mail.supportPrimary address: address
sender: sender:
address: config.mail.username address: config.mail.username
reply_to: sender or user.get('email') reply_to: sender or user.get('email')

View file

@ -27,7 +27,7 @@ module.exports.templates =
generic_email: 'tem_JhRnQ4pvTS4KdQjYoZdbei' generic_email: 'tem_JhRnQ4pvTS4KdQjYoZdbei'
plain_text_email: 'tem_85UvKDCCNPXsFckERTig6Y' plain_text_email: 'tem_85UvKDCCNPXsFckERTig6Y'
next_steps_email: 'tem_RDHhTG5inXQi8pthyqWr5D' next_steps_email: 'tem_RDHhTG5inXQi8pthyqWr5D'
course_invite_email: 'tem_u6D2EFWYC5Ptk38bSykjsU' course_invite_email: 'tem_f5K7BXX5vQ9a7kwYTACbJa'
teacher_free_trial: 'tem_R7d9Hpoba9SceQNiYSXBak' teacher_free_trial: 'tem_R7d9Hpoba9SceQNiYSXBak'
teacher_free_trial_hoc: 'tem_4ZSY9wsA9Qwn4wBFmZgPdc' teacher_free_trial_hoc: 'tem_4ZSY9wsA9Qwn4wBFmZgPdc'
teacher_request_demo: 'tem_cwG3HZjEyb6QE493hZuUra' teacher_request_demo: 'tem_cwG3HZjEyb6QE493hZuUra'

View file

@ -55,6 +55,7 @@ config.mail =
username: process.env.COCO_MAIL_SERVICE_USERNAME or '' username: process.env.COCO_MAIL_SERVICE_USERNAME or ''
supportPrimary: process.env.COCO_MAIL_SUPPORT_PRIMARY or '' supportPrimary: process.env.COCO_MAIL_SUPPORT_PRIMARY or ''
supportPremium: process.env.COCO_MAIL_SUPPORT_PREMIUM or '' supportPremium: process.env.COCO_MAIL_SUPPORT_PREMIUM or ''
supportSchools: process.env.COCO_MAIL_SUPPORT_SCHOOLS or ''
mailchimpAPIKey: process.env.COCO_MAILCHIMP_API_KEY or '' mailchimpAPIKey: process.env.COCO_MAILCHIMP_API_KEY or ''
mailchimpWebhook: process.env.COCO_MAILCHIMP_WEBHOOK or '/mail/webhook' mailchimpWebhook: process.env.COCO_MAILCHIMP_WEBHOOK or '/mail/webhook'
sendwithusAPIKey: process.env.COCO_SENDWITHUS_API_KEY or '' sendwithusAPIKey: process.env.COCO_SENDWITHUS_API_KEY or ''

View file

@ -1,4 +1,4 @@
Problem = require 'views/play/level/tome/Problem' Problem = require 'views/play/level/tome/Problem'
describe 'Problem', -> describe 'Problem', ->
# boilerplate problem params # boilerplate problem params

View file

@ -1,236 +1,236 @@
var Spade = function Spade() { var Spade = function Spade() {
this.stack = []; this.stack = [];
} }
Spade.prototype = { Spade.prototype = {
track: function(_elem) { track: function(_elem) {
this.target = _elem; this.target = _elem;
var spade = this; var spade = this;
var el = document.createElement("div"); var el = document.createElement("div");
keyHook = null; keyHook = null;
if(_elem.textInput && _elem.textInput.getElement) { if(_elem.textInput && _elem.textInput.getElement) {
keyHook = _elem.textInput.getElement(); keyHook = _elem.textInput.getElement();
} else { } else {
keyHook = _elem; keyHook = _elem;
} }
keyHook.addEventListener("keydown", function(_event) {spade.createEvent(spade.target)}); keyHook.addEventListener("keydown", function(_event) {spade.createEvent(spade.target)});
//Maybe this is needed depending on Firefox/other browsers? Duplicate non-diff events get compiled down. //Maybe this is needed depending on Firefox/other browsers? Duplicate non-diff events get compiled down.
keyHook.addEventListener("keyup", function(_event) {spade.createEvent(spade.target)}); keyHook.addEventListener("keyup", function(_event) {spade.createEvent(spade.target)});
_elem.addEventListener("mouseup", function(_event) {spade.createEvent(spade.target)}); _elem.addEventListener("mouseup", function(_event) {spade.createEvent(spade.target)});
}, },
createEvent: function(_target) { createEvent: function(_target) {
if(_target.getValue) { if(_target.getValue) {
this.stack.push({ this.stack.push({
"startPos":_target.selection.getCursor(), "startPos":_target.selection.getCursor(),
"endPos":_target.selection.getSelectionAnchor(), "endPos":_target.selection.getSelectionAnchor(),
"content":_target.getValue(), "content":_target.getValue(),
"timestamp":(new Date()).getTime() "timestamp":(new Date()).getTime()
}); });
} else { } else {
this.stack.push({ this.stack.push({
"startPos":_target.selectionStart, "startPos":_target.selectionStart,
"endPos":_target.selectionEnd, "endPos":_target.selectionEnd,
"content":_target.value, "content":_target.value,
"timestamp":(new Date()).getTime() "timestamp":(new Date()).getTime()
}); });
} }
}, },
compile: function() { compile: function() {
var compiledStack = []; var compiledStack = [];
if(this.stack.length > 0) { if(this.stack.length > 0) {
var startTime = this.stack[0].timestamp; var startTime = this.stack[0].timestamp;
var sum = 0; var sum = 0;
var sum2 = 0; var sum2 = 0;
for(var i = 0; i < this.stack.length; i++) { for(var i = 0; i < this.stack.length; i++) {
var c = this.stack[i]; var c = this.stack[i];
var adjustedTimestamp = c.timestamp - startTime; var adjustedTimestamp = c.timestamp - startTime;
var tString = ""; //The changed string. var tString = ""; //The changed string.
var fIndex = null; //The first index of changes. var fIndex = null; //The first index of changes.
var eIndex = null; //The last index of changes. var eIndex = null; //The last index of changes.
var dCount = 0; //Amount of character changes. var dCount = 0; //Amount of character changes.
if(i >= 1) { if(i >= 1) {
var p = this.stack[i - 1]; var p = this.stack[i - 1];
var isOkay = false; var isOkay = false;
for(var key in p) { for(var key in p) {
if(key != "timestamp") { if(key != "timestamp") {
if(typeof p[key] === "string") { if(typeof p[key] === "string") {
if(p[key] !== c[key]) { if(p[key] !== c[key]) {
isOkay = true; isOkay = true;
} }
} else { } else {
for(var key2 in p[key]) { for(var key2 in p[key]) {
if(c[key][key2] !== undefined) { if(c[key][key2] !== undefined) {
if(p[key][key2] !== c[key][key2]) { if(p[key][key2] !== c[key][key2]) {
isOkay = true; isOkay = true;
} }
} else { } else {
console.warn("Warning: c[key][key2] doesn't exist, but p[key][key2] does."); console.warn("Warning: c[key][key2] doesn't exist, but p[key][key2] does.");
isOkay = true; isOkay = true;
} }
} }
} }
} }
} }
if(!isOkay) { if(!isOkay) {
sum2++; sum2++;
continue; continue;
} }
sum++; sum++;
if(p.content != c.content) { if(p.content != c.content) {
//Check from the start to the end, which characters are different. //Check from the start to the end, which characters are different.
for(var j = 0; j < Math.max(p.content.length, c.content.length); j++) { for(var j = 0; j < Math.max(p.content.length, c.content.length); j++) {
if(p.content.charAt(j) === c.content.charAt(j)) { if(p.content.charAt(j) === c.content.charAt(j)) {
if(fIndex != null) { if(fIndex != null) {
tString += c.content.charAt(j); tString += c.content.charAt(j);
dCount++; dCount++;
} }
} else { } else {
tString += c.content.charAt(j); tString += c.content.charAt(j);
if(fIndex === null) { if(fIndex === null) {
fIndex = j; fIndex = j;
} }
dCount++; dCount++;
} }
} }
//Check from the end to the start, which characters are different. //Check from the end to the start, which characters are different.
for(var j = 0; j < Math.min(p.content.length, c.content.length) - fIndex; j++) { for(var j = 0; j < Math.min(p.content.length, c.content.length) - fIndex; j++) {
if(p.content.charAt(p.content.length - 1 - j) !== c.content.charAt(c.content.length - 1 - j)) { if(p.content.charAt(p.content.length - 1 - j) !== c.content.charAt(c.content.length - 1 - j)) {
if(eIndex == null) { if(eIndex == null) {
eIndex = j; eIndex = j;
break; break;
} }
} }
} }
//This accounts for the fact when changing from "aa" to "aaa" (for example). //This accounts for the fact when changing from "aa" to "aaa" (for example).
if(eIndex === null) { if(eIndex === null) {
eIndex = Math.min(p.content.length, c.content.length) - fIndex; eIndex = Math.min(p.content.length, c.content.length) - fIndex;
} }
tString = tString.substring(0, tString.length - eIndex); tString = tString.substring(0, tString.length - eIndex);
} }
} else { } else {
tString = c.content; tString = c.content;
fIndex = 0; fIndex = 0;
eIndex = tString.length; eIndex = tString.length;
} }
compiledStack.push({ compiledStack.push({
"timestamp":adjustedTimestamp, "timestamp":adjustedTimestamp,
"difContent":tString, "difContent":tString,
"difFIndex":fIndex, "difFIndex":fIndex,
"difEIndex":eIndex, "difEIndex":eIndex,
"selFIndex":c.startPos, "selFIndex":c.startPos,
"selEIndex":c.endPos "selEIndex":c.endPos
}); });
} }
} else { } else {
//Just return the empty array. //Just return the empty array.
} }
return compiledStack; return compiledStack;
}, },
play: function(_stack, _elem) { play: function(_stack, _elem) {
if(_stack.length === 0) { if(_stack.length === 0) {
console.warn("SPADE: No events to play.") console.warn("SPADE: No events to play.")
return return
} }
if(_elem.setValue) { if(_elem.setValue) {
_elem.setValue(_stack[0].difContent); _elem.setValue(_stack[0].difContent);
} else { } else {
_elem.value = _stack[0].difContent _elem.value = _stack[0].difContent
} }
_stack = _stack.slice(); _stack = _stack.slice();
_stack.shift(); _stack.shift();
var curTime, dTime; var curTime, dTime;
var elapsedTime = 0; var elapsedTime = 0;
var prevTime = (new Date()).getTime(); var prevTime = (new Date()).getTime();
var playbackInterval = setInterval(function() { var playbackInterval = setInterval(function() {
curTime = (new Date()).getTime(); curTime = (new Date()).getTime();
dTime = curTime - prevTime; dTime = curTime - prevTime;
dTime *= 1; //Multiply for faster/slower playback speeds. dTime *= 1; //Multiply for faster/slower playback speeds.
elapsedTime += dTime; elapsedTime += dTime;
var tArray = _stack.filter(function(_event) { var tArray = _stack.filter(function(_event) {
return ((_event.timestamp) >= (elapsedTime - dTime)) && ((_event.timestamp) < (elapsedTime)); return ((_event.timestamp) >= (elapsedTime - dTime)) && ((_event.timestamp) < (elapsedTime));
}); });
for(var i = 0; i < tArray.length; i++) { for(var i = 0; i < tArray.length; i++) {
var tEvent = tArray[i]; var tEvent = tArray[i];
var oVal = null; var oVal = null;
if(_elem.getValue) { if(_elem.getValue) {
oVal = _elem.getValue(); oVal = _elem.getValue();
} else { } else {
oVal = _elem.value; oVal = _elem.value;
} }
if(tEvent.difFIndex !== null && tEvent.difEIndex !== null) { if(tEvent.difFIndex !== null && tEvent.difEIndex !== null) {
if(_elem.setValue) { if(_elem.setValue) {
_elem.setValue(oVal.substring(0, tEvent.difFIndex) + tEvent.difContent + oVal.substring(oVal.length - tEvent.difEIndex, oVal.length)); _elem.setValue(oVal.substring(0, tEvent.difFIndex) + tEvent.difContent + oVal.substring(oVal.length - tEvent.difEIndex, oVal.length));
} else { } else {
_elem.value = oVal.substring(0, tEvent.difFIndex) + tEvent.difContent + oVal.substring(oVal.length - tEvent.difEIndex, oVal.length) _elem.value = oVal.substring(0, tEvent.difFIndex) + tEvent.difContent + oVal.substring(oVal.length - tEvent.difEIndex, oVal.length)
} }
} }
if(_elem.selection && _elem.selection.moveCursorToPosition) { if(_elem.selection && _elem.selection.moveCursorToPosition) {
//Maybe this will work someday //Maybe this will work someday
_elem.selection.moveCursorToPosition(tEvent.selFIndex); _elem.selection.moveCursorToPosition(tEvent.selFIndex);
_elem.selection.setSelectionAnchor(tEvent.selEIndex.row, tEvent.selEIndex.column); _elem.selection.setSelectionAnchor(tEvent.selEIndex.row, tEvent.selEIndex.column);
_elem.selection.selectTo(tEvent.selFIndex.row, tEvent.selFIndex.column); _elem.selection.selectTo(tEvent.selFIndex.row, tEvent.selFIndex.column);
} else { } else {
//Likewise //Likewise
_elem.focus(); _elem.focus();
_elem.setSelectionRange(tEvent.selFIndex, tEvent.selEIndex); _elem.setSelectionRange(tEvent.selFIndex, tEvent.selEIndex);
} }
} }
if(_stack[_stack.length - 1] === undefined || elapsedTime > _stack[_stack.length - 1].timestamp) { if(_stack[_stack.length - 1] === undefined || elapsedTime > _stack[_stack.length - 1].timestamp) {
clearInterval(playbackInterval); clearInterval(playbackInterval);
} }
prevTime = curTime; prevTime = curTime;
}, 10); }, 10);
}, },
debugPlay: function(_stack) { debugPlay: function(_stack) {
var area = document.createElement('textarea'); var area = document.createElement('textarea');
area.zIndex = 9999; area.zIndex = 9999;
area.style.width = "512px"; area.style.width = "512px";
area.style.height = "512px"; area.style.height = "512px";
area.style.position = "absolute"; area.style.position = "absolute";
area.style.left = "100px"; area.style.left = "100px";
area.style.top = "100px"; area.style.top = "100px";
document.body.appendChild(area); document.body.appendChild(area);
this.play(_stack, area); this.play(_stack, area);
}, },
condense: function(_stack) { condense: function(_stack) {
var compressedArray = []; var compressedArray = [];
for(var i = 0; i < _stack.length; i++) { for(var i = 0; i < _stack.length; i++) {
var u = _stack[i]; var u = _stack[i];
compressedArray.push([ compressedArray.push([
u.timestamp, u.timestamp,
u.difContent, u.difContent,
u.difFIndex, u.difFIndex,
u.difEIndex, u.difEIndex,
u.selFIndex.row, u.selFIndex.row,
u.selFIndex.column, u.selFIndex.column,
u.selEIndex.row, u.selEIndex.row,
u.selEIndex.column u.selEIndex.column
]); ]);
} }
return compressedArray; return compressedArray;
}, },
expand: function(_array) { expand: function(_array) {
var uncompressedArray = []; var uncompressedArray = [];
for(var i = 0 ; i < _array.length; i++) { for(var i = 0 ; i < _array.length; i++) {
var c = _array[i]; var c = _array[i];
uncompressedArray.push({ uncompressedArray.push({
"timestamp":c[0], "timestamp":c[0],
"difContent":c[1], "difContent":c[1],
"difFIndex":c[2], "difFIndex":c[2],
"difEIndex":c[3], "difEIndex":c[3],
"selFIndex":{ "selFIndex":{
"row":c[4], "row":c[4],
"column":c[5] "column":c[5]
}, },
"selEIndex":{ "selEIndex":{
"row":c[6], "row":c[6],
"column":c[7] "column":c[7]
}, },
}); });
} }
return uncompressedArray; return uncompressedArray;
} }
} }