Fix subscription leak when ~/.atom/styles.less is present running specs

--- spec/atom-spec.coffee
+++ spec/atom-spec.coffee
@@ -510,20 +510,22 @@
           # enabling of theme
           pack = atom.packages.enablePackage(packageName)

-          activatedPackages = null
-          waitsFor ->
-            activatedPackages = atom.packages.getActivePackages()
-            activatedPackages.length > 0
-
-          runs ->
-            expect(activatedPackages).toContain(pack)
+          waitsFor (done) ->
+            atom.themes.once 'reloaded', done
+
+          runs ->
+            expect(atom.packages.getActivePackages()).toContain pack
             expect(atom.config.get('core.themes')).toContain packageName
             expect(atom.config.get('core.disabledPackages')).not.toContain packageName

             # disabling of theme
             pack = atom.packages.disablePackage(packageName)
-            activatedPackages = atom.packages.getActivePackages()
-            expect(activatedPackages).not.toContain(pack)
+
+          waitsFor (done) ->
+            atom.themes.once 'reloaded', done
+
+          runs ->
+            expect(atom.packages.getActivePackages()).not.toContain(pack)
             expect(atom.config.get('core.themes')).not.toContain packageName
             expect(atom.config.get('core.themes')).not.toContain packageName
             expect(atom.config.get('core.disabledPackages')).not.toContain packageName


Record current frame on newly drawn marks

--- app/classifier/subject-viewer.cjsx
+++ app/classifier/subject-viewer.cjsx
@@ -155,6 +155,7 @@
       toolDescription = taskDescription.tools[@props.annotation._toolIndex]
       mark =
         tool: @props.annotation._toolIndex
+        frame: @state.frame
       if toolDescription.details?
         mark.details = for detailTaskDescription in toolDescription.details
           tasks[detailTaskDescription.type].getDefaultAnnotation()


Normalize X-Up-Method header value

Normalize X-Up-Method header value

--- lib/assets/javascripts/unpoly/protocol.coffee
+++ lib/assets/javascripts/unpoly/protocol.coffee
@@ -181,7 +181,8 @@
   @internal
   ###
   methodFromXhr = (xhr) ->
-    xhr.getResponseHeader(config.methodHeader)
+    if method = xhr.getResponseHeader(config.methodHeader)
+      u.normalizeMethod(method)

   ###*
   Server-side companion libraries like unpoly-rails set this cookie so we


ResourceStateModal: minor code cleanup

ResourceStateModal: minor code cleanup

--- client/app/lib/providers/resourcestatemodal/controllers/basepagecontroller.coffee
+++ client/app/lib/providers/resourcestatemodal/controllers/basepagecontroller.coffee
@@ -48,10 +48,7 @@

     @pages.push @loader  if @loader

-    if queue.length > 0
-      async.parallel queue, => @emit 'ready'
-    else
-      @emit 'ready'
+    async.parallel queue, => @emit 'ready'

   _setCurrentPage: (page) ->


minor whitespace

minor whitespace

--- app/js/loader/loader_common.coffee
+++ app/js/loader/loader_common.coffee
@@ -3,7 +3,6 @@
   id && el.id = id
   content && el.appendChild(content)
   el
-

 window.FACTLINK_START_LOADER = ->
   if window.FACTLINK_LOADED


Fix to make JMX beans with spaces in their names work in Chrome.

--- community/server/src/main/coffeescript/neo4j/webadmin/ServerInfoController.coffee
+++ community/server/src/main/coffeescript/neo4j/webadmin/ServerInfoController.coffee
@@ -40,7 +40,7 @@

       bean : (domain, name) =>
         @appState.set( mainView : @getServerInfoView() )
-        @serverInfo.setCurrent(domain, name)
+        @serverInfo.setCurrent(decodeURIComponent(domain), decodeURIComponent(name))

       getServerInfoView : =>
         @view ?= new ServerInfoView


User ResourceInput for project URLs

User ResourceInput for project URLs

--- app/pages/lab/project-details.cjsx
+++ app/pages/lab/project-details.cjsx
@@ -33,9 +33,17 @@
           {for link, i in @props.project.urls
             link._key ?= Math.random()

-              <input type="text" name="urls.#{i}.label" value={link.label} onChange={@handleChange}/>
-              <input type="text" name="urls.#{i}.url" value={link.url} onChange={@handleChange}/>
-              <button type="button" onClick={@handleRemoveLink.bind this, link._key}></button>
+
+                <ResourceInput resource={@props.project} update="urls.#{i}.label" />
+
+
+                <ResourceInput resource={@props.project} update="urls.#{i}.url" />
+
+
+                <button type="button" onClick={@handleRemoveLink.bind this, link}>
+
+                </button>
+

}

@@ -50,10 +58,10 @@
       url: 'https://example.com/'
     @props.project.update changes

-  handleRemoveLink: (linkKey) ->
-    changes = {}
-    changes['urls'] = @props.project.urls.filter((link) -> link._key != linkKey)
-    @props.project.update changes
+  handleRemoveLink: (linkToRemove) ->
+    changes =
+      urls: (link for link in @props.project.urls when link isnt linkToRemove)
+    @props.project.update(changes).save()

 module.exports = React.createClass
   displayName: 'EditProjectDetails'
@@ -154,11 +162,9 @@
             Adding an external link will make it appear as a new tab alongside the science, classify, and discuss tabs.
             <ExternalLinksEditor project={@props.project} />

-

-
-

-
-

+
+

+
           Data export

           <button type="button" disabled={@state.exportRequested} onClick={@requestDataExport}>Request new data export</button>{' '}


phase 1: daily

phase 1: daily

--- application/lib/General.php.coffee
+++ application/lib/General.php.coffee
@@ -16,7 +16,11 @@
 #

 module.exports = class General
-  error_messages: {}
+
+  error_messages: null
+
+  constructor: (@controller, $config = {}) ->
+    @error_messages = {}

   #  Check format of config/accounts ini files


rm callback

rm callback

--- components/layout/body/view.coffee
+++ components/layout/body/view.coffee
@@ -70,11 +70,11 @@
     if clientRoute and clientRoute isnt 'Channel' and clientRoute isnt 'User'
       Backbone.history.navigate "#{url}", trigger: true, replace: false
     else
-      trackOutboundLink url, =>
-        if e.metaKey || e.ctrlKey
-          window.open(url, '_blank')
-        else
-          window.location = url
+      trackOutboundLink url
+      if e.metaKey || e.ctrlKey
+        window.open(url, '_blank')
+      else
+        window.location = url

   triggerMediator: (e)->
     $link = $(e.currentTarget)


Add logs

Add logs

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -44,6 +44,9 @@
       payload =
         message: res.message
         attachments: [dataHelper.parseActivityDetails(activityDetails)]
+
+      console.log 'Emitting payload:'
+      console.log payload

       robot.emit('slack-attachment', payload)

@@ -138,6 +141,9 @@
     message: res.message
     attachments: attachment

+  console.log 'Emitting payload:'
+  console.log payload
+
   robot.emit 'slack-attachment', payload

 checkNetwork = (network) ->
@@ -152,6 +158,8 @@

 # Sends error message as DM in slack
 sendError = (robot, res, message) ->
+  console.log 'Sending error message:'
+  console.log message
   robot.send {room: res.message.user.name, "unfurl_media": false}, message

 tryPlayerId = (res, membershipType, displayName, robot) ->
@@ -275,11 +283,15 @@
       return deferred.reject('No activity details found')

     if activityKey not in constants.FURTHER_DETAILS
+      console.log 'Resolving activity details for #{activityKey}:'
+      console.log activityDetails
       return deferred.resolve(activityDetails)

     parseActivityHash(bot, activityDetails.activityHash).then (details) ->

       combinedDetails = Object.assign {}, activityDetails, details
+      console.log 'Resolving combined activity details for #{activityKey}:'
+      console.log combinedDetails
       deferred.resolve(combinedDetails)

     ,(err) ->


Your name is Hayt now.

Your name is Hayt now.

APPLY DIRECTLY TO MASTER

--- scripts/deploy.coffee
+++ scripts/deploy.coffee
@@ -35,7 +35,7 @@
     if branch[0] == '@' and branch.indexOf('/') != -1
       branch = branch.replace '@', ''
       [remote, branch] = branch.split '/'
-      prefix = "git remote add #{remote} https://github.com/#{remote}/arrakis-hubot"
+      prefix = "git remote add #{remote} https://github.com/#{remote}/hayt"

     msg.send "\"Deploying\" #{remote}/#{branch}"
     exec "#{prefix} ; git fetch --all && git checkout -f '#{remote}/#{branch}'  && git merge origin/master", (err, stdout, stderr) ->


make dgshow suggest more friendly

make dgshow suggest more friendly

--- scripts/dgshow.coffee
+++ scripts/dgshow.coffee
@@ -12,7 +12,7 @@
 # Commands:
 #   hubot dgshow ep[num]- Dave and Gunnar Show Episode <num>
 #   hubot dgshow latest - Most recent Dave and Gunnar Show episode
-#   hubot dgshow suggest "<title>" <url> - Add a link to the cutting room floor
+#   hubot dgshow suggest <text> <url> - Add a link to the cutting room floor
 #   hubot dgshow suggestions - Show all the suggestions so far
 #
 # Notes:
@@ -53,7 +53,7 @@
           object = JSON.parse(body)[0]
           msg.send object.link

-  robot.respond /dgshow suggest ['"](.*)['"] (.*)/, (msg) ->
+  robot.respond /dgshow suggest (.*) (http.*)/, (msg) ->
       cardName = msg.match[1]
       cardUrl = msg.match[2]
       if not cardName.length


what we really need to do

what we really need to do

--- scripts/fierce.coffee
+++ scripts/fierce.coffee
@@ -94,4 +94,7 @@
     msg.send "http://www.wppl.org/kidsteens/paws.jpg"

   robot.hear /(phil|philip|phillip|fill)/i, (msg) ->
-    msg.send "https://fiercemarkets.campfirenow.com/room/470540/uploads/3179113/philweb1.jpg"+    msg.send "https://fiercemarkets.campfirenow.com/room/470540/uploads/3179113/philweb1.jpg"
+
+  robot.hear /secur(e|ity)/i, (msg) ->
+    msg.send 'TODO: add security'


Start setting up auth to exclude awebot.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -103,7 +103,7 @@
             chat = JSON.parse(body)
             people = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers])

-            if points[username] == "awebot"
+            if robot.auth.hasRole('awebot', ['bot'])
                 # No points for awebot
             else
                 for username in people


Temp interval of 1min for testing.

Temp interval of 1min for testing.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -101,6 +101,14 @@

         msg.send "#{msg.envelope.user.name}, you have " + points[username] + " points!"

+    robot.http("https://api.twitch.tv/kraken/streams/masonest")
+        .get() (err, res, body) ->
+            streamer = JSON.parse(body)
+            if streamer.stream == null
+                pointrate = 1
+            else
+                pointrate = 5
+
     # Test online or offline
     robot.respond /status$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
@@ -108,17 +116,9 @@
                 .get() (err, res, body) ->
                     streamer = JSON.parse(body)
                     if streamer.stream == null
-                        msg.send "The stream is offline and point rate is slower."
+                        msg.send "The stream is offline and point rate is " + pointrate + " point per hour."
                     else
-                        msg.send "The stream is online and point rate is increased."
-
-    robot.http("https://api.twitch.tv/kraken/streams/masonest")
-        .get() (err, res, body) ->
-            streamer = JSON.parse(body)
-            if streamer.stream == null
-                pointrate = 1
-            else
-                pointrate = 5
+                        msg.send "The stream is online and point rate is " + pointrate + " points per hour."

     setInterval (->
         robot.http("https://tmi.twitch.tv/group/user/masonest/chatters").get() (err, res, body) ->
@@ -135,7 +135,8 @@
             # Winners (and then recall) is equal to all users
             # with points, and their points respectively.
             robot.brain.set 'winners', points
-    ), 3600000
+    ), 60000
+    # 3600000 for an hour

     # Get a list of stored people and points
     robot.respond /getset$/i, (msg) ->


senpai implement history count

senpai implement history count

--- scripts/senpai.coffee
+++ scripts/senpai.coffee
@@ -113,6 +113,44 @@
   str.replace new RegExp('' + REGEXP_KEISHO + '$'), ''
 # }}}

+class History # {{{
+  constructor: (@robot, @msg, @cache, @keep = 10) ->
+    @cache ||= []
+
+  add: (message) ->
+    @cache.push message
+    while @cache.length > @keep
+      @cache.shift()
+    @cache
+
+  show: (lines) ->
+    if (lines > @cache.length)
+      lines = @cache.length
+#    reply = 'Showing ' + lines + ' lines of history:\n'
+    reply = reply + @entryToString(message) + '\n' for message in @cache[-lines..]
+    return reply
+
+  entryToString: (event) ->
+    return '[' + event.month + '/' + event.date + ' ' + event.hours + ':' + event.minutes + '] ' + event.name + 'から: ' + event.message
+
+  clear: ->
+    @cache = []
+
+class HistoryEntry
+  constructor: (@name, @message) ->
+    @time = new Date()
+    @month = @time.getMonth() + 1
+    if @month < 10
+      @month = '0' + @month
+    @date = @time.getDate()
+    if @date < 10
+      @date = '0' + @date
+    @hours = @time.getHours()
+    @minutes = @time.getMinutes()
+    if @minutes < 10
+      @minutes = '0' + @minutes
+# }}}
+
 module.exports = (robot) ->
   robot.brain.on 'loaded', =># {{{
     robot.brain.data.senpaiStorage ||= {}
@@ -121,16 +159,20 @@

 # {{{ あいさつ
   robot.respond /よろしく/, (msg) ->
-    unless whoIsThis robot, msg, msg.message.user.name
-      msg.send "#{msg.message.user.name} こいつ誰？ > all"
+    fromname = msg.message.user.name
+    fromuser = whoIsThis robot, msg, fromname
+    unless whoIsThis robot, msg, fromuser
+      msg.send "#{fromuser} こいつ誰？ > all"
       checkKeigo robot, msg
       return
     msg.send msg.random AISATSU_YOROSHIKU
     checkKeigo robot, msg

   robot.respond /おはよう/, (msg) ->
-    unless whoIsThis robot, msg, msg.message.user.name
-      msg.send "#{msg.message.user.name} こいつ誰？ > all"
+    fromname = msg.message.user.name
+    fromuser = whoIsThis robot, msg, fromname
+    unless whoIsThis robot, msg, fromuser
+      msg.send "#{fromuser} こいつ誰？ > all"
       checkKeigo robot, msg
       return
     msg.send msg.random AISATSU_OHAYO
@@ -285,27 +327,40 @@
 # }}}

 # {{{ plusplus
-  robot.hear /([^ ]+)\+\+/i, (msg) ->
-    name = msg.match[1]
-    name = trimKeisho name
-    user = whoIsThis robot, msg, name
+  robot.hear /([^ ]+)(\+\+[ ]*(.*))/i, (msg) ->
     fromname = msg.message.user.name
     fromuser = whoIsThis robot, msg, fromname
+    unless whoIsThis robot, msg, fromuser
+      msg.send "#{fromuser} こいつ誰？ > all"
+      checkKeigo robot, msg
+      return
+
+    name = msg.match[1]
+    name = trimKeisho name
+    user = whoIsThis robot, msg, name
+    reason = msg.match[2]

     unless user?
       msg.send "#{name} って誰？先に教えて"
+      checkKeigo robot, msg
       return

     if user is fromuser
       msg.send "#{name} 自分で点数いれんなよ。悲しいやつだな"
-      return
-
+      checkKeigo robot, msg
+      return

     count = getUserInfo robot, msg, user, 'COUNT'
     count ||= 0
     count++
     setUserInfo robot, msg, user, 'COUNT', count

+    count_history = getUserInfo robot, msg, user, 'COUNT_HISTORY'
+    history = new History(robot, msg, count_history)
+    historyentry = new HistoryEntry(fromuser, reason)
+    count_history = history.add historyentry
+    setUserInfo robot, msg, user, 'COUNT_HISTORY', count_history
+
     if user is robot.name
       msg.send "俺: #{count}点 サンキューな"
     else
@@ -313,19 +368,35 @@
 # }}}

 # {{{ minusminus
-  robot.hear /([^ ]+)--/i, (msg) ->
+  robot.hear /([^ ]+)(--[ ]*(.*))/i, (msg) ->
+    fromname = msg.message.user.name
+    fromuser = whoIsThis robot, msg, fromname
+    unless whoIsThis robot, msg, fromuser
+      msg.send "#{fromuser} こいつ誰？ > all"
+      checkKeigo robot, msg
+      return
+
     name = msg.match[1]
     name = trimKeisho name
     user = whoIsThis robot, msg, name
+    reason = msg.match[2]

     unless user?
       msg.send "#{name} って誰？先に教えて"
+      checkKeigo robot, msg
       return

     count = getUserInfo robot, msg, user, 'COUNT'
+
     count ||= 0
     count--
     setUserInfo robot, msg, user, 'COUNT', count
+
+    count_history = getUserInfo robot, msg, user, 'COUNT_HISTORY'
+    history = new History(robot, msg, count_history)
+    historyentry = new HistoryEntry(fromuser, reason)
+    count_history = history.add historyentry
+    setUserInfo robot, msg, user, 'COUNT_HISTORY', count_history

     if user is robot.name
       msg.send "俺: #{count}点 まいったな"
@@ -345,6 +416,11 @@
     count ||= 0

     msg.send "#{name} は #{count}点だな"
-    checkKeigo robot, msg
-# }}}
-
+
+    count_history = getUserInfo robot, msg, user, 'COUNT_HISTORY'
+    history = new History(robot, msg, count_history, 5)
+    msg.send history.show 5
+
+    checkKeigo robot, msg
+# }}}
+


Add tests for m and mm

Add tests for m and mm

--- spec/languages.coffee
+++ spec/languages.coffee
@@ -646,4 +646,21 @@
       mixed: 1
       empty: 0
     }
+    {
+      names: ["m", "mm"]
+      code:
+        """
+          // comment
+          NSLog("foo"); // comment
+
+          /* block comment */
+        """
+      comment: 3
+      source: 1
+      block: 1
+      total: 4
+      single: 2
+      mixed: 1
+      empty: 1
+    }
   ]


simple hierarchical relationships

simple hierarchical relationships

--- spec/modelSpec.coffee
+++ spec/modelSpec.coffee
@@ -183,9 +183,14 @@
       childA  = parent.children.create()
       childB  = parent.children.create()

-      console.log parent
-      console.log childA
-      console.log childB
-      console.log parent.children.all()
+      expect(parent.parentId).toEqual undefined
+      expect(childA.parentId).toEqual 0
+      expect(childB.parentId).toEqual 0
+      expect(parent.children.all()).toEqual [childA, childB]

-      console.log childB.parent+      #console.log parent
+      #console.log childA
+      #console.log childB
+      #console.log parent.children.all()
+      #
+      #console.log childB.parent


Add missing @ before configDirPath

Add missing @ before configDirPath

--- src/config.coffee
+++ src/config.coffee
@@ -41,18 +41,18 @@
       path.join(@resourcePath, 'static', 'variables')
       path.join(@resourcePath, 'static')
     ]
-    @packageDirPaths = [path.join(configDirPath, "packages")]
+    @packageDirPaths = [path.join(@configDirPath, "packages")]
     if atom.getLoadSettings().devMode
-      @packageDirPaths.unshift(path.join(configDirPath, "dev", "packages"))
+      @packageDirPaths.unshift(path.join(@configDirPath, "dev", "packages"))
     @userPackageDirPaths = _.clone(@packageDirPaths)
-    @userStoragePath = path.join(configDirPath, "storage")
+    @userStoragePath = path.join(@configDirPath, "storage")

     @defaultSettings =
       core: _.clone(require('./root-view').configDefaults)
       editor: _.clone(require('./editor').configDefaults)
     @settings = {}
-    @configFilePath = fsUtils.resolve(configDirPath, 'config', ['json', 'cson'])
-    @configFilePath ?= path.join(configDirPath, 'config.cson')
+    @configFilePath = fsUtils.resolve(@configDirPath, 'config', ['json', 'cson'])
+    @configFilePath ?= path.join(@configDirPath, 'config.cson')

   # Private:
   initializeConfigDirectory: (done) ->


Add tests for object clone().

Add tests for object clone().

--- spec/util-spec.coffee
+++ spec/util-spec.coffee
@@ -1,4 +1,5 @@
 uri = require('../lib/util/uri-helpers')
+clone = require('../lib/util/clone-obj')

 xdescribe 'resolveURL()', ->
   it 'should replace the last segment', ->
@@ -15,3 +16,28 @@

   it 'should resolve fully-qualified URIs', ->
     expect(uri.resolveURL('//site.com/a/b/c', 'x/y')).toBe '//site.com/a/b/x/y'
+
+describe 'clone()', ->
+  it 'should clone', ->
+    object =
+      name: 'hello'
+    result = clone(object)
+    expect(object).toEqual result
+    expect(object).not.toBe result
+
+  it 'should throw for circular objects', ->
+    circular =
+      prop:
+        name: 'foo'
+    circular.prop.otherProp = circular.prop
+    expect(-> clone(circular)).toThrow('unable dereference circular structures');
+
+  it 'should permit shared subobjects', ->
+    subobject =
+      name: 'hello'
+    object =
+      one: subobject
+      two: subobject
+    result = clone(object)
+    expect(object).toEqual result
+    expect(object).not.toBe result


fix(vm): missing parameters and wrong value for set_memory_static_max

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -414,7 +414,7 @@
   if 'memoryMax' of params
     memoryMax = parseSize(params.memoryMax)
     if memoryMax > VM.memory.static[1]
-      yield xapi.call 'VM.set_memory_static_max', memoryMax
+      yield xapi.call 'VM.set_memory_static_max', ref, "#{memoryMax}"
     if resourceSet?
       yield @allocateLimitsInResourceSet({
         memory: memory - VM.memory.dynamic[1]
@@ -422,7 +422,7 @@
     yield xapi.call 'VM.set_memory_dynamic_max', ref, "#{memoryMax}"
   if 'memoryStaticMax' of params
     memoryStaticMax = parseSize(params.memoryStaticMax)
-    yield xapi.call 'VM.set_memory_static_max', memoryMax
+    yield xapi.call 'VM.set_memory_static_max', ref, "#{memoryStaticMax}"

   # Memory.


feat(vm.delete): remove ACLs (#496)

feat(vm.delete): remove ACLs (#496)

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -300,6 +300,16 @@
   memory = vm.memory.size

   xapi = @getXapi(vm)
+
+  @getAllAcls().then((acls) =>
+    Promise.all(mapFilter(acls, (acl) =>
+      if (acl.object == vm.id)
+        return pCatch.call(
+          @removeAcl(acl.subject, acl.object, acl.action),
+          noop
+        )
+    ))
+  )

   # Update IP pools
   yield Promise.all(map(vm.VIFs, (vifId) =>


:memo: Pluralize line

:memo: Pluralize line

--- src/editor.coffee
+++ src/editor.coffee
@@ -621,7 +621,7 @@
   largestFoldStartingAtScreenRow: (screenRow) ->
     @displayBuffer.largestFoldStartingAtScreenRow(screenRow)

-  # Public: Moves the selected line up one row.
+  # Public: Moves the selected lines up one row.
   moveLineUp: ->
     selection = @getSelectedBufferRange()
     return if selection.start.row is 0
@@ -655,7 +655,7 @@

       @setSelectedBufferRange(selection.translate([-1]), preserveFolds: true)

-  # Public: Moves the selected line down one row.
+  # Public: Moves the selected lines down one row.
   moveLineDown: ->
     selection = @getSelectedBufferRange()
     lastRow = @buffer.getLastRow()


fix preserve selection for adding newline to list item

--- src/editor.coffee
+++ src/editor.coffee
@@ -377,16 +377,27 @@
     nativeSel = Tandem.Range.getNativeSelection(this)
     if nativeSel?
       isBodyChild = (node) -> return node.parentNode?.tagName == 'BODY'
-      startLine = Tandem.Utils.findAncestor(nativeSel.anchorNode, isBodyChild)
-      endLine = Tandem.Utils.findAncestor(nativeSel.focusNode, isBodyChild)
-      startOffset = Tandem.Position.getIndex(nativeSel.anchorNode, nativeSel.anchorOffset, startLine)
-      endOffset = Tandem.Position.getIndex(nativeSel.focusNode, nativeSel.focusOffset, endLine)
+      isBlockTag = (node) -> return _.indexOf(Tandem.Constants.BLOCK_TAGS, node.tagName, true) > -1
+      startBlock = Tandem.Utils.findAncestor(nativeSel.anchorNode, isBlockTag)
+      endBlock = Tandem.Utils.findAncestor(nativeSel.focusNode, isBlockTag)
+      startLine = Tandem.Utils.findAncestor(startBlock, isBodyChild)
+      endLine = Tandem.Utils.findAncestor(endBlock, isBodyChild)
+      startBlockOffset = Tandem.Position.getIndex(nativeSel.anchorNode, nativeSel.anchorOffset, startBlock)
+      endBlockOffset = Tandem.Position.getIndex(nativeSel.anchorNode, nativeSel.anchorOffset, endBlock)
+      startLineOffset = Tandem.Position.getIndex(startBlock, 0, startLine) + startBlockOffset
+      endLineOffset = Tandem.Position.getIndex(endBlock, 0, endLine) + endBlockOffset
       #[selStart, selEnd] = this.transformSelection(modificationStart, startIndex, endIndex, charAdditions)
       fn()
-      startLine = startLine.ownerDocument.getElementById(startLine.id) if startLine.parentNode == null
-      endLine = endLine.ownerDocument.getElementById(endLine.id) if endLine.parentNode == null
-      startPos = new Tandem.Position(this, startLine, startOffset)
-      endPos = new Tandem.Position(this, endLine, endOffset)
+      if startBlock.parentNode?
+        startPos = new Tandem.Position(this, startBlock, startBlockOffset)
+      else
+        startLine = startLine.ownerDocument.getElementById(startLine.id) if startLine.parentNode == null
+        startPos = new Tandem.Position(this, startLine, startLineOffset)
+      if endBlock.parentNode?
+        endPos = new Tandem.Position(this, startBlock, startBlockOffset)
+      else
+        endLine = endLine.ownerDocument.getElementById(endLine.id) if endLine.parentNode == null
+        endPos = new Tandem.Position(this, endLine, endLineOffset)
       savedSelectionRange = new Tandem.Range(this, startPos, endPos)
       Tandem.Range.setSelection(this, savedSelectionRange)
       this.checkSelectionChange()


Adding FIXME.

Adding FIXME.

--- src/engine.coffee
+++ src/engine.coffee
@@ -55,6 +55,7 @@

     # Remove the given elements from the engine.
     remove : (elements...) ->
+        # FIXME: won't scale. O(n)
         for element in elements
             index = @elements.indexOf(element)
             @elements.splice(index, 1) if index >= 0


extracting Engine.Clear module

extracting Engine.Clear module

--- src/engine.coffee
+++ src/engine.coffee
@@ -158,13 +158,11 @@
       self.trigger "afterUpdate"

     draw = ->
-      return unless I.canvas
+      return unless canvas = I.canvas

       self.trigger "beforeDraw", canvas

       I.canvas.withTransform I.cameraTransform, (canvas) ->
-
-
         if I.zSort
           drawObjects = I.objects.copy().sort (a, b) ->
             a.I.zIndex - b.I.zIndex


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -45,8 +45,7 @@
 ###
 Follow = (I={}, self) ->
   Object.reverseMerge I,
-    followSpeed: 1
     direction: Point(0, 0)

   follow: (obj) ->
-    I.velocity = obj.position().subtract(self.position()).norm().scale(I.followSpeed)
+    I.direction = obj.position().subtract(self.position()).norm()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -36,7 +36,7 @@
       height: 2 * deadzone.y

     if objectCenter.x > centerRect.right
-      I.scroll.x = I.scroll.x.app
+      I.scroll.x = I.scroll.x.approach()
     else if objectCenter.x < centerRect.left


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -35,7 +35,7 @@
       width: 2 * deadzone.x
       height: 2 * deadzone.y

-    if objectCenter.x
+    if objectCenter.x >
     I.scroll = Point(
       I.scroll.x.clamp(centerRect.left, centerRect.right)
       I.scroll.y.clamp(centerRect.top, centerRect.bottom)


Actually wait the time you said you were going to wait

--- src/client.coffee
+++ src/client.coffee
@@ -180,7 +180,7 @@
         setTimeout =>
             @logger.info 'Attempting reconnect'
             @login()
-        , 5000
+        , timeout

     disconnect: ->


Refining of keyboard input for browsers

Refining of keyboard input for browsers

--- src/io/web.coffee
+++ src/io/web.coffee
@@ -4,58 +4,67 @@

 viewport = null

-mapKey = (event) ->
-	keyIdent = event.key ? event.keyIdentifier
-	key = null
+mapKey = (keyIdent) ->
+	switch keyIdent
+		when 'Up', 'ArrowUp'
+			'up'

-	log keyIdent
-	if keyIdent? and not /U+\d{4}/.test keyIdent
-		key =
-			switch keyIdent
-				when 'Up', 'ArrowUp'
-					'up'
+		when 'Down', 'ArrowDown'
+			'down'

-				when 'Down', 'ArrowDown'
-					'down'
+		when 'Left', 'ArrowLeft'
+			'left'

-				when 'Left', 'ArrowLeft'
-					'left'
+		when 'Right', 'ArrowRight'
+			'right'

-				when 'Right', 'ArrowRight'
-					'right'
+		when 'Enter'
+			'enter'

-				when 'Enter'
-					'enter'
+handleKey = (game, events) ->
+	[downEvent, pressEvent] = events
+	log.silly 'Key events:', events

-	key ?= String.fromCharCode event.keyCode
+	ch = undefined
+	name = mapKey (downEvent.key ? downEvent.keyIdentifier)

-	key
+	if pressEvent?
+		ch = pressEvent.char ? String.fromCharCode pressEvent.charCode
+		name ?= ch
+
+	key =
+		ch: ch
+		name: name
+
+		ctrl: downEvent.ctrlKey
+		shift: downEvent.shiftKey
+		alt: downEvent.altKey
+		meta: downEvent.metaKey
+
+	key.full =
+		(if key.ctrl then 'C-' else '') +
+		(if key.meta then 'M-' else '') +
+		(if key.shift then 'S-' else '') +
+		(key.name ? key.ch)
+
+	game.emit "key.#{key.name}", key.ch, key

 initialize = (game) ->
 	canvas = document.getElementById 'viewport'
 	viewport = canvas.getContext '2d'

-	document.addEventListener 'keydown', (event) ->
-		log "Keydown:", event
+	events = []

-		key =
-			ch: (event.char ? String.fromCharCode event.which).toLowerCase()
-			ctrl: event.ctrlKey
-			shift: event.shiftKey
-			alt: event.altKey
-			meta: event.metaKey
+	handle = (event) ->
+		events.push event

-		key.name =
-			mapKey event
-			.toLowerCase()
+		if events.length is 1
+			process.nextTick ->
+				handleKey game, events
+				events = []

-		key.full =
-			(if key.ctrl then 'C-' else '') +
-			(if key.meta then 'M-' else '') +
-			(if key.shift then 'S-' else '') +
-			(key.name ? key.ch)
-
-		game.emit "key.#{key.name}", key.ch, key
+	document.addEventListener 'keypress', handle
+	document.addEventListener 'keydown', handle

 deinitialize = (game) ->
 	viewport = null


add todo

add todo

--- src/js/app.coffee
+++ src/js/app.coffee
@@ -8,6 +8,8 @@
 cards = require('cards')
 timeline = require('timeline')

+
+# TODO: don't use timeline if arch is aplite
 CONFIG_BASE_URL = 'https://pebbleshows.herokuapp.com/pebbleConfig'


better handling of map options for openlayers

--- spec/dummy/app/assets/javascripts/gmaps4rails/openlayers/objects/map.coffee
+++ spec/dummy/app/assets/javascripts/gmaps4rails/openlayers/objects/map.coffee
@@ -17,13 +17,16 @@

     defaultOptions = @setConf()
     @options  = @mergeObjects map_options, defaultOptions
+
+    openlayersOptions =
+      center: @createLatLng(@options.center_latitude, @options.center_longitude)
+      zoom:   @options.zoom

-    @serviceObject = new OpenLayers.Map(@options.id)
+    mergedOpenlayersOptions = @mergeObjects map_options.raw, openlayersOptions
+
+    @serviceObject = new OpenLayers.Map(@options.id, mergedOpenlayersOptions)
+
     @serviceObject.addLayer(new OpenLayers.Layer.OSM())
-    @serviceObject.setCenter(
-      @createLatLng(@options.center_latitude, @options.center_longitude), # Center of the map
-      @options.zoom # Zoom level
-    )

   extendBoundsWithMarker : (marker)->
     @boundsObject.extend(@createLatLng(marker.lat,marker.lng))


minor debug info

minor debug info

--- server/methods.coffee
+++ server/methods.coffee
@@ -19,7 +19,7 @@
     collection.insert object.computeValues()  # insert returns the unique _id

   starUpdate: (name, id, values, formName)->
-    console.log "Inside starUpdate name: #{name} id:#{id} values:#{values}"
+    console.log "Inside starUpdate name: #{name} id:#{id} values:#{values} Meteor.userId:#{@userId}"
     if Object.keys(values).length == 0
       console.log "No values received for object #{name} id:#{id}"
       return 0


Don't rely on state being immediately updated after setState

--- app/assets/javascripts/rglossa/react/components/centre/main_area.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/main_area.jsx.coffee
@@ -1,3 +1,4 @@
+#= require rglossa/react/utils
 #= require ./main_area_top
 #= require ./main_area_bottom

@@ -17,18 +18,20 @@
     @setState(query: query)

   handleMaxHitsChanged: (maxHits) ->
-    @setState(maxHits: maxHits)
-    @handleSearch()
+    newState = maxHits: maxHits
+    @setState(newState)
+    @handleSearch(newState)

   showCorpusHome: ->
     alert 'showCorpusHome'

-  handleSearch: ->
+  handleSearch: (newState = {}) ->
+    state = rglossaUtils.merge(@state, newState)
     {store, statechart, corpus} = @props
     searchEngine = corpus.search_engine ?= 'cwb'
     url = "search_engines/#{searchEngine}_searches"
     query =
-      query: @state.query
+      query: state.query
       corpusShortName: corpus.short_name

     $.ajax(
@@ -36,7 +39,7 @@
       method: 'POST'
       data: JSON.stringify
         queries: [query]
-        max_hits: @state.maxHits
+        max_hits: state.maxHits
       dataType: 'json'
       contentType: 'application/json'
     ).then (res) =>


:lipstick:

:lipstick:

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -262,6 +262,7 @@
     newScrollTop = @scrollTop() - @scrollView[0].clientHeight
     @activeEditSession.moveCursorUp(@getPageRows())
     @scrollTop(newScrollTop,  adjustVerticalScrollbar: true)
+
   getPageRows: ->
     Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight))


fix edit post cancel not working after navigating back

--- resources/assets/coffee/forum/topic-ajax.coffee
+++ resources/assets/coffee/forum/topic-ajax.coffee
@@ -23,7 +23,7 @@
   $postBox = $(e.target).parents('.js-forum-post')

   $postBox
-    .data 'originalPost', $postBox.html()
+    .attr 'data-original-post', $postBox.html()
     .html data
     .find '[name=body]'
     .focus()
@@ -35,7 +35,7 @@
   e.preventDefault()

   $postBox = $(e.target).parents '.js-forum-post'
-  $postBox.html $postBox.data('originalPost')
+  $postBox.html $postBox.attr('data-original-post')

   osu.pageChange()


fix focus for Add Groups dialog.

fix focus for Add Groups dialog.

fixes CNVS-24876

test plan:
-as a teacher, to to an assignments page
-attempt to add group by clicking +Group button
-Focus should be set on the input field
-input field should properly read out its label
-Make sure it works properly across all screen readers

Change-Id: I199c01ce97d21e5e511355aac35ab2b0dc323246
Reviewed-on: https://gerrit.instructure.com/68671
Tested-by: Jenkins
Reviewed-by: John Corrigan <3d73713d0cc718326e4fd7c06c64e067ad6e2270@instructure.com>
QA-Review: Rajkumar Kethavath <943d80ed9dc95d434849fae8b8fb2a95a056374d@instructure.com>
Product-Review: Jason Sparks <542e620f0c6c3447ce6e2e8c83a97102b99f89c6@instructure.com>

--- app/coffeescripts/views/assignments/CreateGroupView.coffee
+++ app/coffeescripts/views/assignments/CreateGroupView.coffee
@@ -144,5 +144,6 @@
         @setDimensions(this.defaults.width, SHORT_HEIGHT)

       super
+      @$el.find("input:first").focus()
       @checkGroupWeight()
       @getNeverDrops()


Fix redrawing of links when they are moved.

--- app/scripts/viewmodels/drawings/link_canvas_element.coffee
+++ app/scripts/viewmodels/drawings/link_canvas_element.coffee
@@ -12,8 +12,7 @@

     constructor: (@element, @canvas) ->
       @draw()
-      @linkToThis(@canvasElement)
-
+      @linkToThis(@canvasElement)
       @element.bind("reshape", @updateSegments)
       @element.bind("destroy", @remove)

@@ -39,13 +38,8 @@
     # TODO make "private"
     updateSegments: =>
       @canvasElement.remove()
-      @canvasElement = @element.toPath()
-      @linkToThis(@canvasElement)
-      @linkToModel(@canvasElement)
-
-      # TODO trim the line in the tool
-      # rather than hiding the overlap behind the nodes here
-      paper.project.activeLayer.insertChild(0, @canvasElement)
+      @draw()
+      @linkToThis(@canvasElement)

     destroy: =>
       @canvas.commander.run(new DeleteLink(@canvas.drawing, @element))


bongo: DRY updating buckets

bongo: DRY updating buckets

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -29,7 +29,7 @@

     KD.getSingleton("groupsController").on "FollowHappened", (info) =>
       {follower, origin} = info
-      @updateBuckets follower, origin
+      @updateFollowerBucket follower, origin

   resetList:->
     @newActivityArrivedList = {}
@@ -158,20 +158,10 @@
   updateNewMemberBucket:(memberAccount)->
     for item in @itemsOrdered
       if item.getData() instanceof NewMemberBucketData
-        data = item.getData()
-        if data.count > 3
-          data.anchors.pop()
-        id = memberAccount.id
-        data.anchors.unshift {bongo_: {constructorName:"ObjectRef"}, constructorName:"JAccount", id:id}
-        data.createdAtTimestamps.push (new Date).toJSON()
-        data.count++
-        item.slideOut =>
-          @removeItem item, data
-          newItem = @addHiddenItem data, 0
-          @utils.wait 500, -> newItem.slideIn()
+        @updateBucket item, "JAccount", memberAccount.id
         break

-  updateBuckets:(follower, followee)->
+  updateFollowerBucket:(follower, followee)->
     for item in @itemsOrdered
       data = item.getData()

@@ -180,19 +170,25 @@

       if data.group[0].constructorName is followee.bongo_.constructorName
         if data.anchor.id is follower.id
-          data.group.unshift {
-            bongo_:
-              constructorName:"ObjectRef"
-            constructorName:followee.bongo_.constructorName, id:followee._id
-          }
-          data.createdAtTimestamps.push (new Date).toJSON()
-          data.count ||= 0
-          data.count++
-          item.slideOut =>
-            @removeItem item, data
-            newItem = @addHiddenItem data, 0
-            @utils.wait 500, -> newItem.slideIn()
+          @updateBucket item, followee.bongo_.constructorName, followee._id
           break
+
+  updateBucket:(item, constructorName, id)->
+    data = item.getData()
+    group = data.group or data.anchors
+    group.unshift {
+      bongo_:
+        constructorName:"ObjectRef"
+      constructorName
+      id
+    }
+    data.createdAtTimestamps.push (new Date).toJSON()
+    data.count ||= 0
+    data.count++
+    item.slideOut =>
+      @removeItem item, data
+      newItem = @addHiddenItem data, 0
+      @utils.wait 500, -> newItem.slideIn()

   fakeItems = []


Minor

Minor

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -214,34 +214,36 @@
     range or= {}
     {skip, limit} = range

+    controller = @activityListController
+
     selector =
       type        :
         $in       : @currentFilter

     options  =
       limit       : limit or= 20
-      skip        : skip  or= @activityListController.getItemCount()
+      skip        : skip  or= controller.getItemCount()
       sort        :
         createdAt : -1

     if not options.skip < options.limit
-      if @activityListController._state is 'public'
+      if controller._state is 'public'
         @fetchTeasers selector, options, (activities)=>
           if activities
             for activity in activities
-              @activityListController.addItem activity
+              controller.addItem activity
             callback? activities
           else
             callback?()

-      else if @activityListController._state is 'private'
+      else if controller._state is 'private'
         KD.whoami().fetchFeeds (err, feeds) =>
           for feed in feeds
             continue unless feed.title is 'followed'
             feed.fetchActivities selector, options, (err, activities) =>
               if not err and activities?
                 for activity in activities
-                  @activityListController.addItem activity
+                  controller.addItem activity
                 callback? activities
               else
                 callback?()


tidying

tidying

--- src/Server.coffee
+++ src/Server.coffee
@@ -92,7 +92,7 @@
       .verb
         name: 'GET'
         response: 'Fetch a list of accounts'
-      response.render('relationship', relationship);
+      response.render 'relationship', relationship

     @expressServer.get '/rels/books', (request, response) =>
       relationship = new Relationship
@@ -100,7 +100,7 @@
       .verb
         name: 'GET'
         response: 'Fetch a list of collections of books by bid currency'
-      response.render('relationship', relationship);
+      response.render 'relationship', relationship

     @expressServer.get '/accounts', (request, response) =>
       response.type 'application/hal+json'


empty buttons removed on codeshareview

empty buttons removed on codeshareview

--- client/landing/app/Applications/Activity.kdapplication/views/codesharetabpaneview.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/codesharetabpaneview.coffee
@@ -356,10 +356,11 @@
     @openAllButton.$().twipsy twOptions("Open All")

   pistachio:->
+    #
{{> @runButton}}{{> @saveButton}}{{> @openButton}}{{> @openAllButton}}{{> @copyButton}}

     """

       {pre{> @codeView}}
-
{{> @runButton}}{{> @saveButton}}{{> @openButton}}{{> @openAllButton}}{{> @copyButton}}

+
{{> @saveButton}}{{> @openButton}}{{> @copyButton}}

     {{> @syntaxMode}}
     """


activity: edge case covered on cache when listing from cache if an overviewitem gets multiple itmes other than newmemberactivities

--- client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -84,7 +84,7 @@
     return @noActivityItem.show() unless Object.keys(cache).length

     for overviewItem in cache.overview when overviewItem
-      if overviewItem.ids.length > 1
+      if overviewItem.ids.length > 1 and overviewItem.type is "CNewMemberBucketActivity"
         @addItem new NewMemberBucketData
           type                : "CNewMemberBucketActivity"
           anchors             : (cache.activities[id].teaser.anchor for id in overviewItem.ids)


ActivityTutorialWidget: @embedBox.hide.fixed line was hiding whole widget.

--- client/app/Applications/Activity.kdapplication/widgets/tutorialwidget.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/tutorialwidget.coffee
@@ -31,15 +31,15 @@
           required  : "Tutorial title is required!"

     @inputTutorialEmbedShowLink = new KDOnOffSwitch
-      cssClass:"show-tutorial-embed"
-      defaultState:off
-      callback:(state)=>
+      cssClass      : "show-tutorial-embed"
+      defaultState  : off
+      callback      : (state)=>
         if state
           if @embedBox.hasValidContent
             @embedBox.show()
             @embedBox.$().animate {top: "0px"}, 300
         else
-          @embedBox.$().animate {top : "-400px"}, 300, @embedBox.hide.bind this
+          @embedBox.$().animate {top : "-400px"}, 300, => @embedBox.hide()

     @inputTutorialEmbedLink = new KDInputView
       name          : "embed"


Enable save after deleting enterprise manager

--- app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee
+++ app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee
@@ -39,6 +39,7 @@
       if manager.id?
         for i, user of $scope.Enterprise.users when user.id == manager.id
           $scope.Enterprise.users.splice i, 1
+        $scope.enterprise_form.$setDirty()

     $scope.addManager = (manager) ->
       if manager.id? and manager.email?


Nodejs Web Server Tests: fix coffeelint error

--- servers/testhelper/handler/confirmhelper.coffee
+++ servers/testhelper/handler/confirmhelper.coffee
@@ -8,8 +8,8 @@
   csrfToken = generateRandomString()

   params =
-    qs         : {  token : 'token' }
-    url        : generateUrl { route : '-/confirm' }
+    qs  : { token : 'token' }
+    url : generateUrl { route : '-/confirm' }

   requestParams = generateRequestParamsEncodeBody params, opts


Load only true plugins from deps.

Load only true plugins from deps.

--- src/brunch.coffee
+++ src/brunch.coffee
@@ -11,10 +11,15 @@
 loadPlugins = (config, callback) ->
   cwd = sysPath.resolve config.rootPath
   fs.readFile 'package.json', (error, data) ->
-    deps = (JSON.parse data).dependencies
-    callback null, Object.keys(deps).map (dependency) ->
-      plugin = require "#{cwd}/node_modules/#{dependency}"
-      new plugin config
+    deps = Object.keys (JSON.parse data).dependencies
+    plugins = deps
+      .map (dependency) ->
+        require "#{cwd}/node_modules/#{dependency}"
+      .filter (plugin) ->
+        (plugin::)? and ((plugin::minifierType)? or (plugin::compilerType)?)
+      .map (plugin) ->
+        new plugin config
+    callback null, plugins

 isCompilerFor = (path) -> (plugin) ->
   pattern = if plugin.pattern


Remove traling comma

Remove traling comma

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -9,7 +9,7 @@

     refaker
       schemas: [schema]
-      fakeroot: 'http://example.com',
+      fakeroot: 'http://example.com'
       directory: path.resolve(__dirname + '/fixtures')
     , (err, refs) ->
       expect(data).toHaveSchema schema, refs


deprecate Hull.api.clearToken

deprecate Hull.api.clearToken

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -43,9 +43,6 @@

       @channel.rpc.message(opts, onSuccess, onError)

-  clearToken    : (args...)=>
-    @channel.rpc.clearAccessToken(args...) # No need to be exposed, IMO
-
   refreshUser   : ()=>
     new Promise (resolve, reject)=>
       onSuccess  = (response={})=>


Track + fix nasty bug

Track + fix nasty bug

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -14,7 +14,9 @@
           backbone: { exports: 'Backbone', deps: ['underscore', 'jquery'] }

     init: (env)->
-      core = env.core
+      core    = env.core
+      sandbox = env.sandbox
+
       _         = require('underscore')
       Backbone  = require('backbone')
       easyXDM   = require('easyXDM')
@@ -151,7 +153,6 @@
         dfd

       models = {}
-
       setupModel = (attrs)->
         if attrs.id
           model = new Model(attrs)
@@ -184,13 +185,13 @@

       api.model.clearAll =->
-        models = {}
+        models = _.pick(models, 'me', 'app', 'org')

       Model = Backbone.Model.extend
         sync: sync
-        url: ->
+        url: ->
           if (@id || @_id)
-            url = normalizeAPIArguments([@_id || @id])[0]
+            url = normalizeAPIArguments([@_id || @id])[0]
           else
             url = @collection?.url
           url
@@ -201,12 +202,12 @@

       collections = {}
-
+
       setupCollection = (path)->
         collection      = new Collection
         collectionURI   = path
         collection.url  = path
-
+
         collection.on 'all', ->
           args = slice.call(arguments)
           eventName = ("collection." + collectionURI.replace(/\//g, ".") + '.' + args[0])
@@ -224,7 +225,7 @@
             error:   ->
               dfd.fail(collection)
         collection
-
+
       api.collection = (path)->
         throw new Error('A model must have an path...') unless path?
         throw new Error('You must specify the provider...') if (path.path && !path.provider)
@@ -232,7 +233,7 @@
         path = normalizedArguments[0]
         collections[path] ?= setupCollection.apply(api, normalizedArguments)
         collections[path]
-
+

       api.get     = exec('get')
       api.post    = exec('post')
@@ -241,6 +242,11 @@
       api.batch   = batch

       core.data.api = api
+      core.track = (eventName, params)->
+        api("track/#{eventName}", 'post', params)
+
+      sandbox.track = (eventName, params)->
+        core.track(eventName, params)

       initialized = core.data.deferred()


Fix command line runner.

Fix command line runner.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -1,10 +1,11 @@
+_ = require 'underscore'
 Mapping = require '../lib/mapping'
 SphereClient = require 'sphere-node-client'
 package_json = require '../package.json'
 fs = require 'fs'
 argv = require('optimist')
   .usage('Usage: $0 --projectKey key --clientId id --clientSecret secret')
-  .default('timeout', 300000)
+  .default('timeout', 60000)
   .describe('projectKey', 'your SPHERE.IO project-key')
   .describe('clientId', 'your OAuth client id for the SPHERE.IO API')
   .describe('clientSecret', 'your OAuth client secret for the SPHERE.IO API')
@@ -25,8 +26,9 @@
 mapping = new Mapping options

 sphere.orders.perPage(0).fetch().then (result) ->
-  mapping.processOrders result.results, (xmlOrders) ->
-    for entry in xmlOrders
+  mapping.processOrders(result.body.results)
+  .then (xmlOrders) ->
+    _.each xmlOrders, (entry) ->
       content = entry.xml.end(pretty: true, indent: '  ', newline: "\n")
       fileName = "#{entry.id}.xml"
       fs.writeFile fileName, content, (err) ->


Possible fix for ensime-atom/#233 and ensime-atom/#234 Just removes colon after drive letter on win to make it a working absolute path under temp

--- src/client.coffee
+++ src/client.coffee
@@ -8,6 +8,12 @@
 temp.track()
 tempDir = temp.mkdirSync()
 getTempDir = -> tempDir
+
+getTempPath = (file) ->
+  if(process.platform == 'win32')
+    path.join(getTempDir(), file.replace(':', ""))
+  else
+    path.join(getTempDir(), file)

 module.exports = createClient = (httpPort, generalMsgHandler, serverPid = undefined) ->
   new Promise (resolve, reject) ->
@@ -64,7 +70,7 @@

     getCompletions = (filePath, bufferText, offset, noOfAutocompleteSuggestions, callback) ->
-      tempFilePath = getTempDir() + filePath
+      tempFilePath = getTempPath(filePath)
       fs.outputFile(tempFilePath, bufferText, (err) ->
         if (err)
           throw err
@@ -98,7 +104,7 @@

     typecheckBuffer = (path, text, callback = ->) ->
-      tempFilePath = getTempDir() + path
+      tempFilePath = getTempPath(path)
       fs.outputFile(tempFilePath, text, (err) ->
         if (err)
           throw err
@@ -128,7 +134,7 @@

     formatSourceFile = (path, contents, callback) ->
-      tempFilePath = getTempDir() + path
+      tempFilePath = getTempPath(path)
       fs.outputFile(tempFilePath, contents, (err) ->
         if (err)
           throw err


save fail/ success messages #478

save fail/ success messages #478

--- app/assets/javascripts/admin/views/task/task_create_subviews/task_create_from_view.coffee
+++ app/assets/javascripts/admin/views/task/task_create_subviews/task_create_from_view.coffee
@@ -8,6 +8,7 @@
 admin/views/selection_view : SelectionView
 ./task_create_from_form_view : TaskCreateFromFormView
 ./task_create_from_nml_view : TaskCreateFromNMLView
+libs/toast : Toast
 ###

 class TaskCreateFromView extends Backbone.Marionette.LayoutView
@@ -142,11 +143,10 @@

     # block submit button
     @ui.submitButton.prop("disabled", true)
+    @ui.submitButton.addClass("disabled")

     # load form contents into model
     @updateModel()
-
-    console.log(@model)

     # send form data to server
     # unblock submit button after model synched
@@ -154,8 +154,15 @@
     @model.save({},
       error : =>
         @ui.submitButton.prop("disabled", false)
+        @ui.submitButton.removeClass("disabled")
+
+        @showSaveError()
+
       success : =>
         @ui.submitButton.prop("disabled", false)
+        @ui.submitButton.removeClass("disabled")
+
+        @showSaveSuccess()
     )

     # prevent actual submitting
@@ -177,13 +184,10 @@
         # cannot nest model attributes
         # insert existign vars to maintain model defaults
         inProgress : @model.get("status").inProgress
-        complete : @model.get("status").complete
+        completed : @model.get("status").completed
       # parse priority, range 0 to 100, to integer
       priority : parseInt( @ui.priority.val() )
     )
-
-    # TODO: remove debug
-    console.log(@model)

     # update models from subviews
     @taskTypeSelectionView.updateModel()
@@ -196,10 +200,22 @@
     if @createFromNMLView?
       @createFromNMLView.updateModel()

-    # TODO: remove debug
-    console.log(@model)
-
     return
+
+
+  showSaveSuccess: ->
+
+    Toast.success('The task was successfully created')
+
+
+  showSaveError: ->
+
+    Toast.error('The task could not be created due to server errors.')
+
+
+  showInvalidData: ->
+
+    Toast.error('The form data is not correct.')

   ###*
   * Render the SelectionViews based on the stored options
@@ -216,7 +232,7 @@
         modelValue: -> return "#{@model.get("id")}"
         modelName: -> return "#{@model.get("summary")}"
       data : "amIAnAdmin=true"
-      name: "taskType"
+      name: "taskTypeId"
       parentModel : @model
     )


Add support to create ElastiCache instances.

--- src/coffin.coffee
+++ src/coffin.coffee
@@ -35,6 +35,11 @@
         SecurityGroupIngress: null
         Volume: null
         VolumeAttachment: null
+      ElastiCache:
+        CacheCluster: null
+        ParameterGroup: null
+        SecurityGroup: null
+        SecurityGroupIngress: null
       ElasticBeanstalk:
         Application: null
         Environment: null


Update configuration defaults to match Jekyll 1.0

--- src/config.coffee
+++ src/config.coffee
@@ -1,38 +1,42 @@
 yaml = require 'js-yaml'
 fs = require 'fs'

+# Use same defaults as Jekyll, per: http://jekyllrb.com/docs/configuration/
 DEFAULTS =
-  safe: false
-  auto: false
-  server: false
-  server_port: 4000
-
   source: '.'
   destination: './_site'
   plugins: '_plugins'
   layout: '_layouts'
-
-  baseurl: ''
-  url: 'http://localhost:4000'
+  include: ['.htaccess']
+  exclude: []
+  keep_files: ['.git', '.svn']
+  timezone: null

   future: true
+  show_drafts: null
+  limit_posts: 0
+  pygments: true
+
+  relative_permalinks: true
+
+  permalink: 'date'
+  paginate_path: 'page:num'
+
+  #markdown: 'maruku'
+  markdown_ext: ['markdown', 'mkd', 'mkdn', 'md']
+  #textile_ext: ['textile']
+
+  excerpt_separator: '\n\n'
+
+  safe: false
+  host: '0.0.0.0'
+  port: 4000
+  baseurl: '/'
+  url: 'http://localhost:4000'
   #lsi: false
-  #pygments: false
-  #markdown: 'maruku'
-  permalink: 'date'
-  include: ['.htaccess']
-  exclude: ['README.md']
+
+  # Enfield-specific
   pretty_urls: false
-  #paginate_path: 'page:num'
-
-  # markdown_ext: 'markdown,mkd,mkdn,md'
-  # textile_ext: 'textile'
-
-  # maruku options
-  # rdiscount options
-  # redcarpet options
-  # kramdown options
-  # redcloth options

 module.exports.getConfig = (configPath) ->
   # Read in config file


:memo: Mention previous value is in second argument object

--- src/config.coffee
+++ src/config.coffee
@@ -222,9 +222,10 @@
   #
   # keyPath - The {String} name of the key to observe
   # options - An optional {Object} containing the `callNow` key.
-  # callback - The {Function} that fires when the value of the key is
-  #            changed. It is given a single argument, `value`, which is
-  #            the new value of `keyPath`.
+  # callback - The {Function} to call when the value of the key changes.
+  #            The first argument will be the new value of the key and the
+  #            second argument will be an {Object} with a `previous` property
+  #            that is the prior value of the key.
   #
   # Returns an {Object} with the following keys:
   #  :off - A {Function} that unobserves the `keyPath` when called.


Add generated number as parameter for onSuccess

--- src/common.coffee
+++ src/common.coffee
@@ -252,7 +252,7 @@
           context.fillStyle = 'red'
           context.fill()

-      @settings.onSuccess.call()
+      @settings.onSuccess.call(this, @number)
     else
       #call error callback
       @settings.onError.call(this, "canvas context is null")


Fix typo

Fix typo

--- src/controller.coffee
+++ src/controller.coffee
@@ -206,7 +206,7 @@
       # ## Tracker Events
       # We allow binding to the tracker element.
       for eventName, elements of {
-          mousedown: [@iceElement, @paletteElement]
+          mousedown: [@iceElement, @paletteElement, @dragCover]
           mouseup: [window]
           mousemove: [window] } then do (eventName, elements) =>

@@ -2946,7 +2946,7 @@
   # appears to "float" over all other elements
   hook 'mousedown', -1, ->
     if @clickedBlock?
-      @dragCover.display = 'block'
+      @dragCover.style.display = 'block'
       @dragCanvas.style.zIndex = 300

   # On mouseup, throw the drag canvas away completely.


Clean whitespace.

Clean whitespace.

--- src/device.coffee
+++ src/device.coffee
@@ -45,7 +45,6 @@
         name == node.getElementsByTagName("Name")[0].textContent
       )[0]

-
   _getDeviceDataTypesXml: ->
     @_deviceDataTypes ||= @deviceInfoXml
       ?.getElementsByTagName("MassStorageMode")[0]


Fix to not listen first post for starting dialog

--- src/dialog.coffee
+++ src/dialog.coffee
@@ -7,6 +7,8 @@
 # Dialog.getDialog user, room
 # Dialog.addDialog dialog
 # Dialog.deleteDialog user, room
+
+md5 = require "MD5"

 class Dialog

@@ -28,8 +30,10 @@

   @listen: (robot) ->
     return false if Dialog.listened
-    robot.hear /.*/i, (msg) ->
+    robot.hear /./i, (msg) ->
+
       dialog = Dialog.getDialog msg.envelope.user.name, msg.envelope.room
+      return if dialog.startMsgMd5 is md5 "#{dialog.key}:#{msg.envelope.message.text}"
       dialog?.callback.call dialog, msg
     Dialog.listened = true

@@ -40,6 +44,7 @@
     @key = Dialog.getKey @user, @room
     @data = {}
     @listen callback
+    @startMsgMd5 = md5 "#{@key}:#{msg.envelope.message.text}"
     Dialog.addDialog @

   set: (key, value) ->


Refs #68; Better codo documentation for the option method.

--- src/core/chart.coffee
+++ src/core/chart.coffee
@@ -124,20 +124,27 @@
       else
         @_setOption prefix + key, value

-  # Gets and sets chart options after initialization. When using this as a setter, if an
-  # option actually changes value then an event option:NAME will be triggered
-  # where NAME is the name of the option.
-  #
-  # This method can be called in four different ways:
-  #

-  #
chart.option() - Returns a an object containing all of the options for the chart.

-  #
chart.option(key) - Returns a specific option. Use . to access nested options.

-  #
chart.option(key, value) - Sets an option and triggers the associated event.

-  #
chart.option(object) - Allows for the setting of multiple options at once.

-  #

-  #
-  # Warning: This method is currently experimental, and is not used by any core part of the library.
-  #   I'd feel much safer with real unit tests in place before proceeding to build around it.
+  # General accessor / mutator for chart options.
+  #
+  # @overload option()
+  #   Fetches chart options.
+  #   @return a copy of this chart's options.
+  #
+  # @overload option(name)
+  #   Fetches the value the option with the given name.
+  #   @param [String] name Name of the option to fetch. Can be hierarchical, e.g. 'margins.left'
+  #   @return The requested option if found, undefined otherwise.
+  #
+  # @overload option(name, value)
+  #   Sets an option and triggers the associated event.
+  #   @param [String] name Name of the option to fetch. Can be hierarchical, e.g. 'margins.top'
+  #   @param value Value to set for the option.
+  #   @event option:`name` Triggers an option event with the given key being set.
+  #
+  # @overload option(options)
+  #   Sets multiple options at once.
+  #   @param [Object] options Options to set for the chart.
+  #   @event option:* Triggers an option event for each key that was set.
   option: ->
     if arguments.length == 0
       @_getAllOptions()


When adjusting Editor.scrollView, use scrollTop instead of translate3d

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -278,9 +278,8 @@

     @updateVisibleLines() if @attached

-    transform = "translate3d(0px, #{-scrollTop}px, 0px)"
-    @visibleLines.css('-webkit-transform', transform)
-    @gutter.lineNumbers.css('-webkit-transform', transform)
+    @scrollView.scrollTop(scrollTop)
+    @gutter.scrollTop(scrollTop)
     if options?.adjustVerticalScrollbar ? true
       @verticalScrollbar.scrollTop(scrollTop)


Wait for tab to open in webdriverio `waitForPaneItemCount` command

--- spec/integration/helpers/start-atom.coffee
+++ spec/integration/helpers/start-atom.coffee
@@ -85,7 +85,8 @@

     .addCommand "waitForPaneItemCount", (count, timeout, cb) ->
       @waitUntil(->
-        @execute(-> atom.workspace?.getActivePane()?.getItems().length)
+        @waitForExist('.tab', 10000)
+        .execute(-> atom.workspace?.getActivePane()?.getItems().length)
           .then(({value}) -> value is count)
       , timeout)
         .then (result) ->


attempt to fix popover vertival positionning.

--- client/app/lib/popover_view.coffee
+++ client/app/lib/popover_view.coffee
@@ -133,12 +133,15 @@

         # Define everything that will be needed for positionning.
         popoverWidth = @$popover.innerWidth()
+        popoverHeight = @$popover.innerHeight()

         # Container is the screen, except the sidebar.
         containerOffset = @$tabCells.offset()
         containerHeight = @$tabCells.innerHeight()
         containerWidth = @$tabCells.innerWidth()

+        windowHeight = window.innerHeight
+
         # Target is the calendar's cell clicked.
         targetOffset = @target.offset()
         targetWidth = @target.width()
@@ -170,7 +173,12 @@
         oneRowHeight = (containerHeight / 6)

         # Cell is on the first two rows.
-        if targetOffset.top < oneRowHeight * 1
+        popoverOverflowWindow = (targetOffset.top + windowHeight) > containerHeight
+        if popoverOverflowWindow
+            topOffset = (windowHeight - popoverHeight)/2
+            top = "#{topOffset}px"
+            bottom = 'auto'
+        else if targetOffset.top < oneRowHeight * 1
             top = '5vh'
             bottom = 'auto'
         else if targetOffset.top < oneRowHeight * 2


Only re render the cursors/selections when in rendered range

--- src/editor.coffee
+++ src/editor.coffee
@@ -1184,8 +1184,15 @@
     for cursorView in @getCursorViews()
       if cursorView.needsRemoval
         cursorView.remove()
-      else if cursorView.needsUpdate
+      else if @shouldUpdateCursor(cursorView)
         cursorView.updateDisplay()
+
+  shouldUpdateCursor: (cursorView) ->
+    return false unless cursorView.needsUpdate
+
+    pos = cursorView.getScreenPosition()
+    should = pos.row >= @firstRenderedScreenRow and pos.row <= @lastRenderedScreenRow
+    should

   updateSelectionViews: ->
     if @newSelections.length > 0
@@ -1195,8 +1202,16 @@
     for selectionView in @getSelectionViews()
       if selectionView.needsRemoval
         selectionView.remove()
-      else
+      else if @shouldUpdateSelection(selectionView)
         selectionView.updateDisplay()
+
+  shouldUpdateSelection: (selectionView) ->
+    screenRange = selectionView.getScreenRange()
+    startRow = screenRange.start.row
+    endRow = screenRange.end.row
+    should = (startRow >= @firstRenderedScreenRow and startRow <= @lastRenderedScreenRow) or (endRow >= @firstRenderedScreenRow and endRow <= @lastRenderedScreenRow)
+    should
+

   syncCursorAnimations: ->
     for cursorView in @getCursorViews()


Allow Editor::batchUpdates calls to be nested

--- src/editor.coffee
+++ src/editor.coffee
@@ -144,6 +144,7 @@
   cursors: null
   selections: null
   suppressSelectionMerging: false
+  updateBatchDepth: 0

   @delegatesMethods 'suggestedIndentForBufferRow', 'autoIndentBufferRow', 'autoIndentBufferRows',
     'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
@@ -1842,9 +1843,11 @@
   abortTransaction: -> @buffer.abortTransaction()

   batchUpdates: (fn) ->
-    @emit 'batched-updates-started'
+    @emit 'batched-updates-started' if @updateBatchDepth is 0
+    @updateBatchDepth++
     result = fn()
-    @emit 'batched-updates-ended'
+    @updateBatchDepth--
+    @emit 'batched-updates-ended' if @updateBatchDepth is 0
     result

   inspect: ->


:memo: Document second argument of callback

--- src/editor.coffee
+++ src/editor.coffee
@@ -1067,9 +1067,11 @@
   # All the changes made inside the given {Function} can be reverted with a
   # single call to {::undo}.
   #
-  # fn - A {Function} that will be called with each {Selection}.
+  # fn - A {Function} that will be called once for each {Selection}. The first
+  #      argument will be a {Selection} and the second argument will be the
+  #      {Number} index of that selection.
   mutateSelectedText: (fn) ->
-    @transact => fn(selection,index) for selection,index in @getSelections()
+    @transact => fn(selection, index) for selection, index in @getSelections()

   replaceSelectedText: (options={}, fn) ->
     {selectWordIfEmpty} = options


Add a character width cache based on scopes.

This is slower than the position cache in the best case, but faster
in the worst and average case. With this, you can search for a space
in find and replace, and still scroll the buffer.

In editor.coffee, there are 10,500 spaces. To highlight all of them,
the previous cache method took 7 seconds, this takes 2 when the cache
is empty, and about 10ms when the entire file is cached.

--- src/editor.coffee
+++ src/editor.coffee
@@ -10,12 +10,15 @@
 _ = require './underscore-extensions'

 MEASURE_RANGE = document.createRange()
+TEXT_NODE_FILTER = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
+NO_SCOPE = ['no-scope']

 # Private: Represents the entire visual pane in Atom.
 #
 # The Editor manages the {EditSession}, which manages the file buffers.
 module.exports =
 class Editor extends View
+  @characterWidthCache: {}
   @configDefaults:
     fontSize: 20
     showInvisibles: false
@@ -96,7 +99,6 @@
     @pendingChanges = []
     @newCursors = []
     @newSelections = []
-    @pixelLeftCache = new WeakMap()

     if editSession?
       @edit(editSession)
@@ -969,6 +971,9 @@
   # fontSize - A {Number} indicating the font size in pixels.
   setFontSize: (fontSize) ->
     @css('font-size', "#{fontSize}px}")
+
+    @clearCharacterWidthCache()
+
     if @isOnDom()
       @redraw()
     else
@@ -985,6 +990,9 @@
   # fontFamily - A {String} identifying the CSS `font-family`,
   setFontFamily: (fontFamily='') ->
     @css('font-family', fontFamily)
+
+    @clearCharacterWidthCache()
+
     @redraw()

   # Gets the font family for the editor.
@@ -1366,7 +1374,6 @@
         currentLine = clearLine(currentLine)

   clearLine: (lineElement) =>
-    @pixelLeftCache.delete(lineElement)
     next = lineElement.nextSibling
     @renderedLines[0].removeChild(lineElement)
     next
@@ -1545,32 +1552,90 @@
     unless existingLineElement
       lineElement = @buildLineElementForScreenRow(actualRow)
       @renderedLines.append(lineElement)
-    left = @positionLeftForLineAndColumn(lineElement, column)
+    left = @positionLeftForLineAndColumn(lineElement, actualRow, column)
     unless existingLineElement
       @renderedLines[0].removeChild(lineElement)
     { top: row * @lineHeight, left }

-  positionLeftForLineAndColumn: (lineElement, column) ->
-    lineCache = @pixelLeftCache.get(lineElement)
-    @pixelLeftCache.set(lineElement, lineCache = {}) unless lineCache?
-
-    return lineCache[column] if lineCache[column]?
-
-    delta = 0
-    iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
+  positionLeftForLineAndColumn: (lineElement, screenRow, column) ->
+    return 0 if column == 0
+
+    bufferRow = @bufferRowsForScreenRows(screenRow)[0] ? screenRow
+    tokenizedLine = @activeEditSession.displayBuffer.tokenizedBuffer.tokenizedLines[bufferRow]
+
+    left = 0
+    index = 0
+    for token in tokenizedLine.tokens
+      for char in token.value
+        return left if index >= column
+
+        val = @checkCharacterWidthCache(token.scopes, char)
+        if val?
+          left += val
+        else
+          return @measureToColumn(lineElement, tokenizedLine, column)
+
+        index++
+    left
+
+  scopesForColumn: (tokenizedLine, column) ->
+    index = 0
+    for token in tokenizedLine.tokens
+      for char in token.value
+        return token.scopes if index == column
+        index++
+    null
+
+  measureToColumn: (lineElement, tokenizedLine, column) ->
+    left = oldLeft = index = 0
+    iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TEXT_NODE_FILTER)
+
+    returnLeft = null
+
     while textNode = iterator.nextNode()
-      nextDelta = delta + textNode.textContent.length
-      if nextDelta >= column
-        offset = column - delta
-        break
-      delta = nextDelta
-
-    MEASURE_RANGE.setEnd(textNode, offset)
-    MEASURE_RANGE.collapse()
-    left = MEASURE_RANGE.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
-
-    lineCache[column] = left
-    left
+      content = textNode.textContent
+
+      for char, i in content
+
+        # Dont return right away, finish caching the whole line
+        returnLeft = left if index == column
+        oldLeft = left
+
+        scopes = @scopesForColumn(tokenizedLine, index)
+        cachedVal = @checkCharacterWidthCache(scopes, char)
+
+        if cachedVal?
+          left = oldLeft + cachedVal
+        else
+          # i + 1 to measure to the end of the current character
+          MEASURE_RANGE.setEnd(textNode, i + 1)
+          MEASURE_RANGE.collapse()
+          left = MEASURE_RANGE.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
+
+          @setCharacterWidthCache(scopes, char, left - oldLeft) if scopes?
+
+        index++
+
+    returnLeft ? left
+
+  checkCharacterWidthCache: (scopes, char) ->
+    scopes ?= NO_SCOPE
+    obj = Editor.characterWidthCache
+    for scope in scopes
+      obj = obj[scope]
+      return null unless obj?
+    obj[char]
+
+  setCharacterWidthCache: (scopes, char, val) ->
+    scopes ?= NO_SCOPE
+    obj = Editor.characterWidthCache
+    for scope in scopes
+      obj[scope] ?= {}
+      obj = obj[scope]
+    obj[char] = val
+
+  clearCharacterWidthCache: ->
+    Editor.characterWidthCache = {}

   pixelOffsetForScreenPosition: (position) ->
     {top, left} = @pixelPositionForScreenPosition(position)


Restore suggestionName-var after refactoring

--- client/suggestion_completer.coffee
+++ client/suggestion_completer.coffee
@@ -96,17 +96,15 @@
   getResults: (q) ->
     @q = q
     params = {
-      "placesuggest": {
-        "text": q,
-        "completion": {
-          "field": @suggestionField,
-          "size": @nrOfResults
-        }
+      "text": q,
+      "completion": {
+        "field": @suggestionField,
+        "size": @nrOfResults
       }
     }
     $.post('http://'+@host + ':' +
-        @port +
-          '/'+@indexName+'/_suggest', JSON.stringify(params)
+        @port + '/'+@indexName+'/_suggest',
+      "{\"#{@suggestionName}\":"+JSON.stringify(params)+"}"
     )


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -3,7 +3,7 @@
     velocity: Point(0, 0)

   follow: (obj) ->
-    I.velocity = obj.I.position().subtract(self.position()).norm().scla
+    I.velocity = obj.I.position().subtract(self.position()).norm()

   self.bind "update", ->
     I.velocity = player.position().subtract(self.position()).norm().scale(5)


Consolidate some update code

Consolidate some update code

--- coffee/react-gui/components.coffee
+++ coffee/react-gui/components.coffee
@@ -64,6 +64,22 @@
     displayName: 'Eyedropper'
     imageName: 'eyedropper'
     getTool: -> new LC.EyeDropper()
+
+
+LC.React.Mixins =
+  UpdateOnDrawingChangeMixin:
+    componentDidMount: ->
+      @subscriber = => @setState @getState()
+      @props.lc.on 'drawingChange', @subscriber
+    componentWillUnmount: ->
+      @props.lc.removeEventListener('drawingChange', @subscriber)
+
+  UpdateOnToolChangeMixin:
+    componentDidMount: ->
+      @subscriber = => @setState @getState()
+      @props.lc.on 'toolChange', @subscriber
+    componentWillUnmount: ->
+      @props.lc.removeEventListener('toolChange', @subscriber)

 LC.React.Picker = React.createClass
@@ -115,12 +131,7 @@
   # we don't want the parent to have to worry about this, so it's in @state.
   getState: -> {isEnabled: @props.lc.canUndo()}
   getInitialState: -> @getState()
-
-  componentDidMount: ->
-    @subscriber = => @setState @getState()
-    @props.lc.on 'drawingChange', @subscriber
-  componentWillUnmount: ->
-    @props.lc.removeEventListener('drawingChange', @subscriber)
+  mixins: [LC.React.Mixins.UpdateOnDrawingChangeMixin]

   render: ->
     {div} = React.DOM
@@ -142,11 +153,7 @@
   displayName: 'RedoButton'
   getState: -> {isEnabled: @props.lc.canRedo()}
   getInitialState: -> @getState()
-  componentDidMount: ->
-    @subscriber = => @setState @getState()
-    @props.lc.on 'drawingChange', @subscriber
-  componentWillUnmount: ->
-    @props.lc.removeEventListener('drawingChange', @subscriber)
+  mixins: [LC.React.Mixins.UpdateOnDrawingChangeMixin]

   render: ->
     {div} = React.DOM
@@ -168,11 +175,7 @@
   displayName: 'ClearButton'
   getState: -> {isEnabled: @props.lc.canUndo()}
   getInitialState: -> @getState()
-  componentDidMount: ->
-    @subscriber = => @setState @getState()
-    @props.lc.on 'drawingChange', @subscriber
-  componentWillUnmount: ->
-    @props.lc.removeEventListener('drawingChange', @subscriber)
+  mixins: [LC.React.Mixins.UpdateOnDrawingChangeMixin]

   render: ->
     {div} = React.DOM
@@ -195,11 +198,7 @@
     tool: @props.lc.tool
   }
   getInitialState: -> @getState()
-  componentDidMount: ->
-    @subscriber = => @setState @getState()
-    @props.lc.on 'toolChange', @subscriber
-  componentWillUnmount: ->
-    @props.lc.removeEventListener('toolChange', @subscriber)
+  mixins: [LC.React.Mixins.UpdateOnToolChangeMixin]

   render: ->
     # style can be null; cast it as a string
@@ -216,6 +215,8 @@
     displayName: 'StrokeWidths'
     getState: -> {strokeWidth: @props.lc.tool?.strokeWidth}
     getInitialState: -> @getState()
+    mixins: [LC.React.Mixins.UpdateOnToolChangeMixin]
+
     render: ->
       {ul, li, svg, circle} = React.DOM
       strokeWidths = [1, 2, 5, 10, 20, 40]


Failing spec for ignoring numbers over 1000

--- coffee/spec/calculator.spec.coffee
+++ coffee/spec/calculator.spec.coffee
@@ -27,3 +27,6 @@
   it "should throw an exception with a negative number", ->
     expect(-> calculator.add '2,-3,4,-5').toThrow 'Negative numbers are not allowed: -3, -5'

+  it "should ignore numbers over 1000", ->
+    expect(calculator.add '1,1001,2').toEqual 3
+


Removing description from Wiki posts

Removing description from Wiki posts

--- src/gitlab.coffee
+++ src/gitlab.coffee
@@ -135,12 +135,7 @@
         else
           switch hook.object_kind
             when "wiki_page"
-              text = "Wiki - {bold(hook.object_attributes.title)}: #{hook.object_attributes.action} at #{hook.object_attributes.url}"
-
-              splitted = hook.object_attributes.content.split  "\r\n"
-              for i in [0...splitted.length]
-                splitted[i] = ">> " + splitted[i]
-              text += "\r\n" + splitted.join "\r\n"
+              text = "Wiki - #{bold(hook.object_attributes.title)}: #{hook.object_attributes.action} at #{hook.object_attributes.url}"
               robot.send user, text
             when "issue"
               unless hook.object_attributes.action == "update"


update speakers

update speakers

--- src/locals.coffee
+++ src/locals.coffee
@@ -96,7 +96,7 @@
   person('gmamaladze', t('George Mamaladze', 'გიორგი მამალაძე'), t('Architect at Siemens'), '/images/speakers/mamaladze.jpeg', [
     link('twitter', 'twitter', 'https://twitter.com/gmamaladze')
     link('google-plus', 'google+', 'https://google.com/+GeorgeMamaladze')
-  ], ""),
+  ], "Basics of Scalable Design"),
   person('shgiorgobiani', t('Shota Giorgobiani', 'შოთა გიორგობიანი'), t('Software Developer'), '/images/speakers/giorgobiani.jpg', [
     link('twitter', 'twitter', 'https://twitter.com/shgiorgobiani')
     link('facebook', 'facebook', 'https://www.facebook.com/shota.giorgobiani')
@@ -124,14 +124,7 @@
   person('guramkajaia', t('Guram Kajaia', 'გურამ ქაჯაია'), t('Software Developer at SHIFT'), '/images/speakers/kajaia.jpg', [
     link('linkedin', 'linkedin', 'https://www.linkedin.com/in/guramkajaia')
     link('twitter', 'twitter', 'https://twitter.com/guramkajaia')
-  ], "Life of a Software Generalist"),
-  person("safareli", t('Irakli Safareli', 'ირაკლი საფარიშვილი'), t("Front-End Developer"), "/images/team/safareli.jpg", [
-    link("twitter", "twitter", "https://twitter.com/safareli")
-    link("google-plus", "google+", "https://google.com/+IrakliSafareli")
-    link("youtube-play", "youtube", "https://www.youtube.com/isafareli")
-    link("linkedin", "linkedin", "https://www.linkedin.com/in/safareli")
-    link("github", "github", "https://github.com/safareli")
-  ], "")
+  ], "Life of a Software Generalist")
 ]

 host = sponsor("Kakha Bendukidze University Campus", "http://www.freeuni.edu.ge/", "/images/sponsors/campus.svg")


removing second lookup in requireStylesheet

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -72,10 +72,7 @@
   requireStylesheet: (path) ->
     if fullPath = require.resolve(path)
       window.applyStylesheet(fullPath, fs.read(fullPath))
-    for theme in atom.loadedThemes
-      for themePath, css of theme.stylesheets
-        matched = true if themePath.match(path)
-    unless fullPath || matched
+    unless fullPath
       throw new Error("Could not find a file at path '#{path}'")

   removeStylesheet: (path) ->


fix to_json impl

fix to_json impl

--- src/base_model.coffee
+++ src/base_model.coffee
@@ -23,22 +23,27 @@
     @attrs: ["name"]
     has_one: Person

-has_many should generate next methods:
-   add_model
-   and set a for _model [] as value
-
-
-has_one it's the same as attr but with
-   add_model under hood
-   set('model', model)
+has_one, has_many must take arrays with models
+
+belongs_to must take a array with objects as { :model => modelName[String] } with optional
+  argument a foreign key :
+
+  belongs_to { model => "person", :back => "name" }
+  generate next methods:
+    model_back ~> person_name
+
+  if a belongs_to not present must be a throw exception
+

 ###

 class BaseModel
+
   attrs: () ->
     @constructor.attrs || []

+  #normalize model name: UserModel => user_model
   normal_name: () ->
     @constructor.name.replace(/([A-Z])/g, '_$1').replace(/^_/,"").toLowerCase()

@@ -48,9 +53,13 @@
   has_one: () ->
     @constructor.has_one || []

+  belongs_to: () ->
+    @constructor.belongs_to || [] #array with object
+
   validators: () ->
     @constructor.validate

+  #
   normalize_attrs: () ->
     for a in @constructor.attrs
       do(a) ->
@@ -77,45 +86,79 @@

     for klass in @has_many()
       do(klass) ->
+        #FIXME : maybe normalize class name
         self["_#{klass}"] = []
         self.attributes.push("#{klass}")
-        self["add_#{klass}"] = (z) ->
+        self["add_#{klass}"] = (z) =>
           name = z.constructor.name
-          me = self.normal_name()
+          me   = self.normal_name()
+          m_name = self.constructor.name
+
           expected = klass.charAt(0).toUpperCase() + klass.slice(1)
           throw "Expected #{expected}, but given: #{name}" if name isnt expected
+          self.get("#{klass}").push(z)
+
           #feedback
-          if z.attributes.indexOf(me) > -1
-            if z.has_one().indexOf(me) > -1
-              m = z.get("#{me}")
-              z.set("#{me}", self) if !m
-            else if z.has_many().indexOf(me) > -1
-              z.get("#{me}").push(self)
-
-
-          self.get("#{klass}").push(z)
+          b_model = (for i in z.belongs_to()
+            do(i) ->
+              i if i['model'] == me
+          )[0]
+
+          if !b_model
+            throw "Model #{name} must contain '@belongs_to: [{model: #{me}, back: #{me}_id]'"
+
+          if !(back = b_model['back'])
+            throw "Define 'back' property for @belongs_to"
+
+          if self.attributes.indexOf(back) == -1
+            throw "Foreign key: '#{back}' not contain in a '#{m_name}' model"
+
+          key = "#{me}_#{back}"
+          if z.attributes.indexOf(key) == -1
+            throw "Define #{key} in @attrs for '#{expected}' model"
+
+          z.set("#{key}", self.get(back))
+          #TODO: logs
+

     for klass in @has_one()
       do(klass) ->
         self["_#{klass}"] = null
         self.attributes.push("#{klass}")
-        self["add_#{klass}"] = (z) ->
+        self["add_#{klass}"] = (z) =>
           name = z.constructor.name
-          me = self.normal_name()
+          me   = self.normal_name()
+          m_name = self.constructor.name
+
           expected = klass.charAt(0).toUpperCase() + klass.slice(1)
           throw "Expected #{expected}, but given: #{name}" if name isnt expected

-          t = self.get("#{klass}")
-          throw "Model #{klass} exist" if t
-
-          if z.attributes.indexOf(me) > -1
-            if z.has_one().indexOf(me) > -1
-              m = z.get("#{me}")
-              z.set("#{me}", self) if !m
-            else if z.has_many().indexOf(me) > -1
-              z.get("#{me}").push(self)
+          if self.get("#{klass}")
+            throw "Model #{expected} already exist for #{m_name}"

           self.set("#{klass}", z)
+
+          #feedback
+          b_model = (for i in z.belongs_to()
+            do(i) ->
+              i if i['model'] == me
+          )[0]
+
+          if !b_model
+            throw "Model #{name} must contain '@belongs_to: [{model: #{me}, back: #{me}_id]'"
+
+          if !(back = b_model['back'])
+            throw "Define 'back' property for @belongs_to"
+
+          if self.attributes.indexOf(back) == -1
+            throw "Foreign key: '#{back}' not contain in a '#{m_name}' model"
+
+          key = "#{me}_#{back}"
+          if z.attributes.indexOf(key) == -1
+            throw "Define #{key} in @attrs for '#{expected}' model"
+
+          z.set("#{key}", self.get(back))
+          #TODO: logs

     if Object.keys(obj).length != 0
@@ -176,7 +219,13 @@

     for attr in @attributes
       do(attr) ->
-        z["#{attr}"] = self.get("#{attr}")
+        value = self.get("#{attr}")
+        z["#{attr}"] = if self.has_many().indexOf(attr) > -1
+          for v in value then JSON.parse(v.to_json())
+        else if self.has_one().indexOf(attr) > -1
+          JSON.parse(value.to_json())
+        else
+          value

     if root
       o = {}
@@ -189,13 +238,13 @@

   to_html: () ->
     to = @constructor.to
-    self = @
-    result = for key, attrs of to
-      value = self.get(key)
+    for key, attrs of to
+      value = @get(key)
       tag = attrs["tag"] || "div"
-      delete attrs["tag"]
-      SiriusApplication.adapter.element(tag, value, attrs)
-    result
+      clone = {}
+      for key, v of attrs when key isnt "tag" then clone[key] = v
+
+      SiriusApplication.adapter.element(tag, value, clone)

   @from_json: (json = {}) ->
     m = new @
@@ -216,3 +265,8 @@
     @.from_json(SiriusApplication.adapter.form_to_json("form[name='#{form_name}'"))

+#if Object.prototype.toString.call(value) is '[object Array]'
+#  if value.length != 0
+#
+#  else
+#    z["#{attr}"] = []


rm console

rm console

--- components/layout/body/view.coffee
+++ components/layout/body/view.coffee
@@ -88,7 +88,6 @@
     mediator.trigger 'body:click', e

   maybeIntercept: (e)->
-    console.log 'maybeIntercept',  href?.indexOf(location.hostname), location.hostname
     href = $(e.currentTarget).attr("href")
     target = $(e.currentTarget).attr("target") || '_self'


Add some info for what the default language is used.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -79,7 +79,7 @@
       .command 'import'
       .description 'Import your products from CSV into your SPHERE.IO project.'
       .option '-c, --csv <file>', 'CSV file containing products to import'
-      .option '-l, --language [lang]', 'Default language to using during import', 'en'
+      .option '-l, --language [lang]', 'Default language to using during import (for slug generation, category linking etc.)', 'en'
       .option '--publish', 'When given, all changes will be published immediately'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --csv <file>'
       .action (opts) ->


Fix document scrolling bug

Fix document scrolling bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -247,7 +247,7 @@
         # ignore mouse clicks that are not the left-button
         if event.type isnt 'mousemove' and event.which isnt 1 then return

-        trackPoint = new @draw.Point(event.pageX, event.pageY)
+        trackPoint = new @draw.Point(event.clientX, event.clientY)

         # We keep a state object so that handlers
         # can know about each other.
@@ -1557,7 +1557,7 @@
       do (block) =>
         hoverDiv.addEventListener 'mousemove', (event) =>
           palettePoint = @trackerPointToPalette new @draw.Point(
-              event.pageX, event.pageY)
+              event.clientX, event.clientY)
           if @mainViewOrChildrenContains block, palettePoint
             unless block is @currentHighlightedPaletteBlock
               @clearPaletteHighlightCanvas()
@@ -3610,8 +3610,8 @@

   Editor::touchEventToPoint = (event, index) ->
     absolutePoint = new @draw.Point(
-      event.changedTouches[index].pageX,
-      event.changedTouches[index].pageY
+      event.changedTouches[index].clientX,
+      event.changedTouches[index].clientY
     )

     return absolutePoint


Fix bug with dragging segments

Fix bug with dragging segments

--- src/controller.coffee
+++ src/controller.coffee
@@ -1289,7 +1289,7 @@
           if @lastHighlight.type is 'document'
             @spliceIn @draggingBlock, @lastHighlight.start

-      futureCursorLocation = @toCrossDocumentLocation @draggingBlock
+      futureCursorLocation = @toCrossDocumentLocation @draggingBlock.start

       # Reparse the parent if we are
       # in a socket


Side-scrolling text input; also don't resize on ace change unless blocks

--- src/controller.coffee
+++ src/controller.coffee
@@ -1458,11 +1458,13 @@
       # anything non-local, only redraw locally.
       if deepEquals newp, oldp
         rect = new draw.NoRectangle()
+
         rect.unite treeView.bounds[line - 1] if line > 0
         rect.unite treeView.bounds[line]
         rect.unite treeView.bounds[line + 1] if line + 1 < treeView.bounds.length

-        rect.width = @mainCanvas.width
+        rect.width = Math.max rect.width, @mainCanvas.width
+

         @redrawMain
           boundingRectangle: rect
@@ -1473,9 +1475,9 @@
     else
       @redrawMain()

-    @redrawTextHighlights()
-
-  Editor::redrawTextHighlights = ->
+    @redrawTextHighlights true
+
+  Editor::redrawTextHighlights = (scrollIntoView = false) ->
     textFocusView = @view.getViewNodeFor @textFocus

     # Determine the coordinate positions
@@ -1522,6 +1524,9 @@
           endPosition - textFocusView.bounds[endRow].x,
           @view.opts.textHeight

+    #if scrollIntoView and endPosition > @scrollOffsets.main.x + @mainCanvas.width
+    #  null
+    #@mainScroller.scrollLeft = endPosition - @mainCanvas.width + @view.opts.padding

   # Convenince function for setting the text input
   Editor::setTextInputFocus = (focus, selectionStart = 0, selectionEnd = 0) ->
@@ -2109,6 +2114,8 @@
     else if axis - @scrollOffsets.main.y > @mainCanvas.height
       @mainScroller.scrollTop = axis - @mainCanvas.height

+    @mainScroller.scrollLeft = 0
+
   # Pressing the up-arrow moves the cursor up.
   hook 'key.up', 0, ->
     @clearLassoSelection()
@@ -2441,6 +2448,11 @@
   # ANIMATION AND ACE EDITOR SUPPORT
   # ================================

+  Editor::copyAceEditor = ->
+    @setFontSize_raw @aceEditor.getFontSize()
+    @gutter.style.width = @aceEditor.renderer.$gutterLayer.gutterWidth + 'px'
+    @resize()
+
   hook 'populate', 0, ->
     @aceElement = document.createElement 'div'
     @aceElement.className = 'ice-ace'
@@ -2455,10 +2467,8 @@
     @aceEditor.getSession().setTabSize 2

     @aceEditor.on 'change', =>
-      unless @suppressChangeEvent
-        @setFontSize_raw @aceEditor.getFontSize()
-        @gutter.style.width = @aceEditor.renderer.$gutterLayer.gutterWidth + 'px'
-        @resize()
+      if @currentlyUsingBlogs and not @suppressChangeEvent
+        @copyAceEditor()

     @currentlyUsingBlocks = true
     @currentlyAnimating = false
@@ -2710,7 +2720,10 @@

   Editor::performFreezeAnimation = (fadeTime = 500, translateTime = 500, cb = ->)->
     if not @currentlyUsingBlocks and not @currentlyAnimating
+      @copyAceEditor()
+
       @fireEvent 'statechange', [true]
+
       setValueResult = @setValue_raw @aceEditor.getValue()

       unless setValueResult.success


Fix ace font size bug

Fix ace font size bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -2615,7 +2615,7 @@
           @gutter.offsetWidth + 5 # TODO see above
     }

-    @mainCtx.font = @aceEditor.getFontSize() + ' ' + @fontFamily
+    @mainCtx.font = @aceFontSize() + ' ' + @fontFamily

     until head is @tree.end
       switch head.type
@@ -2729,7 +2729,7 @@
           setTimeout (=>
             div.style.left = (textElement.bounds[0].x - @scrollOffsets.main.x + translationVectors[i].x) + 'px'
             div.style.top = (textElement.bounds[0].y - @scrollOffsets.main.y + translationVectors[i].y) + 'px'
-            div.style.fontSize = @aceEditor.getFontSize()
+            div.style.fontSize = @aceFontSize()
           ), fadeTime

       top = Math.max @aceEditor.getFirstVisibleRow(), 0
@@ -2762,7 +2762,7 @@
           setTimeout (=>
             div.style.left = '0px'
             div.style.top = (line * lineHeight - aceScrollTop + @scrollOffsets.main.y) + 'px'
-            div.style.fontSize = @aceEditor.getFontSize()
+            div.style.fontSize = @aceFontSize()
           ), fadeTime

       @lineNumberWrapper.style.display = 'none'
@@ -2816,6 +2816,9 @@

       return success: true

+  Editor::aceFontSize = ->
+    parseFloat(@aceEditor.getFontSize()) + 'px'
+
   Editor::performFreezeAnimation = (fadeTime = 500, translateTime = 500, cb = ->)->
     if not @currentlyUsingBlocks and not @currentlyAnimating
       @copyAceEditor()
@@ -2870,7 +2873,7 @@

           div.innerText = textElement.model.value

-          div.style.font = @aceEditor.getFontSize() + ' ' + @fontFamily
+          div.style.font = @aceFontSize() + ' ' + @fontFamily
           div.style.position = 'absolute'

           div.style.left = "#{textElement.bounds[0].x - @scrollOffsets.main.x + translationVectors[i].x}px"
@@ -2903,7 +2906,7 @@

           div.innerText = line + 1

-          div.style.font = @aceEditor.getFontSize() + ' ' + @fontFamily
+          div.style.font = @aceFontSize() + ' ' + @fontFamily
           div.style.width = "#{@aceEditor.renderer.$gutter.offsetWidth}px"

           div.style.left = 0


Fix copy/paste bug for real

Fix copy/paste bug for real

--- src/controller.coffee
+++ src/controller.coffee
@@ -286,6 +286,7 @@
     # ## Document initialization
     # We start of with an empty document
     @tree = new model.Segment()
+    @tree.isRoot = true
     @tree.start.insert @cursor

     @resizeBlockMode()


added 2 missing tests, replaced `should(res).be.ok()` with `true.should.eql res` for better readability

--- _src/test/mocha_test.coffee
+++ _src/test/mocha_test.coffee
@@ -137,7 +137,7 @@
 			localCache.set state.key, state.value2, 0, (err, res) ->
 				state.n++
 				should.not.exist err
-				should(res).be.ok()
+				true.should.eql res

 				# check if update worked
 				localCache.get state.key, (err, res) ->
@@ -181,7 +181,7 @@
 			localCache.set "zero", 0, 0, (err, res) ->
 				state.n++
 				should.not.exist err
-				should(res).be.ok()
+				true.should.eql res
 				done()
 				return
 			return
@@ -242,7 +242,7 @@

 		it "set key", () ->
 			res = localCache.set state.key, state.value, 0
-			should(res).be.ok()
+			true.should.eql res
 			1.should.eql localCache.getStats().keys - state.start.keys
 			return

@@ -263,7 +263,7 @@

 		it "update key (and get it to check if the update worked)", () ->
 			res = localCache.set state.key, state.value2, 0
-			should(res).be.ok()
+			true.should.eql res

 			# check if the update worked
 			res = localCache.get state.key
@@ -290,7 +290,7 @@
 			# set the keys
 			keys.forEach (key) ->
 				res = localCache.set key, state.value3
-				should(res).be.ok()
+				true.should.eql res
 				return
 			# check the keys
 			keys.forEach (key) ->
@@ -321,7 +321,7 @@

 		it "set a key to 0", () ->
 			res = localCache.set "zero", 0
-			should(res).be.ok()
+			true.should.eql res
 			return

 		it "get previously set key", () ->
@@ -331,7 +331,7 @@

 		it "set a key to an object clone", () ->
 			res = localCache.set "clone", state.obj
-			should(res).be.ok()
+			true.should.eql res
 			return

 		it "get cloned object", () ->
@@ -613,7 +613,7 @@
 		return

-	describe.only "ttl", () ->
+	describe "ttl", () ->
 		before () ->
 			state =
 				n: 0
@@ -630,8 +630,7 @@
 		it "set a key with ttl", () ->
 			localCache.set state.key1, state.val, 0.5, (err, res) ->
 				should.not.exist err
-				should(res).be.ok()
-
+				true.should.eql res
 				ts = localCache.getTtl state.key1
 				if state.now < ts < state.now + 300
 					throw new Error "Invalid timestamp"
@@ -674,7 +673,7 @@
 		it "set another key with ttl", () ->
 			localCache.set state.key2, state.val, 0.3, (err, res) ->
 				should.not.exist err
-				should(res).be.ok()
+				true.should.eql res
 				return
 			return

@@ -740,7 +739,7 @@

 				localCache.set innerState.key, innerState.val, 0.5, (err, res) ->
 					should.not.exist err
-					should(res).be.ok()
+					true.should.eql res
 					(innerState.startKeys + 1).should.eql localCache.getStats().keys
 					# event handler should have been fired
 					0.should.eql localCache.listenerCount "set"
@@ -776,7 +775,7 @@
 			it "set a third key with ttl", () ->
 				localCache.set state.key3, state.val, 100, (err, res) ->
 					should.not.exist null
-					should(res).be.ok()
+					true.should.eql res
 					return
 				return

@@ -818,8 +817,76 @@
 				, 500)
 				return

-			it "set key4 with ttl = 0"
-			it "set key5 with default ttl"
+			it "set a key with ttl = 100s (default: infinite), reset it's ttl to default and check if it still exists", () ->
+				localCache.set state.key4, state.val, 100, (err, res) ->
+					should.not.exist err
+					true.should.eql res
+
+					# check immediately
+					localCache.get state.key4, (err, res) ->
+						should.not.exist err
+						state.val.should.eql res
+
+						# set ttl to false key
+						localCache.ttl "#{state.key4}false", (err, wasSet) ->
+							should.not.exist err
+							false.should.eql wasSet
+							return
+
+						# set default ttl (0) to the right key
+						localCache.ttl state.key4, (err, wasSet) ->
+							should.not.exist err
+							true.should.eql wasSet
+
+							# and check if it still exists
+							res = localCache.get state.key4
+							state.val.should.eql res
+							return
+						return
+					return
+				return
+
+			it "set a key with ttl = 100s (default: 0.3s), reset it's ttl to default, check if it still exists, and wait for its timeout", (done) ->
+				localCacheTTL.set state.key5, state.val, 100, (err, res) ->
+					should.not.exist err
+					true.should.eql res
+
+					# check immediately
+					localCacheTTL.get state.key5, (err, res) ->
+						should.not.exist err
+						state.val.should.eql res
+
+						# set ttl to false key
+						localCacheTTL.ttl "#{state.key5}false", (err, wasSet) ->
+							should.not.exist err
+							false.should.eql wasSet
+							return
+
+						# set default ttl (0.3) to right key
+						localCacheTTL.ttl state.key5, (err, wasSet) ->
+							should.not.exist err
+							true.should.eql wasSet
+							return
+
+						# and check if it still exists
+						localCacheTTL.get state.key5, (err, res) ->
+							should.not.exist err
+							state.val.should.eql res
+
+							setTimeout(() ->
+								res = localCacheTTL.get state.key5
+								should.not.exist res
+
+								localCacheTTL._checkData false
+
+								# deep dirty check if key was deleted
+								should(localCacheTTL.data[state.key5]).be.undefined()
+								done()
+								return
+							, 350)
+							return
+						return
+				return

 			return


Work with spies a little more gracefully

--- src/config.coffee
+++ src/config.coffee
@@ -429,6 +429,7 @@
     , 100

     debouncedSave = _.debounce =>
+      @savePending = false
       @save()
     , 100
     @requestSave = =>
@@ -922,7 +923,6 @@
     @notificationManager?.addError(errorMessage, {detail, dismissable: true})

   save: ->
-    @savePending = false
     return if @shouldNotAccessFileSystem()

     allSettings = {'*': @settings}


Update config.coffee

Update config.coffee

--- src/config.coffee
+++ src/config.coffee
@@ -63,4 +63,4 @@
     return '2012' if fs.existsSync(vs2012Path)

     vs2013Path = path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio 12.0", "Common7", "IDE")
-    return '2012' if fs.existsSync(vs2013Path)
+    return '2013' if fs.existsSync(vs2013Path)


Fix viewer

Fix viewer

--- assets/js/src/records/viewer.coffee
+++ assets/js/src/records/viewer.coffee
@@ -3,6 +3,9 @@
 panel = (panel_html, css_class) ->
   css_class = if css_class then " #{css_class}" else ''
   "
#{panel_html}
"
+
+panelBody = (panel_body_html) ->
+  "
#{panel_body_html}
"

 class Viewer
   constructor: (@form, @record) ->


removed wrong error handling code

removed wrong error handling code

if we really need this, please add an error callback to
showInfo, and give the user some actual feedback, instead
of giving up after 17 seconds.

--- app/js/views/balloon.coffee
+++ app/js/views/balloon.coffee
@@ -1,5 +1,3 @@
-stop_loading_timeout = 17000
-
 class Factlink.Balloon
   constructor: (fact) ->
     @_fact = fact
@@ -34,11 +32,9 @@

   startLoading: ->
     @_loading = true
-    @_loadingTimeoutID = setTimeout (=> @stopLoading()), stop_loading_timeout
     @$el.addClass "fl-loading"

   stopLoading: ->
-    window.clearTimeout @_loadingTimeoutID
     @_loading = false
     @hide => @$el.removeClass "fl-loading"


don't let users overlap marks on move events

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -138,9 +138,14 @@
     @save(markLeftX, markLeftX+width)

   move: (e) ->
-    leftXPos = (@toCanvasXPoint(e) - @pointerOffset)
+    markWidth = parseInt(@element.style.width, 10)
+    leftXPos = Math.max (@toCanvasXPoint(e) - @pointerOffset), (@closestXBelow || 0)
+    leftXPos = Math.min leftXPos, ((@closestXAbove || @canvas.width) - markWidth)
+
+    markRightX = leftXPos + markWidth
+
     @element.style.left = leftXPos + "px"
-    @save(leftXPos, leftXPos+parseInt(@element.style.width, 10))
+    @save(leftXPos, markRightX)

   save: (markLeftX, markRightX) ->
     #canvas coords


remove log and constructor var from last commit

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -14,8 +14,6 @@
     @arrayLength = Math.min @data.x.length, @data.y.length

     @mirrorVertically()
-
-    @scale = 1

   enableMarking: ->
     @marks = new Marks
@@ -50,7 +48,6 @@
         mark.save(scaledMin, scaledMax)

     @scale = (@largestX - @smallestX) / (@xMax - @xMin)
-    console.log "SCALE", @scale

   clearCanvas: -> @ctx.clearRect(0,0,@canvas.width, @canvas.height)