diff --git a/app/core/Router.coffee b/app/core/Router.coffee
index 3b9359c98..2d5abeb86 100644
--- a/app/core/Router.coffee
+++ b/app/core/Router.coffee
@@ -149,6 +149,7 @@ module.exports = class CocoRouter extends Backbone.Router
'teachers/classes': go('courses/TeacherClassesView', { teachersOnly: true })
'teachers/classes/:classroomID': go('courses/TeacherClassView', { teachersOnly: true })
'teachers/courses': go('courses/TeacherCoursesView')
+ 'teachers/course-solution/:courseID/:language': go('teachers/TeacherCourseSolutionView')
'teachers/demo': go('teachers/RequestQuoteView')
'teachers/enrollments': redirect('/teachers/licenses')
'teachers/licenses': go('courses/EnrollmentsView', { teachersOnly: true })
diff --git a/app/locale/de-DE.coffee b/app/locale/de-DE.coffee
index 41f1b2970..b7f8f9efd 100644
--- a/app/locale/de-DE.coffee
+++ b/app/locale/de-DE.coffee
@@ -76,23 +76,23 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
boast: "Bietet Rätsel die komplex genug sind um Spieler und Programmiere zu faszinieren."
winning: "Eine gewinnbringende Kombination aus RPG-Gameplay und Programmierhausaufgaben die aufgeht und kindgerechte Bildung berechtigterweise unterhaltsam macht."
run_class: "Alles was man braucht um einen Informatikkurs in der Schule zu geben, ohne dass man Hintergrundwissen braucht."
-# teachers: "Teachers!"
-# teachers_and_educators: "Teachers & Educators"
+ teachers: "Lehrer!"
+ teachers_and_educators: "Lehrkräfte"
# class_in_box: "Learn how our classroom-in-a-box platform fits into your curriculum."
-# get_started: "Get Started"
-# students: "Students:"
+ get_started: "Beginnen"
+ students: "Schüler:"
# join_class: "Join Class"
-# role: "Your role:"
-# student_count: "Number of students:"
-# start_playing_for_free: "Start Playing for Free!"
-# students_and_players: "Students & Players"
-# goto_classes: "Go to My Classes"
-# view_profile: "View My Profile"
-# view_progress: "View Progress"
-# check_out_wiki: "Check out our new Educator Wiki"
-# want_coco: "Want CodeCombat at your school?"
-# form_select_role: "Select primary role"
-# form_select_range: "Select class size"
+ role: "Deine Rolle:"
+ student_count: "Anzahl der Schüler:"
+ start_playing_for_free: "Spiele jetzt kostenlos!"
+ students_and_players: "Schüler und Spieler"
+ goto_classes: "Gehe zu meinen Klassen"
+ view_profile: "Zeige mein Profil"
+ view_progress: "Fortschritt ansehen"
+ check_out_wiki: "Entdecken Sie unser neues Lehrkraft-Wiki"
+ want_coco: "Wollen Sie CodeCombat an ihrer Schule?"
+ form_select_role: "Hauptrolle auswählen"
+ form_select_range: "Klassenstärke auswählen"
nav:
play: "Spielen" # The top nav bar entry where players choose which levels to play
@@ -102,7 +102,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
blog: "Blog"
forum: "Forum"
account: "Account"
-# my_account: "My Account"
+ my_account: "Mein Account"
profile: "Profil"
stats: "Statistiken"
code: "Code"
@@ -112,7 +112,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
about: "Über"
contact: "Kontakt"
twitter_follow: "Folge uns auf Twitter"
-# students: "Students"
+ students: "Schüler"
teachers: "Lehrer"
careers: "Karriere mit CodeCombat!"
facebook: "Facebook"
@@ -121,18 +121,18 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
other: "Andere"
learn_to_code: "Lerne zu coden!"
# toggle_nav: "Toggle navigation"
-# jobs: "Jobs"
-# schools: "Schools"
-# educator_wiki: "Educator Wiki"
-# get_involved: "Get Involved"
-# open_source: "Open source (GitHub)"
-# support: "Support"
-# faqs: "FAQs"
-# help_pref: "Need help? Email"
-# help_suff: "and we'll get in touch!"
+ jobs: "Jobs"
+ schools: "Schulen"
+ educator_wiki: "Lehrkraft-Wiki"
+ get_involved: "Mitmachen"
+ open_source: "Open source (GitHub)"
+ support: "Support"
+ faqs: "FAQs"
+ help_pref: "Noch Fragen? Sende eine Email an"
+ help_suff: "und wir melden uns!"
modal:
-# cancel: "Cancel"
+ cancel: "Abbrechen"
close: "Schließen"
okay: "Okay"
@@ -183,55 +183,55 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
campaign_old_multiplayer: "Ältere Mehrspieler-Abenteuer"
campaign_old_multiplayer_description: "Relikte einer eher zivilisierten Zeit. Keine Simulationen laufen hier ab für diese älteren Multiplayerarenas ohne Helden."
-# code:
-# if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
-# else: "else"
-# elif: "else if"
-# while: "while"
-# loop: "loop"
-# for: "for"
-# break: "break"
-# continue: "continue"
-# pass: "pass"
-# return: "return"
-# then: "then"
-# do: "do"
-# end: "end"
-# function: "function"
-# def: "define"
-# var: "variable"
-# self: "self"
-# hero: "hero"
-# this: "this"
-# or: "or"
-# "||": "or"
-# and: "and"
-# "&&": "and"
-# not: "not"
-# "!": "not"
-# "=": "assign"
-# "==": "equals"
-# "===": "strictly equals"
-# "!=": "does not equal"
-# "!==": "does not strictly equal"
-# ">": "is greater than"
-# ">=": "is greater than or equal"
-# "<": "is less than"
-# "<=": "is less than or equal"
-# "*": "multiplied by"
-# "/": "divided by"
-# "+": "plus"
-# "-": "minus"
-# "+=": "add and assign"
-# "-=": "subtract and assign"
-# True: "True"
-# true: "true"
-# False: "False"
-# false: "false"
-# undefined: "undefined"
-# null: "null"
-# nil: "nil"
-# None: "None"
+ code:
+ if: "wenn" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
+ else: "sonst"
+ elif: "sonst wenn"
+ while: "solange"
+ loop: "Schleife"
+ for: "für"
+ break: "Abbruch"
+ continue: "weiter"
+ pass: "passen"
+ return: "zurückgeben"
+ then: "dann"
+ do: "tu"
+ end: "Ende"
+ function: "Function"
+ def: "definiere"
+ var: "Variable"
+ self: "selbst"
+ hero: "Held"
+ this: "dies"
+ or: "oder"
+ "||": "oder"
+ and: "und"
+ "&&": "und"
+ not: "nicht"
+ "!": "nicht"
+ "=": "zuordnen"
+ "==": "ist gleich"
+ "===": "ist genau gleich"
+ "!=": "ist nicht gleich"
+ "!==": "ist nicht genau gleich"
+ ">": "ist größer als"
+ ">=": "ist größer als oder gleich"
+ "<": "ist kleiner als"
+ "<=": "ist kleiner als oder gleich"
+ "*": "multipliziert mit"
+ "/": "geteilt durch"
+ "+": "plus"
+ "-": "minus"
+ "+=": "addieren und zuweisen"
+ "-=": "subtrahieren und zuweisen"
+ True: "Wahr"
+ true: "wahr"
+ False: "Falsch"
+ false: "falsch"
+ undefined: "undefiniert"
+ null: "null"
+ nil: "nil"
+ None: "Nicht"
share_progress_modal:
blurb: "Du machst dich gut! Sag deinen Eltern wie viel du mit CodeCombat gelernt hast." # {change}
@@ -243,7 +243,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
login:
sign_up: "Registrieren"
-# email_or_username: "Email or username"
+ email_or_username: "Email oder Username"
log_in: "Einloggen"
logging_in: "Logge dich ein"
log_out: "Ausloggen"
@@ -259,10 +259,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
# create_student_header: "Create Student Account"
# create_teacher_header: "Create Teacher Account"
# create_individual_header: "Create Individual Account"
-# create_header: "Create Account"
+ create_header: "Account erstellen"
email_announcements: "Erhalte Benachrichtigungen per Email" # {change}
creating: "Erzeuge Account..."
-# create_account: "Create Account"
+ create_account: "Account erstellen"
sign_up: "Neuen Account anlegen"
log_in: "mit Passwort einloggen"
required: "Du musst dich vorher einloggen diesen Dienst zu nutzen"
@@ -270,7 +270,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
school_name: "Schulname und Stadt"
optional: "optional"
school_name_placeholder: "Beispiel Gymnasium, Musterdorf, DE"
-# connect_with: "Connect with:"
+ connect_with: "Verbinden mit:"
connected_gplus_header: "Du wurdest erfolgreich mit Google+ verknüpft!"
connected_gplus_p: "Schließe das Registrieren ab, so dass du dich mit deinem Google+ Account einloggen."
gplus_exists: "Du hast bereits einen Account mit Google+ verknüpft!"
@@ -278,7 +278,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
connected_facebook_p: "Schließe das Registrieren ab, so dass du dich mit deinem Facebook Account einloggen."
facebook_exists: "Du hast bereits einen Account mit Facebook verknüpft!"
hey_students: "Schüler, gib den Klassencode von deinem Lehrer ein."
-# birthday: "Birthday"
+ birthday: "Geburtstag"
# parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
# classroom_not_found: "No classes exist with this Class Code. Check your spelling or ask your teacher for help."
# checking: "Checking..."
@@ -340,8 +340,8 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
saving: "Speichere..."
sending: "Sende..."
send: "Senden"
-# sent: "Sent"
-# type: "Type"
+ sent: "Gesendet"
+ type: "Typ"
cancel: "Abbrechen"
save: "Speichern"
publish: "Veröffentlichen"
@@ -357,7 +357,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
submit_patch: "Patch einreichen"
submit_changes: "Änderungen einreichen"
save_changes: "Änderungen speichern"
-# required_field: "Required field"
+ required_field: "Benötigte Felder"
general:
and: "und"
@@ -390,7 +390,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
subject: "Betreff"
email: "Email"
password: "Passwort"
-# confirm_password: "Confirm Password"
+ confirm_password: "Passwort bestätigen"
message: "Nachricht"
code: "Code"
ladder: "Rangliste"
@@ -409,10 +409,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
warrior: "Krieger"
ranger: "Waldläufer"
wizard: "Magier"
-# first_name: "First Name"
-# last_name: "Last Name"
+ first_name: "Vorname"
+ last_name: "Nachname"
# last_initial: "Last Initial"
-# username: "Username"
+ username: "Username"
units:
second: "Sekunde"
@@ -431,13 +431,13 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
years: "Jahre"
play_level:
-# level_complete: "Level Complete"
+ level_complete: "Level abgeschlossen"
completed_level: "Abgeschlossene Level:"
course: "Kurse:"
done: "Fertig"
next_level: "Nächster Level"
next_game: "Nächstes Spiel"
-# programming_language: "Programming language"
+ programming_language: "Programmiersprache"
show_menu: "Menü anzeigen"
home: "Startseite" # Not used any more, will be removed soon.
level: "Level" # Like "Level: Dungeons of Kithgard"
@@ -484,8 +484,8 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
tome_available_spells: "Verfügbare Zauber"
tome_your_skills: "Deine Fähigkeiten"
tome_current_method: "Aktuelle Methode"
-# hints: "Hints"
-# hints_title: "Hint {{number}}"
+ hints: "Tipps"
+ hints_title: "Tipp {{number}}"
code_saved: "Code gespeichert"
skip_tutorial: "Überspringen (Esc)"
keyboard_shortcuts: "Tastenkürzel"
@@ -633,7 +633,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
feature5: "Videoanleitungen"
feature6: "Premium Emailsupport"
feature7: "Private Klans"
-# feature8: "No ads!"
+ feature8: "Keine Werbung!"
free: "Kostenlos"
month: "Monat"
must_be_logged: "Du musst eingeloggt sein. Bitte kreiere einen Account oder logge dich oben im Menü ein."
@@ -723,12 +723,12 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
skill_docs:
# function: "function" # skill types
-# method: "method"
+ method: "Methode"
# snippet: "snippet"
# number: "number"
# array: "array"
# object: "object"
-# string: "string"
+ string: "String"
writable: "beschreibbar" # Hover over "attack" in Your Skills while playing a level to see most of this
read_only: "schreibgeschützt"
action: "Aktion"
@@ -911,7 +911,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
# create_account: "Create a Teacher Account"
# create_account_subtitle: "Get access to teacher-only tools for using CodeCombat in the classroom. Set up a class, add your students, and monitor their progress!"
# convert_account_title: "Update to Teacher Account"
-# not: "Not"
+ not: "Nicht"
setup_a_class: "Eine Klasse erstellen"
versions:
@@ -1180,7 +1180,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
remove_student: "Schüler entfernen"
assign: "Zuordnen"
to_assign: "um bezahlte Kurse zuzuordnen."
-# student: "Student"
+ student: "Schüler"
teacher: "Lehrer"
complete: "Abgeschlossen"
none: "Keine"
@@ -1842,10 +1842,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
you_can2: "einen Prepaidcode kaufen"
you_can3: "der ihrem Account hinzugefügt werden kann oder den Sie verschenken können."
-# coppa_deny:
+ coppa_deny:
# text1: "Can’t wait to learn programming?"
# text2: "Your parents will need to create an account for you to use! Email team@codecombat.com if you have any questions."
-# close: "Close Window"
+ close: "Fenster schließen"
loading_error:
could_not_load: "Fehler beim Laden vom Server"
@@ -2001,19 +2001,19 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
license: "Lizenz"
oreilly: "Ebook deiner Wahl"
-# calendar:
-# year: "Year"
-# day: "Day"
-# month: "Month"
-# january: "January"
-# february: "February"
-# march: "March"
-# april: "April"
-# may: "May"
-# june: "June"
-# july: "July"
-# august: "August"
-# september: "September"
-# october: "October"
-# november: "November"
-# december: "December"
+ calendar:
+ year: "Jahr"
+ day: "Tag"
+ month: "Monat"
+ january: "Januar"
+ february: "Februar"
+ march: "März"
+ april: "April"
+ may: "Mai"
+ june: "Juni"
+ july: "July"
+ august: "August"
+ september: "September"
+ october: "Oktober"
+ november: "November"
+ december: "Dezember"
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index dffed55df..d2095a23e 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -337,8 +337,13 @@
common:
back: "Back" # When used as an action verb, like "Navigate backward"
+ coming_soon: "Coming soon!"
continue: "Continue" # When used as an action verb, like "Continue forward"
+ default_code: "Default Code"
loading: "Loading..."
+ overview: "Overview"
+ solution: "Solution"
+ intro: "Intro"
saving: "Saving..."
sending: "Sending..."
send: "Send"
@@ -1316,15 +1321,14 @@
students_assigned: "students assigned"
length: "Length:"
title: "Courses" # Flat style redesign
- subtitle: "Review course guidelines, solutions, and levels"
+ subtitle: "Review course overviews and levels" # {change}
changelog: "View latest changes to course levels."
select_language: "Select language"
select_level: "Select level"
play_level: "Play Level"
concepts_covered: "Concepts covered"
print_guide: "Print Guide (PDF)"
- view_guide_online: "View Guide Online (PDF)"
- last_updated: "Last updated:"
+ view_guide_online: "Level Overviews and Solutions" # {change}
grants_lifetime_access: "Grants access to all Courses."
enrollment_credits_available: "Licenses Available:"
description: "Description" # ClassroomSettingsModal
@@ -1391,6 +1395,8 @@
select_this_hero: "Select this Hero"
teacher:
+ course_solution: "Course Solution"
+ level_overview_solutions: "Level Overview and Solutions"
teacher_dashboard: "Teacher Dashboard" # Navbar
my_classes: "My Classes"
courses: "Course Guides"
diff --git a/app/locale/tr.coffee b/app/locale/tr.coffee
index c5ce95514..70b8f388d 100644
--- a/app/locale/tr.coffee
+++ b/app/locale/tr.coffee
@@ -2001,19 +2001,19 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
# license: "license"
# oreilly: "ebook of your choice"
-# calendar:
-# year: "Year"
-# day: "Day"
-# month: "Month"
-# january: "January"
-# february: "February"
-# march: "March"
-# april: "April"
-# may: "May"
-# june: "June"
-# july: "July"
-# august: "August"
-# september: "September"
-# october: "October"
-# november: "November"
-# december: "December"
+ calendar:
+ year: "Yıl"
+ day: "Gün"
+ month: "Ay"
+ january: "Ocak"
+ february: "Şubat"
+ march: "Mart"
+ april: "Nisan"
+ may: "Mayıs"
+ june: "Haziran"
+ july: "Temmuz"
+ august: "Ağustos"
+ september: "Eylül"
+ october: "Ekim"
+ november: "Kasım"
+ december: "Aralık"
diff --git a/app/styles/teachers/teacher-course-solution-view.sass b/app/styles/teachers/teacher-course-solution-view.sass
new file mode 100644
index 000000000..592477f8a
--- /dev/null
+++ b/app/styles/teachers/teacher-course-solution-view.sass
@@ -0,0 +1,31 @@
+#teacher-course-solution-view
+
+ background-color: white
+ color: black
+ font-family: sans-serif
+ margin: 0px
+ padding: 24px
+
+ hr
+ display: block
+ border-style: inset
+ border-width: 1px
+
+ h1, h2, h3, h4, h5
+ color: black
+ font-family: sans-serif
+ font-variant: normal
+ margin: 20px 0px
+
+ h4
+ color: gray
+
+ img
+ display: block
+ margin: auto
+
+ p
+ margin: 16px 0px
+
+ .page-break-before
+ page-break-before: always
diff --git a/app/templates/courses/teacher-courses-view.jade b/app/templates/courses/teacher-courses-view.jade
index e50fbb8a7..13f7364d8 100644
--- a/app/templates/courses/teacher-courses-view.jade
+++ b/app/templates/courses/teacher-courses-view.jade
@@ -17,9 +17,9 @@ block content
.container
h1(data-i18n="courses.title")
h2(data-i18n="courses.subtitle")
- p
- a(href="https://discourse.codecombat.com/t/course-level-changelog/7352" data-i18n="courses.changelog")
-
+ //- p
+ //- a(href="https://discourse.codecombat.com/t/course-level-changelog/7352" data-i18n="courses.changelog")
+
.courses.container
- var courses = view.courses.models;
- var courseIndex = 0;
@@ -29,7 +29,7 @@ block content
.course.row
.col-sm-9
+course-info(course)
- if me.isTeacher()
+ if me.isTeacher() || me.isAdmin()
.col-sm-3
.play-level-form(data-course-id=course.id)
.form-group
@@ -85,22 +85,12 @@ mixin course-info(course)
if course.get('concepts').indexOf(concept) !== course.get('concepts').length - 1
span.spr ,
- if me.isTeacher() || view.ownedClassrooms.size()
- if view.guideLinks[course.id]
- //- a.btn.btn-primary(href=view.guideLinks[course.id] class=(me.isTeacher() ? '': 'disabled'))
- //- span(data-i18n="courses.print_guide")
- a.guide-btn.btn.btn-primary(href=view.guideLinks[course.id].python data-course-id=course.id data-course-name=course.get('name') data-event-action="Classes Guides Guide Python" class=(me.isTeacher() ? '': 'disabled'))
+ if me.isTeacher() || view.ownedClassrooms.size() || me.isAdmin()
+ p
+ a.guide-btn.btn.btn-primary(href=("/teachers/course-solution/" + course.id + "/python") data-course-id=course.id data-course-name=course.get('name') data-event-action="Classes Guides Guide Python" class=(me.isTeacher() || me.isAdmin() ? '': 'disabled'))
span(data-i18n="courses.view_guide_online")
| — Python
- a.guide-btn.btn.btn-primary(href=view.guideLinks[course.id].javascript data-course-id=course.id data-course-name=course.get('name') data-event-action="Classes Guides Guide JavaScript" class=(me.isTeacher() ? '': 'disabled'))
+ p
+ a.guide-btn.btn.btn-primary(href=("/teachers/course-solution/" + course.id + "/javascript") data-course-id=course.id data-course-name=course.get('name') data-event-action="Classes Guides Guide JavaScript" class=(me.isTeacher() || me.isAdmin() ? '': 'disabled'))
span(data-i18n="courses.view_guide_online")
| — JavaScript
- else
- i.small
- | (
- span(data-i18n='teacher.guides_coming_soon')
- | )
- if campaign && campaign.get('levelsUpdated')
- p.small.m-t-2
- span.spr(data-i18n="courses.last_updated")
- span= moment(campaign.get('levelsUpdated')).format('LL')
diff --git a/app/templates/teachers/teacher-course-solution-view.jade b/app/templates/teachers/teacher-course-solution-view.jade
new file mode 100644
index 000000000..7e956c119
--- /dev/null
+++ b/app/templates/teachers/teacher-course-solution-view.jade
@@ -0,0 +1,52 @@
+block content
+
+ if !me.isTeacher() && !me.isAdmin()
+ a(href="/")
+ img#nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
+ h2.text-center(data-i18n="teacher.teacher_account_required")
+ else
+ .text-center
+ img(src="http://direct.codecombat.com/images/pages/base/logo.png")
+ if view.course.loaded
+ h1 #{view.course.get('name')}
+ h3 #{view.prettyLanguage}
+ i= view.course.get('description')
+ br
+ br
+
+ if view.levels
+ each level, index in view.levels.models
+ h2.page-break-before ##{index + 1}. #{level.get('name')}
+ h3(data-i18n="teacher.level_overview_solutions")
+ i #{level.get('description')}
+ div
+ h4.text-center(data-i18n="common.intro")
+ if level.get('intro')
+ p!= level.get('intro')
+ else
+ .text-center
+ i(data-i18n="common.coming_soon")
+ h4.text-center(data-i18n="common.default_code")
+ if level.get('begin')
+ pre!= level.get('begin')
+ else
+ .text-center
+ i(data-i18n="common.coming_soon")
+ div.overview
+ br
+ h4.text-center(data-i18n="common.overview")
+ if level.get('guide')
+ p!= level.get('guide')
+ else
+ .text-center
+ i(data-i18n="common.coming_soon")
+ h4.text-center
+ span= level.get('name')
+ span.spl(data-i18n="common.solution")
+ if level.get('solution')
+ pre!= level.get('solution')
+ else
+ .text-center
+ i(data-i18n="common.coming_soon")
+ hr
+ br
diff --git a/app/views/teachers/TeacherCourseSolutionView.coffee b/app/views/teachers/TeacherCourseSolutionView.coffee
new file mode 100644
index 000000000..99ee28791
--- /dev/null
+++ b/app/views/teachers/TeacherCourseSolutionView.coffee
@@ -0,0 +1,47 @@
+RootView = require 'views/core/RootView'
+CocoCollection = require 'collections/CocoCollection'
+Course = require 'models/Course'
+Level = require 'models/Level'
+
+module.exports = class TeacherCourseSolutionView extends RootView
+ id: 'teacher-course-solution-view'
+ template: require 'templates/teachers/teacher-course-solution-view'
+
+ getTitle: -> $.i18n.t('teacher.course_solution')
+
+ initialize: (options, @courseID, @language) ->
+ if me.isTeacher() or me.isAdmin()
+ @prettyLanguage = @camelCaseLanguage(@language)
+ @course = new Course(_id: @courseID)
+ @supermodel.trackRequest(@course.fetch())
+ @levels = new CocoCollection([], { url: "/db/course/#{@courseID}/level-solutions", model: Level})
+ @supermodel.loadCollection(@levels, 'levels', {cache: false})
+ super(options)
+
+ camelCaseLanguage: (language) ->
+ return language if _.isEmpty(language)
+ return 'JavaScript' if language is 'javascript'
+ language.charAt(0).toUpperCase() + language.slice(1)
+
+ hideWrongLanguage: (s) ->
+ return '' unless s
+ s.replace /```([a-z]+)[^`]+```/gm, (a, l) =>
+ return '' if l isnt @language
+ a
+
+ onLoaded: ->
+ for level in @levels?.models
+ articles = level.get('documentation').specificArticles
+ if articles
+ guide = articles.filter((x) => x.name == "Overview").pop()
+ level.set 'guide', marked(@hideWrongLanguage(guide.body)) if guide
+ intro = articles.filter((x) => x.name == "Intro").pop()
+ level.set 'intro', marked(@hideWrongLanguage(intro.body)) if intro
+ heroPlaceholder = level.get('thangs').filter((x) => x.id == 'Hero Placeholder').pop()
+ comp = heroPlaceholder?.components.filter((x) => x.original.toString() == '524b7b5a7fc0f6d51900000e' ).pop()
+ programmableMethod = comp?.config.programmableMethods.plan
+ if programmableMethod
+ level.set 'begin', _.template(programmableMethod.languages[@language] or programmableMethod.source)(programmableMethod.context)
+ solution = programmableMethod.solutions?.find (x) => x.language is @language
+ level.set 'solution', _.template(solution?.source)(programmableMethod.context)
+ @render?()
diff --git a/server/middleware/courses.coffee b/server/middleware/courses.coffee
index 330193e78..2070e5b1d 100644
--- a/server/middleware/courses.coffee
+++ b/server/middleware/courses.coffee
@@ -1,4 +1,5 @@
errors = require '../commons/errors'
+log = require 'winston'
wrap = require 'co-express'
database = require '../commons/database'
mongoose = require 'mongoose'
@@ -11,6 +12,24 @@ Level = require '../models/Level'
parse = require '../commons/parse'
module.exports =
+
+ fetchLevelSolutions: wrap (req, res) ->
+ unless req.user?.isTeacher() or req.user?.isAdmin()
+ log.debug "courses.fetchLevelSolutions: level solutions only for teachers, (#{req.user?.id})"
+ throw new errors.Forbidden()
+
+ course = yield database.getDocFromHandle(req, Course)
+ throw new errors.NotFound('Course not found.') unless course
+
+ campaign = yield Campaign.findById course.get('campaignID')
+ throw new errors.NotFound('Campaign not found.') unless campaign
+
+ levelOriginals = (mongoose.Types.ObjectId(levelID) for levelID of campaign.get('levels'))
+ query = { original: { $in: levelOriginals }, slug: { $exists: true }}
+ select = {documentation: 1, intro: 1, name: 1, slug: 1, thangs: 1}
+ levels = yield Level.find(query).select(select).lean()
+ res.status(200).send(levels)
+
fetchNextLevel: wrap (req, res) ->
levelOriginal = req.params.levelOriginal
if not database.isID(levelOriginal)
@@ -18,7 +37,7 @@ module.exports =
course = yield database.getDocFromHandle(req, Course)
if not course
- throw new errors.NotFound('Course Instance not found.')
+ throw new errors.NotFound('Course not found.')
campaign = yield Campaign.findById course.get('campaignID')
if not campaign
@@ -43,7 +62,6 @@ module.exports =
dbq = Level.findOne({original: mongoose.Types.ObjectId(nextLevelOriginal)})
-
dbq.sort({ 'version.major': -1, 'version.minor': -1 })
dbq.select(parse.getProjectFromReq(req))
level = yield dbq
diff --git a/server/routes/index.coffee b/server/routes/index.coffee
index 27264368c..9d614b192 100644
--- a/server/routes/index.coffee
+++ b/server/routes/index.coffee
@@ -82,6 +82,7 @@ module.exports.setup = (app) ->
Course = require '../models/Course'
app.get('/db/course', mw.courses.get(Course))
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
+ app.get('/db/course/:handle/level-solutions', mw.courses.fetchLevelSolutions)
app.get('/db/course/:handle/levels/:levelOriginal/next', mw.courses.fetchNextLevel)
app.get('/db/course_instance/-/non-hoc', mw.auth.checkHasPermission(['admin']), mw.courseInstances.fetchNonHoc)
diff --git a/spec/server/functional/courses.spec.coffee b/spec/server/functional/courses.spec.coffee
index b32bb3155..beb867f20 100644
--- a/spec/server/functional/courses.spec.coffee
+++ b/spec/server/functional/courses.spec.coffee
@@ -109,9 +109,9 @@ describe 'GET /db/course/:handle/levels/:levelOriginal/next', ->
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
expect(res.statusCode).toBe(201)
@classroom = yield Classroom.findById(res.body._id)
-
+
url = getURL('/db/course')
-
+
done()
it 'returns the next level for the course in the linked classroom', utils.wrap (done) ->
@@ -119,7 +119,7 @@ describe 'GET /db/course/:handle/levels/:levelOriginal/next', ->
expect(res.statusCode).toBe(200)
expect(res.body.original).toBe(@levelB.original.toString())
done()
-
+
it 'returns empty object if the given level is the last level in its course', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course/#{@courseA.id}/levels/#{@levelB.id}/next"), json: true }
expect(res.statusCode).toBe(200)
@@ -130,3 +130,64 @@ describe 'GET /db/course/:handle/levels/:levelOriginal/next', ->
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course/#{@courseB.id}/levels/#{@levelA.id}/next"), json: true }
expect(res.statusCode).toBe(404)
done()
+
+describe 'GET /db/course/:handle/level-solutions', ->
+ beforeEach utils.wrap (done) ->
+ yield utils.clearModels [User, Classroom, Course, Level, Campaign]
+ admin = yield utils.initAdmin()
+ yield utils.loginUser(admin)
+
+ levelJSON = { name: 'A', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
+ [res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
+ expect(res.statusCode).toBe(200)
+ @levelA = yield Level.findById(res.body._id)
+ paredLevelA = _.pick(res.body, 'name', 'original', 'type')
+
+ levelJSON = { name: 'B', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
+ [res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
+ expect(res.statusCode).toBe(200)
+ @levelB = yield Level.findById(res.body._id)
+ paredLevelB = _.pick(res.body, 'name', 'original', 'type')
+
+ campaignJSONA = { name: 'Campaign A', levels: {} }
+ campaignJSONA.levels[paredLevelA.original] = paredLevelA
+ campaignJSONA.levels[paredLevelB.original] = paredLevelB
+ [res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONA})
+ @campaignA = yield Campaign.findById(res.body._id)
+
+ @courseA = Course({name: 'Course A', campaignID: @campaignA._id, releasePhase: 'released'})
+ yield @courseA.save()
+
+ done()
+
+ describe 'when admin', ->
+
+ it 'returns level solutions', utils.wrap (done) ->
+ [res, body] = yield request.getAsync { uri: utils.getURL("/db/course/#{@courseA.id}/level-solutions"), json: true }
+ expect(res.statusCode).toBe(200)
+ expect(body.length).toEqual(2)
+ expect(body[0].slug).toEqual('a')
+ done()
+
+ describe 'when teacher', ->
+ beforeEach utils.wrap (done) ->
+ teacher = yield utils.initUser({role: 'teacher'})
+ yield utils.loginUser(teacher)
+ done()
+
+ it 'returns level solutions', utils.wrap (done) ->
+ [res, body] = yield request.getAsync { uri: utils.getURL("/db/course/#{@courseA.id}/level-solutions"), json: true }
+ expect(res.statusCode).toBe(200)
+ expect(body.length).toEqual(2)
+ expect(body[1].slug).toEqual('b')
+ done()
+
+ describe 'when anonymous', ->
+ beforeEach utils.wrap (done) ->
+ yield utils.logout()
+ done()
+
+ it 'returns 403', utils.wrap (done) ->
+ [res, body] = yield request.getAsync { uri: utils.getURL("/db/course/#{@courseA.id}/level-solutions"), json: true }
+ expect(res.statusCode).toBe(403)
+ done()
diff --git a/test/app/models/SuperModel.spec.coffee b/test/app/models/SuperModel.spec.coffee
index c4c615727..be0ff6c95 100644
--- a/test/app/models/SuperModel.spec.coffee
+++ b/test/app/models/SuperModel.spec.coffee
@@ -58,7 +58,7 @@ describe 'SuperModel', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request).toBeDefined()
- describe 'timeout handling', ->
+ xdescribe 'timeout handling', ->
beforeEach ->
jasmine.clock().install()
afterEach ->