codecombat/app/views/i18n/I18NEditModelView.coffee
Scott Erickson 7bab895dee Generalize new I18N view system
Previously, when diplomats submit translations, the system
would try to figure out whether it should be a 'patch' or a 'change',
and then would either create a patch for an admin or artisan to
review and accept or reject, or would apply the changes immediately
and they would be live. This was done as a compromise between
getting translations live quickly, but also preventing already-translated
text from getting overwritten without oversight.

But having the client handle this added logical complexity. So
this makes all diplomats submit patches, no matter what. The server
is then in charge of deciding if it should auto-accept the patch or not.
Either way, a patch is created.

There was also much refactoring. This commit includes:

* Update jsondiffpatch so changes within array items are handled correctly
* Refactor posting patches to use the new auto-accepting logic, and out of Patch model
* Refactor POST /db/patch/:handle/status so that it doesn't rely on handlers
* Refactor patch stat handling to ensure auto-accepted patches are counted
* Refactor User.incrementStat to use mongodb update commands, to avoid race conditions
* Refactor Patch tests
2016-09-09 10:59:26 -07:00

193 lines
5.8 KiB
CoffeeScript

RootView = require 'views/core/RootView'
locale = require 'locale/locale'
Patch = require 'models/Patch'
Patches = require 'collections/Patches'
PatchModal = require 'views/editor/PatchModal'
template = require 'templates/i18n/i18n-edit-model-view'
deltasLib = require 'core/deltas'
###
This view is the superclass for all views which Diplomats use to submit translations
for database documents. They all work mostly the same, except they each set their
`@modelClass` which is a patchable Backbone model class, and they use `@wrapRow()`
to dynamically specify which properties are being translated.
###
UNSAVED_CHANGES_MESSAGE = 'You have unsaved changes! Really discard them?'
module.exports = class I18NEditModelView extends RootView
className: 'editor i18n-edit-model-view'
template: template
events:
'input .translation-input': 'onInputChanged'
'change #language-select': 'onLanguageSelectChanged'
'click #patch-submit': 'onSubmitPatch'
'click .open-patch-link': 'onClickOpenPatchLink'
constructor: (options, @modelHandle) ->
super(options)
@model = new @modelClass(_id: @modelHandle)
@supermodel.trackRequest(@model.fetch())
@patches = new Patches()
@listenTo @patches, 'change', -> @renderSelectors('#patches-col')
@patches.comparator = '_id'
@supermodel.trackRequest(@patches.fetchMineFor(@model))
@selectedLanguage = me.get('preferredLanguage', true)
@madeChanges = false
showLoading: ($el) ->
$el ?= @$el.find('.outer-content')
super($el)
onLoaded: ->
super()
@originalModel = @model.clone()
getRenderData: ->
c = super()
c.model = @model
c.selectedLanguage = @selectedLanguage
@translationList = []
if @supermodel.finished() then @buildTranslationList() else []
result.index = index for result, index in @translationList
c.translationList = @translationList
c
afterRender: ->
super()
@ignoreLanguageSelectChanges = true
$select = @$el.find('#language-select').empty()
@addLanguagesToSelect($select, @selectedLanguage)
@$el.find('option[value="en-US"]').remove()
@ignoreLanguageSelectChanges = false
editors = []
@$el.find('tr[data-format="markdown"]').each((index, el) =>
foundEnEl = enEl=$(el).find('.english-value-row div')[0]
if foundEnEl?
englishEditor = ace.edit(foundEnEl)
englishEditor.el = enEl
englishEditor.setReadOnly(true)
editors.push englishEditor
foundToEl = toEl=$(el).find('.to-value-row div')[0]
if foundToEl?
toEditor = ace.edit(foundToEl)
toEditor.el = toEl
toEditor.on 'change', @onEditorChange
editors.push toEditor
)
for editor in editors
session = editor.getSession()
session.setTabSize 2
session.setMode 'ace/mode/markdown'
session.setNewLineMode = 'unix'
session.setUseSoftTabs true
editor.setOptions({ maxLines: Infinity })
onEditorChange: (event, editor) =>
return if @destroyed
index = $(editor.el).data('index')
rowInfo = @translationList[index]
value = editor.getValue()
@onTranslationChanged(rowInfo, value)
wrapRow: (title, key, enValue, toValue, path, format) ->
@translationList.push {
title: title,
key: key,
enValue: enValue,
toValue: toValue or '',
path: path
format: format
}
buildTranslationList: -> [] # overwrite
onInputChanged: (e) ->
index = $(e.target).data('index')
rowInfo = @translationList[index]
value = $(e.target).val()
@onTranslationChanged(rowInfo, value)
onTranslationChanged: (rowInfo, value) ->
#- Navigate down to where the translation will live
base = @model.attributes
for seg in rowInfo.path
base = base[seg]
base = base.i18n
base[@selectedLanguage] ?= {}
base = base[@selectedLanguage]
if rowInfo.key.length > 1
for seg in rowInfo.key[..-2]
base[seg] ?= {}
base = base[seg]
#- Set the data in a non-kosher way
base[rowInfo.key[rowInfo.key.length-1]] = value
@model.saveBackup()
#- Enable patch submit button
@$el.find('#patch-submit').attr('disabled', null)
@madeChanges = true
onLanguageSelectChanged: (e) ->
return if @ignoreLanguageSelectChanges
if @madeChanges
return unless confirm(UNSAVED_CHANGES_MESSAGE)
@selectedLanguage = $(e.target).val()
if @selectedLanguage
me.set('preferredLanguage', @selectedLanguage)
me.patch()
@madeChanges = false
@model.set(@originalModel.clone().attributes)
@render()
onClickOpenPatchLink: (e) ->
patchID = $(e.currentTarget).data('patch-id')
patch = @patches.get(patchID)
modal = new PatchModal(patch, @model)
@openModalView(modal)
onLeaveMessage: ->
if @madeChanges
return UNSAVED_CHANGES_MESSAGE
onSubmitPatch: (e) ->
delta = @originalModel.getDeltaWith(@model)
flattened = deltasLib.flattenDelta(delta)
collection = _.string.underscored @model.constructor.className
patch = new Patch({
delta
target: { collection, 'id': @model.id }
commitMessage: "Diplomat submission for lang #{@selectedLanguage}: #{flattened.length} change(s)."
})
errors = patch.validate()
button = $(e.target)
button.attr('disabled', 'disabled')
return button.text('No changes submitted, did not save patch.') unless delta
return button.text('Failed to Submit Changes') if errors
res = patch.save(null, { url: _.result(@model, 'url') + '/patch' })
return button.text('Failed to Submit Changes') unless res
button.text('Submitting...')
Promise.resolve(res)
.then =>
@madeChanges = false
@patches.add(patch)
@renderSelectors('#patches-col')
button.text('Submit Changes')
.catch =>
button.text('Error Submitting Changes')
@$el.find('#patch-submit').attr('disabled', null)