mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 16:51:35 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
864e18d23b
30 changed files with 145 additions and 49 deletions
|
@ -69,15 +69,15 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'contribute/diplomat': go('contribute/DiplomatView')
|
'contribute/diplomat': go('contribute/DiplomatView')
|
||||||
'contribute/scribe': go('contribute/ScribeView')
|
'contribute/scribe': go('contribute/ScribeView')
|
||||||
|
|
||||||
'courses': go('courses/CoursesView') # , { studentsOnly: true }) # TODO: Enforce after session-less play for teachers
|
'courses': go('courses/CoursesView', { studentsOnly: true })
|
||||||
'Courses': go('courses/CoursesView') # , { studentsOnly: true })
|
'Courses': go('courses/CoursesView', { studentsOnly: true })
|
||||||
'courses/students': redirect('/courses')
|
'courses/students': redirect('/courses')
|
||||||
'courses/teachers': redirect('/teachers/classes')
|
'courses/teachers': redirect('/teachers/classes')
|
||||||
'courses/purchase': redirect('/teachers/licenses')
|
'courses/purchase': redirect('/teachers/licenses')
|
||||||
'courses/enroll(/:courseID)': redirect('/teachers/licenses')
|
'courses/enroll(/:courseID)': redirect('/teachers/licenses')
|
||||||
'courses/update-account': go('courses/CoursesUpdateAccountView')
|
'courses/update-account': go('courses/CoursesUpdateAccountView')
|
||||||
'courses/:classroomID': go('courses/ClassroomView') #, { studentsOnly: true })
|
'courses/:classroomID': go('courses/ClassroomView', { studentsOnly: true })
|
||||||
'courses/:courseID/:courseInstanceID': go('courses/CourseDetailsView')
|
'courses/:courseID/:courseInstanceID': go('courses/CourseDetailsView', { studentsOnly: true })
|
||||||
|
|
||||||
'db/*path': 'routeToServer'
|
'db/*path': 'routeToServer'
|
||||||
'demo(/*subpath)': go('DemoView')
|
'demo(/*subpath)': go('DemoView')
|
||||||
|
@ -142,14 +142,14 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'SEEN': go('NewHomeView')
|
'SEEN': go('NewHomeView')
|
||||||
|
|
||||||
'teachers': redirect('/teachers/classes')
|
'teachers': redirect('/teachers/classes')
|
||||||
'teachers/classes': go('courses/TeacherClassesView') #, { teachersOnly: true })
|
'teachers/classes': go('courses/TeacherClassesView', { teachersOnly: true })
|
||||||
'teachers/classes/:classroomID': go('courses/TeacherClassView') #, { teachersOnly: true })
|
'teachers/classes/:classroomID': go('courses/TeacherClassView', { teachersOnly: true })
|
||||||
'teachers/courses': go('courses/TeacherCoursesView')
|
'teachers/courses': go('courses/TeacherCoursesView')
|
||||||
'teachers/demo': go('teachers/RequestQuoteView')
|
'teachers/demo': go('teachers/RequestQuoteView')
|
||||||
'teachers/enrollments': redirect('/teachers/licenses')
|
'teachers/enrollments': redirect('/teachers/licenses')
|
||||||
'teachers/licenses': go('courses/EnrollmentsView') #, { teachersOnly: true })
|
'teachers/licenses': go('courses/EnrollmentsView', { teachersOnly: true })
|
||||||
'teachers/freetrial': go('teachers/RequestQuoteView')
|
'teachers/freetrial': go('teachers/RequestQuoteView')
|
||||||
'teachers/quote': go('teachers/RequestQuoteView')
|
'teachers/quote': redirect('/teachers/demo')
|
||||||
'teachers/signup': ->
|
'teachers/signup': ->
|
||||||
return @routeDirectly('teachers/CreateTeacherAccountView', []) if me.isAnonymous()
|
return @routeDirectly('teachers/CreateTeacherAccountView', []) if me.isAnonymous()
|
||||||
@navigate('/teachers/update-account', {trigger: true, replace: true})
|
@navigate('/teachers/update-account', {trigger: true, replace: true})
|
||||||
|
@ -174,7 +174,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
routeDirectly: (path, args=[], options={}) ->
|
routeDirectly: (path, args=[], options={}) ->
|
||||||
if options.teachersOnly and not me.isTeacher()
|
if options.teachersOnly and not me.isTeacher()
|
||||||
return @routeDirectly('teachers/RestrictedToTeachersView')
|
return @routeDirectly('teachers/RestrictedToTeachersView')
|
||||||
if options.studentsOnly and me.isTeacher()
|
if options.studentsOnly and not me.isStudent()
|
||||||
return @routeDirectly('courses/RestrictedToStudentsView')
|
return @routeDirectly('courses/RestrictedToStudentsView')
|
||||||
leavingMessage = _.result(window.currentView, 'onLeaveMessage')
|
leavingMessage = _.result(window.currentView, 'onLeaveMessage')
|
||||||
if leavingMessage
|
if leavingMessage
|
||||||
|
|
|
@ -1279,7 +1279,8 @@
|
||||||
student_age_range_to: "to"
|
student_age_range_to: "to"
|
||||||
create_class: "Create Class"
|
create_class: "Create Class"
|
||||||
class_name: "Class Name"
|
class_name: "Class Name"
|
||||||
teacher_account_restricted: "Your account is a teacher account, and so cannot access student content."
|
teacher_account_restricted: "Your account is a teacher account and cannot access student content." # {change}
|
||||||
|
account_restricted: "A student account is required to access this page."
|
||||||
update_account_login_title: "Log in to update your account"
|
update_account_login_title: "Log in to update your account"
|
||||||
update_account_title: "Your account needs attention!"
|
update_account_title: "Your account needs attention!"
|
||||||
update_account_blurb: "Before you can access your classes, choose how you want to use this account."
|
update_account_blurb: "Before you can access your classes, choose how you want to use this account."
|
||||||
|
|
|
@ -25,7 +25,7 @@ module.exports = class User extends CocoModel
|
||||||
return name if name
|
return name if name
|
||||||
[emailName, emailDomain] = @get('email')?.split('@') or []
|
[emailName, emailDomain] = @get('email')?.split('@') or []
|
||||||
return emailName if emailName
|
return emailName if emailName
|
||||||
return 'Anoner'
|
return 'Anonymous'
|
||||||
|
|
||||||
getPhotoURL: (size=80, useJobProfilePhoto=false, useEmployerPageAvatar=false) ->
|
getPhotoURL: (size=80, useJobProfilePhoto=false, useEmployerPageAvatar=false) ->
|
||||||
photoURL = if useJobProfilePhoto then @get('jobProfile')?.photoURL else null
|
photoURL = if useJobProfilePhoto then @get('jobProfile')?.photoURL else null
|
||||||
|
|
|
@ -6,7 +6,7 @@ UserSchema = c.object
|
||||||
default:
|
default:
|
||||||
visa: 'Authorized to work in the US'
|
visa: 'Authorized to work in the US'
|
||||||
music: true
|
music: true
|
||||||
name: 'Anoner'
|
name: 'Anonymous'
|
||||||
autocastDelay: 5000
|
autocastDelay: 5000
|
||||||
emails: {}
|
emails: {}
|
||||||
permissions: []
|
permissions: []
|
||||||
|
|
|
@ -123,7 +123,7 @@ block content
|
||||||
if memberLanguageMap && memberLanguageMap[member.id]
|
if memberLanguageMap && memberLanguageMap[member.id]
|
||||||
span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
|
span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
|
||||||
div
|
div
|
||||||
a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
|
a(href="/user/#{member.id}")= member.get('name') || 'Anonymous'
|
||||||
div Level #{member.level()}
|
div Level #{member.level()}
|
||||||
if isOwner && member.id !== clan.get('ownerID')
|
if isOwner && member.id !== clan.get('ownerID')
|
||||||
button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}", data-i18n="clans.rem_hero") Remove Hero
|
button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}", data-i18n="clans.rem_hero") Remove Hero
|
||||||
|
@ -220,7 +220,7 @@ block content
|
||||||
if memberLanguageMap && memberLanguageMap[member.id]
|
if memberLanguageMap && memberLanguageMap[member.id]
|
||||||
span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
|
span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
|
||||||
td.name-cell
|
td.name-cell
|
||||||
a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
|
a(href="/user/#{member.id}")= member.get('name') || 'Anonymous'
|
||||||
td.level-cell= member.level()
|
td.level-cell= member.level()
|
||||||
td.achievements-cell
|
td.achievements-cell
|
||||||
if memberAchievementsMap && memberAchievementsMap[member.id]
|
if memberAchievementsMap && memberAchievementsMap[member.id]
|
||||||
|
|
|
@ -45,7 +45,7 @@ block content
|
||||||
if view.idNameMap && view.idNameMap[clan.get('ownerID')]
|
if view.idNameMap && view.idNameMap[clan.get('ownerID')]
|
||||||
a(href="/user/#{clan.get('ownerID')}")= view.idNameMap[clan.get('ownerID')]
|
a(href="/user/#{clan.get('ownerID')}")= view.idNameMap[clan.get('ownerID')]
|
||||||
else
|
else
|
||||||
a(href="/user/#{clan.get('ownerID')}") Anoner
|
a(href="/user/#{clan.get('ownerID')}") Anonymous
|
||||||
td
|
td
|
||||||
if view.myClanIDs.indexOf(clan.id) < 0
|
if view.myClanIDs.indexOf(clan.id) < 0
|
||||||
button.btn.btn-success.join-clan-btn(data-id="#{clan.id}", data-i18n="clans.join_clan") Join Clan
|
button.btn.btn-success.join-clan-btn(data-id="#{clan.id}", data-i18n="clans.join_clan") Join Clan
|
||||||
|
@ -75,7 +75,7 @@ block content
|
||||||
if view.idNameMap && view.idNameMap[clan.get('ownerID')]
|
if view.idNameMap && view.idNameMap[clan.get('ownerID')]
|
||||||
a(href="/user/#{clan.get('ownerID')}")= view.idNameMap[clan.get('ownerID')]
|
a(href="/user/#{clan.get('ownerID')}")= view.idNameMap[clan.get('ownerID')]
|
||||||
else
|
else
|
||||||
a(href="/user/#{clan.get('ownerID')}") Anoner
|
a(href="/user/#{clan.get('ownerID')}") Anonymous
|
||||||
td= clan.get('type')
|
td= clan.get('type')
|
||||||
td
|
td
|
||||||
if clan.get('ownerID') !== me.id
|
if clan.get('ownerID') !== me.id
|
||||||
|
|
|
@ -3,10 +3,19 @@ extends /templates/base-flat
|
||||||
block content
|
block content
|
||||||
.access-restricted.container.text-center.m-y-3
|
.access-restricted.container.text-center.m-y-3
|
||||||
h5(data-i18n='teacher.access_restricted')
|
h5(data-i18n='teacher.access_restricted')
|
||||||
|
if me.isTeacher()
|
||||||
p(data-i18n='courses.teacher_account_restricted')
|
p(data-i18n='courses.teacher_account_restricted')
|
||||||
a.btn.btn-lg.btn-primary(href="/teachers/classes" data-i18n="new_home.goto_classes")
|
a.btn.btn-lg.btn-primary(href="/teachers/classes" data-i18n="new_home.goto_classes")
|
||||||
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||||
|
else
|
||||||
|
p(data-i18n='courses.account_restricted')
|
||||||
|
if me.isAnonymous()
|
||||||
|
.login-button.btn.btn-lg.btn-primary(data-i18n='login.log_in')
|
||||||
|
else
|
||||||
|
a.btn.btn-lg.btn-primary(href="/courses/update-account" data-i18n="courses.update_account_update_student")
|
||||||
|
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||||
|
|
||||||
|
if me.isTeacher()
|
||||||
.teacher-account-blurb.text-center.col-xs-6.col-xs-offset-3.m-y-3
|
.teacher-account-blurb.text-center.col-xs-6.col-xs-offset-3.m-y-3
|
||||||
h5(data-i18n='teacher.what_is_a_teacher_account')
|
h5(data-i18n='teacher.what_is_a_teacher_account')
|
||||||
p(data-i18n='teacher.teacher_account_explanation')
|
p(data-i18n='teacher.teacher_account_explanation')
|
|
@ -11,7 +11,7 @@ ul.user-feedback-list.list-group
|
||||||
em= moment(new Date(feedback.created)).fromNow()
|
em= moment(new Date(feedback.created)).fromNow()
|
||||||
span.spl.spr -
|
span.spl.spr -
|
||||||
a(href="/user/#{feedback.creator}")
|
a(href="/user/#{feedback.creator}")
|
||||||
strong= feedback.creatorName || 'Anoner'
|
strong= feedback.creatorName || 'Anonymous'
|
||||||
if feedback.review
|
if feedback.review
|
||||||
span.spr :
|
span.spr :
|
||||||
span= feedback.review
|
span= feedback.review
|
||||||
|
|
|
@ -12,7 +12,7 @@ for player in view.players
|
||||||
.player-gold
|
.player-gold
|
||||||
.gold-icon
|
.gold-icon
|
||||||
.gold-value
|
.gold-value
|
||||||
.player-name= player.name || 'Anoner'
|
.player-name= player.name || 'Anonymous'
|
||||||
.player-health
|
.player-health
|
||||||
.health-icon
|
.health-icon
|
||||||
.health-bar-container
|
.health-bar-container
|
||||||
|
|
|
@ -81,7 +81,7 @@ block append content
|
||||||
if idNameMap && idNameMap[clan.get('ownerID')]
|
if idNameMap && idNameMap[clan.get('ownerID')]
|
||||||
a(href="/user/#{clan.get('ownerID')}")= idNameMap[clan.get('ownerID')]
|
a(href="/user/#{clan.get('ownerID')}")= idNameMap[clan.get('ownerID')]
|
||||||
else
|
else
|
||||||
a(href="/user/#{clan.get('ownerID')}") Anoner
|
a(href="/user/#{clan.get('ownerID')}") Anonymous
|
||||||
td= clan.get('members').length
|
td= clan.get('members').length
|
||||||
else
|
else
|
||||||
.panel-body
|
.panel-body
|
||||||
|
|
|
@ -23,6 +23,8 @@ module.exports = class AboutView extends RootView
|
||||||
'left': 'onLeftPressed'
|
'left': 'onLeftPressed'
|
||||||
'esc': 'onEscapePressed'
|
'esc': 'onEscapePressed'
|
||||||
|
|
||||||
|
getTitle: -> return $.i18n.t('nav.about')
|
||||||
|
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
super(arguments...)
|
super(arguments...)
|
||||||
@$('#fixed-nav').affix({
|
@$('#fixed-nav').affix({
|
||||||
|
|
|
@ -20,6 +20,8 @@ module.exports = class MainAdminView extends RootView
|
||||||
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
||||||
'click #terminal-create': 'onClickTerminalSubLink'
|
'click #terminal-create': 'onClickTerminalSubLink'
|
||||||
|
|
||||||
|
getTitle: -> return $.i18n.t('account_settings.admin')
|
||||||
|
|
||||||
initialize: ->
|
initialize: ->
|
||||||
if window.amActually
|
if window.amActually
|
||||||
@amActually = new User({_id: window.amActually})
|
@amActually = new User({_id: window.amActually})
|
||||||
|
@ -65,7 +67,7 @@ module.exports = class MainAdminView extends RootView
|
||||||
forms.enableSubmit(@$('#user-search-button'))
|
forms.enableSubmit(@$('#user-search-button'))
|
||||||
result = ''
|
result = ''
|
||||||
if users.length
|
if users.length
|
||||||
result = ("<tr data-user-id='#{user._id}'><td><code>#{user._id}</code></td><td>#{_.escape(user.name or 'Anoner')}</td><td>#{_.escape(user.email)}</td></tr>" for user in users)
|
result = ("<tr data-user-id='#{user._id}'><td><code>#{user._id}</code></td><td>#{_.escape(user.name or 'Anonymous')}</td><td>#{_.escape(user.email)}</td></tr>" for user in users)
|
||||||
result = "<table class=\"table\">#{result.join('\n')}</table>"
|
result = "<table class=\"table\">#{result.join('\n')}</table>"
|
||||||
@$el.find('#user-search-result').html(result)
|
@$el.find('#user-search-result').html(result)
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ module.exports = class ClanDetailsView extends RootView
|
||||||
return unless @members? and @memberSort?
|
return unless @members? and @memberSort?
|
||||||
switch @memberSort
|
switch @memberSort
|
||||||
when "nameDesc"
|
when "nameDesc"
|
||||||
@members.comparator = (a, b) -> return (b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner')
|
@members.comparator = (a, b) -> return (b.get('name') or 'Anonymous').localeCompare(a.get('name') or 'Anonymous')
|
||||||
when "progressAsc"
|
when "progressAsc"
|
||||||
@members.comparator = (a, b) ->
|
@members.comparator = (a, b) ->
|
||||||
aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete')
|
aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete')
|
||||||
|
@ -151,7 +151,7 @@ module.exports = class ClanDetailsView extends RootView
|
||||||
else if aStarted > bStarted then return 1
|
else if aStarted > bStarted then return 1
|
||||||
if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return -1
|
if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return -1
|
||||||
else if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return 1
|
else if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return 1
|
||||||
(a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner')
|
(a.get('name') or 'Anonymous').localeCompare(b.get('name') or 'Anonymous')
|
||||||
when "progressDesc"
|
when "progressDesc"
|
||||||
@members.comparator = (a, b) ->
|
@members.comparator = (a, b) ->
|
||||||
aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete')
|
aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete')
|
||||||
|
@ -164,9 +164,9 @@ module.exports = class ClanDetailsView extends RootView
|
||||||
else if aStarted < bStarted then return 1
|
else if aStarted < bStarted then return 1
|
||||||
if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return -1
|
if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return -1
|
||||||
else if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return 1
|
else if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return 1
|
||||||
(b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner')
|
(b.get('name') or 'Anonymous').localeCompare(a.get('name') or 'Anonymous')
|
||||||
else
|
else
|
||||||
@members.comparator = (a, b) -> return (a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner')
|
@members.comparator = (a, b) -> return (a.get('name') or 'Anonymous').localeCompare(b.get('name') or 'Anonymous')
|
||||||
@members.sort()
|
@members.sort()
|
||||||
|
|
||||||
updateHeroIcons: ->
|
updateHeroIcons: ->
|
||||||
|
|
|
@ -110,10 +110,8 @@ module.exports = class RootView extends CocoView
|
||||||
@buildLanguages()
|
@buildLanguages()
|
||||||
$('body').removeClass('is-playing')
|
$('body').removeClass('is-playing')
|
||||||
|
|
||||||
if application.isProduction()
|
if title = @getTitle() then title += ' | CodeCombat'
|
||||||
title = 'CodeCombat - ' + (@getTitle() or 'Learn how to code by playing a game')
|
else title = 'CodeCombat - Learn how to code by playing a game'
|
||||||
else
|
|
||||||
title = @getTitle() or @constructor.name
|
|
||||||
|
|
||||||
$('title').text(title)
|
$('title').text(title)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ module.exports = class CoursesView extends RootView
|
||||||
'submit #join-class-form': 'onSubmitJoinClassForm'
|
'submit #join-class-form': 'onSubmitJoinClassForm'
|
||||||
'click #change-language-link': 'onClickChangeLanguageLink'
|
'click #change-language-link': 'onClickChangeLanguageLink'
|
||||||
|
|
||||||
|
getTitle: -> return $.i18n.t('teacher.students')
|
||||||
|
|
||||||
initialize: ->
|
initialize: ->
|
||||||
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
||||||
@courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID')
|
@courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID')
|
||||||
|
|
|
@ -19,6 +19,8 @@ module.exports = class EnrollmentsView extends RootView
|
||||||
'click #how-to-enroll-link': 'onClickHowToEnrollLink'
|
'click #how-to-enroll-link': 'onClickHowToEnrollLink'
|
||||||
'click #contact-us-btn': 'onClickContactUsButton'
|
'click #contact-us-btn': 'onClickContactUsButton'
|
||||||
|
|
||||||
|
getTitle: -> return $.i18n.t('teacher.enrollments')
|
||||||
|
|
||||||
initialize: ->
|
initialize: ->
|
||||||
@state = new State({
|
@state = new State({
|
||||||
totalEnrolled: 0
|
totalEnrolled: 0
|
||||||
|
|
|
@ -63,6 +63,8 @@ module.exports = class TeacherClassView extends RootView
|
||||||
enrolledUsers: ""
|
enrolledUsers: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTitle: -> return @classroom?.get('name')
|
||||||
|
|
||||||
initialize: (options, classroomID) ->
|
initialize: (options, classroomID) ->
|
||||||
super(options)
|
super(options)
|
||||||
@singleStudentCourseProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-course'
|
@singleStudentCourseProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-course'
|
||||||
|
@ -277,7 +279,6 @@ module.exports = class TeacherClassView extends RootView
|
||||||
@state.set('searchTerm', $(e.target).val())
|
@state.set('searchTerm', $(e.target).val())
|
||||||
|
|
||||||
onChangeCourseSelect: (e) ->
|
onChangeCourseSelect: (e) ->
|
||||||
console.log '??'
|
|
||||||
@trigger 'course-select:change', { selectedCourse: @courses.get($(e.currentTarget).val()) }
|
@trigger 'course-select:change', { selectedCourse: @courses.get($(e.currentTarget).val()) }
|
||||||
|
|
||||||
getSelectedStudentIDs: ->
|
getSelectedStudentIDs: ->
|
||||||
|
|
|
@ -25,6 +25,8 @@ module.exports = class TeacherClassesView extends RootView
|
||||||
'click .add-students-btn': 'onClickAddStudentsButton'
|
'click .add-students-btn': 'onClickAddStudentsButton'
|
||||||
'click .create-classroom-btn': 'onClickCreateClassroomButton'
|
'click .create-classroom-btn': 'onClickCreateClassroomButton'
|
||||||
|
|
||||||
|
getTitle: -> return $.i18n.t('teacher.my_classes')
|
||||||
|
|
||||||
initialize: (options) ->
|
initialize: (options) ->
|
||||||
super(options)
|
super(options)
|
||||||
@classrooms = new Classrooms()
|
@classrooms = new Classrooms()
|
||||||
|
|
|
@ -39,6 +39,8 @@ module.exports = class TeacherCoursesView extends RootView
|
||||||
"569ed916efa72b0ced971447": null
|
"569ed916efa72b0ced971447": null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTitle: -> return $.i18n.t('teacher.courses')
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super(options)
|
super(options)
|
||||||
@ownedClassrooms = new Classrooms()
|
@ownedClassrooms = new Classrooms()
|
||||||
|
|
|
@ -61,7 +61,7 @@ module.exports = class LadderPlayModal extends ModalView
|
||||||
|
|
||||||
success = (@nameMap) =>
|
success = (@nameMap) =>
|
||||||
for challenger in _.values(@challengers)
|
for challenger in _.values(@challengers)
|
||||||
challenger.opponentName = @nameMap[challenger.opponentID]?.name or 'Anoner'
|
challenger.opponentName = @nameMap[challenger.opponentID]?.name or 'Anonymous'
|
||||||
challenger.opponentWizard = @nameMap[challenger.opponentID]?.wizard or {}
|
challenger.opponentWizard = @nameMap[challenger.opponentID]?.wizard or {}
|
||||||
@checkWizardLoaded()
|
@checkWizardLoaded()
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@ module.exports = class SpectateLevelView extends RootView
|
||||||
findPlayerNames: ->
|
findPlayerNames: ->
|
||||||
playerNames = {}
|
playerNames = {}
|
||||||
for session in [@session, @otherSession] when session?.get('team')
|
for session in [@session, @otherSession] when session?.get('team')
|
||||||
playerNames[session.get('team')] = session.get('creatorName') or 'Anoner'
|
playerNames[session.get('team')] = session.get('creatorName') or 'Anonymous'
|
||||||
playerNames
|
playerNames
|
||||||
|
|
||||||
initGoalManager: ->
|
initGoalManager: ->
|
||||||
|
|
|
@ -353,7 +353,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
return {} unless @level.get('type') in ['ladder', 'hero-ladder', 'course-ladder']
|
return {} unless @level.get('type') in ['ladder', 'hero-ladder', 'course-ladder']
|
||||||
playerNames = {}
|
playerNames = {}
|
||||||
for session in [@session, @otherSession] when session?.get('team')
|
for session in [@session, @otherSession] when session?.get('team')
|
||||||
playerNames[session.get('team')] = session.get('creatorName') or 'Anoner'
|
playerNames[session.get('team')] = session.get('creatorName') or 'Anonymous'
|
||||||
playerNames
|
playerNames
|
||||||
|
|
||||||
# Once Surface is Loaded ####################################################
|
# Once Surface is Loaded ####################################################
|
||||||
|
|
|
@ -87,7 +87,7 @@ emailUserInitialRecruiting = (user, callback) ->
|
||||||
return callback null, false if DEBUGGING and (totalEmailsSent > 1 or Math.random() > 0.05)
|
return callback null, false if DEBUGGING and (totalEmailsSent > 1 or Math.random() > 0.05)
|
||||||
++totalEmailsSent
|
++totalEmailsSent
|
||||||
name = if user.firstName and user.lastName then "#{user.firstName}" else user.name
|
name = if user.firstName and user.lastName then "#{user.firstName}" else user.name
|
||||||
name = 'Wizard' if not name or name is 'Anoner'
|
name = 'Wizard' if not name or name in ['Anoner', 'Anonymous']
|
||||||
team = user.session.levelInfo.team
|
team = user.session.levelInfo.team
|
||||||
team = team.substr(0, team.length - 1)
|
team = team.substr(0, team.length - 1)
|
||||||
context =
|
context =
|
||||||
|
|
|
@ -69,6 +69,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
||||||
return _.omit(doc.toObject(), 'code', 'codeCamel')
|
return _.omit(doc.toObject(), 'code', 'codeCamel')
|
||||||
|
|
||||||
inviteStudents: (req, res, classroomID) ->
|
inviteStudents: (req, res, classroomID) ->
|
||||||
|
return @sendUnauthorizedError(res) if not req.user?
|
||||||
if not req.body.emails
|
if not req.body.emails
|
||||||
return @sendBadInputError(res, 'Emails not included')
|
return @sendBadInputError(res, 'Emails not included')
|
||||||
|
|
||||||
|
@ -86,6 +87,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
||||||
recipient:
|
recipient:
|
||||||
address: email
|
address: email
|
||||||
email_data:
|
email_data:
|
||||||
|
teacher_name: req.user.broadName()
|
||||||
class_name: classroom.get('name')
|
class_name: classroom.get('name')
|
||||||
join_link: "https://codecombat.com/courses?_cc=" + joinCode
|
join_link: "https://codecombat.com/courses?_cc=" + joinCode
|
||||||
join_code: joinCode
|
join_code: joinCode
|
||||||
|
@ -103,7 +105,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
||||||
return @sendSuccess(res, (@formatEntity(req, classroom) for classroom in classrooms))
|
return @sendSuccess(res, (@formatEntity(req, classroom) for classroom in classrooms))
|
||||||
else if memberID = req.query.memberID
|
else if memberID = req.query.memberID
|
||||||
unless req.user and (req.user.isAdmin() or memberID is req.user.id)
|
unless req.user and (req.user.isAdmin() or memberID is req.user.id)
|
||||||
log.debug "classroom_handler.get: memberID (#{memberID}) must be yourself (#{req.user.id})"
|
log.debug "classroom_handler.get: memberID (#{memberID}) must be yourself (#{req.user?.id})"
|
||||||
return @sendForbiddenError(res)
|
return @sendForbiddenError(res)
|
||||||
return @sendBadInputError(res, 'Bad memberID') unless utils.isID memberID
|
return @sendBadInputError(res, 'Bad memberID') unless utils.isID memberID
|
||||||
Classroom.find {members: mongoose.Types.ObjectId(memberID)}, (err, classrooms) =>
|
Classroom.find {members: mongoose.Types.ObjectId(memberID)}, (err, classrooms) =>
|
||||||
|
|
|
@ -192,6 +192,7 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
||||||
address: email
|
address: email
|
||||||
subject: course.get('name')
|
subject: course.get('name')
|
||||||
email_data:
|
email_data:
|
||||||
|
teacher_name: req.user.broadName()
|
||||||
class_name: course.get('name')
|
class_name: course.get('name')
|
||||||
join_link: "https://codecombat.com/courses/students?_ppc=" + prepaid.get('code')
|
join_link: "https://codecombat.com/courses/students?_ppc=" + prepaid.get('code')
|
||||||
sendwithus.api.send context, _.noop
|
sendwithus.api.send context, _.noop
|
||||||
|
|
|
@ -68,4 +68,5 @@ module.exports =
|
||||||
return done(null, activity.sender) if /@codecombat\.com/ig.test(activity.sender)
|
return done(null, activity.sender) if /@codecombat\.com/ig.test(activity.sender)
|
||||||
return done(null, config.mail.supportSchools)
|
return done(null, config.mail.supportSchools)
|
||||||
catch error
|
catch error
|
||||||
|
log.error("closeIO.getSalesContactEmail Error for #{email}: #{JSON.stringify(error)}")
|
||||||
return done(error, config.mail.supportSchools)
|
return done(error, config.mail.supportSchools)
|
||||||
|
|
|
@ -132,7 +132,7 @@ module.exports =
|
||||||
post: wrap (req, res) ->
|
post: wrap (req, res) ->
|
||||||
throw new errors.Unauthorized() unless req.user and not req.user.isAnonymous()
|
throw new errors.Unauthorized() unless req.user and not req.user.isAnonymous()
|
||||||
unless req.user?.isTeacher()
|
unless req.user?.isTeacher()
|
||||||
console.log "classrooms.post: Can't create classroom if you (#{req.user?.id}) aren't a teacher."
|
log.debug "classrooms.post: Can't create classroom if you (#{req.user?.id}) aren't a teacher."
|
||||||
throw new errors.Forbidden()
|
throw new errors.Forbidden()
|
||||||
classroom = database.initDoc(req, Classroom)
|
classroom = database.initDoc(req, Classroom)
|
||||||
classroom.set 'ownerID', req.user._id
|
classroom.set 'ownerID', req.user._id
|
||||||
|
@ -205,7 +205,8 @@ module.exports =
|
||||||
ownedStudentIDs = _.flatten ownedClassrooms.map (c) ->
|
ownedStudentIDs = _.flatten ownedClassrooms.map (c) ->
|
||||||
c.get('members').map (id) ->
|
c.get('members').map (id) ->
|
||||||
id.toString()
|
id.toString()
|
||||||
return next() unless memberID in ownedStudentIDs
|
unless memberID in ownedStudentIDs
|
||||||
|
throw new errors.Forbidden("Can't reset the password of a student that's not in one of your classrooms.")
|
||||||
student = yield User.findById(memberID)
|
student = yield User.findById(memberID)
|
||||||
if student.get('emailVerified')
|
if student.get('emailVerified')
|
||||||
log.debug "classrooms.setStudentPassword: Can't reset password for a student (#{memberID}) that has verified their email address."
|
log.debug "classrooms.setStudentPassword: Can't reset password for a student (#{memberID}) that has verified their email address."
|
||||||
|
|
|
@ -49,7 +49,7 @@ UserSchema.methods.broadName = ->
|
||||||
return name if name
|
return name if name
|
||||||
[emailName, emailDomain] = @get('email').split('@')
|
[emailName, emailDomain] = @get('email').split('@')
|
||||||
return emailName if emailName
|
return emailName if emailName
|
||||||
return 'Anoner'
|
return 'Anonymous'
|
||||||
|
|
||||||
UserSchema.methods.isInGodMode = ->
|
UserSchema.methods.isInGodMode = ->
|
||||||
p = @get('permissions')
|
p = @get('permissions')
|
||||||
|
|
|
@ -578,7 +578,7 @@ sendLadderUpdateEmail = (session, now, daysAgo) ->
|
||||||
#log.info "Not sending email to #{user.get('email')} #{user.get('name')} because the session had levelName #{session.levelName} or team #{session.team} in it."
|
#log.info "Not sending email to #{user.get('email')} #{user.get('name')} because the session had levelName #{session.levelName} or team #{session.team} in it."
|
||||||
return
|
return
|
||||||
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
||||||
name = 'Wizard' if not name or name is 'Anoner'
|
name = 'Wizard' if not name or name is 'Anonymous'
|
||||||
|
|
||||||
# Fetch the most recent defeat and victory, if there are any.
|
# Fetch the most recent defeat and victory, if there are any.
|
||||||
# (We could look at strongest/weakest, but we'd have to fetch everyone, or denormalize more.)
|
# (We could look at strongest/weakest, but we'd have to fetch everyone, or denormalize more.)
|
||||||
|
@ -622,13 +622,13 @@ sendLadderUpdateEmail = (session, now, daysAgo) ->
|
||||||
if err
|
if err
|
||||||
log.error "Couldn't find defeateded opponent: #{err}"
|
log.error "Couldn't find defeateded opponent: #{err}"
|
||||||
defeatedOpponent = null
|
defeatedOpponent = null
|
||||||
victoryContext = {opponent_name: defeatedOpponent?.name ? 'Anoner', url: urlForMatch(victory)} if victory
|
victoryContext = {opponent_name: defeatedOpponent?.name ? 'Anonymous', url: urlForMatch(victory)} if victory
|
||||||
|
|
||||||
onFetchedVictoriousOpponent = (err, victoriousOpponent) ->
|
onFetchedVictoriousOpponent = (err, victoriousOpponent) ->
|
||||||
if err
|
if err
|
||||||
log.error "Couldn't find victorious opponent: #{err}"
|
log.error "Couldn't find victorious opponent: #{err}"
|
||||||
victoriousOpponent = null
|
victoriousOpponent = null
|
||||||
defeatContext = {opponent_name: victoriousOpponent?.name ? 'Anoner', url: urlForMatch(defeat)} if defeat
|
defeatContext = {opponent_name: victoriousOpponent?.name ? 'Anonymous', url: urlForMatch(defeat)} if defeat
|
||||||
|
|
||||||
Level.find({original: session.level.original, created: {$gt: session.submitDate}}).select('created commitMessage version').sort('-created').lean().exec (err, levelVersions) ->
|
Level.find({original: session.level.original, created: {$gt: session.submitDate}}).select('created commitMessage version').sort('-created').lean().exec (err, levelVersions) ->
|
||||||
sendEmail defeatContext, victoryContext, (if levelVersions.length then levelVersions else null)
|
sendEmail defeatContext, victoryContext, (if levelVersions.length then levelVersions else null)
|
||||||
|
@ -706,7 +706,7 @@ sendNextStepsEmail = (user, now, daysAgo) ->
|
||||||
do (err, nextLevel) ->
|
do (err, nextLevel) ->
|
||||||
return log.error "Couldn't find next level for #{user.get('email')}: #{err}" if err
|
return log.error "Couldn't find next level for #{user.get('email')}: #{err}" if err
|
||||||
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
||||||
name = 'hero' if not name or name is 'Anoner'
|
name = 'Hero' if not name or name in ['Anoner', 'Anonymous']
|
||||||
#secretLevel = switch user.get('testGroupNumber') % 8
|
#secretLevel = switch user.get('testGroupNumber') % 8
|
||||||
# when 0, 1, 2, 3 then name: 'Forgetful Gemsmith', slug: 'forgetful-gemsmith'
|
# when 0, 1, 2, 3 then name: 'Forgetful Gemsmith', slug: 'forgetful-gemsmith'
|
||||||
# when 4, 5, 6, 7 then name: 'Signs and Portents', slug: 'signs-and-portents'
|
# when 4, 5, 6, 7 then name: 'Signs and Portents', slug: 'signs-and-portents'
|
||||||
|
|
|
@ -447,3 +447,73 @@ describe 'GET /db/classroom/:handle/members', ->
|
||||||
expect(user.email).toBeDefined()
|
expect(user.email).toBeDefined()
|
||||||
expect(user.passwordHash).toBeUndefined()
|
expect(user.passwordHash).toBeUndefined()
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
describe 'POST /db/classroom/:classroomID/members/:memberID/reset-password', ->
|
||||||
|
it 'changes the password', utils.wrap (done) ->
|
||||||
|
yield utils.clearModels([User, Classroom])
|
||||||
|
teacher = yield utils.initUser()
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
student = yield utils.initUser({ name: "Firstname Lastname" })
|
||||||
|
newPassword = "this is a new password"
|
||||||
|
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student._id] }).save()
|
||||||
|
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||||
|
[res, body] = yield request.postAsync({
|
||||||
|
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||||
|
json: { password: newPassword }
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
changedStudent = yield User.findById(student.id)
|
||||||
|
expect(changedStudent.get('passwordHash')).toEqual(User.hashPassword(newPassword))
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "doesn't change the password if you're not their teacher", utils.wrap (done) ->
|
||||||
|
yield utils.clearModels([User, Classroom])
|
||||||
|
teacher = yield utils.initUser()
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
student = yield utils.initUser({ name: "Firstname Lastname" })
|
||||||
|
student2 = yield utils.initUser({ name: "Firstname Lastname 2" })
|
||||||
|
newPassword = "this is a new password"
|
||||||
|
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student2._id] }).save()
|
||||||
|
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||||
|
[res, body] = yield request.postAsync({
|
||||||
|
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||||
|
json: { password: newPassword }
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
changedStudent = yield User.findById(student.id)
|
||||||
|
expect(changedStudent.get('passwordHash')).toEqual(student.get('passwordHash'))
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "doesn't change the password if their email is verified", utils.wrap (done) ->
|
||||||
|
yield utils.clearModels([User, Classroom])
|
||||||
|
teacher = yield utils.initUser()
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
student = yield utils.initUser({ name: "Firstname Lastname", emailVerified: true })
|
||||||
|
newPassword = "this is a new password"
|
||||||
|
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student._id] }).save()
|
||||||
|
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||||
|
[res, body] = yield request.postAsync({
|
||||||
|
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||||
|
json: { password: newPassword }
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
changedStudent = yield User.findById(student.id)
|
||||||
|
expect(changedStudent.get('passwordHash')).toEqual(student.get('passwordHash'))
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "doesn't let you set a 1-character password", utils.wrap (done) ->
|
||||||
|
yield utils.clearModels([User, Classroom])
|
||||||
|
teacher = yield utils.initUser()
|
||||||
|
yield utils.loginUser(teacher)
|
||||||
|
student = yield utils.initUser({ name: "Firstname Lastname" })
|
||||||
|
newPassword = "e"
|
||||||
|
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student._id] }).save()
|
||||||
|
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||||
|
[res, body] = yield request.postAsync({
|
||||||
|
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||||
|
json: { password: newPassword }
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(422)
|
||||||
|
changedStudent = yield User.findById(student.id)
|
||||||
|
expect(changedStudent.get('passwordHash')).toEqual(student.get('passwordHash'))
|
||||||
|
done()
|
||||||
|
|
Loading…
Reference in a new issue