mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Handling merge conflict.
This commit is contained in:
commit
9893f39ad1
10 changed files with 213 additions and 25 deletions
|
@ -9,12 +9,12 @@ module.exports.formToObject = (el) ->
|
|||
|
||||
obj
|
||||
|
||||
module.exports.applyErrorsToForm = (el, errors) ->
|
||||
module.exports.applyErrorsToForm = (el, errors, warning=false) ->
|
||||
errors = [errors] if not $.isArray(errors)
|
||||
missingErrors = []
|
||||
for error in errors
|
||||
if error.dataPath
|
||||
prop = error.dataPath[1..]
|
||||
console.log prop
|
||||
message = error.message
|
||||
|
||||
else
|
||||
|
@ -23,16 +23,28 @@ module.exports.applyErrorsToForm = (el, errors) ->
|
|||
message = error.message if error.formatted
|
||||
prop = error.property
|
||||
|
||||
input = $("[name='#{prop}']", el)
|
||||
if not input.length
|
||||
missingErrors.push(error)
|
||||
continue
|
||||
formGroup = input.closest('.form-group')
|
||||
formGroup.addClass 'has-error'
|
||||
formGroup.append($("<span class='help-block error-help-block'>#{message}</span>"))
|
||||
return missingErrors
|
||||
setErrorToProperty el, prop, message, warning
|
||||
|
||||
module.exports.setErrorToField = setErrorToField = (el, message, warning=false) ->
|
||||
formGroup = el.closest('.form-group')
|
||||
unless formGroup.length
|
||||
return console.error "#{el} did not contain a form group"
|
||||
|
||||
kind = if warning then 'warning' else 'error'
|
||||
formGroup.addClass "has-#{kind}"
|
||||
formGroup.append $("<span class='help-block #{kind}-help-block'>#{message}</span>")
|
||||
|
||||
module.exports.setErrorToProperty = setErrorToProperty = (el, property, message, warning=false) ->
|
||||
input = $("[name='#{property}']", el)
|
||||
unless input.length
|
||||
return console.error "#{property} not found in #{el}"
|
||||
|
||||
setErrorToField input, message, warning
|
||||
|
||||
module.exports.clearFormAlerts = (el) ->
|
||||
$('.has-error', el).removeClass('has-error')
|
||||
$('.has-warning', el).removeClass('has-warning')
|
||||
$('.alert.alert-danger', el).remove()
|
||||
$('.alert.alert-warning', el).remove()
|
||||
el.find('.help-block.error-help-block').remove()
|
||||
el.find('.help-block.warning-help-block').remove()
|
||||
|
|
|
@ -69,6 +69,13 @@ module.exports = class User extends CocoModel
|
|||
cache[idOrSlug] = user
|
||||
user
|
||||
|
||||
@getUnconflictedName: (name, done) ->
|
||||
$.ajax "/auth/name/#{name}",
|
||||
success: (data) -> done data.name
|
||||
statusCode: 409: (data) ->
|
||||
response = JSON.parse data.responseText
|
||||
done response.name
|
||||
|
||||
getEnabledEmails: ->
|
||||
@migrateEmails()
|
||||
emails = _.clone(@get('emails')) or {}
|
||||
|
|
|
@ -9,7 +9,7 @@ block content
|
|||
|
||||
else
|
||||
#save-button-container
|
||||
button.btn#save-button.disabled.secret(data-i18n="account_settings.autosave") Changes Save Automatically
|
||||
button.btn#save-button.disabled(data-i18n="general.save" disabled="true") No Changes
|
||||
|
||||
ul.nav.nav-pills#settings-tabs
|
||||
li
|
||||
|
@ -30,16 +30,19 @@ block content
|
|||
#general-pane.tab-pane
|
||||
p
|
||||
.form
|
||||
- var name = me.get('name') || '';
|
||||
- var email = me.get('email');
|
||||
- var admin = me.get('permissions').indexOf('admin') != -1;
|
||||
.form-group
|
||||
label.control-label(for="name", data-i18n="general.name") Name
|
||||
input#name.form-control(name="name", type="text", value="#{me.get('name') || ''}")
|
||||
input#name.form-control(name="name", type="text", value="#{name}")
|
||||
.form-group
|
||||
label.control-label(for="email", data-i18n="general.email") Email
|
||||
input#email.form-control(name="email", type="text", value="#{me.get('email')}")
|
||||
input#email.form-control(name="email", type="text", value="#{email}")
|
||||
if !isProduction
|
||||
.form-group.checkbox
|
||||
label(for="admin", data-i18n="account_settings.admin") Admin
|
||||
input#admin(name="admin", type="checkbox", checked=me.get('permissions').indexOf('admin') != -1)
|
||||
input#admin(name="admin", type="checkbox", checked=admin)
|
||||
|
||||
|
||||
#picture-pane.tab-pane
|
||||
|
|
|
@ -34,6 +34,12 @@ block modal-body-content
|
|||
input#password.input-large.form-control(name="password", type="password", value=formValues.password)
|
||||
|
||||
if mode === 'signup'
|
||||
.form-group
|
||||
label.control-label(for="name", data-i18n="general.name") Name
|
||||
if me.get('name')
|
||||
input#name.input-large.form-control(name="name", type="text", value="#{me.get('name')}")
|
||||
else
|
||||
input#name.input-large.form-control(name="name", type="text", value="", placeholder="Anoner")
|
||||
.form-group.checkbox
|
||||
label.control-label(for="subscribe")
|
||||
input#subscribe(name="subscribe", type="checkbox", checked='checked')
|
||||
|
|
|
@ -11,17 +11,73 @@ JobProfileView = require './job_profile_view'
|
|||
module.exports = class SettingsView extends View
|
||||
id: 'account-settings-view'
|
||||
template: template
|
||||
changedFields: [] # DOM input fields
|
||||
|
||||
events:
|
||||
'click #save-button': 'save'
|
||||
'change #settings-panes input': 'save'
|
||||
'change #settings-panes input:checkbox': (e) -> @trigger 'checkboxToggled', e
|
||||
'keyup #settings-panes input:text, #settings-panes input:password': (e) -> @trigger 'inputChanged', e
|
||||
'keyup #name': 'onNameChange'
|
||||
'click #toggle-all-button': 'toggleEmailSubscriptions'
|
||||
'keypress #settings-panes': 'onKeyPress'
|
||||
|
||||
constructor: (options) ->
|
||||
@save = _.debounce(@save, 200)
|
||||
@onNameChange = _.debounce @checkNameExists, 500
|
||||
super options
|
||||
return unless me
|
||||
|
||||
@listenTo(me, 'invalid', (errors) -> forms.applyErrorsToForm(@$el, me.validationError))
|
||||
@on 'checkboxToggled', @onToggle
|
||||
@on 'checkboxToggled', @onInputChanged
|
||||
@on 'inputChanged', @onInputChanged
|
||||
@on 'enterPressed', @onEnter
|
||||
|
||||
onInputChanged: (e) ->
|
||||
return @enableSaveButton() unless e?.currentTarget
|
||||
that = e.currentTarget
|
||||
$that = $(that)
|
||||
savedValue = $that.data 'saved-value'
|
||||
currentValue = $that.val()
|
||||
if savedValue isnt currentValue
|
||||
@changedFields.push that unless that in @changedFields
|
||||
@enableSaveButton()
|
||||
else
|
||||
_.pull @changedFields, that
|
||||
@disableSaveButton() if _.isEmpty @changedFields
|
||||
|
||||
onToggle: (e) ->
|
||||
$that = $(e.currentTarget)
|
||||
$that.val $that[0].checked
|
||||
|
||||
onEnter: ->
|
||||
@save()
|
||||
|
||||
onKeyPress: (e) ->
|
||||
@trigger 'enterPressed', e if e.which is 13
|
||||
|
||||
enableSaveButton: ->
|
||||
$('#save-button', @$el).removeClass 'disabled'
|
||||
$('#save-button', @$el).removeClass 'btn-danger'
|
||||
$('#save-button', @$el).removeAttr 'disabled'
|
||||
$('#save-button', @$el).text 'Save'
|
||||
|
||||
disableSaveButton: ->
|
||||
$('#save-button', @$el).addClass 'disabled'
|
||||
$('#save-button', @$el).removeClass 'btn-danger'
|
||||
$('#save-button', @$el).attr 'disabled', "true"
|
||||
$('#save-button', @$el).text 'No Changes'
|
||||
|
||||
checkNameExists: =>
|
||||
name = $('#name', @$el).val()
|
||||
return if name is me.get 'name'
|
||||
User.getUnconflictedName name, (newName) =>
|
||||
forms.clearFormAlerts(@$el)
|
||||
if name is newName
|
||||
@suggestedName = undefined
|
||||
else
|
||||
@suggestedName = newName
|
||||
forms.setErrorToProperty @$el, 'name', "That name is taken! How about #{newName}?", true
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
|
@ -50,6 +106,7 @@ module.exports = class SettingsView extends View
|
|||
super()
|
||||
if me.get('anonymous')
|
||||
@openModalView new AuthModalView()
|
||||
@updateSavedValues()
|
||||
|
||||
chooseTab: (category) ->
|
||||
id = "##{category}-pane"
|
||||
|
@ -98,7 +155,7 @@ module.exports = class SettingsView extends View
|
|||
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
|
||||
|
||||
onPictureChanged: (e) =>
|
||||
@trigger 'change'
|
||||
@trigger 'inputChanged', e
|
||||
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
|
||||
|
||||
save: (e) ->
|
||||
|
@ -122,7 +179,9 @@ module.exports = class SettingsView extends View
|
|||
errors = JSON.parse(res.responseText)
|
||||
forms.applyErrorsToForm(@$el, errors)
|
||||
save.text($.i18n.t('account_settings.error_saving', defaultValue: 'Error Saving')).removeClass('btn-success').addClass('btn-danger', 500)
|
||||
res.success (model, response, options) ->
|
||||
res.success (model, response, options) =>
|
||||
@changedFields = []
|
||||
@updateSavedValues()
|
||||
save.text($.i18n.t('account_settings.saved', defaultValue: 'Changes Saved')).removeClass('btn-success', 500)
|
||||
|
||||
grabData: ->
|
||||
|
@ -142,6 +201,7 @@ module.exports = class SettingsView extends View
|
|||
me.set('password', password1)
|
||||
|
||||
grabOtherData: ->
|
||||
$('#name', @$el).val @suggestedName if @suggestedName
|
||||
me.set 'name', $('#name', @$el).val()
|
||||
me.set 'email', $('#email', @$el).val()
|
||||
for emailName, enabled of @getSubscriptions()
|
||||
|
@ -162,3 +222,9 @@ module.exports = class SettingsView extends View
|
|||
if updated
|
||||
jobProfile.updated = (new Date()).toISOString()
|
||||
me.set 'jobProfile', jobProfile
|
||||
|
||||
updateSavedValues: ->
|
||||
$('#settings-panes input:text').each ->
|
||||
$(@).data 'saved-value', $(@).val()
|
||||
$('#settings-panes input:checkbox').each ->
|
||||
$(@).data 'saved-value', JSON.stringify $(@)[0].checked
|
||||
|
|
|
@ -15,11 +15,16 @@ module.exports = class AuthModalView extends View
|
|||
'click #switch-to-signup-button': 'onSignupInstead'
|
||||
'click #signup-confirm-age': 'checkAge'
|
||||
'submit': 'onSubmitForm' # handles both submit buttons
|
||||
'keyup #name': 'onNameChange'
|
||||
|
||||
subscriptions:
|
||||
'server-error': 'onServerError'
|
||||
'logging-in-with-facebook': 'onLoggingInWithFacebook'
|
||||
|
||||
constructor: (options) ->
|
||||
@onNameChange = _.debounce @checkNameExists, 500
|
||||
super options
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.showRequiredError = @options.showRequiredError
|
||||
|
@ -31,6 +36,7 @@ module.exports = class AuthModalView extends View
|
|||
c.mode = @mode
|
||||
c.formValues = @previousFormInputs or {}
|
||||
c.onEmployersPage = Backbone.history.fragment is "employers"
|
||||
c.me = me
|
||||
c
|
||||
|
||||
afterInsert: ->
|
||||
|
@ -64,6 +70,8 @@ module.exports = class AuthModalView extends View
|
|||
userObject = forms.formToObject @$el
|
||||
delete userObject.subscribe
|
||||
delete userObject['confirm-age']
|
||||
delete userObject.name if userObject.name is ''
|
||||
userObject.name = @suggestedName if @suggestedName
|
||||
for key, val of me.attributes when key in ['preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1', 'name', 'music', 'volume', 'emails']
|
||||
userObject[key] ?= val
|
||||
subscribe = @$el.find('#signup-subscribe').prop('checked')
|
||||
|
@ -82,3 +90,14 @@ module.exports = class AuthModalView extends View
|
|||
|
||||
onServerError: (e) -> # TODO: work error handling into a separate forms system
|
||||
@disableModalInProgress(@$el)
|
||||
|
||||
checkNameExists: =>
|
||||
name = $('#name', @$el).val()
|
||||
return forms.clearFormAlerts(@$el) if name is ''
|
||||
User.getUnconflictedName name, (newName) =>
|
||||
forms.clearFormAlerts(@$el)
|
||||
if name is newName
|
||||
@suggestedName = undefined
|
||||
else
|
||||
@suggestedName = newName
|
||||
forms.setErrorToProperty @$el, 'name', "That name is taken! How about #{newName}?", true
|
||||
|
|
|
@ -4,20 +4,22 @@ WizardSprite = require 'lib/surface/WizardSprite'
|
|||
ThangType = require 'models/ThangType'
|
||||
{me} = require 'lib/auth'
|
||||
forms = require 'lib/forms'
|
||||
User = require 'models/User'
|
||||
|
||||
module.exports = class WizardSettingsModal extends View
|
||||
id: 'wizard-settings-modal'
|
||||
template: template
|
||||
closesOnClickOutside: false
|
||||
|
||||
events:
|
||||
'keyup #wizard-settings-name': -> @trigger 'nameChanged'
|
||||
'click #wizard-settings-done': 'onWizardSettingsDone'
|
||||
|
||||
constructor: (options) ->
|
||||
@onNameChange = _.debounce(@checkNameExists, 500)
|
||||
@on 'nameChanged', @onNameChange
|
||||
super options
|
||||
|
||||
events:
|
||||
'keyup #wizard-settings-name': 'onNameChange'
|
||||
'click #wizard-settings-done': 'onWizardSettingsDone'
|
||||
|
||||
afterRender: ->
|
||||
WizardSettingsView = require 'views/account/wizard_settings_view'
|
||||
view = new WizardSettingsView()
|
||||
|
@ -27,10 +29,10 @@ module.exports = class WizardSettingsModal extends View
|
|||
checkNameExists: =>
|
||||
forms.clearFormAlerts(@$el)
|
||||
name = $('#wizard-settings-name').val()
|
||||
success = (id) =>
|
||||
User.getUnconflictedName name, (newName) =>
|
||||
forms.clearFormAlerts(@$el)
|
||||
forms.applyErrorsToForm(@$el, {property: 'name', message: 'is already taken'}) if id and id isnt me.id
|
||||
$.ajax("/db/user/#{name}/nameToID", {success: success})
|
||||
if name isnt newName
|
||||
forms.setErrorToProperty @$el, 'name', 'This name is already taken so you won\'t be able to keep it.', true
|
||||
|
||||
onWizardSettingsDone: ->
|
||||
me.set('name', $('#wizard-settings-name').val())
|
||||
|
|
|
@ -157,13 +157,30 @@ module.exports.setup = (app) ->
|
|||
res.send msg + '<p><a href="/account/settings">Account settings</a></p>'
|
||||
res.end()
|
||||
|
||||
app.get '/auth/name/*', (req, res) ->
|
||||
parts = req.path.split '/'
|
||||
console.log parts
|
||||
originalName = parts[3]
|
||||
|
||||
return errors.badInput res, 'No name provided.' unless originalName and originalName isnt ''
|
||||
return errors.notFound res if parts.length isnt 4
|
||||
|
||||
User.unconflictName originalName, (err, name) ->
|
||||
return errors.serverError res, err if err
|
||||
response = name: name
|
||||
if originalName is name
|
||||
res.send 200, response
|
||||
else
|
||||
errors.conflict res, response
|
||||
|
||||
|
||||
module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) ->
|
||||
user.save((err) ->
|
||||
return errors.serverError res, err if err?
|
||||
|
||||
req.logIn(user, (err) ->
|
||||
return errors.serverError res, err if err?
|
||||
return res.send user if send
|
||||
return res.send(user) and res.end() if send
|
||||
next() if next
|
||||
)
|
||||
)
|
||||
|
|
32
test/demo/views/modals/WizardSettingsModal.demo.coffee
Normal file
32
test/demo/views/modals/WizardSettingsModal.demo.coffee
Normal file
File diff suppressed because one or more lines are too long
|
@ -152,3 +152,27 @@ describe '/auth/unsubscribe', ->
|
|||
expect(user.get('emails').recruitNotes.enabled).toBe(false)
|
||||
expect(user.isEmailSubscriptionEnabled('generalNews')).toBeTruthy()
|
||||
done()
|
||||
|
||||
describe '/auth/name', ->
|
||||
url = '/auth/name'
|
||||
|
||||
it 'must provide a name to check with', (done) ->
|
||||
request.get {url: getURL(url + '/'), json: {}}, (err, response) ->
|
||||
expect(err).toBeNull()
|
||||
expect(response.statusCode).toBe 422
|
||||
done()
|
||||
|
||||
it 'can GET a non-conflicting name', (done) ->
|
||||
request.get {url: getURL(url + '/Gandalf'), json: {}}, (err, response) ->
|
||||
expect(err).toBeNull()
|
||||
expect(response.statusCode).toBe 200
|
||||
expect(response.body.name).toBe 'Gandalf'
|
||||
done()
|
||||
|
||||
it 'can GET a new name in case of conflict', (done) ->
|
||||
request.get {url: getURL(url + '/joe'), json: {}}, (err, response) ->
|
||||
expect(err).toBeNull()
|
||||
expect(response.statusCode).toBe 409
|
||||
expect(response.body.name).not.toBe 'joe'
|
||||
expect(response.body.name.length).toBe 4 # 'joe' and a random number
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue