Spreadsheet very close to restored functionality

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -40,6 +40,7 @@
         type: 'number'
         release: constants.release.PUBLIC
         sort: -10000
+        readonly: true
       name: # slot, asset
         id: 'name'
         name: 'name'
@@ -54,21 +55,26 @@
       release: # slot
         id: 'release'
         name: 'release'
+        type: 'release'
         sort: -7000
       classification: # asset
         id: 'classification'
         name: 'classification'
+        type: 'classification'
         sort: -6000
+        readonly: true
       excerpt: # asset
         id: 'excerpt'
         name: 'excerpt'
         sort: -5000
+        readonly: true
       age: # record
         id: 'age'
         name: 'age'
         type: 'number'
         release: constants.release.EXCERPTS
         sort: constants.metric.birthdate + 0.5
+        readonly: true
     constants.deepFreeze(pseudoMetric)
     getMetric = (m) ->
       pseudoMetric[m] || constants.metric[m]
@@ -131,7 +137,7 @@
           asset:
             id: 'asset'
             name: 'file'
-            not: 'No file'
+            not: 'No files'
             template: ['name', 'classification', 'excerpt']
             sort: 10000
             fixed: true
@@ -150,6 +156,7 @@
           #   col: Cols element
           #   category: Category
           #   c: Category_id
+          #   count: Count[i][c]
           #   metric: Metric
           #   row: Rows[i]
           #   slot: Slots[i]
@@ -190,10 +197,10 @@
             n: ->
               0
             id: ->
-              cell.id if (cell = @cell)
+              @cell?.id
             cols: ->
-              if (c = @c)?
-                @cols = Groups.find (col) -> `col.category.id == c`
+              c = @c
+              @cols = Groups.find (col) -> `col.category.id == c`
             col: ->
               Cols[@m]
             category: ->
@@ -203,6 +210,8 @@
                 @category = getCategory(@c)
             c: ->
               @category?.id
+            count: ->
+              Counts[@i][@c] || 0
             metric: ->
               @col?.metric
             row: ->
@@ -210,13 +219,13 @@
             slot: ->
               Slots[@i]
             d: ->
-              Data[c].id[@n]?[@i] if (c = @c)?
+              Data[@c].id[@n]?[@i]
             record: ->
               Volume.records[@d]
             asset: ->
               @slot?.assets[@d]
             v: ->
-              Data[c][m][@n]?[@i] if (c = @c)? and (m = @m)?
+              Data[@c][@m]?[@n]?[@i]

           property = (v, f) ->
             get: f
@@ -371,24 +380,20 @@
                   t = {asset:info.d}
                 else
                   a.className = "session icon hint-action-slot"
+                  #if Editing && !stop
+                  #  a = cell.insertBefore(document.createElement('a'), cell.firstChild)
+                  #  a.className = 'trash icon'
+                  #  $(a).on 'click', (event) ->
+                  #    $scope.$apply () ->
+                  #      removeSlot(cell, i, slot)
+                  #      return
+                  #    event.stopPropagation()
+                  #    return
                   v ?= if info.slot.id == Volume.top.id then constants.message('materials.top') else ''
                   t = {}
                 a.setAttribute('href', if Editing then info.slot.editRoute(t) else info.slot.route(t))
               else
                 v ?= ''
-              #if Editing && !stop
-              #  a = cell.insertBefore(document.createElement('a'), cell.firstChild)
-              #  a.className = 'trash icon'
-              #  $(a).on 'click', (event) ->
-              #    $scope.$apply () ->
-              #      removeSlot(cell, i, slot)
-              #      return
-              #    event.stopPropagation()
-              #    return
-              #
-              #  icon = cell.insertBefore(document.createElement('img'), cell.firstChild)
-              #  icon.src = a.icon
-              #  icon.className = "format hint-format-" + a.format.extension
             when 'release', 'classification'
               cn = constants.release[v || 0]
               info.cell.className = cn + ' release icon hint-release-' + cn
@@ -422,12 +427,12 @@
           return

         generateMultiple = (info) -> # (col, cols, row, i, n, t) ->
-          t = Counts[info.i][info.c] || 0
-          return if (if info.n? then info.n < t else t == 1)
+          t = info.count
+          return if (if info.hasOwnProperty('n') then info.n < t else t == 1)
           td = info.row.appendChild(document.createElement('td'))
           width = info.cols.metrics.length
           td.setAttribute("colspan", width)
-          if info.n? || t <= 1
+          if info.hasOwnProperty('n') || t <= 1
             td.className = 'null'
             if !info.n || info.n == t
               if Editing && width > 1
@@ -460,10 +465,10 @@
         generateRecord = (info) -> # (row, i, col, n) ->
           ms = info.cols.metrics
           return unless l = ms.length
-          t = Counts[info.i][info.c] || 0
+          t = info.count
           r = Data[info.c]
           if td = generateMultiple(info) # (col, l, row, i, n, t)
-            unless info.n?
+            unless info.hasOwnProperty('n')
               for n in [0..t-1] by 1
                 td.classList.add('ss-rec_' + r.id[n][info.i])
             return
@@ -481,26 +486,6 @@
               info.cell.classList.add(ri + '_' + info.metric.id)
           return

-        #generateAsset = (row, i, n) ->
-        #  a = assets[i]
-        #  return if generateMultiple({category:pseudoCategory.asset}, 3, row, i, n, a.length)
-        #  b = i
-        #  if n == undefined
-        #    a = a[0]
-        #  else
-        #    a = a[n]
-        #    b += '_' + n
-        #  cell = generateCell(row, 'asset', a.name, ID + '-asset_' + b)
-        #  icon = cell.insertBefore(document.createElement('img'), cell.firstChild)
-        #  icon.src = a.icon
-        #  icon.onclick = () ->
-        #    t = {asset:a.id}
-        #    $location.url if Editing then Slots[i].editRoute(t) else Slots[i].route(t)
-        #  icon.className = "format hint-format-" + a.format.extension
-        #  generateCell(row, 'classification', a.release, ID + '-class_' + b)
-        #  generateCell(row, 'excerpt', a.excerpt?, ID + '-excerpt_' + b)
-        #  return
-
         # Fill out rows[i].
         generateRow = (i) ->
           info = new Info()
@@ -515,32 +500,9 @@
           if Editing && info.slot.id == Volume.top.id
             row.className = 'top'

-          #name = slot.name
-          #if stop
-          #  name ?= constants.message('materials.top')
-          #cell = generateCell(row, 'name', name, ID + '-name_' + i)
-          #if Editing && !stop
-          #  a = cell.insertBefore(document.createElement('a'), cell.firstChild)
-          #  a.className = 'trash icon'
-          #  $(a).on 'click', (event) ->
-          #    $scope.$apply () ->
-          #      removeSlot(cell, i, slot)
-          #      return
-          #    event.stopPropagation()
-          #    return
-          #a = cell.insertBefore(document.createElement('a'), cell.firstChild)
-          #a.setAttribute('href', if Editing then slot.editRoute() else slot.route())
-          #a.className = "session icon hint-action-slot"
-
-          #unless slot.top
-          #  generateCell(row, 'date', slot.date, ID + '-date_' + i)
-          #  generateCell(row, 'release', slot.release, ID + '-release_' + i)
-
           for col in Groups
             info.category = (info.cols = col).category
             generateRecord(info)
-          #if assets
-          #  generateAsset(row, i)
           return

         # Update all age displays.
@@ -552,12 +514,12 @@
             r = Data[info.c][info.metric.id]
             pre = ID + '-rec_'
             post = '_' + mi
-            if expandedCat == info.c && Counts[expanded][info.c] > 1
-              info.i = expanded
-              for n in [0..Counts[expanded][info.c]-1] by 1 when n of r
+            if expanded?.c == info.c && expanded.count > 1
+              info.i = expanded.i
+              for n in [0..info.count-1] by 1 when n of r
                 info.n = n
-                info.v = r[n][expanded]
-                info.cell = document.getElementById(pre + expanded + '_' + n + post)
+                info.v = r[n][info.i]
+                info.cell = document.getElementById(pre + info.i + '_' + n + post)
                 generateText(info) if info.cell
             return unless 0 of r
             r = r[0]
@@ -696,18 +658,6 @@
             populate()
             return

-        #saveMeasure = (cell, record, metric, v) ->
-        #  return if record.measures[metric.id] == v
-        #  saveRun cell, record.measureSet(metric.id, v).then (rec) ->
-        #    rcm = Data[rec.category || 0][metric.id]
-        #    for i, n of Depends[record.id]
-        #      arr(rcm, n)[i] = v
-        #      # TODO age may have changed... not clear how to update.
-        #    l = TBody.getElementsByClassName('ss-rec_' + record.id + '_' + metric.id)
-        #    for li in l
-        #      generateText(li, metric.id, v, metric.assumed)
-        #    return
-
         setRecord = (info, record) ->
           add = ->
             if record
@@ -721,6 +671,7 @@
               add()

           saveRun info.cell, act.then (record) ->
+            o = info.d
             if record
               r = record.id
               info.n = inc(Counts[info.i], info.c) unless info.record
@@ -739,7 +690,7 @@
                   arr(rcm, n-1)[info.i] = arr(rcm, n)[info.i]
                 delete rcm[t][info.i] if t of rcm

-            delete Depends[info.r][info.i] if info.record
+            delete Depends[o][info.i] if o?
             obj(Depends, r)[info.i] = info.n if record

             collapse()
@@ -750,22 +701,13 @@
             setFocus = undefined
             record

-        #saveAsset = (cell, info, v) ->
-        #  data = {}
-        #  t = info.t
-        #  t = 'name' if t == 'asset'
-        #  data[t] = v ? ''
-        #  return if info.asset[t] == data[t]
-        #  saveRun cell, info.asset.save(data).then () ->
-        #    generateText(cell, t, info.asset[t])
-        #    return
-
         saveDatum = (info, v) ->
           if info.c == 'slot'
             data = {}
             data[info.t] = v ? ''
             return if info.slot[info.t] == data[info.t]
             saveRun info.cell, info.slot.save(data).then () ->
+              info.v = Data[info.c][info.metric.id][info.n][info.i] = v
               generateText(info) #, cell, info.t, info.slot[info.t])
               return
           else if info.c == 'asset'
@@ -774,6 +716,7 @@
             data[t] = v ? ''
             return if info.asset[t] == data[t]
             saveRun info.cell, info.asset.save(data).then () ->
+              info.v = Data[info.c][info.metric.id][info.n][info.i] = v
               generateText(info)
               return
           else
@@ -783,6 +726,7 @@
               for i, n of Depends[info.d]
                 arr(rcm, n)[i] = v
                 # TODO age may have changed... not clear how to update.
+              info.v = v
               l = TBody.getElementsByClassName('ss-rec_' + info.d + '_' + info.metric.id)
               for li in l
                 info.cell = li
@@ -791,14 +735,13 @@

         ################################# Interaction

-        expandedCat = undefined
-        expanded = undefined
+        expanded = undefined # Info

         # Collapse any expanded row.
         collapse = ->
-          return if expanded == undefined
-          i = expanded
-          expanded = expandedCat = undefined
+          return unless expanded
+          i = expanded.i
+          expanded = undefined
           row = Rows[i]
           row.classList.remove('expand')
           t = 0
@@ -815,34 +758,35 @@

         # Expand (or collapse) a row
         expand = (info) ->
-          if expanded == info.i && expandedCat == info.c
+          if expanded?.i == info.i && `expanded.c == info.c`
             if info.t == 'more'
               collapse()
             return
           collapse()

-          expanded = info.i
-          expandedCat = info.c
-          row = Rows[expanded]
-          row.classList.add('expand')
-
-          max = Counts[expanded][expandedCat]
-          max++ if Editing && !info.category.fixed
+          expanded = new Info()
+          expanded.i = info.i
+          expanded.c = info.c
+          info.row.classList.add('expand')
+
+          max = expanded.count
+          max++ if Editing && !expanded.category.fixed
           return if max <= 1
-          next = row.nextSibling
-          start = Counts[expanded][expandedCat] == 1
+          next = info.row.nextSibling
+          start = expanded.count == 1
           for n in [+start..max-1] by 1
-            info.row = TBody.insertBefore(document.createElement('tr'), next)
-            info.row.data = expanded
-            info.row.className = 'expand'
-            info.n = n
-            generateRecord(info)
+            expanded.n = n
+            expanded.row = TBody.insertBefore(document.createElement('tr'), next)
+            expanded.row.data = expanded.i
+            expanded.row.className = 'expand'
+            generateRecord(expanded)
+          expanded.row = info.row

           max++ unless start
-          el = row.firstChild
+          el = info.row.firstChild
           while el
             info = new Info(el)
-            if `info.c != expandedCat`
+            if `info.c != expanded.c`
               el.setAttribute("rowspan", max)
             el = el.nextSibling
           return
@@ -857,7 +801,7 @@
               if value == 'new'
                 setRecord(info)
               else if value == 'remove'
-                setRecord(info, null) if info.r?
+                setRecord(info, null) if info.d?
               else if v = stripPrefix(value, 'add_')
                 u = v.indexOf('_')
                 info.metric = constants.metric[v.slice(0,u)]
@@ -867,7 +811,7 @@
                   saveDatum(info, v) if r
                   return
               else if !isNaN(v = parseInt(value, 10))
-                if v != info.r
+                if v != info.d
                   setRecord(info, Volume.records[v])
               return
             when 'metric'
@@ -894,13 +838,6 @@
             return

           saveDatum(info, value)
-          #switch info.t
-          #  when 'name', 'date', 'release'
-          #    saveSlot(cell, info, value)
-          #  when 'rec'
-          #    saveMeasure(cell, info.record, info.metric, value)
-          #  when 'asset'
-          #    saveAsset(cell, info, value)

         editScope = $scope.$new(true)
         editScope.constants = constants
@@ -930,45 +867,58 @@

         edit = (info) ->
           switch info.t
-            when 'rec', 'add'
+            when 'rec'
               if info.c == 'asset'
                 # for now, just go to slot edit
                 $location.url(info.slot.editRoute())
                 return
               return if info.slot.id == Volume.top.id
-              if info.t == 'rec' && info.metric.id == 'id'
+              m = info.metric.id
+              if m == 'id'
                 # trash/bullet: remove
                 setRecord(info, null)
                 return
-              if info.t == 'rec'
-                m = info.metric.id
-                # we need a real metric here:
-                return unless typeof m == 'number'
-                editInput.value = info.record?.measures[m] ? ''
-                if info.col.first
-                  editScope.type = 'ident'
-                  editScope.info = info
-                  rs = []
-                  mf = (r) -> (m) -> r.measures[m]
-                  for ri, r of Volume.records
-                    if (r.category || 0) == info.category.id && !(ri of Depends && info.i of Depends[ri])
-                      rs.push
-                        r:r
-                        v:(r.measures[info.metric.id] ? '').toLowerCase()
-                        d:recordDescription(r)
-                  editScope.records = rs.sort((a, b) -> byMagic(a.v, b.v))
-                else if info.metric.options
-                  editScope.type = 'options'
-                  editScope.options = info.metric.options
-                else if info.metric.long
-                  editScope.type = 'long'
+              return if info.metric.readonly
+              editScope.type = info.metric.type
+              switch info.c
+                when 'slot'
+                  v = info.slot[m]
+                  if m == 'release'
+                    ### jshint ignore:start ###
+                    v ||= 0
+                    ### jshint ignore:end ###
+                when 'asset' # not reached
+                  v = info.asset[m]
                 else
-                  editScope.type = info.metric.type
-                break
-            # when 'add', fall-through
+                  v = info.record?.measures[m]
+                  if info.col.first
+                    editScope.type = 'ident'
+                    editScope.info = info
+                    rs = []
+                    mf = (r) -> (m) -> r.measures[m]
+                    for ri, r of Volume.records
+                      if (r.category || 0) == info.category.id && !(ri of Depends && info.i of Depends[ri])
+                        rs.push
+                          r:r
+                          v:(r.measures[info.metric.id] ? '').toLowerCase()
+                          d:recordDescription(r)
+                    editScope.records = rs.sort((a, b) -> byMagic(a.v, b.v))
+                  else if info.metric.options
+                    editScope.type = 'options'
+                    editScope.options = info.metric.options
+                  else if info.metric.long
+                    editScope.type = 'long'
+                  break
+              editInput.value = (v ? '')+''
+            when 'add'
+              if info.c == 'asset'
+                # for now, just go to slot edit
+                $location.url(info.slot.editRoute())
+                return
+              return if info.slot.id == Volume.top.id
               c = info.category
-              if 'r' of info
-                editInput.value = info.r + ''
+              if info.d?
+                editInput.value = info.d + ''
               else
                 editInput.value = 'remove'
               editScope.type = 'record'
@@ -1041,7 +991,7 @@
           unselect()
           expand(info)
           if info.t == 'rec'
-            for c, ci in info.cell.classList when c.startsWith('ss-rec_')
+            for c in info.cell.classList when c.startsWith('ss-rec_')
               styles.set('.' + c + '{background-color:' +
                 (if c.includes('_', 7) then 'rgba(226,217,0,0.6)' else 'rgba(242,238,100,0.4)') +
                 ';\n text-}')
@@ -1094,7 +1044,7 @@
               run: f
               default: d && !defd
             defd ||= d
-          if info.r
+          if info.d?
             if input == info.record.measures[info.metric.id]
               add("Keep " + info.record.displayName,
                 () -> return,
@@ -1103,7 +1053,7 @@
               add("Remove " + info.record.displayName + " from this session",
                 (info) -> setRecord(info, null),
                 true)
-          if !info.r || input && input != info.record.measures[info.metric.id]
+          if !info.d? || input && input != info.record.measures[info.metric.id]
             inputl = (input ? '').toLowerCase()
             set = (r) -> (info) ->
               setRecord(info, r)
@@ -1117,7 +1067,7 @@
             if input && !os.length
               os = [input]
             os.forEach (i) ->
-              if info.r
+              if info.d?
                 add("Change all " + info.record.displayName + " " + info.metric.name + " to '" + i + "'",
                   (info) -> saveDatum(info, i),
                   input && !rs.length && os.length == 1 || i == input)


make background flush more adaptive

make background flush more adaptive

--- app/coffee/DeleteQueueManager.coffee
+++ app/coffee/DeleteQueueManager.coffee
@@ -64,10 +64,12 @@
         flushNextProject()

     startBackgroundFlush: () ->
+        SHORT_DELAY = 10
+        LONG_DELAY = 1000
         doFlush = () ->
             if Settings.shuttingDown
                 logger.warn "discontinuing background flush due to shutdown"
                 return
-            DeleteQueueManager.flushAndDeleteOldProjects {timeout:1000,min_delete_age:3*60*1000,limit:1000}, () ->
-                setTimeout doFlush, 10
+            DeleteQueueManager.flushAndDeleteOldProjects {timeout:1000,min_delete_age:3*60*1000,limit:1000}, (err, flushed) ->
+                setTimeout doFlush, (if flushed > 10 then SHORT_DELAY else LONG_DELAY)
         doFlush()


refactor: check if appknox is true

refactor: check if appknox is true

--- app/components/submission-box.coffee
+++ app/components/submission-box.coffee
@@ -10,26 +10,20 @@
   cookies: Ember.inject.service()

   didInsertElement: ->
+    cookies = @get 'cookies'
     if ENV.isAppknox is true
-      cookies = @get 'cookies'
       name = tourName(ENV.TOUR.newScan)
-      try
-        readCookies = cookies.read()
-        cookieKey = readCookies[name]
-        if cookieKey isnt "true"
+    else
+      name = tourName(ENV.TOUR.devknoxTour)
+    try
+      readCookies = cookies.read()
+      cookieKey = readCookies[name]
+      if cookieKey isnt "true"
+        if ENV.isAppknox is true
           this.set('onboard.activeTour', ENV.TOUR.newScan)
-          cookies.write(name, true)
-
-    else if ENV.isDevknox is true
-       cookies = @get 'cookies'
-       name = tourName(ENV.TOUR.devknoxTour)
-       try
-         readCookies = cookies.read()
-         cookieKey = readCookies[name]
-         if cookieKey isnt "true"
-           this.set('onboard.activeTour', ENV.TOUR.devknoxTour)
-           cookies.write(name, true)
-
+        else
+          this.set('onboard.activeTour', ENV.TOUR.devknoxTour)
+        cookies.write(name, true)

   actions:
     startTour: ->


Add helper methods

Add helper methods

--- src/stars.coffee
+++ src/stars.coffee
@@ -57,41 +57,50 @@
         message = body.message ? body.error ? body
         callback("Requesting packages failed: #{message}")

+  installPackages: (packages, callback) ->
+    return callback() if packages.length is 0
+
+    commandArgs = packages.map ({name}) -> name
+    new Install().run({commandArgs, callback})
+
+  logPackagesAsJson: (packages, callback) ->
+    console.log(JSON.stringify(packages))
+    callback()
+
+  logPackagesAsText: (user, packagesAreThemes, packages, callback) ->
+    userLabel = user ? 'you'
+    if packagesAreThemes
+      label = "Themes starred by #{userLabel}"
+    else
+      label = "Packages starred by #{userLabel}"
+    console.log "#{label.cyan} (#{packages.length})"
+
+    tree packages, ({name, version, description, downloads}) ->
+      label = name.yellow
+      label = "\u2B50  #{label}" if process.platform is 'darwin'
+      label += " #{description.replace(/\s+/g, ' ')}" if description
+      label += " (#{_.pluralize(downloads, 'download')})".grey if downloads >= 0
+      label
+
+    console.log()
+    console.log "Use `apm install` to install them or visit #{'http://atom.io/packages'.underline} to read more about them."
+    console.log()
+    callback()
+
   run: (options) ->
     {callback} = options
     options = @parseOptions(options.commandArgs)
     user = options.argv.user?.toString().trim()

-    @getStarredPackages user, (error, packages) ->
+    @getStarredPackages user, (error, packages) =>
       return callback(error) if error?

       if options.argv.themes
         packages = packages.filter ({theme}) -> theme

       if options.argv.install
-        return callback() if packages.length is 0
-
-        commandArgs = packages.map ({name}) -> name
-        new Install().run({commandArgs, callback})
+        @installPackages(packages, callback)
       else if options.argv.json
-        console.log(JSON.stringify(packages))
-        callback()
+        @logPackagesAsJson(packages, callback)
       else
-        userLabel = user ? 'you'
-        if options.argv.themes
-          label = "Themes starred by #{userLabel}"
-        else
-          label = "Packages starred by #{userLabel}"
-        console.log "#{label.cyan} (#{packages.length})"
-
-        tree packages, ({name, version, description, downloads}) ->
-          label = name.yellow
-          label = "\u2B50  #{label}" if process.platform is 'darwin'
-          label += " #{description.replace(/\s+/g, ' ')}" if description
-          label += " (#{_.pluralize(downloads, 'download')})".grey if downloads >= 0
-          label
-
-        console.log()
-        console.log "Use `apm install` to install them or visit #{'http://atom.io/packages'.underline} to read more about them."
-        console.log()
-        callback()
+        @logPackagesAsText(user, options.argv.themes, packages, callback)


Blur list input when you create a new list.

Makes using the keyboard shortcuts a lot nicer.

--- source/scripts/views/lists.coffee
+++ source/scripts/views/lists.coffee
@@ -33,7 +33,7 @@
   keyup: (e) =>
     if e.which is keys.enter and @ui.input.val().length
       @createNew @ui.input.val()
-      @ui.input.val ''
+      @ui.input.blur().val ''

   # Create a new list
   # - name (string) : the name of the list


:lipstick: spec

:lipstick: spec

Use the event system to click on the gutter

--- spec/editor-component-spec.coffee
+++ spec/editor-component-spec.coffee
@@ -228,14 +228,12 @@
           [node]

   describe "gutter rendering", ->
-    {lineNumberHasClass, gutter} = {}
+    [lineNumberHasClass, gutter] = []
+
     beforeEach ->
       {gutter} = component.refs
       lineNumberHasClass = (screenRow, klass) ->
-        if screenRow.classList?
-          screenRow.classList.contains(klass)
-        else
-          component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
+        component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)

     it "renders the currently-visible line numbers", ->
       node.style.height = 4.5 * lineHeightInPixels + 'px'
@@ -346,34 +344,29 @@
         [gutterNode] = []

         buildClickEvent = (target) ->
-          # FIXME: I could not get the simulated event to set the target. I tried several things, and was unable to get it set.
-          # buildMouseEvent('click', {target})
-          {target, type: 'click', bubbles: true, cancelable: true}
+          buildMouseEvent('click', {target})

         beforeEach ->
           gutterNode = node.querySelector('.gutter')

         it "folds and unfolds the block represented by the fold indicator when clicked", ->
-          lineNumber = gutterNode.querySelectorAll('.line-number')[2] # bufferRow 1
-          expect(lineNumberHasClass(lineNumber, 'folded')).toBe false
-
+          expect(lineNumberHasClass(1, 'folded')).toBe false
+
+          lineNumber = component.lineNumberNodeForScreenRow(1)
           target = lineNumber.querySelector('.icon-right')
-          # target.dispatchEvent(buildClickEvent(target))
-          component.onClickGutter(buildClickEvent(target))
-          lineNumber = gutterNode.querySelectorAll('.line-number')[2] # bufferRow 1
-          expect(lineNumberHasClass(lineNumber, 'folded')).toBe true
-
+
+          target.dispatchEvent(buildClickEvent(target))
+          expect(lineNumberHasClass(1, 'folded')).toBe true
+
+          lineNumber = component.lineNumberNodeForScreenRow(1)
           target = lineNumber.querySelector('.icon-right')
-          # target.dispatchEvent(buildClickEvent(target))
-          component.onClickGutter(buildClickEvent(target))
-          lineNumber = gutterNode.querySelectorAll('.line-number')[2] # bufferRow 1
-          expect(lineNumberHasClass(lineNumber, 'folded')).toBe false
+
+          target.dispatchEvent(buildClickEvent(target))
+          expect(lineNumberHasClass(1, 'folded')).toBe false

         it "does not fold when the line number node is clicked", ->
-          lineNumber = gutterNode.querySelectorAll('.line-number')[2]
-          component.onClickGutter(buildClickEvent(lineNumber))
-          lineNumber = gutterNode.querySelectorAll('.line-number')[2]
-          expect(lineNumberHasClass(lineNumber, 'folded')).toBe false
+          component.onClickGutter(buildClickEvent(component.lineNumberNodeForScreenRow(1)))
+          expect(lineNumberHasClass(1, 'folded')).toBe false

     describe "when decorations are used", ->
       describe "when decorations are applied to buffer rows", ->
@@ -853,12 +846,6 @@
       clientX = scrollViewClientRect.left + positionOffset.left - editor.getScrollLeft()
       clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop()
       {clientX, clientY}
-
-    buildMouseEvent = (type, properties...) ->
-      properties = extend({bubbles: true, cancelable: true}, properties...)
-      event = new MouseEvent(type, properties)
-      Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
-      event

   describe "focus handling", ->
     inputNode = null
@@ -1164,3 +1151,12 @@
         editor.setCursorBufferPosition([0, Infinity])
         wrapperView.show()
         expect(node.querySelector('.cursor').style['-webkit-transform']).toBe "translate3d(#{9 * charWidth}px, 0px, 0px)"
+
+  buildMouseEvent = (type, properties...) ->
+    properties = extend({bubbles: true, cancelable: true}, properties...)
+    event = new MouseEvent(type, properties)
+    Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
+    if properties.target?
+      Object.defineProperty(event, 'target', get: -> properties.target)
+      Object.defineProperty(event, 'srcObject', get: -> properties.target)
+    event


Fix spec on windows

Fix spec on windows

--- spec/keybinding-panel-spec.coffee
+++ spec/keybinding-panel-spec.coffee
@@ -1,3 +1,4 @@
+path = require 'path'
 KeybindingPanel = require '../lib/keybinding-panel'

 describe "KeybindingPanel", ->
@@ -7,7 +8,7 @@
     beforeEach ->
       expect(atom.keymap).toBeDefined()
       spyOn(atom.keymap, 'getKeyBindings').andReturn [
-        source: '/atom', keystroke: 'ctrl-a', command: 'core:select-all', selector: '.editor'
+        source: "#{path.sep}atom", keystroke: 'ctrl-a', command: 'core:select-all', selector: '.editor'
       ]
       panel = new KeybindingPanel


Revert "Handle asynchronous save"

Revert "Handle asynchronous save"

Pushed by mistake
This reverts commit 4eeeede719dbdf012631f9a08e2a06fd3081e51a.

--- lib/idris-controller.coffee
+++ lib/idris-controller.coffee
@@ -107,11 +107,10 @@
         null

   saveFile: (editor) ->
-    waitsForPromise ->
-      if editor.getURI()
-        editor.save()
-      else
-        atom.workspace.saveActivePaneItemAs()
+    if editor.getURI()
+      editor.save()
+    else
+      atom.workspace.saveActivePaneItemAs()

   typecheckFile: (event) =>
     editor = @getEditor()


Rename main function to game_loop

Rename main function to game_loop

--- src/tanks.coffee
+++ src/tanks.coffee
@@ -7,11 +7,12 @@
   canvas.setHeight 480
   canvas.setWidth  640

+  game_delay  = 10
+  turn_delay  = 20
   tank_width  = 20
   tank_height = 20
-
-  tank1 = null
-  tank2 = null
+  tank1       = null
+  tank2       = null

   new fabric.Image.fromURL 'tank1.png', (img) ->
     tank1 = img
@@ -76,7 +77,7 @@

   turn_tank = (tank, offset) ->
     tank.tank_turn_cnt += 1
-    if tank.tank_turn_cnt > 20
+    if tank.tank_turn_cnt > turn_delay
       tank.tank_turn_cnt = 0
       # See http://javascript.about.com/od/problemsolving/a/modulobug.htm
       tank.tank_direction = (((tank.tank_direction + offset) % 4) + 4) % 4
@@ -113,22 +114,23 @@
         tank.set arg
         tank.setCoords()

-  check_keyboard = ->
+  update_game = ->
+    move_tank keyboard.tank1_up,
+              keyboard.tank1_right,
+              keyboard.tank1_down,
+              keyboard.tank1_left,
+              tank1
+
+    move_tank keyboard.tank2_up,
+              keyboard.tank2_right,
+              keyboard.tank2_down,
+              keyboard.tank2_left,
+              tank2
+
+  game_loop = ->
+    setTimeout game_loop, game_delay
     if tank1 and tank2
-      move_tank keyboard.tank1_up,
-                keyboard.tank1_right,
-                keyboard.tank1_down,
-                keyboard.tank1_left,
-                tank1
-
-      move_tank keyboard.tank2_up,
-                keyboard.tank2_right,
-                keyboard.tank2_down,
-                keyboard.tank2_left,
-                tank2
-
+      update_game()
       canvas.renderAll()

-    setTimeout check_keyboard, 10
-
-  setTimeout check_keyboard, 10
+  setTimeout game_loop, game_delay


Fix script not working for creating of PR

For the non-slack version, the "msg=" line was at the wrong point,
in the wrong if clause. Must somehow be moved. I also adapted the
syntax for the creation of the reviewer-list to seperate it by
comma and also put a colon after the reviewers to separate them
from the author.

--- src/bitbucket-pr.coffee
+++ src/bitbucket-pr.coffee
@@ -57,15 +57,13 @@
         resp = data.pullrequest_created

         if resp.reviewers.length > 0
-          reviewers = ''
-          for reviewer in resp.reviewers
-            reviewers += " #{reviewer.display_name}"
+          reviewers = (resp.reviewers.map (r) -> r.display_name).join(", ")
         else
-          reviewers = ' no one in particular'
+          reviewers = 'no one in particular'

         content =
           text: "#{resp.author.display_name} created a new request"
-          fallback: "Yo#{reviewers}, #{resp.author.display_name} just *created* the pull request \"#{resp.title}\" for `#{resp.destination.branch.name}` on `#{repo_name}`.\n#{resp.links.html.href}"
+          fallback: "Yo #{reviewers}: #{resp.author.display_name} just *created* the pull request \"#{resp.title}\" for `#{resp.destination.branch.name}` on `#{repo_name}`.\n#{resp.links.html.href}"
           pretext: reviewers
           color: blue
           mrkdwn_in: ["text", "title", "fallback", "fields"]
@@ -166,19 +164,16 @@
       if data.hasOwnProperty('pullrequest_created') && ('created' in announce_options)
         resp = data.pullrequest_created
         if resp.reviewers.length > 0
-          reviewers = ''
-          for reviewer in resp.reviewers
-            reviewers += " #{reviewer.display_name}"
+          reviewers = (resp.reviewers.map (r) -> r.display_name).join(", ")
         else
-          reviewers = ' no one in particular'
+          reviewers = 'no one in particular'
+        msg = "Yo #{reviewers}: #{resp.author.display_name} just *created* the pull request \"#{resp.title}\" for `#{resp.destination.branch.name}` on `#{repo_name}`.\n#{resp.links.html.href}"

       # Comment created
       if data.hasOwnProperty('pullrequest_comment_created') && ('comment_created' in announce_options)
         resp = data.pullrequest_comment_created
         msg = "#{resp.user.display_name} *added a comment* on `#{repo_name}`: \"#{resp.content.raw}\" "
         msg += "\n#{resp.links.html.href}"
-
-        msg = "Yo#{reviewers}, #{resp.author.display_name} just *created* the pull request \"#{resp.title}\" for `#{resp.destination.branch.name}` on `#{repo_name}`.\n#{resp.links.html.href}"

       # Declined
       if data.hasOwnProperty('pullrequest_declined') && ('declined' in announce_options)


Docs para Conjunto Disjunto

Docs para Conjunto Disjunto

--- estructuras-de-datos/conjunto-disjunto.coffee
+++ estructuras-de-datos/conjunto-disjunto.coffee
@@ -10,11 +10,21 @@
       @padres[llave] = llave
       @ranks[llave] = 0

+  ###*
+   * Busca el subconjunto al que pertenece el elemento
+   * @param {number|string} Llave del elemento a buscar
+   * @return {number|string|array} Subconjunto al que pertenece el elemento
+  ###
   buscar: (llave) ->
     if @padres[llave] != llave
       @padres[llave] = @buscar(@padres[llave])
     return @padres[llave]

+  ###*
+   * Une los elementos de dos subconjuntos en uno solo.
+   * @param {number|string} Llave de un elemento de un subconjunto
+   * @param {number|string} Llave de otro elemento de un subconjunto
+  ###
   union: (x, y) ->
     x_raiz = @buscar(x)
     y_raiz = @buscar(y)


:art: main.coffee

:art: main.coffee

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -6,22 +6,13 @@
 path = require 'path'
 yargs = require 'yargs'
 url = require 'url'
-nslog = require 'nslog'
-
-console.log = nslog
-
-process.on 'uncaughtException', (error={}) ->
-  nslog(error.message) if error.message?
-  nslog(error.stack) if error.stack?
+console.log = require 'nslog'

 start = ->
+  setupUncaughtExceptionHandler()
   setupAtomHome()
   setupCompileCache()
-
-  if process.platform is 'win32'
-    SquirrelUpdate = require './squirrel-update'
-    squirrelCommand = process.argv[1]
-    return if SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
+  return if handleStartupEventWithSquirrel()

   args = parseCommandLine()

@@ -29,16 +20,13 @@
     event.preventDefault()
     args.pathsToOpen.push(pathToOpen)

-  args.urlsToOpen = []
   addUrlToOpen = (event, urlToOpen) ->
     event.preventDefault()
     args.urlsToOpen.push(urlToOpen)

   app.on 'open-file', addPathToOpen
   app.on 'open-url', addUrlToOpen
-
-  app.on 'will-finish-launching', ->
-    setupCrashReporter()
+  app.on 'will-finish-launching', setupCrashReporter

   app.on 'ready', ->
     app.removeListener 'open-file', addPathToOpen
@@ -55,8 +43,8 @@
         path.resolve(pathToOpen)

     AtomApplication = require path.join(args.resourcePath, 'src', 'browser', 'atom-application')
+    AtomApplication.open(args)

-    AtomApplication.open(args)
     console.log("App load time: #{Date.now() - global.shellStartTime}ms") unless args.test

 normalizeDriveLetterName = (filePath) ->
@@ -69,12 +57,22 @@
   process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getHomeDir(), 'github', 'atom')
 )

+setupUncaughtExceptionHandler = ->
+  process.on 'uncaughtException', (error={}) ->
+    console.log(error.message) if error.message?
+    console.log(error.stack) if error.stack?
+
+handleStartupEventWithSquirrel = ->
+  return false unless process.platform is 'win32'
+  SquirrelUpdate = require './squirrel-update'
+  squirrelCommand = process.argv[1]
+  SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
+
 setupCrashReporter = ->
   crashReporter.start(productName: 'Atom', companyName: 'GitHub')

 setupAtomHome = ->
   return if process.env.ATOM_HOME
-
   atomHome = path.join(app.getHomeDir(), '.atom')
   try
     atomHome = fs.realpathSync(atomHome)
@@ -143,6 +141,7 @@
   logFile = args['log-file']
   socketPath = args['socket-path']
   profileStartup = args['profile-startup']
+  urlsToOpen = []

   if args['resource-path']
     devMode = true
@@ -169,7 +168,8 @@

   resourcePath = normalizeDriveLetterName(resourcePath)

-  {resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed,
-   devMode, safeMode, newWindow, specDirectory, logFile, socketPath, profileStartup}
+  {resourcePath, pathsToOpen, urlsToOpen, executedFrom, test, version,
+   pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile,
+   socketPath, profileStartup}

 start()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -19,7 +19,7 @@

       else
         f = Easing[data.easing](data.start, data.end)
-        I[property] = f(I.age - data.star)
+        I[property] = f()

   tween: (duration, properties) ->
     for property, target of properties


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -17,4 +17,4 @@
       I[property] = 10

-  tween: () ->
+  tween: (t, props) ->


BACK-695 Created routing and completion handlers

--- lib/service/dataLink.coffee
+++ lib/service/dataLink.coffee
@@ -80,6 +80,86 @@
       registeredCollections[collectionName] = new Collection(collectionName)

     return registeredCollections[collectionName]
+
+  initCompletionHandler = (task) ->
+    unless task.originalRequest?
+      task.originalRequest = task.request
+
+    return () ->
+
+    completionHandler = (entity, callback) ->
+      responseCallback = callback
+      result = task.request
+      result.body = entity
+      methods = {}
+
+      created = ->
+        result.statusCode = 201
+        return methods
+
+      accepted = ->
+        result.statusCode = 202
+        return methods
+
+      ok = ->
+        result.statusCode = 200
+        return methods
+
+      notFound = ->
+        result.statusCode = 404
+        return methods
+
+      badRequest = ->
+        result.statusCode = 400
+        return methods
+
+      unauthorized = ->
+        result.statusCode = 401
+        return methods
+
+      forbidden = ->
+        result.statusCode = 403
+        return methods
+
+      notAllowed = ->
+        result.statusCode = 405
+        return methods
+
+      notImplemented = ->
+        result.statusCode = 501
+        return methods
+
+      runtimeError = ->
+        result.statusCode = 550
+        return methods
+
+      done = ->
+        result.continue = false
+        task.request = result
+        responseCallback null, task
+
+      next = ->
+        result.continue = true
+        task.request = result
+        responseCallback null, task
+
+      methods =
+        created: created
+        accepted: accepted
+        ok: ok
+        done: done
+        next: next
+        notFound: notFound
+        badRequest: badRequest
+        unauthorized: unauthorized
+        forbidden: forbidden
+        notAllowed: notAllowed
+        notImplemented: notImplemented
+        runtimeError: runtimeError
+
+      return methods
+
+    return completionHandler

   process = (task, callback) ->
     unless task.collectionName?
@@ -121,7 +201,7 @@

       # TODO Add error trapping/handling for this code

-      operationHandler task.request, task.response, (err, result) ->
+      operationHandler task.request, completionHandler
         if err?
           return callback err


x closed strings

x closed strings

--- intervals.coffee
+++ intervals.coffee
@@ -104,20 +104,30 @@

   draw_note = (string_number, fret_number, root, color) ->
     y = v_gutter + (fret_number - 1) * fret_height + fret_height / 2
-    # y = v_gutter if fret_number == 0
     ctx.fillStyle = color or (if root then 'red' else 'white')
     ctx.strokeStyle = color or 'black'
     ctx.lineWidth = 1
     ctx.beginPath()
-    ctx.arc v_gutter + (6 - string_number) * string_spacing, y, note_radius, 0, 2 * Math.PI, false
+    ctx.arc h_gutter + (6 - string_number) * string_spacing, y, note_radius, 0, 2 * Math.PI, false
     ctx.fill() if fret_number
     ctx.stroke()
     ctx.strokeStyle = 'black'

-draw_fingerboard = ->
+draw_fingerboard = (fingerings) ->
   draw_strings()
   draw_frets()
+  if fingerings
+    fretted_strings = (false for _ in [0..string_number])
+    for {string, fret, root, color} in fingerings
+      fretted_strings[string] = true
+      draw_note string, fret, root, color
+    for is_fretted, string_number in fretted_strings
+      continue if is_fretted or string_number < 1
+      ctx.font = '4pt Helvetica'
+      ctx.fillStyle = 'black'
+      ctx.fillText "x", h_gutter + (6 - string_number) * string_spacing - 1, v_gutter - 2
+

 note_number_at = (string, fret) ->
   string_note_numbers[string - 1] + fret
@@ -133,14 +143,21 @@
   stream.on 'data', (chunk) -> out.write(chunk)
   stream.on 'end', () -> console.log "Saved #{fname}"

-draw_intervals_from = (semitones, root_string, root_fret, color) ->
+intervals_from = (root_string, root_fret, semitones) ->
   root_note_number = note_number_at(root_string, root_fret)
-  draw_note root_string, root_fret, true, color
+  fingerings = []
   for string in strings
     for fret in frets
       continue if string == root_string and fret == root_fret
       continue unless (note_number_at(string, fret) - root_note_number + 120) % 12 == semitones
-      draw_note string, fret, false, color
+      fingerings.push string: string, fret: fret
+  return fingerings
+
+draw_intervals_from = (semitones, root_string, root_fret, color) ->
+  root_note_number = note_number_at(root_string, root_fret)
+  draw_note root_string, root_fret, true, color
+  for {string, fret} in intervals_from(root_string, root_fret, semitones)
+    draw_note string, fret, false, color

 interval_cards = ->
   fingerings_each (string, fret) ->
@@ -309,19 +326,21 @@
     for ix in [0...12]
       pitch_number = (ix * 5 + 7) % 12
       fingering = pitch_fingers[pitch_number]
-      string = fingering[0]
-      fret = fingering[1]
+      root_string = fingering[0]
+      root_fret = fingering[1]
       draw_cell ->
         ctx.font = '20px Impact'
         ctx.font = '5pt Times' if draw_diagrams
         ctx.fillStyle = 'rgb(10,20,30)'
         ctx.fillText note_names[pitch_number] + ' ' + title, h_gutter, -3
-        draw_fingerboard()
-        for semitones, si in outervals
-          continue if semitones in intervals
-          draw_intervals_from semitones, string, fret, other_colors[si]
+        fingerings = []
+        # for semitones, si in outervals
+        #   continue if semitones in intervals
+        #   draw_intervals_from semitones, string, fret, other_colors[si]
         for semitones, si in intervals
-          draw_intervals_from semitones, string, fret, colors[si]
+          for {string, fret} in intervals_from(root_string, root_fret, semitones)
+            fingerings.push string: string, fret: fret, color: colors[si]
+        draw_fingerboard fingerings

 # chord_page 'Major', [0, 4, 7]

@@ -335,20 +354,20 @@

 chord_book = ->
   chords = [
-    ['Major', [0, 4, 7], [0, 3, 7]],
-    ['Minor', [0, 3, 7], [0, 4, 7]],
-    ['Aug', [0, 4, 8], [0, 4, 7]],
-    ['Dim', [0, 3, 6], [0, 3, 7]],
-    ['7th', [0, 4, 7, 10]],
-    ['Maj7', [0, 4, 7, 11]],
-    ['Sus2', [0, 2, 7], [0, 4, 7]],
-    ['Sus4', [0, 5, 7], [0, 4, 7]],
-    ['Min7', [0, 3, 7, 10], [0, 4, 7, 10]],
-    ['Dim7', [0, 3, 6, 9]],
-    ['6th', [0, 4, 7, 9]],
+    {name: 'Major', offsets: [0, 4, 7], relative: [0, 3, 7]},
+    {name: 'Minor', offsets: [0, 3, 7], relative: [0, 4, 7]},
+    {name: 'Aug', offsets: [0, 4, 8], relative: [0, 4, 7]},
+    {name: 'Dim', offsets: [0, 3, 6], relative: [0, 3, 7]},
+    {name: '7th', offsets: [0, 4, 7, 10]},
+    {name: 'Maj7', offsets: [0, 4, 7, 11]},
+    {name: 'Sus2', offsets: [0, 2, 7], relative: [0, 4, 7]},
+    {name: 'Sus4', offsets: [0, 5, 7], relative: [0, 4, 7]},
+    {name: 'Min7', offsets: [0, 3, 7, 10], relative: [0, 4, 7, 10]},
+    {name: 'Dim7', offsets: [0, 3, 6, 9]},
+    {name: '6th', offsets: [0, 4, 7, 9]},
   ]
-  book "Combined Fretboard Chords.pdf", (pager) ->
-    for chord in chords
-      pager -> chord_page chord[0], chord[1], chord[2], true
+  book "Combined Fretboard Chords.pdf", (page) ->
+    for {name, offsets, relative} in chords
+      page -> chord_page name, offsets, relative

 chord_book()


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -19,7 +19,7 @@
   player.update()

   # x, y position is based on the center point so
-  # the position the player is set
+  # the position the player is set to is
   player.I.x
   # => 2.5
 </pre></code>


switch note name array to midi pitches

switch note name array to midi pitches

--- intervals.coffee
+++ intervals.coffee
@@ -12,7 +12,7 @@
   'Octave', 'Minor 2nd', 'Major 2nd', 'Minor 3rd', 'Major 3rd', 'Perfect 4th',
   'Tritone', 'Perfect 5th', 'Minor 6th', 'Major 6th', 'Minor 7th', 'Major 7th']

-NoteNames = "E F F# G G# A A# B C C# D D#".split(/\s/)
+NoteNames = "G# A A# B C C# D D# E F F# G".split(/\s/)

 Chords = [
   {name: 'Major', abbr: '', offsets: [0, 4, 7]},
@@ -196,10 +196,15 @@
       ctx.restore()
       i += 1

-book = (filename, draw_book) ->
+book = (filename, options, draw_book) ->
+  draw_book = options if typeof options == 'function'
   pdf = true
   mode = 'draw'
+  page_limit = options.pages
+  page_count = 0
   draw_book (draw_page) ->
+    return if page_limit and page_limit <= page_count
+    page_count += 1
     draw_page()
     ctx.addPage()
   fs.writeFile BuildDirectory + filename + ".pdf", canvas.toBuffer()
@@ -435,7 +440,7 @@

   pitch_fingers = []
   finger_positions_each (fingering) ->
-    pitch_number = (fingering_note_number(fingering) - 44 + 120) % 12
+    pitch_number = fingering_note_number(fingering) % 12
     pitch_fingers[pitch_number] = fingering

   colors = ['red', 'blue', 'green', 'orange']
@@ -451,7 +456,7 @@
     ctx.fillText "#{chord.name} Chords", diagram_gutter / 2, header_height / 2

     for ix in [0...12]
-      pitch_number = (ix * 5 + 7) % 12
+      pitch_number = (ix * 5 + 3) % 12
       root_fingering = pitch_fingers[pitch_number]
       chord_name = "#{NoteNames[pitch_number]}#{chord.abbr}"
       cell ->
@@ -469,13 +474,12 @@

 chord_book = (options) ->
   page_count = options.pages
-  book "Combined Fretboard Chords", (page) ->
-    for chord, i in Chords
+  book "Combined Fretboard Chords", pages: options.pages, (page) ->
+    for chord in Chords
       page -> chord_page chord, options
-      break if page_count and i + 1 >= page_count

 # chord_page Chords[0], best_fingering: true
 # intervals_book by_root: true
 # intervals_book by_root: false
-chord_book best_fingering: 0, pages: 0
+chord_book best_fingering: 0, pages: 1
 # chord_fingerings_page Chords[0], 44 + 3


:art: user short var name bs for blockwiseSelection

--- lib/visual-blockwise.coffee
+++ lib/visual-blockwise.coffee
@@ -10,9 +10,8 @@
 class BlockwiseOtherEnd extends VisualBlockwise
   @extend()
   execute: ->
-    for blockwiseSelection in @vimState.getBlockwiseSelections()
-      unless blockwiseSelection.isSingleLine()
-        blockwiseSelection.reverse()
+    for bs in @vimState.getBlockwiseSelections() when not bs.isSingleLine()
+      bs.reverse()
     @new('ReverseSelections').execute()

 class BlockwiseMoveDown extends VisualBlockwise
@@ -20,9 +19,9 @@
   direction: 'down'

   execute: ->
-    for blockwiseSelection in @vimState.getBlockwiseSelections()
+    for bs in @vimState.getBlockwiseSelections()
       @countTimes =>
-        blockwiseSelection.moveSelection(@direction)
+        bs.moveSelection(@direction)

 class BlockwiseMoveUp extends BlockwiseMoveDown
   @extend()
@@ -36,17 +35,17 @@
   execute: ->
     pointByBlockwiseSelection = new Map

-    for blockwiseSelection in @vimState.getBlockwiseSelections()
-      blockwiseSelection.setPositionForSelections('start')
+    for bs in @vimState.getBlockwiseSelections()
+      bs.setPositionForSelections('start')

-      point = blockwiseSelection.getTop().getHeadBufferPosition()
-      pointByBlockwiseSelection.set(blockwiseSelection, point)
+      point = bs.getTop().getHeadBufferPosition()
+      pointByBlockwiseSelection.set(bs, point)

     @vimState.activate('normal')
     @new(@delegateTo).execute()

-    pointByBlockwiseSelection.forEach (point, blockwiseSelection) ->
-      blockwiseSelection.setBufferPosition(point)
+    pointByBlockwiseSelection.forEach (point, bs) ->
+      bs.setBufferPosition(point)

 class BlockwiseChangeToLastCharacterOfLine extends BlockwiseDeleteToLastCharacterOfLine
   @extend()
@@ -59,8 +58,8 @@
   which: 'start'

   execute: ->
-    for blockwiseSelection in @vimState.getBlockwiseSelections()
-      blockwiseSelection.setPositionForSelections(@which)
+    for bs in @vimState.getBlockwiseSelections()
+      bs.setPositionForSelections(@which)
     @new(@delegateTo).execute()

 class BlockwiseInsertAfterEndOfLine extends BlockwiseInsertAtBeginningOfLine


Remove unit details view as the unit is removed from the map.

--- src/views.coffee
+++ src/views.coffee
@@ -112,7 +112,11 @@
                     @map_markers().removeLayer unit.marker

         unselect_service: (service_id) ->
-            @selected_services.remove @selected_services.find (s) -> s.id == service_id
+            service = @selected_services.find (s) -> s.id == service_id
+            @selected_services.remove service
+            if @details_marker?
+                if service.get('shown_units').contains @details_marker.unit
+                    @service_sidebar.hide_details()
             @selected_services.trigger('change')

         effective_horizontal_center: ->


Add descriptions to softwrap settings.

Add descriptions to softwrap settings.

Closes #6920

--- src/config-schema.coffee
+++ src/config-schema.coffee
@@ -139,12 +139,14 @@
       softWrap:
         type: 'boolean'
         default: false
+        description: 'Wraps lines that exceeed the width of the window. When `Soft Wrap At Preferred Line Length` is set, it will wrap to the number characters defined by the `Preferred Line Length` setting.'
       softTabs:
         type: 'boolean'
         default: true
       softWrapAtPreferredLineLength:
         type: 'boolean'
         default: false
+        description: 'Will wrap to the number characters defined by the `Preferred Line Length` setting. This will only take effect when soft wrap is enabled globally or for the current language.'
       softWrapHangingIndent:
         type: 'integer'
         default: 0


:bug: Put a limit of 100 on max font size

This will prevent Atom from crashing, but still allow folks who need large
font sizes for visual impairments to have them.

Fix #4880

--- src/config-schema.coffee
+++ src/config-schema.coffee
@@ -108,6 +108,7 @@
         type: 'integer'
         default: 16
         minimum: 1
+        maximum: 100
       lineHeight:
         type: ['string', 'number']
         default: 1.3


Throw on invalid handler.

Throw on invalid handler.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -8,6 +8,7 @@
 log = console.log
 fs = require 'fs'
 path = require 'path'
+util = require 'util'
 uuid = require 'node-uuid'
 methods = require 'methods'

@@ -434,7 +435,7 @@
         res.type r.type if r.type?
         res.send r.handler
         return
-    else
+    else if r.handler.call?
       app[r.verb] r.path, r.middleware, (req, res, next) ->
         ctx =
           app: app
@@ -496,6 +497,9 @@
         res.type(r.type) if r.type?
         if typeof result is 'string' then res.send result
         else return result
+
+    else
+      throw new Error "ZappaJS invalid handler of type #{typeof r.handler}: #{util.inspect r.handler}"

   # Register socket.io handlers.
   io?.sockets.on 'connection', (socket) ->


api cleanup

api cleanup

--- js/src/http/DocumentSearchKeyApi.coffee
+++ js/src/http/DocumentSearchKeyApi.coffee
@@ -8,7 +8,7 @@
   Logger  = require 'kryptnostic.logger'
   Promise = require 'bluebird'

-  log = Logger.get('DocumentSearchKeyApi`')
+  log = Logger.get('DocumentSearchKeyApi')

   #
   # HTTP calls for submitting document address functions, search keys, and conversion matrices.


feature: autoamtically calculate appropriate range of free coordinate

--- tools/collider.coffee
+++ tools/collider.coffee
@@ -137,6 +137,45 @@
     npos1: npos1  #node point nearest to the collision (below)
     tcoll: tcoll  #when nearest approach occurs
   }
+
+#In what rande DX can change, if minimal distanec between 2 patterns is less than dmax?
+# This fucntion gives the answer.
+# Calculation is in the maxima sheet:
+# solve( d2 = dd^2, dx );
+dxRange = (dPos, v0, v1, dMax) ->
+  #dPos, v0, v1 :: vector3
+  #dMax :: integer
+  [_, dy, dt] = dPos #dx is ignored, because we will find its diapason
+  [vx0, vy0, p0] = v0
+  [vx1, vy1, p1] = v1
+
+  #Solution from Maxima:
+  # dx=-(dd*sqrt(p0^2*vy1^2-2*p0*p1*vy0*vy1+p1^2*vy0^2+p0^2*vx1^2-2*p0*p1*vx0*vx1+p1^2*vx0^2)-dt*vx0*vy1+dt*vx1*vy0-dy*p0*vx1+dy*p1*vx0)/(p0*vy1-p1*vy0)
+  # q = p0^2*vy1^2-2*p0*p1*vy0*vy1+p1^2*vy0^2+p0^2*vx1^2-2*p0*p1*vx0*vx1+p1^2*vx0^2
+  #
+  # dx=(+-dd*sqrt(q)-dt*vx0*vy1+dt*vx1*vy0-dy*p0*vx1+dy*p1*vx0)/(p1*vy0 - p0*vy1)
+  num = p1*vy0 - p0*vy1
+  if num is 0 then throw new Error "Patterns are parallel and neveer touch"
+  dc = (-dt*vx0*vy1+dt*vx1*vy0-dy*p0*vx1+dy*p1*vx0) / num
+  #q = p0^2*vy1^2-2*p0*p1*vy0*vy1+p1^2*vy0^2  +  p0^2*vx1^2-2*p0*p1*vx0*vx1+p1^2*vx0^2
+  q = (p0*vy1-p1*vy0)**2 + (p0*vx1-p1*vx0)**2 #after small simplification
+
+  delta = Math.sqrt(q) * (dMax / num)
+
+  d0 = dc - delta
+  d1 = dc + delta
+  #return upper and lower limits
+  return [Math.floor(d0)|0, Math.ceil(d1)|0]
+
+#Same as dxRange, but for dy. Reuses dxRange
+dyRange = (dPos, v0, v1, dMax) ->
+  swapxy = ([x,y,t]) -> [y,x,t]
+  return dxRange swapxy(dPos), swapxy(v0), swapxy(v1), dMax
+
+#Chooses one of dxRange, dyRange.
+freeIndexRange = (dPos, v0, v1, dMax, index) ->
+  if index not in [0,1] then throw new Error "Unsupported index #{index}"
+  [dxRange, dyRange][index](dPos, v0, v1, dMax)

 #Calculate time, when 2 spaceship approach to the specified distance.
 approachParameters = (pos0, delta0, pos1, delta1, approachDistance) ->
@@ -638,12 +677,20 @@

   offsetRange = [-40, 30]

-  for xFree in [offsetRange[0] .. offsetRange[1]] by 1
-    for offset in ecell
+
+  minimalTouchDistance = (pattern1.length + pattern2.length)*3
+  console.log "#### Minimal touch distance is #{minimalTouchDistance}"
+  for offset in ecell
+    #dPos, v0, v1, dMax, index
+    offsetRange = freeIndexRange offset, v1, v2, minimalTouchDistance, freeIndex
+    #console.log "#### FOr offset #{JSON.stringify offset}, index range is #{JSON.stringify offsetRange}"
+
+    for xFree in [offsetRange[0] .. offsetRange[1]] by 1
       offset = offset[..]
       offset[freeIndex] = xFree
       if isValidOffset offset
-        collision = doCollision rule, pattern1, v1, pattern2, v2, offset
+        #console.log "####  valid offset: #{JSON.stringify offset}"
+        collision = doCollision rule, pattern1, v1, pattern2, v2, offset, minimalTouchDistance*2, minimalTouchDistance
         showCollision collision if collision.collision


Fixed sidebar resize handle issue.

Fixed sidebar resize handle issue.

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -677,7 +677,7 @@
     dragGlobDir.y = if y > 0 then 'bottom' else 'top'

     el = @$()
-    if @dragIsAllowed
+    if @dragIsAllowed # is this line should be here or at the top ?
       dragMeta   = @dragState.meta
       targetPosX = if dragMeta.right  and not dragMeta.left then 'right'  else 'left'
       targetPosY = if dragMeta.bottom and not dragMeta.top  then 'bottom' else 'top'


Some improvments / comments

Some improvments / comments

--- app/views/exemples/tasks/item.coffee
+++ app/views/exemples/tasks/item.coffee
@@ -10,7 +10,7 @@
     'click .toggle'   : 'toggle'
     'dblclick label'  : 'edit'
     'click .destroy'  : 'delete'
-    'keypress .edit'  : 'save'
+    'keydown .edit'  : 'save'
     'blur .edit'      : 'save'

   toggle: => @model.toggle()
@@ -24,12 +24,25 @@
     @$('.edit').show().focus()

   save: (e)=>
-    return if e.type is 'keypress' and e.keyCode isnt 13
-    return @delete() unless value = $(e.currentTarget).val().trim()
+    $input = $(e.currentTarget)

-    @model.save title: value
+    # We do nothing except the keydown is Enter or Escape
+    return if e.type is 'keydown' and -1 is [13, 27].indexOf e.keyCode

-    @$('.edit').hide()
-    @$('label').show()
+    # If keydown is Escape, we reset the modification
+    $input.val(@model.get('title')) if e.type is 'keydown' and e.keyCode is 27
+
+    # If title is empty then we remove this task
+    return @delete() unless value = $input.val().trim()
+
+    # Otherwise, we save it
+    #
+    # `silent: true` means the change event will not be fired...
+    @model.save {title: value}, silent: true
+
+    # ... indeed, we want to do it manualy, it will automaticaly refresh this
+    # view. If we don't do it manualy, and if the title hasn't change, the
+    # view will not be refreshed, and we will still be in edit mode.
+    @model.trigger 'change'

   delete: => @model.destroy()


fix repl completion and navigation

fix repl completion and navigation

The readline interface of node has changed in [aad12d0][] and because of
that the autocompletion and key movement didn't work anymore. This
commit fixes this by checking whether stdin is in raw mode (i.e. invoked
as a script) or not (as a repl).

[aad12d0]: https://github.com/joyent/node/commit/aad12d0

--- repl.coffee
+++ repl.coffee
@@ -115,7 +115,7 @@
     error err
   repl.prompt()

-if stdin.readable
+if stdin.readable and stdin.isRaw
   # handle piped input
   pipedInput = ''
   repl =


Don't need the equals sign.

Don't need the equals sign.

--- src/github-pages.coffee
+++ src/github-pages.coffee
@@ -62,7 +62,7 @@

 formatted_versions = (versions) ->
   all = for gem, version of versions
-    "#{gem} = v#{version}"
+    "#{gem} v#{version}"
   all.join("\n")

 module.exports = (robot) ->


fix late count calc

fix late count calc

--- src/helpers/task.coffee
+++ src/helpers/task.coffee
@@ -64,10 +64,10 @@
     @lateStepCount(task) > 0
   lateStepCount: (task) ->
     if task.type is 'homework'
-      task.completed_exercise_count - task.completed_on_time_exercise_count +
+      task.completed_exercise_count - task.completed_on_time_exercise_count -
         task.completed_accepted_late_exercise_count
     else
-      task.completed_step_count - task.completed_on_time_step_count +
+      task.completed_step_count - task.completed_on_time_step_count -
         task.completed_accepted_late_step_count

   getHumanProgress: (task) ->


Use simplified notation for all events

Use simplified notation for all events

--- src/hook-manager.coffee
+++ src/hook-manager.coffee
@@ -3,33 +3,7 @@
 # The list of all GitHub events.
 #
 # See: https://developer.github.com/v3/activity/events/types/
-ALL_EVENTS = [
-  'commit_comment'
-  'create'
-  'delete'
-  'deployment'
-  'deployment_status'
-  'download'
-  'follow'
-  'fork'
-  'fork_apply'
-  'gist'
-  'gollum'
-  'issue_comment'
-  'issues'
-  'member'
-  'membership'
-  'page_build'
-  'public'
-  'pull_request'
-  'pull_request_review_comment'
-  'push'
-  'release'
-  'repository'
-  'status'
-  'team_add'
-  'watch'
-]
+ALL_EVENTS = ['*']

 # Public: Manages GitHub event hooks.
 class HookManager


Add a couple more useless Google search parameters.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -149,7 +149,7 @@
     # Google search specific replacements; this replaces query parameters which are known to not be helpful.
     # There's some additional information here: http://www.teknoids.net/content/google-search-parameters-2012
     [ "^https?://www\.google\.(com|ca|com\.au|co\.uk|ie)/.*[&?]q="
-      "ei gws_rd url ved usg sa usg sig2 bih biw cd".split(/\s+/).map (param) -> new RegExp "\&#{param}=[^&]+" ]
+      "ei gws_rd url ved usg sa usg sig2 bih biw cd aqs ie".split(/\s+/).map (param) -> new RegExp "\&#{param}=[^&]+" ]

     # General replacements; replaces leading and trailing fluff.
     [ '.', [ "^https?://", "\\W+$" ].map (re) -> new RegExp re ]


(fix) Enable debugging.

(fix) Enable debugging.


Ignore invalid ranges

Ignore invalid ranges

They cannot be cache since multiple commits/branches may
map to the same version number which would lead to
unpredictable results.

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -45,10 +45,8 @@
     try
       new semver.Range(version)
     catch error
-      invalidVersion = version
-      version = JSON.parse(fs.readFileSync(path.join(nodeModulesPath, name, 'package.json')))?.version
-      dependencies[name] = version
-      console.log "Normalized #{name}: #{invalidVersion} -> #{version}"
+      delete dependencies[name]
+      console.log "Ignoring invalid range: #{name} #{version}"

   onDirectory = (childPath) ->
     path.basename(childPath) isnt 'node_modules'


Improve coding Rename variable 'thisisit' to 'ou_id', which is more meaningful.

--- src/opac/ou_tree.coffee
+++ src/opac/ou_tree.coffee
@@ -30,14 +30,14 @@
 			# If an originating library system has been chosen by query string,
 			# then we narrow the ou selector to the system branches.
 			if window.query?.ol?
-				ol = window.query.ol.toUpperCase()
-				for id, ou of ouLookup when ou.shortname is ol
-					thisisit = Number id
+				OL = window.query.ol.toUpperCase()
+				for ou_id, ou of ouTree when ou.shortname is OL
+					ou_id = Number ou_id
 					break

 			@parallel 'organization list',
+				ouTree:  eg.openils 'actor.org_tree.descendants.retrieve', ou_id
 				ouTypes: eg.openils 'actor.org_types.retrieve'
-				ouTree:  eg.openils 'actor.org_tree.descendants.retrieve', thisisit
 			, (x) ->
 				# We use data-native-menu for this selector because it has many options
 				# and jQM's version would display options menu in a dialog,


Reverted

Reverted

--- sync.coffee
+++ sync.coffee
@@ -9,12 +9,11 @@
 module.exports = class AjaxSync

   constructor: (@model_type) ->
-    # set up model name
-    unless @model_type.model_name # model_name can be manually set
-      throw new Error("Missing url for model") unless url = _.result(@model_type.prototype, 'url')
-      @model_type.model_name = Utils.parseUrl(url).model_name
+    throw new Error("Missing url for model") unless @url = _.result(@model_type.prototype, 'url')
+
+    # publish methods and sync on model
+    @model_type.model_name = Utils.parseUrl(@url).model_name unless @model_type.model_name # model_name can be manually set
     throw new Error('Missing model_name for model') unless @model_type.model_name
-
     @schema = new Schema(@model_type)
     @request = require 'superagent'

@@ -75,7 +74,7 @@
           req = request.get(url).query({$one: !model.models}).type('json')

       req.end (err, res) ->
-        return options.error(model, err) if err
+        return options.error(err) if err
         return options.error(model, new Error "Ajax failed with status #{res.status} for #{method} with: #{util.inspect(res.body)}") unless res.ok
         options.success(JSONUtils.parse(res.body))
       return


add appid for connection routing in server

--- sync.coffee
+++ sync.coffee
@@ -757,7 +757,7 @@
             return false

     constructor: (@app_id, @options) ->
-        @bversion = 2012121301
+        @bversion = 2013070401
         @jd = new jsondiff()
         @dmp = jsondiff.dmp
         @auth_token = null
@@ -786,7 +786,7 @@
         if not ('page_delay' of @options)
             @options['page_delay'] = 0

-        @options['prefix'] = 'sock'
+        @options['prefix'] = "sock/1/#{@appid}"

         @options['port'] = parseInt(@options['port'])
         if @options['port'] != 80 and @options['port'] != 443


Add chart.js configuration

Add chart.js configuration

--- app/assets/javascripts/components/moving_records/interactive_chart.js.coffee
+++ app/assets/javascripts/components/moving_records/interactive_chart.js.coffee
@@ -1,3 +1,7 @@
+# Chart.js configuration
+Chart.defaults.global.tooltipEvents = ["mousemove", "touchstart", "touchmove"]
+Chart.defaults.global.scaleLabel    = "<%=value%>cu.ft"
+
 @ChartComponent = (chartType) ->

   React.createClass


fix import path

fix import path

--- src/pointer-tool.coffee
+++ src/pointer-tool.coffee
@@ -1,4 +1,4 @@
-Utils = require './Utils'
+Utils = require './utils'

 module.exports =
 class PointerTool


Fix axis tests

Fix axis tests

--- bokehjs/test/models/axes/axis.coffee
+++ bokehjs/test/models/axes/axis.coffee
@@ -16,11 +16,10 @@
 describe "Axis.Model", ->

   it "should have a SidePanel after add_panel is called", ->
-    pp = new Plot({
+    p = new Plot({
       x_range: new Range1d({start: 0, end: 1})
       y_range: new Range1d({start: 0, end: 1})
     })
-    p = pp.plot_canvas
     p.attach_document(new Document())
     ticker = new BasicTicker()
     formatter = new BasicTickFormatter()
@@ -35,22 +34,19 @@
     expect(axis.panel).to.be.an.instanceOf(SidePanel)

   it "should have a SidePanel after plot.add_layout is called", ->
-    pp = new Plot({
-      x_range: new Range1d({start: 0, end: 1})
-      y_range: new Range1d({start: 0, end: 1})
-    })
-    p = pp.plot_canvas
-    p.attach_document(new Document())
     ticker = new BasicTicker()
     formatter = new BasicTickFormatter()
     axis = new Axis({
       ticker: ticker
       formatter: formatter
-      plot: p
     })
-    axis.attach_document(p.document)
     expect(axis.panel).to.be.undefined
+    p = new Plot({
+      x_range: new Range1d({start: 0, end: 1})
+      y_range: new Range1d({start: 0, end: 1})
+    })
     p.add_layout(axis, 'left')
+    p.attach_document(new Document())
     expect(axis.panel).to.be.an.instanceOf(SidePanel)

 describe "Axis.View", ->
@@ -63,13 +59,7 @@
     utils.stub_canvas()
     solver_stubs = utils.stub_solver()

-    test_plot = new Plot({
-      x_range: new Range1d({start: 0, end: 1})
-      y_range: new Range1d({start: 0, end: 1})
-      toolbar: new Toolbar()
-    })
-    test_plot.attach_document(new Document())
-    test_plot_canvas = test_plot.plot_canvas
+    doc = new Document()
     ticker = new BasicTicker()
     formatter = new BasicTickFormatter()
     @axis = new Axis({
@@ -78,13 +68,20 @@
       ticker: ticker
       formatter: formatter
     })
-    test_plot_canvas.add_layout(@axis, 'below')
-    # Must create the test_plot_view after adding the axis to the plot
-    test_plot_canvas_view = new test_plot_canvas.default_view({ 'model': test_plot_canvas })
+    plot = new Plot({
+      x_range: new Range1d({start: 0, end: 1})
+      y_range: new Range1d({start: 0, end: 1})
+      toolbar: new Toolbar()
+    })
+    plot.add_layout(@axis, 'below')
+    plot.attach_document(doc)
+    plot_canvas = new PlotCanvas({ 'plot': plot })
+    plot_canvas.attach_document(doc)
+    plot_canvas_view = new plot_canvas.default_view({ 'model': plot_canvas })
     @axis_view = new @axis.default_view({
       model: @axis
-      plot_model: test_plot_canvas
-      plot_view: test_plot_canvas_view
+      plot_model: plot_canvas
+      plot_view: plot_canvas_view
     })

   it "_tick_extent should return the major_tick_out property", ->


Removes extra space

Removes extra space

--- models/featured_link.coffee
+++ models/featured_link.coffee
@@ -6,7 +6,7 @@
 { SECURE_IMAGES_URL } = require('sharify').data

 module.exports = class FeaturedLink extends Backbone.Model
-   _.extend @prototype, Image(SECURE_IMAGES_URL)
+  _.extend @prototype, Image(SECURE_IMAGES_URL)

   fetchItems: (cache=false) ->
     items = new Items null, { id: @id }


Fail instead of throwing errors

Fail instead of throwing errors

--- test/auth.coffee
+++ test/auth.coffee
@@ -10,7 +10,7 @@

   auth.register BLANK_REGISTRATION
     .then ->
-      throw new Error 'Should not have been able to register'
+      t.fail 'Should not have been able to register'

     .catch (errors) ->
       t.ok errors?.length is 1, 'Should have gotten one error'
@@ -26,7 +26,7 @@

   auth.register SHORT_PASSWORD_REGISTRATION
     .then ->
-      throw new Error 'Should not have been able to register'
+      t.fail 'Should not have been able to register'

     .catch (errors) ->
       t.ok errors?.length is 1, 'Should have gotten one error'
@@ -44,14 +44,14 @@
       t.ok user.display_name is TEST_LOGIN, 'Display name should be whatever login was given'

     .catch ->
-      throw new Error 'Should have worked'
+      t.fail 'Should have worked'

 # test 'Registering keeps you signed in', ->

 test 'Sign out', (t) ->
   auth.signOut()
     .catch ->
-      throw new Error 'Sign out should work'
+      t.fail 'Sign out should work'

 test 'Registering an account with an already used login fails', (t) ->
   DUPLICATE_REGISTRATION =
@@ -61,7 +61,7 @@

   auth.register DUPLICATE_REGISTRATION
     .then ->
-      throw new Error 'Should not have been able to register with a duplicate login'
+      t.fail 'Should not have been able to register with a duplicate login'

     .catch (errors) ->
       t.ok errors?.length is 1, 'Should have gotten one error'
@@ -75,7 +75,7 @@

   auth.signIn BAD_LOGIN
     .then ->
-      throw new Error 'Should not have been able to sign in with a bad login'
+      t.fail 'Should not have been able to sign in with a bad login'

     .catch (errors) ->
       t.ok errors?.length is 1, 'Should have gotten one error'
@@ -89,7 +89,7 @@

   auth.signIn BAD_PASSWORD
     .then ->
-      throw new Error 'Should not have been able to sign in with a bad password'
+      t.fail 'Should not have been able to sign in with a bad password'

     .catch (errors) ->
       t.ok errors?.length is 1, 'Should have gotten one error'
@@ -106,4 +106,4 @@
       t.ok user.display_name is TEST_LOGIN, 'Display name should be original'

     .catch ->
-      throw new Error 'Sign in should work'
+      t.fail 'Sign in should work'


Specs for the transitionDelay setting

Specs for the transitionDelay setting

--- test/test.coffee
+++ test/test.coffee
@@ -138,6 +138,14 @@
         expect(@$carousel.circular('settings').aControl).to.equal '.bar'
         expect(@$carousel.circular('controls').selector).to.equal '.carousel .bar'

+      it 'the transition delay defaults to 1000ms', ->
+        @$carousel.circular()
+        expect(@$carousel.circular('settings').transitionDelay).to.equal 1000
+
+      it 'the transition delay can be overriden', ->
+        @$carousel.circular({transitionDelay: 250})
+        expect(@$carousel.circular('settings').transitionDelay).to.equal 250
+
       it 'autoStart is true by default', ->
         _resume = sinon.spy @$carousel.circular('_internals'), 'resume'
         @$carousel.circular()


Use single-quote strings

Use single-quote strings

Signed-off-by: Jean-Denis Vauguet <ff9dedefe9ee78b3339d23163d8b29a3c83c6016@vauguet.fr>

--- test/test.coffee
+++ test/test.coffee
@@ -1,4 +1,4 @@
-jsdom = require("jsdom")
+jsdom = require('jsdom')
 chai = require('chai')
 sinon = require('sinon')
 jQuery = $ = require('jquery')
@@ -11,12 +11,12 @@
 document = jsdom.jsdom("hello world")
 window = document.parentWindow

-describe "dummy test", ->
-  it "should be true", ->
+describe 'dummy test', ->
+  it 'should be true', ->
     expect(true).to.be.true

-describe "$.fn.circular", ->
-  it "exists", ->
+describe '$.fn.circular', ->
+  it 'exists', ->
     expect($.fn.circular).to.exist

-  it "can run", ->
+  it 'can run', ->


改为更安全的方式，默认禁止，手动打开

改为更安全的方式，默认禁止，手动打开

--- src/config/router.coffee
+++ src/config/router.coffee
@@ -91,6 +91,6 @@
 exports.requestPermission = (client, router, action)->
 #允许匿名的路由或者用户已经登录，则允许访问
   return true if client.member.isLogin or _.indexOf(router.anonymity || [], action) >= 0
-  #如果开启了访客模式，并且非强制登录的路由，允许用户访问
-  return true if _common.config.guestModel and _.indexOf(router.authorized || [], action) < 0
+  #如果开启了访客模式，并且路由在许诺的列表内，允许用户访问
+  return true if _common.config.guestModel and _.indexOf(router.allowGuest || [], action) >= 0
   return false


Added check to see if displayLimit is actually set (issues when not before); Removed zoom out limit when domainconstrain is false as this is not the expected behaviour

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -242,6 +242,8 @@

         # create the zoom behavior
         minScale = (@options.display.start - @options.display.end) / (@options.domain.start - @options.domain.end)
+        if !@options.constrain
+            minScale = 0
         # Calculate maxScale by gettting milliseconds difference  of the displayed
         # time domain (getting the seconds by dividing by 1000) and halving it.
         # This should allow to zoom into to see up to two seconds in the complete timeslider
@@ -348,7 +350,8 @@
                     s = new Date(s.getTime()-(d/2))
                     e = new Date(e.getTime()+(d/2))
                     [low, high] = @scales.x.domain()
-                    if (e - s) > @options.displayLimit * 1000
+                    if @options.displayLimit != null and
+                       (e - s) > @options.displayLimit * 1000
                         [s, e] = @scales.x.domain()
                     @center(s,e)
                 )


Fix documentation examples for fsUtils.isDirectory

--- lib/resin/data/fs-utils/fs-utils.coffee
+++ lib/resin/data/fs-utils/fs-utils.coffee
@@ -25,8 +25,15 @@
 # @param {Function} callback callback(error, isDirectory)
 #
 # @example Is directory?
-#		console.log isDirectory('/usr/local/share') # True
-#		console.log isDirectory('/Users/me/app.js') # False
+#		isDirectory '/usr/local/share', (error, isDirectory) ->
+#			throw error if error?
+#			console.log(isDirectory)
+#			# True
+#
+#		isDirectory '/Users/me/app.js', (error, isDirectory) ->
+#			throw error if error?
+#			console.log(isDirectory)
+#			# False
 #
 exports.isDirectory = (directory, callback) ->
 	fs.stat directory, (error, stats) ->


Refactor: retire extraneous variable and comments

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -138,8 +138,7 @@
   view_value

 highlights_view_to_model = (view_value) ->
-  # TODO: need to check for null
-  model_value = view_value              # "
one

br>

 two

"
+  view_value                            # "
one

br>

 two

"
     .replace( /
/g, '' )             # "one
br>
 two

"
     .replace( /<\/li>/g, "\n" )         # "one \n
\n two\n
\n \n"
     .replace( /
/g, "\n" )           # "one \n\n two\n\n\n \n"
@@ -148,8 +147,6 @@
     .replace( /[ \t]+$/gm, '' )         # "one\n\ntwo\n\n\n\n"
     .replace( /\n\n+/g, "\n" )          # "one\ntwo\n"
     .replace( /\n$/, '' )               # "one\ntwo"
-  # console.log("view_value: '#{view_value}' to model_value: '#{model_value}'")   # TODO: remove this line
-  model_value

 #


update wording

update wording

--- app/assets/javascripts/i18n.js.coffee
+++ app/assets/javascripts/i18n.js.coffee
@@ -165,7 +165,7 @@
       form_with_errors: 'Ein oder mehr Felder sind unvollständig. Bitte ergänzen/korrigieren Sie dies zuerst.'
       hangup:           'Auflegen'
       hangup_this_call: 'Möchten Sie diesen Anruf auflegen?'
-      i_am_busy:        'Ich bin beschäftigt'
+      i_am_busy:        'Jetzt nicht'
       incoming_call:    'Sie bekommen einen Anruf von
NAME'
       lost_server_conn: 'Leider haben wir die Verbindung zum Server verloren — bitte prüfen Sie Ihr Netzwerk
und melden sich erneut an.'
       no_messages:      'Wir empfangen keine Nachrichten mehr —
bitte öffnen Sie nur ein Fenster zur Zeit.'


Not needed

Not needed

--- models/user_interest.coffee
+++ models/user_interest.coffee
@@ -1,6 +1,5 @@
 _ = require 'underscore'
 Backbone = require 'backbone'
-{ API_URL } = require('sharify').data
 Relations = require './mixins/relations/user_interest.coffee'

 module.exports = class UserInterest extends Backbone.Model


back to cloudmade

back to cloudmade

--- app/assets/javascripts/labs.js.coffee
+++ app/assets/javascripts/labs.js.coffee
@@ -23,7 +23,7 @@
     location = [$('#lab-map').data('latitude'), $('#lab-map').data('longitude')]
     labmap = L.map('lab-map', { scrollWheelZoom: false, zoomControl: false, loadingControl: true }).setView(location, 14 )
     new L.Control.Zoom({ position: 'topright' }).addTo(labmap)
-    L.tileLayer('https://magellan.fablabs.io/{z}/{x}/{y}.png', { attribution: osmAttrib }).addTo(labmap)
+    L.tileLayer('https://ssl_tiles.cloudmade.com/d8b794cdcd1e4e37bd83addfd40b7c68/110755/256/{z}/{x}/{y}.png', { attribution: osmAttrib }).addTo(labmap)
     icon = L.icon({
       iconUrl: '//i.imgur.com/bKe7MW2.png'
       iconSize:     [35, 35]
@@ -72,7 +72,7 @@

     window.map = map
     new L.Control.Zoom({ position: 'topleft' }).addTo(map)
-    L.tileLayer('https://magellan.fablabs.io/{z}/{x}/{y}.png', { attribution: osmAttrib, maxZoom: 14 }).addTo(map)
+    L.tileLayer('https://ssl_tiles.cloudmade.com/384aceabcd0942189d0e93cf0e98cd31/90734/256/{z}/{x}/{y}.png', { attribution: osmAttrib, maxZoom: 14 }).addTo(map)
     navigator.geolocation.getCurrentPosition((position)->
       map.setView([position.coords.latitude, position.coords.longitude], 4)
     )


Hiding arrows after draggin a non-important slider

--- app/assets/javascripts/lib/app.coffee
+++ app/assets/javascripts/lib/app.coffee
@@ -166,6 +166,7 @@
     $(".chart_holder[data-block_ui_on_refresh=true]:visible").busyBox
       spinner: "Loading"
     $("#dashboard .loading").show()
+    $(".arrow").css("opacity", "0.0")

   hideLoading: ->
     $(".chart_holder").busyBox('close')


Add '&projects=nomlab/1' to URL parameter

--- app/assets/javascripts/minutes.coffee
+++ app/assets/javascripts/minutes.coffee
@@ -203,7 +203,7 @@
 #   issue: {title: TEST, body: Blah, labels: bug, assignee: yoshinari-nomura}
 newGithubIssue = (repos, issue) ->
   github = 'https://github.com'
-  window.open("#{github}/#{repos}/issues/new?#{$.param(issue)}")
+  window.open("#{github}/#{repos}/issues/new?#{$.param(issue)}&projects=nomlab/1")

 # Post new github comment
 #   repos: nomlab/sandbox


hacky fix for SVG viewbox with video element

--- app/classifier/subject-annotator.cjsx
+++ app/classifier/subject-annotator.cjsx
@@ -151,6 +151,8 @@
   handleSubjectFrameLoad: (e) ->
     @props.onLoad? e, @state.frame
     {naturalWidth, naturalHeight} = e.target
+    naturalWidth ?= e.target.offsetWidth
+    naturalHeight ?= e.target.offsetHeight
     unless @state.naturalWidth is naturalWidth and @state.naturalHeight is naturalHeight
       @setState {naturalWidth, naturalHeight}, =>
         @updateSize()


Show a confirm message when a role is updated

--- app/coffee/modules/admin/roles.coffee
+++ app/coffee/modules/admin/roles.coffee
@@ -109,7 +109,8 @@
                 @confirm.notify('error')

     setComputable: ->
-        onSuccess = null
+        onSuccess = =>
+            @confirm.notify("success")

         onError = =>
             @confirm.notify("error")
@@ -306,12 +307,15 @@

             $el.on "change", ".category-item input", (event) ->
                 getActivePermissions = ->
-                    activePermissions = _.filter($el.find(".category-item input"), (t) -> angular.element(t).is(":checked"))
+                    activePermissions = _.filter($el.find(".category-item input"), (t) ->
+                        angular.element(t).is(":checked")
+                    )
                     activePermissions = _.sortBy(_.map(activePermissions, (t) ->
                         permission = angular.element(t).parents(".category-item").data("id")
                     ))
                     activePermissions.push("view_project")
                     return activePermissions
+
                 target = angular.element(event.currentTarget)
                 $scope.role.permissions = getActivePermissions()

@@ -320,6 +324,7 @@
                     categoryId = target.parents(".category-config").data("id")
                     renderResume(target.parents(".category-config"), categories[categoryId])
                     $rootscope.$broadcast("projects:reload")
+                    $confirm.notify("success")

                 onError = ->
                     $confirm.notify("error")
@@ -328,7 +333,6 @@

                 $repo.save($scope.role).then onSuccess, onError

-
         $scope.$on "$destroy", ->
             $el.off()


incorrect variable

incorrect variable

--- app/controllers/subject_viewer.coffee
+++ app/controllers/subject_viewer.coffee
@@ -70,7 +70,7 @@
       @classification.bind 'change', @onClassificationChange
       @classification.bind 'add-species', @onClassificationAddSpecies
       if ExperimentServer.currentCohort?
-        @classification.metadata.cohort = cohort
+        @classification.metadata.cohort = ExperimentServer.currentCohort
       @html template @classification
       @active = Math.floor @classification.subject.location.standard.length / 2
       @activate @active


improve function name

improve function name

--- app/templates/gulp/tasks/mysql.coffee
+++ app/templates/gulp/tasks/mysql.coffee
@@ -6,7 +6,7 @@
 # add database credentials to your secrets.coffee
 secret = require '../secrets'

-dbImportFrom = (fromEnv, toEnv) ->
+dbImportFromTo = (fromEnv, toEnv) ->
   dbFromPath = path.resolve(__dirname, '../../database', fromEnv)
   dbEnv = "db_#{toEnv}"
   newestFile = path.resolve(dbFromPath, utils.getNewestFile(dbFromPath))
@@ -63,7 +63,7 @@
   dbDropTables('dev')

 gulp.task 'db-importlocally:prod', ['db-droptables:dev'], () ->
-  dbImportFrom('prod', 'dev')
+  dbImportFromTo('prod', 'dev')

 gulp.task 'db-importlocally:dev', ['db-droptables:dev'], () ->
-  dbImportFrom('dev', 'dev')
+  dbImportFromTo('dev', 'dev')


Use #main-menu as target. Don't move current page in menu transition.

Menu will now overlay the page.

--- js/flight.coffee
+++ js/flight.coffee
@@ -11,7 +11,7 @@

   transitionAnimation: true
   speed: '0.4s'
-  mainMenu: '#slide-out-menu'
+  mainMenu: '#main-menu'

   # Public: Instantiate Flight and set any options.
   #
@@ -133,8 +133,11 @@
       @translate(@currentPage, "X", "0%", "0.3s")
       @menuOpen = false
     else
+      @translate(@mainMenu, "X", "-100%", "0ms")
       @mainMenu.style.display = "block"
-      @translate(@currentPage, "X", "250px", "0.3s")
+      window.setTimeout =>
+        @translate(@mainMenu, "X", "0%", "0.3s")
+      ,10
       @menuOpen = true

   # Private: Translate page on a specified axis.


Make a build page with a Circle Bug load properly

--- js/inner.coffee
+++ js/inner.coffee
@@ -186,6 +186,7 @@
     "#{@vcs_url()}/commit/#{@vcs_revision()}"

   github_revision: =>
+    return unless @vcs_revision()
     @vcs_revision().substring 0, 9

   author: =>


removed special mincha-arbit schedule from 9/2/14

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -108,7 +108,6 @@
     mincha: recent_hadlakat_nerot, arbit: sunset

 window.mincha_and_arbit = (day_iterator, hebrew_date) ->
-  return {mincha: 'See below', arbit: ''} if day_iterator.year() == 2014 && day_iterator.month() == 8 && day_iterator.date() == 1
   zmanim = SunCalc.getTimes(moment(day_iterator).toDate().setHours(12), window.config.latitude, window.config.longitude)
   times = mincha_time(zmanim, hebrew_date)
   (times[prayer] = if times[prayer] then times[prayer].format('h:mm') else '') for prayer in ['mincha', 'arbit']


LMS - added in additional JQUI accordion parameter needed to display full height of active element in courseware sidebar

--- lms/static/coffee/src/navigation.coffee
+++ lms/static/coffee/src/navigation.coffee
@@ -13,6 +13,7 @@
         active: active
         header: 'h3'
         autoHeight: false
+        heightStyle: 'content'
       $('#accordion .ui-state-active').closest('.chapter').addClass('is-open')
       $('#open_close_accordion a').click @toggle
       $('#accordion').show()


Added more debugging info in failing test

--- test.coffee
+++ test.coffee
@@ -42,7 +42,7 @@
               if JSON.stringify(idx) is '[["name",1],["v",1]]'
                 return done()

-            throw Error 'Could not find index in ops db'
+            throw Error "Could not find index in ops db - #{JSON.stringify(indexes)}"
         , 100

   require('livedb/test/snapshotdb') create


add an extra verification step to expectStreamEvents. Several failures found.

--- spec/SpecHelper.coffee
+++ spec/SpecHelper.coffee
@@ -50,6 +50,7 @@
   verifySingleSubscriber src, expectedEvents
   verifySwitching src, expectedEvents unless browser
   verifySwitchingWithUnsub src, expectedEvents unless browser
+  verifySwitching2 src, expectedEvents

 @expectPropertyEvents = (src, expectedEvents) ->
   expect(expectedEvents.length > 0).to.deep.equal(true)
@@ -154,6 +155,30 @@
        verifyExhausted src
     it "cleans up observers", verifyCleanup

+verifySwitching2 = (srcF, expectedEvents, done) ->
+  src = null
+  events = []
+  before ->
+    src = srcF()
+    expect(src instanceof Bacon.EventStream).to.equal(true)
+  before (done) ->
+    newSink = ->
+      (event) ->
+        if event.isEnd()
+          done()
+        else
+          expect(event instanceof Bacon.Initial).to.deep.equal(false)
+          events.push(toValue(event))
+          unsub() if unsub?
+          src.subscribe(newSink())
+          Bacon.noMore
+    unsub = src.subscribe(newSink())
+  it "outputs expected value in order when switching to new observer after each event", ->
+    expect(events).to.deep.equal(toValues(expectedEvents))
+  it "the stream is exhausted", ->
+     verifyExhausted src
+  it "cleans up observers", verifyCleanup
+
 verifyExhausted = (src) ->
   events = []
   src.subscribe (event) ->


improve false positives on cyno detection

--- kills.coffee
+++ kills.coffee
@@ -68,7 +68,7 @@
   robot.hear /I hate (.*)/i, (msg) ->
     msg.send msg.random(insults) + " #{msg.match[1]}"

-  robot.hear /([A-Z]{4,})/, (msg) ->
+  robot.hear /[A-Z]{2,}[,.\-!\? ]?[A-Z]{3,} ?[^a-z]*/, (msg) ->
     msg.send "Did someone light a cyno?  All I see are caps everywhere."

   robot.hear /tired|too hard|to hard|upset|bored/i, (msg) ->


Remove comparison operators that are too specific

--- grammars/powershell.cson
+++ grammars/powershell.cson
@@ -42,7 +42,7 @@
     'name': 'keyword.operator.bitwise.powershell'
   }
   {
-    'match': '\\B(?i:(-eq|-ceq|-ieq|-lt|-gt|-le|-ge|-ne|-notlike|-like|-match|-notmatch|-contains|-notcontains|-in|-notin|-replace))\\b'
+    'match': '\\B(?i:(-eq|-lt|-gt|-le|-ge|-ne|-notlike|-like|-match|-notmatch|-contains|-notcontains|-in|-notin|-replace))\\b'
     'name': 'keyword.operator.comparison.powershell'
   }
   {


fixed a typo

fixed a typo

--- _src/lib/node_cache.coffee
+++ _src/lib/node_cache.coffee
@@ -6,7 +6,7 @@
 module.exports = class NodeCache extends EventEmitter
 	constructor: ( @options = {} )->

-		# container for cached dtaa
+		# container for cached data
 		@data = {}

 		# module options


request uses send

request uses send

--- lib/core.coffee
+++ lib/core.coffee
@@ -61,25 +61,17 @@
         core.respond "#{alias}.#{serviceName}", serviceDef

+  # Subscribe to a response address.
+  # Publish a message, with a response address in the envelope.
+  # Time out based on axiom config.
   request: (channel, data, done) ->
-    # Subscribe to a response address.
-    # Publish a message, with a response address in the envelope.
-    # Time out based on axiom config.
-
-    # Define a unique identifier for the message cycle
-    topicId = uuid.v1()
-
-    # Define metadata to put inside the envelope
-    replyTo =
-      channel: channel
-      topic:
-        err: "err.#{topicId}"
-        info: "info.#{topicId}"
-        success: "success.#{topicId}"
+
+    # Send the message
+    replyTo = core.send channel, data
+

     # Define an 'onTimeout' callback for when we don't get a response
     # (either error or success) in the configured time.
-    timeout = core.config.timeout
     onTimeout = ->
       err = new Error "Request timed out on channel '#{channel}'"

@@ -88,38 +80,41 @@
         topic: replyTo.topic.err
         data: err

-    timeoutId = timers.setTimeout onTimeout, timeout
+    timeoutId = timers.setTimeout onTimeout, core.config.timeout

     # Default callback is of signature (message, envelope).
     # Wrap so we can pass a conventional (err, result)-style callback.
     callback = (message, envelope) ->
-      # Either an error or a success response has been
-      # received, so cancel our timeout callback.
+
+      # We're done, so cancel timeouts and subscriptions.
       timers.clearTimeout timeoutId
+      errSub.unsubscribe()
+      successSub.unsubscribe()

       {topic} = envelope
       [condition, middle..., topicId] = topic.split('.')
+
       switch condition
         when 'err'
-          err = message
-          done err
+          done message
         when 'success'
           done null, message
+
         else
           # This should never be reached, as this callback should only
           # be invoked by a subscription to a topic of the form
           # 'err.<uuid>' or 'success.<uuid>'.
-          err = new Error "Invalid condition '#{condition}'for response with topicId '#{topicId}'"
+          err = new Error "Invalid condition '#{condition}' for response with topicId '#{topicId}'"
           done err

     # Subscribe to the 'err' response for topicId
     # We don't pass a callback immediately so that we can
     # refer to the subscription itself in the callback.
     errSub = bus.subscribe {
-      channel: replyTo.channel,
-      topic: replyTo.topic.err,
-    }
-    errSub.subscribe callback
+      channel: channel
+      topic: replyTo.topic.err
+      callback: callback
+    }

     # Subscribe to the 'success' response for topicId
     # As above, we don't pass a callback immediately so that
@@ -127,22 +122,8 @@
     successSub = bus.subscribe {
       channel: replyTo.channel
       topic: replyTo.topic.success
-    }
-    successSub.subscribe (message, envelope) ->
-      # Stop listening for errors by detaching the error callback
-      errSub.unsubscribe()
-
-      # Actually call the callback
-      callback message, envelope
-
-    # The success callback should only fire once, if at all
-    successSub.once()
-
-    bus.publish
-      channel: channel
-      topic: "request.#{topicId}"
-      data: data
-      replyTo: replyTo
+      callback: callback
+    }

   delegate: (channel, data, done) ->
@@ -244,11 +225,11 @@
     timers.setImmediate finish

   # sends acknowledgement, error, completion to replyTo channels
-  respond: (channel, handler) ->
+  respond: (channel, service) ->
     responderId = uuid.v1()

     callback = (message, envelope) ->
-      handler message, (err, result) ->
+      service message, (err, result) ->
         if err?
           topic = envelope.replyTo.topic.err
           data = err
@@ -267,7 +248,7 @@

     # Map this 'responderId' to the responder and its metadata
     core.responders[channel] or= {}
-    core.responders[channel][callback.responderId] =
+    core.responders[channel][responderId] =
       callback: callback

     # Actually subscribe as a responder
@@ -279,18 +260,30 @@
   # just send the message
   send: (channel, data) ->
     topicId = uuid.v1()
-    topic = topicId
-    pub = bus.publish
-      channel: channel
-      data: data
-      topic: topic
+    replyTo =
+      channel: channel
+      topicId: topicId
+      topic:
+        err: "err.#{topicId}"
+        info: "info.#{topicId}"
+        success: "success.#{topicId}"
+
+    timers.setImmediate ->
+      topic = "request.#{topicId}"
+      bus.publish
+        channel: channel
+        topic: topic
+        data: data
+        replyTo: replyTo
+
+    return replyTo

   # just listen
-  listen: (channel, handler) ->
+  listen: (channel, callback) ->
     sub = bus.subscribe
       channel: channel
       topic: '#'
-      callback: handler
+      callback: callback

   # for sending interrupts
   signal: (channel, data) ->


Force elixirc for Phoenix projects

Force elixirc for Phoenix projects

Running via mix prevents hot code reloading for Phoenix projects (see
https://github.com/phoenixframework/phoenix/issues/1165).

--- lib/init.coffee
+++ lib/init.coffee
@@ -50,6 +50,13 @@
       @forceElixirc
     isExsFile = (textEditor) ->
       textEditor.getPath().endsWith('.exs')
+    isPhoenixProject = (textEditor) ->
+      mixLockPath = projectPath(textEditor) + '/mix.lock'
+      try
+        mixLockContent = fs.readFileSync mixLockPath, 'utf-8'
+        mixLockContent.indexOf('"phoenix"') > 0
+      catch
+        false
     parseError = (row, textEditor) ->
       return unless row.startsWith('** ')
       re = ///
@@ -122,7 +129,7 @@
       lintOnFly: false
       name: 'Elixir'
       lint: (textEditor) =>
-        if isForcedElixirc() or not isMixProject(textEditor) or isExsFile(textEditor)
+        if isForcedElixirc() or not isMixProject(textEditor) or isExsFile(textEditor) or isPhoenixProject(textEditor)
           lintElixirc(textEditor)
         else
           lintMix(textEditor)


fix typo

fix typo

--- lib/init.coffee
+++ lib/init.coffee
@@ -5,4 +5,4 @@
     jsStandardExecutablePath: path.join __dirname, '..', 'node_modules', 'standard', 'bin'

   activate: ->
-    console.log 'activate linter-jshint'
+    console.log 'activate linter-js-standard'


Added description for lintOnFly option.

Added description for lintOnFly option.

--- lib/init.coffee
+++ lib/init.coffee
@@ -8,6 +8,7 @@
     lintOnFly:
       type: 'boolean'
       default: true
+      descrition: "Enable linting on the fly. Need to restart Atom."
     ignoreErrorsAndWarnings:
       type: 'string'
       default: ''
@@ -59,7 +60,6 @@
                  ready to get initialized by AtomLinter.'

   provideLinter: ->
-    console.log 'provideLinter'
     LinterPylama = require './linter-pylama.coffee'
     @provider = new LinterPylama()
     return {


Add special error handling for OS Windows

--- lib/init.coffee
+++ lib/init.coffee
@@ -299,7 +299,7 @@
     ), (err) ->
       # Well, Well, Well... something went wrong.
       # Instead of crashing or giving cryptic error message, let's try to be user friendly.
-      if err.message.match /^Failed\sto\sspawn\scommand\s.+\.\sMake\ssure\s.+\sis\sinstalled\sand\son\syour\sPATH$/gi
+      if err.message.match /^Failed\sto\sspawn\scommand\s.+\.\sMake\ssure\s.+\sis\sinstalled\sand\son\syour\sPATH$/gi || err.message.match /^The\ssystem\scannot\sfind\sthe\spath\sspecified\.$/gi
         ###
         The Problem: The error is about a process spawn which failed


Fixing 9gag update to their IMG src, they no longer include http/https in their img src tags, so appending if it is not automatically found.

--- src/scripts/9gag.coffee
+++ src/scripts/9gag.coffee
@@ -34,7 +34,12 @@
       location = response.headers['location']
       return send_meme( message, location, response_handler )

-    response_handler get_meme_image( body, ".img-wrap img" )
+    img_src = get_meme_image( body, ".img-wrap img" )
+
+    if img_src.substr 0,4 != "http"
+      img_src = "http:#{img_src}"
+
+    response_handler img_src

 get_meme_image = (body, selector)->
   html_handler  = new HTMLParser.DefaultHandler((()->), ignoreWhitespace: true )


if hubot is run under a different name, help should refer to it under the new name

--- src/scripts/help.coffee
+++ src/scripts/help.coffee
@@ -10,5 +10,7 @@
     cmds = robot.helpCommands()
     if msg.match[1]
       cmds = cmds.filter (cmd) -> cmd.match(new RegExp(msg.match[1]))
-    msg.send cmds.join("\n")
-
+    emit = cmds.join("\n")
+    unless robot.name is 'Hubot'
+      emit = emit.replace(/(H|h)ubot/g, robot.name)
+    msg.send emit