diff --git a/app/lib/deltas.coffee b/app/lib/deltas.coffee index 954af00ba..0782231eb 100644 --- a/app/lib/deltas.coffee +++ b/app/lib/deltas.coffee @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index de1695490..59820f703 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -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 diff --git a/app/styles/editor/delta.sass b/app/styles/editor/delta.sass index f41da3667..013478efb 100644 --- a/app/styles/editor/delta.sass +++ b/app/styles/editor/delta.sass @@ -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%) \ No newline at end of file + > .panel-heading + background-color: lighten(darkslategray, 60%) + strong + color: darkslategray diff --git a/app/styles/editor/patch.sass b/app/styles/editor/patch.sass new file mode 100644 index 000000000..3296d946c --- /dev/null +++ b/app/styles/editor/patch.sass @@ -0,0 +1,3 @@ +#patch-modal + .modal-body + padding: 10px \ No newline at end of file diff --git a/app/styles/modal/save_version.sass b/app/styles/modal/save_version.sass index e7ab79751..66de28a29 100644 --- a/app/styles/modal/save_version.sass +++ b/app/styles/modal/save_version.sass @@ -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 diff --git a/app/templates/editor/delta.jade b/app/templates/editor/delta.jade index 961483324..480e4ef01 100644 --- a/app/templates/editor/delta.jade +++ b/app/templates/editor/delta.jade @@ -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 \ No newline at end of file + +deltaPanel(delta) + \ No newline at end of file diff --git a/app/templates/editor/patch_modal.jade b/app/templates/editor/patch_modal.jade new file mode 100644 index 000000000..4b094cd81 --- /dev/null +++ b/app/templates/editor/patch_modal.jade @@ -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 \ No newline at end of file diff --git a/app/templates/editor/patches.jade b/app/templates/editor/patches.jade index ce3b1af84..872788e7d 100644 --- a/app/templates/editor/patches.jade +++ b/app/templates/editor/patches.jade @@ -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 diff --git a/app/templates/editor/thang/edit.jade b/app/templates/editor/thang/edit.jade index 04486aba1..b751d6de8 100644 --- a/app/templates/editor/thang/edit.jade +++ b/app/templates/editor/thang/edit.jade @@ -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 diff --git a/app/views/editor/delta.coffee b/app/views/editor/delta.coffee index 4d4635ebf..09c0981a6 100644 --- a/app/views/editor/delta.coffee +++ b/app/views/editor/delta.coffee @@ -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 \ No newline at end of file diff --git a/app/views/editor/patch_modal.coffee b/app/views/editor/patch_modal.coffee new file mode 100644 index 000000000..2eecab361 --- /dev/null +++ b/app/views/editor/patch_modal.coffee @@ -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) \ No newline at end of file diff --git a/app/views/editor/patches_view.coffee b/app/views/editor/patches_view.coffee index abba96997..f8dd4fa15 100644 --- a/app/views/editor/patches_view.coffee +++ b/app/views/editor/patches_view.coffee @@ -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) \ No newline at end of file diff --git a/app/views/kinds/CocoView.coffee b/app/views/kinds/CocoView.coffee index b73685eb1..c3fd12228 100644 --- a/app/views/kinds/CocoView.coffee +++ b/app/views/kinds/CocoView.coffee @@ -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 diff --git a/server/patches/Patch.coffee b/server/patches/Patch.coffee index a6c5da41f..df621f2a4 100644 --- a/server/patches/Patch.coffee +++ b/server/patches/Patch.coffee @@ -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)