From e21217dad34490a0d7a5e749973d88f0ab71f801 Mon Sep 17 00:00:00 2001 From: iammehmetguler Date: Thu, 11 Aug 2016 23:29:23 +0300 Subject: [PATCH 1/5] Update tr.coffee (#3837) calendar --- app/locale/tr.coffee | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) 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" From 5fc184da674378d7d29ffc30e374de12e40bdd38 Mon Sep 17 00:00:00 2001 From: Zeldaretter Date: Thu, 11 Aug 2016 23:13:03 +0200 Subject: [PATCH 2/5] Update de-De.coffee (#3838) some translations --- app/locale/de-DE.coffee | 230 ++++++++++++++++++++-------------------- 1 file changed, 115 insertions(+), 115 deletions(-) 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" From a7e290fffe4faa341bd031962e80ade78af723f5 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Thu, 11 Aug 2016 15:29:53 -0700 Subject: [PATCH 3/5] Replace course guide PDFs with solutions pages --- app/core/Router.coffee | 1 + app/locale/en.coffee | 12 +++- .../teacher-course-solution-view.sass | 31 +++++++++ .../courses/teacher-courses-view.jade | 28 +++----- .../teacher-course-solution-view.jade | 52 ++++++++++++++ .../teachers/TeacherCourseSolutionView.coffee | 47 +++++++++++++ server/middleware/courses.coffee | 22 +++++- server/routes/index.coffee | 1 + spec/server/functional/courses.spec.coffee | 67 ++++++++++++++++++- 9 files changed, 234 insertions(+), 27 deletions(-) create mode 100644 app/styles/teachers/teacher-course-solution-view.sass create mode 100644 app/templates/teachers/teacher-course-solution-view.jade create mode 100644 app/views/teachers/TeacherCourseSolutionView.coffee 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/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/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..60f076bd8 --- /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() From 290556372918e87dfcdb6cbe36ec6badbffe9794 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Thu, 11 Aug 2016 15:34:39 -0700 Subject: [PATCH 4/5] :bug:Remove semi-colons from course solutions view --- app/views/teachers/TeacherCourseSolutionView.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/teachers/TeacherCourseSolutionView.coffee b/app/views/teachers/TeacherCourseSolutionView.coffee index 60f076bd8..99ee28791 100644 --- a/app/views/teachers/TeacherCourseSolutionView.coffee +++ b/app/views/teachers/TeacherCourseSolutionView.coffee @@ -31,7 +31,7 @@ module.exports = class TeacherCourseSolutionView extends RootView onLoaded: -> for level in @levels?.models - articles = level.get('documentation').specificArticles; + articles = level.get('documentation').specificArticles if articles guide = articles.filter((x) => x.name == "Overview").pop() level.set 'guide', marked(@hideWrongLanguage(guide.body)) if guide @@ -39,7 +39,7 @@ module.exports = class TeacherCourseSolutionView extends RootView 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; + 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 From 0f00ae17185ac2e5a91b431591c27c1776feca83 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Thu, 11 Aug 2016 15:36:52 -0700 Subject: [PATCH 5/5] Disable supermodel timeout handling tests --- test/app/models/SuperModel.spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ->