mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-24 21:13:35 -04:00
Added data merge conflict handling.
This commit is contained in:
parent
177dd2c8cd
commit
34bf484bf2
14 changed files with 346 additions and 157 deletions
app
lib
models
styles
templates/editor
views
server/patches
|
@ -1,75 +1,158 @@
|
|||
# path: an array of indexes to navigate into a JSON object
|
||||
# left:
|
||||
###
|
||||
Good-to-knows:
|
||||
dataPath: an array of keys that walks you up a JSON object that's being patched
|
||||
ex: ['scripts', 0, 'description']
|
||||
deltaPath: an array of keys that walks you up a JSON Diff Patch object.
|
||||
ex: ['scripts', '_0', 'description']
|
||||
###
|
||||
|
||||
module.exports.expandDelta = (delta, left, schema) ->
|
||||
flattenedDeltas = flattenDelta(delta)
|
||||
(expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas)
|
||||
|
||||
|
||||
module.exports.interpretDelta = (delta, path, left, schema) ->
|
||||
# takes a single delta and converts into an object that can be
|
||||
flattenDelta = (delta, dataPath=null, deltaPath=null) ->
|
||||
# takes a single jsondiffpatch delta and returns an array of objects with
|
||||
return [] unless delta
|
||||
dataPath ?= []
|
||||
deltaPath ?= []
|
||||
return [{dataPath:dataPath, deltaPath: deltaPath, o:delta}] if _.isArray delta
|
||||
|
||||
results = []
|
||||
affectingArray = delta._t is 'a'
|
||||
for deltaIndex, childDelta of delta
|
||||
continue if deltaIndex is '_t'
|
||||
dataIndex = if affectingArray then parseInt(deltaIndex.replace('_', '')) else deltaIndex
|
||||
results = results.concat flattenDelta(
|
||||
childDelta, dataPath.concat([dataIndex]), deltaPath.concat([deltaIndex]))
|
||||
results
|
||||
|
||||
|
||||
expandFlattenedDelta = (delta, left, schema) ->
|
||||
# takes a single flattened delta and converts into an object that can be
|
||||
# easily formatted into something human readable.
|
||||
|
||||
delta.action = '???'
|
||||
o = delta.o # the raw jsondiffpatch delta
|
||||
|
||||
betterDelta = { action:'???', delta: delta }
|
||||
if _.isArray(o) and o.length is 1
|
||||
delta.action = 'added'
|
||||
delta.newValue = o[0]
|
||||
|
||||
if _.isArray(delta) and delta.length is 1
|
||||
betterDelta.action = 'added'
|
||||
betterDelta.newValue = delta[0]
|
||||
if _.isArray(o) and o.length is 2
|
||||
delta.action = 'modified'
|
||||
delta.oldValue = o[0]
|
||||
delta.newValue = o[1]
|
||||
|
||||
if _.isArray(delta) and delta.length is 2
|
||||
betterDelta.action = 'modified'
|
||||
betterDelta.oldValue = delta[0]
|
||||
betterDelta.newValue = delta[1]
|
||||
if _.isArray(o) and o.length is 3 and o[1] is 0 and o[2] is 0
|
||||
delta.action = 'deleted'
|
||||
delta.oldValue = o[0]
|
||||
|
||||
if _.isArray(delta) and delta.length is 3 and delta[1] is 0 and delta[2] is 0
|
||||
betterDelta.action = 'deleted'
|
||||
betterDelta.oldValue = delta[0]
|
||||
if _.isPlainObject(o) and o._t is 'a'
|
||||
delta.action = 'modified-array'
|
||||
|
||||
if _.isPlainObject(delta) and delta._t is 'a'
|
||||
betterDelta.action = 'modified-array'
|
||||
if _.isPlainObject(o) and o._t isnt 'a'
|
||||
delta.action = 'modified-object'
|
||||
|
||||
if _.isPlainObject(delta) and delta._t isnt 'a'
|
||||
betterDelta.action = 'modified-object'
|
||||
if _.isArray(o) and o.length is 3 and o[1] is 0 and o[2] is 3
|
||||
delta.action = 'moved-index'
|
||||
delta.destinationIndex = o[1]
|
||||
delta.originalIndex = delta.dataPath[delta.dataPath.length-1]
|
||||
|
||||
if _.isArray(delta) and delta.length is 3 and delta[1] is 0 and delta[2] is 3
|
||||
betterDelta.action = 'moved-index'
|
||||
betterDelta.destinationIndex = delta[1]
|
||||
if _.isArray(o) and o.length is 3 and o[1] is 0 and o[2] is 2
|
||||
delta.action = 'text-diff'
|
||||
delta.unidiff = o[0]
|
||||
|
||||
if _.isArray(delta) and delta.length is 3 and delta[1] is 0 and delta[2] is 2
|
||||
betterDelta.action = 'text-diff'
|
||||
betterDelta.unidiff = delta[0]
|
||||
|
||||
betterPath = []
|
||||
humanPath = []
|
||||
parentLeft = left
|
||||
parentSchema = schema
|
||||
for key, i in path
|
||||
# TODO: A smarter way of getting child schemas
|
||||
for key, i in delta.dataPath
|
||||
# TODO: A more comprehensive way of getting child schemas
|
||||
childSchema = parentSchema?.items or parentSchema?.properties?[key] or {}
|
||||
childLeft = parentLeft?[key]
|
||||
betterKey = null
|
||||
childData = if i is path.length-1 and betterDelta.action is 'added' then delta[0] else childLeft
|
||||
betterKey ?= childData.name or childData.id if childData
|
||||
betterKey ?= "#{childSchema.title} ##{key+1}" if childSchema.title and _.isNumber(key)
|
||||
betterKey ?= "#{childSchema.title}" if childSchema.title
|
||||
betterKey ?= _.string.titleize key
|
||||
betterPath.push betterKey
|
||||
humanKey = null
|
||||
childData = if i is delta.dataPath.length-1 and delta.action is 'added' then o[0] else childLeft
|
||||
humanKey ?= childData.name or childData.id if childData
|
||||
humanKey ?= "#{childSchema.title} ##{key+1}" if childSchema.title and _.isNumber(key)
|
||||
humanKey ?= "#{childSchema.title}" if childSchema.title
|
||||
humanKey ?= _.string.titleize key
|
||||
humanPath.push humanKey
|
||||
parentLeft = childLeft
|
||||
parentSchema = childSchema
|
||||
|
||||
betterDelta.path = betterPath.join(' :: ')
|
||||
betterDelta.schema = childSchema
|
||||
betterDelta.left = childLeft
|
||||
betterDelta.right = jsondiffpatch.patch childLeft, delta unless betterDelta.action is 'moved-index'
|
||||
delta.humanPath = humanPath.join(' :: ')
|
||||
delta.schema = childSchema
|
||||
delta.left = childLeft
|
||||
delta.right = jsondiffpatch.patch childLeft, delta.o unless delta.action is 'moved-index'
|
||||
|
||||
betterDelta
|
||||
delta
|
||||
|
||||
module.exports.flattenDelta = flattenDelta = (delta, path=null) ->
|
||||
# takes a single delta and returns an array of deltas
|
||||
return [] unless delta
|
||||
module.exports.makeJSONDiffer = ->
|
||||
hasher = (obj) -> obj.name || obj.id || obj._id || JSON.stringify(_.keys(obj))
|
||||
jsondiffpatch.create({objectHash:hasher})
|
||||
|
||||
module.exports.getConflicts = (headDeltas, pendingDeltas) ->
|
||||
# headDeltas and pendingDeltas should be lists of deltas returned by interpretDelta
|
||||
# Returns a list of conflict objects with properties:
|
||||
# headDelta
|
||||
# pendingDelta
|
||||
# The deltas that have conflicts also have conflict properties pointing to one another.
|
||||
|
||||
path ?= []
|
||||
headPathMap = groupDeltasByAffectingPaths(headDeltas)
|
||||
pendingPathMap = groupDeltasByAffectingPaths(pendingDeltas)
|
||||
paths = _.keys(headPathMap).concat(_.keys(pendingPathMap))
|
||||
|
||||
return [{path:path, delta:delta}] if _.isArray delta
|
||||
# Here's my thinking:
|
||||
# A) Conflicts happen when one delta path is a substring of another delta path
|
||||
# B) A delta from one self-consistent group cannot conflict with another
|
||||
# So, sort the paths, which will naturally make conflicts adjacent,
|
||||
# and if one is identified, one path is from the headDeltas, the other is from pendingDeltas
|
||||
# This is all to avoid an O(nm) brute force search.
|
||||
|
||||
results = []
|
||||
affectingArray = delta._t is 'a'
|
||||
for index, childDelta of delta
|
||||
continue if index is '_t'
|
||||
index = parseInt(index.replace('_', '')) if affectingArray
|
||||
results = results.concat flattenDelta(childDelta, path.concat([index]))
|
||||
results
|
||||
conflicts = []
|
||||
paths.sort()
|
||||
for path, i in paths
|
||||
continue if i + 1 is paths.length
|
||||
nextPath = paths[i+1]
|
||||
if nextPath.startsWith path
|
||||
headDelta = (headPathMap[path] or headPathMap[nextPath])[0].delta
|
||||
pendingDelta = (pendingPathMap[path] or pendingPathMap[nextPath])[0].delta
|
||||
conflicts.push({headDelta:headDelta, pendingDelta:pendingDelta})
|
||||
pendingDelta.conflict = headDelta
|
||||
headDelta.conflict = pendingDelta
|
||||
|
||||
return conflicts if conflicts.length
|
||||
|
||||
groupDeltasByAffectingPaths = (deltas) ->
|
||||
metaDeltas = []
|
||||
for delta in deltas
|
||||
conflictPaths = []
|
||||
if delta.action is 'moved-index'
|
||||
# every other action affects just the data path, but moved indexes affect a swath
|
||||
indices = [delta.originalIndex, delta.destinationIndex]
|
||||
indices.sort()
|
||||
for index in _.range(indices[0], indices[1]+1)
|
||||
conflictPaths.push delta.dataPath.slice(0, delta.dataPath.length-1).concat(index)
|
||||
else
|
||||
conflictPaths.push delta.dataPath
|
||||
for path in conflictPaths
|
||||
metaDeltas.push {
|
||||
delta: delta
|
||||
path: (item.toString() for item in path).join('/')
|
||||
}
|
||||
_.groupBy metaDeltas, 'path'
|
||||
|
||||
module.exports.pruneConflictsFromDelta = (delta, conflicts) ->
|
||||
# the jsondiffpatch delta mustn't include any dangling nodes,
|
||||
# or else things will get removed which shouldn't be, or errors will occur
|
||||
for conflict in conflicts
|
||||
prunePath delta, conflict.pendingDelta.deltaPath
|
||||
if _.isEmpty delta then undefined else delta
|
||||
|
||||
prunePath = (delta, path) ->
|
||||
if path.length is 1
|
||||
delete delta[path]
|
||||
else
|
||||
prunePath delta[path[0]], path.slice(1)
|
||||
keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t')
|
||||
delete delta[path[0]] if keys.length is 0
|
|
@ -31,6 +31,12 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
type: ->
|
||||
@constructor.className
|
||||
|
||||
clone: (withChanges=true) ->
|
||||
# Backbone does not support nested documents
|
||||
clone = super()
|
||||
clone.set($.extend(true, {}, if withChanges then @attributes else @_revertAttributes))
|
||||
clone
|
||||
|
||||
onLoaded: ->
|
||||
@loaded = true
|
||||
|
@ -223,14 +229,16 @@ class CocoModel extends Backbone.Model
|
|||
return false
|
||||
|
||||
getDelta: ->
|
||||
jsd = jsondiffpatch.create({
|
||||
objectHash: (obj) -> obj.name || obj.id || obj._id || JSON.stringify(_.keys(obj))
|
||||
})
|
||||
jsd.diff @_revertAttributes, @attributes
|
||||
differ = deltasLib.makeJSONDiffer()
|
||||
differ.diff @_revertAttributes, @attributes
|
||||
|
||||
applyDelta: (delta) ->
|
||||
newAttributes = $.extend(true, {}, @attributes)
|
||||
jsondiffpatch.patch newAttributes, delta
|
||||
@set newAttributes
|
||||
|
||||
getExpandedDelta: ->
|
||||
delta = @getDelta()
|
||||
deltas = deltasLib.flattenDelta(delta)
|
||||
(deltasLib.interpretDelta(d.delta, d.path, @_revertAttributes, @schema().attributes) for d in deltas)
|
||||
deltasLib.expandDelta(delta, @_revertAttributes, @schema().attributes)
|
||||
|
||||
module.exports = CocoModel
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.delta-list-view
|
||||
.delta-view
|
||||
.panel-heading
|
||||
font-size: 13px
|
||||
padding: 4px
|
||||
|
@ -7,37 +7,37 @@
|
|||
|
||||
.delta-added
|
||||
border-color: green
|
||||
strong
|
||||
color: green
|
||||
.panel-heading
|
||||
> .panel-heading
|
||||
background-color: lighten(green, 70%)
|
||||
strong
|
||||
color: green
|
||||
|
||||
.delta-modified
|
||||
border-color: darkgoldenrod
|
||||
strong
|
||||
color: darkgoldenrod
|
||||
.panel-heading
|
||||
> .panel-heading
|
||||
background-color: lighten(darkgoldenrod, 40%)
|
||||
strong
|
||||
color: darkgoldenrod
|
||||
|
||||
.delta-text-diff
|
||||
border-color: blue
|
||||
strong
|
||||
color: blue
|
||||
.panel-heading
|
||||
> .panel-heading
|
||||
background-color: lighten(blue, 45%)
|
||||
strong
|
||||
color: blue
|
||||
table
|
||||
width: 100%
|
||||
|
||||
.delta-deleted
|
||||
border-color: red
|
||||
strong
|
||||
color: red
|
||||
.panel-heading
|
||||
> .panel-heading
|
||||
background-color: lighten(red, 42%)
|
||||
strong
|
||||
color: red
|
||||
|
||||
.delta-moved-index
|
||||
border-color: darkslategray
|
||||
strong
|
||||
color: darkslategray
|
||||
.panel-heading
|
||||
background-color: lighten(darkslategray, 60%)
|
||||
> .panel-heading
|
||||
background-color: lighten(darkslategray, 60%)
|
||||
strong
|
||||
color: darkslategray
|
||||
|
|
3
app/styles/editor/patch.sass
Normal file
3
app/styles/editor/patch.sass
Normal file
|
@ -0,0 +1,3 @@
|
|||
#patch-modal
|
||||
.modal-body
|
||||
padding: 10px
|
|
@ -33,7 +33,7 @@
|
|||
font-size: 0.9em
|
||||
font-style: italic
|
||||
|
||||
.delta-list-view
|
||||
.delta-view
|
||||
overflow-y: auto
|
||||
padding: 10px
|
||||
border: 1px solid black
|
||||
|
|
|
@ -1,36 +1,46 @@
|
|||
- var i = 0
|
||||
|
||||
mixin deltaPanel(delta, conflict)
|
||||
- delta.index = i++
|
||||
.delta.panel.panel-default(class='delta-'+delta.action, data-index=i)
|
||||
.panel-heading
|
||||
if delta.action === 'added'
|
||||
strong(data-i18n="delta.added") Added
|
||||
if delta.action === 'modified'
|
||||
strong(data-i18n="delta.modified") Modified
|
||||
if delta.action === 'deleted'
|
||||
strong(data-i18n="delta.deleted") Deleted
|
||||
if delta.action === 'moved-index'
|
||||
strong(data-i18n="delta.modified_array") Moved Index
|
||||
if delta.action === 'text-diff'
|
||||
strong(data-i18n="delta.text_diff") Text Diff
|
||||
span
|
||||
a(data-toggle="collapse" data-parent="#delta-accordion"+(counter) href="#collapse-"+(i+counter))
|
||||
span= delta.humanPath
|
||||
|
||||
.panel-collapse.collapse(id="collapse-"+(i+counter))
|
||||
.panel-body.row(class=conflict ? "conflict-details" : "details")
|
||||
if delta.action === 'added'
|
||||
.new-value.col-md-12= delta.right
|
||||
if delta.action === 'modified'
|
||||
.old-value.col-md-6= delta.left
|
||||
.new-value.col-md-6= delta.right
|
||||
if delta.action === 'deleted'
|
||||
.col-md-12
|
||||
div.old-value= delta.left
|
||||
if delta.action === 'text-diff'
|
||||
.col-md-12
|
||||
div.text-diff
|
||||
if delta.action === 'moved-index'
|
||||
.col-md-12
|
||||
span Moved array value #{JSON.stringify(delta.left)} to index #{delta.destinationIndex}
|
||||
|
||||
if delta.conflict && !conflict
|
||||
.panel-body
|
||||
strong MERGE CONFLICT WITH
|
||||
+deltaPanel(delta.conflict, true)
|
||||
|
||||
.panel-group(id='delta-accordion-'+(counter))
|
||||
for delta in deltas
|
||||
.delta.panel.panel-default(class='delta-'+delta.action)
|
||||
.panel-heading
|
||||
if delta.action === 'added'
|
||||
strong(data-i18n="delta.added") Added
|
||||
if delta.action === 'modified'
|
||||
strong(data-i18n="delta.modified") Modified
|
||||
if delta.action === 'deleted'
|
||||
strong(data-i18n="delta.deleted") Deleted
|
||||
if delta.action === 'moved-index'
|
||||
strong(data-i18n="delta.modified_array") Moved Index
|
||||
if delta.action === 'text-diff'
|
||||
strong(data-i18n="delta.text_diff") Text Diff
|
||||
span
|
||||
a(data-toggle="collapse" data-parent="#delta-accordion"+(counter) href="#collapse-"+(i+counter))
|
||||
span= delta.path
|
||||
|
||||
.panel-collapse.collapse(id="collapse-"+(i+counter))
|
||||
.panel-body.row
|
||||
if delta.action === 'added'
|
||||
.new-value.col-md-12= delta.right
|
||||
if delta.action === 'modified'
|
||||
.old-value.col-md-6= delta.left
|
||||
.new-value.col-md-6= delta.right
|
||||
if delta.action === 'deleted'
|
||||
.col-md-12
|
||||
div.old-value= delta.left
|
||||
if delta.action === 'text-diff'
|
||||
.col-md-12
|
||||
div.text-diff
|
||||
if delta.action === 'moved-index'
|
||||
.col-md-12
|
||||
span Moved array value #{JSON.stringify(delta.left)} to index #{delta.destinationIndex}
|
||||
- i += 1
|
||||
+deltaPanel(delta)
|
||||
|
20
app/templates/editor/patch_modal.jade
Normal file
20
app/templates/editor/patch_modal.jade
Normal file
|
@ -0,0 +1,20 @@
|
|||
extends /templates/modal/modal_base
|
||||
|
||||
block modal-header-content
|
||||
.modal-header-content
|
||||
h3 Patch
|
||||
|
||||
block modal-body-content
|
||||
.modal-body
|
||||
.changes-stub
|
||||
|
||||
|
||||
block modal-footer
|
||||
.modal-footer
|
||||
button(data-dismiss="modal", data-i18n="common.cancel").btn Cancel
|
||||
if canReject
|
||||
button.btn.btn-danger Reject
|
||||
if canWithdraw
|
||||
button.btn.btn-danger Withdraw
|
||||
if canAccept
|
||||
button.btn.btn-primary Accept
|
|
@ -20,8 +20,11 @@ else
|
|||
th Submitter
|
||||
th Submitted
|
||||
th Commit Message
|
||||
th Review
|
||||
for patch in patches
|
||||
tr
|
||||
td= patch.userName
|
||||
td= moment(patch.get('created')).format('llll')
|
||||
td= patch.get('commitMessage')
|
||||
td
|
||||
span.glyphicon.glyphicon-wrench(data-patch-id=patch.id).patch-icon
|
||||
|
|
|
@ -19,7 +19,7 @@ block content
|
|||
h3 Edit Thang Type: "#{thangType.attributes.name}"
|
||||
|
||||
ul.nav.nav-tabs
|
||||
li.active
|
||||
li
|
||||
a(href="#editor-thang-main-tab-view", data-toggle="tab") Main
|
||||
li
|
||||
a(href="#editor-thang-components-tab-view", data-toggle="tab") Components
|
||||
|
@ -27,13 +27,13 @@ block content
|
|||
a(href="#editor-thang-spritesheets-view", data-toggle="tab") Spritesheets
|
||||
li
|
||||
a(href="#editor-thang-colors-tab-view", data-toggle="tab")#color-tab Colors
|
||||
li
|
||||
li.active
|
||||
a(href="#editor-thang-patches-view", data-toggle="tab")#patches-tab Patches
|
||||
|
||||
div.tab-content
|
||||
div.tab-pane#editor-thang-colors-tab-view
|
||||
|
||||
div.tab-pane.active#editor-thang-main-tab-view
|
||||
div.tab-pane#editor-thang-main-tab-view
|
||||
|
||||
div.main-area.well
|
||||
div.file-controls
|
||||
|
@ -86,7 +86,7 @@ block content
|
|||
|
||||
div#spritesheets
|
||||
|
||||
div.tab-pane#editor-thang-patches-view
|
||||
div.tab-pane#editor-thang-patches-view.active
|
||||
|
||||
div.patches-view
|
||||
|
||||
|
|
|
@ -1,57 +1,70 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
template = require 'templates/editor/delta'
|
||||
deltaLib = require 'lib/deltas'
|
||||
deltasLib = require 'lib/deltas'
|
||||
|
||||
module.exports = class DeltaListView extends CocoView
|
||||
TEXTDIFF_OPTIONS =
|
||||
baseTextName: "Old"
|
||||
newTextName: "New"
|
||||
contextSize: 5
|
||||
viewType: 1
|
||||
|
||||
module.exports = class DeltaView extends CocoView
|
||||
@deltaCounter: 0
|
||||
className: "delta-list-view"
|
||||
className: "delta-view"
|
||||
template: template
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@model = options.model
|
||||
@headModel = options.headModel
|
||||
@expandedDeltas = @model.getExpandedDelta()
|
||||
if @headModel
|
||||
@headDeltas = @headModel.getExpandedDelta()
|
||||
@conflicts = deltasLib.getConflicts(@headDeltas, @expandedDeltas)
|
||||
DeltaView.deltaCounter += @expandedDeltas.length
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.deltas = @processedDeltas = @model.getExpandedDelta()
|
||||
c.counter = DeltaListView.deltaCounter
|
||||
DeltaListView.deltaCounter += c.deltas.length
|
||||
c.deltas = @expandedDeltas
|
||||
c.counter = DeltaView.deltaCounter
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
deltas = @$el.find('.delta')
|
||||
deltas = @$el.find('.details')
|
||||
for delta, i in deltas
|
||||
deltaEl = $(delta)
|
||||
deltaData = @processedDeltas[i]
|
||||
if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value')
|
||||
options =
|
||||
data: deltaData.left
|
||||
schema: deltaData.schema
|
||||
readOnly: true
|
||||
treema = TreemaNode.make(leftEl, options)
|
||||
treema.build()
|
||||
deltaData = @expandedDeltas[i]
|
||||
@expandDetails(deltaEl, deltaData)
|
||||
|
||||
conflictDeltas = @$el.find('.conflict-details')
|
||||
conflicts = (delta.conflict for delta in @expandedDeltas when delta.conflict)
|
||||
for delta, i in conflictDeltas
|
||||
deltaEl = $(delta)
|
||||
deltaData = conflicts[i]
|
||||
@expandDetails(deltaEl, deltaData)
|
||||
|
||||
expandDetails: (deltaEl, deltaData) ->
|
||||
treemaOptions = { schema: deltaData.schema, readOnly: true }
|
||||
|
||||
if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value')
|
||||
options = _.defaults {data: deltaData.left}, treemaOptions
|
||||
TreemaNode.make(leftEl, options).build()
|
||||
|
||||
if _.isObject(deltaData.right) and rightEl = deltaEl.find('.new-value')
|
||||
options = _.defaults {data: deltaData.right}, treemaOptions
|
||||
TreemaNode.make(rightEl, options).build()
|
||||
|
||||
if deltaData.action is 'text-diff'
|
||||
left = difflib.stringAsLines deltaData.left
|
||||
right = difflib.stringAsLines deltaData.right
|
||||
sm = new difflib.SequenceMatcher(left, right)
|
||||
opcodes = sm.get_opcodes()
|
||||
el = deltaEl.find('.text-diff')
|
||||
options = {baseTextLines: left, newTextLines: right, opcodes: opcodes}
|
||||
args = _.defaults options, TEXTDIFF_OPTIONS
|
||||
el.append(diffview.buildView(args))
|
||||
|
||||
if _.isObject(deltaData.right) and rightEl = deltaEl.find('.new-value')
|
||||
options =
|
||||
data: deltaData.right
|
||||
schema: deltaData.schema
|
||||
readOnly: true
|
||||
treema = TreemaNode.make(rightEl, options)
|
||||
treema.build()
|
||||
|
||||
if deltaData.action is 'text-diff'
|
||||
left = difflib.stringAsLines deltaData.left
|
||||
right = difflib.stringAsLines deltaData.right
|
||||
sm = new difflib.SequenceMatcher(left, right)
|
||||
opcodes = sm.get_opcodes()
|
||||
el = deltaEl.find('.text-diff')
|
||||
args = {
|
||||
baseTextLines: left
|
||||
newTextLines: right
|
||||
opcodes: opcodes
|
||||
baseTextName: "Old"
|
||||
newTextName: "New"
|
||||
contextSize: 5
|
||||
viewType: 1
|
||||
}
|
||||
el.append(diffview.buildView(args))
|
||||
getApplicableDelta: ->
|
||||
delta = @model.getDelta()
|
||||
delta = deltasLib.pruneConflictsFromDelta delta, @conflicts if @conflicts
|
||||
delta
|
42
app/views/editor/patch_modal.coffee
Normal file
42
app/views/editor/patch_modal.coffee
Normal file
|
@ -0,0 +1,42 @@
|
|||
ModalView = require 'views/kinds/ModalView'
|
||||
template = require 'templates/editor/patch_modal'
|
||||
DeltaView = require 'views/editor/delta'
|
||||
|
||||
module.exports = class PatchModal extends ModalView
|
||||
id: "patch-modal"
|
||||
template: template
|
||||
plain: true
|
||||
|
||||
constructor: (@patch, @targetModel, options) ->
|
||||
super(options)
|
||||
targetID = @patch.get('target').id
|
||||
if false
|
||||
@originalSource = targetModel.clone(false)
|
||||
@onOriginalLoaded()
|
||||
else
|
||||
@originalSource = new targetModel.constructor({_id:targetID})
|
||||
@originalSource.fetch()
|
||||
@listenToOnce @originalSource, 'sync', @onOriginalLoaded
|
||||
@addResourceToLoad(@originalSource)
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
return if @originalSource.loading
|
||||
headModel = @originalSource.clone(false)
|
||||
headModel.set(@targetModel.attributes)
|
||||
|
||||
pendingModel = @originalSource.clone(false)
|
||||
pendingModel.applyDelta(@patch.get('delta'))
|
||||
|
||||
@deltaView = new DeltaView({model:pendingModel, headModel:headModel})
|
||||
changeEl = @$el.find('.changes-stub')
|
||||
@insertSubView(@deltaView, changeEl)
|
||||
super()
|
||||
|
||||
acceptPatch: ->
|
||||
delta = @deltaView.getApplicableDelta()
|
||||
pendingModel = @originalSource.clone(false)
|
||||
pendingModel.applyDelta(delta)
|
|
@ -2,6 +2,7 @@ CocoView = require 'views/kinds/CocoView'
|
|||
template = require 'templates/editor/patches'
|
||||
PatchesCollection = require 'collections/PatchesCollection'
|
||||
nameLoader = require 'lib/NameLoader'
|
||||
PatchModal = require './patch_modal'
|
||||
|
||||
module.exports = class PatchesView extends CocoView
|
||||
template: template
|
||||
|
@ -10,6 +11,7 @@ module.exports = class PatchesView extends CocoView
|
|||
|
||||
events:
|
||||
'change .status-buttons': 'onStatusButtonsChanged'
|
||||
'click .patch-icon': 'openPatchModal'
|
||||
|
||||
constructor: (@model, options) ->
|
||||
super(options)
|
||||
|
@ -47,3 +49,8 @@ module.exports = class PatchesView extends CocoView
|
|||
@initPatches()
|
||||
@load()
|
||||
@render()
|
||||
|
||||
openPatchModal: (e) ->
|
||||
patch = _.find @patches.models, {id:$(e.target).data('patch-id')}
|
||||
modal = new PatchModal(patch, @model)
|
||||
@openModalView(modal)
|
|
@ -10,7 +10,7 @@ classCount = 0
|
|||
makeScopeName = -> "view-scope-#{classCount++}"
|
||||
doNothing = ->
|
||||
|
||||
module.exports = class CocoView extends Backbone.View
|
||||
class CocoView extends Backbone.View
|
||||
startsLoading: false
|
||||
cache: false # signals to the router to keep this view around
|
||||
template: -> ''
|
||||
|
@ -348,6 +348,7 @@ module.exports = class CocoView extends Backbone.View
|
|||
slider
|
||||
|
||||
|
||||
|
||||
mobileRELong = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
|
||||
mobileRELong = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
|
||||
mobileREShort = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
|
||||
|
||||
module.exports = CocoView
|
||||
|
|
|
@ -40,7 +40,6 @@ PatchSchema.pre 'save', (next) ->
|
|||
|
||||
patches = document.get('patches') or []
|
||||
patches.push @_id
|
||||
console.log 'PATCH PUSHED', @_id
|
||||
document.set 'patches', patches
|
||||
document.save (err) -> next(err)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue