AceView: Prevent to race condition and check active pane for every usage

--- client/ace/lib/aceview.coffee
+++ client/ace/lib/aceview.coffee
@@ -175,8 +175,21 @@
             callback    : ->
               modal.destroy()

-  setActiveTabHandleClass: (cssClass)->
-    @getDelegate().tabView?.getActivePane().tabHandle.setClass cssClass
+
+  setActiveTabHandleClass: (cssClass) ->
+
+    { tabView } = @getDelegate()
+
+    return  unless tabView
+
+    activePane = tabView.getActivePane()
+
+    IDEEditorPane = require 'ide/workspace/panes/ideeditorpane'
+
+    return  unless activePane.view instanceof IDEEditorPane
+
+    activePane.tabHandle.setClass cssClass
+

   toggleFullscreen: ->
     mainView = kd.getSingleton 'mainView'


Admin: Integrations: Find targetPane using actions to show sub panes.

--- client/admin/lib/index.coffee
+++ client/admin/lib/index.coffee
@@ -77,7 +77,12 @@

     @mainView.ready =>
       @mainView.tabs.panes.forEach (pane) ->
-        if pane.getOption('slug') is section
+        paneAction = pane.getOption 'action'
+        paneSlug   = pane.getOption 'slug'
+
+        if action is paneAction
+          targetPane = pane
+        else if paneSlug is section
           targetPane = pane

       if   targetPane then @mainView.tabs.showPane targetPane


:lipstick:

:lipstick:

--- spec/app/text-buffer-spec.coffee
+++ spec/app/text-buffer-spec.coffee
@@ -858,7 +858,7 @@
       describe "when the current line has a line ending", ->
         it "uses the same line ending as the line where the text is inserted", ->
           buffer.setText("a\r\n")
-          buffer.insert([0,1], "hello\n1\n\n2")
+          buffer.insert([0, 1], "hello\n1\n\n2")
           expect(buffer.getText()).toBe "ahello\r\n1\r\n\r\n2\r\n"

       describe "when the current line has no line ending (because it's the last line of the buffer)", ->


wait_ussd ruleset_type #19

wait_ussd ruleset_type #19

--- static/coffee/flows/services.coffee
+++ static/coffee/flows/services.coffee
@@ -421,12 +421,13 @@

       @rulesets = [

-        { type: 'wait_message', name:'Wait for Response', verbose_name: 'Wait for response', split:'message response', filter:[TEXT,SURVEY,USSD] },
+        { type: 'wait_message', name:'Wait for Response', verbose_name: 'Wait for response', split:'message response', filter:[TEXT,SURVEY] },
+        { type: 'wait_ussd', name:'Wait for USSD Response', verbose_name: 'Wait for USSD response', split:'USSD response', filter:USSD },

         # voice flows only
         { type: 'wait_recording', name:'Get Recording', verbose_name: 'Wait for recording', filter:VOICE },
-        { type: 'wait_digit', name:'Get Menu Selection', verbose_name: 'Wait for menu selection', filter:[VOICE,USSD] },
-        { type: 'wait_digits', name:'Get Digits', verbose_name: 'Wait for multiple digits', split:'digits', filter:[VOICE,USSD] },
+        { type: 'wait_digit', name:'Get Menu Selection', verbose_name: 'Wait for menu selection', filter:VOICE },
+        { type: 'wait_digits', name:'Get Digits', verbose_name: 'Wait for multiple digits', split:'digits', filter:VOICE },

         # online flows
         { type: 'webhook', name:'Call Webhook', verbose_name: 'Call webhook', split:'webhook response', filter:[TEXT,VOICE,USSD] },
@@ -443,7 +444,7 @@
         # { type: 'pause', verbose_name: 'Pause the flow', ivr:true, text:true},
       ]

-      @supportsRules = ['wait_message', 'expression', 'flow_field', 'contact_field', 'wait_digits', 'form_field']
+      @supportsRules = ['wait_message', 'wait_ussd', 'expression', 'flow_field', 'contact_field', 'wait_digits', 'form_field']

       @operators = [
         { type:'contains_any', name:'Contains any', verbose_name:'has any of these words', operands: 1, localized:true }
@@ -617,7 +618,7 @@

     isPausingRuleset: (node) ->
       if not node?.actions
-        return node.ruleset_type in ['wait_message', 'wait_recording', 'wait_digit', 'wait_digits']
+        return node.ruleset_type in ['wait_ussd', 'wait_message', 'wait_recording', 'wait_digit', 'wait_digits']
       return false

     # check if a potential connection would result in an invalid loop


Stopping the sound if it already exists

Stopping the sound if it already exists

--- client/components/player.cjsx
+++ client/components/player.cjsx
@@ -36,6 +36,7 @@

   playAudio:  (audioUrl) =>
     console.log 'Play ', audioUrl
+    @sound.stop() if @sound
     @sound = new Howl
       urls: [audioUrl]
       volume: 1.0


:art: Move linterRegistry init and deactivate to beforeEach

--- spec/linter-registry-spec.coffee
+++ spec/linter-registry-spec.coffee
@@ -1,56 +1,42 @@
 describe 'linter-registry', ->
   LinterRegistry = require('../lib/linter-registry')
   EditorLinter = require('../lib/editor-linter')
+  linterRegistry = null
   beforeEach ->
     waitsForPromise ->
       atom.workspace.destroyActivePaneItem()
       atom.workspace.open('test.txt')
-
-  describe '::construct', ->
-    it 'expects no arguments', ->
-      linterRegistry = new LinterRegistry
-      expect(true).toBe(true)
-      linterRegistry.deactivate()
+    linterRegistry?.deactivate()
+    linterRegistry = new LinterRegistry

   describe '::addLinter', ->
     it 'adds error notification if linter is invalid', ->
-      linterRegistry = new LinterRegistry
       linterRegistry.addLinter({})
-      linterRegistry.deactivate()
       expect(atom.notifications.getNotifications().length).toBe(1)
     it 'pushes linter into registry when valid', ->
       linter = {grammarScopes: ['*'], lintOnFly: false, modifiesBuffer: false, scope: 'file', lint: -> }
-      linterRegistry = new LinterRegistry
       linterRegistry.addLinter(linter)
       expect(linterRegistry.linters.length).toBe(1)
-      linterRegistry.deactivate()

   describe '::hasLinter', ->
     it 'returns true if present', ->
       linter = {grammarScopes: ['*'], lintOnFly: false, modifiesBuffer: false, scope: 'file', lint: -> }
-      linterRegistry = new LinterRegistry
       linterRegistry.addLinter(linter)
       expect(linterRegistry.hasLinter(linter)).toBe(true)
-      linterRegistry.deactivate()
     it 'returns false if not', ->
       linter = {grammarScopes: ['*'], lintOnFly: false, modifiesBuffer: false, scope: 'file', lint: -> }
-      linterRegistry = new LinterRegistry
       expect(linterRegistry.hasLinter(linter)).toBe(false)
-      linterRegistry.deactivate()

   describe '::deleteLinter', ->
     it 'deletes the linter from registry', ->
       linter = {grammarScopes: ['*'], lintOnFly: false, modifiesBuffer: false, scope: 'file', lint: -> }
-      linterRegistry = new LinterRegistry
       linterRegistry.addLinter(linter)
       expect(linterRegistry.hasLinter(linter)).toBe(true)
       linterRegistry.deleteLinter(linter)
       expect(linterRegistry.hasLinter(linter)).toBe(false)
-      linterRegistry.deactivate()

   describe '::lint', ->
     it "doesn't lint if textEditor isn't active one", ->
-      linterRegistry = new LinterRegistry
       editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
       linter = {
         grammarScopes: ['*']
@@ -63,9 +49,7 @@
       waitsForPromise ->
         atom.workspace.open('test2.txt').then ->
           expect(linterRegistry.lint({onChange: false, editorLinter})).toBeUndefined()
-          linterRegistry.deactivate()
     it "doesn't lint if textEditor doesn't have a path", ->
-      linterRegistry = new LinterRegistry
       editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
       linter = {
         grammarScopes: ['*']
@@ -78,9 +62,7 @@
       waitsForPromise ->
         atom.workspace.open('someNonExistingFile.txt').then ->
           expect(linterRegistry.lint({onChange: false, editorLinter})).toBeUndefined()
-          linterRegistry.deactivate()
     it 'disallows two co-current lints of same type', ->
-      linterRegistry = new LinterRegistry
       editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
       linter = {
         grammarScopes: ['*']
@@ -92,7 +74,6 @@
       linterRegistry.addLinter(linter)
       expect(linterRegistry.lint({onChange: false, editorLinter})).toBeDefined()
       expect(linterRegistry.lint({onChange: false, editorLinter})).toBeUndefined()
-      linterRegistry.deactivate()

     describe 'buffer modifying linter', ->
       it 'triggers before other linters', ->
@@ -111,14 +92,12 @@
           scope: 'file'
           lint: -> last = 'bufferModifying'
         }
-        linterRegistry = new LinterRegistry
         editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
         linterRegistry.addLinter(normalLinter)
         linterRegistry.addLinter(bufferModifying)
         waitsForPromise ->
           linterRegistry.lint({onChange: false, editorLinter}).then ->
             expect(last).toBe('normal')
-            linterRegistry.deactivate()
       it 'runs in sequence', ->
         order = []
         first = {
@@ -142,7 +121,6 @@
           scope: 'file'
           lint: -> order.push('third')
         }
-        linterRegistry = new LinterRegistry
         editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
         linterRegistry.addLinter(first)
         linterRegistry.addLinter(second)
@@ -152,11 +130,9 @@
             expect(order[0]).toBe('first')
             expect(order[1]).toBe('second')
             expect(order[2]).toBe('third')
-            linterRegistry.deactivate()

   describe '::onDidUpdateMessages', ->
     it 'is triggered whenever messages change', ->
-      linterRegistry = new LinterRegistry
       editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
       linter = {
         grammarScopes: ['*']
@@ -173,4 +149,3 @@
         linterRegistry.lint({onChange: false, editorLinter}).then ->
           expect(info).toBeDefined()
           expect(info.messages.length).toBe(1)
-          linterRegistry.deactivate()


adjust specs for ms

adjust specs for ms

--- spec/mavenlink-model-spec.coffee
+++ spec/mavenlink-model-spec.coffee
@@ -16,13 +16,13 @@
       parsed = model.parse({id: 1})
       expect(parsed.id).toEqual(1)

-    it "parses ISO 8601 dates into epoch seconds", ->
+    it "parses ISO 8601 dates into date objects / milliseconds", ->
       parsed = model.parse({created_at: "2013-01-25T11:25:57-08:00"})
-      expect(parsed.created_at).toEqual(1359141957)
-
-    it "passes through dates in Ruby epoch seconds", ->
-      parsed = model.parse({created_at: 1359142047})
-      expect(parsed.created_at).toEqual(1359142047)
+      expect(parsed.created_at).toEqual(1359141957000)
+
+    it "passes through dates in milliseconds already", ->
+      parsed = model.parse({created_at: 1359142047000})
+      expect(parsed.created_at).toEqual(1359142047000)

   describe 'setLoaded', ->
     it "should set the values of @loaded", ->


Force colors override in tests

Force colors override in tests

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -75,15 +75,15 @@
         #{stylesheet}

         atom-text-editor atom-text-editor-minimap, atom-text-editor::shadow atom-text-editor-minimap {
-          background: rgba(255,0,0,0.3);
+          background: rgba(255,0,0,0.3) !important;
         }

         atom-text-editor atom-text-editor-minimap::shadow .minimap-scroll-indicator, atom-text-editor::shadow atom-text-editor-minimap::shadow .minimap-scroll-indicator {
-          background: rgba(0,0,255,0.3);
+          background: rgba(0,0,255,0.3) !important;
         }

         atom-text-editor atom-text-editor-minimap::shadow .minimap-visible-area, atom-text-editor::shadow atom-text-editor-minimap::shadow .minimap-visible-area {
-          background: rgba(0,255,0,0.3);
+          background: rgba(0,255,0,0.3) !important;
         }
       """


removing deprecated text format

removing deprecated text format

--- source/app/coffee/AppData.coffee
+++ source/app/coffee/AppData.coffee
@@ -157,13 +157,6 @@
             wordWrapWidth: @SUBMENU_PANNEL + @MENU_PANNEL + @PADDING

         # 2X
-        MENU_DESCRIPTION_SMALL:
-            font: ( 20 * @RATIO ) + 'px sofia_prolight',
-            fill: 'white'
-            align: 'left'
-            wordWrap: true
-            wordWrapWidth: @SUBMENU_PANNEL + @MENU_PANNEL
-        # 2X
         SETTINGS_TITLE:
             font: ( 40 * @RATIO ) + 'px sofia_prolight',
             fill: 'white'


More linting

More linting

--- spec/pandoc-citation-spec.coffee
+++ spec/pandoc-citation-spec.coffee
@@ -24,7 +24,7 @@
       "[@Goldberg2014 et al.; @Forbus2014; @Finnegan; @Felber; @"))
       .toBe true
     expect(pandoc.isPandocStyleCitation(
-      "[a claim supported by multiple empirical studies [@Elby2001; @Gauss1989; and also @"))
+      "[a claim supported by [@Elby2001; @Gauss1989; and also @"))
       .toBe true

   it "Recognizes when the user has NOT yet begun to type/edit a cite key", ->
@@ -50,7 +50,10 @@
     expect(yaml).toBe undefined
     expect(bibFile).toBe undefined

-  it "fails to find a bibfile if the YAML block exists but contains no `bibliography` field", ->
+  it """
+  fails to find a bibfile if the YAML block exists,
+  but contains no `bibliography` field
+  """, ->
     fileText = """
                ---
                author: "Alan Turing"


updated test case

updated test case

--- test/index.coffee
+++ test/index.coffee
@@ -151,7 +151,8 @@

         it 'should match with input strings', ->

-          expect('format 5').to.match(/format ([0-9]*)$/i)
+          expect('format 123').to.match(/format ([0-9]*)$/i)
+          expect('format abc').to.not.match(/format ([0-9]*)$/i)

       describe '!map', ->


Fix #213: Suppress logic bug

Fix #213: Suppress logic bug

For example, if you press 'x' to close the current tab, it will close
before keyup fires. So keyup (and perhaps keypress) will fire in another
tab. Even if that particular tab is blacklisted, we must suppress the
event, so that 'x' isn't sent to the page. The rule is simple: If the
`suppress` flag is `true`, the event should be suppressed, no matter what.
It has the highest priority.

Previously, `vim.blacklisted` checks were performed even in keypress and
keyup, which caused the suppress to be ignored.

--- extension/packages/events.coffee
+++ extension/packages/events.coffee
@@ -29,26 +29,10 @@
       when 'popuphidden' then false

 suppress = false
-keyListener = (event) ->
-  try
-    return if passthrough or getPref('disabled')
-    return unless window = utils.getEventCurrentTabWindow(event)
-    return unless vim = vimBucket.get(window)
-    return if vim.blacklisted
-
-    if event.type == 'keydown'
-      suppress = false
-
-      return unless keyStr = keyStrFromEvent(event)
-
-      suppress = vim.onInput(keyStr, event)
-
-    if suppress
-      event.preventDefault()
-      event.stopPropagation()
-
-  catch error
-    console.log("#{ error } (in #{ event.type })\n#{ error.stack.replace(/@.+-> /g, '@') }")
+suppressEvent = (event) ->
+  if suppress
+    event.preventDefault()
+    event.stopPropagation()

 removeVimFromTab = (tab, gBrowser) ->
   return unless browser = gBrowser.getBrowserForTab(tab)
@@ -60,9 +44,35 @@

 # The following listeners are installed on every top level Chrome window
 windowsListeners =
-  keydown:     keyListener
-  keypress:    keyListener
-  keyup:       keyListener
+  keydown: (event) ->
+    try
+      # No matter what, always reset the `suppress` flag, so we don't suppress more than intended.
+      suppress = false
+
+      return if passthrough or getPref('disabled')
+
+      return unless window = utils.getEventCurrentTabWindow(event)
+      return unless vim = vimBucket.get(window)
+
+      return if vim.blacklisted
+
+      return unless keyStr = keyStrFromEvent(event)
+      suppress = vim.onInput(keyStr, event)
+
+      suppressEvent(event)
+
+    catch error
+      console.error("#{ error }\n#{ error.stack.replace(/@.+-> /g, '@') }")
+
+  # Note that the below event listeners can suppress the event even in blacklisted sites. That's
+  # intentional. For example, if you press 'x' to close the current tab, it will close before keyup
+  # fires. So keyup (and perhaps keypress) will fire in another tab. Even if that particular tab is
+  # blacklisted, we must suppress the event, so that 'x' isn't sent to the page. The rule is simple:
+  # If the `suppress` flag is `true`, the event should be suppressed, no matter what. It has the
+  # highest priority.
+  keypress: suppressEvent
+  keyup:    suppressEvent
+
   popupshown:  checkPassthrough
   popuphidden: checkPassthrough


">" method for Number

">" method for Number

--- fizzylogo_populateMethods.coffee
+++ fizzylogo_populateMethods.coffee
@@ -971,6 +971,17 @@
         return FLBoolean.createNew false
     ,6

+  FLNumber.addMethod \
+    (flTokenize "> ( toCompare )"),
+    (context) ->
+      #yield
+      toCompare = context.tempVariablesDict[ValidIDfromString "toCompare"]
+      if @value > toCompare.value
+        return FLBoolean.createNew true
+      else
+        return FLBoolean.createNew false
+    ,6
+
   # mutating the number
   FLNumber.addMethod \
     (flTokenize "← ( valueToAssign )"),


added test

added test

--- test/yolo_test.coffee
+++ test/yolo_test.coffee
@@ -4,8 +4,8 @@

 expect = chai.expect

-describe 'Business Cat:', ->
-  businesscat_module = require('../src/businesscat')
+describe 'Yolo:', ->
+  businesscat_module = require('../src/yolo')

   beforeEach ->
     @robot =
@@ -16,15 +16,7 @@
       random: sinon.spy()
     @businesscat_module = businesscat_module(@robot)

-  describe 'summon a business cat', ->
+  describe 'you only live once', ->

     it 'should register a hear listener', ->
-      expect(@robot.hear).to.have.been.calledWith(/we should create a strategic partnership/)
-
-    # it 'should NOT register a hear listener', ->
-    #   expect(@robot.hear).not.to.have.been.calledWith(/cooper seem/)
-
-    # it 'should return an image url', ->
-    #   cb = @robot.respond.firstCall.args[1]
-    #   cb(@msg)
-    #   expect(@msg.send).to.have.been.calledWithMatch(/*.jpg/)
+      expect(@robot.hear).to.have.been.calledWith(/yolo/)


account: fix signature for glanceActivities, fix param hockey for fetchGroups

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -147,8 +147,8 @@
         setEmailPreferences:
           (signature Object, Function)
         glanceActivities: [
-          (signature Object, Function)
-          (signature Object, ObjectId, Function)
+          (signature Function)
+          (signature String, Function)
         ]
         fetchRole:
           (signature Function)
@@ -504,6 +504,7 @@

   fetchGroups: secure (client, options, callback)->
     [callback, options] = [options, callback]  unless callback
+    options       ?= {}
     JGroup        = require './group'
     {groupBy}     = require 'underscore'
     {delegate}    = client.connection


Groups: removed log line

Groups: removed log line

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1433,7 +1433,6 @@
           {_id           : $in: (rel.targetId for rel in rels)},
           {hostnameAlias : $in : vms}
         ]
-        console.log 'selector', selector
         JDomain.some selector, {}, (err, domains)->
           return callback err if err


datadog: add user defined tag variables and more flexible text support

--- workers/social/lib/social/models/datadog.coffee
+++ workers/social/lib/social/models/datadog.coffee
@@ -48,6 +48,14 @@
     return tags

+  parseText = (text, tags)->
+    updatedText = text
+    for tag, value of tags
+      updatedText = updatedText.replace "%#{tag}%", value
+
+    return updatedText
+
+
   @sendEvent = secure (client, data, callback = ->)->

     {connection:{delegate}} = client
@@ -55,18 +63,19 @@
     unless delegate.type is 'registered'
       return callback new KodingError "Not allowed"

-    {eventName, logs} = data
-
+    {eventName, logs, tags} = data
+    tags ?= {}
     ev = Events[eventName]

     unless ev
       return callback new KodingError "Unknown event name"

     {nickname} = delegate.profile
+    tags['nickname'] = nickname

     title = ev.title
-    text  = ev.text.replace '%nickname%', nickname
     tags  = tagReplace ev.tags, nickname
+    text = parseText ev.text, tags

     if logs?
       if logs.length < 400


Update conflict marker spec.

Update conflict marker spec.

--- spec/conflict-marker-spec.coffee
+++ spec/conflict-marker-spec.coffee
@@ -11,11 +11,18 @@
     for sv in m.coveringViews
       sv.detectDirty() if 'detectDirty' of sv

+  beforeEach ->
+    done = false
+
+    GitBridge.locateGitAnd (err) ->
+      throw err if err?
+      done = true
+
+    waitsFor -> done
+
   describe 'with a merge conflict', ->

     beforeEach ->
-      GitBridge._gitCommand = -> 'git'
-
       editorView = util.openPath("triple-2way-diff.txt")
       editorView.getFirstVisibleScreenRow = -> 0
       editorView.getLastVisibleScreenRow = -> 999
@@ -163,8 +170,6 @@
     [active] = []

     beforeEach ->
-      GitBridge._gitCommand = -> 'git'
-
       editorView = util.openPath("rebase-2way-diff.txt")
       editorView.getFirstVisibleScreenRow = -> 0
       editorView.getLastVisibleScreenRow = -> 999


Delete implementation-dependent test

Delete implementation-dependent test

Co-Authored-By: Nathan Sobo <2e8aa918660411855c6d44d5bb2da677aa033255@github.com>

--- spec/squirrel-update-spec.coffee
+++ spec/squirrel-update-spec.coffee
@@ -122,16 +122,3 @@

         it "still has desktop shortcut", ->
           expect(fs.existsSync(desktopShortcutPath)).toBe true
-
-  describe ".restartAtom", ->
-    it "quits the app and spawns a new one", ->
-      app = new EventEmitter()
-      app.quit = jasmine.createSpy('quit')
-
-      SquirrelUpdate.restartAtom(app)
-      expect(app.quit.callCount).toBe 1
-
-      expect(Spawner.spawn.callCount).toBe 0
-      app.emit('will-quit')
-      expect(Spawner.spawn.callCount).toBe 1
-      expect(path.basename(Spawner.spawn.argsForCall[0][0])).toBe 'atom.cmd'


Rescue when user not found during revoke

--- apps/badges/routes.coffee
+++ apps/badges/routes.coffee
@@ -137,39 +137,44 @@
       console.log("revoking badge #{req.params.badgeId} from #{username}")

       User.findOne {username:username}, (err, user) ->
-        badgeIndex = null
-        userBadges = user.badges
-        for i in [0...userBadges.length]
-          if userBadges[ i ]._id.toString() == req.params.badgeId.toString()
-            badgeIndex = i
-            break
-        if badgeIndex?
-          bid = userBadges[ badgeIndex ]._id.toString()
-          console.log("bid=#{bid}")
-          user.badges.splice badgeIndex, 1
-          user.save (err) ->
-            if err?
-              res.send JSON.stringify({revoked:false, message:err}),
-                'content-type': 'application/json'
-            else
-              BadgesToUsers.findOne {badgeId: bid}, (err, btu) ->
-                if err?
-                  res.send JSON.stringify({error: err})
-                else
-                  uindex = null
-                  for i in [0...btu.users]
-                    if btu.users[ i ].toString() == user._id.toString()
-                      uindex = i
-                      break
-                  btu.users.splice uindex, 1
-                  btu.save (err) ->
-                    next(err) if err
-                    res.send JSON.stringify({revoked:true}),
-                      'content-type': 'application/json'
-
+        if err? || user?
+          res.send JSON.stringify({revoked: false, message: "error revoking badge"}),
+            'content-type': 'application/json'
+          return
         else
-          res.send JSON.stringify({revoked:false, message:'User has not earned this badge'}),
-            'content-type': 'application/json'
+          badgeIndex = null
+          userBadges = user.badges
+          for i in [0...userBadges.length]
+            if userBadges[ i ]._id.toString() == req.params.badgeId.toString()
+              badgeIndex = i
+              break
+          if badgeIndex?
+            bid = userBadges[ badgeIndex ]._id.toString()
+            console.log("bid=#{bid}")
+            user.badges.splice badgeIndex, 1
+            user.save (err) ->
+              if err?
+                res.send JSON.stringify({revoked:false, message:err}),
+                  'content-type': 'application/json'
+              else
+                BadgesToUsers.findOne {badgeId: bid}, (err, btu) ->
+                  if err?
+                    res.send JSON.stringify({error: err})
+                  else
+                    uindex = null
+                    for i in [0...btu.users]
+                      if btu.users[ i ].toString() == user._id.toString()
+                        uindex = i
+                        break
+                    btu.users.splice uindex, 1
+                    btu.save (err) ->
+                      next(err) if err
+                      res.send JSON.stringify({revoked:true}),
+                        'content-type': 'application/json'
+
+          else
+            res.send JSON.stringify({revoked:false, message:'User has not earned this badge'}),
+              'content-type': 'application/json'

     app.post '/issue/:slug', (req, res, next) ->
       username = req.body.username


improves error handling while parsing EJSON in commit store

--- source/infrastructure/commit_store.coffee
+++ source/infrastructure/commit_store.coffee
@@ -64,7 +64,10 @@
     commits.forEach (commit) =>

       for event in commit.changes.events
-        event = EJSON.parse(event)
+        try
+          event = EJSON.parse(event)
+        catch error
+          throw new Error "while parsing commit\nevent:#{event}\nerror:#{error}"
         event.version = commit.version
         events.push event


Fix promise queue _setTimeout exception in progress()

--- app/src/utils/promise_queue.coffee
+++ app/src/utils/promise_queue.coffee
@@ -70,7 +70,7 @@
           @emit 'task:done', taskId
           @_taskDone(); return
         ).then( (args...) -> defer.resolve(args...); return
-        ).progress( (args...) ->
+        ).progress( (args...) =>
           clearTimeout(timer)
           timer = @_setTimeout(taskId, defer)
           defer.notify(args...); return


bug in _applyAhrefToSegments (className)

--- app/views/CNeditor/CNeditor.coffee
+++ app/views/CNeditor/CNeditor.coffee
@@ -841,23 +841,23 @@
         pop = @urlPopover
         segments = pop.segments

-        # in case of a link creation and the user validated an empty url, just
+        # 1- in case of a link creation and the user validated an empty url, just
         # cancel the link creation
         if pop.urlInput.value == '' && pop.isLinkCreation
             @_cancelUrlPopover()
             return true

-        # remove background of selection and hide popover
+        # 2- remove background of selection and hide popover
         pop.style.display = 'none'
         seg.style.removeProperty('background-color') for seg in segments

-        # in case of a link creation, addhistory has already be done, it must
+        # 3- in case of a link creation, addhistory has already be done, it must
         # then be done if non a link creation.
         if !pop.isLinkCreation
             @currentSel.sel.setSingleRange pop.initialSelRg # otherwise addhistory will not work
             @_addHistory()

-        # case of a deletion of the urlInput value => 'remove the link'
+        # 4- case of a deletion of the urlInput value => 'remove the link'
         if pop.urlInput.value == ''
             # bps = @_applyMetaData(pop.initialSelRg, false, 'A', [])
             l = segments.length
@@ -865,7 +865,7 @@
                 cont : segments[0].firstChild
                 offset : 0
             bp2 =
-                cont : segments[l-1].firstChild
+                cont   : segments[l-1].firstChild
                 offset : segments[l-1].firstChild.length
             bps = [bp1,bp2]
             @_applyAhrefToSegments(segments[0], segments[l-1], bps, false, 'A', '')
@@ -884,12 +884,13 @@
             @setFocus()
             return true

-        # case if only href is changed but not the text
+        # 5- case if only href is changed but not the text
         else if pop.initialTxt == pop.textInput.value
             seg.href = pop.urlInput.value for seg in segments
             lastSeg = seg

-        # case if the text of the link is modified : we concatenate all segments
+        # 6- case if the text of the link is modified : we concatenate
+        # all segments
         else
             seg = segments[0]
             seg.href = pop.urlInput.value
@@ -900,9 +901,10 @@
                 parent.removeChild(seg)
             lastSeg = segments[0]

-        # manage selection
+        # 7- manage selection
         @_setCaret(lastSeg.firstChild, lastSeg.firstChild.length)
         @setFocus()
+

     _initUrlPopover : () ->
         frag = document.createDocumentFragment()
@@ -1273,7 +1275,7 @@
                     a = document.createElement('A')
                     a.href = href
                     a.textContent = segment.textContent
-                    a.classeName = segment.classeName
+                    a.className = segment.className
                     for bp in bps
                         if bp.cont.parentNode == segment
                             bp.cont = a.firstChild
@@ -1282,7 +1284,7 @@
             else
                     span = document.createElement('SPAN')
                     span.textContent = segment.textContent
-                    span.classeName = segment.classeName
+                    span.className = segment.className
                     for bp in bps
                         if bp.cont.parentNode == segment
                             bp.cont = span.firstChild


Fixed scaling in the thang type editor.

Fixed scaling in the thang type editor.

--- app/views/editor/thang/edit.coffee
+++ app/views/editor/thang/edit.coffee
@@ -221,8 +221,8 @@
       @showMovieClip(animationName)
     else
       @showSprite(animationName)
-    @updateScale()
     @updateRotation()
+    @updateScale() # must happen after update rotation, because updateRotation calls the sprite update() method.

   showMovieClip: (animationName) ->
     vectorParser = new SpriteBuilder(@thangType)
@@ -279,11 +279,12 @@
       @currentSprite.update(true)

   updateScale: =>
-    value = (@scaleSlider.slider('value') + 1) / 10
-    fixed = value.toFixed(1)
-    @scale = value
+    resValue = (@resolutionSlider.slider('value') + 1) / 10
+    scaleValue = (@scaleSlider.slider('value') + 1) / 10
+    fixed = scaleValue.toFixed(1)
+    @scale = scaleValue
     @$el.find('.scale-label').text " #{fixed}x "
-    @currentObject.scaleX = @currentObject.scaleY = value if @currentObject?
+    @currentObject.scaleX = @currentObject.scaleY = scaleValue / resValue if @currentObject?
     @updateGrid()
     @updateDots()


Make remote.getCurrentWindow work for devtools.

--- atom/browser/lib/rpc-server.coffee
+++ atom/browser/lib/rpc-server.coffee
@@ -98,6 +98,7 @@
   try
     BrowserWindow = require 'browser-window'
     window = BrowserWindow.fromProcessIdAndRoutingId processId, routingId
+    window = BrowserWindow.fromDevTools processId, routingId unless window?
     event.returnValue = valueToMeta processId, routingId, window
   catch e
     event.returnValue = errorToMeta e


Fixed repetion and unmapped command

Fixed repetion and unmapped command

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -164,9 +164,6 @@
   "g0": "firstTab"
   "g$": "lastTab"

-  "Ml": "moveTabLeft"
-  "Mr": "moveTabRight"
-
   "W": "moveTabToNewWindow"
   "t": "createTab"
   "yt": "duplicateTab"
@@ -253,8 +250,8 @@
   moveTabToNewWindow: ["Move tab to new window", { background: true }]
   togglePinTab: ["Pin/unpin current tab", { background: true }]

-  moveTabLeft: ["Move tab to the left", { background: true }]
-  moveTabRight: ["Move tab to the right", { background: true }]
+  moveTabLeft: ["Move tab to the left", { background: true, passCountToFunction: true }]
+  moveTabRight: ["Move tab to the right", { background: true, passCountToFunction: true  }]

   "Vomnibar.activate": ["Open URL, bookmark, or history entry"]
   "Vomnibar.activateInNewTab": ["Open URL, bookmark, history entry, in a new tab"]


snap: don't propogate snapshot error

snap: don't propogate snapshot error

--- salvus/snap.coffee
+++ salvus/snap.coffee
@@ -576,7 +576,10 @@

         ], (err) ->
-            cb?(err)
+            #cb?(err)
+            # Don't report an error for now, in order to ensure all other snapshots are made and system goes.
+            # We will see errors in the log, etc.
+            cb?()
             setTimeout(monitor_snapshot_queue, 50)
         )
     else


Update tags with decorations at the end of a line

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -523,6 +523,10 @@
       if spatialDecoration?
         containingTags.splice(containingTags.indexOf(spatialDecoration), 1)

+      while comparePoints(decorationIterator.getPosition(), spatialTokenBufferEnd) is 0
+        @updateTags(closeTags, openTags, containingTags, decorationIterator.getCloseTags(), decorationIterator.getOpenTags())
+        decorationIterator.moveToSuccessor()
+
       screenLines.push({id: @screenLineIterator.getId(), tokens})
       break unless @screenLineIterator.moveToSuccessor()
     screenLines


display errors for sign up

display errors for sign up

--- src/frontend/home.coffee
+++ src/frontend/home.coffee
@@ -20,6 +20,8 @@
     switch erroron
         when "login_email"      then jqElem = ($ "input#login_email")
         when "login_password"   then jqElem = ($ "input#login_password")
+        when "signup_password"  then jqElem = ($ "input#signup_password")
+        when "signup_email"     then jqElem = ($ "input#signup_email")
     jqElem.addClass 'error'
     jqErrorElem  = ($ '<div>', { id : "errorMsg" }).html error
     jqErrorElem.css 'position', 'absolute'
@@ -130,14 +132,14 @@
         mail    = do ((do @_jqForm.get).find 'input#signup_email').val
         passwd  = do ((do @_jqForm.get).find 'input#signup_password').val
         confirm = do ((do @_jqForm.get).find 'input#signup_password_confirm').val
-        err     = (if not mail then "Missing field : Email<br />" else "")
-        if not passwd or not confirm or confirm isnt passwd
+        if not mail
+            do event.preventDefault
+            DisplayError 'signup_email', 'Invalid email'
+        else if not passwd or not confirm or confirm isnt passwd
+            do event.preventDefault
             ((do @_jqForm.get).find 'input#signup_password').addClass 'error'
             ((do @_jqForm.get).find 'input#signup_password_confirm').addClass 'error'
-            err = "ee"
-        if err
-            do event.preventDefault
-            ($ '#msg').html err
+            DisplayError 'signup_password', 'Invalid password'

 class Choice
     constructor  : (@name, formType = ChoiceForm) ->


fix tests to work with newer ngAnimateMock

--- test/loading-bar-interceptor.coffee
+++ test/loading-bar-interceptor.coffee
@@ -9,21 +9,22 @@

 describe 'loadingBarInterceptor Service', ->

-  $http = $httpBackend = $document = $timeout = result = loadingBar = null
+  $http = $httpBackend = $document = $timeout = result = loadingBar = $animate = null
   response = {message:'OK'}
   endpoint = '/service'

   beforeEach ->
-    module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
+    module 'ngAnimateMock', 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
       loadingBar = cfpLoadingBarProvider
       return

     result = null
-    inject (_$http_, _$httpBackend_, _$document_, _$timeout_) ->
+    inject (_$http_, _$httpBackend_, _$document_, _$timeout_, _$animate_) ->
       $http = _$http_
       $httpBackend = _$httpBackend_
       $document = _$document_
       $timeout = _$timeout_
+      $animate = _$animate_

   beforeEach ->
     this.addMatchers
@@ -53,6 +54,8 @@
     expect(cfpLoadingBar.status()).toBe 1
     cfpLoadingBar.complete() # set as complete
     $timeout.flush()
+    $animate.triggerCallbacks()
+

     $http.get(endpoint, cache: cache).then (data) ->
       result = data
@@ -75,6 +78,8 @@
     expect(cfpLoadingBar.status()).toBe 1
     cfpLoadingBar.complete() # set as complete
     $timeout.flush()
+    $animate.triggerCallbacks()
+

     $http.get(endpoint).then (data) ->
       result = data
@@ -96,6 +101,8 @@
     expect(cfpLoadingBar.status()).toBe 1
     cfpLoadingBar.complete() # set as complete
     $timeout.flush()
+    $animate.triggerCallbacks()
+

     $http.get(endpoint, cache: true).then (data) ->
       result = data
@@ -115,6 +122,7 @@
     $httpBackend.flush(1)
     expect(cfpLoadingBar.status()).toBe 1
     $timeout.flush()
+    $animate.triggerCallbacks()

     $httpBackend.expectPOST(endpoint).respond response
@@ -340,6 +348,8 @@
     expect(startedEventCalled).toBe 1 # Should still be one, as complete was never called:
     cfpLoadingBar.complete()
     $timeout.flush()
+    $animate.triggerCallbacks()
+

     cfpLoadingBar.start()
     expect(startedEventCalled).toBe 2
@@ -434,12 +444,12 @@
     cfpLoadingBar.start()
     $timeout.flush()
     $animate.triggerCallbacks()
-
+
     expect(isLoadingBarInjected($document.find(cfpLoadingBar.parentSelector))).toBe true

     cfpLoadingBar.complete()
     $timeout.flush()
     $animate.triggerCallbacks()
-
+
     expect(isLoadingBarInjected($document.find(cfpLoadingBar.parentSelector))).toBe false


Improve select action

Improve select action

--- lib/assets/javascripts/cmi/form_components/select.coffee
+++ lib/assets/javascripts/cmi/form_components/select.coffee
@@ -92,6 +92,7 @@
     target.attr('selected', true)

     @_setValues()
+    @getSelect().trigger 'change'

     CMI.FormComponents.TextField.reset(@getInput())

@@ -150,7 +151,7 @@

   # @nodoc
   _bindListeners: ->
-    @getList().on "click.cmiInput#{@getName()}", 'li', $.proxy(@onListClick, @)
+    @getList().on "mousedown.cmiInput#{@getName()}", 'li', $.proxy(@onListClick, @)

     @getInput().on "focus.cmiInput#{@getName()}", $.proxy(@onInputFocus, @)
     @getInput().on "blur.cmiInput#{@getName()}", $.proxy(@onInputBlur, @)
@@ -158,7 +159,7 @@

   # @nodoc
   _unbindListeners: ->
-    @getList().off "click.cmiInput#{@getName()}", 'li'
+    @getList().off "mousedown.cmiInput#{@getName()}", 'li'

     @getInput().off "focus.cmiInput#{@getName()}"
     @getInput().off "blur.cmiInput#{@getName()}"


app-settings-menu: Added an error notification when user try to compile an application from a file which is not related with an app.

--- client/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/app/Applications/Ace.kdapplication/AppView.coffee
@@ -75,6 +75,7 @@

   compileAndRun: ->
     manifest = KodingAppsController.getManifestFromPath @getData().path
+    return @ace.notify "Not found an app to compile", null, yes unless manifest?.name
     @ace.notify "Compiling...", null, yes
     @getSingleton('kodingAppsController').compileApp manifest.name, (err)=>
       if not err


Removed whole plan fetch logic

Removed whole plan fetch logic

--- client/app/lib/providers/machinesettingsspecsview.coffee
+++ client/app/lib/providers/machinesettingsspecsview.coffee
@@ -28,13 +28,6 @@
     @createIcon 'DISK',  disk
     @createIcon 'CORES', cpu

-    kd.singletons.paymentController.subscriptions (err, subscription) =>
-
-      if err
-        @createMoreView()
-        return showError err
-
-

   createIcon: (title, spec) ->


stacks: Using hyphens in var names, doesnt fails

--- client/stacks/lib/views/stacks/requirementsparser.coffee
+++ client/stacks/lib/views/stacks/requirementsparser.coffee
@@ -44,7 +44,7 @@

     # And this one for userInputs which will be asked to user when they
     # want to build their stacks which created from the stack template
-    /\$\{var\.(userInput)\_(\w+?)\}/g
+    /\$\{var\.(userInput)\_([A-Za-z0-9\-_]+)\}/g

     # This is for custom data which will be asked from the admin
     # These variables needed to provide in custom variables section


Make remove method fat-arrowed

Make remove method fat-arrowed

This makes sure that in every context `@options` links to the correct
object

--- js-library/src/js/views/notification.coffee
+++ js-library/src/js/views/notification.coffee
@@ -23,5 +23,5 @@
     @$el.fadeIn @options.fade_time, =>
       setTimeout(@remove, @options.in_screen_time)

-  remove: ->
+  remove: =>
     @$el.fadeOut(@options.fade_time, => @$el.remove())


#989 - Option ID is appended with _1 although not needed

--- jsapp/xlform_model_view/view.choices.coffee
+++ jsapp/xlform_model_view/view.choices.coffee
@@ -104,6 +104,8 @@
       @n = $('span', @c)
       $viewUtils.makeEditable @, @model, @n, edit_callback: (val) =>
         other_names = @options.cl.getNames()
+        if @model.get('name')? && val.toLowerCase() == @model.get('name').toLowerCase()
+          other_names.splice _.indexOf(other_names, @model.get('name')), 1
         if val is ''
           @model.unset('name')
           @model.set('setManually', false)


Allow other component protocol messages through

--- src/runtimes/base.coffee
+++ src/runtimes/base.coffee
@@ -45,12 +45,12 @@

   recvComponent: (command, payload) ->
     switch command
-      when 'component'
-        @emit 'component',
+      when 'error'
+        @emit 'network',
           command: command
           payload: payload
-      when 'error'
-        @emit 'network',
+      else
+        @emit 'component',
           command: command
           payload: payload


Updating decrecated bit.ly API apiKey in favor of OAuth token

--- src/scripts/bitly.coffee
+++ src/scripts/bitly.coffee
@@ -5,8 +5,7 @@
 #   None
 #
 # Configuration:
-#   HUBOT_BITLY_USERNAME
-#   HUBOT_BITLY_API_KEY
+#   HUBOT_BITLY_ACCESS_TOKEN
 #
 # Commands:
 #   hubot (bitly|shorten) (me) <url> - Shorten the URL using bit.ly
@@ -15,14 +14,14 @@
 # Author:
 #   sleekslush
 #   drdamour
+#   johnwyles

 module.exports = (robot) ->
   robot.respond /(bitly|shorten)\s?(me)?\s?(.+)$/i, (msg) ->
     msg
-      .http("http://api.bitly.com/v3/shorten")
+      .http("https://api-ssl.bitly.com/v3/shorten")
       .query
-        login: process.env.HUBOT_BITLY_USERNAME
-        apiKey: process.env.HUBOT_BITLY_API_KEY
+        access_token: process.env.HUBOT_BITLY_ACCESS_TOKEN
         longUrl: msg.match[3]
         format: "json"
       .get() (err, res, body) ->
@@ -32,10 +31,9 @@
    #TODO: can we make this list more expansive/dynamically generated?
    robot.hear /(https?:\/\/(bit\.ly|yhoo\.it|j\.mp|pep\.si|amzn\.to)\/[0-9a-z\-]+)/ig, (msg) ->
     msg
-      .http("http://api.bitly.com/v3/expand")
+      .http("https://api-ssl.bitly.com/v3/expand")
       .query
-        login: process.env.HUBOT_BITLY_USERNAME
-        apiKey: process.env.HUBOT_BITLY_API_KEY
+        access_token: process.env.HUBOT_BITLY_ACCESS_TOKEN
         shortUrl: msg.match
       .get() (err, res, body) ->
         parsedBody = JSON.parse body


doc change

doc change

--- src/scripts/oauth.coffee
+++ src/scripts/oauth.coffee
@@ -16,7 +16,7 @@
 #
 # Supported APIs / widgets by default: analytics, analytics2, twitter, facebook, linkedin

-# requires scribe-node module >= 0.0.23 which can be installed with: npm install scribe-node
+# requires scribe-node module >= 0.0.24 which can be installed with: npm install scribe-node
 # or describing dependencity on package.json file
 scribe = require('scribe-node').load(['OAuth'])
 # set custom service configurations if not available from scribe OAuth widget.


Make demoEl run _.once

Make demoEl run _.once

--- app/views/TestView.coffee
+++ app/views/TestView.coffee
@@ -88,7 +88,7 @@
     application.testing = true
     specFiles ?= @getAllSpecFiles()
     if demosOn
-      jasmine.demoEl = ($el) ->
+      jasmine.demoEl = _.once ($el) ->
         $('#demo-area').append($el)
       jasmine.demoModal = _.once (modal) ->
         currentView.openModalView(modal)


Fix for duplicate slug test in parallel as api sometimes fails to validate duplicate slugs.

--- test/integration.spec.coffee
+++ test/integration.spec.coffee
@@ -27,7 +27,7 @@
     Promise.resolve()

 ensureResource = (service, predicate, sampleData) ->
-  debug "Ensuring existence of #{service}..."
+  debug 'Ensuring existence for: %s', predicate
   service.where(predicate).fetch()
   .then (result) ->
     if result.statusCode is 200 and result.body.count is 0
@@ -171,17 +171,26 @@
     , 10000

     it ' :: should continue on error - duplicate slug', (done) ->
-      cleanup(@logger, @client)
-      .then =>
-        sampleImport = _.deepClone sampleImportJson
-        sampleImport.products[1].slug.en = 'product-sync-test-product-1'
-        @import._processBatches(sampleImport.products)
-        .then =>
-          expect(@import._summary.failed).toBe 1
-          expect(@import._summary.created).toBe 1
-          done()
-        .catch (err) ->
-          done(err)
+      # FIXME: looks like the API doesn't correctly validate for duplicate slugs
+      # for 2 concurrent requests (this happens randomly).
+      # For now we have to test it as 2 separate imports.
+      sampleImport = _.deepClone sampleImportJson
+      @import._processBatches([sampleImport.products[0]])
+      .then =>
+        expect(@import._summary.created).toBe
+        sampleImport2 = _.deepClone sampleImportJson
+        sampleImport2.products[1].slug.en = 'product-sync-test-product-1'
+        @import._resetSummary()
+        @import._processBatches([sampleImport2.products[1]])
+      .then =>
+        # import should fail because product 1 has same slug
+        expect(@import._summary.failed).toBe 1
+        expect(@import._summary.created).toBe 0
+        errorJson = require path.join(@import.errorDir,'error-1.json')
+        expect(errorJson.message).toEqual "A duplicate value '\"product-sync-test-product-1\"' exists for field 'slug'."
+        done()
+      .catch (err) ->
+        done(err)

     it ' :: should continue of error - missing product name', (done) ->
       cleanup(@logger, @client)


Updates the file snippet to add additional space

--- snippets/file.cson
+++ snippets/file.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe file':
     'prefix': 'file'
-    'body': "# http://inspec.io/docs/reference/resources/file/\ndescribe file('${1:/tmp/filename}') do\n  it { should${2:_not} ${3:exist} }\n  it { should be_owned_by '${4:user}'}\n  its('content') { should${5:_not} match /${6:content}/}${7}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/file/\ndescribe file('${1:/tmp/filename}') do\n  it { should${2:_not} ${3:exist} }\n  it { should be_owned_by '${4:user}'}\n  its('content') { should${5:_not} match /${6:content}/ }${7}\nend"


Updating port resource snippet

Updating port resource snippet

--- snippets/port.cson
+++ snippets/port.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe port':
     'prefix': 'port'
-    'body': "# http://inspec.io/docs/reference/resources/port/\ndescribe port('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/port/\ndescribe port(${1:number}) do\n  it { should${2:_not} be_listening }\n  its('protocols') { should{3:_not} include('${4:protocol}') }${5}\nend"


Fixed S3 test related to buffer not being filled on initialization

--- test/services/s3.spec.coffee
+++ test/services/s3.spec.coffee
@@ -294,7 +294,9 @@

       it 'will compute contentMD5', ->
         s3 = new AWS.S3(s3DisableBodySigning: true, signatureVersion: 'v4')
-        req = build('putObject', Bucket: 'bucket', Key: 'key', Body: new Buffer(1024*1024*5))
+        buf = new Buffer(1024*1024*5)
+        buf.fill(0)
+        req = build('putObject', Bucket: 'bucket', Key: 'key', Body: buf)
         expect(req.headers['Content-MD5']).to.equal('XzY+DlipXwbL6bvGYsXftg==')

       it 'will not disable body signing when the endpoint is not https', ->


Only set the rpi3 as the default device.

The `isDefault` attribute is used to set the default device when a user creates
a new application on the dashboard. The only device type that should have the
`isDefault` attribute set to true at this point is the rpi3.

--- raspberrypi3-64.coffee
+++ raspberrypi3-64.coffee
@@ -8,7 +8,6 @@
 	name: 'Raspberry Pi 3 64bits'
 	arch: 'aarch64'
 	state: 'experimental'
-	isDefault: true

 	instructions: commonImg.instructions
 	gettingStartedLink:


refactor(logging): extend serialization of errors

extend the serialization since it strips away all the valueable error info that the sdk added to the error object

--- src/coffee/helpers/extended-logger.coffee
+++ src/coffee/helpers/extended-logger.coffee
@@ -14,10 +14,12 @@
     @bunyanLogger = new Logger logConfig

   _serializeError: (e) ->
+    body: e.body
     message: e.message
     name: e.name
     stack: e.stack
     code: e.code
+    statusCode: e.statusCode
     signal: e.signal

   _wrapOptions: (type, opts, msg) ->


fixed display of task id on tracing view. fixes #1497

--- app/assets/javascripts/oxalis/view/viewmode/right-menu/dataset_info_view.coffee
+++ app/assets/javascripts/oxalis/view/viewmode/right-menu/dataset_info_view.coffee
@@ -54,9 +54,9 @@
     name = tracing.name

     # In case we have a task display its id as well
-    if task then annotationType += " - #{task.formattedHash}"
+    if task then annotationType += ": #{task.id}"
     # Or display an explorative tracings name if there is one
-    if name then annotationType += " - #{name}"
+    if name then annotationType += ": #{name}"

     return {
       annotationType : annotationType


Update metadata selection when a select is closed

--- app/assets/javascripts/rglossa/react/components/left/metadata_select.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/left/metadata_select.jsx.coffee
@@ -28,6 +28,7 @@
     node = @refs.hidden.getDOMNode()
     if @state.isActive
       @closeSelect(node)
+      @props.handleSelectedValuesChanged()
     else
       @openSelect(node)


Deleted unused mixin

Deleted unused mixin

--- core/app/backbone/views/tour/tour_users_list_view.coffee
+++ core/app/backbone/views/tour/tour_users_list_view.coffee
@@ -1,6 +1,4 @@
 class TourUserView extends Backbone.Marionette.Layout
-
-  _.extend @prototype, Backbone.Factlink.PopoverMixin

   template: 'tour/interesting_user'
   className: 'tour-interesting-user'


add method upgrade

add method upgrade

--- lib/client/javascripts/nali/model.js.coffee
+++ lib/client/javascripts/nali/model.js.coffee
@@ -34,7 +34,7 @@
         @views[ short = view._shortName ] = view
         shortCap = short.capitalize()
         unless @[ viewMethod = 'view' + shortCap ]? then @[ viewMethod ] = -> @view short
-        unless @[ showMethod = 'show' + shortCap ]? then @[ showMethod ] = -> @show short
+        unless @[ showMethod = 'show' + shortCap ]? then @[ showMethod ] = ( insertTo ) -> @show short, insertTo
         unless @[ hideMethod = 'hide' + shortCap ]? then @[ hideMethod ] = -> @hide short
     @

@@ -155,6 +155,11 @@
       @trigger "update.#{ name }"
       true
     else false
+
+  upgrade: ( attributes, success, failure ) ->
+    # обновляет атрибуты модели и сохраняет её на сервер
+    @update( attributes ).save success, failure
+    @

   destroy: ( success, failure ) ->
     # отправляет на сервер запрос на удаление модели, вызывает success в случае успеха и failure при неудаче


component's props may not have "close" method

--- coach/src/concept-coach/index.cjsx
+++ coach/src/concept-coach/index.cjsx
@@ -158,7 +158,7 @@
   updateToView: (view) ->
     if @component
       if view is 'close'
-        @component.props.close()
+        @component.props?.close?()
       else
         navigation.channel.emit("show.#{view}", {view})
     else if componentModel.mounter? and view isnt 'close'


remove trash

remove trash

--- lib/carte/client/views/edit.cjsx
+++ lib/carte/client/views/edit.cjsx
@@ -3,7 +3,6 @@
 React = require('react')
 Modal = require('react-bootstrap/lib/Modal')
 Button = require('react-bootstrap/lib/Button')
-Loader = require('react-loader')

 module.exports = React.createClass
   displayName: 'Edit'


remove trash

remove trash

--- lib/carte/client/views/list.cjsx
+++ lib/carte/client/views/list.cjsx
@@ -181,7 +181,6 @@
       }
       {
         if @props.card && @props.card.query.context == 'none'
-          console.log 'HELOLO!!!!!!!!!!!!!!!!!!!!!!'
           <div className='row'>
             <Card key={@props.card.get("title")} card={@props.card} />
           </div>


daily

daily

--- lib/cli/generators/fsharp.coffee
+++ lib/cli/generators/fsharp.coffee
@@ -137,6 +137,7 @@
      * Components List
     ###
     ex.push "let isNull x = match x with null -> true | _ -> false"
+    ex.push "let notNull x = match x with null -> false | _ -> true"
     ex.push ""
     ex.push "type Component with "
     kc = 0
@@ -223,6 +224,60 @@
           ex.push "    static member #{Name} with get() = Matcher.AllOf(Component.#{Name}) "
           ex.push ""

+    ###
+     * Pooled Entities
+    ###
+    ex.push "type World with"
+    for Name, pooled of config.entities
+      if pooled
+        name = Name[0].toLowerCase()+Name[1...];
+        properties = config.components[Name]
+        if config.components[Name] is false
+          ex.push ""
+          ex.push "    member this.#{name}Entity"
+          ex.push "        with get() = this.GetGroup(Matcher.#{Name}).GetSingleEntity()"
+          ex.push ""
+          ex.push "    member this.is#{Name}"
+          ex.push "        with get() ="
+          ex.push "            notNull(this.#{name}Entity)"
+          ex.push "        and  set(value) ="
+          ex.push "            let entity = this.#{name}Entity"
+          ex.push "            if value <> notNull(entity) then"
+          ex.push "                if value then"
+          ex.push "                    this.CreateEntity().is#{Name} <- true"
+          ex.push "                else"
+          ex.push "                    this.DestroyEntity(entity)"
+          ex.push ""
+        else
+          ex.push ""
+          ex.push "    member this.#{name}Entity"
+          ex.push "        with get() = this.GetGroup(Matcher.#{Name}).GetSingleEntity()"
+          ex.push ""
+          ex.push "    member this.#{name}"
+          ex.push "        with get() = this.#{name}Entity.#{name}"
+          ex.push ""
+          ex.push "    member this.has#{Name}"
+          ex.push "        with get() = notNull(this.#{name}Entity)"
+          ex.push ""
+          ex.push "    member this.Set#{Name}(newValue) ="
+          ex.push "        if this.has#{Name} then"
+          ex.push "            failwith \"Single Entity Exception: #{Name}\""
+          ex.push "        let entity = this.CreateEntity()"
+          ex.push "        entity.Add#{Name}(newValue) |> ignore"
+          ex.push "        entity"
+          ex.push ""
+          ex.push "    member this.Replace#{Name}(newValue) ="
+          ex.push "        let entity = this.#{name}Entity"
+          ex.push "        if isNull(entity) then"
+          ex.push "            entity = this.Set#{Name}(newValue) |> ignore"
+          ex.push "        else"
+          ex.push "            entity.Replace#{Name}(newValue) |> ignore"
+          ex.push "        entity"
+          ex.push ""
+          ex.push "    member this.Remove#{Name}() ="
+          ex.push "        this.DestroyEntity(this.#{name}Entity)"
+          ex.push ""
+

     mkdirp.sync path.dirname(path.join(process.cwd(), config.output.fsharp))
     mkdirp.sync path.join(process.cwd(), config.output.fsharp, "Systems")


Added raw nodes #89

Added raw nodes #89

--- coffeequate/src/coffeequate.coffee
+++ coffeequate/src/coffeequate.coffee
@@ -3,7 +3,7 @@
 require.config
 		baseUrl: "./"

-define ["operators", "Expression", "parse", "uncertainties"], (operators, Expression, parse, uncertainties) ->
+define ["operators", "Expression", "parse", "uncertainties", "terminals"], (operators, Expression, parse, uncertainties, terminals) ->

 	# Converts string inputs into Expressions, which Coffeequate can then work with.
 	# (Public interface for Coffeequate - this is all that the user will see!)
@@ -27,4 +27,16 @@
 			# This is an expression.
 			return new Expression(input)

+	# Adding a way to access the raw nodes.
+	CQ.raw = {
+		Add: operators.Add
+		Mul: operators.Mul
+		Pow: operators.Pow
+		Terminal: terminals.Terminal
+		Variable: terminals.Variable
+		Constant: terminals.Constant
+		SymbolicConstant: terminals.SymbolicConstant
+		Uncertainty: terminals.Uncertainty
+	}
+
 	return CQ


Allow 7 units per side

Allow 7 units per side

--- ui.coffee
+++ ui.coffee
@@ -32,7 +32,9 @@
   '''

 module.factory 'unitsService', ->
-  units = {my: [new Unit(30), new Unit(0), new Unit(0)], enemy: [new Unit(30), new Unit(2), new Unit(0), new Unit(0), new Unit(0)]}
+  units = {
+    my: [new Unit(30), new Unit(0), new Unit(0), new Unit(0), new Unit(0), new Unit(0), new Unit(0), new Unit(0)],
+    enemy: [new Unit(30), new Unit(2), new Unit(0), new Unit(0), new Unit(0), new Unit(0), new Unit(0), new Unit(0)]}
   return units

 module.factory 'resultsService', ->


Scroll to the top of the page in the next run loop.

--- app/assets/javascripts/controllers/application_controller.js.coffee
+++ app/assets/javascripts/controllers/application_controller.js.coffee
@@ -13,7 +13,8 @@
         @afterFirstHit = true

     # Scroll to the top of the page.
-    window.scrollTo(0, 0)
+    Ember.run.next ->
+      window.scrollTo(0, 0)

   ).observes('currentPath')


teams: creation: show register/login tab selectively

--- client/landing/site.landing/coffee/team/tabs/teamusernametab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamusernametab.coffee
@@ -1,6 +1,7 @@
-JView                   = require './../../core/jview'
-MainHeaderView          = require './../../core/mainheaderview'
-TeamUsernameTabForm     = require './../forms/teamusernametabform'
+JView                     = require './../../core/jview'
+MainHeaderView            = require './../../core/mainheaderview'
+TeamUsernameTabForm       = require './../forms/teamusernametabform'
+TeamLoginAndCreateTabForm = require './../forms/teamloginandcreatetabform'

 module.exports = class TeamUsernameTab extends KDTabPaneView

@@ -12,16 +13,43 @@

     super options, data

+    teamData           = KD.utils.getTeamData()
+    { alreadyMember }  = teamData.signup
     { mainController } = KD.singletons

     @header = new MainHeaderView
       cssClass : 'team'
       navItems : []

-    @form = new TeamUsernameTabForm
-      callback : (formData) ->
-        KD.utils.storeNewTeamData 'username', formData
-        KD.utils.createTeam (err, res) -> console.log err, res
+    if alreadyMember
+
+      @title = new KDCustomHTMLView
+        tagName : 'h4'
+        partial : 'Almost there'
+
+      @subtitle = new KDCustomHTMLView
+        tagName : 'h5'
+        partial : 'please enter your Koding password'
+
+      @form = new TeamLoginAndCreateTabForm
+        callback : (formData) ->
+          KD.utils.storeNewTeamData 'username', formData
+          KD.utils.createTeam (err, res) -> console.log err, res
+    else
+
+      @title = new KDCustomHTMLView
+        tagName : 'h4'
+        partial : 'Choose a Username'
+
+      @subtitle = new KDCustomHTMLView
+        tagName : 'h5'
+        partial : '...or login with your existing Koding account.'
+
+      @form = new TeamUsernameTabForm
+        callback : (formData) ->
+          KD.utils.storeNewTeamData 'username', formData
+          KD.utils.createTeam (err, res) -> console.log err, res
+

   pistachio: ->
@@ -29,7 +57,8 @@
     """
     {{> @header }}

-
Choose a Username

+      {{> @title}}
+      {{> @subtitle}}
       {{> @form}}

     """


add static data to window

add static data to window

--- app/assets/javascripts/frontend_app.js.coffee
+++ app/assets/javascripts/frontend_app.js.coffee
@@ -9,4 +9,87 @@
 # for more details see: http://emberjs.com/guides/application/
 window.ReportCard = Ember.Application.create({
   LOG_TRANSITIONS: true
-  })
+})
+
+window.ReportCard.event_verbs =
+  "CommitCommentEvent": "commit comments",
+  "CreateEvent": "new repos or branches",
+  "DeleteEvent": "deletion of branches",
+  "DownloadEvent": "creation of downloads",
+  "FollowEvent": "following",
+  "ForkEvent": "forking",
+  "ForkApplyEvent": "fork merges",
+  "GistEvent": "gist creation",
+  "GollumEvent": "wiki edits",
+  "IssueCommentEvent": "issue comments",
+  "IssuesEvent": "new issues",
+  "MemberEvent": "new collaborations",
+  "PublicEvent": "open-sourcing",
+  "PullRequestEvent": "pull requests",
+  "PullRequestReviewCommentEvent": "pull request comments",
+  "PushEvent": "pushes",
+  "TeamAddEvent": "teams",
+  "WatchEvent": "watching",
+  "ReleaseEvent": "new releases"
+
+window.ReportCard.event_actions =
+  "CreateEvent": "creating new repositories and branches",
+  "CommitCommentEvent": "commenting on your commits",
+  "FollowEvent": "following other users",
+  "ForkEvent": "forking other people's code",
+  "IssuesEvent": "creating issues",
+  "IssueCommentEvent": "commenting on issues",
+  "PublicEvent": "open sourcing new projects",
+  "PullRequestEvent": "submitting pull requests"
+
+window.ReportCard.times_of_day = [
+  times: [0, 1]
+  name: "around midnight"
+,
+  times: [1, 7]
+  name: "in the wee hours"
+,
+  times: [7, 12]
+  name: "in the morning"
+,
+  times: [12, 13]
+  name: "around noon"
+,
+  times: [13, 18]
+  name: "in the afternoon"
+,
+  times: [18, 21]
+  name: "in the evening"
+,
+  times: [21, 24]
+  name: "late at night"
+]
+
+window.ReportCard.week_types = [
+  name: "a Tuesday tinkerer"
+  vector: [1.0, 1.0, 10.0, 1.0, 1.0, 1.0, 1.0]
+,
+  name: "an early-week worker"
+  vector: [1.0, 6.0, 4.0, 2.0, 1.0, 1.0, 1.0]
+,
+  name: "a weekend warrior"
+  vector: [10.0, 1.0, 1.0, 1.0, 1.0, 1.0, 10.0]
+,
+  name: "a hump day hero"
+  vector: [1.0, 1.0, 3.0, 10.0, 3.0, 1.0, 1.0]
+,
+  name: "a late-week deadliner"
+  vector: [1.0, 1.0, 1.0, 3.0, 10.0, 1.0, 1.0]
+,
+  name: "a Friday hacker"
+  vector: [1.0, 1.0, 1.0, 1.0, 1.0, 10.0, 1.0]
+,
+  name: "a fulltime hacker"
+  vector: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
+,
+  name: "a Sunday driver"
+  vector: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 10.0]
+,
+  name: "a nine-to-fiver"
+  vector: [1.0, 5.0, 5.0, 5.0, 5.0, 5.0, 1.0]
+]


inflecting singular for venta

inflecting singular for venta

--- app/assets/javascripts/models/venta.js.coffee
+++ app/assets/javascripts/models/venta.js.coffee
@@ -17,3 +17,4 @@
     @_super()

 Ember.Inflector.inflector.irregular('venta', 'ventas');
+Ember.Inflector.inflector.singular(/venta/, 'venta');


Made the bind function unbound in chat.js

Signed-off-by: Philip Vieira <3ba3974e96044dcde818e6310ed799abe10671d4@vallin.se>

--- app/assets/javascripts/modules/chat.js.coffee
+++ app/assets/javascripts/modules/chat.js.coffee
@@ -26,7 +26,7 @@

       @bind()

-    bind: ->
+    bind: =>
       @input.BetterGrow
         initial_height: 12
         do_not_enter: null


Extend shopify parser to understand borg jobservers.

--- app/assets/javascripts/plugins/borg.js.coffee
+++ app/assets/javascripts/plugins/borg.js.coffee
@@ -152,6 +152,20 @@
       else if match = log.output.match(/(fatal|fail|down):( run:)? ([\-\w\d]+)/)
         task = @getTask(log.host)
         task.addStatus("down", "#{match[1]}: #{match[3]}")
+        # Legacy jobservers --^, borg jobservers --V
+      else if match = log.output.match(/\[(\d+)\/(\d+)\].* restarting/i)
+        @getTask(log.host).update
+          numPending: match[1]
+          numLights: match[2]
+      else if match = log.output.match(/\[(\d+)\/(\d+)\].* successfully restarted/i)
+        task = @getTask(log.host)
+        task.addStatus("up", match[1])
+      else if match = log.output.match(/\[(\d+)\/(\d+)\].* did not restart in time/i)
+        task = @getTask(log.host)
+        task.addStatus("partial", "timeout: #{match[1]}")
+      else if match = log.output.match(/\[(\d+)\/(\d+)\].* (failed to restart|unable to restart)/i)
+        task = @getTask(log.host)
+        task.addStatus("down", "fail: #{match[1]}")
     null


Fix setting property checked for spinaSwitch

--- app/assets/javascripts/spina/switch.js.coffee
+++ app/assets/javascripts/spina/switch.js.coffee
@@ -32,10 +32,10 @@
     checkbox.removeClass('active')
     checkbox.removeClass('activated')
     checkbox.addClass('deactivated')
-    input.attr("checked", false)
+    input.prop("checked", false)
   else
     checkbox.addClass('activated')
     checkbox.removeClass('deactivated')
-    input.attr("checked", true)
+    input.prop("checked", true)

   return false


Tweaked atom editor configuration

Tweaked atom editor configuration

--- atom/dot-atom/config.cson
+++ atom/dot-atom/config.cson
@@ -20,6 +20,8 @@
       commonmark: true
       default_beautifier: "Remark"
     python:
+      default_beautifier: "yapf"
+      formater: "yapf"
       sort_imports: true
     r:
       beautify_on_save: true
@@ -28,12 +30,12 @@
       disabled: true
   "atom-clock":
     showClockIcon: true
-  "autocomplete-python":
-    useKite: false
   "atom-html-preview":
     enableMathJax: true
     scrollToCursor: true
     triggerOnSave: true
+  "autocomplete-python":
+    useKite: false
   build:
     saveOnBuild: true
     scrollOnError: true
@@ -43,6 +45,8 @@
     disabledPackages: [
       "language-gfm"
       "language-python"
+      "atom-ide-ui"
+      "ide-python"
     ]
     packagesWithKeymapsDisabled: []
     telemetryConsent: "no"


Added file extension for export.

Added file extension for export.

--- PythonRequestsCodeGenerator.coffee
+++ PythonRequestsCodeGenerator.coffee
@@ -116,8 +116,9 @@

 PythonRequestsCodeGenerator.identifier =
-    "com.luckymarmot.PawExtensions.PythonRequestsCodeGenerator";
+    "com.luckymarmot.PawExtensions.PythonRequestsCodeGenerator"
 PythonRequestsCodeGenerator.title =
-    "Python (Requests)";
+    "Python (Requests)"
+PythonRequestsCodeGenerator.fileExtension = "py"

 registerCodeGenerator PythonRequestsCodeGenerator


add unload handler

add unload handler

--- lib/assets/javascripts/webrtc_rails/main.js.coffee
+++ lib/assets/javascripts/webrtc_rails/main.js.coffee
@@ -23,6 +23,13 @@
     @_startOutput(@localOutput)
     @_webSocketInitialize(url, userToken)
     @_addNetworkEventListener()
+
+    window.onbeforeunload = (e) =>
+      if @_hangedUp then null else '通話が切断されます。'
+
+    window.onunload = (e) =>
+      unless @_hangedUp
+        @hangUp()

   call: (remoteUserIdentifier) ->
     if @_webRTCReconnecting && @_hangedUp


Missed a quote in the view template

Missed a quote in the view template

--- lib/generators/ember/view/templates/view.js.coffee
+++ lib/generators/ember/view/templates/view.js.coffee
@@ -2,4 +2,4 @@
 //= require_self

 <%= application_name %>.Views.<%= controller_name %>.<%= view_name %> = Ember.View.extend
-  templateName: 'app/assets/javascripts/ember/templates/<%= controller_name %>/<%= template_name %>
+  templateName: 'app/assets/javascripts/ember/templates/<%= controller_name %>/<%= template_name %>'


Uncomented and properly implemented the playlist description #27

--- resources/assets/scripts/app/controllers/playlist.coffee
+++ resources/assets/scripts/app/controllers/playlist.coffee
@@ -28,7 +28,7 @@
         playlists.fetch($state.params.id).done (playlistResponse) ->
             $scope.playlist = playlistResponse
             playlist = playlistResponse
-            #$rootScope.description = "Listen to #{track.title} by #{track.user.name} on the largest pony music site"
+            $rootScope.description = "Listen to #{playlist.title} by #{playlist.user.name} on the largest pony music site"

         $scope.share = () ->
             dialog = $dialog.dialog


Add exception so that unrecognized constructs are not unnoticed

--- javascripts/lib/math/build_expression_from_javascript_object.coffee
+++ javascripts/lib/math/build_expression_from_javascript_object.coffee
@@ -32,9 +32,7 @@
                 when "*" then @multiplication_builder.build()
             when "object" then @convertObject(object_to_convert)

-
       # privates
-
       processExpression: (exp, parts)->
         for x in parts
           exp = exp.append(@process(x))
@@ -46,9 +44,8 @@
         else if (it = object['open_expression'])
           exp = @expression_builder.build()
           @processExpression(exp, it).open()
-
-
-
+        else
+          throw "Build Exp not recognized"

     class_mixer BuildExpressionFromJavascriptObject


Remove old term model

Remove old term model

--- resources/static/brunch/src/app/models/term_model.coffee
+++ resources/static/brunch/src/app/models/term_model.coffee
@@ -1,18 +0,0 @@
-class exports.Term extends Backbone.Model
-  validate: (str) ->
-    if !str?
-      str = @get "term"
-
-    # TODO: Modify this Rule regex to become a Term regex
-    # Token -> a word with possibly spaces in front and after
-    # Rule  -> Token ( Token {, Token}* )
-    # Regex -> Rule {, Rule}* {. | :- { Rule {, | .} }* }
-    token = "\\s*\\w+\\s*"
-    rule  = token + "\\(" + token + "(," + token + ")*\\)\\s*"
-    regex = new RegExp(rule + "(\\.|:-(" + rule + "(,\\s*|\\.))*)")
-    # regex = new RegExp(rule + "(," + rule + ")*" + "(\\.|:-(" + rule + "(,\\s*|\\.))*)") -- TODO: Enable after modifying server-side parser
-    regex.test str
-
-  clear: ->
-    @destroy()
-    @view.remove()


error check fixed at checkAvailability

error check fixed at checkAvailability

--- client/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -169,15 +169,15 @@
       if err
         notifyUser "An error occured. Please try again later."
       KD.remote.api.JDomain.isDomainAvailable domain, tld, (avErr, result) =>
-        status = result.status
-        price = result.price
-        suggestions = result.suggestions
         if avErr
           checkButton.hideLoader()
           log result
           log tld
           log avErr
           return notifyUser "An error occured: #{avErr}"
+        status = result.status
+        price = result.price
+        suggestions = result.suggestions
         checkButton.hideLoader()
         switch status
           when "available"


Fix a NaN bug

Fix a NaN bug

--- src/6.timer.coffee
+++ src/6.timer.coffee
@@ -114,7 +114,7 @@
                 catch
                     Date.Observer._error
             if newValue == undefined then newValue = Date.Observer._error
-            if @_oldValue == undefined or newValue != @_oldValue
+            if @_oldValue == undefined or not Object.is(newValue, @_oldValue)
                 @onUpdate.fire(value: newValue)
                 if @_oldValue != undefined
                     @onChange.fire(


remove unused path module

remove unused path module

--- services/web/app/coffee/Features/Project/SafePath.coffee
+++ services/web/app/coffee/Features/Project/SafePath.coffee
@@ -1,5 +1,3 @@
-Path = require('path')
-
 BADCHAR_RX = ///
 	[
 		\/ # no slashes


Update snippets

Update snippets

--- services/web/public/coffee/auto-complete/Snippets.coffee
+++ services/web/public/coffee/auto-complete/Snippets.coffee
@@ -80,6 +80,14 @@
 		"""
 		meta: "env"
 	}, {
+		caption: "\\begin{itemize}..."
+		snippet: """
+			\\begin{itemize}
+			\\item $1
+			\\end{itemize}
+		"""
+		meta: "env"
+	}, {
 		caption: "\\begin{frame}..."
 		snippet: """
 			\\begin{frame}{${1:Frame Title}}


Handles diff display on split pane

Handles diff display on split pane

--- lib/minimap-git-diff-binding.coffee
+++ lib/minimap-git-diff-binding.coffee
@@ -14,7 +14,6 @@
     @gitDiff = require(@gitDiffPackage.path)

     @subscribe @editorView, 'editor:path-changed', @subscribeToBuffer
-    @subscribe @minimap, 'activated', @subscribeToMinimapView
     @subscribe @editorView, 'editor:contents-modified', @renderDiffs
     @subscribe atom.project.getRepo(), 'statuses-changed', =>
       @scheduleUpdate()
@@ -22,6 +21,11 @@
       @scheduleUpdate()

     @subscribeToBuffer()
+
+    if @minimap.active
+      @subscribeToMinimapView()
+    else
+      @subscribe @minimap, 'activated', @subscribeToMinimapView

   scheduleUpdate: ->
     setImmediate(@updateDiffs)


fix import error

fix import error

--- src/containers/middleware.coffee
+++ src/containers/middleware.coffee
@@ -1,5 +1,9 @@
-import { utils } from 'el-controls'
-import { requiresPostalCode }        from '../utils/country'
+import { requiresPostalCode } from '../utils/country'
+import {
+  luhnCheck
+  cardFromNumber
+} from '../utils/card'
+

 emailRe = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/

@@ -75,14 +79,14 @@
   if @get('order.type') != 'stripe'
     return value

-  card = utils.card.cardFromNumber value
+  card = card.cardFromNumber value
   throw new Error('Enter a valid card number') unless card

   number = value.replace(/\D/g, '')
   length = number.length

   throw new Error('Enter a valid card number') unless /^\d+$/.test(number)
-  throw new Error('Enter a valid card number') unless length in card.length and (card.luhn is false or utils.card.luhnCheck(number))
+  throw new Error('Enter a valid card number') unless length in card.length and (card.luhn is false or luhnCheck(number))

   value

@@ -125,7 +129,7 @@
   if @('order.type') != 'stripe'
     return value

-  card = utils.card.cardFromNumber(@get 'payment.account.number')
+  card = cardFromNumber(@get 'payment.account.number')
   cvc_ = value.trim()

   throw new Error('Enter a valid cvc') unless /^\d+$/.test(cvc_)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/approachable.coffee
+++ test/approachable.coffee
@@ -10,6 +10,7 @@

   obj.include(Approachable)

+  4.times ->
     obj.update()

   equals obj.I.bullet, 78, "bullet should decrease by 5"


Updated Lambda test to check correct error type due to updated whitelist

--- test/browser.spec.coffee
+++ test/browser.spec.coffee
@@ -274,11 +274,10 @@
         expect(Array.isArray(data.Functions)).to.equal(true)
         done()

-    # TODO This error code needs to be updated when the X-Amzn-ErrorType is whitelisted.
     it 'handles errors', (done) ->
       lambda.invoke {FunctionName: 'fake-function'}, (err, data) ->
         noData(data)
-        assertError(err, 'UnknownError')
+        assertError(err, 'ResourceNotFoundException')
         matchError(err, 'function not found')
         done()


themesh normal buffer on dynamic memory access

--- public/coffee/view/themesh.coffee
+++ public/coffee/view/themesh.coffee
@@ -26,7 +26,7 @@
          data: @normals
          attribPointerId: @program.vertexNormalAttribute
          attribPointerSize: 3
-         usage: gl.STATIC_DRAW
+         usage: gl.DYNAMIC_DRAW
       @initBuffer(bufferData)

    addLevel: (triangleVertex = null) ->


ensure unicode content is compiled through correctly

--- test/compile_test.coffee
+++ test/compile_test.coffee
@@ -28,6 +28,7 @@
           'unicode_land.html'
         ])
         should(fs.statSync(path.join(output, "doge.png")).size).equal(fs.statSync(path.join(p, "doge.png")).size)
+        should.contain_content(output, 'unicode_land.html', /å sky so hîgh ☆/)
         done()

   it 'should copy files in nested directories', (done) ->


+tip now works with pure SteamIDs by prepending '#'

--- bot.iced
+++ bot.iced
@@ -274,7 +274,7 @@
 				shibe = tipInfo[1]
 				amount = tipInfo[2]
 			else
-				return bot.sendMessage chatterID, "Invalid +tip input. Notation for +tip is '+tip <STEAM NAME> <AMOUNT|all> doge'."
+				return bot.sendMessage chatterID, "Invalid +tip input. Notation for +tip is '+tip <STEAM NAME|#STEAMIDNUMBER> <AMOUNT|all> doge'."

 			if amount.toLowerCase() is "all"
 				amount = user.funds
@@ -289,7 +289,11 @@
 			# Retrieve the user's steamid
 			shibeID = undefined
 			# First check if the bot has them registered already
-			await Users_collection.findOne {"name": shibe}, defer(err, registeredShibe)
+			if shibe[0] is "#"
+				shibeID = shibe
+				await Users_collection.findOne {"id": shibeID}, defer(err, registeredShibe)
+			else
+				await Users_collection.findOne {"name": shibe}, defer(err, registeredShibe)
 			if err
 				console.error err
 				return bot.sendMessage chatterID, "The database ran into an error"
@@ -330,7 +334,7 @@
 				+balance - Check the amount of DOGE in your account
 				+history - Display your current balance and a list of your 10 most recent transactions
 				+withdraw <ADDRESS> <AMOUNT|all> doge - Withdraw funds in your account to the specified address
-				+tip <STEAM NAME> <AMOUNT|all> doge - Send a Steam user a tip. Currently, this will fail if they haven't registered with the bot
+				+tip <STEAM NAME|#STEAMIDNUMBER> <AMOUNT|all> doge - Send a Steam user a tip. Currently, this will fail if they haven't registered with the bot
 				+help - This help dialog

 			Find a bug? Want a feature? File an issue at https://github.com/petschekr/SteamDogeTipBot/issues or submit a pull request


Add phantomjs shim

Add phantomjs shim

This way it's included on all files.  Now that we're including the src/*
it's needed

--- test/karma.config.coffee
+++ test/karma.config.coffee
@@ -3,23 +3,22 @@
 module.exports = (config) ->
   config.set
     basePath: '../'
-    frameworks: ['mocha', 'chai', 'chai-sinon']
+    frameworks: ['mocha', 'chai', 'chai-sinon', 'phantomjs-shim']
     browsers: ['PhantomJS']
     reporters: ['mocha', 'coverage']

     files: [
       'test/all-tests.coffee'
+      'src/all.coffee'
     ]

     preprocessors:
-      'src/**/*.cjsx': ['webpack', 'coverage']
+      'src/**/*.{coffee,cjsx}': ['webpack', 'coverage']
       'test/*':  ['webpack', 'coverage']

     coverageReporter:
       type: 'text'
       # type: 'html' # will create html report in ./coverage directory
-
-

     webpack:
       resolve:
@@ -41,11 +40,12 @@

     webpackMiddleware:
       stats:
-        colors: true
+        colors: false

-    logLevel: config.LOG_DEBUG
+    #logLevel: config.LOG_DEBUG

     plugins:[
+      require("karma-phantomjs-shim")
       require("karma-coverage")
       require("karma-mocha")
       require("karma-webpack")


Fix tests after paginator merge

Fix tests after paginator merge

--- test/request.spec.coffee
+++ test/request.spec.coffee
@@ -41,6 +41,9 @@
       expect(service.makeRequest('mockMethod').isPageable()).toEqual(false)

   describe 'send', ->
+    beforeEach ->
+      service = new MockService
+
     it 'accepts an optional callback', ->
       error = null; data = null
       helpers.mockHttpResponse 200, {}, ['FOO', 'BAR', 'BAZ', 'QUX']


remove sample node

remove sample node

--- examples/sample-node.coffee
+++ examples/sample-node.coffee
@@ -1,5 +0,0 @@
-class SampleNode
-  constructor: (@config, @data) ->
-  onStart: (callback=->)=>
-  onMessage:(message, callback=->)=>
-  onStop: (callback=->)=>


add targets to tests to fix tests

add targets to tests to fix tests

--- test/src/tourSpec.coffee
+++ test/src/tourSpec.coffee
@@ -41,6 +41,7 @@

The user quit early

         '''
         okButton: true
+        target: $('#target-one')
         setup: ->
         teardown: ->

@@ -49,6 +50,7 @@

User made it all the way through

         '''
         okButton: true
+        target: $('#target-one')
         setup: ->
         teardown: ->


Disambiguate: this is bracket context from inports

--- src/lib/Component.coffee
+++ src/lib/Component.coffee
@@ -39,7 +39,7 @@
     @ordered = options.ordered ? false
     @autoOrdering = options.autoOrdering ? null
     @outputQ = []
-    @bracketContext = {}
+    @bracketContextIn = {}
     @activateOnInput = options.activateOnInput ? true
     @forwardBrackets = in: ['out', 'error']

@@ -72,7 +72,7 @@
     return unless @started
     inPorts = @inPorts.ports or @inPorts
     inPort.clear() for inPort in inPorts
-    @bracketContext = {}
+    @bracketContextIn = {}
     callback = =>
       @started = false
       @emit 'end'
@@ -228,9 +228,9 @@
     if @inPorts[port].isAddressable()
       portname = "#{port}[#{idx}]"
     # Ensure we have a bracket context for the current scope
-    @bracketContext[portname] = {} unless @bracketContext[portname]
-    @bracketContext[portname][scope] = [] unless @bracketContext[portname][scope]
-    return @bracketContext[portname][scope]
+    @bracketContextIn[portname] = {} unless @bracketContextIn[portname]
+    @bracketContextIn[portname][scope] = [] unless @bracketContextIn[portname][scope]
+    return @bracketContextIn[portname][scope]

   addToResult: (result, port, ip, before = false) ->
     if port.indexOf('[') is -1
@@ -281,12 +281,13 @@
                 ipClone = ctx.ip.clone()
                 ipClone.index = parseInt idx
                 idxIps.unshift ipClone
-                debugBrackets "#{@nodeId} register from '#{inport}' to '#{portIdentifier}' < '#{ctx.ip.data}'"
+                debugBrackets "#{@nodeId} openBracket from '#{inport}' to '#{portIdentifier}': '#{ctx.ip.data}'"
                 ctx.ports.push portIdentifier
             continue
           # Don't register ports we're only sending brackets to
           datas = ips.filter (ip) -> ip.type is 'data'
           continue unless datas.length
+          console.log context.map (ctx) -> return "#{ctx.source} < #{ctx.ip.data}"
           unforwarded = context.filter (ctx) =>
             return false unless @isForwardingOutport inport, outport
             ctx.ports.indexOf(outport) is -1
@@ -294,7 +295,7 @@
           unforwarded.reverse()
           for ctx in unforwarded
             ips.unshift ctx.ip.clone()
-            debugBrackets "#{@nodeId} register from '#{inport}' to '#{outport}' < '#{ctx.ip.data}'"
+            debugBrackets "#{@nodeId} openBracket from '#{inport}' to '#{outport}': '#{ctx.ip.data}'"
             ctx.ports.push outport

     if result.__bracketClosingAfter?.length
@@ -698,7 +699,7 @@
       # We're doing bracket forwarding. See if there are
       # dangling closeBrackets in buffer since we're the
       # last running process function.
-      for port, contexts of @nodeInstance.bracketContext
+      for port, contexts of @nodeInstance.bracketContextIn
         continue unless contexts[@scope]
         nodeContext = contexts[@scope]
         continue unless nodeContext.length


First argument is an event, then eventKey

--- src/components/performance-forecast/teacher-student.cjsx
+++ src/components/performance-forecast/teacher-student.cjsx
@@ -30,7 +30,7 @@

   bindStore: PerformanceForecast.TeacherStudent.store

-  onSelectStudent: (roleId) ->
+  onSelectStudent: (ev, roleId) ->
     {courseId} = @props
     PerformanceForecast.TeacherStudent.actions.load(courseId, {roleId})
     @setState({roleId})


add "View Stats" button back in

add "View Stats" button back in

--- src/components/task-plan/teacher-task-plans-listing.cjsx
+++ src/components/task-plan/teacher-task-plans-listing.cjsx
@@ -19,15 +19,23 @@
     {id} = @props.plan
     @transitionTo('editReading', {courseId, id})

+  onViewStats: ->
+    {courseId} = @props
+    {id} = @props.plan
+    @transitionTo('viewStats', {courseId, id})
+
   render: ->
     {plan} = @props
     start  = moment(plan.opens_at)
     ending = moment(plan.due_at)
     duration = moment.duration( ending.diff(start) ).humanize()

-    <BS.ListGroupItem header={plan.title} onClick={@onEditPlan}>
-      {start.fromNow()} ({duration})
-    </BS.ListGroupItem>
+    <div className="-list-item">
+      <BS.ListGroupItem header={plan.title} onClick={@onEditPlan}>
+        {start.fromNow()} ({duration})
+      </BS.ListGroupItem>
+      <BS.Button bsStyle="link" className="-tasks-list-stats-button" onClick={@onViewStats}>View Stats</BS.Button>
+    </div>

 TeacherTaskPlanListing = React.createClass


Add meta data to active page

Add meta data to active page

--- app/assets/javascripts/models/active_page.coffee
+++ app/assets/javascripts/models/active_page.coffee
@@ -46,6 +46,9 @@
       # TODO make this work with span and p
       $("div[data-k-uuid='" + @uuid + "'][data-attr='" + attr + "'], span[data-k-uuid='" + @uuid + "'][data-attr='" + attr + "'], p[data-k-uuid='" + @uuid + "'][data-attr='" + attr + "']").html(val)

+  update_meta_on_page: ->
+    $("[data-kindred-model]").find("div[data-k-uuid='" + @uuid + "']").data("meta", @meta)
+
   dirty_from_page: ->
     dirty = []
     $.each $("input[data-k-uuid='" + @uuid + "'], select[data-k-uuid='" + @uuid + "']"), (i, input) =>
@@ -75,6 +78,8 @@
     if !isNaN(parseFloat(model_data.data("id"))) && isFinite(model_data.data("id"))
       @id = model_data.data("id")

+    @meta = model_data.data("meta")
+
     $("select[data-k-uuid='" + @uuid + "']").each (i, select) =>
       @set $(select).data("attr"), $(select).val()

@@ -91,7 +96,7 @@
       $("[data-k-uuid='" + @uuid + "'][data-attr='" + attr + "']").data("val", val)

   _append_data_model_to_page: ->
-    model_div = "

"
+    model_div = "

"
     $("[data-kindred-model]").append(model_div)

   _input_dirty: (input) ->
@@ -101,3 +106,9 @@
       !(input.data("val").toString() == input.prop("checked").toString())
     else
       !(input.data("val").toString() == input.val().toString())
+
+  _stringified_meta: ->
+    if @meta?
+      JSON.stringify(@meta)
+    else
+      ""


replaced by entity_mappings

replaced by entity_mappings

--- app/assets/javascripts/object_mappings.js.coffee
+++ app/assets/javascripts/object_mappings.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/


disabling proposal title editing on the homepage for real this time

Former-commit-id: 581e2d370a703bc596b0d823e7eac29555e7bf63 [formerly cce3a8865d1d07968b4a75e88dae39a0bb4b0881] [formerly 05924972f3ece64fc5d6f6939021643d1b34b235 [formerly 05924972f3ece64fc5d6f6939021643d1b34b235 [formerly 898629cc7f61e6bb49a76d4b0e19f4db7613d4ea]]]
Former-commit-id: 3b6ab63d4fec7288f0d8f071aeac9b22ceedee52 [formerly dcea85e3f81284e5d333c72a6296771c9b9c31f6]
Former-commit-id: c3c6fcd8280546f6acf8443dfdb1e5237f901a8e
Former-commit-id: 6105799018ee12b4e42a95c1498ad803728cb4d3

--- app/assets/javascripts/views/proposalview.coffee
+++ app/assets/javascripts/views/proposalview.coffee
@@ -322,7 +322,7 @@
         @$el.find(selector).editable
           resource: 'proposal'
           pk: @long_id
-          disabled: !@state? && @model.get('published')
+          disabled: @state == 0 && @model.get('published')
           url: Routes.proposal_path @model.long_id
           type: type
           name: name


disable new course form while submitting, fixes #10246

prevent users from double-submitting the form while they wait

test plan:
1. go to the dashboard
2. click the "Start a New Course" button
3. try to double or triple-submit the form
4. confirm that the form gets disabled and only one course gets created

Change-Id: If58995a5404b9d7d572d7068ad98142ffabbc044
Reviewed-on: https://gerrit.instructure.com/13360
Reviewed-by: Brian Palmer <3cb4e1df5ec4da2c7c4af7c52cec8cf340a55a10@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>

--- app/coffeescripts/bundles/new_course_form.coffee
+++ app/coffeescripts/bundles/new_course_form.coffee
@@ -1,5 +1,6 @@
 require [
   'jquery'
+  'jquery.disableWhileLoading'
 ], ($) ->
   $ ->

@@ -10,4 +11,5 @@
         $('#course_code_wrapper').slideDown('fast')

     $nameInput = $('#new_course_form [name="course[name]"]')
-    $nameInput.bind changeEvents, showCourseCodeIfNeeded+    $nameInput.bind changeEvents, showCourseCodeIfNeeded
+    $('#new_course_form').submit -> $(this).disableWhileLoading($.Deferred())


Make MSF work for wiki pages in old UI

Make MSF work for wiki pages in old UI

A check against new UI feature flags was overlooked
when b9c7017456be8faff4e2e2beb8e128c967d8a35a
(g/68729) was merged.

This adds that check ensuring that old UI users have the
module sequence footers that they have always had and new
UI users get the sticky footers.

closes CNVS-26164

Test Plan:
  - Disable New UI
  - Make sure that module progression footers work as expected
    for wiki pages
  - Note that they are not sticky to the bottom of the window
    with the old UI.
  - Regression test the other module item types with the Old UI
    to ensure they are all working properly.
  - Turn on the new UI
  - Make sure wiki pages footers work with the sticky footer as
    expected

Change-Id: Ia8fb8d820b025994e2f3e64d2012fa9a65778189
Reviewed-on: https://gerrit.instructure.com/69605
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Tested-by: Jenkins
QA-Review: Ryan Allen <b65b638b3333142073650bd017e5f36609bed2c6@instructure.com>
Product-Review: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>

--- app/coffeescripts/views/wiki/WikiPageView.coffee
+++ app/coffeescripts/views/wiki/WikiPageView.coffee
@@ -70,7 +70,10 @@

       # attach/re-attach the sequence footer (if this is a course, but not the home page)
       unless @$sequenceFooter || @course_home || !@course_id
-        @$sequenceFooter ||= $('

').hide()
+        if (ENV.use_new_styles)
+          @$sequenceFooter ||= $('

').hide()
+        else
+          @$sequenceFooter ||= $('

').hide()
         @$sequenceFooter.moduleSequenceFooter(
           courseID: @course_id
           assetType: 'Page'
@@ -79,7 +82,10 @@
         )
       else
         @$sequenceFooter?.msfAnimation(false)
-      @$sequenceFooter.appendTo($('.ic-app-main-and-right-side')) if @$sequenceFooter
+      if (ENV.use_new_styles)
+        @$sequenceFooter.appendTo($('.ic-app-main-and-right-side')) if @$sequenceFooter
+      else
+        @$sequenceFooter.appendTo(@$el) if @$sequenceFooter

     navigateToLinkAnchor: ->
       anchor_name = window.location.hash.replace(/^#/, "")


Expose ControlIframe publically

Expose ControlIframe publically

--- app/js/jail_iframe/classes/control_iframe.coffee
+++ app/js/jail_iframe/classes/control_iframe.coffee
@@ -1,5 +1,5 @@

-class ControlIframe
+class FactlinkJailRoot.ControlIframe
   constructor: ->
     console.log('creating frame')
     @el = document.createElement('iframe')


Fix edge case for bbox initial fitting.

Fix edge case for bbox initial fitting.

Received municipality param without units.

--- src/map-base-view.coffee
+++ src/map-base-view.coffee
@@ -132,7 +132,7 @@
         setInitialView: ->
             if @mapOpts?.bbox?
                 @fitBbox @mapOpts.bbox
-            else if @mapOpts?.fitAllUnits == true
+            else if @mapOpts?.fitAllUnits == true and not @units.isEmpty()
                 latlngs = @units.map (u) -> u.getLatLng()
                 bounds = L.latLngBounds latlngs
                 @map.fitBounds bounds


Remove connected nodes when both aren't supernodes

--- src/public/library/node.structured.coffee
+++ src/public/library/node.structured.coffee
@@ -45,7 +45,7 @@

 		_pingCandidateTimeout : 1000
 		_coordinateDelta : 1
-		_maxChildren : 4
+		_maxChildren : 8
 		_foundationNodes : 5
 		_superNodeSwitchThreshold : 0.7
 		_tokenMoveThreshold : 1
@@ -130,47 +130,6 @@

 					callback info

-		# # Responds to a request
-		# #
-		# # @param request [String] the string identifier of the request
-		# # @param args... [Any] any arguments that may be accompanied with the request
-		# # @param callback [Function] the callback to call with the response
-		# #
-		# query: ( request, args..., callback ) =>
-		# 	switch request
-		# 		when 'ping'
-		# 			callback 'pong', @position.serialize(), @token?.serialize()
-		# 		when 'position'
-		# 			callback @position.serialize()
-		# 		when 'isSuperNode'
-		# 			callback @isSuperNode
-		# 		when 'siblings'
-		# 			callback @getSiblings().map( ( peer ) ->
-		# 				id: peer.id
-		# 			)
-		# 		when 'peer.requestAdoption'
-		# 			id = args[0]
-		# 			if @isSuperNode and child = @getPeer(id)
-		# 				@addChild(child)
-		# 				callback true
-		# 			else
-		# 				callback false
-		# 		when 'info'
-		# 			info =
-		# 				id: @id
-		# 				type: @type
-		# 				position: @position
-		# 				isSuperNode: @isSuperNode
-		# 				token: @token
-		# 				peers: @getPeers().map( ( peer ) ->
-		# 					id: peer.id
-		# 					role: peer.role
-		# 				)
-
-		# 			callback info
-		# 		else
-		# 			super
-
 		# Relays a message to other nodes. If the intended receiver is not a direct
 		# neighbor, we route the message through other nodes in an attempt to reach
 		# the destination.
@@ -520,10 +479,15 @@
 			# our correct position in the network and to catch our fall we we
 			# lose our parent.
 			else
+				for peer in @getPeers()
+					unless peer.isSuperNode then peer.die()
+
 				current = _(@getPeers()).filter( (peer) -> peer.isSuperNode).length
 				needed = @_foundationNodes - current
 				if needed <= 0
 					return
+
+

 				@_parent.query('siblings', ( superNodes ) =>
 					if not superNodes? or superNodes.length is 0


modular data actions

modular data actions

--- src/mixins/detail.coffee
+++ src/mixins/detail.coffee
@@ -5,6 +5,27 @@
   bindings: {}
   detailSelector: null
   autoBind: (name) -> "[data-" + name + "]"
+  dataActions:
+    "toggle": (opts) ->
+      if opts.value
+        opts.el.show()
+      else
+        opts.el.hide()
+    "inverse-toggle": (opts) ->
+      if opts.value
+        opts.el.hide()
+      else
+        opts.el.show()
+    "pluralize": (opts) ->
+      if parseFloat(opts.value) == 1
+        opts.el.hide()
+      else
+        opts.el.show()
+    "data": (opts) ->
+      opts.el.data(name, opts.value)
+    "default": (opts) ->
+      console.log "default action", opts, @model
+      opts.el.html opts.value

   initialize: (options) ->
     @listenTo @model, "change", @handleModelUpdate
@@ -58,21 +79,14 @@
       e = el
       e.each (idx, e) =>
         el = @.$(e)
-        action = el.attr("data-#{name}")
-        if action == "toggle"
-          if @model.get(name)
-            el.show()
-          else
-            el.hide()
-        else if action == "inverse-toggle"
-          if @model.get(name)
-            el.hide()
-          else
-            el.show()
-        else if action == "data"
-          el.data(name, @model.get(name))
+        action = el.attr("data-#{name}") or "default"
+        func = @dataActions[action]
+        console.log name, action, func
+        opts = {el: el, name: name, value: @model.get(name)}
+        if func
+          func.bind(@)(opts)
         else
-          el.html @model.get name
+          @dataActions["default"].bind(@)(opts)

 class SingleObjectMixin


Wait for package activation promise

Wait for package activation promise

--- spec/bracket-matcher-spec.coffee
+++ spec/bracket-matcher-spec.coffee
@@ -11,10 +11,13 @@

     atom.workspaceView.openSync('sample.js')

-    atom.packages.activatePackage('bracket-matcher')
-    editorView = atom.workspaceView.getActiveView()
-    {editor} = editorView
-    {buffer} = editor
+    waitsForPromise ->
+      atom.packages.activatePackage('bracket-matcher')
+
+    runs ->
+      editorView = atom.workspaceView.getActiveView()
+      {editor} = editorView
+      {buffer} = editor

   describe "matching bracket highlighting", ->
     describe "when the cursor is before a starting pair", ->


tolto Grazie maiuscolo

tolto Grazie maiuscolo

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -31,5 +31,3 @@
 	robot.respond /grazie/i, (res) ->
 		res.reply 'prego, dovere'

-	robot.respond /Grazie/i, (res) ->
-		res.reply 'Prego, dovere'


add expect & rewire to spec helper

add expect & rewire to spec helper

--- tests/spec_helper.coffee
+++ tests/spec_helper.coffee
@@ -1,6 +1,9 @@
-global.chai = require("chai")
-global.sinon = require("sinon")
-global.sinonChai = require("sinon-chai")
+global.chai = require "chai"
+global.sinon = require "sinon"
+global.sinonChai = require "sinon-chai"
+global.rewire = require "rewire"
+
+global.expect = chai.expect
 chai.should()
 chai.use(sinonChai)


closes #233

closes #233

--- src/textwrap/helpers/getDimensions.coffee
+++ src/textwrap/helpers/getDimensions.coffee
@@ -3,19 +3,19 @@

   if not vars.width.value or not vars.height.value

-    parent = d3.select vars.container.value.node().parentNode
-    rect   = parent.select "rect"
-    circle = parent.select "circle"
+    prev  = vars.container.value.node().previousElementSibling
+    shape = if prev then prev.tagName.toLowerCase() else ""
+    prev  = d3.select(prev) if prev

-    unless rect.empty()
+    if shape is "rect"
       unless vars.width.value
-        width = rect.attr "width" or rect.style "width"
+        width = prev.attr "width" or prev.style "width"
         vars.self.width parseFloat width, 10
       unless vars.height.value
-        height = rect.attr "height" or rect.style "height"
+        height = prev.attr "height" or prev.style "height"
         vars.self.height parseFloat height, 10
-    else unless circle.empty()
-      radius = circle.attr "r"
+    else if shape is "circle"
+      radius = prev.attr "r"
       vars.self.width parseFloat radius * 2, 10 unless vars.width.value
       vars.self.height parseFloat radius * 2, 10 unless vars.height.value
     else


cleanup

cleanup

--- scripts/release.coffee
+++ scripts/release.coffee
@@ -1,6 +1,6 @@
 require('dotenv').config()
 pkg = require('../package.json')
-version = require('./version')
+version = require('../webpack/version')
 path = require('path')

 Bluebird = require('bluebird')


Use default message handler

Use default message handler

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -12,6 +12,6 @@
     if argv.v
       console.log JSON.parse(fs.readFileSync('package.json')).version
     else if argv.h
-      optimist.showHelp(console.log)
+      optimist.showHelp()
     else
-      optimist.showHelp(console.log)
+      optimist.showHelp()


fix image regex

fix image regex

--- src/modules/image-tooltip.coffee
+++ src/modules/image-tooltip.coffee
@@ -64,7 +64,7 @@
       @preview.replaceChild(img, @preview.firstChild)

   _matchImageURL: (url) ->
-    return /^https?:\/\/.+\.(jp?g|gif|png)$/.test(url)
+    return /^https?:\/\/.+\.(jpe?g|gif|png)$/.test(url)

   _normalizeURL: (url) ->
     # For now identical to link-tooltip but will change when we allow data uri


Better guard for trimming locations

Better guard for trimming locations

--- apps/artwork_2/components/partner/helpers.coffee
+++ apps/artwork_2/components/partner/helpers.coffee
@@ -1,4 +1,4 @@
-{ uniq } = require 'underscore'
+{ uniq, compact } = require 'underscore'
 { numberFormat } = require 'underscore.string'

 module.exports =
@@ -14,5 +14,5 @@
       irregular or word + 's'

   cities: (locations) ->
-    uniq locations.map ({ city }) -> city.trim()
+    uniq compact(locations.map ({ city }) -> city?.trim())
       .join ' • '