Make sure that we display the status of the replicas (in case of unsatisfiable goals for instance)

--- admin/static/coffee/namespaces/replicas.coffee
+++ admin/static/coffee/namespaces/replicas.coffee
@@ -142,10 +142,14 @@
                             replicated_blocks: if backfilling_info.replicated_blocks>backfilling_info.replicated_blocks then backfilling_info.total_blocks else backfilling_info.replicated_blocks

                         @.$('.replica-status').html @progress_bar.render(num_replicas_ready, num_replicas_ready+num_replicas_not_ready, progress_bar_info).$el
+                else # The blueprint was not regenerated, so we can consider that no replica is up to date
+                    @.$('.replica-status').html @progress_bar.render(0, @expected_num_replicas, {}).$el
             else
                 # Blueprint was regenerated, so we can display the bar
                 if num_replicas_ready+num_replicas_not_ready is @expected_num_replicas
                     @.$('.replica-status').html @progress_bar.render(num_replicas_ready, num_replicas_ready+num_replicas_not_ready, progress_bar_info).$el
+                else # The blueprint was not regenerated, so we can consider that no replica is up to date
+                    @.$('.replica-status').html @progress_bar.render(0, @expected_num_replicas, {}).$el
             return @

         # Render the list of datacenters for MDC


Fix bug #750

Fix bug #750

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -155,22 +155,25 @@
     link = ($scope, $el, $attrs) ->
         $scope.pubblicRegisterEnabled = $config.get("pubblicRegisterEnabled")
         $scope.data = {}
-        form = $el.find("form").checksley()

         onSuccessSubmit = (response) ->
-            if $routeParams and $routeParams['next'] and $routeParams['next'] != '/login'
-                $el.find("form").attr('action', $routeParams['next'])
+            # NOTE: Hack to remember the login form values
+            form = $el.find("form")
+
+            form.attr("method", "POST")
+            if $routeParams['next'] and $routeParams['next'] != '/login'
+                form.attr("action", $routeParams['next'])
             else
-                $el.find("form").attr('action', "/")
-
-            $el.find("form")
-            .submit()
+                form.attr("action", "/")
+
+            form.submit()

         onErrorSubmit = (response) ->
-            $confirm.notify("light-error", "According to our Oompa Loompas, your username/email or password
-                                            are incorrect.") #TODO: i18n
+            $confirm.notify("light-error", "According to our Oompa Loompas, your username/email
+                                            or password are incorrect.") #TODO: i18n

         submit = ->
+            form = $el.find("form").checksley()
             if not form.validate()
                 return


renamed getDocument to readDocument

renamed getDocument to readDocument

--- lib/middleware.coffee
+++ lib/middleware.coffee
@@ -101,7 +101,7 @@

   # # Return an object representing this daemon process.
   return daemon =
-    getDocument: (filename, callback) ->
+    readDocument: (filename, callback) ->
       callback(null, "TextObj content retrieval not implemented yet")

 middleware = (options) ->
@@ -230,7 +230,7 @@
         next()

   # Add some helper methods to the middleware object before returning it.
-  mobwriteMiddleware.getDocument = -> daemon.getDocument.apply(daemon, arguments)
+  mobwriteMiddleware.readDocument = -> daemon.readDocument.apply(daemon, arguments)
   mobwriteMiddleware.on = -> emitter.on.apply(emitter, arguments)
   return mobwriteMiddleware


mocks/jobs: ensure events fire in sequence

The timeouts were unreliable - sometimes the complete event would fire before
the data events all fired. So now we wait for each event to fire before firing
the next one. This also makes it much easier to handle very low timeouts (like
0).

--- lib/mocks/jobs.coffee
+++ lib/mocks/jobs.coffee
@@ -1,3 +1,5 @@
+_ = require 'underscore'
+async = require 'async'
 {EventEmitter}  = require 'events'

 class MockJob extends EventEmitter
@@ -7,39 +9,46 @@
     @events = []
   delayEmit: (event, timeout, args...) =>
     @events.push {event, timeout, args}
+  intervalEmit: (event, timeout, data) ->
+    interval = timeout / (data.length + 1)
+    @delayEmit event, (i + 1) * interval, el for el, i in data
   start: =>
-    for {event, timeout, args} in @events
-      setTimeout @emit.bind(@), timeout, event, @handle, args...
+    events = if @events.length < 2
+      @events
+    else
+      events = _.sortBy @events, (e) -> e.timeout
+      event_pairs = _.zip events, _.rest(events).concat [_.last events]
+      _.reduce event_pairs, (acc, [event, next_event]) ->
+        acc.concat _.extend event, timeout: next_event.timeout - event.timeout
+      , []
+    async.forEachSeries events, ({event, timeout, args}, cb_fe) =>
+      setTimeout =>
+        @emit event, @handle, args...
+        cb_fe()
+      , timeout
+    , -> # No errors to handle
     @

-class FinishJob extends MockJob
-  constructor: (event, timeout = 500) ->
+class CompleteJob extends MockJob
+  constructor: (timeout = 500) ->
     super
-    @delayEmit event, timeout
+    @delayEmit 'complete', timeout

-class IntervalJob extends FinishJob
-  constructor: (finish_event, event, data, timeout = 500) ->
-    # Short timeouts can cause the finish event to fire before all the data
-    # events fire
-    timeout = if timeout <= data.length then (data.length + 1) * 2 else timeout
-    super finish_event, timeout
-    interval = timeout / (data.length + 1)
-    @delayEmit event, (i + 1) * interval, el for el, i in data
+class DataJob extends CompleteJob
+  constructor: (data, timeout = 500) ->
+    # In case timeout is 0, add 1 so 'complete' event fires last
+    super timeout + 1
+    @intervalEmit 'data', timeout, data

-class CompleteJob extends FinishJob
+class FailJob extends MockJob
   constructor: (timeout = 500) ->
-    super 'complete', timeout
+    super
+    @delayEmit 'fail', timeout

-class DataJob extends IntervalJob
-  constructor: (data, timeout = 500) ->
-    super 'complete', 'data', data, timeout
-
-class FailJob extends FinishJob
-  constructor: (timeout = 500) ->
-    super 'fail', timeout
-
-class ErrorJob extends IntervalJob
+class ErrorJob extends FailJob
   constructor: (messages, timeout = 500) ->
-    super 'fail', 'warning', messages, timeout
+    # In case timeout is 0, add 1 so 'fail' event fires last
+    super timeout + 1
+    @intervalEmit 'warning', timeout, messages

 module.exports = {MockJob, CompleteJob, DataJob, FailJob, ErrorJob}


config/hasownproperty

config/hasownproperty

--- test/specs/@base/init.coffee
+++ test/specs/@base/init.coffee
@@ -4,6 +4,7 @@
   'config_alias': 'config/alias/main'
   'config_base': 'config/base/main'
   'config_charset': 'config/charset/main'
+  'config_hasOwnProperty': 'config/hasownproperty/main'

 seajs.config
   base: base


range -> domRange in test helpers

range -> domRange in test helpers

--- test/src/test_helpers.coffee
+++ test/src/test_helpers.coffee
@@ -190,10 +190,10 @@
   deleteSelection()
   getDOMRange()?.insertNode(node)

-  range = document.createRange()
-  range.selectNode(node)
-  range.collapse(false)
-  setDOMRange(range)
+  domRange = document.createRange()
+  domRange.selectNode(node)
+  domRange.collapse(false)
+  setDOMRange(domRange)
   callback?()

 getDOMRange = ->
@@ -201,10 +201,10 @@
   if selection.rangeCount
     selection.getRangeAt(0)

-setDOMRange = (range) ->
+setDOMRange = (domRange) ->
   selection = window.getSelection()
   selection.removeAllRanges()
-  selection.addRange(range)
+  selection.addRange(domRange)

 @createEvent = (type, properties = {}) ->
   event = document.createEvent("Events")
@@ -281,14 +281,14 @@

 @collapseSelection = (direction, callback) ->
   selection = window.getSelection()
-  range = selection.getRangeAt(0)
-  newRange = document.createRange()
+  domRange = selection.getRangeAt(0)
+  newDOMRange = document.createRange()
   if direction is "left"
-    newRange.setStart(range.startContainer, range.startOffset)
-  else
-    newRange.setStart(range.endContainer, range.endOffset)
+    newDOMRange.setStart(domRange.startContainer, domRange.startOffset)
+  else
+    newDOMRange.setStart(domRange.endContainer, domRange.endOffset)
   selection.removeAllRanges()
-  selection.addRange(newRange)
+  selection.addRange(newDOMRange)
   Trix.selectionChangeObserver.update()
   defer(callback)


enhance: simpler checks

enhance: simpler checks

--- app/app.coffee
+++ app/app.coffee
@@ -8,9 +8,7 @@
 config.isAppknox = !config.isDevknox

 config.platform =
-  if config.isAppknox
-    " | Appknox"
-  else if config.isDevknox
+  if config.isDevknox
     " | Devknox"
   else
     " | Appknox"


add sublToSubl

add sublToSubl

--- lib/atomizr.coffee
+++ lib/atomizr.coffee
@@ -3,10 +3,12 @@
 # 3rd-party dependencies
 fs = require 'fs'
 path = require 'path'
+glob = require 'glob'
 CSON = require 'cson'
 parseCson = require 'cson-parser'
 parseJson = require 'parse-json'
-{parseString} = require 'xml2js'
+convert = require('xml-js');
+tidy = require("tidy-html5").tidy_html5

 module.exports = Atomizr =
   config:
@@ -50,11 +52,12 @@

     # Register commands
     @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:automatic-conversion': => @autoConvert()
-    @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:convert-atom-to-sublime-text': => @atomToSubl()
+    @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:convert-atom-to-sublime-text': => @atomToSubl(null)
     @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:convert-sublime-text-to-atom': => @sublToAtom()
     @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:convert-sublime-text-completions-to-atom': => @sublCompletionsToAtom()
     @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:convert-sublime-text-snippet-to-atom': => @sublSnippetToAtom()
-    @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:toggle-atom-snippet-format': => @atomToAtom()
+    @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:toggle-atom-format': => @atomToAtom()
+    @subscriptions.add atom.commands.add 'atom-workspace', 'atomizr:toggle-sublime-sublformat': => @sublToSubl()

   deactivate: ->
     @subscriptions.dispose()
@@ -68,15 +71,17 @@
     scope = editor.getGrammar().scopeName

     if scope is "source.coffee"
-      @atomToSubl()
+      @atomToSubl(null)
     else if scope is "source.json.subl"
       @sublCompletionsToAtom()
     else if scope is "text.xml.subl"
       @sublSnippetToAtom()

   # Convert Atom snippet into Sublime Text completion
-  atomToSubl: ->
-    editor = @workspace.getActiveTextEditor()
+  atomToSubl: (editor) ->
+    if editor is null
+      editor = @workspace.getActiveTextEditor()
+
     unless editor?
       atom.beep()
       return
@@ -218,34 +223,34 @@

     # Validate XML
     try
-      parseString text, (e, result) ->
-        obj = result.snippet
+      obj = convert.xml2js(text, {spaces: 4, compact:true})
     catch e
       atom.notifications.addError("Atomizr", detail: "Invalid XML, aborting", dismissable: false)
       return

     # Minimum requirements
-    unless obj.scope? or obj.content?
+    unless obj.snippet.scope? or obj.snippet.content._cdata?
       @invalidFormat("Sublime Text snippet")
       return

     # Get scope, convert if necessary
     for subl,atom of @exceptions
-      if obj.scope.toString() is subl
+      if obj.snippet.scope.toString() is subl
         scope = atom
         break
       else
-        scope = "." + obj.scope
-
-    if obj.description
-      description = obj.description
+        scope = "." + obj.snippet.scope["_text"]
+
+    if obj.snippet.description
+      description = obj.snippet.description["_text"]
     else
-      description = obj.tabTrigger
-
-    obj.content = @addTrailingTabstops(obj.content[0].trim())
+      description = obj.snippet.tabTrigger["_text"]
+
+    prefix = obj.snippet.tabTrigger["_text"]
+    content = @addTrailingTabstops(obj.snippet.content._cdata.trim())

     snippet = {}
-    snippet[obj.description] = { prefix: obj.tabTrigger[0], body: obj.content }
+    snippet[description] = { prefix: prefix, body: content }

     atom = {}
     atom[scope] = snippet
@@ -305,8 +310,90 @@
     # Convert to CSON
     @makeCoffee(editor, input)

+  # Convert Sublime snippet format (JSON to XML, or vice versa)
+  sublToSubl: ->
+    editor = @workspace.getActiveTextEditor()
+    unless editor?
+      atom.beep()
+      return
+    scope = editor.getGrammar().scopeName
+
+
+    # Automatic conversion, based on scope
+    if scope is "source.json.subl"
+      @jsonToXml()
+    else if scope is "text.xml"
+      @xmlToJson()
+
+  xmlToJson: () ->
+    editor = @workspace.getActiveTextEditor()
+    unless editor?
+      atom.beep()
+      return
+    text = editor.getText()
+
+    # Conversion
+    try
+      input = convert.xml2js(text, {spaces: 4, compact:true})
+    catch e
+      atom.notifications.addError("Atomizr", detail: e, dismissable: true)
+      return
+
+    # Minimum requirements
+    unless input.snippet.scope? or input.snippet.content._cdata?
+      @invalidFormat("Sublime Text snippet")
+      return
+
+    obj =
+      meta: @meta
+      scope: input.snippet.scope["_text"]
+      completions: [
+        contents: input.snippet.content._cdata
+        trigger: input.snippet.tabTrigger["_text"]
+      ]
+
+    if input.snippet.description
+      obj.completions.trigger = "#{obj.completions.trigger}\t#{input.snippet.description['_text']}"
+
+    json = JSON.stringify(obj, null, '\t')
+
+    # Write back to editor and change scope
+    editor.setText(json)
+    editor.setGrammar(@grammars.grammarForScopeName('source.json.subl'))
+    @renameFile(editor, "sublime-completion")
+
+  jsonToXml: () ->
+    editor = @workspace.getActiveTextEditor()
+    unless editor?
+      atom.beep()
+      return
+    text = editor.getText()
+
+    # Conversion
+    try
+      input = parseJson(text)
+    catch e
+      atom.notifications.addError("Atomizr", detail: e, dismissable: true)
+      return
+
+    obj =
+      _comment: @meta
+      snippet:
+        content:
+          _cdata: input.completions[0].contents
+        tabTrigger:
+          _text: input.completions[0].trigger
+        scope:
+          _text: input.scope
+
+    xml = convert.js2xml(obj, {compact: true})
+
+    # Write back to editor and change scope
+    editor.setText(xml)
+    editor.setGrammar(@grammars.grammarForScopeName('text.xml'))
+    @renameFile(editor, "sublime-snippet")
+
   addTrailingTabstops: (input) ->
-
     unless input.match(/\$\d+$/g) is null and atom.config.get('atomizr.addTrailingTabstops') is not false
       # nothing to do here
       return input


editor page updates

editor page updates

--- app/pages/lab/mini-course.cjsx
+++ app/pages/lab/mini-course.cjsx
@@ -7,10 +7,10 @@
   render: ->

-
Here’s some text all about the tutorial editor. Lorem ipsum dolor sit amet, etc.

+
Here’s some text all about the mini-course editor. Lorem ipsum dolor sit amet, etc.

Work in progress.

-        <ProjectModalEditor project={@props.project} type="tutorials" />
+        <ProjectModalEditor project={@props.project} type="minicourses" />


re-impl takeUntil, now passes tests.

re-impl takeUntil, now passes tests.

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -630,9 +630,23 @@

   takeUntil: (stopper) =>
     endMarker = {}
-    withDescription this, "takeUntil", stopper, Bacon.groupSimultaneous(this.mapEnd(endMarker), stopper.skipErrors())
-        .takeWhile(([data, stopper]) -> (stopper.length == 0) && (data[data.length-1] != endMarker))
-        .flatMap(([data, stopper]) -> Bacon.fromArray(data))
+    withDescription(this, "takeUntil", stopper, Bacon.groupSimultaneous(this.mapEnd(endMarker), stopper.skipErrors())
+      .withHandler((event) ->
+        if !event.hasValue()
+          @push event
+        else
+          [data, stopper] = event.value()
+          if stopper.length
+            @push end()
+          else
+            reply = Bacon.more
+            for value in data
+              if value == endMarker
+                reply = @push end()
+              else
+                reply = @push next(value)
+            reply
+      ))

   skipUntil: (starter) ->
     started = starter.take(1).map(true).toProperty(false)


add TODO

add TODO

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -707,6 +707,7 @@
       if sampler instanceof Property then new Property(subscribe) else new EventStream(subscribe)

     @subscribe = (sink) =>
+      # TODO: it's unoptimal to do this bookkeepping per subscriber
       reply = Bacon.more
       class LatestEvent
         set: (event) -> @event = event


Add aliasProperty test cases.

Add aliasProperty test cases.

--- test/unit/helpersSpec.coffee
+++ test/unit/helpersSpec.coffee
@@ -4,35 +4,88 @@
       ObjectHelper = null

       beforeEach ->
-        # Obtain the Helpers service.
+        # Obtain the ObjectHelper service.
         angular.mock.module('qiprofile.helpers')
         inject ['ObjectHelper', (_ObjectHelper_) ->
           ObjectHelper = _ObjectHelper_
         ]
+
+      describe 'fromJSON', ->
+        mock =
+          _id: 1
+          an_int: 1
+          inner:
+            an_obj:
+              an_int: 3
+        obj = null
+
+        beforeEach ->
+          data = JSON.stringify(mock)
+          obj = ObjectHelper.fromJson(data)
+
+        it 'should add camelCase aliases to underscore properties', ->
+          expect('anInt' in Object.keys(obj), "the alias is not defined as an enumerable property")
+            .to.be.true
+          expect(obj.anInt, "the alias value is incorrect").to.equal(mock.an_int)
+          obj.an_int = obj.an_int + 1
+          expect(obj.anInt, "the alias value does not reflect an underscore change")
+            .to.equal(obj.an_int)
+          obj.anInt = obj.anInt + 1
+          expect(obj.an_int, "the underscore value does not reflect an alias change")
+            .to.equal(obj.anInt)

-      describe 'copyNonNullPublicProperties', ->
-        it 'should copy public properties', ->
+        it 'should make the camelCase alias enumerable', ->
+          expect('anInt' in Object.keys(obj), "the underscore property is still enumerable")
+            .to.be.true
+
+        it 'should make the underscore property non-enumerable', ->
+          expect('an_int' in Object.keys(obj), "the underscore property is still enumerable")
+            .to.be.false
+
+        it 'should make the underscore property nonenumerable', ->
+          expect(obj.an_int, "an_int is incorrect").to.equal(mock.an_int)
+
+        it 'should add camelCase aliases to inner objects', ->
+          expect('anObj' in Object.keys(obj.inner), "the inner underscore property is not aliased")
+            .to.be.true
+          expect(obj.inner.anObj.anInt, "the inner reference alias value is incorrect")
+            .to.eql(mock.inner.an_obj.an_int)
+
+      describe 'aliasPublicDataProperties', ->
+        it 'should alias public value properties', ->
           src = {a: 1}
           dest = {}
-          ObjectHelper.copyNonNullPublicProperties(src, dest)
-          expect(dest.a, 'A public property was not copied').to.equal(src.a)
+          ObjectHelper.aliasPublicDataProperties(src, dest)
+          expect(dest.a, 'The public value property was not aliased').to.equal(src.a)

-        it 'should replace null public properties', ->
+        it 'should alias public virtual properties', ->
+          src = {a: 1}
+          Object.defineProperty src, 'b',
+            enumerable: true
+            get: -> src.a
+          dest = {}
+          ObjectHelper.aliasPublicDataProperties(src, dest)
+          expect(dest.b, 'The public virtual property was not aliased').to.equal(src.b)
+
+        it 'should alias non-eumerable public properties', ->
+          src = {}
+          Object.defineProperty src, 'a',
+            enumerable: false
+            value: 1
+          dest = {}
+          ObjectHelper.aliasPublicDataProperties(src, dest)
+          expect(dest.a, 'The public virtual property was not aliased').to.equal(src.a)
+
+        it 'should not alias private properties', ->
+          src = {_a: 2}
+          dest = {}
+          ObjectHelper.aliasPublicDataProperties(src, dest)
+          expect(dest._a, 'A private property was aliased').to.not.exist
+
+        it 'should not alias existing properties', ->
           src = {a: 1}
           dest = {a: null}
-          ObjectHelper.copyNonNullPublicProperties(src, dest)
-          expect(dest.a, 'A public property was not copied').to.equal(src.a)
-
-        it 'should not copy private properties', ->
-          src = {_a: 2}
-          dest = {}
-          ObjectHelper.copyNonNullPublicProperties(src, dest)
-          expect(dest._a, 'A private property was copied').to.not.exist
-
-        it 'should not overwrite non-null properties', ->
-          src = {a: 1}
-          dest = {a: 2}
-          ObjectHelper.copyNonNullPublicProperties(src, dest)
+          ObjectHelper.aliasPublicDataProperties(src, dest)
           expect(dest.a, 'A non-null destination property was overrwritten')
             .to.not.equal(src.a)

@@ -40,7 +93,7 @@
       DateHelper = null

       beforeEach ->
-        # Obtain the Helpers service.
+        # Obtain the DateHelper service.
         angular.mock.module('qiprofile.helpers')
         inject ['DateHelper', (_DateHelper_) ->
           DateHelper = _DateHelper_


add reload and move browser sync to top

add reload and move browser sync to top


Bug fix: simulation of land base air corps

--- lib/simulator2.coffee
+++ lib/simulator2.coffee
@@ -254,7 +254,7 @@
       stages.push getEngagementStage(packet)
       # Land base air attack
       for api_kouku in packet.api_air_base_attack || []
-        stages.push simulateAerial(@mainFleet, @escortFleet, @enemyFleet, api_kouku)
+        stages.push simulateLandBase(@enemyFleet, api_kouku)
       # Aerial Combat
       stages.push simulateAerial(@mainFleet, @escortFleet, @enemyFleet, packet.api_kouku)
       # Aerial Combat 2nd


guessing opening anti sub for ec battle

guessing opening anti sub for ec battle

--- lib/simulator2.coffee
+++ lib/simulator2.coffee
@@ -336,7 +336,7 @@
           stages.push simulateTorpedo(@mainFleet, @enemyFleet, null, packet.api_raigeki)
         if enemyType == 1
           # Opening Anti-Sub
-          #stages.push simulateShelling(@mainFleet, @enemyFleet, null, packet.api_opening_taisen, StageType.Opening)
+          stages.push simulateShelling(@mainFleet, @enemyFleet, @enemyEscort, packet.api_opening_taisen, StageType.Opening)
           # Opening Torpedo Salvo
           stages.push simulateTorpedo(@mainFleet, @enemyFleet, @enemyEscort, packet.api_opening_atack, StageType.Opening)
           # Shelling (Escort)


add support for realtime diff

add support for realtime diff

--- lib/split-diff.coffee
+++ lib/split-diff.coffee
@@ -6,20 +6,20 @@

 module.exports = SplitDiff =
   subscriptions: null
-  JsDiff: require 'diff'
-  SplitDiffCompute: require './split-diff-compute'
   isEnabled: false
   diffViewEditor1: null
   diffViewEditor2: null
+  editorSubscriptions: null

   activate: (state) ->
     # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
     @subscriptions = new CompositeDisposable
+    @editorSubscriptions = new CompositeDisposable

     # Register command that finds visible text editors
     @subscriptions.add atom.commands.add 'atom-workspace',
-      'split-diff:diffPanes': => @turnOn()
-      'split-diff:disable': => @turnOff()
+      'split-diff:diffPanes': => @diffPanes()
+      'split-diff:disable': => @clearDiff()

   deactivate: ->
     @subscriptions.dispose()
@@ -33,12 +33,10 @@
     else #turn on
       @turnOn()

-  turnOn: ->
-    if @isEnabled
-      @turnOff()
-
+  getVisibleEditors: ->
     editor1 = null
     editor2 = null
+
     atom.workspace.observeTextEditors (editor) =>
       editorView = atom.views.getView(editor)
       $editorView = $(editorView)
@@ -48,25 +46,53 @@
         else if editor2 == null
           editor2 = editor

-    if editor1 == null || editor2 == null
+    editors =
+      editor1: editor1
+      editor2: editor2
+    return editors
+
+  diffPanes: ->
+    editors = @getVisibleEditors()
+
+    if editors.editor1 == null || editors.editor2 == null
       atom.notifications.addInfo('Split Diff', {detail: 'You must have two panes open.'})
       return

-    computedDiff = @SplitDiffCompute.computeDiff(editor1.getText(), editor2.getText())
+    @editorSubscriptions.add editors.editor1.onDidStopChanging =>
+      @updateDiff(editors)
+    @editorSubscriptions.add editors.editor2.onDidStopChanging =>
+      @updateDiff(editors)
+    @editorSubscriptions.add editors.editor1.onDidDestroy =>
+      console.log('dest e1')
+      @editorSubscriptions.dispose()
+      @clearDiff()
+    @editorSubscriptions.add editors.editor2.onDidDestroy =>
+      console.log('dest e2')
+      @editorSubscriptions.dispose()
+      @clearDiff()
+
+    @updateDiff(editors)
+
+  updateDiff: (editors) ->
+    if @isEnabled
+      @clearDiff()
+
+    SplitDiffCompute = require './split-diff-compute'
+    computedDiff = SplitDiffCompute.computeDiff(editors.editor1.getText(), editors.editor2.getText())
     console.log computedDiff

-    @displayDiff(editor1, editor2, computedDiff)
+    @displayDiff(editors, computedDiff)

-    @syncScroll = new SyncScroll(editor1, editor2)
+    @syncScroll = new SyncScroll(editors.editor1, editors.editor2)

     @isEnabled = true
     console.log 'split-diff enabled'

-  turnOff: ->
+  clearDiff: ->
     @diffViewEditor1.removeLineOffsets()
+    @diffViewEditor1.removeLineHighlights()
+
     @diffViewEditor2.removeLineOffsets()
-
-    @diffViewEditor1.removeLineHighlights()
     @diffViewEditor2.removeLineHighlights()

     @syncScroll.dispose()
@@ -77,9 +103,9 @@
     @isEnabled = false
     console.log 'split-diff disabled'

-  displayDiff: (editor1, editor2, computedDiff)->
-    @diffViewEditor1 = new DiffViewEditor(editor1)
-    @diffViewEditor2 = new DiffViewEditor(editor2)
+  displayDiff: (editors, computedDiff) ->
+    @diffViewEditor1 = new DiffViewEditor(editors.editor1)
+    @diffViewEditor2 = new DiffViewEditor(editors.editor2)

     @diffViewEditor1.setLineOffsets(computedDiff.oldLineOffsets)
     @diffViewEditor2.setLineOffsets(computedDiff.newLineOffsets)
@@ -87,5 +113,5 @@
     @diffViewEditor1.setLineHighlights(undefined, computedDiff.removedLines)
     @diffViewEditor2.setLineHighlights(computedDiff.addedLines, undefined)

-    @diffViewEditor1.scrollToTop()
-    @diffViewEditor2.scrollToTop()
+    #@diffViewEditor1.scrollToTop()
+    #@diffViewEditor2.scrollToTop()


refactor tests

refactor tests

--- lib/translator.coffee
+++ lib/translator.coffee
@@ -68,12 +68,12 @@
   ###
   Add new *object of the Translator
   ###
-  add: (objt, o2)->
-    args = Args([
-      {objt: Args.OBJECT | Args.Required}
-      ], arguments)
+  add: (objects...)->
+    # args = Args([
+    #   {objt: Args.OBJECT | Args.Required}
+    #   ], arguments)

-    @_translate = _.assign(@_translate, args.objt, o2)
+    @_translate = _.assign(@_translate, objects)
     this

   ###


Fix object type bug introduced in core/kd

--- lib/core/kd.coffee
+++ lib/core/kd.coffee
@@ -21,7 +21,7 @@
       if @[key] then throw new Error "#{key} is already registered"
       else @[key] = val

-  deleteSingletons: -> singletons = []
+  deleteSingletons: -> singletons = {}

   registerSingleton: (name, obj, override = no)->
     if (existing = singletons[name])?
@@ -46,7 +46,7 @@
   deleteInstance: (id) ->
     delete instances[id]

-  deleteInstances: -> instances = []
+  deleteInstances: -> instances = {}

   getSingleton: getSingleton


couple small fixes

couple small fixes

--- lib/crudify.coffee
+++ lib/crudify.coffee
@@ -3,7 +3,6 @@

 # underscores is amazing, we'll be using this a lot.
 _ = require "underscore"
-qs = require "querystring"

 extendify = require "./extended"

@@ -95,10 +94,9 @@

           _.each exclude, (element, index, list) ->
             for k in [0..keys.length]
-              do (k) ->
-                cur = datastores[k]
-                if datastores.indexOf(cur) != -1
-                  delete datastores[k][element]
+              cur = datastores[k]
+              if datastores.indexOf(cur) != -1
+                delete datastores[k][element]

         # build out our skip query
         if skip? then queryHandler.skip skip, datastores, (err, skipped) ->


Update mix colors

Update mix colors

--- lib/duotone.coffee
+++ lib/duotone.coffee
@@ -51,9 +51,9 @@
   unsetColors() # prevents adding endless properties

   # Color limits
-  _high = chroma.mix('hsl(0,0%,100%)', uno, 0.5);
+  _high = chroma.mix( uno, 'hsl(0,0%,100%)', 0.5); # mix with white
   _mid  = uno
-  _low  = chroma.mix('hsl(0,0%,25%)', uno, 0.25);
+  _low  = chroma.mix(uno, 'hsl(0,0%,25%)', 0.25); # mix with dark grey

   # Color scales
   _scaleUno = chroma.scale([ _high, _mid, _low]).colors(5)


Change the default gateway to https://rocket.chat

--- packages/rocketchat-lib/server/startup/settings.coffee
+++ packages/rocketchat-lib/server/startup/settings.coffee
@@ -96,7 +96,7 @@
 RocketChat.settings.add 'Push_debug', false, { type: 'boolean', group: 'Push', public: true }
 RocketChat.settings.add 'Push_enable', false, { type: 'boolean', group: 'Push', public: true }
 RocketChat.settings.add 'Push_enable_gateway', true, { type: 'boolean', group: 'Push' }
-RocketChat.settings.add 'Push_gateway', 'https://push.rocket.chat', { type: 'string', group: 'Push' }
+RocketChat.settings.add 'Push_gateway', 'https://rocket.chat', { type: 'string', group: 'Push' }
 RocketChat.settings.add 'Push_production', false, { type: 'boolean', group: 'Push', public: true }
 RocketChat.settings.add 'Push_apn_passphrase', '', { type: 'string', group: 'Push' }
 RocketChat.settings.add 'Push_apn_key', '', { type: 'string', multiline: true, group: 'Push' }


fix room publication

fix room publication

--- packages/rocketchat-ui-sidenav/side-nav/sideNav.coffee
+++ packages/rocketchat-ui-sidenav/side-nav/sideNav.coffee
@@ -55,6 +55,9 @@
 		e.preventDefault()

 Template.sideNav.onRendered ->
+	Meteor.subscribe 'room', "c"
+	Meteor.subscribe 'room', "p"
+	Meteor.subscribe 'room', "d"
 	SideNav.init()
 	menu.init()


Remove debugging code

Remove debugging code

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -109,7 +109,6 @@
   #  console.log "\\#{def} = #{val}"
   if 0 < _.size defs
     r = ///\\(#{_.keys(defs).join '|'})\s*///g
-    console.log _.keys(defs).join '|'
     while 0 <= tex.search(r)
       tex = tex.replace r, (match, def) -> defs[def]
   ## After \def expansion and verbatim processing, protect math


LaTeX above HTML

LaTeX above HTML

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -1,4 +1,4 @@
-@availableFormats = ['markdown', 'html', 'latex']  ## 'file' not an option for user
+@availableFormats = ['markdown', 'latex', 'html']  ## 'file' not an option for user
 @mathjaxFormats = availableFormats  ## Don't do tex2jax for files

 if Meteor.isClient


bit of refactoring

bit of refactoring

--- lib/gotodef.coffee
+++ lib/gotodef.coffee
@@ -1,5 +1,6 @@
 {$$, SelectListView} = require 'atom-space-pen-views'
 fuzzaldrinPlus = require 'fuzzaldrin-plus'
+loading = require 'util/loading'

 # ## GoToDef-Panel
 #
@@ -13,17 +14,24 @@
 #     .line:      Line of definition.
 #     .dispfile:  Humanized file path, displayed.
 #
-# On rejection, the `goToView` will only shown the error message provided.
+# On rejection, the `GotoView` will only shown the error message provided.

 module.exports =
 goto: (promise) ->
-  @view ?= new MethodView()
-  @view.setLoading "Loading..."
-  @view.show()
-  promise.then ((items) -> @view.setItems items),
-                (error) -> @view.setError error
+  @view ?= new GotoView()

-class goToView extends SelectListView
+  promise
+    .then (items) ->
+      if items.length == 1
+        GotoView.openItem items[0]
+      else if items.length > 1
+        @view.setItems items
+        @view.show()
+
+    .catch (error) ->
+      @view.setError error
+
+class GotoView extends SelectListView
   initialize: ->
     super
     @panel = atom.workspace.addModalPanel(item: this, visible: false)
@@ -79,10 +87,13 @@

   # Jump to `item.file` at line `item.line`, when an item was selected.
   confirmed: (item) ->
-    atom.workspace.open item.file,
-      initialLine: item.line
+    @openItem item
     @hide()

   # Return to previously focused element when the modal panel is cancelled.
   cancelled: ->
     @hide()
+
+  @openItem: (item) ->
+    atom.workspace.open item.file,
+      initialLine: item.line


auto pin mode

auto pin mode

--- grovepi.coffee
+++ grovepi.coffee
@@ -97,6 +97,7 @@

 	sendWithMode: (cmd, port) ->
 		args = arguments
+		console.log args
 		@mode port, MODES.output, => @send args...

@@ -116,5 +117,6 @@
 	ranger: ( port, callback ) ->
 		@sendWithMode CMD.ranger, port, [0,0], =>
 			@receiveWithMode 3, (data) ->
+
 				callback data[2] + data[1]


accept optional start of column

accept optional start of column

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -48,11 +48,13 @@
         spawnedProcess.process.stdin.write(options.stdin.toString())
         spawnedProcess.process.stdin.end() # We have to end it or the programs will keep waiting forever

-  rangeFromLineNumber: (textEditor, lineNumber) ->
+  rangeFromLineNumber: (textEditor, lineNumber, colStart) ->
     throw new Error('Provided text editor is invalid') unless textEditor instanceof TextEditor
     throw new Error('Invalid lineNumber provided') if typeof lineNumber is 'undefined'
+    unless typeof colStart is 'number'
+      colStart = (textEditor.indentationForBufferRow(lineNumber) * textEditor.getTabLength())
     return [
-      [lineNumber, (textEditor.indentationForBufferRow(lineNumber) * textEditor.getTabLength())],
+      [lineNumber, colStart],
       [lineNumber, textEditor.getBuffer().lineLengthForRow(lineNumber)]
     ]


:bug: throw an error if we get something on stderr when we were expecting it to be on stdout

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -25,18 +25,22 @@
         options.env ?= {}
         options.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE = '1' # Needed for electron
       spawnedProcess = child_process.spawn(command, args, options)
-      data = []
-      if options.stream is 'stdout'
-        spawnedProcess.stdout.on 'data', (d) -> data.push(d.toString())
-      else if options.stream is 'stderr'
-        spawnedProcess.stderr.on 'data', (d) -> data.push(d.toString())
+      data = stdout: [], stderr: []
+      spawnedProcess.stdout.on 'data', (d) -> data.stdout.push(d.toString())
+      spawnedProcess.stderr.on 'data', (d) -> data.stderr.push(d.toString())
       if options.stdin
         spawnedProcess.stdin.write(options.stdin.toString())
         spawnedProcess.stdin.end() # We have to end it or the programs will keep waiting forever
       spawnedProcess.on 'error', (err) ->
         reject(err)
       spawnedProcess.on 'close', ->
-        resolve(data.join(''))
+        if options.stream is 'stdout'
+          if data.stderr.length
+            reject(data.stderr.join(''))
+          else
+            resolve(data.stdout.join(''))
+        else
+          resolve(data.stderr.join(''))

   # Due to what we are attempting to do, the only viable solution right now is
   #   XRegExp.


Implement stdin, and did some cleanup.

Implement stdin, and did some cleanup.

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -35,11 +35,13 @@
     throw new Error "Nothing to execute." if not arguments.length
     return new Promise (resolve, reject) ->
       process = child_process.exec(command, options)
+      options.stream = 'stdout' if not options.stream
       data = []
       if options.stream == 'stderr'
         process.stderr.on 'data', (d) -> data.push(d.toString())
       else
         process.stdout.on 'data', (d) -> data.push(d.toString())
+      process.stdin.write(options.stdin.toString()) if options.stdin
       process.on 'close', ->
         resolve(data)

@@ -75,36 +77,27 @@
       toReturn = []
       regex = XRegExp(regex)
       for line in data
-        match = XRegExp.exec(line, regex)
-        if match
-          if match.lineStart
-            lineStart = match.lineStart - options.baseReduction
-          else if match.line
-            lineStart = match.line - options.baseReduction
-          else
-            lineStart = 0
-          if match.colStart
-            colStart = match.colStart - options.baseReduction
-          else if match.col
-            colStart = match.col - options.baseReduction
-          else
-            colStart = 0
-          if match.lineEnd
-            lineEnd = match.lineEnd - options.baseReduction
-          else if match.line
-            lineEnd = match.line - options.baseReduction
-          else
-            lineEnd = 0
-          if match.colEnd
-            colEnd = match.colEnd - options.baseReduction
-          else if match.col
-            colEnd = match.col - options.baseReduction
-          else
-            colEnd = 0
+        if regex.test line
+          match = XRegExp.exec(line, regex)
+          options.baseReduction = 1 if not options.baseReduction
+          lineStart = 0
+          lineStart = match.line - options.baseReduction if match.line
+          lineStart = match.lineStart - options.baseReduction if match.lineStart
+          colStart = 0
+          colStart = match.col - options.baseReduction if match.col
+          colStart = match.colStart - options.baseReduction if match.colStart
+          lineEnd = 0
+          lineEnd = match.line - options.baseReduction if match.line
+          lineEnd = match.lineEnd - options.baseReduction if match.lineEnd
+          colEnd = 0
+          colEnd = match.col - options.baseReduction if match.col
+          colEnd = match.colEnd - options.baseReduction if match.colEnd
+          filePath = match.file
+          filePath = options.filePath if options.filePath
           toReturn.push(
             type: match.type,
             text: match.message,
-            filePath: match.file
+            filePath: filePath,
             range: [[lineStart, colStart], [lineEnd, colEnd]]
           )
       resolve(toReturn)


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -86,8 +86,7 @@
       events.push(event)
       return event

-    all: ->
-      events
+    all: -> events

   self


Use pane:item-added instead of editor:attached

Now there is info about what pane item is added. Also we don't get
events for mini-editors being attached.

--- lib/metrics.coffee
+++ lib/metrics.coffee
@@ -10,8 +10,9 @@
     Reporter.sendEvent('started')
     Reporter.sendTiming('core-load', atom.getWindowLoadTime())
     Reporter.sendTiming('shell-load', atom.getLoadSettings().shellLoadTime)
-    atom.workspaceView.on 'editor:attached', (event, editorView) ->
-      Reporter.sendView('EditorView')
+     atom.workspaceView.on 'pane:item-added', (event, item) ->
+        name = item.getViewClass?().name ? item.constructor.name
+        Reporter.sendView(name)

   serialize: ->
     sessionLength: Date.now() - @sessionStart


CoffeeScript.helpers.count now handles empty strings properly instead of going into an infinite loop

--- helpers.coffee
+++ helpers.coffee
@@ -15,10 +15,11 @@
 exports.compact = (array) ->
   item for item in array when item

-# Count the number of occurrences of a character in a string.
-exports.count = (string, letter) ->
+# Count the number of occurrences of a string in a string.
+exports.count = (string, substr) ->
   num = pos = 0
-  num++ while pos = 1 + string.indexOf letter, pos
+  return 1/0 unless substr.length
+  num++ while pos = 1 + string.indexOf substr, pos
   num

 # Merge objects, returning a fresh copy with attributes from both sides.


Optimize by reusing sync state object across requests

--- lib/session.coffee
+++ lib/session.coffee
@@ -341,6 +341,10 @@
   if sessionData.rpcRequest?
     return callback(null, generateRpcId(sessionData), sessionData.rpcRequest)

+  if sessionData.syncState? and (rpcReq = generateRpcRequest(sessionData))?
+    sessionData.rpcRequest = rpcReq
+    return callback(null, generateRpcId(sessionData), rpcReq)
+
   revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1

   v.revision = revision for k, v of sessionData.deviceData.timestamps
@@ -427,6 +431,7 @@
     if not found
       path = syncState.paths.add(path.slice(0, -1))
       syncState.refreshGpn.set(path, syncState.refreshGpn.get(path) or 1)
+  syncState.refreshAttributes.exist.clear()

   iter = syncState.refreshAttributes.object.values()
@@ -443,11 +448,13 @@
     if not found
       path = syncState.paths.add(path.slice(0, -1))
       syncState.refreshGpn.set(path, syncState.refreshGpn.get(path) or 1)
+  syncState.refreshAttributes.object.clear()

   iter = syncState.refreshAttributes.writable.values()
   while (path = iter.next().value)
     path = syncState.paths.add(path.slice(0, -1))
     syncState.refreshGpn.set(path, syncState.refreshGpn.get(path) or 1)
+  syncState.refreshAttributes.writable.clear()

   iter = syncState.refreshGpn.keys()
   while (path = iter.next().value)
@@ -465,7 +472,7 @@

   # Delete instance
   syncState.instancesToDelete.forEach((instances, parent) ->
-    instances.forEach((instance) ->
+    instances.forEach((instance, index, thisArray) ->
       if rpcReq?
         completed = false
       else
@@ -473,12 +480,13 @@
           type: 'DeleteObject'
           objectName: instance.join('.') + '.'
         }
+        thisArray.splice(index, 1)
     )
   )

   # Create instance
   syncState.instancesToCreate.forEach((instances, parent) ->
-    instances.forEach((instance) ->
+    instances.forEach((instance, index, thisArray) ->
       if rpcReq?
         completed = false
       else
@@ -488,6 +496,7 @@
           next: 'getInstanceKeys'
           instanceValues: instance
         }
+        thisArray.splice(index, 1)
     )
   )

@@ -514,6 +523,7 @@
         parameterPath: pair[0].concat('').join('.')
         nextLevel: nextLevel
       }
+      syncState.refreshGpn.delete(pair[0])

   if syncState.refreshAttributes.value.size
     if rpcReq?
@@ -527,6 +537,7 @@
       while (path = iter.next().value) and
           parameterNames.length < TASK_PARAMETERS_BATCH_SIZE
         parameterNames.push(path)
+        syncState.refreshAttributes.value.delete(path)

       if syncState.refreshAttributes.value.size > parameterNames.length
         completed = false
@@ -556,6 +567,7 @@
         completed = false
         return
       parameterValues.push([k, val[0], val[1]])
+      syncState.setValues.delete(k)
   )

   if parameterValues.length and not rpcReq?


fixed bug in IE

fixed bug in IE

--- src/color.coffee
+++ src/color.coffee
@@ -196,6 +196,7 @@
         if hex.length == 4 or hex.length == 7
             hex = hex.substr(1)
         if hex.length == 3
+            hex = hex.split("")
             hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]
         u = parseInt(hex, 16)
         r = u >> 16


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -21,7 +21,7 @@
           parseInt(hexString.substr(0, 1), 16) * 0x11
           parseInt(hexString.substr(1, 1), 16) * 0x11
           parseInt(hexString.substr(2, 1), 16) * 0x11
-          if hexString.substr(3, 1).length then (parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0)
+          if hexString.substr(3, 1).length then (parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0
         ]
       else
         return [


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -41,10 +41,10 @@
   window.Color = (color) ->
     color ||= "rgba(0, 0, 0, 0)"

-    parsedColor = null
+    parsedColor = lookup[color] || parseHex(color) || parseRGB(color)
+
+    return unless parsedColor

-    parsedColor = lookup[color] || parseHex(color) || parseRGB(color)
-
     alpha = parsedColor[3]

     channels = [


Add help message

Add help message

--- src/csonc.coffee
+++ src/csonc.coffee
@@ -6,19 +6,24 @@
 module.exports = (argv=[]) ->
   options = optimist(argv)
   options.usage """
-    Usage: csonc [options] file
-           csonc [options] < input_file [> output_file]
+    Usage: csonc [options] cson_file --output json_file
+           csonc [options] < cson_file [> json_file]

     If no input file is specified then the CSON is read from standard in.

     If no output file is specified then the JSON is written to standard out.
   """
+  options.alias('h', 'help').describe('help', 'Print this help message')
   options.alias('r', 'root').boolean('root').describe('root', 'Require that the input file contain an object at the root.').default('root', false)
   options.alias('o', 'output').string('output').describe('output', 'File path to write the JSON output to.')

   {argv} = options
   [inputFile] = argv._
   inputFile = path.resolve(inputFile) if inputFile
+
+  if argv.help
+    options.showHelp()
+    return

   parseData = (data) ->
     try


PollModel.isActive(): consider if poll has closingAt

--- vue/src/shared/models/poll_model.coffee
+++ vue/src/shared/models/poll_model.coffee
@@ -285,7 +285,7 @@
     !!@details

   isActive: ->
-    !@discardedAt && !@closedAt?
+    !@discardedAt && @closingAt && !@closedAt?

   isClosed: ->
     @closedAt?


Update english help video

Update english help video

--- angular/core/services/user_help_service.coffee
+++ angular/core/services/user_help_service.coffee
@@ -14,7 +14,7 @@
     helpVideo: ->
       switch CurrentUser.locale
         when 'es', 'an', 'ca', 'gl' then "https://www.youtube.com/embed/BT9f0Nj0zB8"
-        else "https://www.youtube.com/embed/RonxhKSSlG8"
+        else "https://www.youtube.com/embed/CoYYNthNxOY"

     helpVideoUrl: ->
       $sce.trustAsResourceUrl(@helpVideo())


update

update

--- app/assets/javascripts/vote_products.js.coffee
+++ app/assets/javascripts/vote_products.js.coffee
@@ -21,7 +21,9 @@
     disableDblClickSelection: true,
     pickTime: true
   }
+
   $('.create-voting').on 'click', ->
+    $(window).scrollTop(0)
     ids = []
     $('[type="checkbox"]').each ->
       ids.push $(this).data('id') if $(this).is(':checked')


I think maybe get by socket is using identicality

--- src/index.coffee
+++ src/index.coffee
@@ -41,6 +41,11 @@
     nickPairs.push (idNickPairForClient client) for client in sClients
     {clients:sClients.length, idNickPairs:nickPairs}

+getSocketById = (sid) ->
+        clients = io.sockets.clients()
+        client = null
+        _.map(clients, (c) -> console.log c.id+" "+sid+" "+(c.id == sid)
+
 io.sockets.manager.settings.blacklist = []

 io.of('/teacher')
@@ -48,7 +53,7 @@
     socket.emit 'render', onlineData()

     socket.on 'edit', (data) ->
-        console.log data
+        getSocketById data.sid
         io.sockets.socket(data.sid).emit 'edit', data.text

 io.of('/student')


Add key for mechanics

Add key for mechanics

--- src/index.coffee
+++ src/index.coffee
@@ -7,7 +7,7 @@

 class Model

-  constructor: (@name,  @keysize=keygen.medium) ->
+  constructor: (@name) ->
     @doc_client = new aws.DynamoDB.DocumentClient()

   _request: (method, params, include_table=true) ->
@@ -20,6 +20,7 @@
           console.log "DynamoDB Error: #{err}"
           return reject(err)
         resolve result
+
   put: (item, condition) ->
     params = Item: item
     if condition?
@@ -35,23 +36,28 @@
     self = @
     @_request('batchWrite', params, false).then (results) ->
       Promise.resolve items
+
   insert: (item) ->
     item.identifier = keygen.url @key_size unless item.identifier?
     @put item, expression: 'identifier <> :id', values: ':id': item.identifier
+
   update: (key, update) ->
     params = Key: key, UpdateExpression: update.expression
     params.ExpressionAttributeNames = update.names
     params.ExpressionAttributeValues = update.values
     @_request('update', params).then (result) ->
       Promise.resolve result.Items
-  get: (key, params={}) ->
-    params.Key = key
+
+  get: (thing, params={}) ->
+    params.Key = @key_for thing
     @_request('get', params).then (result) ->
       Promise.resolve result.Item
-  delete: (key, params={}) ->
-    params = Key: key
+
+  delete: (thing, params={}) ->
+    params = Key: @key_for thing
     @_request('delete', params).then (result) ->
       Promise.resolve result.Item
+
   query: (params) ->
     self = @
     params.KeyConditionExpression ?= params.expression
@@ -59,6 +65,7 @@
     params.ExpressionAttributeValues ?= params.values
     @_request('query', params).then (result) ->
       Promise.resolve result.Items
+
   scan: (params={}) ->
     self = @
     params.FilterExpression ?= params.expression
@@ -66,16 +73,29 @@
     params.ExpressionAttributeValues ?= params.values
     @_request('scan', params).then (result) ->
       Promise.resolve result.Items
+
   for_keys: (keys) ->
     params = RequestItems: {}
     params.RequestItems[@name] = Keys: keys
     self = @
     @_request('batchGet', params, false).then (results) ->
       Promise.resolve results.Responses[self.name]
+
   for_id: (id) ->
     @get identifier: id
+
   for_ids: (ids) ->
     @for_keys (identifier: id for id in ids)
+
+  key_for: (item) ->
+    key = {}
+    key[@hash_key] = item[@hash_key]
+    key[@range_key] = item[@range_key] if @range_key?
+    key
+
+  hash_key: 'identifier'
+
+  range_key: undefined

   id_for: id_for


Removed console.log

Removed console.log

--- sample.coffee
+++ sample.coffee
@@ -157,7 +157,6 @@

    Template.gallery.rendered = () ->
       # This assigns a file drop zone to the "file table"
-      console.log "assigning client", $(".#{myData.root}DropZone"), this
       this.data.resumable.assignDrop $(".#{myData.root}DropZone")

    Template.fileControls.events


Improved handling of graphicsmagick errors

--- sample.coffee
+++ sample.coffee
@@ -135,9 +135,6 @@
    shortFilename = (w = 16) ->
       shorten this.filename, w

-   isImage = () ->
-      imageTypes[this.contentType]? and (this.length isnt 0)
-
    Template.top.helpers
       loginToken: () ->
          Meteor.userId()
@@ -175,8 +172,6 @@
       uploadProgress: () ->
          percent = Session.get "#{this._id}"

-      isImage: isImage
-
    Template.fileTable.events
       # Wire up the event to remove a file by clicking the `X`
       'click .del-file': (e, t) ->
@@ -195,20 +190,21 @@
          "#{this._id}"

       thumb: () ->
-         unless this?.metadata?.thumb
+         unless this.metadata?.thumbComplete
             null
          else
             "#{this.metadata.thumb}"

-      isImage: isImage
+      isImage: () ->
+         imageTypes[this.contentType]?

       shortFilename: shortFilename

       altMessage: () ->
-         if this.length isnt 0
+         if this.length is 0
+            "Uploading..."
+         else
             "Processing thumbnail..."
-         else
-            "Uploading..."

    Template.gallery.rendered = () ->
       # This assigns a file drop zone to the "file table"
@@ -468,7 +464,7 @@
                inputFileId: file._id
                outputFileId: outputFileId
             if jobId = job.delay(5000).retry({ wait: 20000, retries: 5 }).save()
-               myData.update({ _id: file._id }, { $set: { 'metadata._Job': jobId }})
+               myData.update({ _id: file._id }, { $set: { 'metadata._Job': jobId, 'metadata.thumb': outputFileId }})
                myData.update({ _id: outputFileId }, { $set: { 'metadata._Job': jobId, 'metadata.thumbOf': file._id }})
             else
                console.error "Error saving new job for file #{file._id}"
@@ -481,7 +477,8 @@
                job.cancel (err, res) ->
                   myData.remove
                      _id: job.data.outputFileId
-         thumb = myData.remove { _id: file.metadata.thumb }
+         if file.metadata?.thumb?
+            thumb = myData.remove { _id: file.metadata.thumb }

       # When a file's data changes, call the appropriate functions
       # for the removal of the old file and addition of the new.
@@ -513,36 +510,40 @@
                job.fail "Error running graphicsmagick: #{err}", { fatal: true }
                return cb()

-            outStream = myData.upsertStream { _id: job.data.outputFileId }, {}, (err, file) ->
-               if err
-                  job.fail "#{err}"
-               else
-                  job.progress 80, 100
-                  myData.update { _id: job.data.inputFileId }, { $set: { 'metadata.thumb': job.data.outputFileId } }
-                  job.done()
-               cb()
-            unless outStream
-               job.fail 'Output file not found'
-               cb null
+            inFile = myData.findOne { _id: job.data.inputFileId }

             inStream = myData.findOneStream { _id: job.data.inputFileId }
             unless inStream
                outStream.releaseLock()
                job.fail 'Input file not found'
-               cb null
+               return cb()

             job.progress 20, 100

-            gm(inStream)
+            gm(inStream, inFile.filename or '')
                .resize(150,150)
-               .stream('png')
-               .pipe(outStream, (err) ->
-                  console.warn 'Error running graphicsmagick:', err
-                  outStream.releaseLock()
-                  inStream.releaseLock()
-                  job.fail "Error running graphicsmagick: #{err}"
-                  cb()
-               )
+               .stream 'png', Meteor.bindEnvironment (err, stdout, stderr) ->
+                  stderr.pipe process.stderr
+                  if err
+                     job.fail "Error running graphicsmagick: #{err}"
+                     return cb()
+                  else
+                     outStream = myData.upsertStream { _id: job.data.outputFileId }, {}, (err, file) ->
+                        if err
+                           job.fail "#{err}"
+                        else if file.length is 0
+                           job.fail 'Empty output from graphicsmagick!'
+                        else
+                           job.progress 80, 100
+                           myData.update { _id: job.data.inputFileId }, { $set: 'metadata.thumbComplete': true }
+                           job.done()
+                        return cb()
+
+                     unless outStream
+                        job.fail 'Output file not found'
+                        return cb()
+
+                     stdout.pipe(outStream)

       workers = myJobs.processJobs 'makeThumb', { concurrency: 2, prefetch: 2, pollInterval: 1000000000 }, worker
       myJobs.find({ type: 'makeThumb', status: 'ready' })


Fix issues with correctly displaying contents of the job collection

--- sample.coffee
+++ sample.coffee
@@ -7,7 +7,7 @@
 # Both client and server

 # Default collection name is 'fs'
-myData = FileCollection('images', {
+myData = new FileCollection('images', {
    resumable: true,     # Enable the resumable.js compatible chunked file upload interface
    http: [
       { method: 'get', path: '/:_id', lookup: (params, query) -> return { _id: params._id }},
@@ -15,7 +15,7 @@
    ]}
 )

-myJobs = JobCollection 'queue', { idGeneration: 'MONGO' }
+myJobs = new JobCollection 'queue', { idGeneration: 'MONGO' }

 Router.configure
    layoutTemplate: 'master'
@@ -238,9 +238,7 @@
    Template.jobTable.helpers
       jobEntries: () ->
          # Reactively populate the table
-         cur = this.find({})
-         console.warn "job Entries #{cur.count()}", this
-         return cur
+         this.find({})

       numDepends: () ->
          this.depends?.length
@@ -376,13 +374,9 @@
          # This prevents a race condition on the client between Meteor.userId() and subscriptions to this publish
          # See: https://stackoverflow.com/questions/24445404/how-to-prevent-a-client-reactive-race-between-meteor-userid-and-a-subscription/24460877#24460877
          if this.userId is clientUserId
-            cursor = myJobs.find({ 'data.owner': this.userId })
-            console.warn "UserID matches #{cursor.count()}"
-            return cursor
-         else
-            cursor = myJobs.find({ invalid: true })
-            console.warn "UserID doesn't match #{cursor.count()}"
-            return cursor
+            return myJobs.find({ 'data.owner': this.userId })
+         else
+            return []

       # Only publish files owned by this userId, and ignore temp file chunks used by resumable
       Meteor.publish 'allData', (clientUserId) ->


FIxed whitespace typo

FIxed whitespace typo

--- sample.coffee
+++ sample.coffee
@@ -90,7 +90,7 @@

       shortFilename: (w = 16) ->
          if this.filename?.length
-         shorten this.filename, w
+            shorten this.filename, w
          else
             "(no filename)"


Add `overflow: scroll` to text sizing elem

Also remove references to `fixed`; as it is no longer used

--- src/label.coffee
+++ src/label.coffee
@@ -5,11 +5,9 @@
     @_font = { size: 10, family: 'monospace' }
     @_text = 'text goes here'
     @_alignment = 'center'
-    @fixed = true # Does not move with camera/parent objects

     super(args)

-    @fixed = args.fixed if args.hasOwnProperty('fixed')
     @font = args.font if args.hasOwnProperty('font')
     @text = args.text if args.hasOwnProperty('text')
     @alignment = args.alignment if args.hasOwnProperty('alignment') # allowed values: "left", "right", "center", "start", "end"
@@ -38,6 +36,7 @@
       element['id'] = 'text-dimensions'
       element.style['position'] = 'absolute'
       element.style['top'] = '-9999px'
+      element.style['overflow'] = 'scroll'
       document.body.appendChild(element)

     element.style['font'] = @font


error when using "New Session" for a remote connection

--- init-nw.coffee
+++ init-nw.coffee
@@ -144,8 +144,14 @@
   {execPath} = process; if D.mac then execPath = execPath.replace /(\/Contents\/).*$/, '$1MacOS/nwjs'
   D.rideConnect = -> spawn execPath, ['--no-spawn'], detached: true, stdio: ['ignore', 'ignore', 'ignore']; return
   D.rideNewSession = ->
-    env = {}; (for ek, ev of process.env then env[ek] = ev); env.DYALOG_IDE_INTERPRETER_EXE = D.lastSpawnedExe
-    spawn execPath, ['-s'], detached: true, stdio: ['ignore', 'ignore', 'ignore'], env: env
+    if D.lastSpawnedExe
+      env = {}; (for ek, ev of process.env then env[ek] = ev); env.DYALOG_IDE_INTERPRETER_EXE = D.lastSpawnedExe
+      spawn execPath, ['-s'], detached: true, stdio: ['ignore', 'ignore', 'ignore'], env: env
+    else
+      $.alert(
+        'The current session is remote.  To connect elsewhere or launch a local interpreter, please use "Connect..." instead.',
+        'Cannot Start New Session' # title
+      )
     return

   D.quit = -> gui.Window.get().close(); return


Stop sounds on player switch

Stop sounds on player switch

--- jarkle/client/webgl_synth.coffee
+++ jarkle/client/webgl_synth.coffee
@@ -25,27 +25,36 @@
     noteIndex = midiNoteNumber % NOTES.length
     return NOTES[noteIndex]

+  stopAll: ->
+    @synth.stopAll()
+    @webGLVis.stopAll()
+
+
   handleMidiMessage: (noteInfo) =>
     unless noteInfo.func != NOTE_ON_MIDI_NUMBER
       return
     noteLetter = @_midiNoteNumberToNoteLetter(noteInfo.note)
     switch noteLetter
       when 'C'
+        # Next player
+        @stopAll()
         nextPlayer = @playerManager.getNextActivePlayerId()
         if nextPlayer?
           Session.set 'infoMessage', nextPlayer.profile.userAgent
           @pubSub.trigger CURRENT_PLAYER, nextPlayer
         @currentPlayerId = nextPlayer
       when 'D'
+        # All players
         @currentPlayerId = null
         Session.set 'infoMessage', null
         @pubSub.trigger CURRENT_PLAYER, nextPlayer
       when 'E'
+        # No players (apart from masters)
         Session.set 'infoMessage', null
         @currentPlayerId = 'only masters'
         @pubSub.trigger CURRENT_PLAYER,
           _id: @currentPlayerId
       when 'F'
-        @synth.stopAll()
-        @webGLVis.stopAll()
+        # Clear sounds
+        @stopAll()


[refactor] Used FontAwesome rather than svgs for Alert types (4m)

--- app/assets/javascripts/houston/alerts/handlebars_helpers.coffee
+++ app/assets/javascripts/houston/alerts/handlebars_helpers.coffee
@@ -5,8 +5,7 @@

 Handlebars.registerHelper 'iconForAlert', (alertType)->
   icon = ICONS[alertType]
-  size = 24
-  ""
+  """"""

 Handlebars.registerHelper 'ifEql', (value1, value2, options)->
   if value1 == value2


Bug fix

Bug fix

--- app/assets/javascripts/oxalis/model/binary/ping_strategy.coffee
+++ app/assets/javascripts/oxalis/model/binary/ping_strategy.coffee
@@ -82,7 +82,7 @@
     zoomStepDiff = requestedZoomStep - zoomStep
     pullQueue = []

-    return unless zoomStepDiff <= @MAX_ZOOM_STEP_DIFF
+    return pullQueue unless zoomStepDiff <= @MAX_ZOOM_STEP_DIFF

     for plane in [0..2]
       [@u, @v, @w] = Dimensions.getIndices(plane)


Also transfer the font-family so the baseline matches that of the paragraph better /cc @EamonNerbonne

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -65,6 +65,7 @@
     @$el.css
       'line-height': style.lineHeight
       'font-size': style.fontSize
+      'font-family': style.fontFamily

     @$el.find('factlink-icon-button-bubble').css
       'background-color': targetColor


Stylistic order.

Stylistic order.

--- client/base.coffee
+++ client/base.coffee
@@ -164,6 +164,14 @@
           currentPublicationSlug: publicationSlug
         'publication'

+    '/t/:tagId/:tagSlug?':
+      as: 'tag'
+      to: (tagId, tagSlug) ->
+        setSession
+          currentTagId: tagId
+          currentTagSlug: tagSlug
+        'tag'
+
     '/u/:personSlug':
       as: 'profile'
       to: (personSlug) ->
@@ -184,14 +192,6 @@
         setSession()
         redirectAnnotationId annotationId
         'redirecting'
-
-    '/t/:tagId/:tagSlug?':
-      as: 'tag'
-      to: (tagId, tagSlug) ->
-        setSession
-          currentTagId: tagId
-          currentTagSlug: tagSlug
-        'tag'

     '/s/:searchQuery?':
       as: 'search'


change subscribed status for paypal based on lastPaymentDate

--- app/views/account/SubscriptionView.coco.coffee
+++ app/views/account/SubscriptionView.coco.coffee
@@ -314,6 +314,7 @@
             @nextPaymentDate = new Date(lastPayment.get('created'))
             @nextPaymentDate.setUTCMonth(@nextPaymentDate.getUTCMonth() + 1)
             @cost = "$#{(lastPayment.get('amount')/100).toFixed(2)}"
+            @subscribed = @nextPaymentDate > Date.now()
             render()
           else
             console.error("No subscription payments found!")


radius units should default to 'data'

radius units should default to 'data'

--- bokehjs/src/coffee/renderer/glyph/glyph.coffee
+++ bokehjs/src/coffee/renderer/glyph/glyph.coffee
@@ -282,7 +282,7 @@
     display_defaults: () ->
       return {
         level: 'glyph'
-        radius_units: 'screen'
+        radius_units: 'data'
         length_units: 'screen'
         angle_units: 'deg'
         start_angle_units: 'deg'


Debug info on auth

Debug info on auth

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -15,13 +15,16 @@
   # if the user is in the session storage, we're using it. if we have a flag
   # for auto signin then we're trying to sign in.
   autoSignIn: (path) ->
+    console.log 'autoSignIn'
     if user = sessionStorage.getItem('travis.user')
       @setData(user: JSON.parse(user))
     else if localStorage.getItem('travis.auto_signin')
+      console.log 'travis.auto_signin', localStorage.getItem('travis.auto_signin')
       @signIn()

   # try signing in, but check later in case we have a timeout
   signIn: () ->
+    console.log 'set state, signing-in'
     @set('state', 'signing-in')
     @trySignIn()
     Ember.run.later(this, @checkSignIn.bind(this), @timeout)
@@ -33,12 +36,14 @@
     @setData()

   trySignIn: ->
+    console.log 'trySignIn', "#{@endpoint}/auth/post_message?origin=#{@receivingEnd}"
     @iframe.attr('src', "#{@endpoint}/auth/post_message?origin=#{@receivingEnd}")

   checkSignIn: ->
     @forceSignIn() if @get('state') == 'signing-in'

   forceSignIn: ->
+    console.log 'forceSignIn'
     localStorage.setItem('travis.auto_signin', 'true')
     window.location = "#{@endpoint}/auth/handshake?redirect_uri=#{location}"

@@ -46,6 +51,7 @@
   setData: (data) ->
     data = JSON.parse(data) if typeof data == 'string'
     @storeToken(data.token) if data?.token
+    console.log 'setData', data.user if data?.user
     user = @storeUser(data.user) if data?.user
     @set('state', if user then 'signed-in' else 'signed-out')
     @set('user',  if user then user else undefined)


support for ipv6 command-line arg

support for ipv6 command-line arg

--- server.coffee
+++ server.coffee
@@ -118,9 +118,12 @@
 server = https.createServer(httpsOptions, app).listen (httpsPort = 8443),
   -> log "https server listening on :#{httpsPort}"

-[host, port] = opts.addr.split ':'
-port ?= 4502
-log "connecting to interpreter at #{host}:#{port}"
+if addrMatch = /^\[([0-9a-f:]+)\]:(\d+)$/i.exec opts.addr
+  host = addrMatch[1]; port = addrMatch[2]
+else
+  [host, port] = opts.addr.split ':'
+port or= 4502
+log "connecting to interpreter, host: #{host}, port: #{port}"
 client = net.connect {host, port}, -> log 'interpreter connected'

 toInterpreter = (s) ->


accumulate notes

accumulate notes

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -8,6 +8,13 @@
   fragmentParser: Components.classes['@mozilla.org/feed-unescapehtml;1'].getService(Components.interfaces.nsIScriptableUnescapeHTML)
   Cache: new loki('betterbibtex.db', {env: 'BROWSER'})
 }
+
+#HTMLParser = (aHTMLString) ->
+#  html = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null)
+#  body = document.createElementNS('http://www.w3.org/1999/xhtml', 'body')
+#  html.documentElement.appendChild body
+#  body.appendChild Components.classes['@mozilla.org/feed-unescapehtml;1'].getService(Components.interfaces.nsIScriptableUnescapeHTML).parseFragment(aHTMLString, false, null, body)
+#  body

 Zotero.BetterBibTeX.debug_off = ->


Provide an additional filter for super-admins to fetch not approved apps

--- client/app/Applications/Apps.kdapplication/AppController.coffee
+++ client/app/Applications/Apps.kdapplication/AppController.coffee
@@ -21,7 +21,7 @@
     @createFeed()

   createFeed:(view)->
-    appManager.tell 'Feeder', 'createContentFeedController', {
+    options =
       subItemClass          : AppsListItemView
       limitPerPage          : 10
       filter                :
@@ -32,21 +32,15 @@
         kodingAddOns        :
           title             : "Koding Add-ons"
           dataSource        : (selector, options, callback)=>
-            setTimeout =>
-              callback null,@dummy
-            ,200
+            callback 'Coming soon!'
         serverStacks        :
           title             : "Server Stacks"
           dataSource        : (selector, options, callback)=>
-            setTimeout =>
-              callback null,@dummy
-            ,200
+            callback 'Coming soon!'
         frameworks          :
           title             : "Frameworks"
           dataSource        : (selector, options, callback)=>
-            setTimeout =>
-              callback null,@dummy
-            ,200
+            callback 'Coming soon!'
       sort                  :
         'counts.followers'  :
           title             : "Most popular"
@@ -67,8 +61,14 @@
           html      : yes
           animate   : yes

-
-    }, (controller)=>
+    if KD.checkFlag 'super-admin'
+      options.filter.waitsForApprove =
+        title             : "New Apps"
+        dataSource        : (selector, options, callback)=>
+          selector.approved = no
+          bongo.api.JApp.someWithRelationship selector, options, callback
+
+    appManager.tell 'Feeder', 'createContentFeedController', options, (controller)=>
       # @getSingleton("kodingAppsController").fetchAppsFromDb (err, apps)=>
       #   log "Installed Apps:", apps
       for own name,listController of controller.resultsController.listControllers


don't need this

don't need this

--- lib/atom-ternjs-type-view.coffee
+++ lib/atom-ternjs-type-view.coffee
@@ -1,7 +1,5 @@
 class TypeView extends HTMLElement
-
-  @container: null
-
+
   createdCallback: ->
     @classList.add('atom-ternjs-type')
     @container = document.createElement('span')


Inline grim require

Inline grim require

--- lib/highlighted-area-view.coffee
+++ lib/highlighted-area-view.coffee
@@ -1,6 +1,5 @@
 {Range, CompositeDisposable, Emitter, MarkerLayer} = require 'atom'
 StatusBarView = require './status-bar-view'
-Grim = require 'grim'
 escapeRegExp = require './escape-reg-exp'

 module.exports =
@@ -27,10 +26,12 @@
     @statusBarTile = null

   onDidAddMarker: (callback) =>
+    Grim = require 'grim'
     Grim.deprecate("Please do not use. This method will be removed.")
     @emitter.on 'did-add-marker', callback

   onDidAddSelectedMarker: (callback) =>
+    Grim = require 'grim'
     Grim.deprecate("Please do not use. This method will be removed.")
     @emitter.on 'did-add-selected-marker', callback


Bind to finish event

Bind to finish event

--- lib/psd/image_exports/png.coffee
+++ lib/psd/image_exports/png.coffee
@@ -15,5 +15,5 @@
       @toPng().then (image) ->
         image
           .pack()
-          .on 'end', -> setTimeout(resolve, 1)
           .pipe(fs.createWriteStream(output))
+          .on 'finish', resolve


fall back to value if rendered is not present

--- lib/repliers/query_runner.coffee
+++ lib/repliers/query_runner.coffee
@@ -47,12 +47,15 @@

     shareUrl = "<#{query.share_url}>"

+    renderString = (d) ->
+      d.rendered || d.value
+
     renderField = (f, row) =>
       d = row[f.name]
       if d.drilldown_uri && (f.is_measure || f.measure)
-        "<#{@replyContext.looker.url}#{d.drilldown_uri}|#{d.rendered}>"
+        "<#{@replyContext.looker.url}#{d.drilldown_uri}|#{renderString(d)}>"
       else if d? && d.value != null
-        d.rendered
+        renderString(d)
       else
         "∅"


Fix the specs in nodejs.

Fix the specs in nodejs.


Remove unused requires in spec-helper

Remove unused requires in spec-helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -6,16 +6,14 @@
 _ = require 'underscore-plus'
 fs = require 'fs-plus'
 Grim = require 'grim'
-KeymapManager = require '../src/keymap-extensions'
+pathwatcher = require 'pathwatcher'

 {Point} = require 'text-buffer'
 Workspace = require '../src/workspace'
-ServiceHub = require 'service-hub'
 TextEditor = require '../src/text-editor'
 TextEditorElement = require '../src/text-editor-element'
 TokenizedBuffer = require '../src/tokenized-buffer'
 TextEditorComponent = require '../src/text-editor-component'
-pathwatcher = require 'pathwatcher'
 clipboard = require '../src/safe-clipboard'

 jasmineStyle = document.createElement('style')


to make test-spec runnable again in 1.25.0-beta2

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -6,7 +6,7 @@
 settings = require '../lib/settings'

 KeymapManager = atom.keymaps.constructor
-{normalizeKeystrokes} = require(atom.config.resourcePath + "/node_modules/atom-keymap/lib/helpers")
+{normalizeKeystrokes} = require(atom.getLoadSettings().resourcePath + "/node_modules/atom-keymap/lib/helpers")

 supportedModeClass = [
   'normal-mode'


neat #270

neat #270

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -1,7 +1,7 @@
 SPEC_ENSURE_LEVEL = 1
 # -------------------------
 _ = require 'underscore-plus'
-{Range, Point} = require 'atom'
+{Range, Point, Disposable} = require 'atom'
 {inspect} = require 'util'
 swrap = require '../lib/selection-wrapper'

@@ -34,6 +34,8 @@
   wrapper = document.createElement('div')
   wrapper.className = platform
   wrapper.appendChild(editorElement)
+  new Disposable ->
+    editorElement.parentNode.removeChild(editorElement)

 unmockPlatform = (editorElement) ->
   editorElement.parentNode.removeChild(editorElement)
@@ -348,8 +350,6 @@
   # options
   # - waitsForFinish
   keystroke: (keys, options={}) =>
-    # {element} = options
-    # console.log element
     if options.waitsForFinish
       finished = false
       @vimState.onDidFinishOperation -> finished = true
@@ -361,48 +361,26 @@
     # keys must be String or Array
     # Not support Object for keys to avoid ambiguity.
     target = @editorElement
-    mocked = null
-    keys = [keys] unless _.isArray(keys)
-
-    for k in keys
+    mockDisposable = null
+
+    for k in toArray(keys)
       if _.isString(k)
-        specialKeystroke = ['enter', 'escape', 'backspace']
-        if k.length is 1 or (' ' in k) or (k in specialKeystroke) or k.match(/ctrl-.*/)
-          # NEW style
-          # "ensure 'h h'" comes here
-          newKeystroke(k, target)
-        else
-          # OLD style
-          # "ensure 'hh'" comes here
-          if SPEC_ENSURE_LEVEL >= 1
-            throw new Error('OLD STYLE USED')
-            console.log "OLD STYLE USED"
-
-          _keystroke(k, {target})
+        newKeystroke(k, target)
       else
-        if SPEC_ENSURE_LEVEL >= 2
-          throw new Error('OLD STYLE USED')
-          console.log "OLD STYLE USED"
         switch
           when k.platform?
-            mockPlatform(target, k.platform)
-            mocked = true
-          when k.char?
-            chars =
-              # [FIXME] Cause insertText('escape'), useless.
-              if k.char in ['', 'escape']
-                toArray(k.char)
-              else
-                k.char.split('')
-            for c in chars
-              @vimState.input.editor.insertText(c)
+            mockDisposable = mockPlatform(target, k.platform)
+          when k.input?
+            if k.input is 'escape'
+              atom.commands.dispatch(@vimState.input.editorElement, 'core:cancel')
+            else
+              @vimState.input.editor.insertText(k.input)
           when k.search?
-            {editor, editorElement} = @vimState.searchInput
-            editor.insertText(k.search)
-            atom.commands.dispatch(editorElement, 'core:confirm')
-          when k.ctrl? then _keystroke(k.ctrl, {ctrl: true, target})
-          when k.cmd? then _keystroke(k.cmd, {meta: true, target})
-          when k.raw? then _keystroke(k.raw, {raw: true, target})
-    unmockPlatform(target) if mocked
-
-module.exports = {getVimState, getView, dispatch, TextData}
+            @vimState.searchInput.editor.insertText(k.search)
+            atom.commands.dispatch(@vimState.searchInput.editorElement, 'core:confirm')
+          else
+            newKeystroke(k, target)
+    mockDisposable?.dispose()
+    mockDisposable = null
+
+module.exports = {getVimState, getView, dispatch, TextData, mockPlatform}


:bug: for spec helper getVimState when specified file

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -29,7 +29,7 @@

   waitsForPromise ->
     if file
-      file = atom.project.resolvePath('sample.coffee')
+      file = atom.project.resolvePath(file)
     atom.workspace.open(file).then (e) ->
       editor = e


shareHref

shareHref

--- models/channel.coffee
+++ models/channel.coffee
@@ -5,6 +5,7 @@
 Block = require "./block.coffee"
 sd = require("sharify").data
 params = require 'query-params'
+mediator = require '../lib/mediator.coffee'

 module.exports = class Channel extends Block
   defaultOptions:
@@ -29,13 +30,13 @@
     "#{sd.APP_URL}/share/#{@get('share_link')}"

   generateShareLink: ->
-    mediator.publish 'sharelink:created'
+    mediator.trigger 'sharelink:created'

     $.post "#{sd.API_URL}/channels/#{@slugOrId()}/share", (response) =>
       @set 'share_link', response.share_link

   removeShareLink: ->
-    mediator.publish 'sharelink:removed'
+    mediator.trigger 'sharelink:removed'

     $.ajax
       type: "DELETE"


renamed __processSyncRegionTag to __processRegionTag since there is virtually no difference between the sync and async version

renamed __processSyncRegionTag to __processRegionTag since there is
virtually no difference between the sync and async version

--- meta-engine.coffee
+++ meta-engine.coffee
@@ -168,7 +168,7 @@
     return content

-  __processSyncRegionTag: (resourcePath, content)->
+  __processRegionTag: (resourcePath, content)->

     linebreakCharacter = @optionMap.linebreakCharacter

@@ -352,7 +352,7 @@

     if isolated

-      [ regionMap, content ] = @__processSyncRegionTag resourcePath, content
+      [ regionMap, content ] = @__processRegionTag resourcePath, content

       content = @__processSyncUseTag resourcePath, content, regionMap


Réduction de l'amplitude du mouvement des hoverpoules

--- js/game/entities/enemies/HoverPoule.coffee
+++ js/game/entities/enemies/HoverPoule.coffee
@@ -35,8 +35,10 @@
             @health = 100
             @destroyed = no

-            # t
-            @t = 0
+            # Vitesse et déplacement de l'hoverpoule
+            @t = Math.random() * 10
+            @waveAmplitude = 10
+            @waveVSpeed = 0.4

             # Hitbox
             @hitBox = new RectHitBox @, -5 * 3, -5 * 3, 10 * 3, 10 * 3
@@ -100,7 +102,8 @@
         # Appliquer le déplacement vertical
         updateVelocity: (dt) ->

-            @vel.y = Math.cos(@t * 4) * 50
+            @vel.y = Math.cos(@t * @waveVSpeed) * @waveAmplitude
+            @updateVelocityToKeepOnScreen()

             # Faire tomber
             if @destroyed


:memo:

:memo:

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -3,13 +3,16 @@
     showOnStartup: false
   core:
     autoHideMenuBar: true
+    disabledPackages: [
+      "metrics"
+    ]
   editor:
     invisibles:
       eol: " "
     fontFamily: "Hasklig"
     softWrap: true
     showInvisibles: true
-    fontSize: 13
+    zoomFontWhenCtrlScrolling: false
   linter:
     errorPanelHeight: 263
   "linter-rubocop":
@@ -18,6 +21,3 @@
     coloured: false
   "markdown-preview":
     useGitHubStyle: true
-  "terminal-plus":
-    core: {}
-    style: {}


Prevent getOwner() returning for input

Prevent getOwner() returning for input

--- src/AccountsUtil.coffee
+++ src/AccountsUtil.coffee
@@ -54,7 +54,10 @@

   hasOwner: (doc) -> doc[@AUTHOR_FIELD]?

-  getOwner: (doc) -> @resolveUser(doc[@AUTHOR_FIELD])
+  getOwner: (doc) ->
+    userId = doc[@AUTHOR_FIELD]
+    return unless userId?
+    @resolveUser(userId)

   isOwnerOrAdmin: (doc, user) -> @isOwner(doc, user) || @isAdmin(user)


Fix Jordans change:y bug that gets fired even when vertical is disabled

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -177,9 +177,8 @@
 		# See if horizontal/vertical was set and set the offset

 		point = @layer.point
-		point.x = @_layerStartPoint.x + (event.offset.x * (1 / @layer.canvasScaleX() * @layer.scale * @layer.scaleX))
-		point.y = @_layerStartPoint.y + (event.offset.y * (1 / @layer.canvasScaleY() * @layer.scale * @layer.scaleY))
-
+		point.x = @_layerStartPoint.x + (event.offset.x * (1 / @layer.canvasScaleX() * @layer.scale * @layer.scaleX)) if @horizontal
+		point.y = @_layerStartPoint.y + (event.offset.y * (1 / @layer.canvasScaleY() * @layer.scale * @layer.scaleY)) if @vertical

 		# Constraints and overdrag
 		point = @_constrainPosition(point, @_constraints, @overdragScale) if @_constraints
@@ -195,8 +194,8 @@

 		# Pixel align all moves
 		if @pixelAlign
-			point.x = parseInt(point.x)
-			point.y = parseInt(point.y)
+			point.x = parseInt(point.x) if @horizontal
+			point.y = parseInt(point.y) if @vertical

 		# Update the dragging status
 		if point.x isnt @_layerStartPoint.x or point.y isnt @_layerStartPoint.y
@@ -447,6 +446,8 @@

 	_onSimulationStop: (axis, state) =>

+		return if axis is "x" and @horizontal is false
+		return if axis is "y" and @vertical is false
 		return unless @_simulation

 		# Round the end position to whole pixels


Add logging to publish-build-task

Add logging to publish-build-task

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -119,6 +119,7 @@

 zipAssets = (buildDir, assets, callback) ->
   zip = (directory, sourcePath, assetName, callback) ->
+    grunt.log.ok("Zipping #{sourcePath} into #{assetName}")
     if process.platform is 'win32'
       sevenZipPath = if process.env.JANKY_SHA1? then "C:/psmodules/" else ""
       zipCommand = "#{sevenZipPath}7z.exe a -r \"#{assetName}\" \"#{sourcePath}\""
@@ -136,6 +137,7 @@
   async.parallel(tasks, callback)

 getAtomDraftRelease = (isPrerelease, branchName, callback) ->
+  grunt.log.ok("Obtaining GitHub draft release for #{branchName}")
   atomRepo = new GitHub({repo: repo, token})
   atomRepo.getReleases {prerelease: isPrerelease}, (error, releases=[]) ->
     if error?
@@ -154,12 +156,14 @@
             logError('Fetching draft release assets failed', error, assets)
             callback(error ? new Error(response.statusCode))
           else
+            grunt.log.ok("Using GitHub draft release #{firstDraft.name}")
             firstDraft.assets = assets
             callback(null, firstDraft)
       else
         createAtomDraftRelease(isPrerelease, branchName, callback)

 createAtomDraftRelease = (isPrerelease, branchName, callback) ->
+  grunt.log.ok("Creating GitHub draft release #{branchName}")
   {version} = require('../../package.json')
   options =
     uri: "https://api.github.com/repos/#{repo}/releases"
@@ -185,6 +189,7 @@
       callback(null, body)

 deleteRelease = (release) ->
+  grunt.log.ok("Deleting GitHub release #{release}")
   options =
     uri: release.url
     method: 'DELETE'
@@ -195,6 +200,7 @@
       logError('Deleting release failed', error, body)

 deleteExistingAssets = (release, assetNames, callback) ->
+  grunt.log.ok("Deleting #{assetNames.join(',')} from GitHub release #{release}")
   [callback, assetNames] = [assetNames, callback] if not callback?

   deleteAsset = (url, callback) ->
@@ -216,6 +222,7 @@

 uploadAssets = (release, buildDir, assets, callback) ->
   uploadToReleases = (release, assetName, assetPath, callback) ->
+    grunt.log.ok("Uploading #{assetName} to GitHub release #{release}")
     options =
       uri: release.upload_url.replace(/\{.*$/, "?name=#{assetName}")
       method: 'POST'
@@ -248,6 +255,7 @@
     s3 = new AWS.S3 s3Info

     key = "releases/#{release.tag_name}/#{assetName}"
+    grunt.log.ok("Uploading to S3 #{key}")
     uploadParams =
       Bucket: s3Bucket
       ACL: 'public-read'


build: mongo bump

build: mongo bump

+ we don't need fetchClient there

--- bongo-api-builder/build.coffee
+++ bongo-api-builder/build.coffee
@@ -9,25 +9,12 @@

 koding = new Bongo
   root        : __dirname
-  mongo       : KONFIG.mongo
+  mongo       : "mongodb://#{KONFIG.mongo}"
   models      : '../workers/social/lib/social/models'
   resourceName: 'bongo-api-builder'
   mq          : broker
-  fetchClient : (sessionToken, context, callback) ->
-    { JUser, JAccount } = koding.models
-    [callback, context] = [context, callback] unless callback
-    context             ?= group: 'koding'
-    callback            ?= ->
-    JUser.authenticateClient sessionToken, context, (err, account) ->
-      if err
-        koding.emit 'error', err
-      else if account instanceof JAccount
-        callback {sessionToken, context, connection:delegate:account}
-      else
-        console.log "this is not a proper account".red, { account }
-        koding.emit 'error', message: 'this is not a proper account'

 koding.describeApi (api)->
   source = "var REMOTE_API = #{ JSON.stringify api };"
   require('fs').writeFileSync argv.o, source, 'utf-8'
-  process.exit()+  process.exit()


code-quality

code-quality

--- lib/assets/CacheManifest.coffee
+++ lib/assets/CacheManifest.coffee
@@ -54,22 +54,23 @@
     @markDirty()

   @getter 'text', ->
-    unless "_text" of this
+    unless '_text' of this
       if @_parseTree
         getSectionText = (nodes) ->
           nodes.map((node) ->
-            if "comment" of node
-              "#" + node.comment
+            if 'comment' of node
+              '#' + node.comment
             else
-              node.tokens.join " "
-          ).join("\n") + "\n"
-        @_text = "CACHE MANIFEST\n"
+              node.tokens.join ' '
+          ).join('\n') + '\n'
+        @_text = 'CACHE MANIFEST\n'

         # The heading for the CACHE section can be omitted if it's the first
         # thing in the manifest, so put it first if there is one.
         @_text += getSectionText(@_parseTree.CACHE)  if @_parseTree.CACHE
         _.each @_parseTree, ((nodes, sectionName) ->
-          @_text += sectionName + ":\n" + getSectionText(nodes)  if sectionName isnt "CACHE" and nodes.length
+          if sectionName isnt 'CACHE' and nodes.length
+            @_text += "#{sectionName}:\n#{getSectionText(nodes)}"
         ), this
       else
         @_text = @_getTextFromRawSrc()
@@ -78,15 +79,16 @@
   findOutgoingRelationsInParseTree: ->
     outgoingRelations = []

-    # Traverse the sections in alphabetical order so the order of the relations is predictable
+    # Traverse the sections in alphabetical order so the order of the relations
+    # is predictable
     Object.keys(@parseTree).sort().forEach ((sectionName) ->
       nodes = @parseTree[sectionName]
       if sectionName isnt "NETWORK"
         nodes.forEach ((node) ->
           if node.tokens
             # In the CACHE section there's only one token per entry, in FALLBACK
-            # there's the online URL followed by the offline URL (the one we want).
-            # Just pick the last token as the url.
+            # there's the online URL followed by the offline URL (the one we
+            # want). Just pick the last token as the url....
             outgoingRelations.push new AssetGraph.CacheManifestEntry(
               from: this
               to:


Require All Item Components To Be Present

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -402,7 +402,7 @@
       # Suppress activation if the editorView has classes that match the suppression list
       if shouldActivate and editorView?.classList? and atom.config.get('autocomplete-plus.suppressActivationForEditorClasses')?.length
         for item in @suppressForClasses
-          shouldActivate = false if _.intersection(editorView.classList, item)?.length
+          shouldActivate = false if _.intersection(editorView.classList, item)?.length is item.length

     if shouldActivate
       @cancelHideSuggestionListRequest()


Improve index change detection

Improve index change detection

fs.watch() for directory is sometimes very slow. using
file directly significantly reduced delay overall.

--- lib/git-diff-staged-view.coffee
+++ lib/git-diff-staged-view.coffee
@@ -1,4 +1,5 @@
 fs = require 'fs'
+{join} = require 'path'
 {Directory, CompositeDisposable} = require 'atom'
 {repositoryForPath} = require './helpers'

@@ -49,13 +50,16 @@
         @status = pathStatus
         @scheduleUpdate()
       @scheduleUpdate()
-      # Watch the directory because index is recreated on every change.
-      # Otherwise we loose the watch after the first change.
-      @indexWatch = fs.watch @repository.path, (__, name)=>
-        # atom/git-utils Repository does not always detect index changes
-        # ... let others know (e.g. tree-view)
-        @repository?.refreshStatus()
-        @scheduleUpdate() if name is 'index'
+      @addIndexWatch()
+
+  addIndexWatch: ->
+    @indexWatch?.close()
+    @indexWatch = fs.watch join(@repository.path, 'index'), =>
+      # atom/git-utils Repository does not always detect index changes
+      # ... let others know (e.g. tree-view)
+      @repository?.refreshStatus()
+      @scheduleUpdate()
+      @addIndexWatch()

   cancelUpdate: ->
     clearTimeout(@timeoutId)


Added check for whether a project is enabled. Made isPublic() and isEnabled() optional.

--- src/ProjectUtils.coffee
+++ src/ProjectUtils.coffee
@@ -153,9 +153,11 @@
       throw new Meteor.Error(500, 'No project specified when subscribing.')
     unless userId?
       throw new Meteor.Error(403, 'No user provided')
+    if Projects.isEnabled?(projectId) == false
+      throw new Meteor.Error(403, 'Project is disabled')
     unless (AccountsUtil.isOwner(Projects.findOne(projectId), userId) ||
         AccountsUtil.isAdmin(userId) ||
-        Projects.isPublic(projectId))
+        Projects.isPublic?(projectId))
       throw new Meteor.Error(403, 'User ' + userId +
           ' not authorized to view collections of project ' + projectId)


Fix #5

Fix #5

--- lib/ide-haskell-hasktags.coffee
+++ lib/ide-haskell-hasktags.coffee
@@ -26,9 +26,9 @@
     setTimeout (=> @tags = new Tags), 1000
     @disposables = new CompositeDisposable
     @disposables.add atom.commands.add 'atom-workspace',
-      'ide-haskell-hasktags:show-tags': =>
+      'ide-haskell-hasktags:show-tags': ({target}) =>
         return unless @tags?
-        @showList @tags.listTags()
+        @showList target.getModel(), @tags.listTags()
       'ide-haskell-hasktags:go-back': =>
         if (prevpos = @stack.pop())?
           atom.workspace.open prevpos.uri,
@@ -37,7 +37,7 @@
     @disposables.add atom.commands.add 'atom-text-editor',
       'ide-haskell-hasktags:show-file-tags': ({target}) =>
         return unless @tags?
-        @showList @tags.listTags(target.getModel().getURI())
+        @showList target.getModel(), @tags.listTags(target.getModel().getURI())
     @disposables.add atom.contextMenu.add
       'atom-text-editor[data-grammar~="haskell"]': [
           label: 'Show File Tags'


Actual, you know, working auto update logic

--- lib/models/learn-updater.coffee
+++ lib/models/learn-updater.coffee
@@ -59,6 +59,9 @@
   noCheckToday: =>
     checkDate = atom.blobStore.get('learnUpdateCheckDate', 'learn-update-key')

+    if checkDate
+      checkDate = parseInt(checkDate.toString())
+
     if !checkDate || (checkDate && Date.now() - checkDate >= 86400)
       true
     else


Fix log of language validation

Fix log of language validation

--- server/startup/i18n-validation.coffee
+++ server/startup/i18n-validation.coffee
@@ -29,5 +29,5 @@

 		console.log s.rpad('', len + 4, '=').red
 		for error in errors
-			console.log "| #{error} |".red
+			console.log "|".red, s.rpad("#{error}", len).red,  "|".red
 		console.log s.rpad('', len + 4, '=').red


Sketch out new project editing pages

Sketch out new project editing pages

--- app/pages/edit-project.cjsx
+++ app/pages/edit-project.cjsx
@@ -82,6 +82,127 @@
   displayName: 'EditProjectPageWrapper'

   render: ->
+

+

+

+
Project details

+
Science case

+
Results

+
Collaborators

+

+            Workflows
+

+
Get started

+
<button type="button">Create a new workflow</button>

+

+

+

+            Subject sets
+

+
Get started expert subjects

+
Get started initial subjects

+
<button type="button">Create a new subject set</button>

+

+

+

+

+

+

+

+

+              Avatar <button type="button">×</button>

+

+              <input type="file" />
+

+

+              Background image <button type="button">×</button>

+

+              <input type="file" />
+

+

+

+

+              Name

+              <input type="text" placeholder="Project name" />
+

+

+              Description

+              <textarea />
+

+

+              Introduction

+              <textarea />
+

+

+

+

+

+

+          Science Case

+          <textarea />
+

+

+

+

+          Results

+          <textarea />
+

+

+

+

+          Collaborators

+

+

+              User name
+              <input type="checkbox" /> Admin
+              <input type="checkbox" /> Scientist
+              <input type="checkbox" /> Moderator
+              <input type="checkbox" /> Translator
+              <button type="button">×</button>
+

+

+

+            <input type="text" /> <button type="button">Add collaborator</button>
+

+

+

+

+

+

+

+              Name

+              <input type="text" placeholder="Workflow name" />
+

+

+              (Retirement rules editor)
+

+

+              Associated subject sets
+

+

+                  <input type="checkbox" checked />
+                  Subject set display name
+

+

+

+

+

+            Tasks

+            (Workflow tasks editor)
+

+

+

+

+

+          Name

+          <input type="text" placeholder="Subject set name" />

+          (Subject set editor)

+          <input type="file" />
+

+

+

+
+  Xrender: ->
     <ChangeListener target={auth}>{=>
       getProject = auth.checkCurrent().then =>
         apiClient.type('projects').get(@props.params.id).then (project) ->


Some irc services (like anope) doesn't have the dot in "Password accepted" notice

--- src/adapters/irc.coffee
+++ src/adapters/irc.coffee
@@ -60,7 +60,7 @@
       bot.addListener 'notice', (from, to, text) ->
         if from is 'NickServ' and text.indexOf('registered') isnt -1
           bot.say 'NickServ', "identify #{options.nickpass}"
-        else if options.nickpass and from is 'NickServ' and text.indexOf('Password accepted.') isnt -1
+        else if options.nickpass and from is 'NickServ' and text.indexOf('Password accepted') isnt -1
           for room in options.rooms
             @join room


Add comments for default correlationName

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -86,6 +86,8 @@
     { name, correlationName, region, transportOptions,
       username, environment, queryString } = options

+    # If no `correlationName` is defined assume this kite instance
+    # is a singleton kite instance and keep track of it with this keyword
     correlationName ?= "singleton"

     # If queryString provided try to split it first


Terminal: minor

Terminal: minor

--- client/Terminal/webtermappview.coffee
+++ client/Terminal/webtermappview.coffee
@@ -67,7 +67,7 @@

       , =>
         KD.mixpanel "Can't open Webterm", {vmName}
-        @setMessage "Couldn't connect to your VM, please try again later. close this ", no, yes
+        @setMessage "Couldn't connect to your VM, please try again later. close this", no, yes
       , 5000

   showApprovalModal: (remote, command)->


shortcuts: allow passing predicate to 'toCollection'

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -10,7 +10,7 @@
 events         = require 'events'

 STORAGE_NAME    = 'shortcuts'
-STORAGE_VERSION = '175.2'
+STORAGE_VERSION = '175.3'
 THROTTLE_WAIT   = 500

 module.exports =
@@ -281,13 +281,13 @@
   # be displayed along; but in fact they are not and should be separated
   # to avoid collisions.
   #
-  toJSON: ->
+  toJSON: (predicate) ->

     extended = {}

     repr  = _
       .reduce defaults, (acc, value, key) =>
-        models = @getJSON key
+        models = @getJSON key, predicate
         parent = value.extends

         if _.isString parent
@@ -297,7 +297,7 @@
             _key        : key
             title       : value.title
             description : value.description
-            models      : @getJSON key
+            models      : @getJSON key, predicate
         return acc
       , []

@@ -310,8 +310,8 @@

   # Convenience method that returns a json representation as Collection.
   #
-  toCollection: ->
-    new Collection @toJSON()
+  toCollection: (predicate) ->
+    new Collection @toJSON predicate

   # Convenience method that returns a _keyconfig#Collection's_ json representation


#3 добавлена функция копирования

#3 добавлена функция копирования

--- builder.coffee
+++ builder.coffee
@@ -5,6 +5,7 @@
 path         = require 'path'
 async        = require 'async'
 {deepExtend} = require './helpers'
+CopyFiles    = require './copy-files'

 {normalize, basename, dirname, extname, join, existsSync} = path

@@ -14,8 +15,15 @@
   console.log 'bundle', options
   for key, val of options when typeof val is 'object'
     builder.execConfig(key, val)
+
 copy = (builder, name, options) ->
   console.log 'copy', options
+  builder.lock()
+  cp = new CopyFiles options.source, options.destination,
+    replaceStrategy: 2
+    on_complete: () ->
+      builder.unlock()
+
 remove = (builder, name, options) ->
   console.log 'remove', options
 rollback = (builder, name, options) ->


removed old getRawLogs socket.io endpoint

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -314,11 +314,6 @@
 				AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
 					CompileManager.compile(project_id, user._id, opts, callback)

-			# This is deprecated and can be removed once all editors have had a chance to refresh
-			client.on 'getRawLogs', (callback)->
-				AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
-					CompileManager.getLogLines project_id, callback
-
 			client.on 'changeUsersPrivlageLevel', (user_id, newPrivalageLevel)->
 				AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
 					projectHandler.changeUsersPrivlageLevel project_id, user_id, newPrivalageLevel


DevTools: Set liveMode as true by default

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -5,8 +5,13 @@
   constructor:->
     super

-    @storage = KD.singletons.localStorageController.storage "DevTools"
-    @liveMode = @storage.getAt 'liveMode'
+    @storage   = KD.singletons.localStorageController.storage "DevTools"
+    @liveMode  = @storage.getValue 'liveMode'
+
+    unless @liveMode?
+     @liveMode = yes
+     @storage.setValue 'liveMode', @liveMode
+
     @_currentMode = 'home'

   getToggleLiveReloadMenuView: (item, menu)->