Update memes available

Update memes available

- Referenced https://github.com/jacebrowning/memegen/tree/master/data/templates
  and http://memegen.link/templates/ for updates

--- src/memes.coffee
+++ src/memes.coffee
@@ -18,37 +18,58 @@
 Util = require 'util'

 memes =
+  "aag": "Ancient Aliens Guy",
   "afraid": "'Afraid to Ask'",
+  "ants": "Do You Want Ants? (archer)",
+  "awesome": "Socially Awesome Penguin",
+  "awkward": "Socially Awkward Penguin",
+  "badchoice": "Milk Was a Bad Choice (anchorman)",
+  "biw": "BABY Insanity Wolf",
   "blb": "Bad Luck Brian",
+  "both": "Why Not Both?",
+  "bs": "This is Bull, Shark",
   "buzz": "X, X Everywhere",
+  "chosen": "You Were the Chosen One!",
+  "crazypills": "I Feel Like I'm Taking Crazy Pills",
+  "doge": "Doge",
+  "elf": "You Sit on a Throne of Lies",
+  "ermg": "Ermahgerd",
+  "fa": "Forever Alone",
+  "fetch": "Stop Trying to Make Fetch Happen",
+  "fry": "Futurama Fry",
+  "fwp": "First World Problems",
+  "ggg": "Goog Guy Greg",
+  "hipster": "Hipster Barista",
+  "icanhas": "I can Has Cheezburger",
+  "interesting": "The Most Interesting Man in the World",
+  "iw": "Insanity Wolf",
+  "jetpack": "Jetpack Guy / Nothing to Do Here",
+  "jw": "Probably Not a Good Idea",
+  "keanu": "Conspiracy Keanu",
+  "kermit": "But That's None of My Business",
+  "live": "Do It Live!",
+  "ll": "Laughing Lizard",
+  "mordor": "One Does Not Simply Walk into Mordor",
+  "morpheus": "Matrix Morpheus",
+  "officespace": "That Would be Great",
+  "oprah": "Oprah You Get a Car",
+  "philosoraptor": "Philosoraptor",
+  "regret": "I Immediately Regret This Decision (anchorman)",
+  "remembers": "Pepperidge Farm Remembers",
+  "sarcasticbear": "Sarcastic Bear",
+  "sb": "Scumbag Brian",
+  "sf": "Sealed Fate",
+  "ski": "Super Cool Ski Instructor (You're gonna have a bad time)",
+  "sohappy": "I Would Be So Happy",
+  "sohot": "So Hot Right Now",
+  "ss": "Scumbag Steve",
+  "success": "Success Kid",
+  "tenguy": "[10] Guy",
+  "toohigh": "The Rent is Too Damn High",
   "wonka": "Condescending Wonka",
-  "keanu": "Conspriracy Keanu",
-  "live": "Do It Live!",
-  "doge": "Doge",
-  "ermg": "Ermahgerd",
-  "fwp": "First World Problems",
-  "fa": "Forever Alone",
-  "fry": "Futurama Fry",
-  "ggg": "Goog Guy Greg",
-  "icanhas": "I can Has Cheezburger",
-  "iw": "Insanity Wolf",
-  "ll": "Laughing Lizard",
-  "morpheus": "Matrix Morpheus",
-  "mordor": "One Does Not Simply Walk into Mordor",
-  "oprah": "Oprah You Get a Car",
-  "remember": "Pepperidge Farm Remembers",
-  "philosoraptor": "Philosoraptor",
-  "sb": "Scumbag Brian",
-  "ss": "Scumbag Steve",
-  "fetch": "Stop Trying to Make Fetch Happen",
-  "success": "Success Kid",
-  "officespace": "That Would be Great",
-  "interesting": "The Most Interesting Man in the World",
-  "toohigh": "The Rent is Too Damn High",
   "xy": "X all the Y",
-  "elf": "You Sit on a Throne of Lies",
-  "chosen": "You Were the Chosen One!",
-  "10guy": "[10] Guy"
+  "yallgot": "Y'All Got Anymore of",
+  "yuno": "Y U NO?"

 checkCode = (meme, memes) ->
   return meme of memes


comment beautification

comment beautification

--- src/monad.coffee
+++ src/monad.coffee
@@ -30,9 +30,7 @@

 # Haskell-style List with indeterminate binding!
 List = (items) ->
-  # Keep us safe from naughty users mutating the underlying Array.
-  items = if items then items.slice() else []
-
+  items = if items then items.slice() else [] # Nasty Mutability, begone!
   Trait.create Object.prototype, (Trait.override (Trait {
   unit : List
   bind : (fn) ->


Text nodes

Text nodes

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -41,6 +41,15 @@
     context = { render, model, controller }
     element = helperFunction.apply(context, ast.arguments)
     new Node(ast, element, model, controller)
+
+  @text: (ast, model, controller) ->
+    getValue = ->
+      value = format(model, ast.value, ast.bound)
+      value = "0" if value is 0
+      value or ""
+    textNode = Serenade.document.createTextNode(getValue())
+    model.bind?("change:#{ast.value}", -> textNode.nodeValue = getValue()) if ast.bound
+    new Node(ast, textNode, model, controller)

   constructor: (@ast, @element, @model, @controller) ->

@@ -135,30 +144,6 @@

   get: -> format(@model, @ast.value, @ast.bound)

-class TextNode
-  constructor: (@ast, @model, @controller) ->
-    @textNode = Serenade.document.createTextNode(@get())
-    if @ast.bound
-      model.bind? "change:#{@ast.value}", =>
-        @textNode.nodeValue = @get()
-
-  append: (inside) ->
-    inside.appendChild(@textNode)
-
-  insertAfter: (after) ->
-    after.parentNode.insertBefore(@textNode, after.nextSibling)
-
-  remove: ->
-    @textNode.parentNode.removeChild(@textNode)
-
-  lastElement: ->
-    @textNode
-
-  get: ->
-    value = format(@model, @ast.value, @ast.bound)
-    value = "0" if value is 0
-    value or ""
-
 class If
   constructor: (@ast, @model, @controller) ->
     @anchor = Serenade.document.createTextNode('')
@@ -297,7 +282,7 @@
   compile: (ast, model, controller) ->
     switch ast.type
       when 'element' then Node.element(ast, model, controller)
-      when 'text' then new TextNode(ast, model, controller)
+      when 'text' then new Node.text(ast, model, controller)
       when 'instruction'
         switch ast.command
           when "view" then new Node.view(ast, model, controller)


[FIX] Try/catch for JSON serialization from chainAPI

--- src/chainAPI.coffee
+++ src/chainAPI.coffee
@@ -123,15 +123,18 @@
     options.method = 'GET'
     req = http.request options, (res) ->
       res.on 'data', (d) =>
-        obj = JSON.parse(d)
-        done = false
-        for element in obj._links.items
-          if element.title == SensorName
-            done = true
-            id = element.href.substr 15+element.href.search /scalar_sensors/
-            f(0, SensorName, id)
-        if done == false
-          f(-1, SensorName)
+        try
+          obj = JSON.parse(d)
+          done = false
+          for element in obj._links.items
+            if element.title == SensorName
+              done = true
+              id = element.href.substr 15+element.href.search /scalar_sensors/
+              f(0, SensorName, id)
+          if done == false
+            f(-1, SensorName)
+        catch err
+          console.error "[chainAPI] getSensorID " + err
     req.end();
     req.on 'error', (e) => f(-2, SensorName)

@@ -154,15 +157,18 @@
     options.method = 'GET'
     req = http.request options, (res) ->
       res.on 'data', (d) =>
-        obj = JSON.parse(d)
-        done = false
-        for element in obj._links.items
-          if element.title == DeviceName
-            done = true
-            id = element.href.substr 8+element.href.search /devices/
-            f(0, DeviceName, id)
-        if done == false
-          f(-1, DeviceName)
+        try
+          obj = JSON.parse(d)
+          done = false
+          for element in obj._links.items
+            if element.title == DeviceName
+              done = true
+              id = element.href.substr 8+element.href.search /devices/
+              f(0, DeviceName, id)
+          if done == false
+            f(-1, DeviceName)
+        catch err
+          console.error "[chainAPI] getSensorID " + err
     req.end();
     req.on 'error', (e) => f(-2, DeviceName)


Add TODO

Add TODO

--- spec/fixtures/sandbox.coffee
+++ spec/fixtures/sandbox.coffee
@@ -79,8 +79,14 @@
 #TODO
 for { a } in arr
   a = 100
+for a, { b } of obj
+  a = 100
+  b = 100
 for [ a ] in arr
   a = 100
+for a, [ b ] of obj
+  a = 100
+  b = 100

 obj =
@@ -114,3 +120,5 @@

 func = ({ a }) ->
   a 1
+func = ([ a ]) ->
+  a 1


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -1,5 +1,5 @@
 ###*
-The TimedEvents module allows arbitrary code to be executed at set intervals. Ga
+The TimedEvents module allows arbitrary code to be executed at set intervals. <GameObject>

 player = GameObject()


Print version on ‘brave-mouse -v’

Print version on ‘brave-mouse -v’

--- src/cli/argv.coffee
+++ src/cli/argv.coffee
@@ -1,8 +1,17 @@
 parser = require 'nomnom'

 braveMouse = require '../brave-mouse'
+braveMousePackageJson = require '../../package.json'

 module.exports = (argv) ->
-	parser
+	opts = parser
 	.script 'brave-mouse'
+	.options
+		version:
+			abbr: 'v'
+			flag: true
+			help: 'Print brave-mouse’s version'
 	.parse argv.slice 2
+
+	if opts.version
+		return console.log 'v' + braveMousePackageJson.version


re-run tests on changes in site.js

re-run tests on changes in site.js

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -4,6 +4,7 @@
     frameworks: [ 'jasmine' ]
     files: [
       'public/vendor/zepto-1.1.4.js'
+      'public/site.js'
       'spec/**/*.coffee'
     ]
     exclude: []


fix(atom-window): The worker window should also auto-reload on crash

Neverclose applies to both the main window and the worker window

T1640

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -170,7 +170,7 @@
     @browserWindow.webContents.on 'crashed', =>
       global.application.exit(100) if @exitWhenDone

-      if @mainWindow
+      if @neverClose
         @browserWindow.restart()
       else
         dialog = require 'dialog'


Whenever a kdapp is deleted refreshApps

Whenever a kdapp is deleted refreshApps

--- client/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
+++ client/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
@@ -176,6 +176,8 @@
             separator             : yes
           "Publish to App Catalog":
             action                : "publish"
+      fileData.on "fs.delete.finished", =>
+        @getSingleton("kodingAppsController").refreshApps()

     return items


Add liveReload along other steps.

Add liveReload along other steps.

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -172,10 +172,11 @@
         steps.push minifierTransform minifier for minifier in minifiers

       steps.push gulp.dest destination
-      stream = stream.pipe step for step in steps

       if @configuration.liveReload.enabled
-        stream.pipe filters.livereload @glp.liveReload
+        steps.push filters.livereload @glp.liveReload
+
+      stream = stream.pipe step for step in steps

       return stream


refactoring on sort method and added doc

--- client/landing/app/Applications/Feeder.kdapplication/FeedController.coffee
+++ client/landing/app/Applications/Feeder.kdapplication/FeedController.coffee
@@ -141,24 +141,18 @@
       partial  : noItemFoundText or @getOptions().noItemFoundText or "There is no item found."
     @noItemFound.hide()

-  sortByKey: (arr, key) ->
-    key = key.split(".")
-    len = key.length
-    arr.sort (a, b) ->
-      i = 0
-      while i < len
-        a = a[key[i]]
-        b = b[key[i]]
-        i++
-      if a < b
-        1
-      else if a > b
-        -1
-      else
-        0
+  # this is a temporary solution for a bug that
+  # bongo returns correct result set in a wrong order
+  sortByKey : (array, key) ->
+    array.sort (first, second) ->
+      firstVar  = JsPath.getAt first,  key
+      secondVar = JsPath.getAt second, key
+      #quick sort-ware
+      if (firstVar < secondVar) then return 1
+      else if (firstVar > secondVar) then return -1
+      else return 0

-    arr
-
+
   loadFeed:(filter = @selection)=>

     options    = @getFeedOptions()
@@ -175,7 +169,8 @@
           unless err
             if items.length is 0 and listController.getItemCount() is 0
               @noItemFound.show()
-            items = @sortByKey(items, filter.activeSort)
+            filter.activeSort = 'meta.modifiedAt' if not filter.activeSort
+            items = @sortByKey(items, filter.activeSort) #if filter.activeSort
             listController.instantiateListItems items
             @emitCountChanged listController.itemsOrdered.length, filter.name
             if items.length is options.limit and listController.scrollView.getScrollHeight() <= listController.scrollView.getHeight()


Add an optional-title when search happened

--- client/landing/app/Applications/Members.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppController.coffee
@@ -28,6 +28,7 @@
       filter                :
         everything          :
           title             : "All Members "
+          optional_title    : if @_searchValue then "Search results for #{@_searchValue} in all members" else null
           dataSource        : (selector, options, callback)=>
             if @_searchValue
               bongo.api.JAccount.byRelevance @_searchValue, options, callback


change property `$objects` position

change property `$objects` position

change from class property to instance property

--- src/core/App.coffee
+++ src/core/App.coffee
@@ -21,9 +21,9 @@

 class Bu.App

-	$objects: {}
+	constructor: (@options = {}) ->
+		@$objects = {}

-	constructor: (@options = {}) ->
 		for k in ["renderer", "camera", "data", "objects", "hierarchy", "methods", "events"]
 			@options[k] or= {}


ensure robot name in master help directory

--- src/tf.coffee
+++ src/tf.coffee
@@ -8,7 +8,7 @@
 #   None
 #
 # Commands:
-#   tf help - list commands
+#   hubot tf help - list commands
 #
 # Author:
 #   gsfjohnson


Added back missing menu items removed in last commit

--- src/code/views/app-view.coffee
+++ src/code/views/app-view.coffee
@@ -66,7 +66,7 @@
   render: ->
     if @props.usingIframe
       (div {className: 'app'},
-        (MenuBar {filename: @state.filename, fileStatus: @state.fileStatus, options: @state.menuOptions})
+        (MenuBar {filename: @state.filename, fileStatus: @state.fileStatus, items: @state.menuItems, options: @state.menuOptions})
         (InnerApp {app: @props.app})
         if @state.providerDialog
           (ProviderTabbedDialog {client: @props.client, dialog: @state.providerDialog, close: @closeProviderDialog})


Updated flowdock connector to Adapter class

--- src/adapters/flowdock.coffee
+++ src/adapters/flowdock.coffee
@@ -1,7 +1,7 @@
 Robot        = require "../robot"
 flowdock     = require "flowdock"

-class Flowdock extends Robot
+class Flowdock extends Robot.Adapter
   send: (user, strings...) ->
     strings.forEach (str) =>
       @bot.chatMessage(user.flow.subdomain, user.flow.name, str)


Chrome extension: add tests

Chrome extension: add tests

--- test/chromeExtension/storyReducer.coffee
+++ test/chromeExtension/storyReducer.coffee
@@ -7,18 +7,22 @@
 #-------------------------------------------------
 # ## Helpers
 #-------------------------------------------------
-_recordsReceived = (records) -> {type: 'RECORDS_RECEIVED', records}
+_recordsReceived = (records, fPastRecords = false) ->
+ return {type: 'RECORDS_RECEIVED', records, fPastRecords}
+
 _forget = (action) -> timm.merge action,
   type: 'FORGET'
   forgetHysteresis: 0.25
   pathStr: "records/0"

+_seqId = 0
 _log = (record) -> timm.addDefaults record,
   fStory: false
   fServer: false
   src: 'main'
   level: 30
   storyId: '*'
+  id: "llll#{_seqId++}"

 _actionRecord = (record) -> timm.addDefaults record,
   fStory: true
@@ -204,3 +208,28 @@
         expect(state2).to.equal state
         state2 = reducer state, {type: 'TOGGLE_ATTACHMENT', pathStr: 'unknown/path', recordId: 'xxx'}
         expect(state2).to.equal state
+
+  describe 'adding a new story to a closed story', ->
+
+    _storyId = null
+    beforeEach ->
+      openStoryRecord = _openStory {title: 'story0'}
+      _storyId = openStoryRecord.storyId
+      state = reducer state, _recordsReceived [
+        openStoryRecord
+        _closeStory _storyId
+      ]
+
+    it 'with fPastRecords: false (should NOT include it in the closed story)', ->
+      state = reducer state, _recordsReceived([_openStory {title: "child", parents: [_storyId]}], false)
+      expect(state.mainStory.records[0].records[0].records.length).to.equal 2 # CREATED and CLOSED
+      expect(state.mainStory.records[0].records[1].title).to.equal 'child'
+
+    it 'with fPastRecords: true (should include it in the closed story)', ->
+      state = reducer state, _recordsReceived([_openStory {title: "child", parents: [_storyId]}], true)
+      expect(state.mainStory.records[0].records[0].records.length).to.equal 3
+      expect(state.mainStory.records[0].records[0].records[2].title).to.equal 'child'
+
+  it 'adding a root story should be ignored', ->
+    state2 = reducer state, _recordsReceived [_openStory {title: 'rootStory23', storyId: '*'}]
+    expect(state2).to.equal state


Optimize style cloning

Optimize style cloning

--- src/style_mixin.coffee
+++ src/style_mixin.coffee
@@ -4,22 +4,22 @@

 	@mixin: (proto)->
 		cloneStyle = (self)->
+			return if self._.styleMixin.styleCloned
 			mixin = self._.styleMixin = Object.clone(self._.styleMixin)
 			mixin.style = mixin.sm.cloneResource(mixin.style)
+			mixin.styleCloned = true

 		cloneFont = (self)->
+			return if self._.styleMixin.fontCloned
 			cloneStyle(self)
 			mixin = self._.styleMixin
 			mixin.style.font = mixin.sm.cloneResource(mixin.style.font)
+			mixin.fontCloned = true

 		fontSetter = (self, key, val) ->
-			mixin = self._.styleMixin
-			if mixin.cloned
-				return mixin.style.font[key] = val
 			cloneFont(self)
 			mixin = self._.styleMixin
 			mixin.style.font[key] = val
-			mixin.cloned = true
 			mixin.onUpdate?()


Tests: Added activityComment and activityLike tests.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -1,15 +1,33 @@
 utils   = require '../utils/utils.js'
 helpers = require '../helpers/helpers.js'
+faker   = require 'faker'

 module.exports =

   activityPost: (browser) ->

-    helpers.beginTest(browser)
-
-    browser.click '[testpath=public-feed-link]'
-
     helpers.postActivity(browser)

     browser.end()
+
+
+  activityComment: (browser) ->
+
+    helpers.postComment(browser)
+
+    browser.end()
+
+
+  activityLike: (browser) ->
+
+    helpers.postActivity(browser)
+    selector = '[testpath=activity-list] section:nth-of-type(1) [testpath=ActivityListItemView]:first-child [testpath=activity-like-link]'
+
+    browser
+      .waitForElementVisible selector, 10000
+      .click                 selector
+
+    browser.waitForElementVisible selector + '.liked:not(.count)', 10000 # Assertion
+
+    browser.end()


updated atom config

updated atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -11,7 +11,6 @@
     telemetryConsent: "limited"
     warnOnLargeFileLimit: 10
   editor:
-    showIndentGuide: true
     softWrap: true
     softWrapAtPreferredLineLength: true
   "exception-reporting":


update atom config

update atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,4 +1,16 @@
 "*":
+  "atom-material-ui":
+    colors:
+      genAccent: true
+      paintCursor: true
+    tabs:
+      compactTabs: true
+      noTabMinWidth: true
+    treeView:
+      compactList: true
+    ui:
+      panelContrast: true
+      panelShadows: true
   autosave:
     enabled: true
   core:
@@ -18,13 +30,12 @@
       "linter-htmlhint"
       "linter-scss-lint"
       "git-plus"
-      "tree-view"
     ]
     excludeVcsIgnoredPaths: false
     projectHome: "/Users/Sean/Developer"
     themes: [
-      "one-dark-ui"
-      "solarized-dark-syntax"
+      "atom-material-ui"
+      "atom-material-syntax"
     ]
   editor:
     fontSize: 12
@@ -63,10 +74,14 @@
   "one-light-ui": {}
   "quick-editor": {}
   "remote-edit": {}
-  "tree-view": {}
+  "tree-view":
+    hideVcsIgnoredFiles: true
   welcome:
     showOnStartup: false
 ".ansi.text":
+  editor:
+    tabLength: 4
+".dockerfile.source":
   editor:
     tabLength: 4
 ".hack.html.text":


Bring back the visible row range API

Bring back the visible row range API

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -3007,6 +3007,12 @@
   getScrollHeight: -> @displayBuffer.getScrollHeight()
   getScrollWidth: -> @displayBuffer.getScrollWidth()

+  getVisibleRowRange: -> @displayBuffer.getVisibleRowRange()
+
+  intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow)
+
+  selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection)
+
   screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition)

   pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)


Actually get the cursor

Actually get the cursor

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2416,7 +2416,7 @@

   scopesAtCursor: ->
     deprecate 'Use editor.getLastCursor().scopesAtCursor() instead'
-    cursor.getScopeDescriptor()
+    @getLastCursor().getScopeDescriptor()
   getCursorScopes: ->
     deprecate 'Use editor.getLastCursor().scopesAtCursor() instead'
     @scopesAtCursor()


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -8,6 +8,7 @@
     centerText: (canvas, options={}) ->
       font = options.font || I.font
       size = options.size || I.fontSize
+      color
       canvas.font "#{size}px #{font}"

       canvas.centerText


foolish

foolish

--- src/fill-all-the-things.coffee
+++ src/fill-all-the-things.coffee
@@ -50,9 +50,8 @@

   f.fill = ->
     $inputs = $(':input:visible:enabled:not([readonly])').val (i, val) ->
-      newVal = doFirst(types, [$(@), val])
-      if val != newVal then _.defer => $(@).trigger('change')
-      newVal
+      _(doFirst(types, [$(@), val])).tap (newVal) =>
+        if val != newVal then _.defer => $(@).trigger('change')

   figureOutAValue = (it, val) ->


New API for sending messages (supports private conversations)

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -48,14 +48,14 @@
     flows = [flows] unless Array.isArray(flows)
     Stream.connect @auth, flows, options

-  # Send message to flowdock
-  send: (flow, message, callback) ->
+  # Send message to Flowdock
+  send: (path, message, callback) ->
     uri = baseURL()
-    uri.path = '/messages'
+    uri.path = path
     options =
       uri: uri
       method: 'POST'
-      json: extend(message, flow: flow.replace('/', ':'))
+      json: message
       headers:
         'Authorization': @auth
         'Accept': 'application/json'
@@ -70,14 +70,22 @@

       callback res if callback

-  # Send a chat message to flowdock
+  # Send a chat message to Flowdock
   message: (flow, message, tags, callback) ->
+    data =
+      flow: flow
+      event: 'message'
+      content: message
+      tags: tags || []
+    @send "/messages", data, callback
+
+  # Send a private message to Flowdock
+  privateMessage: (userId, message, tags, callback) ->
     data =
       event: 'message'
       content: message
       tags: tags || []
-    @send flow, data, callback
-
+    @send "/private/#{userId}/messages", data, callback

   # Change status on Flowdock
   status: (flow, status) ->


Restart streaming after error

Restart streaming after error

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -62,6 +62,11 @@

 class Session extends process.EventEmitter
   constructor: (@email, @password) ->
+    @flows = []
+    @users = []
+    @initialize()
+
+  initialize: () ->
     @clientId = (random = (length) ->
       if length == 0
         ''
@@ -69,20 +74,26 @@
         chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"
         chars.charAt(Math.floor(Math.random() * chars.length)) + random(length - 1)
     )(16)
+    @users = []
     @cookies = []
-    @flows = []
-    @users = []
+    @socket = null
     @login()
+    @subscribe flow.subdomain, flow.name for flow in @flows

   start: () ->
     @socket = new FlowdockSocket(@cookies, @clientId)
     @socket.on "message", (message) =>
       @emit("message", message)
+    @socket.on "error", (statusCode, message) =>
+      setTimeout () =>
+        @initialize()
+      , 5000

   login: () ->
     post_data = querystring.stringify(
       "user_session[email]": @email
       "user_session[password]": @password
+      "user_session[remember_me]": "1"
     )

     options =


fix tests

fix tests

--- test/controllers/instances-controller-spec.coffee
+++ test/controllers/instances-controller-spec.coffee
@@ -30,6 +30,7 @@
   describe 'when /instances receives a message', ->
     beforeEach ->
       request =
+        get: sinon.stub().withArgs('deploymentUuid').returns('the-deployment-uuid')
         params:
           flowId: 'some-flow-uuid'
         meshbluAuth:
@@ -55,6 +56,7 @@

       it 'should call _createNanocyteDeployer', ->
         expect(@_createNanocyteDeployer).to.have.been.calledWith
+          deploymentUuid: 'the-deployment-uuid'
           flowUuid: 'some-flow-uuid'
           instanceId: 'an-instance-uuid'
           flowToken: 'cool-token-bro'
@@ -98,6 +100,7 @@
   describe 'when /instances receives a different message', ->
     beforeEach ->
       request =
+        get: sinon.stub().withArgs('deploymentUuid').returns('some-other-deployment-uuid')
         params:
           flowId: 'some-other-flow-uuid'
         meshbluAuth:
@@ -118,6 +121,7 @@

       it 'should call _createNanocyteDeployer', ->
         expect(@_createNanocyteDeployer).to.have.been.calledWith
+          deploymentUuid: 'some-other-deployment-uuid'
           flowUuid: 'some-other-flow-uuid'
           instanceId: 'an-instance-uuid'
           flowToken: 'do-you-even-token-bro'
@@ -137,6 +141,7 @@
   describe 'when nanocyte deployer is given a new instance uuid', ->
     beforeEach ->
       request =
+        get: sinon.stub().withArgs('deploymentUuid').returns 'this-deployment-uuid'
         params:
           flowId: 'some-other-new-flow-uuid'
         meshbluAuth:
@@ -154,6 +159,7 @@

       it 'should call _createNanocyteDeployer', ->
         expect(@_createNanocyteDeployer).to.have.been.calledWith
+          deploymentUuid: 'this-deployment-uuid'
           flowUuid: 'some-other-new-flow-uuid'
           instanceId: 'a-new-instance-uuid'
           flowToken: 'lame-token-bro'


Autoscroll even when text does not change

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -27,7 +27,7 @@
       {textChanged} = e
       return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)

-      @needsAutoscroll ?= @isLastCursor() and !textChanged
+      @needsAutoscroll ?= @isLastCursor()

       movedEvent =
         oldBufferPosition: oldHeadBufferPosition


Add Editor:onScrollTopChanged and Editor:onScrollLeftChanged

--- src/editor.coffee
+++ src/editor.coffee
@@ -226,8 +226,12 @@

     @languageMode = new LanguageMode(this)

-    @subscribe @$scrollTop, (scrollTop) => @emit 'scroll-top-changed', scrollTop
-    @subscribe @$scrollLeft, (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
+    @subscribe @$scrollTop, (scrollTop) =>
+      @emit 'scroll-top-changed', scrollTop
+      @emitter.emit 'did-change-scroll-top', scrollTop
+    @subscribe @$scrollLeft, (scrollLeft) =>
+      @emit 'scroll-left-changed', scrollLeft
+      @emitter.emit 'did-change-scroll-left', scrollLeft

     @subscribe atom.config.observe 'editor.showInvisibles', callNow: false, (show) => @updateInvisibles()
     @subscribe atom.config.observe 'editor.invisibles', callNow: false, => @updateInvisibles()
@@ -255,10 +259,13 @@
       @emitter.emit 'did-change-title', @getTitle()
       @emit "path-changed"
       @emitter.emit 'did-change-path', @getPath()
+    @subscribe @buffer.onDidDestroy => @destroy()
+
+    # TODO: remove these thwne we remove the deprecations. They are old events.
     @subscribe @buffer.onDidStopChanging => @emit "contents-modified"
     @subscribe @buffer.onDidConflict => @emit "contents-conflicted"
     @subscribe @buffer.onDidChangeModified => @emit "modified-status-changed"
-    @subscribe @buffer.onDidDestroy => @destroy()
+
     @preserveCursorPositionOnBufferReload()

   subscribeToDisplayBuffer: ->
@@ -338,6 +345,12 @@

   onDidChangeScreenLines: (callback) ->
     @emitter.on 'did-change-screen-lines', callback
+
+  onDidChangeScrollTop: (callback) ->
+    @emitter.on 'did-change-scroll-top', callback
+
+  onDidChangeScrollLeft: (callback) ->
+    @emitter.on 'did-change-scroll-left', callback

   on: (eventName) ->
     switch eventName
@@ -385,6 +398,11 @@
       when 'screen-lines-changed'
         deprecate("Use Editor::onDidChangeScreenLines instead")

+      when 'scroll-top-changed'
+        deprecate("Use Editor::onDidChangeScrollTop instead")
+      when 'scroll-left-changed'
+        deprecate("Use Editor::onDidChangeScrollLeft instead")
+
     EmitterMixin::on.apply(this, arguments)

   # Retrieves the current {TextBuffer}.


Publicize Editor::foldBufferRow

Publicize Editor::foldBufferRow

--- src/editor.coffee
+++ src/editor.coffee
@@ -777,14 +777,13 @@
   foldAllAtIndentLevel: (level) ->
     @languageMode.foldAllAtIndentLevel(level)

-  # Fold the given row in buffer coordinates based on its indentation level.
+  # Public: Fold the given row in buffer coordinates based on its indentation
+  # level.
   #
   # If the given row is foldable, the fold will begin there. Otherwise, it will
   # begin at the first foldable row preceding the given row.
   #
   # bufferRow - A {Number}.
-  #
-  # TODO: Make public when ::unfoldBufferRow goes public
   foldBufferRow: (bufferRow) ->
     @languageMode.foldBufferRow(bufferRow)


fix null exception

fix null exception

--- src/editor.coffee
+++ src/editor.coffee
@@ -185,7 +185,7 @@
       return if startOffset == endOffset
       [prevNode, startNode] = line.splitContents(startOffset)
       [endNode, nextNode] = line.splitContents(endOffset)
-      parentNode = startNode.parentNode
+      parentNode = startNode?.parentNode || prevNode?.parentNode
       if value && Tandem.Utils.getAttributeDefault(attr) != value
         fragment = @doc.root.ownerDocument.createDocumentFragment()
         Tandem.Utils.traverseSiblings(startNode, endNode, (node) ->


Fixed ord variable reference in ad_config

--- app/assets/javascripts/lib/core/ad_manager.coffee
+++ app/assets/javascripts/lib/core/ad_manager.coffee
@@ -5,7 +5,7 @@
       _config.service?= 'http://ad.doubleclick.net/adi/2009.lonelyplanet'
       iframe = document.createElement('iframe')
       ord = Math.random()*10000000000000000
-      iframe.src = "#{_config.service}/#{_config.adZone};#{_config.adKeywords};#{_config.segQS};tile=#{_config.tile};mtfIFPath=#{_config.mtfIFPath};sz=#{_config.unit[0]}x#{_config.unit[1]};ord=#{_config.ord}?"
+      iframe.src = "#{_config.service}/#{_config.adZone};#{_config.adKeywords};#{_config.segQS};tile=#{_config.tile};mtfIFPath=#{_config.mtfIFPath};sz=#{_config.unit[0]}x#{_config.unit[1]};ord=#{ord}?"
       iframe.title = "ad-leaderboard--frame"
       iframe.marginHeight = "0"
       iframe.marginWidth = "0"


engine

engine

--- src/engine.coffee
+++ src/engine.coffee
@@ -35,7 +35,6 @@
   @name beforeAdd
   @methodOf Engine#
   @event
-
   @param {Object} entityData
   ###

@@ -45,7 +44,6 @@
   @name afterAdd
   @methodOf Engine#
   @event
-
   @param {GameObject} object The object that has just been added to the
   engine.
   ###
@@ -73,7 +71,6 @@
   @name beforeDraw
   @methodOf Engine#
   @event
-
   @params {PowerCanvas} canvas A reference to the canvas to draw on.
   ###

@@ -89,7 +86,6 @@
   @name draw
   @methodOf Engine#
   @event
-
   @params {PowerCanvas} canvas A reference to the canvas to draw on.
   ###

@@ -110,7 +106,6 @@
   @name overlay
   @methodOf Engine#
   @event
-
   @params {PowerCanvas} canvas A reference to the canvas to draw on.
   ###


Revert "rename previewBase to previewUrlProvider"

This reverts commit a75662378ac19c5ef6a0b8102653998202fa30ca.

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -37,7 +37,7 @@
     cdnBase: 'https://ucarecdn.com'
     urlBase: 'https://upload.uploadcare.com'
     socialBase: 'https://social.uploadcare.com'
-    previewUrlProvider: null
+    previewBase: null
     overridePreviewUrl: null
     # fine tuning
     imagePreviewMaxSize: 25 * 1024 * 1024
@@ -204,11 +204,11 @@
     if settings.validators
       settings.validators = settings.validators.slice()

-    if settings.previewUrlProvider and not settings.overridePreviewUrl
+    if settings.previewBase and not settings.overridePreviewUrl
       settings.overridePreviewUrl = (url, info) =>
-        useGetParam = /\?$/.test(settings.previewUrlProvider)
+        useGetParam = /\?$/.test(settings.previewBase)
         location = if useGetParam then "url=#{encodeURIComponent(url)}" else "/#{url}"
-        utils.normalizeUrl(settings.previewUrlProvider) + location
+        utils.normalizeUrl(settings.previewBase) + location

     if settings.overridePreviewUrl
       if typeof settings.overridePreviewUrl is 'string'


Fix defaultPreviewUrlCallback behavior

Fix defaultPreviewUrlCallback behavior

There was a bunch of problems:
append('http://domain/path', 'query')  "http://domain/path?&url=query"
append('http://domain/path/', 'query')  "http://domain/path?&url=query"
append('http://domain/path/=', 'query')  "http://domain/path/=?query"
append('http://domain/path/?', 'query')  "http://domain/path/?url=query"
append('http://domain/path/?a', 'query')  "http://domain/path/?a&url=query"
append('http://domain/path/?a=', 'query')  "http://domain/path/?a=query"
append('http://domain/path/?a=b', 'query')  "http://domain/path/?a=b&url=query"
append('http://domain/path/?a=b&', 'query')  "http://domain/path/?a=b&url=query"

Now all is ok:
append('http://domain/path', 'query')  "http://domain/path?url=query"
append('http://domain/path/', 'query')  "http://domain/path/?url=query"
append('http://domain/path/=', 'query')  "http://domain/path/=?url=query"
append('http://domain/path/?', 'query')  "http://domain/path/?url=query"
append('http://domain/path/?a', 'query')  "http://domain/path/?a&url=query"
append('http://domain/path/?a=', 'query')  "http://domain/path/?a=query"
append('http://domain/path/?a=b', 'query')  "http://domain/path/?a=b&url=query"
append('http://domain/path/?a=b&', 'query')  "http://domain/path/?a=b&url=query"

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -152,18 +152,17 @@
   defaultPreviewUrlCallback = (url, info) ->
     if not @previewProxy
       return url
-
-    encodedUrl = encodeURIComponent(url)
-
-    justAppend = /\=$/.test(@previewProxy)
-    useAmpersand = /[^\&\?\=]$/.test(@previewProxy)
-    addQuestionSign = not /\?/.test(@previewProxy)
-
-    queryPart = if justAppend then encodedUrl else "url=#{encodedUrl}"
-    if useAmpersand then queryPart = '&' + queryPart
-    if addQuestionSign then queryPart = '?' + queryPart
-
-    utils.normalizeUrl(@previewProxy) + queryPart
+
+    addQuery = not /\?/.test(@previewProxy)
+    addName = addQuery or not /\=$/.test(@previewProxy)
+    addAmpersand = not addQuery and not /[\&\?\=]$/.test(@previewProxy)
+
+    queryPart = encodeURIComponent(url)
+    if addName then queryPart = 'url=' + queryPart
+    if addAmpersand then queryPart = '&' + queryPart
+    if addQuery then queryPart = '?' + queryPart
+
+    return @previewProxy + queryPart

   normalize = (settings) ->
     arrayOptions(settings, [


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -17,6 +17,6 @@

   enemy.follow(player)

-  ok enemy.I.velocity.equals(Poi)
+  ok enemy.I.velocity.equals(Point(1, 0))

 module()


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -21,7 +21,7 @@

 # now the enemy's velocity will point toward the player
 enemy.I.velocity
-# => Point(-1, )
+# => Point(-1, 0)

 player.update()
 </pre></code>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -47,6 +47,25 @@
   Object.reverseMerge I,
     direction: Point(0, 0)

-
+  ###
+  Keep an objects attributes within a given range.
+
+

+  # Player's health will be within [0, 100] at the end of every update
+  player.clamp
+    health:
+      min: 0
+      max: 100
+
+  # Score can only be positive
+  player.clamp
+    score:
+      min: 0
+

+
+  @name clamp
+  @methodOf Clampable#
+  @param {Object} data
+  ###
   follow: (obj) ->
     I.direction = obj.position().subtract(self.position()).norm()


ie quirk

ie quirk

--- src/format.coffee
+++ src/format.coffee
@@ -86,7 +86,7 @@
   remove: (node) ->
     return unless this.match(node)
     if _.isString(@config.style)
-      node.style[@config.style] = null
+      node.style[@config.style] = ''  # IE10 requires setting to '', other browsers can take null
       node.removeAttribute('style') unless node.getAttribute('style')   # If style is now ''
     if _.isString(@config.attribute)
       node.removeAttribute(@config.attribute)


Remove some console logging

Remove some console logging

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -19,13 +19,11 @@

   show: =>
     @stopLoading()
-    console.log @el.className
     FactlinkJailRoot.set_position_of_element @_top, @_left, window, @frame.$el
     #TODO:what's this line do?
     #FactlinkJailRoot.$factlinkCoreContainer.find('div.fl-button').removeClass('active')
     @$el.addClass 'active'
     @frame.$el.addClass 'factlink-control-visible'
-    console.log @el.className

   hide: =>
     @$el.removeClass 'active'


change server before test action to be async

--- test/server.coffee
+++ test/server.coffee
@@ -22,8 +22,7 @@
     before (done) ->
       @cms    = new RootsCMS(@project, {env: 'test'})
       @server = new Server(@cms)
-      @server.start()
-      done()
+      @server.start().then(-> done()).catch((e) -> done(e))

     describe '#start()', ->


refs #25. refactoring the code

refs #25. refactoring the code

--- lib/package/emp-debugger-adapter-package-view.coffee
+++ lib/package/emp-debugger-adapter-package-view.coffee
@@ -11,6 +11,8 @@
 PackageBarView = require './emp-debugger-pkg-plugin-view'
 PackageAdpView = require './emp-debugger-pkg-adp-view'
 tmp_offline_path = null
+project_path = null
+only_flag=true

 module.exports =
 class EmpDebugAdpPackageView extends View
@@ -31,6 +33,7 @@

     @emp_debugger_bar = new PackageBarView()
     @emp_debugger_adp_pkg = new PackageAdpView()
+    project_path = atom.project.getPath()
     this

   # 打普通资源包
@@ -40,6 +43,7 @@
     gather_common_files(offline_path)

   #  打普通资源整合包，即把普通资源报再压缩为一个压缩包
+
   do_union_package: ->
     project_path = atom.project.getPath()
     offline_path = path.join project_path, tmp_offline_path
@@ -69,24 +73,47 @@

 union_package_index = 0
 union_package_pkgs = []
+tmp_path = null
 package_union_package = (adapter_dir)->
   try
     #  全局变量初始化
     # 通过判断 union_package_index，来判断生成 综合包的时机
-    union_package_pkgs = []
-    union_package_index = 0
-
-    re_dir = check_dir(adapter_dir)
-    union_package_index = re_dir.length
-    delete re_dir.length
-
-
-    for key, value of re_dir
-      if value.length isnt 0
-        create_adapter_zip(key, value, emp.ADAPTER_UNION_PACKAGE_CHEAD, true)
-      else
-        union_package_index -= 1
+    if only_flag
+      only_flag = false
+      union_package_pkgs = []
+      union_package_index = 0
+
+      re_dir = check_dir(adapter_dir)
+      union_package_index = re_dir.length
+      delete re_dir.length
+      # console.log re_dir
+
+      project_path?= atom.project.getPath()
+      tmp_path?= path.join project_path, "tmp"
+      # console.log tmp_path
+      emp.mkdir_sync tmp_path
+
+      tmp_arr = []
+      for key, value of re_dir
+        if value.length isnt 0
+          tmp_arr.push {k:key, v:value}
+
+
+      create_adapter_buffer(tmp_arr)
+    else
+      console.log "now is packageing~"
+
+
+    # for key, value of re_dir
+    #   if value.length isnt 0
+    #     create_adapter_buffer(key, value, emp.ADAPTER_UNION_PACKAGE_CHEAD)
+    #   else
+    #     union_package_index -= 1
+
+    # common_zip(union_package_pkgs, emp.ADAPTER_UNION_PACKAGE_NAME)
+
   catch e
+      only_flag = true
       console.error e
       emp.show_error(e)

@@ -97,6 +124,7 @@
     union_package_index = re_dir.length
     delete re_dir.length

+    console.log re_dir
     for key, value of re_dir
       if value.length isnt 0
         create_adapter_zip(key, value)
@@ -187,7 +215,7 @@

-create_adapter_zip = (package_type, file_list, extra_header="", sync_flag=false) ->
+create_adapter_zip = (package_type, file_list) ->
   # console.log "create adapter zip :#{package_type}"
   project_path = atom.project.getPath()

@@ -195,7 +223,7 @@
   emp.mkdir_sync tmp_path
   package_type.split("/").join(".")

-  f_name = extra_header+([emp.ADAPTER_PACKAGE_HEAD].concat package_type.split("/")).join(".")
+  f_name = ([emp.ADAPTER_PACKAGE_HEAD].concat package_type.split("/")).join(".")
   fe_name = f_name + ".zip"
   re_pa = path.join tmp_path, fe_name
   zip = new ZipWriter()
@@ -204,38 +232,70 @@
     tmp_file_name = path.join f_name, path.basename(path.dirname tmp_file), tmp_file_name
     console.log tmp_file_name
     zip.addFile(tmp_file_name, tmp_file)
-  if sync_flag
-    union_package_pkgs.push(re_pa)
-    zip.saveAs(re_pa, () ->
-      union_package_index-=1
-      #  console.log union_package_index
-      if union_package_index is 0
-        common_zip(union_package_pkgs, emp.ADAPTER_UNION_PACKAGE_NAME)
-      console.log "zip written."
+  zip.saveAs(re_pa, () ->
+    union_package_index-=1
+    if union_package_index is 0
+      emp.show_info emp.EMP_PACKAGE_PKG_SUCCESS
+    console.log "zip written."
+    )
+  re_pa
+
+create_adapter_buffer = (re_arr) ->
+  # console.log "create adapter zip :#{package_type}"
+  # console.log re_arr
+  if re_arr.length
+    tmp_obj = re_arr.pop()
+    package_type = tmp_obj.k
+    file_list = tmp_obj.v
+
+    package_type.split("/").join(".")
+    f_name = emp.ADAPTER_UNION_PACKAGE_CHEAD+([emp.ADAPTER_PACKAGE_HEAD].concat package_type.split("/")).join(".")
+    fe_name = f_name + ".zip"
+    re_pa = path.join tmp_path, fe_name
+    zip = new ZipWriter()
+    for tmp_file in file_list
+      tmp_file_name = path.basename tmp_file
+      tmp_file_name = path.join f_name, path.basename(path.dirname tmp_file), tmp_file_name
+      # console.log tmp_file_name
+      zip.addFile(tmp_file_name, tmp_file)
+
+    # union_package_pkgs.push(re_pa)
+    zip.toBuffer((tmp_buf) ->
+        console.log re_pa
+        # console.log tmp_buf
+        # console.log union_package_pkgs
+        union_package_pkgs.push({name:re_pa, buf:tmp_buf})
+        create_adapter_buffer(re_arr)
+        # console.log union_package_pkgs
       )
   else
-    zip.saveAs(re_pa, () ->
-      union_package_index-=1
-      if union_package_index is 0
-        emp.show_info emp.EMP_PACKAGE_PKG_SUCCESS
-      console.log "zip written."
-      )
-  re_pa
-
+    only_flag = true
+    common_zip(union_package_pkgs, emp.ADAPTER_UNION_PACKAGE_NAME)
+  # console.log "--------"
+  # zip.saveAs(re_pa, () ->
+  #   union_package_index-=1
+  #   #  console.log union_package_index
+  #   if union_package_index is 0
+  #     common_zip(union_package_pkgs, emp.ADAPTER_UNION_PACKAGE_NAME)
+  #   console.log "zip written."
+  #   )
+
+  # re_pa

 common_zip = (file_list, zip_name = emp.DEFAULT_ZIP_FULE_NAME) ->
+  # console.log file_list
   project_path = atom.project.getPath()
   tmp_path = path.join project_path, "tmp"
   re_pa = path.join tmp_path, zip_name

   zip = new ZipWriter()
-  for tmp_file in file_list
-    tmp_file_name = path.basename tmp_file
+  for tmp_obj in file_list
+    tmp_file_name = path.basename tmp_obj.name
     # tmp_dir_name = path.dirname tmp_file
     # tmp_file_name = path.join path.basename(path.dirname tmp_file), tmp_file_name
-    zip.addFile(tmp_file_name, tmp_file)
-  zip.toBuffer((buf) ->
-    )
+    zip.addData(tmp_file_name, tmp_obj.buf)
+  # zip.toBuffer((buf) ->
+  #   )

   zip.saveAs(re_pa, () ->
      console.log "zip written."


Also include Bootstrap Javascript by default

--- lib/source_board/web/assets/js/sourceboard.js.coffee
+++ lib/source_board/web/assets/js/sourceboard.js.coffee
@@ -1 +1,2 @@
-#= require foo
+#= require jquery
+#= require bootstrap


Support simpler component loading also for file-based components

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -101,7 +101,10 @@
       instance = component.getComponent()
     else
       implementation = require component
-      instance = implementation.getComponent()
+      if implementation.getComponent and typeof implementation.getComponent is 'function'
+        instance = implementation.getComponent()
+      else
+        instance = implementation()
     instance.baseDir = @baseDir if name is 'Graph'
     @setIcon name, instance
     callback instance


use createPreferredFactory() in GeneralFactory

--- src/lib/general-factory.coffee
+++ src/lib/general-factory.coffee
@@ -30,7 +30,7 @@
     @create: (modelName, root) ->

         try
-            return root.createFactory(modelName)
+            return root.createPreferredFactory(modelName) # TODO: enable to pass 2nd arguement (noParent: boolean)

         catch e
             return new GeneralFactory(modelName, root)


converted mixed spaces and tabs to all spaces

--- src/lib/tmpl-precompile.coffee
+++ src/lib/tmpl-precompile.coffee
@@ -33,10 +33,10 @@

 settings:
   "uglify": Boolean(default:false), whether to minify JS
-	"namespace": String(Required), namespace object when including templates to browser
-	"source": String(Required), relative path to source directory
-	"output": String, relative path to output directory
-	"templates": Array(Required), names of templates to be precompiled
+  "namespace": String(Required), namespace object when including templates to browser
+  "source": String(Required), relative path to source directory
+  "output": String, relative path to output directory
+  "templates": Array(Required), names of templates to be precompiled
   "compileDebug": Boolean(default: false), whether to compile Jade debugging
   "inline": Boolean(default: false), whether to inline Jade runtime functions

@@ -89,8 +89,8 @@
     buf.push @helpers() if helpers isnt false and inline isnt true

     for template in @settings.templates
-  	  #buf += optimizeOutput @settings, @compileTemplate(template).toString()
-  	  buf.push @compileTemplate(template).toString()
+      #buf += optimizeOutput @settings, @compileTemplate(template).toString()
+      buf.push @compileTemplate(template).toString()

     buf = buf.join("")

@@ -111,7 +111,7 @@
     {source, namespace, compileDebug, inline} = @settings

     templateNamespace = template.replace(/\//g, '.') # Replaces '/' with '.'
-
+
     if @settings.verbose
       console.log "Compiling #{namespace}.#{templateNamespace} from #{source+template}"

@@ -155,10 +155,10 @@
   ###

   uglifyOutput : (output) ->
-  	ast = jsp.parse output  # parse code and get the initial AST
-  	ast = pro.ast_mangle ast # get a new AST with mangled names
-  	ast = pro.ast_squeeze ast # get an AST with compression optimizations
-  	pro.gen_code ast # compressed code here
+    ast = jsp.parse output  # parse code and get the initial AST
+    ast = pro.ast_mangle ast # get a new AST with mangled names
+    ast = pro.ast_squeeze ast # get an AST with compression optimizations
+    pro.gen_code ast # compressed code here

 ###
 ---Module exports---
@@ -174,14 +174,14 @@
 ###

 module.exports.precompile = (settings,dir) ->
-	extend(globalSettings, settings)
-	globalSettings.dir = dir
-
-	# Asynchronously run compilers at same time
-	async.forEach settings.groups, (groupSetting, callback) ->
-	  precompiler = new Precompiler(groupSetting, callback)
-	  precompiler.compile()
-	, (err, res) ->
+  extend(globalSettings, settings)
+  globalSettings.dir = dir
+
+  # Asynchronously run compilers at same time
+  async.forEach settings.groups, (groupSetting, callback) ->
+    precompiler = new Precompiler(groupSetting, callback)
+    precompiler.compile()
+  , (err, res) ->
     if err? then console.log err
     else console.log "\n\n\n...Done.\n\n"


fix duplicate emits

fix duplicate emits

--- src/quill.coffee
+++ src/quill.coffee
@@ -240,10 +240,8 @@

   update: (source = Quill.sources.USER) ->
     delta = @editor.update()
-    if delta.length() > 0
-      this.emit(Quill.events.TEXT_CHANGE, delta, source)
-    else if (range = @selection.update())
-      this.emit(Quill.events.SELECTION_CHANGE, range, source)
+    if delta.length() == 0
+      @selection.update()

   updateContents: (delta, source = Quill.sources.API) ->
     delta = new Delta(delta.slice()) if Array.isArray(delta)


Updated io.coffee to ensure cleanup

Updated io.coffee to ensure cleanup

--- src/io.coffee
+++ src/io.coffee
@@ -104,19 +104,17 @@
     res.promise.then ->
       unless piped
         throw new Error 'Failed to pipe stdout'
+
+  stream: ->
+    res = new PassThrough()
+    promise = if @_stdin
+      @_stream @source, res
+    else
+      When(@_fileSource()).then (filename) =>
+        @_stream filename, res
+      .ensure => @_cleanup()
+    promise.then ->
       outstream.end()
     .otherwise (err) ->
       outstream.emit 'error', err
-
-  stream: ->
-    res = new PassThrough()
-    if @_stdin
-      @_stream @source, res
-    else
-      res.once 'end', => @_cleanup()
-      res.once 'error', => @_cleanup()
-      When(@_fileSource()).then (filename) =>
-        @_stream filename, res
-      .otherwise (err) ->
-        res.emit 'error', err
     res


Fix hubot require lines

Fix hubot require lines

--- src/skype.coffee
+++ src/skype.coffee
@@ -1,11 +1,10 @@
 Readline = require 'readline'

-Robot    = require './hubot/src/robot'
-Adapter  = require './hubot/src/adapter'
+Robot   = require("hubot").robot()
+Adapter = require("hubot").adapter()

 class SkypeAdapter extends Adapter
   send: (user, strings...) ->
-    console.log "send"
     out = ""
     out = ("#{str}\n" for str in strings)
     json = JSON.stringify
@@ -25,8 +24,6 @@
     @skype = require('child_process').spawn('./skype.py')
     @skype.stdout.on 'data', (data) =>
         decoded = JSON.parse(data.toString())
-        console.log decoded
-
         user = self.userForName decoded.user
         unless user?
             id = (new Date().getTime() / 1000).toString().replace('.','')


Don't log the secret token

Don't log the secret token

--- src/slack.coffee
+++ src/slack.coffee
@@ -14,7 +14,6 @@
       autoReconnect: true
       autoMark: true

-    @robot.logger.info Util.inspect(options)
     return @robot.logger.error "No services token provided to Hubot" unless options.token
     return @robot.logger.error "v2 services token provided, please follow the upgrade instructions" unless (options.token.substring(0, 5) == 'xoxb-')


update Cube.solve()

update Cube.solve()

update Cube.solve() with the good order of faces

--- src/solve.coffee
+++ src/solve.coffee
@@ -477,7 +477,7 @@
 Cube::solve = (maxDepth=22) ->
   # Names for all moves, i.e. U, U2, U', F, F2, ...
   moveNames = do ->
-    faceName = ['U', 'F', 'L', 'D', 'B', 'R']
+    faceName = ['U', 'R', 'F', 'D', 'L', 'B']
     powerName = ['', '2', "'"]

     result = []


Use select value instead of option selected

- Gets rid of react console warning

--- app/talk/discussion.cjsx
+++ app/talk/discussion.cjsx
@@ -264,9 +264,9 @@
                   <PromiseRenderer promise={talkClient.type('boards').get({section: discussion.section, page_size: 100})}>{(boards) =>

Board:

-                      <select onChange={@onChangeSelect}>
+                      <select value={discussion.board_id}>
                         {boards.map (board, i) =>
-                          <option key={board.id} value={board.id} selected={board.id is discussion.board_id}>{board.title}</option>
+                          <option key={board.id} value={board.id}>{board.title}</option>
                           }
                       </select>


Move mysql socket into tmp

Move mysql socket into tmp

--- actions/mysql_server.coffee
+++ actions/mysql_server.coffee
@@ -66,7 +66,19 @@
       , (err, updated) ->
         return next err if err
         modified = true if updated
-        do_start()
+        do_socket()
+  do_socket = ->
+    # Create the socket inside "/tmp" instead of default "/var/lib/mysql/mysql.sock". That
+    # way, on restart, there won't be any existing file preventing the mysql server to start
+    ctx.ini
+      destination: '/etc/my.cnf'
+      content: mysqld: socket: '/tmp/mysql/mysql.sock'
+      merge: true
+      backup: true
+    , (err, updated) ->
+      return next err if err
+      modified = true if updated
+      do_start()
   do_start = ->
     ctx.service
       name: 'mysql-server'


use methods vs direct access

use methods vs direct access

--- src/models/message.coffee
+++ src/models/message.coffee
@@ -7,7 +7,7 @@
     options =
       key: @msg.ts
       msg: @msg
-      user: @channel.team.connection.client.users[@msg.user]
+      user: @channel.userFor(@msg.user)
       channel: @channel
       parent: @channel


Some tidy in preperation for `operations`

--- src/htmldiff.coffee
+++ src/htmldiff.coffee
@@ -6,8 +6,6 @@

 is_whitespace = (char)->
   /\s/.test char
-
-util = require 'util'

 class Match
   constructor: (@start_in_before, @start_in_after, @length)->
@@ -131,6 +129,8 @@
         match.end_in_after + 1, end_in_after,
         matching_blocks

+  return matching_blocks
+
 create_index = (p)->
   throw new Error 'params must have find_these key' unless p.find_these?
   throw new Error 'params must have in_these key' unless p.in_these?
@@ -146,18 +146,21 @@
   return index

 find_matching_blocks = (before_tokens, after_tokens)->
+  matching_blocks = []
   index_of_before_locations_in_after_tokens =
-    create_index find_these: before_tokens, in_these: after_tokens
-  matching_blocks = []
+    create_index
+      find_these: before_tokens
+      in_these: after_tokens
+
   recursively_find_matching_blocks before_tokens, after_tokens,
     index_of_before_locations_in_after_tokens,
     0, before_tokens.length,
-    0, after_tokens.length, matching_blocks
-  return matching_blocks
-
+    0, after_tokens.length,
+    matching_blocks

 calculate_operations = (before_tokens, after_tokens)->
-  #find matches
+  matches = find_matching_blocks before_tokens, after_tokens
+
   #--work on matches--

@@ -168,8 +171,11 @@
   after = html_to_tokens after

 diff.html_to_tokens = html_to_tokens
+
 diff.find_matching_blocks = find_matching_blocks
 find_matching_blocks.find_match = find_match
 find_matching_blocks.create_index = create_index
+
 diff.calculate_operations = calculate_operations
+
 module.exports = diff


Week starts on Sunday, not on Monday. Oops!

--- src/utils.coffee
+++ src/utils.coffee
@@ -59,7 +59,7 @@
   # Needs to be expanded
   "#{date.getDate()}/#{date.getMonth()+1}/#{date.getFullYear()}"

-utils.weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
-utils.shortWeekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+utils.weekdays = ['Sunday','Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
+utils.shortWeekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

 module.exports = utils


Sends less data on hull.auth.login

Sends less data on hull.auth.login

--- src/hull.api.coffee
+++ src/hull.api.coffee
@@ -13,7 +13,10 @@
       _emitter = emitter()
       api.init(config, _emitter).then (api)->
         _reporting = reporting.init(api)
-        _emitter.on 'hull.auth.login', (me)-> _reporting.track('hull.auth.login', me)
+        _emitter.on 'hull.auth.login', (me)->
+          #TODO Add the provider used to login
+          providers = _.pluck me.identities, 'provider'
+          _reporting.track 'hull.auth.login', providers: providers
         _emitter.on 'hull.auth.logout', ()-> _reporting.track('hull.auth.logout')

         handleUserPromise = (promise)->


Add couple more translations

Add couple more translations

Verify email link expired,  Send again

--- t9n/pt.coffee
+++ t9n/pt.coffee
@@ -103,6 +103,8 @@
       "User not found": "Usuário não encontrado"
       "Verify email link expired": "O link de verificação de e-mail expirou"
       "Verify email link is for unknown address": "O link de verificação de e-mail está configurado para um endereço desconhecido"
+      "Verify email link expired":"E-mail de verificação recebido?"
+      "Send again": "Renviar"

     #---- match
       "Match failed": "Senhas não coincidem"


modify LockManager test to avoid dependence on timing

--- services/document-updater/test/unit/coffee/LockManager/getLockTests.coffee
+++ services/document-updater/test/unit/coffee/LockManager/getLockTests.coffee
@@ -35,9 +35,11 @@
 	describe "when the lock is initially set", ->
 		beforeEach (done) ->
 			startTime = Date.now()
+			tries = 0
 			@LockManager.LOCK_TEST_INTERVAL = 5
 			@LockManager.tryLock = (doc_id, callback = (error, isFree) ->) ->
-				if Date.now() - startTime < 20
+				if (Date.now() - startTime < 20) or (tries < 2)
+					tries = tries + 1
 					callback null, false
 				else
 					callback null, true


a more positive view on is_valid_move

a more positive view on is_valid_move

--- javascripts/lib/move.coffee
+++ javascripts/lib/move.coffee
@@ -65,18 +65,18 @@

 is_valid_move = (stone, board) ->

-  field_is_occupied = (stone, board)->
-    get_color(stone.x, stone.y, board) != EMPTY
+  field_is_unoccupied = (stone, board)->
+    get_color(stone.x, stone.y, board) == EMPTY

-  is_double_move = (move, board)->
-    if board.moves.length >= 1
+  is_no_double_move = (move, board)->
+    if board.moves.length == 0
+      true
+    else
       last_move = board.moves[board.moves.length - 1]
-      last_move.color == move.color
-    else
-      false
+      last_move.color != move.color

-  is_suicide_move = (move, board)->
-    not (has_liberties(move, board) or is_capturing_stones(move, board))
+  is_no_suicide_move = (move, board)->
+    has_liberties(move, board) or is_capturing_stones(move, board)

   is_capturing_stones = (move, board)->
     # we don't want to modify the original board as this is just a test
@@ -85,7 +85,7 @@
     neighbours = enemy_neighbours(move, copied_board)
     not _.every neighbours, (stone)-> has_liberties(stone, copied_board)

-  is_forbidden_ko_move = (move, board, captures)->
+  is_no_illegal_ko_move = (move, board, captures)->

     last_move_and_current_move_captured_exactly_one = (captures, last_move) ->
       captures.length == 1 && last_move.captures.length == 1
@@ -98,17 +98,17 @@
       capture_stones_with(stone, copied_board)

-    return false if is_first_move(board)
+    return true if is_first_move(board)
     last_move = board.moves[board.moves.length - 1]
     captures = captures_of_move(stone, board)
-    last_move_and_current_move_captured_exactly_one(captures, last_move) and
-    is_same_move(last_move.captures[0], move)
+    not (last_move_and_current_move_captured_exactly_one(captures, last_move) and
+    is_same_move(last_move.captures[0], move))

-  not (field_is_occupied(stone, board) or
-  is_double_move(stone, board) or
-  is_suicide_move(stone, board) or
-  is_forbidden_ko_move(stone, board))
+  field_is_unoccupied(stone, board) and
+  is_no_double_move(stone, board) and
+  is_no_suicide_move(stone, board) and
+  is_no_illegal_ko_move(stone, board)

 is_same_move = (move, other_move)->
   move.x == other_move.x &&


Missing NgTableParams from function params

--- src/ng_table_async.coffee
+++ src/ng_table_async.coffee
@@ -127,7 +127,7 @@

     """

-  controller: ($scope, $element, $q, ngTableAsyncDefaults) ->
+  controller: ($scope, $element, $q, ngTableAsyncDefaults, NgTableParams) ->

     $scope.options = _.merge
       pagerOnTop    : ngTableAsyncDefaults.PAGER_ON_TOP


Remove placeholder texts.

Remove placeholder texts.

--- src/views.coffee
+++ src/views.coffee
@@ -603,13 +603,7 @@
                 for pid in _.keys profiles
                     shortcomings.push accessibility.get_shortcomings(@model.get('accessibility_properties'), pid)...
                 # TODO: Fetch real details here once the data is available.
-                details = [
-                    'Lorem ipsum dolor sit amet, eros honestatis ullamcorper ut vel, eum cu.'
-                    'purto decore, mea te errem regione. Mei cu utamur tacimates consetetur.'
-                    'paulo soleat maiorum vim ne. Ea vim liber euripidis voluptatibus, eam.'
-                    'cu etiam sapientem imperdiet. Quo sint pertinacia conclusionemque ut.'
-                    'Ea pro autem appellantur, usu ne illum suscipit. Ex mei detracto.'
-                ]
+                details = []

             if has_data and _.keys(profiles).length
                 if shortcomings.length


Fix breadcrumbs not up to date on reopen issue

The original breadcrumbs array was not updated properly
so when the service tree was closed and reopened again,
it contained old breadcrumbs.
Refs #91.

--- src/views.coffee
+++ src/views.coffee
@@ -747,14 +747,15 @@

             if service_id == 'root'
                 service_id = null
-                @breadcrumbs = []
+                # Use splice to affect the original breadcrumbs array.
+                @breadcrumbs.splice 0, @breadcrumbs.length
             else
                 # See if the service is already in the breadcrumbs.
                 index = _.indexOf(_.pluck(@breadcrumbs, 'service_id'), service_id)
                 if index != -1
-                    @breadcrumbs = _.first @breadcrumbs, index + 1
-                else
-                    @breadcrumbs.push(service_id: service_id, service_name: service_name)
+                    # Use splice to affect the original breadcrumbs array.
+                    @breadcrumbs.splice index, @breadcrumbs.length - index
+                @breadcrumbs.push(service_id: service_id, service_name: service_name)

             spinner_options =
                 container: $target.get(0)


Refactor custom server instantiation

Refactor custom server instantiation

--- src/watch.coffee
+++ src/watch.coffee
@@ -54,28 +54,34 @@
     params.server.port = options.port if options.port
   params

-startServer = (config, callback = (->)) ->
+startServer = (config, callback = ->) ->
   serverOpts = config.server or {}
   port = parseInt config.server.port, 10
   publicPath = config.paths.public
-  log = ->
-    logger.info "application started on http://localhost:#{port}/"
+  serverCb = ->
+    logger.info if config.server.path
+      'custom server started, initializing watcher'
+    else
+       "application started on http://localhost:#{port}/"
     callback()
   if config.server.path
+    logger.info 'starting custom server'
     try
       server = require sysPath.resolve config.server.path
     catch error
-      logger.error "couldn\'t load server #{config.server.path}: #{error}"
+      logger.error "couldn't load server #{config.server.path}: #{error}"
     unless server.startServer?
       throw new Error 'Brunch server file needs to have startServer function'
-    if serverOpts.config
-      opts = {port, path: publicPath}
-      server.startServer helpers.extend(opts, serverOpts.config), callback
-    else
-      server.startServer port, publicPath, log
+    opts = {port, path: publicPath}
+    serverConfig = helpers.extend opts, serverOpts.config or {}
+    debug "Invoking custom startServer with: #{JSON.stringify serverConfig}"
+    if server.startServer.length is 2
+      server.startServer serverConfig, serverCb
+    else
+      server.startServer port, publicPath, serverCb
   else
     opts = noLog: yes, path: publicPath
-    pushserve helpers.extend(opts, serverOpts), log
+    pushserve helpers.extend(opts, serverOpts), serverCb

 # Filter paths that exist and watch them with `chokidar` package.
 #
@@ -399,7 +405,7 @@
     if worker.isWorker
       return callback null, {config, fileList, compilers, linters}

-    serverIsLaunched = ->
+    launchWatcher = ->
       # Initialise file watcher.
       initWatcher config, (error, watcher) ->
         return callback error if error?
@@ -412,9 +418,9 @@
         }

     if config.persistent and config.server.run
-      server = startServer config, serverIsLaunched
-    else
-      serverIsLaunched()
+      server = startServer config, launchWatcher
+    else
+      launchWatcher()

 isConfigFile = (basename, configPath) ->
   files = Object.keys(require.extensions).map (_) -> configPath + _


correcting definition of getMyIssues

correcting definition of getMyIssues

--- src/jira-cli.coffee
+++ src/jira-cli.coffee
@@ -188,7 +188,7 @@
     getMyIssues: (open, details, projects)->
         jql = "assignee = \"#{@config.user}\""
         if open
-            jql += ' AND status in (Open, "In Progress", Reopened)'
+            jql += ' AND resolution = unresolved'
         jql += projects if projects?

         @searchJira jql, details


update

update

update - Support for portable Browsers on Windows

--- lib/config.coffee
+++ lib/config.coffee
@@ -7,15 +7,21 @@
         IE:
           cmd: 'start iexplore '
         Edge:
-          cmd: 'start microsoft-edge: '
+          cmd: 'start microsoft-edge '
         Chrome:
           cmd:  'start chrome '
+        ChromePortable:
+          cmd:  'start ' + atom.config.get('open-in-browsers.ChromePortablePath') + ' '
         Firefox:
           cmd:  'start firefox '
+        FirefoxPortable:
+          cmd:  'start ' + atom.config.get('open-in-browsers.FirefoxPortablePath') + ' '
         Opera:
           cmd: 'start opera '
         Safari:
           cmd: 'start safari '
+        SafariPortable:
+          cmd:  'start ' + atom.config.get('open-in-browsers.SafariPortablePath') + ' '
       win64:
         Edge:
           cmd: 'start microsoft-edge '
@@ -23,12 +29,18 @@
           cmd: 'start iexplore '
         Chrome:
           cmd:  'start chrome '
+        ChromePortable:
+          cmd:  'start ' + atom.config.get('open-in-browsers.ChromePortablePath') + ' '
         Firefox:
           cmd:  'start firefox '
+        FirefoxPortable:
+          cmd:  'start ' + atom.config.get('open-in-browsers.FirefoxPortablePath') + ' '
         Opera:
           cmd: 'start opera '
         Safari:
           cmd: 'start safari '
+        SafariPortable:
+          cmd:  'start ' + atom.config.get('open-in-browsers.SafariPortablePath') + ' '

       darwin:
         Chrome:


maybe this will work

maybe this will work

--- app/controllers/home.coffee
+++ app/controllers/home.coffee
@@ -15,26 +15,26 @@
     , res

   router.get '/current', (req, res) ->
-    request  {
+    request.get  {
       url: "http://www.airnowapi.org/aq/forecast/zipCode/?format=application/json&zipCode=#{req.param("zip-code")}&date=2016-06-04&distance=25&API_KEY=#{process.env.AIR_NOW_KEY}"
       json: true
-    }, (error, response, body) ->
-        res.json
-          frames: [
-            {
-              index: 0
-              text: "Air Quality for #{body[0].ReportingArea}, #{body[0].StateCode} (#{req.param("zip-code")})"
-              icon: "a2286"
-            },
-            {
-              index: 1
-              text: "Air Quality for " + req.param("zip-code")
-              icon: "i120"
-            },
-            {
-              index: 2
-              text:  req.param("zip-code")
-              icon: "i120"
-            }
-          ]
+    }, (e, r, body) ->
+      res.json
+        frames: [
+          {
+            index: 0
+            text: "Air Quality for #{body[0].ReportingArea}, #{body[0].StateCode} (#{req.query("zip-code")})"
+            icon: "a2286"
+          },
+          {
+            index: 1
+            text: "Air Quality for " + req.query("zip-code")
+            icon: "i120"
+          },
+          {
+            index: 2
+            text:  req.query("zip-code")
+            icon: "i120"
+          }
+        ]


#15 - Add comments and fix sync.pref_update

--- app/controllers/sync.coffee
+++ app/controllers/sync.coffee
@@ -137,6 +137,12 @@
   ###
    * (private) Update Model
    *
+   * Setup update event
+   *
+   * - classname (string)
+   * - changes (object)
+   * - timestamps (object)
+   * > boolean
   ###

   model_update_setup: (classname, changes, timestamps) =>
@@ -150,6 +156,19 @@
       return false

     return true
+
+
+  ###
+   * (private) Model Update Timestamps
+   *
+   * Handle timestamps for an update event
+   *
+   * - classname (string)
+   * - id (string)
+   * - changes (object)
+   * - timestamps (object)
+   * > timestamps
+  ###

   model_update_timestamps: (classname, id, changes, timestamps) ->

@@ -171,11 +190,23 @@

     return timestamps

-  # Save to server
+
+  ###
+   * (private) Model Update Save
+   *
+   * Save model changes and timestamps
+   *
+   * - classname (string)
+   * - id (string)
+   * - changes (object)
+   * - timestamps (object)
+   * > changes
+  ###
+
   model_update_save: (classname, id, changes, timestamps) ->
+    log "[#{ classname }] [update] saved #{ id }"
     @time.set(classname, id, timestamps)
     @user.updateModel(classname, id, changes)
-    changes.id = id
     return changes

@@ -210,7 +241,11 @@
       warn '[task] [update] timestamps is null'
       return null

-    return @model_update_save(TASK, id, changes, timestamps)
+    @model_update_save(TASK, id, changes, timestamps)
+
+    changes.id = id
+    return changes
+

   ###
@@ -241,36 +276,31 @@
       delete changes.tasks
       delete timestamps.tasks

-    return @model_update_save(LIST, id, changes, timestamps)
+    @model_update_save(LIST, id, changes, timestamps)
+
+    changes.id = id
+    return changes
+
+
+  ###
+   * Update Pref
+   *
+   * - changes (object)
+   * - timestamps (timestamps)
+   * > changes
+  ###

   pref_update: (changes, timestamps) =>

     # Pref id is always s0
     id = PREF_ID

-    # Set timestamp
-    if timestamps
-      for attr, time of timestamps
-        old = @time.get PREF, id, attr
-        if old > time
-          delete timestamps[attr]
-          delete changes[attr]
-      if Object.keys(changes).length is 0
-        warn '[pref] [update] old event', id
-        return null
-    else
-      timestamps = {}
-      now = Date.now()
-      for key of changes
-        timestamps[key] = now
-
-    @time.set PREF, id, timestamps
-
-    # Save to server
-    model = @user.updateModel PREF, id, changes
-    log '[pref] updated', changes
-
-    return changes
+    timestamps = @model_update_timestamps(PREF, id, changes, timestamps)
+    unless timestamps
+      warn '[pref] [updaet] timestamps is null'
+      return null
+
+    return @model_update_save(PREF, id, changes, timestamps)


fix isProduction logic

fix isProduction logic

--- app/core/application.coffee
+++ app/core/application.coffee
@@ -46,7 +46,7 @@

 Application = initialize: ->
   Router = require('core/Router')
-  @isProduction = -> document.location.href.search('https?://localhost:3000') is -1
+  @isProduction = -> document.location.href.search('https?://localhost') is -1
   @isIPadApp = webkit?.messageHandlers? and navigator.userAgent?.indexOf('CodeCombat-iPad') isnt -1
   $('body').addClass 'ipad' if @isIPadApp
   $('body').addClass 'picoctf' if window.serverConfig.picoCTF


spell out entirely

spell out entirely

--- library/exposure/inventory.coffee
+++ library/exposure/inventory.coffee
@@ -103,7 +103,7 @@
     # cased with no difference. Please see `RestfulService` class
     # for more information on verbs, especially `SUPPORTED` const.
     # Also, take a look at `ApiService` for advanced definitions.
-    @GET "/api/inventory/:uid:", @g @cache (scoped) ->
+    @GET "/api/inventory/:uid:", @guard @cache (scoped) ->
         identify = try @constructor.identify().underline
         assert _.isObject Asc = ApiService # a shorthand
         malfReg = "the routing registry seems is broken"


minor change in the BL

minor change in the BL

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -75,7 +75,7 @@
             context.invokes.push "\r\n#{template}\r\n"
             uplinks = context.uplinks ?= new Object
             uplinks[name] = directives; return @
-        return next undefined
+        return next.call this, undefined

     # Declarate the supplied implementation function as the uplink.
     # An uplink is an external (remote) function published on socket


Methods reordering

Methods reordering

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -88,6 +88,28 @@
         logger.info running.grey, identify.underline
         @installation kernel, targets, options, next

+    # An internal routine that launches the actual Bower installer.
+    # It takes a series of pre calculated parameters to be able to
+    # perform the installation properly. Plese refer to the register
+    # hook implementation in this ABC service for more information.
+    installation: (kernel, targets, options, next) ->
+        install = bower.commands.install
+        bowerings = (@constructor.bowerings ?= [])
+        installer = install(targets, {}, options)
+        installer.on "error", (error) ->
+            reason = "failed Bower package installation"
+            logger.error error.message.red, error
+            kernel.shutdownKernel reason
+        installer.on "end", (installed) =>
+            bowerings.installed = installed
+            message = "Get Bower lib %s@%s at %s"
+            for packet in _.values(installed or {})
+                what = packet.pkgMeta?.name.underline
+                vers = packet.pkgMeta?.version.underline
+                where = @constructor.identify().underline
+                logger.debug message.cyan, what, vers, where
+        return next()
+
     # This server side method is called on the context prior to the
     # context being compiled and flushed down to the client site. The
     # method is wired in an synchronous way for greater functionality.
@@ -109,25 +131,3 @@
                     context.scripts.push file if ext ".js"
                     context.sheets.push file if ext ".css"
             return next bowerings.cached context
-
-    # An internal routine that launches the actual Bower installer.
-    # It takes a series of pre calculated parameters to be able to
-    # perform the installation properly. Plese refer to the register
-    # hook implementation in this ABC service for more information.
-    installation: (kernel, targets, options, next) ->
-        install = bower.commands.install
-        bowerings = (@constructor.bowerings ?= [])
-        installer = install(targets, {}, options)
-        installer.on "error", (error) ->
-            reason = "failed Bower package installation"
-            logger.error error.message.red, error
-            kernel.shutdownKernel reason
-        installer.on "end", (installed) =>
-            bowerings.installed = installed
-            message = "Get Bower lib %s@%s at %s"
-            for packet in _.values(installed or {})
-                what = packet.pkgMeta?.name.underline
-                vers = packet.pkgMeta?.version.underline
-                where = @constructor.identify().underline
-                logger.debug message.cyan, what, vers, where
-        return next()


Removed file that was unintentionally added

--- lib/interp.coffee
+++ lib/interp.coffee
@@ -1,90 +0,0 @@
-special =
-  lambda: (input, context) ->
-    (lambda_args...) ->
-      lambda_scope = input[1].reduce (acc, x, i) ->
-        acc[x.value] = lambda_args[i]
-        acc
-
-      interpret input[2], new Context(lambda_scope, context)
-
-tokenize = (input) ->
-  input.split('"')
-    .map((x, i) ->
-      if i % 2 is 0 # not in string
-        x.replace(/\(/g, ' ( ')
-          .replace /\)/g, ' ) '
-      else
-        x.replace /\s/g, "!%!"
-    )
-    .join('"')
-    .trim()
-    .split(/\s+/)
-    .map (x) -> x.replace(/!%!/g, " ")
-
-parenthesize = (input, list) ->
-  if list is undefined
-    parenthesize input, []
-  else
-    token = input.shift()
-    switch token
-      when undefined
-        list.pop()
-      when '('
-        list.push parenthesize input, []
-        parenthesize input, list
-      when ')'
-        list
-      else
-        parenthesize input, list.concat categorize token
-
-categorize = (input) ->
-  if not isNaN parseFloat input
-    {
-      type: 'literal'
-      value: parseFloat input
-    }
-  else if input[0] is '"' and input[-1..] is '"'
-    {
-      type: 'literal'
-      value: input.slice 1, -1
-    }
-  else
-    {
-      type: 'identifier'
-      value: input
-    }
-
-parse = (input) ->
-  parenthesize tokenize input
-
-class Context
-  constructor: (scope, parent) ->
-    @scope = scope
-    @parent = parent
-    return
-
-  get: (ident) ->
-    if ident of @scope
-      @scope[ident]
-    else if @parent isnt undefined
-      @parent.get ident
-
-interpret = (input, context) ->
-  if context is undefined
-    interpret input, new Context(library)
-  else if input instanceof Array
-    interpret_list input, context
-  else if input.type is 'identifier'
-    context.get input.value
-  else
-    input.value
-
-interpret_list = (input, context) ->
-  if input[0].value in special
-    special[input[0].value] input, context
-  else
-    list = input.map (x) -> interpret x, context
-    if list[0] instanceof Function
-      list[0].apply undefined, list[1..]
-    else
-      list


Update 24 December Summary: Fixes steelbrain/atom-hack#8 steelbrain/atom-hack#7

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -24,8 +24,10 @@
       if typeof pane.onDidDestroy is 'undefined' then return
       pane.onDidDestroy ->
         if me.errors.length isnt 0
-          me.markClear()
-          me.markErrors()
+          try
+            me.markClear()
+            me.markErrors()
+          catch
         return
       return
     atom.workspace.observeTextEditors (editor)->
@@ -94,7 +96,8 @@
       delete error.message[0]
       for trace in error.message when typeof trace isnt 'undefined'
         dis.trace.push line: trace.line,start: trace.start, end: trace.end, message:trace.descr,file:trace.path.replace(@config.remoteDir,@config.localDir).split('/').join(path.sep)
-      @errors.push dis
+      if @errors.indexOf dis is -1
+        @errors.push dis
     if atom.workspaceView.find('.am-panel').length isnt 1
       msgPanel.init(' Hack report')
       @msgPanel = 1


Disable remove log button on pro for now

--- assets/scripts/app/views/repo/show.coffee
+++ assets/scripts/app/views/repo/show.coffee
@@ -292,6 +292,7 @@
     ).property('displayRemoveLog')

     displayRemoveLog: (->
+      return if Travis.config.pro
       if job = @get('_job')
         (@get('isJobTab') || (@get('isBuildTab') && @get('build.jobs.length') == 1)) &&
           job.get('canRemoveLog') && @get('hasPermission')


REFACTOR: break up RocketChat saml initialisation.

--- packages/meteor-accounts-saml/saml_rocketchat.coffee
+++ packages/meteor-accounts-saml/saml_rocketchat.coffee
@@ -17,8 +17,8 @@
 		RocketChat.settings.add "SAML_Custom_#{name}_entry_point"       , 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php', { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Entry_point'}
 		RocketChat.settings.add "SAML_Custom_#{name}_issuer"            , 'https://rocket.chat/'                                        , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Issuer'}
 		RocketChat.settings.add "SAML_Custom_#{name}_cert"              , ''                                                            , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Cert'}
-		RocketChat.settings.add "SAML_Custom_#{name}_public_cert_file_path"  , 'filename.crt'                                           , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Public_Cert_File_Path'}
-		RocketChat.settings.add "SAML_Custom_#{name}_private_key_file_path"  , 'filename.key'                                           , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Private_Key_File_Path'}
+		RocketChat.settings.add "SAML_Custom_#{name}_public_cert_file_path"  , '/path/to/filename.crt'                                           , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Public_Cert_File_Path'}
+		RocketChat.settings.add "SAML_Custom_#{name}_private_key_file_path"  , '/path/to/filename.key'                                           , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Private_Key_File_Path'}
 		RocketChat.settings.add "SAML_Custom_#{name}_button_label_text" , ''                                                            , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text'}
 		RocketChat.settings.add "SAML_Custom_#{name}_button_label_color", '#FFFFFF'                                                     , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color'}
 		RocketChat.settings.add "SAML_Custom_#{name}_button_color"      , '#13679A'                                                     , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Button_Color'}
@@ -26,8 +26,8 @@

 timer = undefined

-# Find existing SAML providers (idp's) in in RocketChat settings
-# (instances of RocketChat.models.Settings) and update the system.
+# Find existing SAML services (idp's) in RocketChat settings and
+# update the system.
 #
 # Updating the system includes:
 # 1) Appending enabled providers to Accounts.saml.settings.providers.
@@ -36,50 +36,57 @@

 updateServices = ->
 	Meteor.clearTimeout timer if timer?
-
 	timer = Meteor.setTimeout ->
 		services = RocketChat.settings.get(/^(SAML_Custom_)[a-z]+$/i)
-
 		Accounts.saml.settings.providers = []
-
 		for service in services
-			logger.updated service.key
-
 			serviceName = 'saml'
-
 			if service.value is true
-				data =
-					buttonLabelText: RocketChat.settings.get("#{service.key}_button_label_text")
-					buttonLabelColor: RocketChat.settings.get("#{service.key}_button_label_color")
-					buttonColor: RocketChat.settings.get("#{service.key}_button_color")
-					clientConfig:
-						provider: RocketChat.settings.get("#{service.key}_provider")
-
-				Accounts.saml.settings.generateUsername = RocketChat.settings.get("#{service._id}_generate_username")
-				privateKeyFilePath = RocketChat.settings.get("#{service._id}_private_key_file_path")
-				publicCertFilePath = RocketChat.settings.get("#{service._id}_public_cert_file_path")
-
-				try
-					privateKey = fs.readFileSync(privateKeyFilePath).toString()
-					privateCert = fs.readFileSync(publicCertFilePath).toString()
-				catch err
-					console.warn "Can't configure key or cert for SAML, signing and encryption will not work"
-					console.warn "Error was: #{err.message}"
-					privateKey = false
-					privateCert = false
-
-				Accounts.saml.settings.providers.push
-					provider: data.clientConfig.provider
-					entryPoint: RocketChat.settings.get("#{service.key}_entry_point")
-					issuer: RocketChat.settings.get("#{service.key}_issuer")
-					cert: RocketChat.settings.get("#{service.key}_cert")
-					privateCert: privateCert
-					privateKey: privateKey
-
-				ServiceConfiguration.configurations.upsert {service: serviceName.toLowerCase()}, $set: data
+				samlConfigs = getSamlConfigs(service)
+				configureSamlService(samlConfigs)
+				ServiceConfiguration.configurations.upsert {service: serviceName.toLowerCase()}, $set: samlConfigs
 			else
 				ServiceConfiguration.configurations.remove {service: serviceName.toLowerCase()}
+			logger.updated service.key
 	, 2000
+
+# Fetch config settings from RocketChat for a given SAML service "SAML_Custom_<name>".
+
+getSamlConfigs = (service) ->
+	buttonLabelText: RocketChat.settings.get("#{service.key}_button_label_text")
+	buttonLabelColor: RocketChat.settings.get("#{service.key}_button_label_color")
+	buttonColor: RocketChat.settings.get("#{service.key}_button_color")
+	clientConfig:
+		provider: RocketChat.settings.get("#{service.key}_provider")
+	privateKeyFilePath: RocketChat.settings.get("#{service.key}_private_key_file_path")
+	publicCertFilePath: RocketChat.settings.get("#{service.key}_public_cert_file_path")
+	entryPoint: RocketChat.settings.get("#{service.key}_entry_point")
+	issuer: RocketChat.settings.get("#{service.key}_issuer")
+	cert: RocketChat.settings.get("#{service.key}_cert")
+	generateUsername: RocketChat.settings.get("#{service.key}_generate_username")
+
+# Configure Meteor SAML.
+#
+# Get private key for signing and update Accounts.saml.settings.
+# Meteor saml package uses Accounts.saml.settings.
+
+configureSamlService = (samlConfigs) ->
+	try
+		privateKey = fs.readFileSync(samlConfigs.privateKeyFilePath).toString()
+		privateCert = fs.readFileSync(samlConfigs.publicCertFilePath).toString()
+	catch err
+		console.warn "Can't configure key or cert for SAML, signing will not be performed by RocketChat"
+		console.warn "Error was: #{err.message}"
+		privateKey = false
+		privateCert = false
+	Accounts.saml.settings.generateUsername = samlConfigs.generateUsername
+	Accounts.saml.settings.providers.push
+		provider: samlConfigs.clientConfig.provider
+		entryPoint: samlConfigs.entryPoint
+		issuer: samlConfigs.issuer
+		cert: samlConfigs.cert
+		privateCert: privateCert
+		privateKey: privateKey

 RocketChat.settings.get /^SAML_.+/, (key, value) ->
 	updateServices()


complete abstracting isAuthorized for each robot command

--- src/tf.coffee
+++ src/tf.coffee
@@ -72,8 +72,7 @@
       msg.send {room: msg.message.user.name}, "```\n#{pubkey}\n```"

   robot.respond /tf show key$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     if ! fs.existsSync("#{publickey}")
       return msg.send {room: msg.message.user.name}, "No key on file!  Create one first."
@@ -82,8 +81,7 @@
     return msg.send {room: msg.message.user.name}, "```\n#{pubkey}\n```"

   robot.respond /tf destroy key$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     if ! fs.existsSync("#{publickey}")
       return msg.send {room: msg.message.user.name}, "No key on file!  Create one first."
@@ -93,8 +91,7 @@
     return msg.send {room: msg.message.user.name}, "Key destroyed!"

   robot.respond /tf clone ([^\s]+) ([^\s]+)$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     url = msg.match[1]
     projname = msg.match[2].replace /\//, "_"
@@ -113,8 +110,7 @@
         msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"

   robot.respond /tf list(\sprojects)?$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     dir = []
     for fn in fs.readdirSync(basepath)
@@ -124,8 +120,7 @@
     return msg.send {room: msg.message.user.name}, dir.join "\n"

   robot.respond /tf remote ([^\s]+)$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     projname = msg.match[1].replace /\//, "_"

@@ -141,8 +136,7 @@
         msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"

   robot.respond /tf (get) ([^\s]+)$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     action = msg.match[1]
     projname = msg.match[2].replace /\//, "_"
@@ -159,8 +153,7 @@
         msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"

   robot.respond /tf delete ([^\s]+)$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     projname = msg.match[1].replace /\//, "_"

@@ -171,8 +164,7 @@
       msg.send {room: msg.message.user.name}, "Project deleted: #{projname}"

   robot.respond /tf (plan|refresh|apply|destroy) ([^\s]+)( verbose)?$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     action = msg.match[1]
     projname = msg.match[2].replace /\//, "_"
@@ -220,8 +212,7 @@
         msg.send {room: msg.message.user.name}, "error:\n```\n#{error}\n```"

   robot.respond /tf env ([^\s]+) set ([^\s]+)=(.+)$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     projname = msg.match[1].replace /\//, "_"
     ekey = msg.match[2]
@@ -239,8 +230,7 @@
     return msg.send {room: msg.message.user.name}, "`#{projname}` env set: `#{ekey}` = `#{evalue}`"

   robot.respond /tf env ([^\s]+) unset ([^\s]+)$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     projname = msg.match[1].replace /\//, "_"
     ekey = msg.match[2]
@@ -257,8 +247,7 @@
     return msg.send {room: msg.message.user.name}, "`#{projname}` env `#{ekey}` unset."

   robot.respond /tf env ([^\s]+)(?:\slist)?$/i, (msg) ->
-    unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,tfRole)
-      return msg.send {room: msg.message.user.name}, "Not authorized.  Missing #{tfRole} role."
+    return unless isAuthorized robot, msg

     projname = msg.match[1].replace /\//, "_"


use proper syntax for iterating objects in listing env

--- src/tf.coffee
+++ src/tf.coffee
@@ -204,5 +204,5 @@

     msg.send {room: msg.message.user.name}, "```\n#{JSON.stringify(localstorage)}\n```"
     ekvs = [ "`#{projname}` env set:" ]
-    ekvs.push "  `#{ekey}` = `#{localstorage[ekey]}`" for ekey in localstorage
+    ekvs.push "  `#{k}` = `#{v}`" for k,v of localstorage
     return msg.send {room: msg.message.user.name}, ekvs.join "\n"


fix tests: download atom-shell through http instead of https

--- test/basic-test.coffee
+++ test/basic-test.coffee
@@ -94,7 +94,7 @@
 	releaseName = "atom-shell-v#{target}-#{platform}-#{arch}"
 	dir = path.join 'atom-shell', releaseName
 	return cb() if fs.existsSync dir # skip if we already have it
-	url = "https://github.com/atom/atom-shell/releases/download/v#{target}/#{releaseName}.zip"
+	url = "http://github.com/atom/atom-shell/releases/download/v#{target}/#{releaseName}.zip"
 	zipfileName = path.join 'atom-shell', "#{releaseName}.zip"
 	zipfile = fs.createWriteStream zipfileName
 	request(url).pipe(zipfile).on 'finish', ->


urg, this still breaks us...

urg, this still breaks us...

--- test/browser/main.iced
+++ test/browser/main.iced
@@ -1,7 +1,7 @@

 mods =
   sha512 : require '../files/sha512.iced'
-  #wordarray : require '../files/wordarray.iced'
+  wordarray : require '../files/wordarray.iced'

 {BrowserRunner} = require('iced-test')


Remove parenthesis

Remove parenthesis

--- test/deprecated.coffee
+++ test/deprecated.coffee
@@ -4,19 +4,19 @@
 describe 'deprecated behavior', ->

   it 'does nothing when changing the language like before', ->
-    assert.equal(humanize(1000), '1 second')
+    assert.equal humanize(1000), '1 second'
     humanize.language = 'es'
-    assert.equal(humanize(1000), '1 second')
+    assert.equal humanize(1000), '1 second'
     delete humanize.language

   it 'does nothing when setting the language as a string', ->
-    assert.equal(humanize(1000, 'en'), '1 second')
-    assert.equal(humanize(1000, 'es'), '1 second')
-    assert.equal(humanize(1000, 'pt'), '1 second')
-    assert.equal(humanize(1000, 'garbage'), '1 second')
+    assert.equal humanize(1000, 'en'), '1 second'
+    assert.equal humanize(1000, 'es'), '1 second'
+    assert.equal humanize(1000, 'pt'), '1 second'
+    assert.equal humanize(1000, 'garbage'), '1 second'

   it 'removes old methods', ->
-    assert.equal(humanize.componentsOf, undefined)
-    assert.equal(humanize.addLanguage, undefined)
-    assert.equal(humanize.defaults, undefined)
-    assert.equal(humanize.language, undefined)
+    assert.equal humanize.componentsOf, undefined
+    assert.equal humanize.addLanguage, undefined
+    assert.equal humanize.defaults, undefined
+    assert.equal humanize.language, undefined


Repair caching.

Repair caching.

--- src/scripts/jira-issues.coffee
+++ src/scripts/jira-issues.coffee
@@ -53,8 +53,10 @@
           now = new Date().getTime()
           if cache.length > 0
             cache.shift() until cache.length is 0 or cache[0].expires >= now
+
+          msg.send item.message for item in cache when item.issue is issue
+
           if cache.length == 0 or (item for item in cache when item.issue is issue).length == 0
-            cache.push({issue: issue, expires: now + 120000})
             robot.http(jiraUrl + "/rest/api/2/issue/" + issue)
               .auth(auth)
               .get() (err, res, body) ->
@@ -74,11 +76,12 @@
                   else
                     message += ', fixVersion: NONE'

-                  msg.send message
-
                   urlRegex = new RegExp(jiraUrl + "[^\\s]*" + key)
                   if not msg.message.text.match(urlRegex)
-                    msg.send jiraUrl + "/browse/" + key
+                    message += "\n" + jiraUrl + "/browse/" + key
+
+                  msg.send message
+                  cache.push({issue: issue, expires: now + 120000, message: message})
                 catch error
                   try
                     msg.send "[*ERROR*] " + json.errorMessages[0]


:art: use underscore partition to simplify grouping before/after

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -620,25 +620,19 @@
     return [] if @input is ""

     cursorPosition = cursor.getBufferPosition()
-
-    [rangesBefore, rangesAfter] = [[], []]
-    @editor.scan @getSearchTerm(@input), ({range}) =>
-      {start} = range
-      isBefore =
-        if @isBackwards()
-          start.compare(cursorPosition) < 0
-        else
-          start.compare(cursorPosition) <= 0
-
-      if isBefore
-        rangesBefore.push(range)
+    ranges = []
+    @editor.scan @getSearchTerm(@input), ({range}) ->
+      ranges.push range
+
+    [rangesBefore, rangesAfter] = _.partition ranges, ({start}) =>
+      if @isBackwards()
+        start.isLessThan(cursorPosition)
       else
-        rangesAfter.push(range)
-
-    if @isBackwards()
-      rangesAfter.concat(rangesBefore).reverse()
-    else
-      rangesAfter.concat(rangesBefore)
+        start.isLessThanOrEqual(cursorPosition)
+
+    ranges = rangesAfter.concat(rangesBefore)
+    ranges.reverse() if @isBackwards()
+    ranges

   getSearchTerm: (term) ->
     modifiers = {'g': true}