mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-24 11:50:58 -04:00
Merge branch 'master' into courses-vhoc
This commit is contained in:
commit
c385aaa414
59 changed files with 1092 additions and 588 deletions
app
core/services
lib
locale
models
schemas/models
styles
templates
views
scripts
analytics/mongodb/queries
mongodb/migrations
server
test/server/functional
|
@ -1,15 +1,18 @@
|
|||
publishableKey = if application.isProduction() then 'pk_live_27jQZozjDGN1HSUTnSuM578g' else 'pk_test_zG5UwVu6Ww8YhtE9ZYh0JO6a'
|
||||
|
||||
module.exports = handler = StripeCheckout.configure({
|
||||
key: publishableKey
|
||||
name: 'CodeCombat'
|
||||
email: me.get('email')
|
||||
image: "https://codecombat.com/images/pages/base/logo_square_250.png"
|
||||
token: (token) ->
|
||||
console.log 'trigger?', handler.trigger
|
||||
handler.trigger 'received-token', { token: token }
|
||||
Backbone.Mediator.publish 'stripe:received-token', { token: token }
|
||||
locale: 'auto'
|
||||
})
|
||||
|
||||
if StripeCheckout?
|
||||
module.exports = handler = StripeCheckout.configure({
|
||||
key: publishableKey
|
||||
name: 'CodeCombat'
|
||||
email: me.get('email')
|
||||
image: "https://codecombat.com/images/pages/base/logo_square_250.png"
|
||||
token: (token) ->
|
||||
console.log 'trigger?', handler.trigger
|
||||
handler.trigger 'received-token', { token: token }
|
||||
Backbone.Mediator.publish 'stripe:received-token', { token: token }
|
||||
locale: 'auto'
|
||||
})
|
||||
else
|
||||
module.exports = {}
|
||||
console.error "Failure loading StripeCheckout API, returning empty object."
|
||||
_.extend(handler, Backbone.Events)
|
|
@ -123,6 +123,7 @@ module.exports = class LevelBus extends Bus
|
|||
onWinnabilityUpdated: (e) ->
|
||||
return unless @onPoint() and e.winnable
|
||||
return unless e.level.get('slug') in ['ace-of-coders'] # Mirror matches don't otherwise show victory, so we win here.
|
||||
return if @session.get('state')?.complete
|
||||
@onVictory()
|
||||
|
||||
onNewWorldCreated: (e) ->
|
||||
|
|
|
@ -38,7 +38,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
okay: "Okay"
|
||||
|
||||
not_found:
|
||||
page_not_found: "Tut uns leid ! Wir haben die Seite nicht gefunden"
|
||||
page_not_found: "Tut uns leid! Wir haben die Seite nicht gefunden"
|
||||
|
||||
diplomat_suggestion:
|
||||
title: "Hilf CodeCombat zu übersetzen!" # This shows up when a player switches to a non-English language using the language selector.
|
||||
|
@ -62,11 +62,11 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
available: "Verfügbar"
|
||||
skills_granted: "Verfügbare Fähigkeiten" # Property documentation details
|
||||
heroes: "Helden" # Tooltip on hero shop button from /play
|
||||
achievements: "Medaillen" # Tooltip on achievement list button from /play
|
||||
achievements: "Errungenschaften" # Tooltip on achievement list button from /play
|
||||
account: "Account" # Tooltip on account button from /play
|
||||
settings: "Einstellungen" # Tooltip on settings button from /play
|
||||
poll: "Umfrage" # Tooltip on poll button from /play
|
||||
next: "Nächster" # Go from choose hero to choose inventory before playing a level
|
||||
next: "Weiter" # Go from choose hero to choose inventory before playing a level
|
||||
change_hero: "Held wechseln" # Go back from choose inventory to choose hero
|
||||
choose_inventory: "Inventar"
|
||||
buy_gems: "Edelsteine kaufen"
|
||||
|
@ -138,7 +138,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
publish: "Veröffentlichen"
|
||||
create: "Erstellen"
|
||||
fork: "Kopieren"
|
||||
play: "Nächstes Level starten" # When used as an action verb, like "Play next level"
|
||||
play: "Spielen" # When used as an action verb, like "Play next level"
|
||||
retry: "Erneut versuchen"
|
||||
actions: "Aktionen"
|
||||
info: "Informationen"
|
||||
|
@ -263,10 +263,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
tome_minion_spells: "Die Zaubersprüche Deiner Knechte" # Only in old-style levels.
|
||||
tome_read_only_spells: "Nur-lesen Zaubersprüche" # Only in old-style levels.
|
||||
tome_other_units: "Andere Einheiten" # Only in old-style levels.
|
||||
tome_cast_button_run: "Zaubern"
|
||||
tome_cast_button_running: "Wird gezaubert"
|
||||
tome_cast_button_ran: "Wurde gezaubert"
|
||||
tome_submit_button: "Senden"
|
||||
tome_cast_button_run: "Ausführen"
|
||||
tome_cast_button_running: "Wird ausgeführt"
|
||||
tome_cast_button_ran: "Wurde ausgeführt"
|
||||
tome_submit_button: "Absenden"
|
||||
tome_reload_method: "Original Code für diese Methode neu laden" # Title text for individual method reload button.
|
||||
tome_select_method: "Methode auswählen"
|
||||
tome_see_all_methods: "Alle bearbeitbaren Methoden anzeigen" # Title text for method list selector (shown when there are multiple programmable methods).
|
||||
|
@ -281,11 +281,11 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
loading_ready: "Bereit!"
|
||||
loading_start: "Starte Level"
|
||||
problem_alert_title: "Repariere deinen Code"
|
||||
time_current: "Aktuell"
|
||||
time_total: "Total"
|
||||
time_goto: "Gehe zu"
|
||||
# non_user_code_problem_title: "Unable to Load Level"
|
||||
# infinite_loop_title: "Infinite Loop Detected"
|
||||
time_current: "Aktuell:"
|
||||
time_total: "Gesamt:"
|
||||
time_goto: "Gehe zu:"
|
||||
non_user_code_problem_title: "Level konnte nicht geladen werden"
|
||||
infinite_loop_title: "Unendliche Schleife entdeckt"
|
||||
# infinite_loop_description: "The initial code to build the world never finished running. It's probably either really slow or has an infinite loop. Or there might be a bug. You can either try running this code again or reset the code to the default state. If that doesn't fix it, please let us know."
|
||||
# check_dev_console: "You can also open the developer console to see what might be going wrong."
|
||||
# check_dev_console_link: "(instructions)"
|
||||
|
@ -361,16 +361,16 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
auth_caption: "Fortschritt speichern."
|
||||
|
||||
leaderboard:
|
||||
view_other_solutions: "Andere Lösungen" # {change}
|
||||
scores: "Punktzahl"
|
||||
top_players: "Die besten Spieler von"
|
||||
view_other_solutions: "Zur Bestenliste"
|
||||
scores: "Bestenliste"
|
||||
top_players: "Die besten Spieler nach"
|
||||
day: "Heute"
|
||||
week: "dieser Woche"
|
||||
week: "diese Woche"
|
||||
all: "insgesamt"
|
||||
time: "Zeit"
|
||||
damage_taken: "Erhaltener Schaden"
|
||||
damage_dealt: "Ausgeteilter Schaden"
|
||||
difficulty: "Schwierigkeit"
|
||||
damage_taken: "Schaden (ausgeteilt)"
|
||||
damage_dealt: "Schaden (erhalten)"
|
||||
difficulty: "Schwierigkeitsgrad"
|
||||
gold_collected: "Gold gesammelt"
|
||||
|
||||
inventory:
|
||||
|
@ -379,10 +379,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
required_purchase_title: "Benötigt"
|
||||
available_item: "Verfügbar"
|
||||
restricted_title: "Eingeschränkt"
|
||||
should_equip: "(Doppelklick zum Hinzufügen)"
|
||||
equipped: "(hinzugefügt)"
|
||||
should_equip: "(Doppelklicken zum Ausrüsten)"
|
||||
equipped: "(ausgerüstet)"
|
||||
locked: "(gesperrt)"
|
||||
restricted: "(benötigt für dieses Level)"
|
||||
restricted: "(In diesem Level nicht verfügbar)"
|
||||
equip: "Ausrüsten"
|
||||
unequip: "Ablegen"
|
||||
|
||||
|
@ -429,33 +429,33 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
parent_email_sent: "Email gesendet!"
|
||||
parent_email_title: "Wie lautet die Emailadresse deiner Eltern?"
|
||||
parents: "Für Eltern"
|
||||
parents_title: "Dein Kind lernt zu programmieren." # {change}
|
||||
parents_blurb1: "Mit CodeCombat, lernt dein Kind richtige Programme zu schreiben. Es fängt mit einfachen Befehlen an, und schreitet ganz unmerklich zu schwierigeren Themen fort."
|
||||
# parents_blurb1a: "Computer programming is an essential skill that your child will undoubtedly use as an adult. By 2020, basic software skills will be needed by 77% of jobs, and software engineers are in high demand across the world. Did you know that Computer Science is the highest-paid university degree?"
|
||||
parents_blurb2: "Für 9.99 im Monat, bekommt es jede Woche neue Herausforderungen sowie persönlichen Email Support von professionellen Programmierern." # {change}
|
||||
parents_title: "Liebe Eltern: Ihr kind erlernt das Programmieren. Wollen Sie es nicht dabei unterstützen?"
|
||||
parents_blurb1: "Ihr Kind hat __nLevels__ Level gemeistert und dabei Grundkenntnisse des Programmierens erlangt. Fördern Sie ihr Kind weiterhin, indem sie mit einem Abonnement weitere Herausforderungen freischalten."
|
||||
parents_blurb1a: "Das Programmieren entwickelt sich zunehmend zu einem Grundwerkzeug, dass in immer mehr Berufen benötigt wird und ohne Zweifel sich im Leben als eine sehr nützliche Fähigkeit erweisen wird."
|
||||
parents_blurb2: "Für 9.99 im Monat, bekommt Ihr Kind jede Woche neue Herausforderungen sowie persönlichen Email Support von erfahrenen Programmierern."
|
||||
parents_blurb3: "Kein Risiko: 100% Geld zurück Garantie, und 1-Klick Abokündigung."
|
||||
# payment_methods: "Payment Methods"
|
||||
# payment_methods_title: "Accepted Payment Methods"
|
||||
# payment_methods_blurb1: "We currently accept credit cards and Alipay."
|
||||
# payment_methods_blurb2: "If you require an alternate form of payment, please contact"
|
||||
# sale_already_subscribed: "You're already subscribed!"
|
||||
# sale_blurb1: "Save 35%"
|
||||
# sale_blurb2: "off regular subscription price of $120 for a whole year!"
|
||||
# sale_button: "Sale!"
|
||||
# sale_button_title: "Save 35% when you purchase a 1 year subscription"
|
||||
# sale_click_here: "Click Here"
|
||||
payment_methods: "Zahlungsarten"
|
||||
payment_methods_title: "Akzeptierte Zahlungsarten"
|
||||
payment_methods_blurb1: "Momentan akzeptieren wir nur Kreditkarten und Alipay."
|
||||
payment_methods_blurb2: "Wenn Sie auf eine andere Art zahlen wollen, bitte kontaktieren Sie"
|
||||
sale_already_subscribed: "Sie haben bereits ein Abonnement!"
|
||||
sale_blurb1: "Spare 35%"
|
||||
sale_blurb2: "vom regelmäßigen Abonnementpreis von $120 für ein ganzes Jahr!"
|
||||
sale_button: "Angebot!"
|
||||
sale_button_title: "Spare 35% beim Kauf eines Jahresabonnements"
|
||||
sale_click_here: "Hier klicken"
|
||||
# sale_ends: "Ends"
|
||||
# sale_extended: "*Existing subscriptions will be extended by 1 year."
|
||||
# sale_feature_here: "Here's what you'll get:"
|
||||
sale_extended: "*Bestehende Abonnements werden um 1 Jahr verlängert."
|
||||
sale_feature_here: "Dies ist im Abonnement enthalten:"
|
||||
# sale_feature2: "Access to 9 powerful <strong>new heroes</strong> with unique skills!"
|
||||
# sale_feature4: "<strong>42,000 bonus gems</strong> awarded immediately!"
|
||||
# sale_continue: "Ready to continue adventuring?"
|
||||
# sale_limited_time: "Limited time offer!"
|
||||
# sale_new_heroes: "New heroes!"
|
||||
# sale_title: "Back to School Sale"
|
||||
# sale_view_button: "Buy 1 year subscription for"
|
||||
sale_continue: "Bereit das Abenteuer fortzusetzen?"
|
||||
sale_limited_time: "Nur für beschränkte Zeit!"
|
||||
sale_new_heroes: "Neue Helden!"
|
||||
sale_title: "Angebot zum neuen Schuljahr"
|
||||
sale_view_button: "Kaufe ein Jahresabonnement für"
|
||||
stripe_description: "Monatsabo"
|
||||
# stripe_description_year_sale: "1 Year Subscription (35% discount)"
|
||||
stripe_description_year_sale: "Jahresabonnement (35% Angebot)"
|
||||
subscription_required_to_play: "Leider musst du ein Abo haben, um dieses Level spielen zu können."
|
||||
unlock_help_videos: "Abonniere, um alle Videoanleitungen freizuschalten."
|
||||
personal_sub: "Persönliches Abonnement" # Accounts Subscription View below
|
||||
|
@ -482,7 +482,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
users_subscribed: "Abonnement für Spieler übernommen:"
|
||||
no_users_subscribed: "Abonnement für keine Spieler übernommen, bitte prüfe deine E-Mail-Adressen."
|
||||
current_recipients: "Aktuelle Empfänger"
|
||||
unsubscribing: "Abmelden..." # {change}
|
||||
unsubscribing: "Abonnement wird gekündigt..."
|
||||
# subscribe_prepaid: "Click Subscribe to use prepaid code"
|
||||
# using_prepaid: "Using prepaid code for monthly subscription"
|
||||
|
||||
|
@ -501,9 +501,9 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
status: "Status"
|
||||
hero_type: "Typ"
|
||||
weapons: "Waffen"
|
||||
weapons_warrior: "Schwert - Kurze Reichweite, Keine Zauber"
|
||||
weapons_ranger: "Armbrust, Geschütz - Hohe Reichweite, Keine Zauber"
|
||||
weapons_wizard: "Stäbe, Stäbe - Lange Reichweite, Zauber"
|
||||
weapons_warrior: "Schwerter - Kurze Reichweite, Keine Zauber"
|
||||
weapons_ranger: "Schusswaffen - Hohe Reichweite, Keine Zauber"
|
||||
weapons_wizard: "Stäbe - Lange Reichweite, Zauber"
|
||||
attack: "Schaden" # Can also translate as "Attack"
|
||||
health: "Gesundheit"
|
||||
speed: "Geschwindigkeit"
|
||||
|
@ -518,7 +518,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
health_1: "Erhält"
|
||||
health_2: "der genannten"
|
||||
health_3: "Rüstungspunkte."
|
||||
speed_1: "Gehe zu"
|
||||
speed_1: "Bewegt sich mit"
|
||||
speed_2: "Meter pro Sekunde."
|
||||
available_for_purchase: "Zum Kauf verfügbar" # Shows up when you have unlocked, but not purchased, a hero in the hero store
|
||||
level_to_unlock: "Level zum Freischalten:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
|
||||
|
@ -607,26 +607,26 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
|
||||
teachers:
|
||||
more_info: "Info für Lehrer"
|
||||
# intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages."
|
||||
# intro_2: "No experience required!"
|
||||
# free_title: "How much does it cost?"
|
||||
intro_1: "CodeCombat ist ein Onlinespiel, dass Programmieren lehrt, indem es Schüler Code in gängigen Programmiersprachen schreiben lässt."
|
||||
intro_2: "Keine Vorkenntnisse nötig!"
|
||||
free_title: "Wie viel kostet es?"
|
||||
# cost_premium_server: "CodeCombat is free for the first five levels, after which it costs $9.99 USD per month for access to our other 190+ levels on our exclusive country-specific servers."
|
||||
# free_1: "There are 110+ FREE levels which cover every concept."
|
||||
# free_2: "A monthly subscription provides access to video tutorials and extra practice levels."
|
||||
# teacher_subs_title: "Teachers get free subscriptions!"
|
||||
# teacher_subs_0: "We offer free subscriptions to teachers for evaluation purposes."
|
||||
free_1: "Es gibt 110+ kostenlose Level, die alle Konzepte abedecken."
|
||||
free_2: "Ein monatliches Abonnement verschafft Zugang zu Video-Tutorien and zusätzlichen Übungsleveln."
|
||||
teacher_subs_title: "Für Lehrer ist das Abonnement kostenlos!"
|
||||
teacher_subs_0: "Wir bieten Lehrern ein kostenloses Abonnement zu evaluirungszwecken."
|
||||
# teacher_subs_1: "Please fill out our"
|
||||
# teacher_subs_2: "Teacher Survey"
|
||||
# teacher_subs_3: "to set up your subscription."
|
||||
# sub_includes_title: "What is included in the subscription?"
|
||||
sub_includes_title: "Was beinhaltet ein Abonnement?"
|
||||
# sub_includes_1: "In addition to the 110+ basic levels, students with a monthly subscription get access to these additional features:"
|
||||
# sub_includes_2: "80+ practice levels"
|
||||
# sub_includes_3: "Video tutorials"
|
||||
# sub_includes_4: "Premium email support"
|
||||
# sub_includes_5: "10 new heroes with unique skills to master"
|
||||
# sub_includes_6: "3500 bonus gems every month"
|
||||
# sub_includes_7: "Private Clans"
|
||||
# monitor_progress_title: "How do I monitor student progress?"
|
||||
sub_includes_2: "80+ Übungslevel"
|
||||
sub_includes_3: "Video-Tutorien"
|
||||
sub_includes_4: "Email-Support"
|
||||
sub_includes_5: "10 neue Helden zum Meistern"
|
||||
sub_includes_6: "3500 Juwelen jeden Monat"
|
||||
sub_includes_7: "Private Clans"
|
||||
monitor_progress_title: "Wie verfolge ich die Fortschritte der Schüler?"
|
||||
# monitor_progress_1: "Student progress can be monitored by creating a"
|
||||
# monitor_progress_2: "for your class."
|
||||
# monitor_progress_3: "To add a student, send them the invite link for your Clan, which is on the"
|
||||
|
@ -635,32 +635,32 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
# private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
|
||||
# private_clans_2: "To create a private Clan, check the 'Make clan private' checkbox when creating a"
|
||||
# private_clans_3: "."
|
||||
# who_for_title: "Who is CodeCombat for?"
|
||||
# who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed."
|
||||
# who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
|
||||
# material_title: "How much material is there?"
|
||||
# material_premium_server: "Approximately 50 hours of gameplay spread over 190+ subscriber-only levels so far."
|
||||
# material_1: "Approximately 25 hours of free content and an additional 15 hours of subscriber content."
|
||||
# concepts_title: "What concepts are covered?"
|
||||
# how_much_title: "How much does a monthly subscription cost?"
|
||||
who_for_title: "An wen richtet sich CodeCombat?"
|
||||
who_for_1: "Wir empfehlen CodeCombat Schülern im Alter von 9 Jahren und älter. Es werden keine Vorkenntnisse vorausgesetzt."
|
||||
who_for_2: "Wir haben CodeCombat so gestalltet, dass es sowohl Jungen als auch Mädchen ansprechend finden."
|
||||
material_title: "Wie viel Lernstoff gibt es?"
|
||||
material_premium_server: "Bislang gibt es ungefähr 50 Spielstunden verteilt über 190+ Level (mit Abonnement)."
|
||||
material_1: "Ungefähr 25 Spielstunden kostenlosen Inhalts und zusätzliche 15 Spielstunden die mit einem Abonnement freigeschaltet werden."
|
||||
concepts_title: "Welche Konzepte werde abgedeckt?"
|
||||
how_much_title: "Wie viel kostet ein monatliches Abonnement?"
|
||||
# how_much_1: "A"
|
||||
# how_much_2: "monthly subscription"
|
||||
# how_much_3: "costs $9.99, and can be cancelled anytime."
|
||||
# how_much_4: "Additionally, we provide discounts for larger groups:"
|
||||
# how_much_5: "We accept discounted one-time purchases and yearly subscription purchases for groups, such as a class or school. Please contact"
|
||||
# how_much_6: "for more details."
|
||||
# more_info_title: "Where can I find more information?"
|
||||
more_info_title: "Wo kann ich mehr Information finden?"
|
||||
# more_info_1: "Our"
|
||||
# more_info_2: "teachers forum"
|
||||
# more_info_3: "is a good place to connect with fellow educators who are using CodeCombat."
|
||||
sys_requirements_title: "System Voraussetzungen"
|
||||
# sys_requirements_1: "A modern web browser. Newer versions of Chrome, Firefox, or Safari. Internet Explorer 9 or later."
|
||||
sys_requirements_2: "Nutzen Sie die neuesten Versionen von Google Chrome oder Firefox." # {change}
|
||||
sys_requirements_1: "Ein aktueller Webbrowser. Neuere Versionen von Chrome, Firefox, oder Safari. Internet Explorer 9 oder aktueller."
|
||||
sys_requirements_2: "CodeCombat wird auf dem iPad noch nicht unterstützt." # {change}
|
||||
|
||||
# teachers_survey:
|
||||
# title: "Teacher Survey"
|
||||
# must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
|
||||
# retrieving: "Retrieving information..."
|
||||
teachers_survey:
|
||||
title: "Lehrerumfrage"
|
||||
must_be_logged: "Sie müssen sich zunächst einloggen. Bitte erstellen Sie ein Account oder loggen Sie sich im oberen Menü ein."
|
||||
retrieving: "Information abrufen..."
|
||||
# being_reviewed_1: "Your application for a free trial subscription is being"
|
||||
# being_reviewed_2: "reviewed."
|
||||
# approved_1: "Your application for a free trial subscription was"
|
||||
|
@ -720,21 +720,21 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
me_tab: "Ich"
|
||||
picture_tab: "Bild"
|
||||
delete_account_tab: "Account löschen"
|
||||
wrong_email: "Falsche Email Adresse"
|
||||
# wrong_password: "Wrong Password"
|
||||
wrong_email: "Die Emailadresse ist falsch"
|
||||
wrong_password: "Das Passwort ist falsch"
|
||||
upload_picture: "Ein Bild hochladen"
|
||||
delete_this_account: "Das Löschen deines Accounts kann nicht rückgängig gemacht werden!"
|
||||
# reset_progress_tab: "Reset All Progress"
|
||||
# reset_your_progress: "Clear all your progress and start over"
|
||||
delete_this_account: "Den Account unwiderruflich löschen!"
|
||||
reset_progress_tab: "Spielfortschritt zurücksetzen"
|
||||
reset_your_progress: "Gesamten Fortschritt zurücksetzen und Spiel von vorn beginnen"
|
||||
god_mode: "Gottmodus"
|
||||
password_tab: "Passwort"
|
||||
emails_tab: "Emails"
|
||||
admin: "Admin"
|
||||
new_password: "Neues Passwort"
|
||||
new_password_verify: "Passwort verifizieren"
|
||||
type_in_email: "Email eingeben, um Löschung zu bestätigen" # {change}
|
||||
# type_in_email_progress: "Type in your email to confirm deleting your progress."
|
||||
# type_in_password: "Also, type in your password."
|
||||
type_in_email: "Email eingeben um das Löschen des Accounts zu bestätigen"
|
||||
type_in_email_progress: "Gib zum Bestätigen Deine Email ein."
|
||||
type_in_password: "Gib Dein Passwort ebenfalls ein."
|
||||
email_subscriptions: "Email Abonnements"
|
||||
email_subscriptions_none: "Keine Email Abonnements."
|
||||
email_announcements: "Ankündigungen"
|
||||
|
@ -979,8 +979,8 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
indoor: "Indoor"
|
||||
desert: "Wüste"
|
||||
grassy: "Gräsern"
|
||||
# mountain: "Mountain"
|
||||
# glacier: "Glacier"
|
||||
mountain: "Berg"
|
||||
glacier: "Gletscher"
|
||||
small: "Klein"
|
||||
large: "Groß"
|
||||
fork_title: "Forke neue Version"
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
multiplayer: "Multiplayer" # Not currently shown on home page
|
||||
for_developers: "For Developers" # Not currently shown on home page.
|
||||
or_ipad: "Or download for iPad"
|
||||
hoc_class_code: "I Have a Class Code"
|
||||
hoc_enter: "Enter"
|
||||
hoc_title: "Hour of Code?"
|
||||
|
||||
nav:
|
||||
play: "Levels" # The top nav bar entry where players choose which levels to play
|
||||
|
@ -218,7 +221,10 @@
|
|||
years: "years"
|
||||
|
||||
play_level:
|
||||
completed_level: "Completed Level:"
|
||||
course: "Course:"
|
||||
done: "Done"
|
||||
next_level: "Next Level:"
|
||||
next_game: "Next game"
|
||||
show_menu: "Show game menu"
|
||||
home: "Home" # Not used any more, will be removed soon.
|
||||
|
@ -616,11 +622,15 @@
|
|||
cost_premium_server: "CodeCombat is free for the first five levels, after which it costs $9.99 USD per month for access to our other 190+ levels on our exclusive country-specific servers."
|
||||
free_1: "There are 110+ FREE levels which cover every concept."
|
||||
free_2: "A monthly subscription provides access to video tutorials and extra practice levels."
|
||||
teacher_subs_title: "Teachers get free subscriptions!"
|
||||
teacher_subs_0: "We offer free subscriptions to teachers for evaluation purposes."
|
||||
free_3: "The CodeCombat content is divided into"
|
||||
free_4: "courses"
|
||||
free_5: ". The first course is free, and about an hour of material."
|
||||
free_6: "Access to the additional courses can be unlocked with a one-time purchase."
|
||||
teacher_subs_title: "Teachers get a free trial!" # {change}
|
||||
teacher_subs_0: "We offer free trials to teachers." # {change}
|
||||
teacher_subs_1: "Please fill out our"
|
||||
teacher_subs_2: "Teacher Survey"
|
||||
teacher_subs_3: "to set up your subscription."
|
||||
teacher_subs_3: "to try out the paid courses." # {change}
|
||||
sub_includes_title: "What is included in the subscription?"
|
||||
sub_includes_1: "In addition to the 110+ basic levels, students with a monthly subscription get access to these additional features:"
|
||||
sub_includes_2: "80+ practice levels"
|
||||
|
@ -664,16 +674,20 @@
|
|||
title: "Teacher Survey"
|
||||
must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
|
||||
retrieving: "Retrieving information..."
|
||||
being_reviewed_1: "Your application for a free trial subscription is being"
|
||||
being_reviewed_1: "Your application for a free trial is being" # {change}
|
||||
being_reviewed_2: "reviewed."
|
||||
approved_1: "Your application for a free trial subscription was"
|
||||
approved_1: "Your application for a free trial was" # {change}
|
||||
approved_2: "approved."
|
||||
approved_3: "Further instructions have been sent to"
|
||||
denied_1: "Your application for a free trial subscription has been"
|
||||
approved_4: "Enroll your students on the"
|
||||
approved_5: "courses"
|
||||
approved_6: "page."
|
||||
denied_1: "Your application for a free trial has been" # {change}
|
||||
denied_2: "denied."
|
||||
contact_1: "Please contact"
|
||||
contact_2: "if you have further questions."
|
||||
description_1: "We offer free subscriptions to teachers for evaluation purposes. You can find more information on our"
|
||||
description_1: "We offer free trials to teachers. You will be given 2 free enrollments which can be used to enroll students in paid courses." # {change}
|
||||
description_1b: "You can find more information on our"
|
||||
description_2: "teachers"
|
||||
description_3: "page."
|
||||
description_4: "Please fill out this quick survey and we’ll email you setup instructions."
|
||||
|
|
|
@ -724,16 +724,16 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
wrong_password: "間違ったパスワード"
|
||||
upload_picture: "画像をアップロード"
|
||||
delete_this_account: "アカウントを完全削除する"
|
||||
# reset_progress_tab: "Reset All Progress"
|
||||
# reset_your_progress: "Clear all your progress and start over"
|
||||
reset_progress_tab: "すべての進捗をリセットする"
|
||||
reset_your_progress: "すべての進捗をリセットしやり直す"
|
||||
god_mode: "ゴッドモード"
|
||||
password_tab: "パスワード"
|
||||
emails_tab: "メール"
|
||||
admin: "管理者"
|
||||
new_password: "新パスワード"
|
||||
new_password_verify: "新パスワードを再入力"
|
||||
type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい"
|
||||
# type_in_email_progress: "Type in your email to confirm deleting your progress."
|
||||
type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい。"
|
||||
type_in_email_progress: "進捗を消すために、メールアドレスを入力してください。"
|
||||
type_in_password: "そして、パスワードを入力してください。"
|
||||
email_subscriptions: "ニュースレターの購読"
|
||||
email_subscriptions_none: "No Email Subscriptions."
|
||||
|
@ -1147,22 +1147,22 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
summary_wins: " 勝利数, "
|
||||
summary_losses: " 敗北数"
|
||||
rank_no_code: "新しいコードがランクにありません"
|
||||
# rank_my_game: "Rank My Game!"
|
||||
rank_my_game: "試合をランキングに送信!"
|
||||
rank_submitting: "送信中..."
|
||||
rank_submitted: "ランキングに送信されました。"
|
||||
# rank_failed: "Failed to Rank"
|
||||
# rank_being_ranked: "Game Being Ranked"
|
||||
rank_failed: "ランキングに送信できませんでした。"
|
||||
rank_being_ranked: "ランキングにのっています"
|
||||
# rank_last_submitted: "submitted "
|
||||
# help_simulate: "Help simulate games?"
|
||||
# code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
|
||||
# no_ranked_matches_pre: "No ranked matches for the "
|
||||
# no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
|
||||
# choose_opponent: "Choose an Opponent"
|
||||
choose_opponent: "相手を選んでください"
|
||||
select_your_language: "使う言語を選んでください!"
|
||||
tutorial_play: "チュートリアルで遊ぶ"
|
||||
tutorial_recommended: "はじめて遊ぶ人におすすめ"
|
||||
tutorial_skip: "チュートリアルをスキップする"
|
||||
# tutorial_not_sure: "Not sure what's going on?"
|
||||
tutorial_not_sure: "なにが起きているのかわかりませんか?"
|
||||
tutorial_play_first: "はじめからチュートリアルを遊ぶ"
|
||||
simple_ai: "単純なAI"
|
||||
warmup: "ウォームアップ"
|
||||
|
|
|
@ -31,7 +31,7 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
contact: "Kontakt"
|
||||
twitter_follow: "Subskrybuj"
|
||||
teachers: "Nauczyciele"
|
||||
# careers: "Careers"
|
||||
careers: "Kariera"
|
||||
|
||||
modal:
|
||||
close: "Zamknij"
|
||||
|
@ -159,9 +159,9 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
accepted: "Przyjęto"
|
||||
rejected: "Odrzucono"
|
||||
withdrawn: "Wycofano"
|
||||
# accept: "Accept"
|
||||
# reject: "Reject"
|
||||
# withdraw: "Withdraw"
|
||||
accept: "Akceptuj"
|
||||
reject: "Odrzuć"
|
||||
withdraw: "Wycofaj"
|
||||
submitter: "Przesyłający"
|
||||
submitted: "Przesłano"
|
||||
commit_msg: "Wiadomość do commitu"
|
||||
|
@ -217,8 +217,8 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
|
||||
play_level:
|
||||
done: "Zrobione"
|
||||
# next_game: "Next game"
|
||||
# show_menu: "Show game menu"
|
||||
next_game: "Następna gra"
|
||||
show_menu: "Pokaż menu gry"
|
||||
home: "Strona główna" # Not used any more, will be removed soon.
|
||||
level: "Poziom" # Like "Level: Dungeons of Kithgard"
|
||||
skip: "Pomiń"
|
||||
|
@ -527,8 +527,8 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
skill_docs:
|
||||
writable: "zapisywalny" # Hover over "attack" in Your Skills while playing a level to see most of this
|
||||
read_only: "tylko do odczytu"
|
||||
# action: "Action"
|
||||
# spell: "Spell"
|
||||
action: "Akcja"
|
||||
spell: "Zaklęcie"
|
||||
action_name: "nazwa"
|
||||
action_cooldown: "Zajmuje"
|
||||
action_specific_cooldown: "Odpoczynek"
|
||||
|
@ -859,9 +859,9 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
# track_concepts8: "to join"
|
||||
# private_require_sub: "Private clans require a subscription to create or join."
|
||||
|
||||
# courses:
|
||||
# course: "Course"
|
||||
# courses: "courses"
|
||||
courses:
|
||||
course: "Kurs"
|
||||
courses: "kursy"
|
||||
# not_enrolled: "You are not enrolled in this course."
|
||||
# visit_pref: "Please visit the"
|
||||
# visit_suf: "page to enroll."
|
||||
|
@ -872,8 +872,8 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
# edit_settings: "edit class settings"
|
||||
# edit_settings1: "Edit Class Settings"
|
||||
# progress: "Class Progress"
|
||||
# add_students: "Add Students"
|
||||
# stats: "Statistics"
|
||||
add_students: "Dodaj Studentów"
|
||||
stats: "Statystyki"
|
||||
# total_students: "Total students:"
|
||||
# average_time: "Average level play time:"
|
||||
# total_time: "Total play time:"
|
||||
|
@ -881,8 +881,8 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
# total_levels: "Total levels completed:"
|
||||
# furthest_level: "Furthest level completed:"
|
||||
# concepts_covered: "Concepts Covered"
|
||||
# students: "Students"
|
||||
# students1: "students"
|
||||
students: "Studenci"
|
||||
students1: "studenci"
|
||||
# expand_details: "Expand details"
|
||||
# concepts: "Concepts"
|
||||
# levels: "levels"
|
||||
|
@ -896,7 +896,7 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
# capacity_used: "Course slots used:"
|
||||
# enter_emails: "Enter student emails to invite, one per line"
|
||||
# send_invites: "Send Invites"
|
||||
# title: "Title"
|
||||
title: "Tytuł"
|
||||
# description: "Description"
|
||||
# creating_class: "Creating class..."
|
||||
# purchasing_course: "Purchasing course..."
|
||||
|
@ -938,9 +938,9 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
# enter_code1: "Enter unlock code"
|
||||
# enroll: "Enroll"
|
||||
# pick_from_classes: "Pick from your current classes"
|
||||
# enter: "Enter"
|
||||
# or: "Or"
|
||||
# topics: "Topics"
|
||||
enter: "Enter"
|
||||
or: "Lub"
|
||||
topics: "Tematy"
|
||||
# hours_content: "Hours of content:"
|
||||
# get_free: "Get FREE course"
|
||||
|
||||
|
@ -1185,12 +1185,12 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
# tournament_blurb_blog: "on our blog"
|
||||
rules: "Zasady"
|
||||
winners: "Zwycięzcy"
|
||||
# league: "League"
|
||||
league: "Liga"
|
||||
# red_ai: "Red AI" # "Red AI Wins", at end of multiplayer match playback
|
||||
# blue_ai: "Blue AI"
|
||||
# wins: "Wins" # At end of multiplayer match playback
|
||||
# humans: "Red" # Ladder page display team name
|
||||
# ogres: "Blue"
|
||||
humans: "Czerwoni" # Ladder page display team name
|
||||
ogres: "Niebiescy"
|
||||
|
||||
user:
|
||||
stats: "Statystyki"
|
||||
|
@ -1227,7 +1227,7 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
# sale: "Sale"
|
||||
subscription: "Subskrypcje"
|
||||
invoices: "Faktury"
|
||||
# service_apple: "Apple"
|
||||
service_apple: "Apple"
|
||||
# service_web: "Web"
|
||||
# paid_on: "Paid On"
|
||||
# service: "Service"
|
||||
|
@ -1287,7 +1287,7 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
bad_input: "Złe dane wejściowe."
|
||||
server_error: "Błąd serwera."
|
||||
unknown: "Nieznany błąd."
|
||||
# error: "ERROR"
|
||||
error: "BŁĄD"
|
||||
|
||||
resources:
|
||||
sessions: "Sesje"
|
||||
|
@ -1306,7 +1306,7 @@ module.exports = nativeDescription: "polski", englishDescription: "Polish", tran
|
|||
patches: "Łatki"
|
||||
patched_model: "Dokument źródłowy"
|
||||
model: "Model"
|
||||
# system: "System"
|
||||
system: "System"
|
||||
# systems: "Systems"
|
||||
component: "Komponent"
|
||||
components: "Komponenty"
|
||||
|
|
|
@ -159,9 +159,9 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
accepted: "Aceito"
|
||||
rejected: "Rejeitado"
|
||||
withdrawn: "Retirado"
|
||||
# accept: "Accept"
|
||||
# reject: "Reject"
|
||||
# withdraw: "Withdraw"
|
||||
accept: "Aceitar"
|
||||
reject: "Rejeitar"
|
||||
withdraw: "Recuar"
|
||||
submitter: "Enviar"
|
||||
submitted: "Enviado"
|
||||
commit_msg: "Mensagem de Submissão"
|
||||
|
@ -217,8 +217,8 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
|
||||
play_level:
|
||||
done: "Pronto"
|
||||
# next_game: "Next game"
|
||||
# show_menu: "Show game menu"
|
||||
next_game: "Próximo jogo"
|
||||
show_menu: "Mostrar menu do jogo"
|
||||
home: "Início" # Not used any more, will be removed soon.
|
||||
level: "Fase" # Like "Level: Dungeons of Kithgard"
|
||||
skip: "Pular"
|
||||
|
@ -341,7 +341,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
tip_free_your_mind: "Você tem que deixar isso tudo passar, Neo. O medo, a dúvida e a descrença. Liberte sua mente - Morpheus"
|
||||
tip_strong_opponents: "Mesmo o mais forte dos adversários tem sua fraqueza. - Itachi Uchiha"
|
||||
tip_paper_and_pen: "Antes de começar a programar, você sempre deve planejar com papel e caneta."
|
||||
# tip_solve_then_write: "First, solve the problem. Then, write the code. - John Johnson"
|
||||
tip_solve_then_write: "Primeiro, resolva o problema. Então, escreva o código. - John Johnson"
|
||||
|
||||
game_menu:
|
||||
inventory_tab: "Inventário"
|
||||
|
@ -410,7 +410,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
feature7: "<strong>Clãs</strong> Privados"
|
||||
free: "Grátis"
|
||||
month: "mês"
|
||||
# must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
|
||||
must_be_logged: "Você deve estar logado primeiro. Por gentileza crie uma conta ou faça o log in no menu acima."
|
||||
subscribe_title: "Inscrever-se"
|
||||
unsubscribe: "Desinscrever-se"
|
||||
confirm_unsubscribe: "Confirmar Desinscrição"
|
||||
|
@ -453,7 +453,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
sale_limited_time: "Oferta por tempo limitado!"
|
||||
sale_new_heroes: "Novos heróis!"
|
||||
sale_title: "Promoção de volta às aulas"
|
||||
# sale_view_button: "Buy 1 year subscription for"
|
||||
sale_view_button: "Compre uma assinatura de 1 ano por"
|
||||
stripe_description: "Inscrição Mensal"
|
||||
stripe_description_year_sale: "Assinatura de 1 Ano (35% de desconto"
|
||||
subscription_required_to_play: "Você precisará se inscrever para jogar este nível."
|
||||
|
@ -527,8 +527,8 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
skill_docs:
|
||||
writable: "gravável" # Hover over "attack" in Your Skills while playing a level to see most of this
|
||||
read_only: "apenas leitura"
|
||||
# action: "Action"
|
||||
# spell: "Spell"
|
||||
action: "Ação"
|
||||
spell: "Feitiço"
|
||||
action_name: "nome"
|
||||
action_cooldown: "Demora"
|
||||
action_specific_cooldown: "Recarregando"
|
||||
|
@ -598,19 +598,19 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
jose_blurb: "Sou descolado"
|
||||
retrostyle_title: "Ilustração"
|
||||
retrostyle_blurb: "Games estilo Retrô"
|
||||
# rob_title: "Compiler Engineer"
|
||||
# rob_blurb: "Codes things and stuff"
|
||||
# josh_c_title: "Game Designer"
|
||||
# josh_c_blurb: "Designs games"
|
||||
# carlos_title: "Region Manager, Brazil"
|
||||
# carlos_blurb: "Celery Man"
|
||||
rob_title: "Engenheiro Compilador"
|
||||
rob_blurb: "Os paranauês dos códigos"
|
||||
josh_c_title: "Game Designer"
|
||||
josh_c_blurb: "Desenha jogos"
|
||||
carlos_title: "Gerente Regional, Brasil"
|
||||
carlos_blurb: "O Homem Célere"
|
||||
|
||||
teachers:
|
||||
more_info: "Informações para Professores"
|
||||
intro_1: "CodeCombat é um jogo online que ensina programação. Estudantes criam código em linguagens de programação usadas na vida real."
|
||||
intro_2: "Não é necessário ter experiência!"
|
||||
free_title: "Quanto custa?"
|
||||
# cost_premium_server: "CodeCombat is free for the first five levels, after which it costs $9.99 USD per month for access to our other 190+ levels on our exclusive country-specific servers."
|
||||
cost_premium_server: "CodeCombat é gratuito para os primeiros cinco níveis, depois disso custa $9.99 USD por mês para ter acesso aos nossos outros 190+ níveis em nossos servidores exclusivos por países."
|
||||
free_1: "CodeCombat Basic é gratuito! Há mais de 80 níveis gratuitos que cobrem todos os conceitos." # {change}
|
||||
free_2: "Uma assinatura mensal dá acesso aos vídeos tutoriais e mais níveis para praticar."
|
||||
teacher_subs_title: "Professores recebem assinaturas gratuitas!"
|
||||
|
@ -733,7 +733,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
new_password: "Nova Senha"
|
||||
new_password_verify: "Confirmação"
|
||||
type_in_email: "Digite seu e-mail para confirmar a exclusão" # {change}
|
||||
# type_in_email_progress: "Type in your email to confirm deleting your progress."
|
||||
type_in_email_progress: "Escreva seu email para confirmar que está apagando seu progresso."
|
||||
type_in_password: "Digite sua senha."
|
||||
email_subscriptions: "Assinaturas para Notícias por Email"
|
||||
email_subscriptions_none: "Sem Assinaturas de Email"
|
||||
|
@ -845,32 +845,32 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
playtime: "Tempo de Jogo"
|
||||
last_played: "Último Jogo"
|
||||
leagues_explanation: "Jogar em um campeonato contra outros membros do clã nestes casos de arena multiplayer."
|
||||
# track_concepts1: "Track concepts"
|
||||
# track_concepts2a: "learned by each student"
|
||||
# track_concepts2b: "learned by each member"
|
||||
# track_concepts3a: "Track levels completed for each student"
|
||||
# track_concepts3b: "Track levels completed for each member"
|
||||
# track_concepts4a: "See your students'"
|
||||
# track_concepts4b: "See your members'"
|
||||
# track_concepts5: "solutions"
|
||||
# track_concepts6a: "Sort students by name or progress"
|
||||
# track_concepts6b: "Sort members by name or progress"
|
||||
# track_concepts7: "Requires invitation"
|
||||
# track_concepts8: "to join"
|
||||
# private_require_sub: "Private clans require a subscription to create or join."
|
||||
track_concepts1: "Rastrear conceitos"
|
||||
track_concepts2a: "aprendido por cada estudante"
|
||||
track_concepts2b: "aprendido por cada membro"
|
||||
track_concepts3a: "Rastrear níveis completados por cada estudante"
|
||||
track_concepts3b: "Rastrear níveis completados por cada membro"
|
||||
track_concepts4a: "Ver seus alunos'"
|
||||
track_concepts4b: "Ver seus membros'"
|
||||
track_concepts5: "soluções"
|
||||
track_concepts6a: "Classificar alunos por nome ou progresso"
|
||||
track_concepts6b: "Classificar membros por nome ou progresso"
|
||||
track_concepts7: "Requer convite"
|
||||
track_concepts8: "para se juntar"
|
||||
private_require_sub: "Clãs particulares requerem uma assinatura para criar ou juntar-se."
|
||||
|
||||
# courses:
|
||||
# course: "Course"
|
||||
# courses: "courses"
|
||||
# not_enrolled: "You are not enrolled in this course."
|
||||
# visit_pref: "Please visit the"
|
||||
# visit_suf: "page to enroll."
|
||||
# select_class: "Select one of your classes"
|
||||
# unnamed: "*unnamed*"
|
||||
# select: "Select"
|
||||
# unnamed_class: "Unnamed Class"
|
||||
# edit_settings: "edit class settings"
|
||||
# edit_settings1: "Edit Class Settings"
|
||||
courses:
|
||||
course: "Curso"
|
||||
courses: "cursos"
|
||||
not_enrolled: "Você não está matriculado nesse curso."
|
||||
visit_pref: "Por gntileza, visite e"
|
||||
visit_suf: "página para matricular-se."
|
||||
select_class: "Selecione uma de suas classes"
|
||||
unnamed: "*sem nome*"
|
||||
select: "Selecionar"
|
||||
unnamed_class: "Classe Sem Nome"
|
||||
edit_settings: "editar configurações da classe"
|
||||
edit_settings1: "Editar Configurações da Classe"
|
||||
# progress: "Class Progress"
|
||||
# add_students: "Add Students"
|
||||
# stats: "Statistics"
|
||||
|
|
|
@ -147,7 +147,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
unwatch: "Ngừng Quan Sát"
|
||||
submit_patch: "Gửi Bản Cập Nhật"
|
||||
submit_changes: "Gửi Những Thay Đổi"
|
||||
# save_changes: "Save Changes"
|
||||
save_changes: "Lưu Những Thay Đổi"
|
||||
|
||||
general:
|
||||
and: "và"
|
||||
|
@ -251,7 +251,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
victory_saving_progress: "Đang lưu tiến trình"
|
||||
victory_go_home: "Quay về màn hình chính"
|
||||
victory_review: "Hãy cho chúng tôi biết thêm"
|
||||
# victory_review_placeholder: "How was the level?"
|
||||
victory_review_placeholder: "Bàn vừa rồi như thế nào ?"
|
||||
victory_hour_of_code_done: "Bạn xong chưa?"
|
||||
victory_hour_of_code_done_yes: "Đúng vậy, tôi đã hoàn tất thời gian lập trình!"
|
||||
victory_experience_gained: "XP nhận được"
|
||||
|
@ -336,7 +336,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
tip_google: "Có vấn đề mà bạn không thể giải quyết ? Hãy sử dụng Google để tìm ra phương án!"
|
||||
# tip_adding_evil: "Adding a pinch of evil."
|
||||
# tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
|
||||
# tip_open_source_contribute: "You can help CodeCombat improve!"
|
||||
tip_open_source_contribute: "Bạn có thể giúp CodeCombat trở nên tốt hơn!"
|
||||
# tip_recurse: "To iterate is human, to recurse divine. - L. Peter Deutsch"
|
||||
# tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
|
||||
# tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
|
||||
|
@ -434,7 +434,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# parents_blurb1a: "Computer programming is an essential skill that your child will undoubtedly use as an adult. By 2020, basic software skills will be needed by 77% of jobs, and software engineers are in high demand across the world. Did you know that Computer Science is the highest-paid university degree?"
|
||||
parents_blurb2: "Chỉ với $9.99 USD một tháng, con của bạn sẽ nhận được những thử thách mới mỗi tháng và sẽ nhận được sự hỗ trợ từ các lập trình viên chuyên nghiệp qua email." # {change}
|
||||
parents_blurb3: "Không hề có rủi ro: Nếu bạn không hài lòng bạn có thể nhận lại 100% số tiền mình bỏ ra chỉ với 1 cú nhấp chuốt."
|
||||
# payment_methods: "Payment Methods"
|
||||
payment_methods: "Những phương thức thanh toán"
|
||||
# payment_methods_title: "Accepted Payment Methods"
|
||||
# payment_methods_blurb1: "We currently accept credit cards and Alipay."
|
||||
# payment_methods_blurb2: "If you require an alternate form of payment, please contact"
|
||||
|
@ -497,7 +497,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
coffeescript_blurb: "Thực ra là JavaScript nhưng với cú pháp tốt hơn."
|
||||
clojure_blurb: "Lisp thời đại mới."
|
||||
lua_blurb: "Ngôn ngữ hay dùng trong làm game."
|
||||
# io_blurb: "Simple but obscure."
|
||||
io_blurb: "Đơn giản nhưng ít người biết đến."
|
||||
status: "Tình trạng"
|
||||
hero_type: "Loại"
|
||||
weapons: "Vũ khí"
|
||||
|
@ -635,7 +635,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
|
||||
# private_clans_2: "To create a private Clan, check the 'Make clan private' checkbox when creating a"
|
||||
# private_clans_3: "."
|
||||
# who_for_title: "Who is CodeCombat for?"
|
||||
who_for_title: "CodeCombat dành cho ai ?"
|
||||
# who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed."
|
||||
# who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
|
||||
# material_title: "How much material is there?"
|
||||
|
@ -719,11 +719,11 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
autosave: "Tự động lưu thay đổi"
|
||||
me_tab: "Tôi"
|
||||
picture_tab: "Bức tranh"
|
||||
# delete_account_tab: "Delete Your Account"
|
||||
# wrong_email: "Wrong Email"
|
||||
# wrong_password: "Wrong Password"
|
||||
delete_account_tab: "Xóa tài khoản của bạn"
|
||||
wrong_email: "Email không đúng"
|
||||
wrong_password: "Mật khẩu không đúng"
|
||||
upload_picture: "Tải ảnh lên"
|
||||
# delete_this_account: "Delete this account permanently"
|
||||
delete_this_account: "Xóa tài khoản này vĩnh viễn"
|
||||
# reset_progress_tab: "Reset All Progress"
|
||||
# reset_your_progress: "Clear all your progress and start over"
|
||||
# god_mode: "God Mode"
|
||||
|
@ -745,7 +745,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# email_any_notes_description: "Disable to stop all activity notification emails."
|
||||
# email_news: "News"
|
||||
email_recruit_notes: "Cơ hội việc làm"
|
||||
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
|
||||
email_recruit_notes_description: "Nếu bạn chơi trò này rất giỏi, chúng tôi có thể sẽ liên lạc với bạn về cơ hội nghề nghiệp."
|
||||
# contributor_emails: "Contributor Class Emails"
|
||||
contribute_prefix: "Chúng tôi đang tìm thêm người vào nhóm của chúng tôi! Hãy kiểm "
|
||||
contribute_page: "trang đóng góp"
|
||||
|
@ -765,7 +765,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
keyboard_shortcuts: "Các phím tắt"
|
||||
space: "Phím Space"
|
||||
enter: "Phím Enter"
|
||||
# press_enter: "press enter"
|
||||
press_enter: "Ấn Enter"
|
||||
escape: "Phím Escape"
|
||||
shift: "Phím Shift"
|
||||
# run_code: "Run current code."
|
||||
|
@ -801,19 +801,19 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# social_hipchat: "Chat with us in the public CodeCombat Slack channel"
|
||||
# contribute_to_the_project: "Contribute to the project"
|
||||
|
||||
# clans:
|
||||
# clan: "Clan"
|
||||
clans:
|
||||
clan: "Clan"
|
||||
# clans: "Clans"
|
||||
# new_name: "New clan name"
|
||||
# new_description: "New clan description"
|
||||
# make_private: "Make clan private"
|
||||
# subs_only: "subscribers only"
|
||||
# create_clan: "Create New Clan"
|
||||
create_clan: "Tạo một clan mới"
|
||||
# private_preview: "Preview"
|
||||
# public_clans: "Public Clans"
|
||||
# my_clans: "My Clans"
|
||||
# clan_name: "Clan Name"
|
||||
# name: "Name"
|
||||
name: "Tên"
|
||||
# chieftain: "Chieftain"
|
||||
# type: "Type"
|
||||
# edit_clan_name: "Edit Clan Name"
|
||||
|
@ -829,7 +829,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# join_clan: "Join Clan"
|
||||
# invite_1: "Invite:"
|
||||
# invite_2: "*Invite players to this Clan by sending them this link."
|
||||
# members: "Members"
|
||||
members: "Những thành viên"
|
||||
# progress: "Progress"
|
||||
# not_started_1: "not started"
|
||||
# started_1: "started"
|
||||
|
@ -859,9 +859,9 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# track_concepts8: "to join"
|
||||
# private_require_sub: "Private clans require a subscription to create or join."
|
||||
|
||||
# courses:
|
||||
# course: "Course"
|
||||
# courses: "courses"
|
||||
courses:
|
||||
course: "Khóa học"
|
||||
courses: "Những khóa học"
|
||||
# not_enrolled: "You are not enrolled in this course."
|
||||
# visit_pref: "Please visit the"
|
||||
# visit_suf: "page to enroll."
|
||||
|
@ -872,11 +872,11 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# edit_settings: "edit class settings"
|
||||
# edit_settings1: "Edit Class Settings"
|
||||
# progress: "Class Progress"
|
||||
# add_students: "Add Students"
|
||||
# stats: "Statistics"
|
||||
# total_students: "Total students:"
|
||||
add_students: "Thêm học sinh"
|
||||
stats: "Thống kê"
|
||||
total_students: "Tổng số học sinh:"
|
||||
# average_time: "Average level play time:"
|
||||
# total_time: "Total play time:"
|
||||
total_time: "Tổng thời gian chơi:"
|
||||
# average_levels: "Average levels completed:"
|
||||
# total_levels: "Total levels completed:"
|
||||
# furthest_level: "Furthest level completed:"
|
||||
|
@ -896,20 +896,20 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# capacity_used: "Course slots used:"
|
||||
# enter_emails: "Enter student emails to invite, one per line"
|
||||
# send_invites: "Send Invites"
|
||||
# title: "Title"
|
||||
title: "Tiêu đề"
|
||||
# description: "Description"
|
||||
# creating_class: "Creating class..."
|
||||
# purchasing_course: "Purchasing course..."
|
||||
# buy_course: "Buy Course"
|
||||
# buy_course1: "Buy this course"
|
||||
buy_course: "Mua khóa học"
|
||||
buy_course1: "Mua khóa học này"
|
||||
# create_class: "Create Class"
|
||||
# select_all_courses: "Select 'All Courses' for a 50% discount!"
|
||||
# all_courses: "All Courses"
|
||||
# number_students: "Number of students"
|
||||
# enter_number_students: "Enter the number of students you need for this class."
|
||||
all_courses: "Tất cả những khóa học"
|
||||
number_students: "Số lượng học sinh"
|
||||
enter_number_students: "Hãy nhập vào số lượng học sinh bạn cần cho lớp học này."
|
||||
# name_class: "Name your class"
|
||||
# displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
|
||||
# buy: "Buy"
|
||||
buy: "Mua"
|
||||
# purchasing_for: "You are purchasing a license for"
|
||||
# creating_for: "You are creating a class for"
|
||||
# for: "for" # Like in 'for 30 students'
|
||||
|
@ -925,7 +925,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
|
||||
# teachers_click: "Teachers Click Here"
|
||||
# students_click: "Students Click Here"
|
||||
# courses_on_coco: "Courses on CodeCombat"
|
||||
courses_on_coco: "Những khóa học trên CodeCombat"
|
||||
# designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
|
||||
# more_in_less: "Learn more in less time"
|
||||
# no_experience: "No coding experience necesssary"
|
||||
|
@ -938,7 +938,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# enter_code1: "Enter unlock code"
|
||||
# enroll: "Enroll"
|
||||
# pick_from_classes: "Pick from your current classes"
|
||||
# enter: "Enter"
|
||||
enter: "Enter"
|
||||
# or: "Or"
|
||||
# topics: "Topics"
|
||||
# hours_content: "Hours of content:"
|
||||
|
@ -979,7 +979,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
indoor: "Trong nhà"
|
||||
desert: "Sa mạc"
|
||||
# grassy: "Grassy"
|
||||
# mountain: "Mountain"
|
||||
mountain: "Đồi núi"
|
||||
# glacier: "Glacier"
|
||||
small: "Bé"
|
||||
large: "Lớn"
|
||||
|
@ -1110,7 +1110,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# diplomat_i18n_page: "translations page"
|
||||
# diplomat_i18n_page_suffix: ", or our interface and website on GitHub."
|
||||
# diplomat_join_pref_github: "Find your language locale file "
|
||||
# diplomat_github_url: "on GitHub"
|
||||
diplomat_github_url: "ở trên GitHub"
|
||||
# diplomat_join_suf_github: ", edit it online, and submit a pull request. Also, check this box below to keep up-to-date on new internationalization developments!"
|
||||
# diplomat_subscribe_desc: "Get emails about i18n developments and levels to translate."
|
||||
# ambassador_introduction: "This is a community we're building, and you are the connections. We've got forums, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
|
||||
|
@ -1206,7 +1206,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# no_achievements: "No Achievements earned yet."
|
||||
favorite_prefix: "Ngôn ngữ lập trình ưu thích là "
|
||||
favorite_postfix: "."
|
||||
# not_member_of_clans: "Not a member of any clans yet."
|
||||
not_member_of_clans: "Không phải là thành viên của bất kì clan nào."
|
||||
|
||||
# achievements:
|
||||
# last_earned: "Last Earned"
|
||||
|
@ -1476,7 +1476,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
complete: "Hoàn tất"
|
||||
next: "Tiếp"
|
||||
next_city: "Thành phố?"
|
||||
# next_country: "pick your country."
|
||||
next_country: "Chọn đất nước của bạn."
|
||||
# next_name: "name?"
|
||||
# next_short_description: "write a short description."
|
||||
# next_long_description: "describe your desired position."
|
||||
|
@ -1487,7 +1487,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# next_links: "add any personal or social links."
|
||||
# next_photo: "add an optional professional photo."
|
||||
# next_active: "mark yourself open to offers to show up in searches."
|
||||
# example_blog: "Blog"
|
||||
example_blog: "Blog"
|
||||
example_personal_site: "Trang cá nhân"
|
||||
links_header: "Đường truyền cá nhân"
|
||||
# links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."
|
||||
|
@ -1510,12 +1510,12 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# basics_looking_for_part_time: "Part-time"
|
||||
# basics_looking_for_remote: "Remote"
|
||||
# basics_looking_for_contracting: "Contracting"
|
||||
# basics_looking_for_internship: "Internship"
|
||||
basics_looking_for_internship: "Thực tập"
|
||||
# basics_looking_for_help: "What kind of developer position do you want?"
|
||||
name_header: "Điền tên của bạn"
|
||||
# name_anonymous: "Anonymous Developer"
|
||||
# name_help: "Name you want employers to see, like 'Nick Winter'."
|
||||
# short_description_header: "Write a short description of yourself"
|
||||
short_description_header: "Hãy viết một bản giới thiệu ngắn gọn về bạn"
|
||||
# short_description_blurb: "Add a tagline to help an employer quickly learn more about you."
|
||||
# short_description: "Tagline"
|
||||
# short_description_help: "Who are you, and what are you looking for? 140 characters max."
|
||||
|
@ -1577,9 +1577,9 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# filter_visa_no: "Not Authorized"
|
||||
# filter_education_top: "Top School"
|
||||
filter_education_other: "Khác"
|
||||
# filter_role_web_developer: "Web Developer"
|
||||
# filter_role_software_developer: "Software Developer"
|
||||
# filter_role_mobile_developer: "Mobile Developer"
|
||||
filter_role_web_developer: "Nhà phát triển web"
|
||||
filter_role_software_developer: "Nhà phát triển phần mềm"
|
||||
filter_role_mobile_developer: "Nhà phát triển phần ứng dụng di động"
|
||||
filter_experience: "Kinh Nghiệm"
|
||||
# filter_experience_senior: "Senior"
|
||||
# filter_experience_junior: "Junior"
|
||||
|
|
|
@ -6,13 +6,14 @@ module.exports = class Prepaid extends CocoModel
|
|||
urlRoot: '/db/prepaid'
|
||||
|
||||
openSpots: ->
|
||||
@get('maxRedeemers') - @get('redeemers')?.length
|
||||
return @get('maxRedeemers') - @get('redeemers')?.length if @get('redeemers')?
|
||||
@get('maxRedeemers')
|
||||
|
||||
userHasRedeemed: (userID) ->
|
||||
for redeemer in @get('redeemers')
|
||||
return redeemer.date if redeemer.userID is userID
|
||||
return null
|
||||
|
||||
|
||||
initialize: ->
|
||||
@listenTo @, 'add', ->
|
||||
maxRedeemers = @get('maxRedeemers')
|
||||
|
|
|
@ -131,17 +131,6 @@ module.exports = class User extends CocoModel
|
|||
application.tracker.identify fourthLevelGroup: @fourthLevelGroup unless me.isAdmin()
|
||||
@fourthLevelGroup
|
||||
|
||||
getSubscriptionPromptGroup: ->
|
||||
return @subscriptionPromptGroup if @subscriptionPromptGroup
|
||||
group = me.get('testGroupNumber') % 3
|
||||
@subscriptionPromptGroup = switch group
|
||||
when 0 then 'favorable-odds'
|
||||
when 1 then 'tactical-strike'
|
||||
when 2 then 'boom-and-bust'
|
||||
@subscriptionPromptGroup = 'favorable-odds' if me.isAdmin()
|
||||
application.tracker.identify subscriptionPromptGroup: @subscriptionPromptGroup unless me.isAdmin()
|
||||
@subscriptionPromptGroup
|
||||
|
||||
getVideoTutorialStylesIndex: (numVideos=0)->
|
||||
# A/B Testing video tutorial styles
|
||||
# Not a constant number of videos available (e.g. could be 0, 1, 3, or 4 currently)
|
||||
|
|
|
@ -116,6 +116,7 @@ _.extend CampaignSchema.properties, {
|
|||
}}
|
||||
|
||||
campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor.
|
||||
campaignIndex: c.int title: 'Campaign Index', description: 'The 0-based index of this level in its campaign.', format: 'hidden' # Automatically set by campaign editor.
|
||||
|
||||
tasks: c.array {title: 'Tasks', description: 'Tasks to be completed for this level.'}, c.task
|
||||
concepts: c.array {title: 'Programming Concepts', description: 'Which programming concepts this level covers.'}, c.concept
|
||||
|
|
|
@ -8,6 +8,7 @@ _.extend ClassroomSchema.properties,
|
|||
ownerID: c.objectId()
|
||||
description: {type: 'string'}
|
||||
code: c.shortString(title: "Unique code to redeem")
|
||||
codeCamel: c.shortString(title: "UpperCamelCase version of code for display purposes")
|
||||
aceConfig:
|
||||
language: {type: 'string', 'enum': ['python', 'javascript']}
|
||||
|
||||
|
|
|
@ -344,6 +344,7 @@ _.extend LevelSchema.properties,
|
|||
type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference'
|
||||
}}
|
||||
campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor.
|
||||
campaignIndex: c.int title: 'Campaign Index', description: 'The 0-based index of this level in its campaign.', format: 'hidden' # Automatically set by campaign editor.
|
||||
scoreTypes: c.array {title: 'Score Types', description: 'What metric to show leaderboards for.', uniqueItems: true},
|
||||
c.shortString(title: 'Score Type', 'enum': ['time', 'damage-taken', 'damage-dealt', 'gold-collected', 'difficulty']) # TODO: good version of LoC; total gear value.
|
||||
concepts: c.array {title: 'Programming Concepts', description: 'Which programming concepts this level covers.', uniqueItems: true}, c.concept
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
width: 330px
|
||||
padding: 5px
|
||||
float: left
|
||||
z-index: 0
|
||||
|
||||
&:hover img
|
||||
outline: 3px solid #161a9e
|
||||
|
|
|
@ -3,6 +3,29 @@
|
|||
|
||||
#home-view
|
||||
|
||||
#hour-of-code
|
||||
top: 632px
|
||||
height: 190px
|
||||
width: 400px
|
||||
padding: 15px
|
||||
border: 0
|
||||
background: transparent url(/images/pages/play/level-info-background.png) no-repeat center center
|
||||
background-size: 100% 100%
|
||||
|
||||
@media screen and ( max-height: 800px )
|
||||
top: 382px
|
||||
h1
|
||||
color: rgb(255,253,149)
|
||||
text-shadow: 0 0 6px black,0 0 6px black,0 0 6px black,0 0 6px black, 0 0 6px black,0 0 6px black,0 0 6px black,0 0 6px black, 0 0 6px black,0 0 6px black,0 0 6px black,0 0 6px black, 0 0 6px black,0 0 6px black,0 0 6px black,0 0 6px black, 0 0 6px black,0 0 6px black,0 0 6px black,0 0 6px black
|
||||
.btn-class-code
|
||||
float: left
|
||||
margin-left: 10px
|
||||
margin-top: 10px
|
||||
.btn-enter
|
||||
float: right
|
||||
margin-right: 10px
|
||||
margin-top: 10px
|
||||
|
||||
#kids-coding-container
|
||||
$coding-image-size: 272px
|
||||
position: relative
|
||||
|
@ -29,8 +52,8 @@
|
|||
height: 626px
|
||||
@media screen and ( max-height: 800px )
|
||||
height: 510px
|
||||
|
||||
#play-button, #or-ipad, #apple-store-button, #slogan, .alert, #news
|
||||
|
||||
#play-button, #or-ipad, #apple-store-button, #slogan, .alert, #news, #hour-of-code
|
||||
text-align: center
|
||||
text-transform: uppercase
|
||||
font-weight: bold
|
||||
|
@ -40,7 +63,7 @@
|
|||
left: 0
|
||||
right: 0
|
||||
font-weight: bold
|
||||
|
||||
|
||||
#play-button
|
||||
text-align: center
|
||||
padding-top: 170px
|
||||
|
@ -52,15 +75,15 @@
|
|||
height: 219px
|
||||
@media screen and ( max-height: 800px )
|
||||
top: 78px
|
||||
|
||||
|
||||
background-image: url(/images/pages/home/play_button.png)
|
||||
background-position: 0 219px
|
||||
|
||||
|
||||
&:hover
|
||||
background-position: 0 0
|
||||
color: rgb(230,180,75)
|
||||
text-decoration: none
|
||||
|
||||
|
||||
#or-ipad
|
||||
top: 540px
|
||||
color: rgb(119,101,84)
|
||||
|
@ -69,7 +92,7 @@
|
|||
|
||||
@media screen and ( max-height: 800px )
|
||||
top: 310px
|
||||
|
||||
|
||||
#apple-store-button
|
||||
top: 593px
|
||||
height: 63px
|
||||
|
@ -114,18 +137,6 @@
|
|||
top: 213px
|
||||
border: 5px solid darkred
|
||||
|
||||
&.hour-of-code
|
||||
#site-footer
|
||||
background-color: rgb(70, 58, 44)
|
||||
height: 185px
|
||||
|
||||
.hour-of-code-explanation
|
||||
color: #9e8777
|
||||
text-align: center
|
||||
|
||||
a
|
||||
color: lighten(#0b63bc, 10%)
|
||||
|
||||
body[lang='ru'], body[lang^='de'], body[lang^='pt-BR'], body[lang='pl'], body[lang='tr'], body[lang^='nl'], body[lang^='cs'], body[lang^='sv'], body[lang^='el'], body[lang^='hu'], body[lang^='bg']
|
||||
#home-view #slogan
|
||||
font-size: 22px
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
margin-top: -130px
|
||||
#victory-header
|
||||
display: none
|
||||
|
||||
|
||||
//- Header
|
||||
|
||||
|
||||
.background-wrapper
|
||||
//background: url("/images/pages/play/level/modal/victory_modal_background.png")
|
||||
width: 750px
|
||||
|
@ -57,10 +57,10 @@
|
|||
text-align: center
|
||||
background: transparent url(/images/pages/play/level/modal/victory_hero.png) no-repeat
|
||||
background-position: center -88px
|
||||
|
||||
|
||||
&.out
|
||||
@include scale(0)
|
||||
|
||||
|
||||
#victory-title
|
||||
display: inline-block
|
||||
margin-top: 74px
|
||||
|
@ -73,14 +73,14 @@
|
|||
margin: 0
|
||||
padding: 0
|
||||
text-shadow: black 8px 8px 0, black -8px -8px 0, black 8px -8px 0, black -8px 8px 0, black 8px 0px 0, black 0px -8px 0, black -8px 0px 0, black 0px 8px 0
|
||||
|
||||
|
||||
//- Achievement panels
|
||||
|
||||
|
||||
.modal-body
|
||||
padding: 0 20px
|
||||
min-height: 30px
|
||||
margin-top: 160px
|
||||
|
||||
|
||||
.achievement-panel
|
||||
background: transparent url("/images/pages/play/level/modal/victory_modal_shelf.png") no-repeat center 73px
|
||||
width: 824px
|
||||
|
@ -127,9 +127,9 @@
|
|||
top: 0
|
||||
@include flexbox()
|
||||
@include flex-justify-center()
|
||||
|
||||
|
||||
//- Reward panels
|
||||
|
||||
|
||||
.reward-panel
|
||||
background: url("/images/pages/play/level/modal/reward_plate.png")
|
||||
width: 77px
|
||||
|
@ -167,10 +167,10 @@
|
|||
|
||||
@include scale(0)
|
||||
@include transition-duration(0.5s)
|
||||
|
||||
|
||||
&.show
|
||||
@include scale(1)
|
||||
|
||||
|
||||
&.pending-reward-image
|
||||
img
|
||||
-webkit-filter: brightness(2000%) contrast(25%)
|
||||
|
@ -202,7 +202,7 @@
|
|||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
|
||||
//- Pulse effect
|
||||
|
||||
+keyframes(rewardPulse)
|
||||
|
@ -222,7 +222,7 @@
|
|||
|
||||
.gems .pulse
|
||||
@include animation(rewardPulse 0.25s infinite)
|
||||
|
||||
|
||||
|
||||
//- Footer - totals
|
||||
|
||||
|
@ -282,7 +282,7 @@
|
|||
margin-top: 3px
|
||||
position: relative
|
||||
float: left
|
||||
|
||||
|
||||
.xp-bar-already-achieved
|
||||
background-color: rgb(166, 213, 88)
|
||||
//background-color: white
|
||||
|
@ -290,7 +290,7 @@
|
|||
height: 100%
|
||||
position: absolute
|
||||
z-index: 1
|
||||
|
||||
|
||||
.xp-bar-total
|
||||
background-color: rgb(253, 171, 45)
|
||||
border: 1px solid rgb(239, 177, 73)
|
||||
|
@ -300,13 +300,13 @@
|
|||
|
||||
|
||||
//- Footer - other stuff
|
||||
|
||||
|
||||
.modal-footer
|
||||
// Negative bottom margin counteracts most of the extra the border image height.
|
||||
margin: 0 0 -80px 0
|
||||
padding: 0 20px
|
||||
text-align: center
|
||||
|
||||
|
||||
.sign-up-poke
|
||||
width: 430px
|
||||
|
||||
|
@ -328,7 +328,7 @@
|
|||
line-height: 30px
|
||||
margin: 0
|
||||
float: left
|
||||
|
||||
|
||||
.leaderboard-button, .courses-button
|
||||
height: 60px
|
||||
line-height: 30px
|
||||
|
@ -403,6 +403,26 @@
|
|||
font-family: $headings-font-family
|
||||
text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0
|
||||
|
||||
.level-title
|
||||
z-index: 1
|
||||
margin: 10px
|
||||
font-size: 24px
|
||||
color: $hero-yellow-text
|
||||
font-weight: bold
|
||||
text-transform: uppercase
|
||||
font-family: $headings-font-family
|
||||
text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0
|
||||
|
||||
.level-name
|
||||
z-index: 1
|
||||
text-align: center
|
||||
font-size: 18px
|
||||
color: white
|
||||
font-weight: bold
|
||||
text-transform: uppercase
|
||||
font-family: $headings-font-family
|
||||
text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0
|
||||
|
||||
#level-feedback
|
||||
color: $hero-yellow-text
|
||||
font-weight: bold
|
||||
|
|
|
@ -82,7 +82,8 @@
|
|||
left: 219px
|
||||
top: 21px
|
||||
width: 571px
|
||||
height: 514px
|
||||
height: 495px
|
||||
margin-top: 15px
|
||||
padding: 50px
|
||||
overflow-y: scroll
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
#teachers-free-trial-view
|
||||
|
||||
.input-email-address
|
||||
width: 40%
|
||||
|
||||
.input-school
|
||||
width: 40%
|
||||
|
||||
|
@ -18,6 +15,9 @@
|
|||
.thanks-submit
|
||||
display: none
|
||||
|
||||
.email-address
|
||||
margin-right: 12px
|
||||
|
||||
.error-message
|
||||
display: none
|
||||
color: red
|
||||
|
|
|
@ -31,7 +31,7 @@ block content
|
|||
if numReviewed > maxReviewedShown
|
||||
- break
|
||||
tr
|
||||
td.created= new Date(parseInt(trialRequest.get('_id').substring(0, 8), 16) * 1000).toISOString().substring(0, 10)
|
||||
td.created= trialRequest.get('created').substring(0, 10)
|
||||
td.reviewed
|
||||
if trialRequest.get('reviewDate')
|
||||
span= trialRequest.get('reviewDate').substring(0, 10)
|
||||
|
|
|
@ -269,6 +269,7 @@ mixin levels-tab
|
|||
tbody
|
||||
if campaign
|
||||
- var lastLevelCompleted = true;
|
||||
- var levelCount = 0;
|
||||
each level, levelID in campaign.get('levels')
|
||||
tr
|
||||
td
|
||||
|
@ -281,7 +282,7 @@ mixin levels-tab
|
|||
- lastLevelCompleted = userLevelStateMap[me.id][levelID] === 'complete'
|
||||
else
|
||||
- lastLevelCompleted = false
|
||||
td= level.name.replace('Course: ', '')
|
||||
td= ++levelCount + '. ' + level.name.replace('Course: ', '')
|
||||
td
|
||||
if levelConceptMap[levelID]
|
||||
each concept in course.get('concepts')
|
||||
|
|
|
@ -10,7 +10,7 @@ block modal-body-content
|
|||
p(data-i18n="courses.invite_link_p_1")
|
||||
.alert.alert-info
|
||||
|
||||
strong= document.location.origin + "/courses/students?_cc=" + view.classroom.get('code')
|
||||
strong= document.location.origin + "/courses/students?_cc=" + (view.classroom.get('codeCamel') || view.classroom.get('code'))
|
||||
p(data-i18n="courses.invite_link_p_2")
|
||||
.form
|
||||
.form-group
|
||||
|
|
|
@ -16,11 +16,18 @@ block outer_content
|
|||
//a(href="https://itunes.apple.com/us/app/codecombat/id936523909?mt=8")
|
||||
// img(src="/images/pages/home/app_store_badge.svg")#apple-store-button
|
||||
|
||||
// Example of how to throw down a temporary news link
|
||||
//#news
|
||||
// a(href="/play/ladder/ace-of-coders", data-i18n="temp.ace_of_coders_tournament") New: play in the Ace of Coders tournament now!
|
||||
|
||||
#slogan(data-i18n="home.slogan")
|
||||
//- Example of how to throw down a temporary news link
|
||||
//- #news
|
||||
//- a(href="/play/ladder/ace-of-coders", data-i18n="temp.ace_of_coders_tournament") New: play in the Ace of Coders tournament now!
|
||||
|
||||
//- TODO: Delete HoC section and replace with slogan below
|
||||
#hour-of-code
|
||||
h1(data-i18n="home.hoc_title")
|
||||
div
|
||||
a.btn.btn-lg.btn-info.btn-class-code(href='/courses/students', data-i18n="home.hoc_class_code")
|
||||
a.btn.btn-lg.btn-success.btn-enter(href='/hoc', data-i18n="home.hoc_enter")
|
||||
|
||||
//- #slogan(data-i18n="home.slogan")
|
||||
|
||||
//- TODO: This does not work on IE8
|
||||
.alert.alert-danger.lt-ie9
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
if level.get('type', true) == 'hero-ladder'
|
||||
td.hero-portrait-cell(style="background-image: url(/file/db/thang.type/#{(session.get('heroConfig') || {}).thangType || '529ffbf1cf1818f2be000001'}/portrait.png)")
|
||||
td.rank-cell= rank + 1
|
||||
td.score-cell= Math.round(sessionStats.totalScore * 100)
|
||||
td.score-cell= Math.round((sessionStats.totalScore || session.get('totalScore') / 2) * 100)
|
||||
td(class='name-col-cell' + ((new RegExp('(Simple|Shaman|Brawler|Chieftain|Thoktar) AI')).test(session.get('creatorName')) ? ' ai' : ''))= session.get('creatorName') || "Anonymous"
|
||||
td.age-cell= moment(session.get('submitDate')).fromNow().replace('a few ', '')
|
||||
td.fight-cell
|
||||
|
@ -50,7 +50,7 @@
|
|||
if level.get('type', true) == 'hero-ladder'
|
||||
td.hero-portrait-cell(style="background-image: url(/file/db/thang.type/#{(session.get('heroConfig') || {}).thangType || '529ffbf1cf1818f2be000001'}/portrait.png)")
|
||||
td.rank-cell= session.rank
|
||||
td.score-cell= Math.round(sessionStats.totalScore * 100)
|
||||
td.score-cell= Math.round((sessionStats.totalScore || session.get('totalScore') / 2) * 100)
|
||||
td(class='name-col-cell' + ((new RegExp('(Simple|Shaman|Brawler|Chieftain|Thoktar) AI')).test(session.get('creatorName')) ? ' ai' : ''))= session.get('creatorName') || "Anonymous"
|
||||
td.age-cell= moment(session.get('submitDate')).fromNow().replace('a few ', '')
|
||||
td.fight-cell
|
||||
|
|
|
@ -5,11 +5,12 @@ block modal-header-content
|
|||
|
||||
block modal-body-content
|
||||
|
||||
h4.language-selection(data-i18n="ladder.select_your_language") Select your language!
|
||||
.form-group.select-group
|
||||
select#tome-language(name="language")
|
||||
for option in languages
|
||||
option(value=option.id selected=(language === option.id))= option.name
|
||||
if view.level.get('type') != 'course-ladder'
|
||||
h4.language-selection(data-i18n="ladder.select_your_language") Select your language!
|
||||
.form-group.select-group
|
||||
select#tome-language(name="language")
|
||||
for option in languages
|
||||
option(value=option.id selected=(language === option.id))= option.name
|
||||
|
||||
div#noob-view.secret
|
||||
a(href="/play/level/#{levelID}-tutorial" + (league ? "?league=" + league.id : "")).btn.btn-success.btn-block.btn-lg
|
||||
|
|
|
@ -22,7 +22,7 @@ else
|
|||
.level-name-area
|
||||
.level-label(data-i18n="play_level.level")
|
||||
.level-name(title=difficultyTitle || "")
|
||||
span= worldName.replace('Course: ', '')
|
||||
span= (campaignIndex ? (campaignIndex + 1) + '. ' : '') + worldName.replace('Course: ', '')
|
||||
if levelDifficulty
|
||||
sup.level-difficulty= levelDifficulty
|
||||
|
||||
|
|
|
@ -11,6 +11,23 @@ block modal-body-content
|
|||
if victoryText
|
||||
#victory-text= victoryText
|
||||
|
||||
if isCourseLevel
|
||||
if currentCourseName
|
||||
p
|
||||
span.spr.level-title(data-i18n="play_level.course")
|
||||
span.level-name= currentCourseName
|
||||
.container-fluid
|
||||
.row
|
||||
.col-md-6
|
||||
if currentLevelName
|
||||
.level-title(data-i18n="play_level.completed_level")
|
||||
.level-name= currentLevelName.replace('Course: ', '')
|
||||
.col-md-6
|
||||
if nextLevelName
|
||||
.level-title(data-i18n="play_level.next_level")
|
||||
.level-name= nextLevelName.replace('Course: ', '')
|
||||
br
|
||||
|
||||
#level-feedback
|
||||
div.rating.secret
|
||||
div.rating-label(data-i18n="play_level.victory_rate_the_level") Rate the level:
|
||||
|
|
|
@ -11,3 +11,7 @@ if !observing
|
|||
|
||||
button.btn.btn-lg.btn-illustrated.btn-success.done-button.secret
|
||||
span(data-i18n="play_level.done") Done
|
||||
|
||||
if view.autoSubmitsToLadder
|
||||
.hidden
|
||||
.ladder-submission-view
|
||||
|
|
|
@ -16,10 +16,14 @@ block content
|
|||
strong(data-i18n="teachers_survey.being_reviewed_2")
|
||||
else if existingRequest.get('status') === 'approved'
|
||||
p
|
||||
span.spr(data-i18n="teachers_survey.approved_1")
|
||||
strong.spr(data-i18n="teachers_survey.approved_2")
|
||||
span.spr(data-i18n="teachers_survey.approved_3")
|
||||
strong= existingRequest.get('properties').email
|
||||
span.spr(data-i18n="teachers_survey.approved_1")
|
||||
strong.spr(data-i18n="teachers_survey.approved_2")
|
||||
span.spr(data-i18n="teachers_survey.approved_3")
|
||||
strong= existingRequest.get('properties').email
|
||||
p
|
||||
span.spr(data-i18n="teachers_survey.approved_4")
|
||||
a(href='/courses', data-i18n="teachers_survey.approved_5")
|
||||
span.spl(data-i18n="teachers_survey.approved_6")
|
||||
else
|
||||
p
|
||||
span.spr(data-i18n="teachers_survey.denied_1")
|
||||
|
@ -29,15 +33,20 @@ block content
|
|||
a(href='mailto:team@codecombat.com') team@codecombat.com
|
||||
span.spl(data-i18n="teachers_survey.contact_2")
|
||||
else
|
||||
p(data-i18n="teachers_survey.description_1")
|
||||
p
|
||||
strong.spr Hour of Code Special!
|
||||
span Complete the survey by December 31st and enroll all your students in the paid courses for 2 months.
|
||||
p
|
||||
span.spr(data-i18n="teachers_survey.description_1")
|
||||
span.spr(data-i18n="teachers_survey.description_1b")
|
||||
a(href='/teachers', data-i18n="teachers_survey.description_2")
|
||||
span.spl(data-i18n="teachers_survey.description_3")
|
||||
p(data-i18n="teachers_survey.description_4")
|
||||
p.container-email-address
|
||||
label.control-label(data-i18n="teachers_survey.email")
|
||||
br
|
||||
input.control-label.input-email-address(type='text', value=view.email)
|
||||
span.email-address= view.email
|
||||
a(href='/account/settings') Change
|
||||
p.container-school
|
||||
label.control-label(data-i18n="teachers_survey.school")
|
||||
br
|
||||
|
|
|
@ -6,17 +6,10 @@ block content
|
|||
p
|
||||
strong Hi Teachers!
|
||||
p We're excited to participate in Hour of Code this year!
|
||||
p We've set up an Introduction to Computer Science course, just for you.
|
||||
p
|
||||
strong How to use CodeCombat with your students:
|
||||
ol
|
||||
li
|
||||
span.spr Navigate to the
|
||||
a(href='/courses/teachers?hoc=true') Courses
|
||||
span.spl page
|
||||
li Click the green 'Get FREE course' button under Introduction to Computer Science
|
||||
li Follow the enrollment instructions
|
||||
li Add students via the 'Add Students' tab
|
||||
span.spr Navigate to the
|
||||
a(href='/courses/teachers?hoc=true') CodeCombat Courses
|
||||
span.spl page to get started.
|
||||
p
|
||||
span.spr If you have any problems, please email
|
||||
a(href='mailto:team@codecombat.com') team@codecombat.com
|
||||
|
@ -25,144 +18,27 @@ block content
|
|||
h2(data-i18n="teachers.more_info")
|
||||
p(data-i18n="teachers.intro_1")
|
||||
p(data-i18n="teachers.intro_2")
|
||||
|
||||
|
||||
h3(data-i18n="teachers.free_title")
|
||||
if me.isOnPremiumServer()
|
||||
p(data-i18n="teachers.cost_premium_server")
|
||||
else
|
||||
p(data-i18n="teachers.free_1")
|
||||
p(data-i18n="teachers.free_2")
|
||||
|
||||
p
|
||||
span.spr(data-i18n="teachers.free_3")
|
||||
a(href='/courses', data-i18n="teachers.free_4")
|
||||
span(data-i18n="teachers.free_5")
|
||||
p(data-i18n="teachers.free_6")
|
||||
p
|
||||
span.spr For more details, please email
|
||||
a(href='mailto:team@codecombat.com') team@codecombat.com
|
||||
|
||||
h3.teachers-title(data-i18n="teachers.teacher_subs_title")
|
||||
p
|
||||
strong.spr Hour of Code Special!
|
||||
span Complete the survey by December 31st and enroll all your students in the paid courses for 2 months.
|
||||
p(data-i18n="teachers.teacher_subs_0")
|
||||
p
|
||||
span.spr(data-i18n="teachers.teacher_subs_1")
|
||||
a(href='/teachers/freetrial', data-i18n="teachers.teacher_subs_2")
|
||||
span.spl(data-i18n="teachers.teacher_subs_3")
|
||||
|
||||
h3(data-i18n="teachers.sub_includes_title")
|
||||
p(data-i18n="teachers.sub_includes_1")
|
||||
ul
|
||||
li(data-i18n="teachers.sub_includes_2")
|
||||
li(data-i18n="teachers.sub_includes_3")
|
||||
li(data-i18n="teachers.sub_includes_4")
|
||||
li(data-i18n="teachers.sub_includes_5")
|
||||
li(data-i18n="teachers.sub_includes_6")
|
||||
li(data-i18n="teachers.sub_includes_7")
|
||||
|
||||
h3(data-i18n="teachers.who_for_title")
|
||||
p(data-i18n="teachers.who_for_1")
|
||||
p(data-i18n="teachers.who_for_2")
|
||||
|
||||
h3(data-i18n="teachers.monitor_progress_title")
|
||||
p
|
||||
span.spr(data-i18n="teachers.monitor_progress_1")
|
||||
a(href='/clans', data-i18n="clans.clan")
|
||||
span.spl(data-i18n="teachers.monitor_progress_2")
|
||||
p
|
||||
span.spr(data-i18n="teachers.monitor_progress_3")
|
||||
a(href='/clans', data-i18n="clans.clans")
|
||||
span.spl(data-i18n="teachers.monitor_progress_4")
|
||||
p(data-i18n="teachers.monitor_progress_5")
|
||||
h4(data-i18n="teachers.sub_includes_7")
|
||||
ul
|
||||
li
|
||||
strong(data-i18n="clans.track_concepts1")
|
||||
span.spl(data-i18n="clans.track_concepts2a")
|
||||
li(data-i18n="clans.track_concepts3a")
|
||||
li
|
||||
span(data-i18n="clans.track_concepts4a")
|
||||
strong.spl(data-i18n="clans.track_concepts5")
|
||||
li(data-i18n="clans.track_concepts6a")
|
||||
li
|
||||
strong(data-i18n="clans.track_concepts7")
|
||||
span.spl(data-i18n="clans.track_concepts8")
|
||||
p
|
||||
img(src='/images/pages/clans/dashboard_preview.png' height='400')
|
||||
p
|
||||
span.spr(data-i18n="teachers.private_clans_2")
|
||||
a(href='/clans', data-i18n="clans.clan")
|
||||
span(data-i18n="teachers.private_clans_3")
|
||||
p(data-i18n="clans.private_require_sub")
|
||||
|
||||
h3(data-i18n="teachers.material_title")
|
||||
if me.isOnPremiumServer()
|
||||
p(data-i18n="teachers.material_premium_server")
|
||||
else
|
||||
p(data-i18n="teachers.material_1")
|
||||
|
||||
h3(data-i18n="teachers.concepts_title")
|
||||
|
||||
//- TODO: i18n for concepts?
|
||||
|
||||
table.table.table-condensed.concepts-table
|
||||
thead
|
||||
tr
|
||||
th
|
||||
a(href='/play/dungeon') Kithgard Dungeon
|
||||
th
|
||||
a(href='/play/forest') Backwoods Forest
|
||||
th
|
||||
a(href='/play/desert') Sarven Desert
|
||||
th
|
||||
a(href='/play/mountain') Cloudrip Mountain
|
||||
tbody
|
||||
tr
|
||||
td Basic Syntax
|
||||
td If Statements
|
||||
td Arithmetic
|
||||
td Object Literals
|
||||
tr
|
||||
td Methods
|
||||
td Relational Operators
|
||||
td While Loops
|
||||
td For Loops
|
||||
tr
|
||||
td Parameters
|
||||
td Object Properties
|
||||
td Break Statements
|
||||
td Functions
|
||||
tr
|
||||
td Strings
|
||||
td Input Handling
|
||||
td Arrays
|
||||
td Drawing
|
||||
tr
|
||||
td Loops
|
||||
td
|
||||
td String Comparison
|
||||
td Modulo
|
||||
tr
|
||||
td Variables
|
||||
td
|
||||
td Finding Min/Max
|
||||
td
|
||||
|
||||
h3(data-i18n="teachers.how_much_title")
|
||||
p
|
||||
span(data-i18n="teachers.how_much_1")
|
||||
span.spr.spl
|
||||
a(href='/account/subscription', data-i18n="teachers.how_much_2")
|
||||
span.spr.spl(data-i18n="teachers.how_much_3")
|
||||
p
|
||||
span.spr(data-i18n="teachers.how_much_5")
|
||||
a(href='mailto:team@codecombat.com') team@codecombat.com
|
||||
span.spl(data-i18n="teachers.how_much_6")
|
||||
p(data-i18n="teachers.how_much_4")
|
||||
h4
|
||||
a(href='/account/subscription', data-i18n="subscribe.group_discounts")
|
||||
p(data-i18n="subscribe.group_discounts_1")
|
||||
table.table.table-condensed.discount-table
|
||||
tr
|
||||
td(data-i18n="subscribe.group_discounts_1st")
|
||||
td(data-i18n="subscribe.group_discounts_full")
|
||||
tr
|
||||
td(data-i18n="subscribe.group_discounts_2nd")
|
||||
td(data-i18n="subscribe.group_discounts_20")
|
||||
tr
|
||||
td(data-i18n="subscribe.group_discounts_12th")
|
||||
td(data-i18n="subscribe.group_discounts_40")
|
||||
|
||||
h3(data-i18n="teachers.more_info_title")
|
||||
p
|
||||
span.spr(data-i18n="teachers.more_info_1")
|
||||
|
|
|
@ -30,7 +30,6 @@ module.exports = class TeachersFreeTrialView extends RootView
|
|||
$('.radio-other').prop("checked", true)
|
||||
|
||||
onClickSubmit: (e) ->
|
||||
email = $('.input-email-address').val()
|
||||
school = $('.input-school').val()
|
||||
location = $('.input-location').val()
|
||||
age = $('input[name=age]:checked').val()
|
||||
|
@ -46,10 +45,6 @@ module.exports = class TeachersFreeTrialView extends RootView
|
|||
$('.container-num-students').removeClass('has-error')
|
||||
$('.container-heard-about').removeClass('has-error')
|
||||
$('.error-message').hide()
|
||||
unless email
|
||||
$('.container-email-address').addClass('has-error')
|
||||
$('.error-message').show()
|
||||
return
|
||||
unless school
|
||||
$('.container-school').addClass('has-error')
|
||||
$('.error-message').show()
|
||||
|
@ -75,7 +70,7 @@ module.exports = class TeachersFreeTrialView extends RootView
|
|||
trialRequest = new TrialRequest
|
||||
type: 'subscription'
|
||||
properties:
|
||||
email: email
|
||||
email: @email
|
||||
school: school
|
||||
location: location
|
||||
age: age
|
||||
|
|
|
@ -4,9 +4,3 @@ template = require 'templates/teachers'
|
|||
module.exports = class TeachersView extends RootView
|
||||
id: 'teachers-view'
|
||||
template: template
|
||||
|
||||
constructor: ->
|
||||
super()
|
||||
_.defer ->
|
||||
# Redirect to HoC version of /courses/teachers until we update the /teachers landing page
|
||||
application.router.navigate "/courses/teachers?hoc=true", trigger: true
|
||||
|
|
|
@ -311,7 +311,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
|
|||
getAnalyticsInvoices = (done) =>
|
||||
@updateFetchDataState "Fetching invoices #{Object.keys(invoices).length}..."
|
||||
options =
|
||||
url: '/db/analytics_stripe_invoice/-/all'
|
||||
url: '/db/analytics.stripe.invoice/-/all'
|
||||
method: 'GET'
|
||||
options.error = (model, response, options) =>
|
||||
return if @destroyed
|
||||
|
|
|
@ -18,7 +18,7 @@ module.exports = class TrialRequestsView extends RootView
|
|||
statusA = a.get('status')
|
||||
statusB = b.get('status')
|
||||
if statusA is 'submitted' and statusB is 'submitted'
|
||||
if a.get('_id') < b.get('_id')
|
||||
if a.get('created') < b.get('created')
|
||||
-1
|
||||
else
|
||||
1
|
||||
|
@ -30,7 +30,7 @@ module.exports = class TrialRequestsView extends RootView
|
|||
-1
|
||||
else
|
||||
1
|
||||
@trialRequests = new CocoCollection([], { url: '/db/trial.request?conditions[sort]=-_id&conditions[limit]=500', model: TrialRequest, comparator: sortRequests })
|
||||
@trialRequests = new CocoCollection([], { url: '/db/trial.request?conditions[sort]=-created&conditions[limit]=500', model: TrialRequest, comparator: sortRequests })
|
||||
@supermodel.loadCollection(@trialRequests, 'trial-requests', {cache: false})
|
||||
|
||||
getRenderData: ->
|
||||
|
|
|
@ -100,7 +100,7 @@ module.exports = class DiplomatView extends ContributeClassView
|
|||
fr: ['Anon', 'Armaldio', 'ChrisLightman', 'Elfisen', 'Feugy', 'MartinDelille', 'Oaugereau', 'Xeonarno', 'dc55028', 'jaybi', 'pstweb', 'veritable', 'xavismeh'] # français, French
|
||||
ja: ['Coderaulic', 'g1itch', 'kengos', 'treby'] # 日本語, Japanese
|
||||
ar: ['5y', 'ahmed80dz'] # العربية, Arabic
|
||||
'pt-BR': ['Bia41', 'Gutenberg Barros', 'Kieizroe', 'Matthew Burt', 'brunoporto', 'cassiocardoso', 'jklemm'] # português do Brasil, Portuguese (Brazil)
|
||||
'pt-BR': ['Bia41', 'Gutenberg Barros', 'Kieizroe', 'Matthew Burt', 'brunoporto', 'cassiocardoso', 'jklemm', 'Arkhad'] # português do Brasil, Portuguese (Brazil)
|
||||
'pt-PT': ['Imperadeiro98', 'Matthew Burt', 'ProgramadorLucas', 'ReiDuKuduro', 'batista', 'gutierri'] # Português (Portugal), Portuguese (Portugal)
|
||||
pl: ['Anon', 'Kacper Ciepielewski', 'TigroTigro', 'kvasnyk'] # język polski, Polish
|
||||
it: ['AlessioPaternoster', 'flauta', 'Atomk'] # italiano, Italian
|
||||
|
|
|
@ -28,7 +28,7 @@ module.exports = class StudentCoursesView extends RootView
|
|||
@supermodel.loadCollection(@classrooms, 'classrooms', { data: {memberID: me.id} })
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@supermodel.loadCollection(@courses, 'courses')
|
||||
|
||||
|
||||
onLoaded: ->
|
||||
if (@classCode = utils.getQueryVariable('_cc', false)) and not me.isAnonymous()
|
||||
@joinClass()
|
||||
|
@ -63,17 +63,17 @@ module.exports = class StudentCoursesView extends RootView
|
|||
onJoinClassroomSuccess: (data, textStatus, jqxhr) ->
|
||||
classroom = new Classroom(data)
|
||||
application.tracker?.trackEvent 'Joined classroom', {
|
||||
classroomID: classroom.id,
|
||||
classroomID: classroom.id,
|
||||
classroomName: classroom.get('name')
|
||||
ownerID: classroom.get('ownerID')
|
||||
}
|
||||
@classrooms.add(classroom)
|
||||
@render()
|
||||
|
||||
|
||||
classroomCourseInstances = new CocoCollection([], { url: "/db/course_instance", model: CourseInstance })
|
||||
classroomCourseInstances.fetch({ data: {classroomID: classroom.id} })
|
||||
@listenToOnce classroomCourseInstances, 'sync', ->
|
||||
|
||||
|
||||
# join any course instances in the classroom which are free to join
|
||||
jqxhrs = []
|
||||
for courseInstance in classroomCourseInstances.models
|
||||
|
|
|
@ -161,7 +161,9 @@ module.exports = class TeacherCoursesView extends RootView
|
|||
return
|
||||
|
||||
user = @usersToRedeem.first()
|
||||
prepaid = @prepaids.find (prepaid) -> prepaid.openSpots()
|
||||
|
||||
prepaid = @prepaids.find((prepaid) -> prepaid.get('properties').endDate? and prepaid.openSpots())
|
||||
prepaid = @prepaids.find((prepaid) -> prepaid.openSpots()) unless prepaid
|
||||
$.ajax({
|
||||
method: 'POST'
|
||||
url: _.result(prepaid, 'url') + '/redeemers'
|
||||
|
|
|
@ -92,7 +92,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
onLoaded: ->
|
||||
@toSave.add @campaign if @campaign.hasLocalChanges()
|
||||
campaignLevels = $.extend({}, @campaign.get('levels'))
|
||||
for level in @levels.models
|
||||
for level, levelIndex in @levels.models
|
||||
levelOriginal = level.get('original')
|
||||
campaignLevel = campaignLevels[levelOriginal]
|
||||
continue if not campaignLevel
|
||||
|
@ -129,6 +129,8 @@ module.exports = class CampaignEditorView extends RootView
|
|||
delete campaignLevel.unlocks
|
||||
# Save campaign to level, unless it's a course campaign, since we reuse hero levels for course levels.
|
||||
campaignLevel.campaign = @campaign.get 'slug' if @campaign.get('type', true) isnt 'course'
|
||||
# Save campaign index to level if it's a course campaign, since we show linear level order numbers for course levels.
|
||||
campaignLevel.campaignIndex = (@levels.models.length - levelIndex - 1) if @campaign.get('type', true) is 'course'
|
||||
campaignLevels[levelOriginal] = campaignLevel
|
||||
|
||||
@campaign.set('levels', campaignLevels)
|
||||
|
|
|
@ -280,7 +280,10 @@ module.exports = class LadderTabView extends CocoView
|
|||
consolidateFriends: ->
|
||||
allFriendSessions = (@facebookFriendSessions or []).concat(@gplusFriendSessions or [])
|
||||
sessions = _.uniq allFriendSessions, false, (session) -> session._id
|
||||
sessions = _.sortBy sessions, 'totalScore'
|
||||
if @options.league
|
||||
sessions = _.sortBy sessions, (session) -> _.find(session.leagues, leagueID: @options.league.id)?.stats.totalScore ? (session.totalScore / 2)
|
||||
else
|
||||
sessions = _.sortBy sessions, 'totalScore'
|
||||
sessions.reverse()
|
||||
sessions
|
||||
|
||||
|
|
|
@ -315,10 +315,6 @@ module.exports = class CampaignView extends RootView
|
|||
foundNext = false
|
||||
dontPointTo = ['lost-viking', 'kithgard-mastery'] # Challenge levels we don't want most players bashing heads against
|
||||
subscriptionPrompts = [{slug: 'boom-and-bust', unless: 'defense-of-plainswood'}]
|
||||
if me.getSubscriptionPromptGroup() is 'favorable-odds'
|
||||
subscriptionPrompts.push slug: 'favorable-odds', unless: 'the-raised-sword'
|
||||
if me.getSubscriptionPromptGroup() is 'tactical-strike'
|
||||
subscriptionPrompts.push slug: 'tactical-strike', unless: 'a-mayhem-of-munchkins'
|
||||
for level in levels
|
||||
# Iterate through all levels in order and look to find the first unlocked one that meets all our criteria for being pointed out as the next level.
|
||||
level.nextLevels = (reward.level for reward in level.rewards ? [] when reward.level)
|
||||
|
|
|
@ -268,6 +268,7 @@ module.exports = class SpectateLevelView extends RootView
|
|||
|
||||
onNextGamePressed: (e) ->
|
||||
@fetchRandomSessionPair (err, data) =>
|
||||
return if @destroyed
|
||||
if err? then return console.log "There was an error fetching the random session pair: #{data}"
|
||||
@sessionOne = data[0]._id
|
||||
@sessionTwo = data[1]._id
|
||||
|
|
|
@ -61,6 +61,7 @@ module.exports = class ControlBarView extends CocoView
|
|||
getRenderData: (c={}) ->
|
||||
super c
|
||||
c.worldName = @worldName
|
||||
c.campaignIndex = @level.get('campaignIndex') if @level.get('type') is 'course'
|
||||
c.multiplayerEnabled = @session.get('multiplayer')
|
||||
c.ladderGame = @level.get('type') in ['ladder', 'hero-ladder', 'course-ladder']
|
||||
if c.isMultiplayerLevel = @isMultiplayerLevel
|
||||
|
|
|
@ -12,12 +12,12 @@ module.exports = class LevelGoalsView extends CocoView
|
|||
template: template
|
||||
className: 'secret expanded'
|
||||
playbackEnded: false
|
||||
mouseEntered: false
|
||||
|
||||
subscriptions:
|
||||
'goal-manager:new-goal-states': 'onNewGoalStates'
|
||||
'tome:cast-spells': 'onTomeCast'
|
||||
'level:set-letterbox': 'onSetLetterbox'
|
||||
'level:set-playing': 'onSetPlaying'
|
||||
'surface:playback-restarted': 'onSurfacePlaybackRestarted'
|
||||
'surface:playback-ended': 'onSurfacePlaybackEnded'
|
||||
|
||||
|
@ -84,6 +84,13 @@ module.exports = class LevelGoalsView extends CocoView
|
|||
@$el.find('.goal-status').addClass('secret')
|
||||
@$el.find('.goal-status.running').removeClass('secret')
|
||||
|
||||
onSetPlaying: (e) ->
|
||||
return unless e.playing
|
||||
# Automatically hide it while we replay
|
||||
@mouseEntered = false
|
||||
@expanded = true
|
||||
@updatePlacement()
|
||||
|
||||
onSurfacePlaybackRestarted: ->
|
||||
@playbackEnded = false
|
||||
@$el.removeClass 'brighter'
|
||||
|
@ -105,11 +112,17 @@ module.exports = class LevelGoalsView extends CocoView
|
|||
@normalHeight = @$el.outerHeight()
|
||||
|
||||
updatePlacement: ->
|
||||
expand = @playbackEnded or @mouseEntered
|
||||
# Expand it if it's at the end. Mousing over reverses this.
|
||||
expand = @playbackEnded isnt @mouseEntered
|
||||
return if expand is @expanded
|
||||
@updateHeight()
|
||||
sound = if expand then 'goals-expand' else 'goals-collapse'
|
||||
top = if expand then -5 else 41 - (@normalHeight ? @$el.outerHeight())
|
||||
if expand
|
||||
top = -5
|
||||
else
|
||||
height = @normalHeight
|
||||
height = @$el.outerHeight() if not height or @playbackEnded
|
||||
top = 41 - height
|
||||
@$el.css 'top', top
|
||||
if @soundTimeout
|
||||
# Don't play the sound we were going to play after all; the transition has reversed.
|
||||
|
|
|
@ -11,6 +11,7 @@ LadderSubmissionView = require 'views/play/common/LadderSubmissionView'
|
|||
AudioPlayer = require 'lib/AudioPlayer'
|
||||
User = require 'models/User'
|
||||
utils = require 'core/utils'
|
||||
Course = require 'models/Course'
|
||||
Level = require 'models/Level'
|
||||
LevelFeedback = require 'models/LevelFeedback'
|
||||
|
||||
|
@ -65,6 +66,9 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
if @level.get('type', true) is 'course' and nextLevel = @level.get('nextLevel')
|
||||
@nextLevel = new Level().setURL "/db/level/#{nextLevel.original}/version/#{nextLevel.majorVersion}"
|
||||
@nextLevel = @supermodel.loadModel(@nextLevel, 'level').model
|
||||
if @courseID
|
||||
@course = new Course().setURL "/db/course/#{@courseID}"
|
||||
@course = @supermodel.loadModel(@course, 'course').model
|
||||
if @level.get('type', true) in ['course', 'course-ladder']
|
||||
@saveReviewEventually = _.debounce(@saveReviewEventually, 2000)
|
||||
@loadExistingFeedback()
|
||||
|
@ -120,8 +124,10 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
@thangTypes[thangTypeOriginal] = @supermodel.loadModel(thangType, 'thang').model
|
||||
|
||||
@newEarnedAchievements = []
|
||||
hadOneCompleted = false
|
||||
for achievement in @achievements.models
|
||||
continue unless achievement.completed
|
||||
hadOneCompleted = true
|
||||
ea = new EarnedAchievement({
|
||||
collection: achievement.get('collection')
|
||||
triggeredBy: @session.id
|
||||
|
@ -137,7 +143,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
@updateSavingProgressStatus()
|
||||
me.fetch cache: false unless me.loading
|
||||
|
||||
@readyToContinue = true if not @achievements.models.length
|
||||
@readyToContinue = true unless hadOneCompleted
|
||||
|
||||
# have to use a something resource because addModelResource doesn't handle models being upserted/fetched via POST like we're doing here
|
||||
@newEarnedAchievementsResource = @supermodel.addSomethingResource('earned achievements') if @newEarnedAchievements.length
|
||||
|
@ -207,6 +213,10 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
c.showLeaderboard = @level.get('scoreTypes')?.length > 0 and @level.get('type', true) isnt 'course'
|
||||
|
||||
c.showReturnToCourse = not c.showLeaderboard and not me.get('anonymous') and @level.get('type', true) in ['course', 'course-ladder']
|
||||
c.isCourseLevel = @level.get('type', true) in ['course']
|
||||
c.currentCourseName = @course?.get('name')
|
||||
c.currentLevelName = @level?.get('name')
|
||||
c.nextLevelName = @nextLevel?.get('name')
|
||||
|
||||
return c
|
||||
|
||||
|
@ -444,10 +454,9 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
viewArgs.push @courseInstanceID if @courseInstanceID
|
||||
else if @level.get('type', true) is 'course-ladder'
|
||||
leagueID = @getQueryVariable 'league'
|
||||
link = "/play/ladder/"+@level.get('slug')+"/course/"+leagueID
|
||||
Backbone.Mediator.publish 'router:navigate', {
|
||||
route: link
|
||||
}
|
||||
link = "/play/ladder/#{@level.get('slug')}"
|
||||
link += "/course/#{leagueID}" if leagueID
|
||||
Backbone.Mediator.publish 'router:navigate', route: link
|
||||
return
|
||||
else
|
||||
viewClass = require 'views/play/CampaignView'
|
||||
|
|
|
@ -31,6 +31,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
@updateReplayabilityInterval = setInterval @updateReplayability, 1000
|
||||
@observing = options.session.get('creator') isnt me.id
|
||||
@loadMirrorSession() if @options.level.get('slug') in ['ace-of-coders']
|
||||
@autoSubmitsToLadder = @options.level.get('slug') in ['wakka-maul']
|
||||
|
||||
destroy: ->
|
||||
clearInterval @updateReplayabilityInterval
|
||||
|
@ -101,10 +102,10 @@ module.exports = class CastButtonView extends CocoView
|
|||
@casting = false
|
||||
if @hasCastOnce # Don't play this sound the first time
|
||||
@playSound 'cast-end', 0.5
|
||||
# Worked great for live Ace of Coders tournament, but probably annoying for asynchronous tournament mode.
|
||||
#myHeroID = if me.team is 'ogres' then 'Hero Placeholder 1' else 'Hero Placeholder'
|
||||
#if @ladderSubmissionView and not e.world.thangMap[myHeroID]?.errorsOut
|
||||
# _.delay (=> @ladderSubmissionView?.rankSession()), 1000 if @ladderSubmissionView
|
||||
# Worked great for live beginner tournaments, but probably annoying for asynchronous tournament mode.
|
||||
myHeroID = if me.team is 'ogres' then 'Hero Placeholder 1' else 'Hero Placeholder'
|
||||
if @autoSubmitsToLadder and not e.world.thangMap[myHeroID]?.errorsOut
|
||||
_.delay (=> @ladderSubmissionView?.rankSession()), 1000 if @ladderSubmissionView
|
||||
@hasCastOnce = true
|
||||
@updateCastButton()
|
||||
@world = e.world
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// subscriptionPromptGroup A/B Results
|
||||
// Test started 2015-09-18, ended 2015-11-22
|
||||
// Final results:
|
||||
// Subscribers by group: {
|
||||
// "tactical-strike": 246,
|
||||
// "boom-and-bust": 255,
|
||||
// "favorable-odds": 303
|
||||
// }
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
// Except actually now you run these scripts on the analytics server itself.
|
||||
// https://docs.google.com/document/d/1d5mOsTjioX2KRNAqhWXdGyBevuhxSPH1xX7UWlYPpwk/edit#
|
||||
|
||||
load('abTestHelpers.js');
|
||||
|
||||
var scriptStartTime = new Date();
|
||||
try {
|
||||
var logDB = new Mongo("localhost").getDB("analytics");
|
||||
var startDay = '2015-09-18';
|
||||
log("Today is " + new Date().toISOString().substr(0, 10));
|
||||
log("Start day is " + startDay);
|
||||
|
||||
var eventFunnel = ['Started Level', 'Saw Victory'];
|
||||
var levelSlugs = ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith', 'true-names', 'favorable-odds', 'the-raised-sword', 'lowly-kithmen', 'closing-the-distance', 'tactical-strike', 'a-mayhem-of-munchkins', 'kithgard-gates', 'boom-and-bust', 'defense-of-plainswood'];
|
||||
|
||||
// getSubscriptionPromptGroup
|
||||
var testGroupFn = function (testGroupNumber) {
|
||||
var group = testGroupNumber % 3;
|
||||
if (group === 0) return 'favorable-odds';
|
||||
if (group === 1) return 'tactical-strike';
|
||||
if (group === 2) return 'boom-and-bust';
|
||||
};
|
||||
|
||||
var funnelData = getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs, logDB);
|
||||
|
||||
printFunnelData(funnelData, function (day, level, browser, group, started, finished, rate) {
|
||||
if (day && level && browser && group) {
|
||||
log(day + "\t" + group + "\t" + started + "\t" + finished + "\t" + rate.toFixed(2));
|
||||
}
|
||||
else if (level && browser && group) {
|
||||
log(level + "\t" + browser + "\t" + (browser.length < 8 ? "\t": "") + group + "\t" + started + "\t" + finished + "\t" + rate.toFixed(2));
|
||||
}
|
||||
else if (level && group) {
|
||||
log(level + "\t" + group + "\t" + started + "\t" + finished + "\t" + rate.toFixed(2));
|
||||
}
|
||||
else if (group) {
|
||||
log(group + "\t" + started + "\t" + finished + "\t" + rate.toFixed(2));
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(err) {
|
||||
log("ERROR: " + err);
|
||||
printjson(err);
|
||||
}
|
||||
finally {
|
||||
log("Script runtime: " + (new Date() - scriptStartTime));
|
||||
}
|
|
@ -14,6 +14,7 @@ var analyticsStringIDCache = {};
|
|||
// *** Helper functions ***
|
||||
|
||||
function log(str) {
|
||||
str = Array.prototype.slice.call(arguments).join(' ');
|
||||
print(new Date().toISOString() + " " + str);
|
||||
}
|
||||
|
||||
|
@ -24,7 +25,7 @@ function objectIdWithTimestamp(timestamp) {
|
|||
var hexSeconds = Math.floor(timestamp/1000).toString(16);
|
||||
// Create an ObjectId with that hex timestamp
|
||||
var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");
|
||||
return constructedObjectId
|
||||
return constructedObjectId;
|
||||
}
|
||||
|
||||
function getAnalyticsString(strID) {
|
||||
|
@ -47,7 +48,7 @@ function getAnalyticsStringID(str) {
|
|||
throw new Error("ERROR: Did not find analytics.strings insert for: " + str);
|
||||
}
|
||||
|
||||
function getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs) {
|
||||
function getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs, logDB) {
|
||||
if (!startDay || !eventFunnel || eventFunnel.length === 0 || !testGroupFn) return {};
|
||||
|
||||
// log('getFunnelData:');
|
||||
|
@ -56,13 +57,14 @@ function getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs) {
|
|||
|
||||
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
|
||||
var queryParams = {$and: [{_id: {$gte: startObj}},{"event": {$in: eventFunnel}}]};
|
||||
var cursor = db['analytics.log.events'].find(queryParams);
|
||||
var cursor = (logDB.log || db['analytics.log.events']).find(queryParams);
|
||||
|
||||
log("Fetching events..");
|
||||
// Map ordering: level, user, event, day
|
||||
var levelUserEventMap = {};
|
||||
var levelSessions = [];
|
||||
var users = [];
|
||||
var eventsCounted = 0;
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var created = doc._id.getTimestamp().toISOString();
|
||||
|
@ -72,10 +74,12 @@ function getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs) {
|
|||
var user = doc.user.valueOf();
|
||||
var level = 'n/a';
|
||||
var ls = null;
|
||||
if (eventsCounted++ % 10000 == 0)
|
||||
log("Counted", eventsCounted, "events, up to", created);
|
||||
|
||||
// TODO: Switch to properties.levelID for 'Saw Victory'
|
||||
if (event === 'Saw Victory' && properties.level) level = properties.level.toLowerCase().replace(/ /g, '-');
|
||||
else if (properties.levelID) level = properties.levelID
|
||||
else if (properties.levelID) level = properties.levelID;
|
||||
|
||||
if (levelSlugs && levelSlugs.indexOf(level) < 0) continue;
|
||||
|
||||
|
@ -101,33 +105,52 @@ function getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs) {
|
|||
|
||||
log("Fetching users..");
|
||||
var userGroupMap = {};
|
||||
cursor = db['users'].find({_id : {$in: users}});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var user = doc._id.valueOf();
|
||||
userGroupMap[user] = testGroupFn(doc.testGroupNumber);
|
||||
var groupSubscribedMap = {};
|
||||
var countedSubscriberMap = {};
|
||||
for (var userOffset = 0; userOffset < users.length; userOffset += 1000) {
|
||||
cursor = db['users'].find({_id : {$in: users.slice(userOffset, userOffset + 1000)}});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var user = doc._id.valueOf();
|
||||
userGroupMap[user] = group = testGroupFn(doc.testGroupNumber);
|
||||
if (!countedSubscriberMap[doc._id + ''] &&
|
||||
doc.created >= ISODate(startDay + "T00:00:00.000Z") &&
|
||||
doc.stripe &&
|
||||
doc.stripe.customerID &&
|
||||
doc.purchased &&
|
||||
doc.purchased.gems &&
|
||||
!doc.stripe.free
|
||||
) {
|
||||
countedSubscriberMap[doc._id + ''] = true;
|
||||
groupSubscribedMap[group] = (groupSubscribedMap[group] || 0) + 1;
|
||||
}
|
||||
}
|
||||
log("Fetched", Math.min(userOffset, users.length), "users");
|
||||
}
|
||||
// printjson(userGroupMap);
|
||||
|
||||
log("Fetching level sessions..");
|
||||
var lsBrowserMap = {};
|
||||
var userBrowserMap = {};
|
||||
cursor = db['level.sessions'].find({_id : {$in: levelSessions}});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var user = doc._id.valueOf();
|
||||
var browser = doc.browser;
|
||||
var browserInfo = '';
|
||||
if (browser && browser.platform) {
|
||||
browserInfo += browser.platform;
|
||||
}
|
||||
if (browser && browser.name) {
|
||||
browserInfo += browser.name;
|
||||
}
|
||||
if (browserInfo.length > 0) {
|
||||
lsBrowserMap[doc._id.valueOf()] = browserInfo;
|
||||
userBrowserMap[user] = browserInfo;
|
||||
for (var sessionOffset = 0; sessionOffset < levelSessions.length; sessionOffset += 1000) {
|
||||
cursor = db['level.sessions'].find({_id : {$in: levelSessions.slice(sessionOffset, sessionOffset + 1000)}});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var user = doc._id.valueOf();
|
||||
var browser = doc.browser;
|
||||
var browserInfo = '';
|
||||
if (browser && browser.platform) {
|
||||
browserInfo += browser.platform;
|
||||
}
|
||||
if (browser && browser.name) {
|
||||
browserInfo += browser.name;
|
||||
}
|
||||
if (browserInfo.length > 0) {
|
||||
lsBrowserMap[doc._id.valueOf()] = browserInfo;
|
||||
userBrowserMap[user] = browserInfo;
|
||||
}
|
||||
}
|
||||
log("Fetched", Math.min(sessionOffset, levelSessions.length), "sessions");
|
||||
}
|
||||
// printjson(lsBrowserMap);
|
||||
|
||||
|
@ -238,6 +261,8 @@ function getFunnelData(startDay, eventFunnel, testGroupFn, levelSlugs) {
|
|||
return a.group < b.group ? -1 : 1;
|
||||
});
|
||||
|
||||
log("Subscribers by group:", JSON.stringify(groupSubscribedMap, null, 2));
|
||||
|
||||
return funnelData;
|
||||
}
|
||||
|
||||
|
|
44
scripts/analytics/mongodb/queries/classroomCounts.js
Normal file
44
scripts/analytics/mongodb/queries/classroomCounts.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Print out classrooms ordered by size
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
|
||||
var userClassroomMap = {};
|
||||
var cursor = db.classrooms.find({$where: 'this.members.length > 40'}, {ownerID: 1, name: 1, members: 1});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var userID = doc.ownerID.valueOf();
|
||||
if (!userClassroomMap[userID]) userClassroomMap[userID] = [];
|
||||
userClassroomMap[userID].push({
|
||||
classroomID: doc._id,
|
||||
className: doc.name,
|
||||
count: doc.members.length
|
||||
});
|
||||
}
|
||||
|
||||
var userIDs = [];
|
||||
for (var userID in userClassroomMap) {
|
||||
userIDs.push(new ObjectId(userID));
|
||||
}
|
||||
|
||||
var classrooms = [];
|
||||
cursor = db.users.find({_id: {$in: userIDs}}, {email: 1});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var userID = doc._id.valueOf();
|
||||
for (var i = 0; i < userClassroomMap[userID].length; i++) {
|
||||
classrooms.push({
|
||||
ownerID: userID,
|
||||
email: doc.email,
|
||||
classroomID: userClassroomMap[userID][i].classroomID,
|
||||
className: userClassroomMap[userID][i].className,
|
||||
count: userClassroomMap[userID][i].count
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
classrooms.sort(function(a, b) { return a.count > b.count ? -1 : 1;});
|
||||
for (var i = 0; i < classrooms.length; i++) {
|
||||
print(classrooms[i].count, classrooms[i].className, classrooms[i].email, classrooms[i].classroomID.valueOf());
|
||||
}
|
||||
print("Total classes:", classrooms.length);
|
|
@ -101,10 +101,10 @@ try {
|
|||
}
|
||||
}
|
||||
|
||||
log("Getting monthly recurring revenue counts...");
|
||||
log("Getting recurring revenue counts...");
|
||||
var recurringRevenueCounts = getRecurringRevenueCounts(startDay);
|
||||
// printjson(recurringRevenueCounts);
|
||||
log("Inserting monthly recurring revenue counts...");
|
||||
log("Inserting recurring revenue counts...");
|
||||
for (var event in recurringRevenueCounts) {
|
||||
for (var day in recurringRevenueCounts[event]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
|
@ -674,9 +674,9 @@ function getRecurringRevenueCounts(startDay) {
|
|||
dailyRevenueCounts['DRR monthly subs'][day] += doc.amount
|
||||
}
|
||||
else if (doc.service === 'paypal') {
|
||||
if (!dailyRevenueCounts['DRR paypal']) dailyRevenueCounts['DRR paypal'] = {};
|
||||
if (!dailyRevenueCounts['DRR paypal'][day]) dailyRevenueCounts['DRR paypal'][day] = 0;
|
||||
dailyRevenueCounts['DRR paypal'][day] += doc.amount
|
||||
if (!dailyRevenueCounts['DRR monthly subs']) dailyRevenueCounts['DRR monthly subs'] = {};
|
||||
if (!dailyRevenueCounts['DRR monthly subs'][day]) dailyRevenueCounts['DRR monthly subs'][day] = 0;
|
||||
dailyRevenueCounts['DRR monthly subs'][day] += doc.amount
|
||||
}
|
||||
// else {
|
||||
// // printjson(doc);
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
// Add 2 course headcount to older approved teacher surveys
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
|
||||
// Who needs 2 course headcount added?
|
||||
// Approved trial.requests but no prepaid with properties.trialRequestID set
|
||||
|
||||
// Priority userID selection
|
||||
// 1. UserID that redeemed approved prepaid
|
||||
// 2. UserID that applied for trial request
|
||||
// 3. User email that applied for trial request
|
||||
// NOTE: May give course headcount to multiple accounts if they applied and redeemed with different users
|
||||
|
||||
addHeadcount();
|
||||
|
||||
function addHeadcount() {
|
||||
print("Finding approved trial requests..");
|
||||
|
||||
var approvedUserIDMap = {};
|
||||
var approvedUserEmails = [];
|
||||
var codeRequestMap = {};
|
||||
var userIDRequestMap = {};
|
||||
var userEmailRequestMap = {};
|
||||
var cursor = db['trial.requests'].find({status: 'approved', type: 'subscription'}, {});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
if (doc.applicant) {
|
||||
approvedUserIDMap[doc.applicant.valueOf()] = false;
|
||||
userIDRequestMap[doc.applicant.valueOf()] = doc._id;
|
||||
}
|
||||
if (doc.prepaidCode) {
|
||||
codeRequestMap[doc.prepaidCode] = doc._id;
|
||||
}
|
||||
approvedUserEmails.push(doc.properties.email.toLowerCase());
|
||||
userEmailRequestMap[doc.properties.email.toLowerCase()] = doc._id;
|
||||
}
|
||||
|
||||
print("Finding users via redeemed prepaids..");
|
||||
cursor = db.prepaids.find({code: {$in: Object.keys(codeRequestMap)}});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
if (doc.redeemers && doc.redeemers.length > 0) {
|
||||
for (var i = 0; i < doc.redeemers.length; i++) {
|
||||
approvedUserIDMap[doc.redeemers[i].userID.valueOf()] = false;
|
||||
userIDRequestMap[doc.redeemers[i].userID.valueOf()] = codeRequestMap[doc.code];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("Finding users via approved emails..");
|
||||
cursor = db.users.find({emailLower: {$in: approvedUserEmails}});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
approvedUserIDMap[doc._id.valueOf()] = false;
|
||||
if (userEmailRequestMap[doc.emailLower]) {
|
||||
// Trial request had a known email, but not an applicant field set
|
||||
userIDRequestMap[doc._id.valueOf()] = userEmailRequestMap[doc.emailLower];
|
||||
}
|
||||
}
|
||||
|
||||
var approvedUserIDs = [];
|
||||
for (var userID in approvedUserIDMap) {
|
||||
approvedUserIDs.push(new ObjectId(userID));
|
||||
}
|
||||
print("Approved user IDs:", approvedUserIDs.length);
|
||||
|
||||
print("Finding approved users with trial request headcount..");
|
||||
cursor = db.prepaids.find({$and: [{creator: {$in: approvedUserIDs}}, {'properties.trialRequestID': {$exists: true}}]});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
approvedUserIDMap[doc.creator.valueOf()] = true;
|
||||
}
|
||||
|
||||
var needsHeadcount = [];
|
||||
for (var userID in approvedUserIDMap) {
|
||||
if (approvedUserIDMap[userID] === false) {
|
||||
needsHeadcount.push(ObjectId(userID));
|
||||
}
|
||||
}
|
||||
print("Needs headcount:", needsHeadcount.length);
|
||||
|
||||
var updateCount = 0;
|
||||
function insertHeadCount(userID) {
|
||||
if (!userIDRequestMap[userID.valueOf()]) {
|
||||
print('ERROR: No trial request ID', userID);
|
||||
print('Trial course headcount prepaids inserted:', updateCount);
|
||||
return;
|
||||
}
|
||||
|
||||
generateNewCode(function(code) {
|
||||
if (!code) {
|
||||
print("ERROR: no code");
|
||||
return;
|
||||
}
|
||||
criteria = {
|
||||
creator: userID,
|
||||
type: 'course',
|
||||
maxRedeemers: NumberInt(2),
|
||||
properties: {
|
||||
trialRequestID: userIDRequestMap[userID.valueOf()]
|
||||
},
|
||||
exhausted: false,
|
||||
__v: NumberInt(0)
|
||||
};
|
||||
if (!db.prepaids.findOne(criteria)) {
|
||||
// print('Adding trial request prepaid for', userID, code);
|
||||
criteria.code = code;
|
||||
var writeResult = db.prepaids.insert(criteria);
|
||||
updateCount += writeResult.nInserted;
|
||||
}
|
||||
else {
|
||||
print('ERROR: Already has trial request headcount', userID, criteria.properties.trialRequestID);
|
||||
print('Trial course headcount prepaids inserted:', updateCount);
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateCount < 500 && needsHeadcount.length > 0) {
|
||||
insertHeadCount(needsHeadcount.pop());
|
||||
}
|
||||
else {
|
||||
print('Trial course headcount prepaids inserted:', updateCount);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (needsHeadcount.length > 0) {
|
||||
insertHeadCount(needsHeadcount.pop());
|
||||
}
|
||||
}
|
||||
|
||||
function generateNewCode(done)
|
||||
{
|
||||
function tryCode() {
|
||||
code = createCode(8);
|
||||
criteria = {code: code};
|
||||
if (db.prepaids.findOne(criteria)) {
|
||||
return tryCode();
|
||||
}
|
||||
return done(code);
|
||||
}
|
||||
tryCode();
|
||||
}
|
||||
|
||||
function createCode(length)
|
||||
{
|
||||
var text = "";
|
||||
var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < length; i++ ) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Set created field if necessary, and migrate previous teacher trial requests to database collection trial.requests
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password> --eval "var csvFilePath = '/Users/blah/Downloads/externalRequestData.csv'"
|
||||
|
||||
// NOTES
|
||||
// Do not have prepaid IDs for these approved requests
|
||||
// Do not have applicant user IDs
|
||||
// Adding special properties.fromGoogleForm = true flag
|
||||
|
||||
|
||||
if (typeof csvFilePath === 'undefined') {
|
||||
print("ERROR: no csvFilePath specified");
|
||||
}
|
||||
else {
|
||||
addCreated();
|
||||
insertExternalRequests();
|
||||
}
|
||||
|
||||
function addCreated() {
|
||||
// Set created property if it's missing
|
||||
var cursor = db['trial.requests'].find({created: {$exists: false}});
|
||||
var updateCount = 0;
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var writeResult = db['trial.requests'].update(
|
||||
{_id: doc._id},
|
||||
{$set: {created: doc._id.getTimestamp()}}
|
||||
);
|
||||
// printjson(writeResult);
|
||||
updateCount += writeResult.nModified;
|
||||
if (updateCount > 500) break;
|
||||
}
|
||||
print("Num created field added: ", updateCount);
|
||||
}
|
||||
|
||||
function insertExternalRequests() {
|
||||
var file = cat(csvFilePath);
|
||||
var lines = file.split('\n');
|
||||
var lineRegExp = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
|
||||
updateCount = 0;
|
||||
for (var i = 1; i < lines.length; i++) {
|
||||
var matches = lines[i].match(lineRegExp);
|
||||
if (!matches || matches.length < 6) {
|
||||
print(i, lines[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Instead of fixing the line regex
|
||||
for (var j = 0; j < matches.length; j++) {
|
||||
matches[j] = matches[j].substring(0, matches[j].length - 1);
|
||||
matches[j] = matches[j].replace(/"/ig, '');
|
||||
}
|
||||
|
||||
// Build date
|
||||
var dateMatches = matches[0].split(/[\/: ]{1}/ig);
|
||||
if (!dateMatches || dateMatches.length !== 6) {
|
||||
print(matches[0], dateMatches.length);
|
||||
break;
|
||||
}
|
||||
|
||||
var year = parseInt(dateMatches[2]);
|
||||
var month = parseInt(dateMatches[0]);
|
||||
var day = parseInt(dateMatches[1]);
|
||||
var hours = parseInt(dateMatches[3]);
|
||||
var minutes = parseInt(dateMatches[4]);
|
||||
var seconds = parseInt(dateMatches[5]);
|
||||
if (month < 10) month = "0" + month;
|
||||
if (day < 10) day = "0" + day;
|
||||
if (hours < 10) hours = "0" + hours;
|
||||
if (minutes < 10) minutes = "0" + minutes;
|
||||
if (seconds < 10) seconds = "0" + seconds;
|
||||
var isoDate = year + "-" + month + "-" + day + "T" + hours + ":" + minutes + ":" + seconds + ".000Z";
|
||||
var created = new Date(isoDate);
|
||||
// print(created.toISOString());
|
||||
|
||||
var reviewDate = created;
|
||||
var reviewer = new ObjectId('52f94443fcb334581466a992');
|
||||
var properties = {
|
||||
heardAbout: matches[5],
|
||||
numStudents: matches[4],
|
||||
age: matches[3],
|
||||
location: matches[2],
|
||||
school: matches[2],
|
||||
email: matches[1],
|
||||
fromGoogleForm: true
|
||||
};
|
||||
var status = 'approved';
|
||||
var type = 'subscription';
|
||||
|
||||
// print(created, reviewDate, reviewer, status, type);
|
||||
// printjson(properties);
|
||||
|
||||
// Insert based on email
|
||||
writeResult = db['trial.requests'].update(
|
||||
{$and: [{'properties.fromGoogleForm': true}, {'properties.email': properties.email}]},
|
||||
{
|
||||
$set: {
|
||||
created: created,
|
||||
type: 'subscription',
|
||||
properties: properties,
|
||||
status: 'approved',
|
||||
reviewDate: reviewDate,
|
||||
reviewer: reviewer
|
||||
}
|
||||
},
|
||||
{ upsert: true}
|
||||
);
|
||||
updateCount += writeResult.nModified + writeResult.nUpserted;
|
||||
if (updateCount >= 500) break;
|
||||
}
|
||||
print("Num external trial requests inserted:", updateCount);
|
||||
}
|
|
@ -57,14 +57,14 @@ CampaignHandler = class CampaignHandler extends Handler
|
|||
projection = {}
|
||||
if req.query.project
|
||||
projection[field] = 1 for field in req.query.project.split(',')
|
||||
q = @modelClass.find {}, projection
|
||||
q = @modelClass.find {type: 'hero'}, projection
|
||||
q.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
formatCampaign = (doc) =>
|
||||
obj = @formatEntity(req, doc)
|
||||
obj.adjacentCampaigns = _.mapValues(obj.adjacentCampaigns, (a) -> _.pick(a, ['showIfUnlocked', 'color', 'name', 'description' ]))
|
||||
for original, level of obj.levels
|
||||
obj.levels[original] = _.pick level, ['locked', 'disabled', 'original', 'rewards']
|
||||
obj.levels[original] = _.pick level, ['locked', 'disabled', 'original', 'rewards', 'slug']
|
||||
obj
|
||||
documents = (formatCampaign(doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
|
|
|
@ -14,12 +14,16 @@ ClassroomSchema.statics.editableProperties = [
|
|||
'aceConfig'
|
||||
]
|
||||
|
||||
# 250 words; will want to use 4 code words once we get past 10M classrooms.
|
||||
words = 'angry apple arm army art baby back bad bag ball bath bean bear bed bell best big bird bite blue boat book box boy bread burn bus cake car cat chair city class clock cloud coat coin cold cook cool corn crash cup dark day deep desk dish dog door down draw dream drink drop dry duck dust east eat egg enemy eye face false farm fast fear fight find fire flag floor fly food foot fork fox free fruit full fun funny game gate gift glass goat gold good green hair half hand happy heart heavy help hide hill home horse house ice idea iron jelly job jump key king lamp large last late lazy leaf left leg life light lion lock long luck map mean milk mix moon more most mouth music name neck net new next nice night north nose old only open page paint pan paper park party path pig pin pink place plane plant plate play point pool power pull push queen rain ready red rest rice ride right ring road rock room run sad safe salt same sand sell shake shape share sharp sheep shelf ship shirt shoe shop short show sick side silly sing sink sit size sky sleep slow small snow sock soft soup south space speed spell spoon star start step stone stop sweet swim sword table team thick thin thing think today tooth top town tree true turn type under want warm watch water west wide win word yes zoo'.split(' ')
|
||||
|
||||
ClassroomSchema.statics.generateNewCode = (done) ->
|
||||
tryCode = ->
|
||||
code = _.sample("abcdefghijklmnopqrstuvwxyz0123456789", 8).join('')
|
||||
codeCamel = _.map(_.sample(words, 3), (s) -> s[0].toUpperCase() + s.slice(1)).join('')
|
||||
code = codeCamel.toLowerCase()
|
||||
Classroom.findOne code: code, (err, classroom) ->
|
||||
return done() if err
|
||||
return done(code) unless classroom
|
||||
return done(code, codeCamel) unless classroom
|
||||
tryCode()
|
||||
tryCode()
|
||||
|
||||
|
@ -27,14 +31,15 @@ ClassroomSchema.statics.generateNewCode = (done) ->
|
|||
|
||||
ClassroomSchema.pre('save', (next) ->
|
||||
return next() if @get('code')
|
||||
Classroom.generateNewCode (code) =>
|
||||
Classroom.generateNewCode (code, codeCamel) =>
|
||||
@set 'code', code
|
||||
@set 'codeCamel', codeCamel
|
||||
next()
|
||||
)
|
||||
|
||||
ClassroomSchema.methods.isOwner = (userID) ->
|
||||
return userID.equals(@get('ownerID'))
|
||||
|
||||
|
||||
ClassroomSchema.methods.isMember = (userID) ->
|
||||
return _.any @get('members') or [], (memberID) -> userID.equals(memberID)
|
||||
|
||||
|
|
|
@ -51,7 +51,8 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
|
||||
joinClassroomAPI: (req, res, classroomID) ->
|
||||
return @sendBadInputError(res, 'Need an object with a code') unless req.body?.code
|
||||
Classroom.findOne {code: req.body.code}, (err, classroom) =>
|
||||
code = req.body.code.toLowerCase()
|
||||
Classroom.findOne {code: code}, (err, classroom) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) if not classroom
|
||||
members = _.clone(classroom.get('members'))
|
||||
|
@ -63,16 +64,16 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
members.push req.user.get('_id')
|
||||
classroom.set('members', members)
|
||||
return @sendSuccess(res, @formatEntity(req, classroom))
|
||||
|
||||
|
||||
formatEntity: (req, doc) ->
|
||||
if req.user?.isAdmin() or req.user?.get('_id').equals(doc.get('ownerID'))
|
||||
return doc.toObject()
|
||||
return _.omit(doc.toObject(), 'code')
|
||||
return _.omit(doc.toObject(), 'code', 'codeCamel')
|
||||
|
||||
inviteStudents: (req, res, classroomID) ->
|
||||
if not req.body.emails
|
||||
return @sendBadInputError(res, 'Emails not included')
|
||||
|
||||
|
||||
Classroom.findById classroomID, (err, classroom) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless classroom
|
||||
|
@ -86,10 +87,10 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
email_data:
|
||||
class_name: classroom.get('name')
|
||||
# TODO: join_link
|
||||
join_link: "https://codecombat.com/courses/students?_cc=" + classroom.get('code')
|
||||
join_link: "https://codecombat.com/courses/students?_cc=" + (classroom.get('codeCamel') or classroom.get('code'))
|
||||
sendwithus.api.send context, _.noop
|
||||
return @sendSuccess(res, {})
|
||||
|
||||
|
||||
get: (req, res) ->
|
||||
if ownerID = req.query.ownerID
|
||||
return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or ownerID is req.user.id)
|
||||
|
|
|
@ -61,6 +61,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'tasks'
|
||||
'helpVideos'
|
||||
'campaign'
|
||||
'campaignIndex'
|
||||
'replayable'
|
||||
'buildTime'
|
||||
'scoreTypes'
|
||||
|
@ -236,11 +237,8 @@ LevelHandler = class LevelHandler extends Handler
|
|||
|
||||
getLeaderboard: (req, res, id) ->
|
||||
sessionsQueryParameters = @makeLeaderboardQueryParameters(req, id)
|
||||
|
||||
sortParameters =
|
||||
'totalScore': req.query.order
|
||||
selectProperties = ['totalScore', 'creatorName', 'creator', 'submittedCodeLanguage', 'heroConfig', 'leagues.leagueID', 'leagues.stats.totalScore', 'submitDate']
|
||||
|
||||
sortParameters = totalScore: req.query.order
|
||||
selectProperties = ['totalScore', 'creatorName', 'creator', 'submittedCodeLanguage', 'heroConfig', 'leagues.leagueID', 'leagues.stats.totalScore', 'submitDate', 'team']
|
||||
query = Session
|
||||
.find(sessionsQueryParameters)
|
||||
.limit(req.query.limit)
|
||||
|
@ -251,7 +249,13 @@ LevelHandler = class LevelHandler extends Handler
|
|||
query.exec (err, resultSessions) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
resultSessions ?= []
|
||||
@sendSuccess res, resultSessions
|
||||
leaderboardOptions = find: sessionsQueryParameters, limit: req.query.limit, sort: sortParameters, select: selectProperties
|
||||
@interleaveAILeaderboardSessions leaderboardOptions, resultSessions, (err, resultSessions) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
if league = req.query['leagues.leagueID']
|
||||
resultSessions = _.sortBy resultSessions, (session) -> _.find(session.get('leagues'), leagueID: league)?.stats.totalScore ? session.get('totalScore') / 2
|
||||
resultSessions.reverse() if sortParameters.totalScore is -1
|
||||
@sendSuccess res, resultSessions
|
||||
|
||||
getMyLeaderboardRank: (req, res, id) ->
|
||||
req.query.order = 1
|
||||
|
@ -282,6 +286,36 @@ LevelHandler = class LevelHandler extends Handler
|
|||
req.query.team ?= 'humans'
|
||||
req.query.limit = parseInt(req.query.limit) ? 20
|
||||
|
||||
ladderBenchmarkAIs: [
|
||||
'564ba6cea33967be1312ae59'
|
||||
'564ba830a33967be1312ae61'
|
||||
'564ba91aa33967be1312ae65'
|
||||
'564ba95ca33967be1312ae69'
|
||||
'564ba9b7a33967be1312ae6d'
|
||||
]
|
||||
|
||||
interleaveAILeaderboardSessions: (leaderboardOptions, sessions, cb) ->
|
||||
return cb null, sessions unless leaderboardOptions.find['leagues.leagueID']
|
||||
return cb null, sessions if leaderboardOptions.limit < 10 # Don't put them in when we're fetching sessions around another session
|
||||
# Get our list of benchmark AI sessions
|
||||
benchmarkSessions = Session
|
||||
.find(level: leaderboardOptions.find.level, creator: {$in: @ladderBenchmarkAIs})
|
||||
.sort(leaderboardOptions.sort)
|
||||
.select(leaderboardOptions.select.join ' ')
|
||||
.cache() # TODO: How long does this cache? We will probably want these to be pretty long.
|
||||
.exec (err, aiSessions) ->
|
||||
return cb err if err
|
||||
matchingAISessions = _.filter aiSessions, (aiSession) ->
|
||||
return false unless aiSession.get('team') is leaderboardOptions.find.team
|
||||
return false if $gt = leaderboardOptions.find.totalScore.$gt and aiSession.get('totalScore') <= $gt
|
||||
return false if $lt = leaderboardOptions.find.totalScore.$lt and aiSession.get('totalScore') >= $lt
|
||||
true
|
||||
# TODO: these aren't real league scores for AIs, but rather the general leaderboard scores, which will make most AI scores artificially high. So we divide by 2 for AI scores not part of the league. Pretty weak, I know. Eventually we'd want them to actually play league matches as if they were in all leagues, but without having infinite space requirements or something? Or change the UI to take them out of the main league table and into their separate area.
|
||||
sessions = _.sortBy sessions.concat(matchingAISessions), (session) -> _.find(session.get('leagues'), leagueID: leaderboardOptions.find['leagues.leagueID'])?.stats.totalScore ? session.get('totalScore') / 2
|
||||
sessions.reverse() if leaderboardOptions.sort.totalScore is -1
|
||||
sessions = sessions.slice 0, leaderboardOptions.limit
|
||||
return cb null, sessions
|
||||
|
||||
getLeaderboardFacebookFriends: (req, res, id) -> @getLeaderboardFriends(req, res, id, 'facebookID')
|
||||
getLeaderboardGPlusFriends: (req, res, id) -> @getLeaderboardFriends(req, res, id, 'gplusID')
|
||||
getLeaderboardFriends: (req, res, id, serviceProperty) ->
|
||||
|
|
|
@ -74,8 +74,9 @@ PrepaidHandler = class PrepaidHandler extends Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) if not prepaid
|
||||
return @sendForbiddenError(res) if prepaid.get('creator').toString() isnt req.user.id
|
||||
return @sendForbiddenError(res) if _.size(prepaid.get('redeemers')) >= prepaid.get('maxRedeemers')
|
||||
return @sendForbiddenError(res) if prepaid.get('redeemers')? and _.size(prepaid.get('redeemers')) >= prepaid.get('maxRedeemers')
|
||||
return @sendForbiddenError(res) unless prepaid.get('type') is 'course'
|
||||
return @sendForbiddenError(res) if prepaid.get('properties')?.endDate < new Date()
|
||||
User.findById(req.body.userID).exec (err, user) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res, 'User for given ID not found') if not user
|
||||
|
@ -85,14 +86,14 @@ PrepaidHandler = class PrepaidHandler extends Handler
|
|||
query =
|
||||
_id: prepaid.get('_id')
|
||||
'redeemers.userID': { $ne: user.get('_id') }
|
||||
$where: "this.redeemers.length < #{prepaid.get('maxRedeemers')}"
|
||||
$where: "this.maxRedeemers > 0 && (!this.redeemers || this.redeemers.length < #{prepaid.get('maxRedeemers')})"
|
||||
update = { $push: { redeemers : { date: new Date(), userID: userID } }}
|
||||
Prepaid.update query, update, (err, nMatched) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
if nMatched is 0
|
||||
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers")
|
||||
return @sendForbiddenError(res)
|
||||
|
||||
|
||||
user.set('coursePrepaidID', prepaid.get('_id'))
|
||||
user.save (err, user) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
|
@ -100,7 +101,7 @@ PrepaidHandler = class PrepaidHandler extends Handler
|
|||
redeemers = _.clone(prepaid.get('redeemers') or [])
|
||||
redeemers.push({ date: new Date(), userID: userID })
|
||||
prepaid.set('redeemers', redeemers)
|
||||
@sendSuccess(res, @formatEntity(req, prepaid))
|
||||
@sendSuccess(res, @formatEntity(req, prepaid))
|
||||
|
||||
createPrepaid: (user, type, maxRedeemers, properties, done) ->
|
||||
Prepaid.generateNewCode (code) =>
|
||||
|
@ -241,12 +242,15 @@ PrepaidHandler = class PrepaidHandler extends Handler
|
|||
return @sendBadInputError(res, 'Bad creator') unless utils.isID creator
|
||||
q = {
|
||||
_id: {$gt: cutoffID}
|
||||
creator: mongoose.Types.ObjectId(creator),
|
||||
creator: mongoose.Types.ObjectId(creator)
|
||||
type: 'course'
|
||||
}
|
||||
Prepaid.find q, (err, prepaids) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, (@formatEntity(req, prepaid) for prepaid in prepaids))
|
||||
documents = []
|
||||
for prepaid in prepaids
|
||||
documents.push(@formatEntity(req, prepaid)) unless prepaid.get('properties')?.endDate < new Date()
|
||||
return @sendSuccess(res, documents)
|
||||
else
|
||||
super(arguments...)
|
||||
|
||||
|
@ -254,5 +258,5 @@ PrepaidHandler = class PrepaidHandler extends Handler
|
|||
prepaid = super(req)
|
||||
prepaid.set('redeemers', [])
|
||||
return prepaid
|
||||
|
||||
|
||||
module.exports = new PrepaidHandler()
|
||||
|
|
|
@ -11,7 +11,6 @@ if config.unittest
|
|||
module.exports.api.send = ->
|
||||
module.exports.templates =
|
||||
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
|
||||
setup_free_sub_email: 'tem_sqdvLCZRwoDQc6jAf5RrQE'
|
||||
share_progress_email: 'tem_VHE3ihhGmVa3727qds9zY8'
|
||||
welcome_email: 'utnGaBHuSU4Hmsi7qrAypU'
|
||||
ladder_update_email: 'JzaZxf39A4cKMxpPZUfWy4'
|
||||
|
@ -23,4 +22,5 @@ module.exports.templates =
|
|||
plain_text_email: 'tem_85UvKDCCNPXsFckERTig6Y'
|
||||
next_steps_email: 'tem_RDHhTG5inXQi8pthyqWr5D'
|
||||
course_invite_email: 'tem_u6D2EFWYC5Ptk38bSykjsU'
|
||||
|
||||
teacher_free_trial: 'tem_sqdvLCZRwoDQc6jAf5RrQE'
|
||||
teacher_free_trial_hoc: 'tem_4ZSY9wsA9Qwn4wBFmZgPdc'
|
||||
|
|
|
@ -11,51 +11,41 @@ TrialRequestSchema = new mongoose.Schema {}, {strict: false, minimize: false, re
|
|||
TrialRequestSchema.pre 'save', (next) ->
|
||||
return next() unless @get('status') is 'approved'
|
||||
|
||||
# Add subscription
|
||||
Prepaid.generateNewCode (code) =>
|
||||
unless code
|
||||
log.error "Trial request pre save prepaid gen new code failure"
|
||||
return next()
|
||||
# Add 2 course headcount
|
||||
prepaid = new Prepaid
|
||||
creator: @get('applicant')
|
||||
type: 'course'
|
||||
maxRedeemers: 2
|
||||
properties:
|
||||
trialRequestID: @get('_id')
|
||||
prepaid.save (err) =>
|
||||
if err
|
||||
log.error "Trial request prepaid creation error: #{err}"
|
||||
|
||||
# Special HoC trial: Add 500 course headcount with end date
|
||||
endDate = new Date()
|
||||
endDate.setUTCMonth(endDate.getUTCMonth() + 2)
|
||||
prepaid = new Prepaid
|
||||
creator: @get('reviewer')
|
||||
type: 'subscription'
|
||||
maxRedeemers: 1
|
||||
code: code
|
||||
creator: @get('applicant')
|
||||
type: 'course'
|
||||
maxRedeemers: 500
|
||||
properties:
|
||||
couponID: 'free'
|
||||
endDate: endDate
|
||||
trialRequestID: @get('_id')
|
||||
prepaid.save (err) =>
|
||||
if err
|
||||
log.error "Trial request prepaid creation error: #{err}"
|
||||
return next()
|
||||
@set('prepaidCode', code)
|
||||
|
||||
# Add 2 course headcount
|
||||
prepaid = new Prepaid
|
||||
creator: @get('applicant')
|
||||
type: 'course'
|
||||
maxRedeemers: 2
|
||||
properties:
|
||||
trialRequestID: @get('_id')
|
||||
prepaid.save (err) =>
|
||||
if err
|
||||
log.error "Trial request prepaid creation error: #{err}"
|
||||
next()
|
||||
next()
|
||||
|
||||
TrialRequestSchema.post 'save', (doc) ->
|
||||
if doc.get('status') is 'submitted'
|
||||
msg = "<a href=\"http://codecombat.com/admin/trial-requests\">Trial Request</a> submitted by #{doc.get('properties').email}"
|
||||
msg = "<a href=\"http://codecombat.com/admin/trial-requests\">Trial Request</a> submitted by #{doc.get('properties')?.email}"
|
||||
hipchat.sendHipChatMessage msg, ['tower']
|
||||
else if doc.get('status') is 'approved'
|
||||
ppc = doc.get('prepaidCode')
|
||||
unless ppc
|
||||
log.error 'Trial request post save no ppc'
|
||||
return
|
||||
emailParams =
|
||||
recipient:
|
||||
address: doc.get('properties')?.email
|
||||
email_id: sendwithus.templates.setup_free_sub_email
|
||||
email_data:
|
||||
url: "https://codecombat.com/account/subscription?_ppc=#{ppc}";
|
||||
email_id: sendwithus.templates.teacher_free_trial_hoc
|
||||
sendwithus.api.send emailParams, (err, result) =>
|
||||
log.error "sendwithus trial request approved error: #{err}, result: #{result}" if err
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ describe '/db/prepaid', ->
|
|||
clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) ->
|
||||
throw err if err
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /db/prepaid/<id>/redeemers', ->
|
||||
|
||||
it 'adds a given user to the redeemers property', (done) ->
|
||||
|
@ -60,7 +60,7 @@ describe '/db/prepaid', ->
|
|||
User.findById otherUser.id, (err, user) ->
|
||||
expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true)
|
||||
done()
|
||||
|
||||
|
||||
it 'does not allow more redeemers than maxRedeemers', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
prepaid = new Prepaid({
|
||||
|
@ -97,7 +97,7 @@ describe '/db/prepaid', ->
|
|||
request.post {uri: url, json: redeemer }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
|
||||
it 'is idempotent across prepaids collection', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
otherUser = new User({
|
||||
|
@ -125,7 +125,7 @@ describe '/db/prepaid', ->
|
|||
return done() unless res.statusCode is 200
|
||||
expect(body.redeemers.length).toBe(0)
|
||||
done()
|
||||
|
||||
|
||||
it 'is idempotent to itself for a user other than the creator', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
prepaid = new Prepaid({
|
||||
|
@ -184,6 +184,101 @@ describe '/db/prepaid', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
it 'return terminal prepaids', (done) ->
|
||||
endDate = new Date()
|
||||
endDate.setUTCMonth(endDate.getUTCMonth() + 2)
|
||||
loginNewUser (user1) ->
|
||||
prepaid = new Prepaid({
|
||||
maxRedeemers: 500,
|
||||
redeemers: [],
|
||||
creator: user1.get('_id')
|
||||
type: 'course'
|
||||
properties:
|
||||
endDate: endDate
|
||||
})
|
||||
prepaid.save (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
url = getURL("/db/prepaid?creator=#{user1.id}")
|
||||
request.get {uri: url}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
documents = JSON.parse(body)
|
||||
expect(documents.length).toEqual(1)
|
||||
return done() unless documents.length is 1
|
||||
expect(documents[0]?.properties?.endDate).toEqual(endDate.toISOString())
|
||||
done()
|
||||
|
||||
it 'do not return expired terminal prepaids', (done) ->
|
||||
endDate = new Date()
|
||||
endDate.setUTCMonth(endDate.getUTCMonth() - 1)
|
||||
loginNewUser (user1) ->
|
||||
prepaid = new Prepaid({
|
||||
maxRedeemers: 500,
|
||||
redeemers: [],
|
||||
creator: user1.get('_id')
|
||||
type: 'course'
|
||||
properties:
|
||||
endDate: endDate
|
||||
})
|
||||
prepaid.save (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
url = getURL("/db/prepaid?creator=#{user1.id}")
|
||||
request.get {uri: url}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
documents = JSON.parse(body)
|
||||
expect(documents.length).toEqual(0)
|
||||
done()
|
||||
|
||||
it 'redeem terminal prepaids', (done) ->
|
||||
endDate = new Date()
|
||||
endDate.setUTCMonth(endDate.getUTCMonth() + 2)
|
||||
loginNewUser (user1) ->
|
||||
prepaid = new Prepaid({
|
||||
maxRedeemers: 500,
|
||||
redeemers: [],
|
||||
creator: user1.get('_id')
|
||||
type: 'course'
|
||||
properties:
|
||||
endDate: endDate
|
||||
})
|
||||
prepaid.save (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
otherUser = new User()
|
||||
otherUser.save (err, otherUser) ->
|
||||
url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
|
||||
redeemer = { userID: otherUser.id }
|
||||
request.post {uri: url, json: redeemer }, (err, res, body) ->
|
||||
expect(body.redeemers?.length).toBe(1)
|
||||
expect(res.statusCode).toBe(200)
|
||||
return done() unless res.statusCode is 200
|
||||
prepaid = Prepaid.findById body._id, (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
expect(prepaid.get('redeemers').length).toBe(1)
|
||||
User.findById otherUser.id, (err, user) ->
|
||||
expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true)
|
||||
done()
|
||||
|
||||
it 'do not redeem expired terminal prepaids', (done) ->
|
||||
endDate = new Date()
|
||||
endDate.setUTCMonth(endDate.getUTCMonth() - 1)
|
||||
loginNewUser (user1) ->
|
||||
prepaid = new Prepaid({
|
||||
maxRedeemers: 500,
|
||||
redeemers: [],
|
||||
creator: user1.get('_id')
|
||||
type: 'course'
|
||||
properties:
|
||||
endDate: endDate
|
||||
})
|
||||
prepaid.save (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
otherUser = new User()
|
||||
otherUser.save (err, otherUser) ->
|
||||
url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
|
||||
redeemer = { userID: otherUser.id }
|
||||
request.post {uri: url, json: redeemer }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Clear database', (done) ->
|
||||
clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) ->
|
||||
throw err if err
|
||||
|
@ -304,7 +399,7 @@ describe '/db/prepaid', ->
|
|||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
|
||||
it 'Standard user purchases a prepaid for 1 seat', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
|
@ -324,7 +419,7 @@ describe '/db/prepaid', ->
|
|||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
verifyCoursePrepaid(user1, prepaid, done)
|
||||
|
||||
|
||||
describe 'Purchase terminal_subscription', ->
|
||||
it 'Anonymous submits a prepaid purchase', (done) ->
|
||||
stripe.tokens.create {
|
||||
|
@ -567,16 +662,16 @@ describe '/db/prepaid', ->
|
|||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginJoe (joe) ->
|
||||
loginNewUser (user) ->
|
||||
purchasePrepaid 'terminal_subscription', months: 1, 3, token.id, (err, res, prepaid) ->
|
||||
request.get "#{getURL('/db/user')}/#{joe.id}/prepaid_codes", (err, res) ->
|
||||
request.get "#{getURL('/db/user')}/#{user.id}/prepaid_codes", (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toEqual(200);
|
||||
codes = JSON.parse res.body
|
||||
expect(codes.length).toEqual(2)
|
||||
expect(codes.length).toEqual(1)
|
||||
expect(codes[0].maxRedeemers).toEqual(3)
|
||||
expect(codes[0].properties).toBeDefined()
|
||||
expect(codes[0].properties.months).toEqual(3)
|
||||
expect(codes[0].properties.months).toEqual(1)
|
||||
done()
|
||||
|
||||
it 'Test for injection', (done) ->
|
||||
|
|
|
@ -120,20 +120,24 @@ describe 'Trial Requests', ->
|
|||
expect(body.reviewDate).toBeDefined()
|
||||
expect(new Date(body.reviewDate)).toBeLessThan(new Date())
|
||||
expect(body.reviewer).toEqual(admin.id)
|
||||
expect(body.prepaidCode).toBeDefined()
|
||||
TrialRequest.findById body._id, (err, doc) ->
|
||||
expect(err).toBeNull()
|
||||
expect(doc.get('status')).toEqual('approved')
|
||||
expect(doc.get('reviewDate')).toBeDefined()
|
||||
expect(new Date(doc.get('reviewDate'))).toBeLessThan(new Date())
|
||||
expect(doc.get('reviewer')).toEqual(admin._id)
|
||||
expect(doc.get('prepaidCode')).toBeDefined()
|
||||
Prepaid.findOne {'properties.trialRequestID': doc.get('_id')}, (err, doc) ->
|
||||
Prepaid.find {'properties.trialRequestID': doc.get('_id')}, (err, prepaids) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
expect(doc.get('type')).toEqual('course')
|
||||
expect(doc.get('creator')).toEqual(user.get('_id'))
|
||||
expect(doc.get('maxRedeemers')).toEqual(2)
|
||||
expect(prepaids.length).toEqual(2)
|
||||
for prepaid in prepaids
|
||||
expect(prepaid.get('type')).toEqual('course')
|
||||
expect(prepaid.get('creator')).toEqual(user.get('_id'))
|
||||
if prepaid.get('properties').endDate
|
||||
expect(prepaid.get('maxRedeemers')).toEqual(500)
|
||||
expect(prepaid.get('properties').endDate).toBeGreaterThan(new Date())
|
||||
else
|
||||
expect(prepaid.get('maxRedeemers')).toEqual(2)
|
||||
done()
|
||||
|
||||
it 'Deny trial request', (done) ->
|
||||
|
|
Loading…
Add table
Reference in a new issue