Add JSDoc comments to all methods + Minor refactorings

--- lib/list-edit.coffee
+++ lib/list-edit.coffee
@@ -1,3 +1,9 @@
+###*
+ * Main list-edit module.
+ *
+ * @module ListEdit
+###
+
 {CompositeDisposable, Range, TextBuffer} = require 'atom'
 _ = require 'underscore-plus'
 TextManipulation = require './text-manipulation'
@@ -174,14 +180,30 @@
         textBuffer.setTextInRange pasteRange, pasteText
         editor.setCursorBufferPosition (textBuffer.positionForCharacterIndex pasteIxRange[0] + pasteText.length)

-  # Wrapper for easy access of common variables
+  # TODO: @callbacks and @typedefs somehow need to be global or they cannot be referenced (~ an / don't work).
+  ###*
+   * Wrapper callback for accessing objects common to all list-edit actions.
+   * @global
+   * @callback WrapperCallback
+   * @param {TextEditor}  editor           - The active text editor.
+   * @param {TextBuffer}  textBuffer       - The corresponding text buffer.
+   * @param {string}      bufferText       - The text string for textBuffer.
+   * @param {number[]}    selectionIxRange - Character index range ([startIx, endIx]) for the most recently added user selection (multiple ranges are not supported).
+   * @param {ElementList} elementList      - The element list that contains selectionIxRange.
+   * @param {number[]}    listSelection    - List-element index range corresponding to selectionIxRange.
+  ###
+
+  ###*
+   * Wrapper that creates the ElementList object and provides access to common objects.
+   * @param  {WrapperCallback} callback - Is only called if there is a valid element list for the current selection.
+  ###
   withSelectedList: (callback) ->
     editor = atom.workspace.getActiveTextEditor()
     if editor?
       textBuffer = editor.getBuffer()
       bufferText = textBuffer.getText()
       selectionIxRange = TextManipulation.getIxRangeForRange textBuffer, (editor.getSelectedBufferRange())
-      ignoreRanges = @scanIgnoreRanges editor, textBuffer, bufferText
+      ignoreRanges = @scanIgnoreRanges editor
       elementList = TextManipulation.ElementList.getElementList bufferText, ignoreRanges, selectionIxRange
       if not elementList?
         atom.notifications.addWarning 'Selection is not in well-formed list.'
@@ -194,6 +216,27 @@
           # Can also use => in the definition of the callbacks that require @, but this is safer
           (callback.bind this) editor, textBuffer, bufferText, selectionIxRange, elementList, listSelection

+  ###*
+   * Clipboard metadata for list-edit operations. Contains the specifics for the source list of a copy or cut operation.
+   *
+   * @global
+   * @typedef {Object} ListEditMeta
+   * @property {string}     id                - Is always 'list-edit-clip-meta'.
+   * @property {string}     openBracket       - String of length 1.
+   * @property {string}     initialWhitespace - Whitespace following the opening bracket.
+   * @property {string}     finalWhitespace   - Whitespace preceding the closing bracket.
+   * @property {string}     separator         - String of length 1.
+   * @property {number[][]} eltRanges         - Character index in clipboard string (not buffer) for actual list elements (without leading/trailing whitespace).
+  ###
+
+  ###*
+   * Create clipboard metadata object.
+   *
+   * @param  {ElementList} elementList Source element list for the clip.
+   * @param  {number} selStart    Buffer-text index of start of selection.
+   * @param  {number} selEnd      Buffer-text index of end of selection.
+   * @return {ListEditMeta}
+  ###
   mkListEditMeta: (elementList, selStart, selEnd) ->
     { startIx: listStartIx, endIx: listEndIx
     , openBracket: openBracket, separator: separator
@@ -211,13 +254,34 @@
     , eltRanges: eltRanges
     }

+  ###*
+   * Return clipboard metadata if it was generated by list-edit (i.e. has a 'list-edit-clip-metadata' id).
+   * @param  {string} clip - Atom clipboard
+   * @return {ListEditMeta}
+  ###
   getListEditMeta: (clip) ->
     clip.metadata if clip?.metadata?.id == 'list-edit-clip-meta'

+    if clip?.metadata?.id == 'list-edit-clip-meta'
+      clip.metadata
+    else
+      null
+
+  ###*
+   * List of top-level scopes that should be ignored.
+   * @constant {String}
+  ###
   ignoreScopes: ['string', 'comment']

-  # Return all ranges that have a scope from ignoreScopes. Connecting ranges are merged.
-  scanIgnoreRanges: (editor, textBuffer, bufferText) ->
+  ###*
+   * Return all ranges that have a scope from ignoreScopes.
+   * @param  {Editor} editor
+   * @return {number[][]} List of index ranges ([startIx, endIx]). Connecting ranges are merged.
+  ###
+  scanIgnoreRanges: (editor) ->
+    textBuffer = editor.getBuffer()
+    bufferText = textBuffer.getText()
+
     grammar = editor.getGrammar()
     ignoredScopeIds = []
     for scope, scopeId of grammar.registry.idsByScope # TODO: is this how we should access the GrammarRegistry?


set up routes

set up routes

--- assets/js/routes.coffee
+++ assets/js/routes.coffee
@@ -3,12 +3,24 @@
   'angular'
 ], (cfg, A) ->
   routes = ($routeProvider, $locationProvider) ->
-    # $routeProvider
-    #   .when '/',
-    #     templateUrl: cfg.path.partial + 'some-partial.html'
+    $routeProvider
+      .when '/getting-started',
+        templateUrl: cfg.path.partial + 'getting-started.html'

-    # $routeProvider.otherwise
-    #   redirectTo: '/'
+      .when '/code/:section',
+        templateUrl: cfg.path.partial + 'code.html'
+
+      .when '/code',
+        templateUrl: cfg.path.partial + 'code.html'
+
+      .when '/support',
+        templateUrl: cfg.path.partial + 'support.html'
+
+      .when '/',
+        templateUrl: cfg.path.partial + 'home.html'
+
+    $routeProvider.otherwise
+      redirectTo: '/'

     $locationProvider.html5Mode true


Group early returns

Group early returns

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -261,11 +261,12 @@
     editor = @editorView.editor
     pos = editor.getCursorBufferPosition()
     currentRowLength = editor.lineLengthForBufferRow(pos.row)
+
     # Do nothing on an empty line
     return unless currentRowLength > 0
-
     # Do nothing if asked to replace more characters than there are on a line
     return unless currentRowLength - pos.column >= count
+
     @undoTransaction =>
       start = editor.getCursorBufferPosition()
       _.times count, =>


Also use dpr when scaling.X and Y

Also use dpr when scaling.X and Y

--- framer/LayerStyle.coffee
+++ framer/LayerStyle.coffee
@@ -110,8 +110,8 @@
 			#{roundToZero(layer._properties.y * dpr)}px,
 			#{roundToZero(layer._properties.z)}px)
 		scale3d(
-			#{roundToZero(layer._properties.scaleX * layer._properties.scale)},
-			#{roundToZero(layer._properties.scaleY * layer._properties.scale)},
+			#{roundToZero(layer._properties.scaleX * layer._properties.scale * dpr)},
+			#{roundToZero(layer._properties.scaleY * layer._properties.scale * dpr)},
 			#{roundToZero(layer._properties.scaleZ)})
 		skew(#{roundToZero(layer._properties.skew)}deg,#{roundToZero(layer._properties.skew)}deg)
 		skewX(#{roundToZero(layer._properties.skewX)}deg)
@@ -136,7 +136,7 @@

 		dpr = layer.context.devicePixelRatio
 		css.push "translate(#{roundToZero(layer._properties.x * dpr)}px,#{roundToZero(layer._properties.y * dpr)}px)"
-		css.push "scale(#{roundToZero(layer._properties.scaleX * layer._properties.scale)},	#{roundToZero(layer._properties.scaleY * layer._properties.scale)})"
+		css.push "scale(#{roundToZero(layer._properties.scaleX * layer._properties.scale * dpr)},	#{roundToZero(layer._properties.scaleY * layer._properties.scale * dpr)})"
 		css.push "skew(#{roundToZero(layer._properties.skew)}deg,#{roundToZero(layer._properties.skew)}deg)"
 		css.push "rotate(#{roundToZero(layer._properties.rotationZ)}deg)"


Create left pane before splitting (close #115)

--- lib/show-todo.coffee
+++ lib/show-todo.coffee
@@ -99,10 +99,13 @@

     return if @destroyPaneItem()

-    if direction is 'down'
-      prevPane.splitDown() if prevPane.parent.orientation isnt 'vertical'
-    else if direction is 'up'
-      prevPane.splitUp() if prevPane.parent.orientation isnt 'vertical'
+    switch direction
+      when 'down'
+        prevPane.splitDown() if prevPane.parent.orientation isnt 'vertical'
+      when 'up'
+        prevPane.splitUp() if prevPane.parent.orientation isnt 'vertical'
+      when 'left'
+        prevPane.splitLeft() if prevPane.parent.orientation isnt 'horizontal'

     atom.workspace.open(uri, split: direction).then (@showTodoView) =>
       prevPane.activate()


forward path to router stub

forward path to router stub

--- tutor/specs/components/helpers/enzyme-context.coffee
+++ tutor/specs/components/helpers/enzyme-context.coffee
@@ -19,7 +19,7 @@
   build: (options = {}) ->
     merge({
       context:
-        router: new TestRouter()
+        router: new TestRouter(options.pathname or '/')
         broadcasts: { location: (cb) -> cb(pathname: (options.pathname or '/')) }
       childContextTypes:
         broadcasts: React.PropTypes.object


Fix #161 new params for getProfile

Fix #161 new params for getProfile

--- src/bot.coffee
+++ src/bot.coffee
@@ -615,9 +615,9 @@
       if typeof arguments[0] == 'function'
         callback = arguments[0]
       else if typeof arguments[0] == 'string'
-        rq.userid = arguments[0]
+        rq.profileid = arguments[0]
     else if arguments.length == 2
-      rq.userid = arguments[0]
+      rq.profileid = arguments[0]
       callback  = arguments[1]
     @_send rq, callback


Destroy root directory on deactivate

Destroy root directory on deactivate

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -66,6 +66,7 @@
     width: @width()

   deactivate: ->
+    @root?.directory.destroy()
     @remove()

   handleEvents: ->


Add inline sort config to @disposable

Add inline sort config to @disposable

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -129,7 +129,7 @@
       @updateRoot() if atom.config.get('tree-view.hideIgnoredNames')
     @disposables.add atom.config.onDidChange 'tree-view.showOnRightSide', ({newValue}) =>
       @onSideToggled(newValue)
-    @subscribe atom.config.observe 'tree-view.sortFoldersInline', callNow: false, =>
+    @disposables.add atom.config.onDidChange 'tree-view.sortFoldersInline', =>
       @updateRoot()

   toggle: ->


Fix bluebird 3.0 regression

Fix bluebird 3.0 regression

--- lib/urlLoader.coffee
+++ lib/urlLoader.coffee
@@ -5,7 +5,7 @@
 class UrlLoader extends events.EventEmitter
     constructor: (@url, options = {}) ->
         request = options.request || UrlLoader.request.defaults(options.requestDefaults || { json: true, method: "GET" })
-        @requestAsync = Promise.promisify(request).bind(request)
+        @requestAsync = Promise.promisify(request, multiArgs: true, context: request)
         @ignoreEtag = options.ignoreEtag
         @ignoreLastModified = options.ignoreLastModified
         @allowedResultCodes = options.allowedResultCodes || [ 200 ]


add openInVim command and make tryit.coffee buffer exception for lock

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -15,6 +15,8 @@
 CountManager = require './count-manager'
 MarkManager = require './mark-manager'
 ModeManager = require './mode-manager'
+
+path = require 'path'

 module.exports =
 class VimState
@@ -130,6 +132,7 @@
       'generate-introspection-report': => @generateIntrospectionReport()
       'jump-to-related': => @jumpToRelated()
       'report-key-binding': => @reportKeyBinding()
+      'open-in-vim': => @openInVim()

     # InsertMode
     # -------------------------
@@ -276,6 +279,9 @@
     for selection in @editor.getSelections()
       selection.setBufferRange(selection.getBufferRange(), {reversed})

+  # Developper helpeing,
+  # [FIXME] clean up needed and make it available only in dev-mode.
+  # -------------------------
   generateIntrospectionReport: ->
     excludeProperties = [
       'findClass'
@@ -345,6 +351,13 @@
     {getKeyBindingInfo} = require './introspection'
     console.log getKeyBindingInfo(klass)

+  openInVim: ->
+    {BufferedProcess} = require 'atom'
+    {row} = @editor.getCursorBufferPosition()
+    new BufferedProcess
+      command: "/Applications/MacVim.app/Contents/MacOS/mvim"
+      args: [@editor.getPath(), "+#{row+1}"]
+
   # Search History
   # -------------------------
   # Public: Append a search to the search history.
@@ -429,6 +442,9 @@

   ensureCursorIsWithinLine: (cursor) =>
     return if @operationStack.isProcessing() or (not @isNormalMode())
+    # [FIXME] I'm developping in buffer 'tryit.coffee'.
+    # So disable auto-cursor modification especially for this bufffer temporarily.
+    return if path.basename(@editor.getPath()) is 'tryit.coffee'

     {goalColumn} = cursor
     if cursor.isAtEndOfLine() and not cursor.isAtBeginningOfLine()


add --version & --help option

add --version & --help option

--- src/cli.coffee
+++ src/cli.coffee
@@ -1,2 +1,16 @@
+fs = require 'fs'
+path = require 'path'
+commander = require 'commander-b'
+
+getVersion = ->
+  packageJsonFile = path.join __dirname, '../package.json'
+  data = fs.readFileSync packageJsonFile, encoding: 'utf-8'
+  packageJson = JSON.parse data
+  packageJson.version
+
 module.exports = ->
-  console.log 'Hello, MyMyJCB!'
+  program = commander()
+  program.version getVersion()
+  program.action ->
+    console.log 'Hello, MyMyJCB!'
+  program.execute()


Initial partial work on xpath_range selector for sentence-based selection

--- annotran/static/scripts/annotator/plugin/sentenceselection.coffee
+++ annotran/static/scripts/annotator/plugin/sentenceselection.coffee
@@ -1,5 +1,7 @@
 Annotator = require('annotator')
 $ = Annotator.$
+
+RangeAnchor = require('../../../../../../h/h/static/scripts/annotator/anchoring/types').RangeAnchor

 # This plugin implements the UI code for selecting sentences by clicking
@@ -28,20 +30,21 @@
   # Returns nothing.
   makeSentenceSelection: (event = {}) =>
     # Get the currently selected ranges.
-    range = document.createRange()
-    range.selectNodeContents(event.target)

     desiredText = event.target.innerText || event.target.textContent
     desiredText = desiredText.split('.')[0]

-    currentText = range.toString()
+    selector = new Selector_Class(event.target, event.target, 0, desiredText.length)

-    if desiredText != currentText
-      #range.collapse (true)
-      #range.setEnd(event.target, 5)
-      alert(range.endOffset)
-      alert(range.toString())
-
+    xpath_range = RangeAnchor.fromSelector(event.target, selector)

     window.getSelection().removeAllRanges()
-    window.getSelection().addRange(range)+    window.getSelection().addRange(xpath_range)
+
+class Selector_Class
+  constructor: (startC, endC, startO, endO) ->
+    @startContainer = -> startC
+    @endContainer = -> endC
+    @startOffset = -> startO
+    @endOffset = -> endO
+


Removed unnecessary console.log statement.

--- src/glp.coffee
+++ src/glp.coffee
@@ -42,7 +42,6 @@
       @changedFiles.push path

   reload: ->
-    console.log 'Reloading', @changedFiles
     @liveReload?.changed
       body:
         files: @changedFiles


Only warn if there's actually an error unlinking

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -134,7 +134,7 @@
     deleteAvatar = (child) =>
       avatarPath = path.join(@getCachePath(), child)
       fs.unlink avatarPath, (error) ->
-        if error.code isnt 'ENOENT' # Ignore cache paths that don't exist
+        if error and error.code isnt 'ENOENT' # Ignore cache paths that don't exist
           console.warn("Error deleting avatar (#{error.code}): #{avatarPath}")

     fs.readdir @getCachePath(), (error, _files) =>


Fix bug in QueryParams that renders `include` params for query even if necessary includes is zero

--- src/query_params.coffee
+++ src/query_params.coffee
@@ -35,13 +35,15 @@
     queryParams = {}

     if @queryParams()['include']?
-      queryParams['include'] =
+      includes =
         ActiveResource::Collection.build(@queryParams()['include']).inject [], (out, i) ->
           if _.isObject(i)
             _.each _.keys(i), (i2) ->
               if i2 == reflection.name
                 out.push _.flatten([i[i2]])...
           out
+
+      queryParams['include'] = includes unless includes.length == 0

     if !reflection.polymorphic?() && @queryParams()['fields']?[reflection.klass().queryName]?
       queryParams['fields'] = _.pick(@queryParams()['fields'], reflection.klass().queryName)


Use event.key for numpad.

Use event.key for numpad.

See this comment:
https://github.com/philc/vimium/pull/2626#issuecomment-326553282.

--- lib/keyboard_utils.coffee
+++ lib/keyboard_utils.coffee
@@ -18,12 +18,13 @@
   getKeyChar: (event) ->
     unless Settings.get "ignoreKeyboardLayout"
       key = event.key
+    else if event.code[...6] == "Numpad"
+      # We cannot correctly emulate the numpad, so fall back to event.key; see #2626.
+      key = event.key
     else
       # The logic here is from the vim-like-key-notation project (https://github.com/lydell/vim-like-key-notation).
       key = event.code
-      # Strip some standard prefixes.
       key = key[3..] if key[...3] == "Key"
-      key = key[6..] if key[...6] == "Numpad"
       # Translate some special keys to event.key-like strings.
       if @enUsTranslations[key]
         key = if event.shift then @enUsTranslations[key][1] else @enUsTranslations[key][0]


Add help for adapter command.

Add help for adapter command.

--- src/scripts/ping.coffee
+++ src/scripts/ping.coffee
@@ -3,6 +3,7 @@
 #
 # Commands:
 #   hubot ping - Reply with pong
+#   hubot adapter - Reply with the adapter
 #   hubot echo <text> - Reply back with <text>
 #   hubot time - Reply with current time
 #   hubot die - End hubot process


Only show used categories in ss view

Only show used categories in ss view

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -122,6 +122,7 @@
         Groups = []     # [] Array over categories :: {category: Category, metrics[]: Array of Metric}
         Cols = []       # [] Array over metrics :: {category: Category, metric: Metric} (flattened version of Groups)
         Rows = []       # [i] :: Row (-1 => foot)
+        Cats = {}       # Set of used categories
         Order = []      # Permutation Array of Row in display order
         Expanded = undefined # Info

@@ -138,6 +139,7 @@
             return

           add: (c, d) ->
+            Cats[c] = true
             if v = this[c]
               if Array.isArray(v)
                 n = v.push(d) - 1
@@ -308,7 +310,10 @@
           Cols = []
           if slot
             slot = undefined
-            cats = Object.keys(volume.metrics).sort(byNumber).map((i) -> constants.category[i])
+            cats = Object.keys(volume.metrics)
+            unless Editing
+              cats = cats.filter((i) -> Cats[i])
+            cats = cats.sort(byNumber).map((i) -> constants.category[i])
             if Top
               cats.unshift(pseudoCategory.asset)
             else
@@ -416,11 +421,9 @@

         # Call all populate functions
         populate = ->
-          bySlot = Key == pseudoCategory.slot
-          populateCols(bySlot)
           foot = Rows[-1]
           Rows = []
-          if bySlot
+          if bySlot = Key == pseudoCategory.slot
             populateSlots()
           else
             populateRecords()
@@ -429,6 +432,7 @@
           $(TBody).empty()
           Expanded = undefined
           Rows[-1] = foot if foot
+          populateCols(bySlot)
           generate()
           return

@@ -474,12 +478,7 @@
             when 'release'
               cn = constants.release[v]
               cell.className = cn + ' release icon hint-release-' + cn
-              #if slot.top
-              #  cell.classList.add('null')
               v = ''
-            when 'date'
-              #if slot.top
-              #  cell.classList.add('null')
             when 'age'
               v = display.formatAge(v)
             else


Make sure NIH enrollment doesn't break ss editing

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -1353,17 +1353,18 @@
             $scope.pivot.load(state.pivot)
             return

-        volume.state['NIH Inclusion Enrollment Report'] ?=
-          key: constants.categoryName.participant.id
-          pivot:
-            cols: ["participant ethnicity", "participant gender"]
-            rows: ["participant race"]
-            rendererName: "Table"
-            aggregatorName: "Count"
-          filter: [ # these aren't actually used (yet)
-            {c: "slot", m: "top", op: "false"},
-            {c: "slot", m: "date", op: "ge"},
-            {c: "slot", m: "date", op: "le", v: $filter('date')(new Date(), 'yyyy-MM-dd')}]
+        if volume.state
+          volume.state['NIH Inclusion Enrollment Report'] ?=
+            key: constants.categoryName.participant.id
+            pivot:
+              cols: ["participant ethnicity", "participant gender"]
+              rows: ["participant race"]
+              rendererName: "Table"
+              aggregatorName: "Count"
+            filter: [ # these aren't actually used (yet)
+              {c: "slot", m: "top", op: "false"},
+              {c: "slot", m: "date", op: "ge"},
+              {c: "slot", m: "date", op: "le", v: $filter('date')(new Date(), 'yyyy-MM-dd')}]

         return
     ]


Label change 'full' --> 40min

Label change 'full' --> 40min

--- lib/model/proposal.coffee
+++ lib/model/proposal.coffee
@@ -24,7 +24,7 @@
     @where(notDeleted, options)

   @types: ->
-    full: 'Full'
+    full: '40min talk'
     ignite: 'Ignite'
     ossil: 'Open Source in Israel'
     # workshop: 'Workshop'


Change the description of `Open Default Panes on Startup`

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -356,8 +356,9 @@
           openDefaultPanesOnStartUp:
             title: 'Open Default Panes on Startup'
             description: 'If enabled, opens panes specified above on startup.
-                          If there are the panes restored from a previous window state,
-                          the pane would still stay there.'
+                          Note a layout deserialized from a previous window state
+                          would be modified by that, i.e.: disable this if you want
+                          to keep the deserialized layout.'
             type: 'boolean'
             default: true
             order: 10


Add atom prefix to pasteboard references

--- spec/github-file-spec.coffee
+++ spec/github-file-spec.coffee
@@ -163,12 +163,12 @@
       describe "when text is selected", ->
         it "copies the URL to the pasteboard with the selection range in the hash", ->
           githubFile.copyUrl([[0, 0], [1, 1]])
-          expect(pasteboard.read()[0]).toBe 'https://github.com/some-user/some-repo/blob/master/some-dir/some-file.md#L1-L2'
+          expect(atom.pasteboard.read()[0]).toBe 'https://github.com/some-user/some-repo/blob/master/some-dir/some-file.md#L1-L2'

       describe "when no text is selected", ->
         it "copies the URL to the pasteboard with the cursor location in the hash", ->
           githubFile.copyUrl([[2, 1], [2, 1]])
-          expect(pasteboard.read()[0]).toBe 'https://github.com/some-user/some-repo/blob/master/some-dir/some-file.md#L3'
+          expect(atom.pasteboard.read()[0]).toBe 'https://github.com/some-user/some-repo/blob/master/some-dir/some-file.md#L3'

   describe "githubRepoUrl", ->
     githubFile = null


Index: removed typekit

Index: removed typekit

--- client/landing/server/index.parser.coffee
+++ client/landing/server/index.parser.coffee
@@ -1,5 +1,7 @@
 module.exports = (siteName) ->

+    # <script src="//use.typekit.net/rbd0tum.js"></script>
+    # <script>try{Typekit.load();}catch(e){}</script>
   """
   <!doctype html>
   <html>
@@ -7,8 +9,6 @@
     <title>Koding</title>
     <link rel="stylesheet" type="text/css" href="/a/site.#{siteName}/css/kd.css">
     <link rel="stylesheet" type="text/css" href="/a/site.#{siteName}/css/main.css">
-    <script src="//use.typekit.net/rbd0tum.js"></script>
-    <script>try{Typekit.load();}catch(e){}</script>
   </head>
   <body class='home'>
     <script src="/a/site.#{siteName}/js/pistachio.js"></script>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -8,8 +8,8 @@
   spriteUrl = "http://images.pixie.strd6.com/avatars/1/thumb.png"

   callback = (s) ->
-    sprite = Sprite.load
-
+    sprite = Sprite.load spriteUrl
+
   sprite = Sprite.load


Add test coverage for save before close

Add test coverage for save before close

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -5173,12 +5173,16 @@
       expect(editor.getSelectedBufferRange()).toEqual [[13, 0], [14, 2]]

   describe ".shouldPromptToSave()", ->
-    it "returns false when an edit session's buffer is in use by more than one session", ->
+    it "returns true when buffer changed", ->
       jasmine.unspy(editor, 'shouldPromptToSave')
       expect(editor.shouldPromptToSave()).toBeFalsy()
       buffer.setText('changed')
       expect(editor.shouldPromptToSave()).toBeTruthy()

+    it "returns false when an edit session's buffer is in use by more than one session", ->
+      jasmine.unspy(editor, 'shouldPromptToSave')
+      buffer.setText('changed')
+
       editor2 = null
       waitsForPromise ->
         atom.workspace.getActivePane().splitRight()
@@ -5188,6 +5192,16 @@
         expect(editor.shouldPromptToSave()).toBeFalsy()
         editor2.destroy()
         expect(editor.shouldPromptToSave()).toBeTruthy()
+
+    it "returns false when close of a window requested and edit session opened inside project", ->
+      jasmine.unspy(editor, 'shouldPromptToSave')
+      buffer.setText('changed')
+      expect(editor.shouldPromptToSave(windowCloseRequested: true, projectHasPaths: true)).toBeFalsy()
+
+    it "returns true when close of a window requested and edit session opened without project", ->
+      jasmine.unspy(editor, 'shouldPromptToSave')
+      buffer.setText('changed')
+      expect(editor.shouldPromptToSave(windowCloseRequested: true, projectHasPaths: false)).toBeTruthy()

   describe "when the editor contains surrogate pair characters", ->
     it "correctly backspaces over them", ->


Adding command to build tm-4-0-json-data

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -19,6 +19,26 @@
      request options, (error, response, body)->
       #console.log body.json_Pretty
       res.reply 'The current HEAD of TM Design is: ' + body.json_Parse().object.sha
+
+
+   robot.respond 'json',(res) ->
+     res.reply "About to trigger build of tm-4-0-json-data repository"
+     options =
+       url    : 'https://ci.appveyor.com/api/builds',
+       headers:
+         'Authorization': 'Bearer ' + process.env.auth_key
+         'Content-Type': 'application/x-www-form-urlencoded'
+       body   : 'accountName=DinisCruz&projectSlug=tm-4-0-json-data&branch=master'
+
+
+     res.reply options.json_Pretty()
+
+
+     request.post options, (error, response, data)->
+       res.reply error
+       res.reply data.json_Parse().json_Pretty()
+
+

    robot.hear /badger/i, (res) ->
      res.send "Badgers? we like badgers here"


Minor enhancements in the bilateral

Minor enhancements in the bilateral

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -114,11 +114,11 @@
             mangled += "/#{nsp}" if _.isString nsp = @nsp
             assert value; return @socket.on mangled, =>
                 logger.info uplinking, reference, nsp
-                return value i(arguments)..., (parameters...) =>
+                return value i(arguments)..., (params...) =>
                     id = @socket.sacks = (@socket.sacks ?= 0) + 1
-                    packet = type: "ack", name: mangled, ack: "data"
-                    _.extend packet, ackId: id, args: o(parameters)
-                    assert packet.ackId; @socket.packet packet
+                    ack = type: "ack", name: mangled, ack: "data"
+                    assert _.extend ack, ackId: id, args: o(params)
+                    assert ack.ackId > 0; @socket.packet ack

     # This is a complementary part of the bilateral implementation.
     # It is invoked to produce a server side agent that is aware of


read convo on submit

read convo on submit

--- app/assets/javascripts/app/controllers/messages/article.js.coffee
+++ app/assets/javascripts/app/controllers/messages/article.js.coffee
@@ -55,6 +55,7 @@
     if message.body
       @input.text('')
       message.save()
+      @record.open()

 class App.Messages.Article extends Spine.Controller
   elements:


updated the articles controller

updated the articles controller

--- app/assets/javascripts/ems/controllers/articles_controller.coffee
+++ app/assets/javascripts/ems/controllers/articles_controller.coffee
@@ -23,18 +23,26 @@
     # give the article the correct category
     # @todo we need to be able to retrieve the category by its slug from the server rather than going round the houses
     # like its currently done
-    Ems.Category.load (err, cats) =>
+    @set 'category', Ems.Category.find parseInt(params.categoryId), (err) =>
+      @redirect '/404' if err?.status is 404
       throw err if err
-      # indicator to let us know if we got a valid category slug
-      found = false
-      # iterate through returned category and check if the result returned includes one with our slug
-      for cat in cats
-        if(cat.get('slug') == params.category)
-          found = true
-          @get('article').set('category', cat)
-          @set 'channels', @get 'article.category.channels'
-      # WHOOPS, we dont seems to have a valid slug or atleast there is no category on the server with this slug
-      @redirect '/404' unless found
+
+      @set 'channels', new Batman.Set
+      @observe 'category', (category, oldCategory)=>
+        @set 'article.category', newVal
+        @set 'channels', @get 'category.channels'
+
+
+      # # indicator to let us know if we got a valid category slug
+      # found = false
+      # # iterate through returned category and check if the result returned includes one with our slug
+      # for cat in cats
+      #   if(cat.get('slug') == params.category)
+      #     found = true
+      #     @get('article').set('category', cat)
+      #     @set 'channels', @get 'article.category.channels'
+      # # WHOOPS, we dont seems to have a valid slug or atleast there is no category on the server with this slug
+      # @redirect '/404' unless found

   # Lets go and persist the new article on the server
   create: (params) ->


activity: simplify callback function in `ActivityInputWidget::update`

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -244,7 +244,7 @@
       #   targetSelf : 1

-  update: (data, callback) ->
+  update: (data, callback = noop) ->

     {appManager} = KD.singletons
     {channelId}  = @getOptions()
@@ -258,9 +258,10 @@
       activity
     }, (err, activity) =>

-      KD.showError err
-      @reset()  unless err
-      callback? err
+      return KD.showError err  if err
+
+      @reset()
+      callback()

       KD.mixpanel "Status update edit, success"


Redirectly to game if there is already an active character

--- client/components/login/login.controller.coffee
+++ client/components/login/login.controller.coffee
@@ -14,7 +14,11 @@
         password: $scope.user.password

       .then ->
-        $location.path '/characters'
+        Auth.getCurrentCharacter().$promise
+        .then (data) ->
+          $location.path '/game'
+        .catch (err) ->
+          $location.path '/characters'

       .catch (err) ->
         $scope.errors.other = err.message


default to 0 if null

default to 0 if null

--- client/lanes/components/shared/NumberInput.cjsx
+++ client/lanes/components/shared/NumberInput.cjsx
@@ -4,11 +4,13 @@
         Lanes.Components.Form.InputFieldMixin
     ]
     handleNumberChange: (n) ->
-        @handleChange(target: value: n)
+        value = if _.isNull(n) then 0 else n
+        @handleChange(target: {value})

     renderInputField: (props, handlers, label) ->
         props.format ||= '#,###.00'
         props = _.omit(props, 'label')
+
         <Lanes.Vendor.ReactWidgets.NumberPicker
             ref="select"
             className={@props.className}


form workflow: code cleanup

form workflow: code cleanup

--- client/app/CommonViews/formworkflow.coffee
+++ client/app/CommonViews/formworkflow.coffee
@@ -2,19 +2,21 @@

   constructor: (options = {}, data) ->
     super options, data
-    @forms = {}
+
     @collector = new Collector
-    @collector.on 'Pending', => @nextForm()
+    @collector.on 'Pending', @bound 'nextForm'
     @forwardEvent @collector, 'DataCollected'
-    @providers = {}
-
-  enter: -> @ready => @nextForm()
-
-  requireData: (fields) ->
+
+    @forms      = {}
+    @providers  = {}
+
+  enter: -> @ready @bound 'nextForm'
+
+  requireData: (fields, mode = 'all') ->
     gate =
       if fields.isGate
       then fields
-      else @all fields...
+      else @[mode] fields...

     @collector.addRequirement gate

@@ -24,8 +26,7 @@

   getData: -> @collector.data

-  isSatisfied: ->
-    @collector.gate.isSatisfied()
+  isSatisfied: -> @collector.gate.isSatisfied()

   collectData: (data) ->
     @collector.collectData data
@@ -37,8 +38,8 @@

     return this

-  provideData: (form, provides) ->
-    for field in provides
+  provideData: (form, providers) ->
+    for field in providers
       @providers[field] ?= []
       @providers[field].push(
         if 'string' is typeof form
@@ -46,20 +47,27 @@
         else form
       )

+    return this
+
   nextForm: ->
     requirement = @nextRequirement()
     return  unless requirement?
+
     provider = @nextProvider requirement
+    return  unless provider?
+
     @showForm provider

-  nextRequirement: ->
-    @collector.nextRequirement()
+    return provider
+
+  nextRequirement: -> @collector.nextRequirement()

   nextProvider: (key, from) ->
     providers = @providers[key]
     providers.i = from ? providers.i ? 0
     provider = providers[providers.i++]
-    return provider  if provider
+    return provider  if provider?
+
     try @nextProvider key, 0

   addForm: (formName, form, provides = []) ->
@@ -174,36 +182,36 @@
       else
         (key for own key of @fields when not (key of @children))

-    nextFieldName: ->
-      (Object.keys @fields)[@indexes.fields]
-
     nextNode: ->
-      field = @ordered[@index]
-
-      unless field?
+      node = @ordered[@index]
+
+      unless node? #cycle
         @index = 0

-        return @nextNode()
-
-      if field.isGate
-        if field.isSatisfied()
+        return @nextNode()
+
+      if node.isGate #fork
+
+        if node.isSatisfied() #skip
           @index++

           return @nextNode()
-        else
-
-          return field.nextNode()
-
-      @index++
-
-      return field
+
+        else #propagate
+          return node.nextNode()
+
+      @index++ #continue
+
+      return node

     addChild: (child) ->
       @children[child] = child
+
       return this

     removeChild: (child) ->
       delete @children[child]
+
       return this

     addField: (field) ->
@@ -217,7 +225,7 @@

         @addChild field

-        field.on 'status', (isSatisfied) =>
+        field.on 'status', (isSatisfied) ->
           if isSatisfied
           then satisfier.satisfy()
           else satisfier.cancel()
@@ -234,8 +242,7 @@
       return this

     satisfy: (field) ->
-      if (satisfier = @fields[field])?
-        satisfier.satisfy()
+      satisfier.satisfy()  if (satisfier = @fields[field])?

       child.satisfy field  for own _, child of @children

@@ -259,11 +266,11 @@
     compliment: (value) -> value

     isSatisfied: ->
-      for collection in [@fields, @children]
-        for own _, field of collection
-          return @kill()  unless @compliment field.isSatisfied()
-
-      return !@kill()
+      for category in [@fields, @children]
+        for own _, node of category when not @compliment node.isSatisfied()
+          return @kill()
+
+      return not @kill()

     toString: -> "gate-#{@id}"


Allow commands to be any object

Allow commands to be any object

--- client/lanes/screens/CommonComponents.cjsx
+++ client/lanes/screens/CommonComponents.cjsx
@@ -1,7 +1,7 @@
 class Lanes.Screens.CommonComponents extends Lanes.React.Component

     propTypes:
-        commands:        React.PropTypes.instanceOf(Lanes.Screens.Commands).isRequired
+        commands:        React.PropTypes.object.isRequired
         errors:          React.PropTypes.bool
         networkActivity: React.PropTypes.bool
         toolbarProps:    React.PropTypes.object


Fix pivoting around aTime

Fix pivoting around aTime

--- client/templates/results/results.js.coffee
+++ client/templates/results/results.js.coffee
@@ -129,8 +129,12 @@
     valueAsMoment = moment.utc(value, "YYYY/MM/DDTHH:mm:ss.SSS")
     sTimeValueAsMoment = valueAsMoment.clone().subtract(spread, 'milliseconds')
     eTimeValueAsMoment = valueAsMoment.clone().add(spread, 'milliseconds')
+    sTimeValueAsString = sTimeValueAsMoment.format("YYYY/MM/DDTHH:mm:ss.SSS")
+    eTimeValueAsString = eTimeValueAsMoment.format("YYYY/MM/DDTHH:mm:ss.SSS")
     query = share.Queries.findOne(template.data._id)
-    share.Pivoting.execute(query, "aTime", sTimeValueAsMoment.format("YYYY/MM/DDTHH:mm:ss.SSS") + "-" + eTimeValueAsMoment.format("YYYY/MM/DDTHH:mm:ss.SSS"))
+    share.Pivoting.execute(query, "sTime", sTimeValueAsString)
+    share.Pivoting.execute(query, "eTime", eTimeValueAsString)
+    share.Pivoting.execute(query, "aTime", sTimeValueAsString + "-" + eTimeValueAsString)
   "click .new-query": grab (event, template) ->
     $target = $(event.currentTarget)
     _id = $target.attr("data-id")


RemixerView.spec: events block

RemixerView.spec: events block

--- coffee/test/views/remixer_view.spec.coffee
+++ coffee/test/views/remixer_view.spec.coffee
@@ -41,25 +41,27 @@
     viewOptions: options
     checkDOM: (cEl, pEl) -> cEl.parentNode.parentNode is pEl

-  it 'should trigger `Remixer:Duplicate` on clicking btn', ->
-    view = new RemixerView options
-    spy = new EventSpy view, 'Remixer:Duplicate'
+  describe 'events', ->

-    view.render()
-    expect(spy.triggered).toBe false
-    view.toolbarView.$('button#duplicate').trigger 'click'
-    expect(spy.triggered).toBe true
-    expect(spy.arguments[0]).toEqual [view]
+    it 'should trigger `Remixer:Duplicate` on clicking btn', ->
+      view = new RemixerView options
+      spy = new EventSpy view, 'Remixer:Duplicate'

-  it 'should trigger `Remixer:Delete` on clicking btn', ->
-    view = new RemixerView options
-    spy = new EventSpy view, 'Remixer:Delete'
+      view.render()
+      expect(spy.triggered).toBe false
+      view.toolbarView.$('button#duplicate').trigger 'click'
+      expect(spy.triggered).toBe true
+      expect(spy.arguments[0]).toEqual [view]

-    view.render()
-    expect(spy.triggered).toBe false
-    view.toolbarView.$('button#delete').trigger 'click'
-    expect(spy.triggered).toBe true
-    expect(spy.arguments[0]).toEqual [view]
+    it 'should trigger `Remixer:Delete` on clicking btn', ->
+      view = new RemixerView options
+      spy = new EventSpy view, 'Remixer:Delete'
+
+      view.render()
+      expect(spy.triggered).toBe false
+      view.toolbarView.$('button#delete').trigger 'click'
+      expect(spy.triggered).toBe true
+      expect(spy.arguments[0]).toEqual [view]

   it 'should look good', ->
     # setup DOM


payment: JPaymentPlan#fetchPlans API improvments

Change the API to use named parameters (options object) rather than positional parameters.  This is more intuitive, and lets us write code that is more DRY.

--- workers/social/lib/social/models/payment/plan.coffee
+++ workers/social/lib/social/models/payment/plan.coffee
@@ -1,10 +1,11 @@
 jraphical = require 'jraphical'
-recurly   = require 'koding-payment'
+module.exports = class JPaymentPlan extends jraphical.Module

-forceRefresh  = yes
-forceInterval = 60
+  force =
+    forceRefresh  : yes
+    forceInterval : 60

-module.exports = class JPaymentPlan extends jraphical.Module
+  recurly   = require 'koding-payment'

   {secure, dash}       = require 'bongo'
   {difference, extend} = require 'underscore'
@@ -39,17 +40,18 @@
   @fetchAccountDetails = secure ({connection:{delegate}}, callback)->
     recurly.fetchAccountDetailsByAccountCode (JPayment.userCodeOf delegate), callback

-  @fetchPlans = secure (client, filter..., callback)->
-    [prefix, category, item] = filter
-    selector = {}
-    selector['product.prefix']   = prefix    if prefix
-    selector['product.category'] = category  if category
-    selector['product.item']     = item      if item
+  @fetchPlans = secure (client, options, callback)->

-    JPayment.invalidateCacheAndLoad this, selector, {forceRefresh, forceInterval}, callback
+    selector = (Object.keys options)
+      .reduce( (acc, key) ->
+        acc["product.#{key}"] = options[key]
+        acc
+      , {})
+
+    JPayment.invalidateCacheAndLoad this, selector, force, callback

   @getPlanWithCode = (code, callback)->
-    JPaymentPlan.one { code }, callback
+    @one { code }, callback

   getToken: secure (client, data, callback)->
     JPaymentToken.createToken client, planCode: @code, callback


remove useless trace in init

remove useless trace in init

--- server/lib/init.coffee
+++ server/lib/init.coffee
@@ -53,11 +53,9 @@
             db.get binary, (err, doc) =>
                 if not err and doc
                     db.remove doc._id, doc._rev, (err, doc) =>
-                        log.error 'ERROR 1' if err?
                         log.error err if err
                         cb()
                 else
-                    log.error 'ERROR 2' if err?
                     log.error err if err
                     cb()
         , callback
@@ -69,7 +67,7 @@
             return cb(err) if err? or apps.length is 0
             async.forEachSeries apps, (app, cb) ->
                 # Check if access exists
-                db.view 'access/byApp', key:app._id, (err, accesses) ->
+                db.view 'access/byApp', key:app.id, (err, accesses) ->
                     return cb(err) if err? or accesses.length is 0
                     if accesses?.length is 0
                         # Create it if necessary
@@ -82,7 +80,6 @@
                             # Remove access information
                             # from application/device document
                             db.save app, (err, doc) ->
-                                log.error 'ERROR 3' if err?
                                 log.error err if err?
                                 cb()
                     else
@@ -91,10 +88,8 @@

     # Add access for all applications and devices
     addAccess 'application', (err) ->
-        log.error 'ERROR 4' if err?
         log.error err if err?
         addAccess 'device', (err) ->
-            log.error 'ERROR 5' if err?
             log.error err if err?
             callback()


Fix: ModelFieldView missing placeholder when value set to null in a valueChosen callback.

--- lib/ST/ModelFieldView.coffee
+++ lib/ST/ModelFieldView.coffee
@@ -99,7 +99,7 @@
     newValue = ST.trim newValue

     if @_loaded && ST.trim(@_inputElement.val()) != newValue
-      if newValue == '' && !@_focused
+      if newValue == '' && (!@_focused || @_hiding)
         @_inputElement.css 'color', 'gray'
         @_inputElement.val @_placeholder
       else


server self.onStop was never being called

--- lib/datatables.server.coffee
+++ lib/datatables.server.coffee
@@ -62,6 +62,6 @@
           changedAt: -> updateCount countInitialized
           removedAt: -> updateCount countInitialized
         countInitialized = true
-        self.onStop ->
-          handle.stop()
-          countHandle.stop()+      self.onStop ->
+        handle.stop()
+        countHandle.stop()


Positioning logic is now completely handled on server

--- app/assets/javascripts/controllers/overlays/authors_overlay_controller.js.coffee
+++ app/assets/javascripts/controllers/overlays/authors_overlay_controller.js.coffee
@@ -15,17 +15,6 @@

   shiftAuthorPositions: (author, newPosition)->
     oldPosition = author.get 'position'
-
-    if oldPosition < newPosition
-      newPosition = newPosition - 1
-      relevantAuthors = @get('authors').filter (a)->
-        a != author && (a.get('position') > oldPosition) && (a.get('position') <= newPosition)
-      relevantAuthors.invoke 'decrementProperty', 'position'
-    else
-      relevantAuthors = @get('authors').filter (a)->
-        a != author && (a.get('position') < oldPosition) && (a.get('position') >= newPosition)
-      relevantAuthors.invoke 'incrementProperty', 'position'
-
     author.set('position', newPosition)
     author.save()


Fix wrong arrow in send mobile view controller

--- app/src/controllers/wallet/send/wallet_send_mobile_dialog_view_controller.coffee
+++ app/src/controllers/wallet/send/wallet_send_mobile_dialog_view_controller.coffee
@@ -31,7 +31,7 @@
     dialog = new WalletSendMethodDialogViewController(transaction: @params.transaction)
     @getDialog().push dialog

-  _onComplete: (pincode, error) =>
+  _onComplete: (pincode, error) ->
     if error?
       @_request = null
       @dismiss =>


Added ReloadDB function (for after deleting the db file with running process).

--- src/pie.coffee
+++ src/pie.coffee
@@ -60,8 +60,7 @@
   # slurp up the Piefile (can override the default tasks if it wants)
   evaluatePiefile (err) ->
     return cb(err) if err
-    nStoreAlreadyFiredCallback = false
-    _db = nStore.new(".pie.db", ((err) -> if !nStoreAlreadyFiredCallback then nStoreAlreadyFiredCallback = true; cb(err)))
+    reloadDB(cb)

 evaluatePiefile = (cb) ->
   fs.readFile "Piefile", (err, code) ->
@@ -97,8 +96,15 @@
   w = new Watcher(paths, options, eventHandler)
   w.start (err) -> cb(err, w)

+reloadDB = (cb) ->
+  nStoreAlreadyFiredCallback = false
+  _db = nStore.new ".pie.db", (err) ->
+    if !nStoreAlreadyFiredCallback
+      nStoreAlreadyFiredCallback = true
+      cb(err)
+
 # exports, available in global namespace of Piefile
-_.extend(global, { option: option, task: task, invoke: invoke, map: map, compilers: compilers, watch: watch })
+_.extend(global, { option: option, task: task, invoke: invoke, map: map, compilers: compilers, watch: watch, reloadDB: reloadDB })

 getMtime = (file, cb) ->
   fs.stat file, (err, stats) ->


upload sound recording once 'stop' is raised on audioService.recorder

--- www/js/controllers/msg.coffee
+++ www/js/controllers/msg.coffee
@@ -48,7 +48,7 @@
 				# no more listen to those registered events
 				io.socket?.removeAllListeners 'msg'

-	.controller 'ChatCtrl', ($scope, $cordovaClipboard, $log, $ionicScrollDelegate, $location, type, chat, me, collection, resource, $cordovaFileTransfer, $http) ->
+	.controller 'ChatCtrl', ($scope, $cordovaClipboard, $log, $ionicScrollDelegate, $location, type, chat, me, collection, resource, $cordovaFileTransfer, $http, audioService) ->
 		_.extend $scope,
 			type: type
 			chat: chat
@@ -109,6 +109,12 @@
 				$scope.$apply 'collection.models'
 				$ionicScrollDelegate.scrollTop true

+		recorded = ->
+			$scope.putfile [audioService.recorder.file]
+		audioService.recorder.on 'stop', recorded
+		$scope.$on '$destroy', ->
+			audioService.recorder.removeListener 'stop', recorded
+
 	.controller 'msgCtrl', ($scope, resource, $cordovaFileOpener2, $http, fileService, $log) ->
 		fs = fileService.fs


add function creating to detect if new msg is creating

--- www/js/controllers/msg.coffee
+++ www/js/controllers/msg.coffee
@@ -60,14 +60,15 @@
 					.then ->
 						$scope.$broadcast('scroll.infiniteScrollComplete')
 				return @
+			creating: ->
+				_.last(collection.models)?.$isNew()
 			addFile: (files) ->
 				if files?.length != 0
 					msg = $scope.addMsg()
 					msg.local = files[0]
 					msg.file = _.extend url: URL.createObjectURL(files[0]), _.pick(files[0], 'name', 'type'), base: files[0].name, ext: path.extname(files[0].name)
+					msg.file_inode = contentType: files[0].type
 					$scope.$apply 'collection.models'
-			addImg: (files) ->
-			addAudio: (files) ->
 			addMsg: ->
 				msg = new resource.Msg type: type, to: chat.jid, body: ''
 				collection.add msg
@@ -97,7 +98,7 @@
 				$ionicScrollDelegate.scrollTop true

 		recorded = ->
-			$scope.putfile [audioService.recorder.file]
+			$scope.addFile [audioService.recorder.file]
 		audioService.recorder.on 'stop', recorded
 		$scope.$on '$destroy', ->
 			audioService.recorder.removeListener 'stop', recorded
@@ -107,7 +108,7 @@

 		msg = $scope.model

-		if msg.file
+		if msg.file and not msg.$isNew()
 			dest = msg.file.org
 			switch true
 				when sails.services.file.isImg(msg.file_inode) or sails.services.file.isVideo(msg.file_inode)


fix layout issue at smaller screen size

fix layout issue at smaller screen size

--- @client/responsive_vars.coffee
+++ @client/responsive_vars.coffee
@@ -45,7 +45,7 @@
   document_width = Math.max(900, w)

   # There will be at least 80px of whitespace on either side of the document
-  gutter = Math.max(80, w / 14)
+  gutter = Math.max(80, w / 11)
   content_width = document_width - 2 * gutter

   whitespace = Math.max(100, w / 10)


The Boyajian+16 paper was initially misnumbered on arXiv. Should be IX, not X.

--- app/pages/about/publications-page.cjsx
+++ app/pages/about/publications-page.cjsx
@@ -147,10 +147,10 @@
     },
     {slug: "zooniverse/planet-hunters"
     publications: [
+      {citation: "Planet Hunters. IX. KIC 8462852 – Where’s the flux?, Boyajian+ 2016."
+      href: "http://mnras.oxfordjournals.org/content/457/4/3988.full.pdf"},
       {citation: "Planet Hunters. VIII. Characterization of 41 Long-Period Exoplanet Candidates from Kepler Archival Data, Wang+ 2015."
       href: "http://arxiv.org/pdf/1512.02559v2.pdf"},
-      {citation: "Planet Hunters. X. KIC 8462852 – Where’s the flux?, Boyajian+ 2015."
-      href: "http://arxiv.org/pdf/1509.03622v1.pdf"},
       {citation: "GALEX J194419.33+491257.0: An unusually active SU UMa-type dwarf nova with a very short orbital period in the Kepler data, Kato & Osaki 2014."
       href: "http://adsabs.harvard.edu/doi/10.1093/pasj/psu025"},
       {citation: "Planet Hunters. VI. An Independent Characterization of KOI-351 and Several Long Period Planet Candidates from the Kepler Archival Data, Schmitt+ 2014."


Intensify enemies and add underwater attacks

--- app/scripts/lazerscripts/stage1.coffee
+++ app/scripts/lazerscripts/stage1.coffee
@@ -17,6 +17,7 @@
         @droneTakeover()
         @swirlAttacks()
         @setScenery('CoastStart')
+        @underWaterAttacks()
       )

     introText: ->
@@ -73,10 +74,21 @@
         @wave('SwirlAttack2', drop: 'lasers')

         @parallel(
+          @repeat(2, @wave('SwirlAttack2', drop: 'lasers'))
           @sequence(
             @wait(2000)
-            @repeat(4, @wave('SwirlAttack', drop: 'lasers'))
+            @repeat(2, @wave('SwirlAttack', drop: 'lasers'))
           )
-          @repeat(4, @wave('SwirlAttack2', drop: 'lasers'))
+          @sequence(
+            @wait(4000)
+            @repeat(2, @wave('SwirlAttack2', drop: 'lasers'))
+          )
+          @sequence(
+            @wait(6000)
+            @repeat(2, @wave('SwirlAttack', drop: 'lasers'))
+          )
         )
       )
+
+    underWaterAttacks: ->
+      @repeat(3, @wave('UnderWater'))


make a test more clear

make a test more clear

--- tests/batman/extras/i18n_test.coffee
+++ tests/batman/extras/i18n_test.coffee
@@ -124,7 +124,7 @@
     QUnit.start()

 asyncTest "it should look up keys in the translations under t", ->
-  viewHelpers.render '

', false, {}, (node) ->
+  viewHelpers.render '

', false, {}, (node) ->
     equal node.childNodes[0].innerHTML, "pamplemouse", 't has been added to the default render stack'
     QUnit.start()


Fixes variable name bug in forms plugin

Fixes variable name bug in forms plugin

--- vendor/assets/javascripts/styleguide/plugins/tbg-forms.js.coffee
+++ vendor/assets/javascripts/styleguide/plugins/tbg-forms.js.coffee
@@ -41,7 +41,7 @@
         data: data
         dataType: "json"
         success: (successData)=>
-          @showSuccess(sucessData)
+          @showSuccess(successData)
         error: (err) =>
           @showErrors $.parseJSON(err.responseText)


pass args to payload, only mark handled if all were

--- tutor/src/helpers/api.coffee
+++ tutor/src/helpers/api.coffee
@@ -161,9 +161,9 @@

       rejected = (response) ->
         {status, statusText, statusMessage, data} = response
-        onRequestError(response, requestConfig)
         requestConfig.handleError?(response, args...)
         unless response.handled is true
+          onRequestError(response, requestConfig)
           Actions.FAILED(status, statusMessage, args...)

       axios(requestConfig)
@@ -174,17 +174,22 @@
 route = (method, interpolatedUrl, options) ->
   pathMaker = (actionArguments) ->
     url = interpolate(interpolatedUrl, actionArguments)
-    payload = _.result(options, payload)
+    payload = if _.isFunction(options.payload)
+      options.payload.call(options, actionArguments)
+    else
+      options.payload
     {url, payload}

   if options.errorHandlers
     options.handleError = (request, args...) ->
       return unless _.isObject(request.data) and _.isArray(request.data.errors)
-      request.handled = true
+      handled_count = 0
       for error in request.data.errors
-        options.actions[ options.errorHandlers[error.code] ]?(
-          args..., error, request
-        )
+        handler = options.actions[ options.errorHandlers[error.code] ]
+        if handler
+          handler(args..., error, request)
+          handled_count += 1
+      request.handled = (handled_count is request.data.errors.length)

   apiHelper(
     options.actions, options.actions[options.trigger], options.actions[options.onSuccess],


check project structure version in unit tests

--- services/web/test/unit/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee
+++ services/web/test/unit/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee
@@ -390,6 +390,8 @@
 	describe "updateProjectStructure ", ->
 		beforeEach ->
 			@user_id = 1234
+			@version = 999
+			@Project.findOne = sinon.stub().callsArgWith(2,null, {_id: @project_id, version:@version})

 		describe "with project history disabled", ->
 			beforeEach ->
@@ -434,7 +436,7 @@

 					@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
 						@request.post
-							.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id})
+							.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id, version:@version})
 							.should.equal true
 						done()

@@ -454,7 +456,7 @@

 					@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
 						@request.post
-							.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id})
+							.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id, version:@version})
 							.should.equal true
 						done()

@@ -474,7 +476,7 @@

 					@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
 						@request.post
-							.calledWith(url: @url, json: {docUpdates: [], fileUpdates, userId: @user_id})
+							.calledWith(url: @url, json: {docUpdates: [], fileUpdates, userId: @user_id, version:@version})
 							.should.equal true
 						done()

@@ -493,7 +495,7 @@

 					@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
 						@request.post
-							.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id})
+							.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id, version:@version})
 							.should.equal true
 						done()


Throw unobserved errors

Throw unobserved errors

--- src/lib/InternalSocket.coffee
+++ src/lib/InternalSocket.coffee
@@ -19,6 +19,7 @@
     try
       @emit event, data
     catch error
+      throw error if @listenerCount('error') is 0
       @emit 'error',
         id: @to.process.id
         error: error


remove unecessary logging

remove unecessary logging

--- tests/index.coffee
+++ tests/index.coffee
@@ -276,7 +276,6 @@
             body.mailboxIDs.should.not.have.property @newBoxID

             SMTPTesting.mailStore.should.have.lengthOf 1
-            console.log SMTPTesting.mailStore.pop()
             done()

     it "When I delete the box", (done) ->


Add scss support for "tab" hotkey

Add scss support for "tab" hotkey

Every update i change this line in my emmet package for atom.
I think for somebody it will be useful.

--- keymaps/emmet.cson
+++ keymaps/emmet.cson
@@ -30,7 +30,7 @@

 # language-specific Tab triggers
 # you can add more triggers by changing `grammar` attribute values
-'atom-text-editor[data-grammar="text html basic"]:not([mini]), atom-text-editor[data-grammar~="erb"]:not([mini]), atom-text-editor[data-grammar~="jade"]:not([mini]), atom-text-editor[data-grammar~="css"]:not([mini]), atom-text-editor[data-grammar~="stylus"]:not([mini]), atom-text-editor[data-grammar~="sass"]:not([mini])':
+'atom-text-editor[data-grammar="text html basic"]:not([mini]), atom-text-editor[data-grammar~="erb"]:not([mini]), atom-text-editor[data-grammar~="jade"]:not([mini]), atom-text-editor[data-grammar~="css"]:not([mini]), atom-text-editor[data-grammar~="stylus"]:not([mini]), atom-text-editor[data-grammar~="sass"]:not([mini]), atom-text-editor[data-grammar~="scss"]:not([mini])':
   'tab': 'emmet:expand-abbreviation-with-tab'

 '.platform-darwin atom-text-editor:not([mini])':


HTML: removed unnecessary markup on payload

--- workers/social/lib/social/render/loggedout/kodinghome.coffee
+++ workers/social/lib/social/render/loggedout/kodinghome.coffee
@@ -6,7 +6,6 @@

   prepareHTML = (scripts)->
     """
-
     <!doctype html>

@@ -18,20 +17,8 @@

-
-

-

-
-

-
-            Login
-

-
-

-
       #{KONFIG.getConfigScriptTag { roles: ['guest'], permissions: [] } }
       #{scripts}
-

     """


Rename datestamp() to mmddyy(), which is more meaningful

--- src/account/checkouts.coffee
+++ src/account/checkouts.coffee
@@ -94,11 +94,10 @@
 	# One of the compoments of the status line is the due date,
 	# which we will convert into MMDDYY format.
 	pad = (x) -> if x < 10 then '0' + x else x
-	datestamp = (x) ->
-		"#{pad x.getMonth() + 1}/#{pad x.getDate()}/#{x.getFullYear()}"
-
+	mmddyy = (x) ->
+		"#{pad x.getMonth() + 1}/#{pad x.getDate()}/#{pad x.getFullYear()}"
 	show_status_line = (circ) ->
-		$('.due_date', @).text datestamp circ.due_date
+		$('.due_date', @).text mmddyy circ.due_date
 		$('.remaining_renewals', @).text circ.renewal_remaining
 		$('input:checkbox', @).val circ.target_copy


Add emacs-style bindings for selecting

Add emacs-style bindings for selecting

--- src/app/keymaps/emacs.coffee
+++ src/app/keymaps/emacs.coffee
@@ -1,10 +1,16 @@
 window.keymap.bindKeys '.editor',
   'ctrl-f': 'move-right'
+  'ctrl-F': 'select-right'
   'ctrl-b': 'move-left'
+  'ctrl-B': 'select-left'
   'ctrl-p': 'move-up'
+  'ctrl-P': 'select-up'
   'ctrl-n': 'move-down'
+  'ctrl-N': 'select-down'
   'alt-f': 'move-to-end-of-word'
+  'alt-F': 'select-to-end-of-word'
   'alt-b': 'move-to-beginning-of-word'
+  'alt-B': 'select-to-beginning-of-word'
   'ctrl-a': 'move-to-first-character-of-line'
   'ctrl-e': 'move-to-end-of-line'
   'ctrl-h': 'backspace'


Removed a rouge console.log

Removed a rouge console.log

--- src/coffee/ui/hotkeys.coffee
+++ src/coffee/ui/hotkeys.coffee
@@ -188,7 +188,6 @@
   setup: ->
     # Map the ctrl key as cmd if the user is on windows.
     if ~navigator.appVersion.indexOf("Win")
-      console.log 'windows'
       @modifierCodes[17] = 'cmd'
       dom.body?.setAttribute 'os', 'windows'


Add Copyright and License Doc Tags

Add Copyright and License Doc Tags

--- lib/docTags.coffee
+++ lib/docTags.coffee
@@ -1,7 +1,7 @@
 ###
 # # Known Doc Tags
 #
-# Code from [`groc/lib/doc_tags`][1]
+# Most code from [`groc/lib/doc_tags`][1].
 #
 # @copyright Ian MacLeod and groc contributors
 #
@@ -33,6 +33,14 @@
     section:     'description'
     markdown:    '{value}'

+  copyright:
+    section:     'metadata'
+    markdown:    '© {value}'
+  license:
+    valuePrefix: 'under'
+    section:     'metadata'
+    markdown:    'licensed under {value}'
+
   internal:
     section:     'access'
   'private':
@@ -54,10 +62,10 @@
     section:     'type'
   method:
     section:     'type'
-    markdown:    'Method: `{value}`'
+    markdown:    'Method **{value}**'
   module:
     section:     'type'
-    markdown:    'Module: `{value}`'
+    markdown:    'Module **{value}**'
   'package':
     section:     'type'
   property:
@@ -211,4 +219,4 @@
   defaultNoValue:
     section:     'flag'
   defaultHasValue:
-    section:     'metadata'+    section:     'metadata'


add js doc

add js doc

--- BackofficeBundle/Resources/public/coffee/ribbonButton/RibbonFormButtonView.coffee
+++ BackofficeBundle/Resources/public/coffee/ribbonButton/RibbonFormButtonView.coffee
@@ -31,6 +31,7 @@
   ###*
    * Method call to clone and move button
    * @param {Object} button
+   * @param {String} container
   ###
   cloneButton: (button, container) ->
     button.uniqueId();


dont return needsClosingBrace from Helpers

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/Helpers.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/Helpers.coffee
@@ -40,7 +40,6 @@
 				commandFragment,
 				commandName,
 				lineBeyondCursor,
-				needsClosingBrace,
 				closingBrace
 			}


Added scrollContainer property, allowing the user to specify the container to watch for scrolling

--- src/wow.coffee
+++ src/wow.coffee
@@ -107,10 +107,13 @@
     mobile:       true
     live:         true
     callback:     null
+    scrollContainer: undefined

   constructor: (options = {}) ->
     @scrolled = true
     @config   = @util().extend(options, @defaults)
+    if typeof options.scrollContainer is 'string'
+      @config.scrollContainer = document.querySelector(options.scrollContainer)
     # Map of elements to animation names:
     @animationNameCache = new WeakMap()
     @wowEvent = @util().createEvent(@config.boxClass)
@@ -133,7 +136,7 @@
       else
         @applyStyle(box, true) for box in @boxes
     if !@disabled()
-      @util().addEvent window, 'scroll', @scrollHandler
+      @util().addEvent @config.scrollContainer || window, 'scroll', @scrollHandler
       @util().addEvent window, 'resize', @scrollHandler
       @interval = setInterval @scrollCallback, 50
     if @config.live
@@ -147,7 +150,7 @@
   # unbind the scroll event
   stop: ->
     @stopped = true
-    @util().removeEvent window, 'scroll', @scrollHandler
+    @util().removeEvent @config.scrollContainer || window, 'scroll', @scrollHandler
     @util().removeEvent window, 'resize', @scrollHandler
     clearInterval @interval if @interval?

@@ -273,7 +276,7 @@
   # check if box is visible
   isVisible: (box) ->
     offset     = box.getAttribute('data-wow-offset') or @config.offset
-    viewTop    = window.pageYOffset
+    viewTop    = (@config.scrollContainer && @config.scrollContainer.scrollTop) || window.pageYOffset
     viewBottom = viewTop + Math.min(@element.clientHeight, @util().innerHeight()) - offset
     top        = @offsetTop(box)
     bottom     = top + box.clientHeight


Using elapsed time in delayed events

Using elapsed time in delayed events

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -23,7 +23,7 @@
       (event.delay -= elapsedTime) >= 0

     firingEvents.each (event) ->
-      self.sendOrApplyevent.callback()
+      self.sendOrApply event.fn()

   ###*
   Execute fn every n frames.


sort [mobile] auction lots by position, not lot_label (#1250)

--- mobile/apps/auction/routes.coffee
+++ mobile/apps/auction/routes.coffee
@@ -31,8 +31,7 @@
   auction = new Auction id: id
   saleArtworks = new SaleArtworks [], id: id
   artworks = new Artworks
-  artworks.comparator = (artwork) ->
-    (saleArtwork = artwork.related().saleArtwork).get('lot_label') or saleArtwork.id
+  artworks.comparator = (artwork) -> (saleArtwork = artwork.related().saleArtwork).get('position') or saleArtwork.id

   promise = Q.all([
     auction.fetch(cache: true)


Do not expect ids on commands

Do not expect ids on commands

--- spec/MakeCircle.coffee
+++ spec/MakeCircle.coffee
@@ -64,7 +64,9 @@
       expected = {type: 'circle', center, radius}
       out.once "data", (data) ->
         chai.expect(data).to.be.an 'object'
-        chai.expect(data).to.deep.equal expected
+        chai.expect(data.type).to.be.equal expected.type
+        chai.expect(data.center).to.be.equal expected.center
+        chai.expect(data.radius).to.be.equal expected.radius
         done()
       sock_center.send center
       sock_radius.send radius
@@ -82,7 +84,10 @@
       out.once "data", (data) ->
         chai.expect(data).to.be.an 'array'
         chai.expect(data.length).to.equal 2
-        chai.expect(data).to.deep.equal expected
+        for expectedCircle, i in expected
+          chai.expect(data[i].type).to.be.equal expectedCircle.type
+          chai.expect(data[i].center).to.be.equal expectedCircle.center
+          chai.expect(data[i].radius).to.be.equal expectedCircle.radius
         done()
       sock_center.send center
       sock_radius.send radius


Disable spec for window.close() since it would not be fixed for a while.

--- spec/api/window.coffee
+++ spec/api/window.coffee
@@ -21,7 +21,7 @@
         done()
       w.loadUrl 'file://' + path.join(fixtures, 'api', 'unload.html')

-  describe 'window.close()', ->
+  xdescribe 'window.close()', ->
     it 'should emit unload handler', (done) ->
       w = new BrowserWindow(show: false)
       w.on 'destroyed', ->


add color support

add color support


More fixes to crosslinking

More fixes to crosslinking

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -198,10 +198,11 @@
   .replace ///(<a\s[^<>]*href\s*=\s*['"])#{coauthorLinkRe}///ig,
     (match, p1, p2) ->
       ## xxx Could add msg.title, when available, to hover text...
-      #msg = Messages.findOne p2
+      ## xxx Currently assuming message is in same group if can't find it.
+      msg = Messages.findOne p2
       p1 + pathFor 'message',
-        group: msg.group
-        message: msg._id
+        group: msg?.group or Router.current().params.group
+        message: p2

 katex = require 'katex'


Fix zombie tip appearance callbacks.

Fix zombie tip appearance callbacks.

Explicit cancel any running appearance procedure
before running another appearance procedure.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -396,7 +396,7 @@
       wake = =>
         @_positionToTrigger $trigger, event
         @onShow triggerChanged, event
-        @$tip.fadeIn duration, =>
+        @$tip.stop().fadeIn duration, =>
           if triggerChanged
             onWake() if onWake?
           if @safeToggle is on then @$tip.siblings(@classNames.tip).fadeOut()
@@ -426,7 +426,7 @@
       clearTimeout @_wakeCountdown
       @_sleepCountdown = setTimeout =>
         @onHide()
-        @$tip.fadeOut @ms.duration.out, =>
+        @$tip.stop().fadeOut @ms.duration.out, =>
           @_state = 'asleep'
           @afterHide()
       , @ms.delay.out


[#1] Changes for Electron upgrade.

[#1] Changes for Electron upgrade.

--- lib/impress.coffee
+++ lib/impress.coffee
@@ -4,8 +4,8 @@
 StepListViewManager = require './step-list-view-manager'
 ConfigResolver = require './config-resolver'
 config = require './config'
-remote = require 'remote'
-BrowserWindow = remote.require 'browser-window'
+electron = require 'electron'
+BrowserWindow = electron.remote.BrowserWindow
 path = require 'path'

 module.exports = Impress =
@@ -69,5 +69,5 @@
       @previewWindow.destroy()
       @previewWindow = null
     @previewWindow.setMenuBarVisibility false
-    @previewWindow.loadUrl mainHtmlPath
+    @previewWindow.webContents.loadURL mainHtmlPath
     @previewWindow.show()


change the algorithm of range search

change the algorithm of range search

--- src/lib/interval-tree.coffee
+++ src/lib/interval-tree.coffee
@@ -214,22 +214,17 @@

         resultsById = {}

+        # 1. pointSearch to arbitrary point between start and end (here: point = start)
         for interval in @pointSearch(start)
             resultsById[interval.id] = interval

-        for interval in @pointSearch(end)
-            resultsById[interval.id] = interval
-
-        # add intervals whose point is included in the given range
+        # 2. add intervals whose either point is included in the given range
         firstPos = @pointTree.firstPositionOf new Point(start)
-
-        lastPos = @pointTree.lastPositionOf new Point(end)
-
-        if lastPos >= 0
-
-            for point in @pointTree.slice(firstPos, lastPos)
-
-                resultsById[point.id] = @intervalsById[point.id]
+        lastPos  = @pointTree.lastPositionOf new Point(end)
+
+        for point in @pointTree.slice(firstPos, lastPos + 1)
+
+            resultsById[point.id] = @intervalsById[point.id]

         return (interval for id, interval of resultsById)


Use parentheses when spanning multiple lines

--- src/lib/mpq/storm-lib.coffee
+++ src/lib/mpq/storm-lib.coffee
@@ -36,7 +36,7 @@

 LPDWORD = voidPtr

-module.exports = new ffi.Library 'libstorm',
+module.exports = new ffi.Library('libstorm',

   SFileGetLocale: [uint32, []]

@@ -364,6 +364,7 @@
 #   ]]

   GetLastError: [int32, []]
+)

 module.exports.FIND_DATA = FIND_DATA


adding tabs to some homepage templates

adding tabs to some homepage templates

--- @client/customizations_helpers.coffee
+++ @client/customizations_helpers.coffee
@@ -495,6 +495,20 @@

           HomepageTabs
             tab_style: opts.tab_style
+            tab_wrapper_style: _.defaults {}, opts.tab_wrapper_style or {},
+              backgroundColor: opts.tab_background_color # '#005596'
+              margin: '0 6px'
+            active_style: _.defaults {}, opts.tab_active_style or {},
+              backgroundColor: 'white'
+              color: 'black'
+            active_tab_wrapper_style: _.defaults {}, opts.active_tab_wrapper_style or {},
+              backgroundColor: opts.tab_background_color
+            hovering_tab_wrapper_style: _.defaults {}, opts.active_tab_wrapper_style or {},
+              backgroundColor: opts.tab_background_color
+            wrapper_style: _.defaults {}, opts.tabs_wrapper_style or {},
+              marginTop: 80
+              top: 0
+            list_style: opts.tabs_list_style or {}

@@ -544,129 +558,6 @@
     color: "#0B4D92"

-  DIV
-    style:
-      position: 'relative'
-
-    if opts.external_link
-      A
-        href: opts.external_link
-        target: '_blank'
-        style: opts.external_link_style
-
-        I
-          className: 'fa fa-chevron-left'
-          style:
-            display: 'inline-block'
-            marginRight: 5
-
-        opts.external_link_anchor or opts.external_link
-
-    if opts.background_image_url
-      IMG
-        alt: opts.background_image_alternative_text
-        style: _.defaults {}, opts.image_style,
-          width: '100%'
-          display: 'block'
-        src: opts.background_image_url
-
-    DIV
-      style:
-        padding: '20px 0'
-
-      DIV
-        style:
-          width: HOMEPAGE_WIDTH()
-          margin: 'auto'
-
-        if opts.quote
-
-          DIV
-            style: quote_style
-            "“#{opts.quote.what}”"
-
-            if opts.quote.who
-              DIV
-                style:
-                  paddingLeft: '70%'
-                  paddingTop: 10
-                "– #{opts.quote.who}"
-
-        DIV null,
-
-          for section, idx in opts.sections
-
-            DIV
-              style:
-                marginBottom: 20
-
-
-              if section.label
-                HEADING = if idx == 0 then H1 else DIV
-                HEADING
-                  style: _.defaults {}, (section.label_style or {}), section_heading_style
-                  section.label
-
-              DIV null,
-                for paragraph in (section.paragraphs or [])
-                  SPAN
-                    style: paragraph_style
-                    dangerouslySetInnerHTML: { __html: paragraph }
-
-        if opts.salutation
-          DIV
-            style: _.extend {}, paragraph_style,
-              marginTop: 10
-
-            if opts.salutation.text
-              DIV
-                style:
-                  marginBottom: 18
-                opts.salutation.text
-
-            A
-              href: if opts.external_link then opts.external_link
-              target: '_blank'
-              style:
-                display: 'block'
-                marginBottom: 8
-
-              if opts.salutation.image
-                IMG
-                  src: opts.salutation.image
-                  alt: ''
-                  style:
-                    height: 70
-              else
-                opts.salutation.from
-
-            if opts.salutation.after
-              DIV
-                style: _.extend {}, paragraph_style,
-                  margin: 0
-                dangerouslySetInnerHTML: { __html: opts.salutation.after }
-
-        if opts.login_callout
-          AuthCallout()
-
-        if opts.closed
-          DIV
-            style:
-              marginTop: 40
-              backgroundColor: "#F06668"
-              color: 'white'
-              fontSize: 28
-              textAlign: 'center'
-              padding: "30px 42px"
-
-            "The comment period is now closed. Thank you for your input!"
-
-
-window.PhotoBanner = (opts) ->
-  homepage = fetch('location').url == '/'
-
-  opts ?= {}
-  opts.tab_background_color ?= '#666'
   if !homepage
     return  DIV
               style:
@@ -690,36 +581,124 @@
                       left: 5
                     'Homepage'

-  DIV null,
+
+  DIV
+    style:
+      position: 'relative'
+
+    if opts.external_link
+      A
+        href: opts.external_link
+        target: '_blank'
+        style: opts.external_link_style
+
+        I
+          className: 'fa fa-chevron-left'
+          style:
+            display: 'inline-block'
+            marginRight: 5
+
+        opts.external_link_anchor or opts.external_link
+
+    if opts.background_image_url
+      IMG
+        alt: opts.background_image_alternative_text
+        style: _.defaults {}, opts.image_style,
+          width: '100%'
+          display: 'block'
+        src: opts.background_image_url

     DIV
-      style:
-        backgroundImage: opts.backgroundImage
-        backgroundSize: 'cover'
-        paddingTop: 140
-
-      DIV
-        style: _.defaults {}, opts.header_style or {},
-          padding: '48px 48px 48px 48px'
+      style:
+        padding: '20px 0'
+
+      DIV
+        style:
           width: HOMEPAGE_WIDTH()
-          maxWidth: 720
           margin: 'auto'
-          backgroundColor: 'rgba(0, 85, 150, .8)'
-          color: 'white'
-          position: 'relative'
-          top: 0
-
-        DIV
-          style: _.defaults {}, opts.header_text_style or {},
-            fontSize: 56
-            fontWeight: 800
-            fontStyle: 'oblique'
-            textAlign: 'center'
-            paddingBottom: 28
-          dangerouslySetInnerHTML: __html: opts.header
-
-        DIV null,
-          opts.supporting_text?()
+
+        if opts.quote
+
+          DIV
+            style: quote_style
+            "“#{opts.quote.what}”"
+
+            if opts.quote.who
+              DIV
+                style:
+                  paddingLeft: '70%'
+                  paddingTop: 10
+                "– #{opts.quote.who}"
+
+        DIV null,
+
+          for section, idx in opts.sections
+
+            DIV
+              style:
+                marginBottom: 20
+
+
+              if section.label
+                HEADING = if idx == 0 then H1 else DIV
+                HEADING
+                  style: _.defaults {}, (section.label_style or {}), section_heading_style
+                  section.label
+
+              DIV null,
+                for paragraph in (section.paragraphs or [])
+                  SPAN
+                    style: paragraph_style
+                    dangerouslySetInnerHTML: { __html: paragraph }
+
+        if opts.salutation
+          DIV
+            style: _.extend {}, paragraph_style,
+              marginTop: 10
+
+            if opts.salutation.text
+              DIV
+                style:
+                  marginBottom: 18
+                opts.salutation.text
+
+            A
+              href: if opts.external_link then opts.external_link
+              target: '_blank'
+              style:
+                display: 'block'
+                marginBottom: 8
+
+              if opts.salutation.image
+                IMG
+                  src: opts.salutation.image
+                  alt: ''
+                  style:
+                    height: 70
+              else
+                opts.salutation.from
+
+            if opts.salutation.after
+              DIV
+                style: _.extend {}, paragraph_style,
+                  margin: 0
+                dangerouslySetInnerHTML: { __html: opts.salutation.after }
+
+        if opts.login_callout
+          AuthCallout()
+
+        if opts.closed
+          DIV
+            style:
+              marginTop: 40
+              backgroundColor: "#F06668"
+              color: 'white'
+              fontSize: 28
+              textAlign: 'center'
+              padding: "30px 42px"
+
+            "The comment period is now closed. Thank you for your input!"
+

       if customization('homepage_tabs')
         HomepageTabs
@@ -744,3 +723,86 @@
             top: 0
           list_style: opts.tabs_list_style or {}

+
+window.PhotoBanner = (opts) ->
+  homepage = fetch('location').url == '/'
+
+  opts ?= {}
+  opts.tab_background_color ?= '#666'
+  if !homepage
+    return  DIV
+              style:
+                backgroundColor: 'white'
+              DIV
+                style:
+                  width: HOMEPAGE_WIDTH()
+                  margin: 'auto'
+                  fontSize: 43
+                  padding: '10px 0'
+
+                A
+                  href: '/'
+
+                  '< '
+
+                  SPAN
+                    style:
+                      fontSize: 32
+                      position: 'relative'
+                      left: 5
+                    'Homepage'
+
+  DIV null,
+
+    DIV
+      style:
+        backgroundImage: opts.backgroundImage
+        backgroundSize: 'cover'
+        paddingTop: 140
+
+      DIV
+        style: _.defaults {}, opts.header_style or {},
+          padding: '48px 48px 48px 48px'
+          width: HOMEPAGE_WIDTH()
+          maxWidth: 720
+          margin: 'auto'
+          backgroundColor: 'rgba(0, 85, 150, .8)'
+          color: 'white'
+          position: 'relative'
+          top: 0
+
+        DIV
+          style: _.defaults {}, opts.header_text_style or {},
+            fontSize: 56
+            fontWeight: 800
+            fontStyle: 'oblique'
+            textAlign: 'center'
+            paddingBottom: 28
+          dangerouslySetInnerHTML: __html: opts.header
+
+        DIV null,
+          opts.supporting_text?()
+
+      if customization('homepage_tabs')
+        HomepageTabs
+          tab_style: _.defaults {}, opts.tab_style or {},
+            textTransform: 'uppercase'
+            fontStyle: 'oblique'
+            fontWeight: 600
+            fontSize: 20
+            padding: '10px 16px 4px'
+          tab_wrapper_style: _.defaults {}, opts.tab_wrapper_style or {},
+            backgroundColor: opts.tab_background_color # '#005596'
+            margin: '0 6px'
+          active_style: _.defaults {}, opts.tab_active_style or {},
+            backgroundColor: 'white'
+            color: 'black'
+          active_tab_wrapper_style: _.defaults {}, opts.active_tab_wrapper_style or {},
+            backgroundColor: opts.tab_background_color
+          hovering_tab_wrapper_style: _.defaults {}, opts.active_tab_wrapper_style or {},
+            backgroundColor: opts.tab_background_color
+          wrapper_style: _.defaults {}, opts.tabs_wrapper_style or {},
+            marginTop: 80
+            top: 0
+          list_style: opts.tabs_list_style or {}
+


Tighten up the spring physics tolerance

Tighten up the spring physics tolerance

--- framer/Animators/SpringRK4Animator.coffee
+++ framer/Animators/SpringRK4Animator.coffee
@@ -11,7 +11,7 @@
 			tension: 250
 			friction: 25
 			velocity: 0
-			tolerance: 1/10000
+			tolerance: 1/100
 			time: null # Hack

 		@_time = 0


Fix SCAN_LIMIT enum

Fix SCAN_LIMIT enum

--- app/enums.coffee
+++ app/enums.coffee
@@ -39,6 +39,6 @@
   PRICING:
     UNKNOWN: -1
     TIME_LIMIT: 0
-    SCAN_LIMIT: 2
+    SCAN_LIMIT: 1

 `export default ENUMS;`


UPDATED UserCtrl

UPDATED UserCtrl

--- app/assets/javascripts/angular/controllers/UserCtrl.js.coffee
+++ app/assets/javascripts/angular/controllers/UserCtrl.js.coffee
@@ -1,6 +1,12 @@
-Grasshopper.controller "UserCtrl", (['$scope', '$location', 'Restangular', 'getCurrentUser', ($scope, $location, Restangular, getCurrentUser) ->
-  $scope.currentUser = getCurrentUser.currentUser
+Grasshopper.controller "UserCtrl", (['$scope', '$location', 'Restangular', 'currentUser', ($scope, $location, Restangular, currentUser) ->
+  $scope.currentUser = currentUser

-  getCurrentUser.loadData()
+  currentUser.loadData()

+  $scope.updateProfile = () ->
+
+    currentUser.data.route = 'users'
+    currentUser.data.put().then ( ->
+      $location.path('/')
+    )
 ])


Slide toggle valition errors box

Slide toggle valition errors box

--- app/assets/javascripts/components/validation_errors.js.coffee
+++ app/assets/javascripts/components/validation_errors.js.coffee
@@ -5,13 +5,13 @@
     @state = {
       data: @props.validationErrors
     }
+    @toggleBox = @toggleBox.bind(@)

   render: ->
     div(
       { className: 'validation-errors-box' }
       @renderHeader()
-      @renderInfoBox()
-      @renderValidationErrors()
+      @renderBody()
     )

   renderHeader: ->
@@ -22,9 +22,19 @@
         "Validation errors (#{@state.data.length})"
       )
       a(
-        { className: 'toggle-errors button' }
+        {
+          className: 'toggle-errors button'
+          onClick: @toggleBox
+        }
         i({ className: 'fa fa-angle-up' })
       )
+    )
+
+  renderBody: ->
+    div(
+      { className: 'validation-errors-body' }
+      @renderInfoBox()
+      @renderValidationErrors()
     )

   renderInfoBox: ->
@@ -46,5 +56,10 @@
         }
       )

+  toggleBox: ->
+    $('.validation-errors-body').slideToggle();
+    $('.validation-errors-header').toggleClass('border-btm');
+    $('.toggle-errors').find('i').toggleClass('fa-angle-up')
+    $('.toggle-errors').find('i').toggleClass('fa-angle-down')


Default new users as active

Default new users as active

--- app/assets/javascripts/controllers/user_new_controller.coffee
+++ app/assets/javascripts/controllers/user_new_controller.coffee
@@ -7,8 +7,10 @@
     STATE_SAVING = 1
     STATE_DEFAULT = 2

+    newUser = -> {active: true}
+
     cancel = ->
-      $scope.user = {}
+      $scope.user = newUser()
       $scope.newUserForm.$setPristine()

     save = (user)->
@@ -32,5 +34,5 @@
     $scope.SAVING = STATE_SAVING
     $scope.DEFAULT = STATE_DEFAULT

-    $scope.user = {}
+    $scope.user = newUser()
 ])


[#58788638] split out observers for particular filters; this reduces the number of operations involved in every filtering action

--- app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
@@ -105,45 +105,82 @@
     !@get('filtersVisible')
   ).property('filtersVisible')

-  filtersChanged: ( ->
-    @set('filtersSelected', false)
-    shipments = @get('content.sandboxShipments')
-    @get('columnNames').forEach (columnName) =>
-      capitalisedColumnName = @capitaliseFirstLetter(columnName)
-      selectedValuesName = 'selected' + capitalisedColumnName + 'Values'
-      blankValue = 'blank' + capitalisedColumnName
-      if @get(selectedValuesName + '.length') > 0 || @get(blankValue)
-        @set('filtersSelected', true)
-        shipments = shipments.filter((element) =>
-          return @get(selectedValuesName).contains(element.get(columnName)) ||
-            @get(blankValue) && (
-              # check if null, undefined or blank
-              !element.get(columnName) || /^\s*$/.test(element.get(columnName))
-            )
-        )
-    @set('visibleShipments', shipments)
-  ).observes(
-    'selectedAppendixValues.@each', 'blankAppendix',
-    'selectedSpeciesNameValues.@each', 'blankSpeciesName',
-    'selectedTermCodeValues.@each', 'blankTermCode',
-    'selectedQuantityValues.@each', 'blankQuantity',
-    'selectedUnitCodeValues.@each', 'blankUnitCode',
-    'selectedTradingPartnerValues.@each', 'blankTradingPartner',
-    'selectedCountryOfOriginValues.@each', 'blankCountryOfOrigin',
-    'selectedImportPermitValues.@each', 'blankImportPermit',
-    'selectedExportPermitValues.@each', 'blankExportPermit',
-    'selectedOriginPermitValues.@each', 'blankOriginPermit',
-    'selectedPurposeCodeValues.@each', 'blankPurposeCode',
-    'selectedSourceCodeValues.@each', 'blankSourceCode',
-    'selectedYearValues.@each', 'blankYear'
-  )
+  selectedAppendixChanged: ( ->
+    @applyFilter('appendix')
+  ).observes('selectedAppendixValues.@each', 'blankAppendix')
+
+  selectedSpeciesNameChanged: ( ->
+    @applyFilter('speciesName')
+  ).observes('selectedSpeciesNameValues.@each', 'blankSpeciesName')
+
+  selectedTermCodeChanged: ( ->
+    @applyFilter('termCode')
+  ).observes('selectedTermCodeValues.@each', 'blankTermCode')
+
+  selectedQuantityChanged: ( ->
+    @applyFilter('quantity')
+  ).observes('selectedQuantityValues.@each', 'blankQuantity')
+
+  selectedUnitCodeChanged: ( ->
+    @applyFilter('unitCode')
+  ).observes('selectedUnitCodeValues.@each', 'blankUnitCode')
+
+  selectedTradingPartnerChanged: ( ->
+    @applyFilter('tradingPartner')
+  ).observes('selectedTradingPartnerValues.@each', 'blankTradingPartner')
+
+  selectedCountryOfOriginChanged: ( ->
+    @applyFilter('countryOfOrigin')
+  ).observes('selectedCountryOfOriginValues.@each', 'blankCountryOfOrigin')
+
+  selectedImportPermitChanged: ( ->
+    @applyFilter('importPermit')
+  ).observes('selectedImportPermitValues.@each', 'blankImportPermit')
+
+  selectedExportPermitChanged: ( ->
+    @applyFilter('exportPermit')
+  ).observes('selectedExportPermitValues.@each', 'blankExportPermit')
+
+  selectedOriginPermitChanged: ( ->
+    @applyFilter('originPermit')
+  ).observes('selectedOriginPermitValues.@each', 'blankOriginPermit')
+
+  selectedPurposeCodeChanged: ( ->
+    @applyFilter('purposeCode')
+  ).observes('selectedPurposeCodeValues.@each', 'blankPurposeCode')
+
+  selectedSourceCodeChanged: ( ->
+    @applyFilter('sourceCode')
+  ).observes('selectedSourceCodeValues.@each', 'blankSourceCode')
+
+  selectedYearChanged: ( ->
+    @applyFilter('year')
+  ).observes('selectedYearValues.@each', 'blankYear')
+
+  applyFilter: (columnName) ->
+    capitalisedColumnName = @capitaliseFirstLetter(columnName)
+    selectedValuesName = 'selected' + capitalisedColumnName + 'Values'
+    blankValue = 'blank' + capitalisedColumnName
+    if @get(selectedValuesName + '.length') > 0 || @get(blankValue)
+      @set('filtersSelected', true)
+      shipments = @get('visibleShipments').filter((element) =>
+        value = element.get(columnName)
+        return @get(selectedValuesName).contains(value) ||
+          # check if null, undefined or blank
+          @get(blankValue) && (!value || /^\s*$/.test(value))
+      )
+      @set('visibleShipments', shipments)

   resetFilters: ->
+    @beginPropertyChanges()
     @get('columnNames').forEach (columnName) =>
       selectedValuesName = 'selected' + @capitaliseFirstLetter(columnName) + 'Values'
       @set(selectedValuesName, [])
       blankValueName = 'blank' + @capitaliseFirstLetter(columnName)
       @set(blankValueName, false)
+    @set('visibleShipments', @get('content.sandboxShipments'))
+    @set('filtersSelected', false)
+    @endPropertyChanges()

   capitaliseFirstLetter: (string) ->
     string.charAt(0).toUpperCase() + string.slice(1)


- Auto adjust source code comments for lua to use proper call format.

--- app/views/play/level/tome/Spell.coffee
+++ app/views/play/level/tome/Spell.coffee
@@ -83,6 +83,11 @@
     # Translate comments chosen spoken language.
     return unless @commentContext
     context = $.extend true, {}, @commentContext
+
+    if @language is 'lua'
+      for k,v of context
+        context[k] = v.replace /\b([a-zA-Z]+)\.([a-zA-Z_]+\()/, '$1:$2'
+
     if @commentI18N
       spokenLanguage = me.get 'preferredLanguage'
       while spokenLanguage


Allow bot call deleteOutgoingIntegration

--- packages/rocketchat-integrations/server/methods/outgoing/deleteOutgoingIntegration.coffee
+++ packages/rocketchat-integrations/server/methods/outgoing/deleteOutgoingIntegration.coffee
@@ -1,6 +1,6 @@
 Meteor.methods
 	deleteOutgoingIntegration: (integrationId) ->
-		if not RocketChat.authz.hasPermission @userId, 'manage-integrations'
+		if not RocketChat.authz.hasPermission(@userId, 'manage-integrations') and not RocketChat.authz.hasPermission(@userId, 'manage-integrations', 'bot')
 			throw new Meteor.Error 'not_authorized'

 		integration = RocketChat.models.Integrations.findOne(integrationId)


bootstrapping VentasNewView

bootstrapping VentasNewView

--- app/assets/javascripts/views/ventas/ventas_new_view.js.coffee
+++ app/assets/javascripts/views/ventas/ventas_new_view.js.coffee
@@ -3,6 +3,12 @@

   modelBinding: "controller.model"

+  success: (a) ->
+    console.log a
+
+  failure: (r) ->
+    console.log r
+
   submit: ->
-    console.log "submit!"
+    @get("model").save().then $.proxy(@success, @), $.proxy(@failure, @)
     false


Update English copy for camera dialog

Update English copy for camera dialog

--- app/assets/javascripts/uploadcare/locale/en.js.coffee
+++ app/assets/javascripts/uploadcare/locale/en.js.coffee
@@ -62,9 +62,9 @@
           capture: 'Take a photo'
           retry: 'Request permissions again'
           pleaseAllow:
-            title: 'Please, allow access to your camera'
-            text: 'You have been requested pemissions to access camera from this site. ' +
-                  'In order to take pictures from your cam you should approve this request.'
+            title: 'Please allow access to your camera'
+            text: 'You have been prompted to allow camera access from this site. ' +
+                  'In order to take pictures with your camera you must approve this request.'
           notFound:
             title: 'No camera detected'
             text: 'Looks like you have no camera connected to this device.'


Fixed problem with initials.

Fixed problem with initials.

--- app/client/templates/user-profile/user-profile.coffee
+++ app/client/templates/user-profile/user-profile.coffee
@@ -14,6 +14,8 @@
       Meteor.user().emails[0].address
   userInitials: () ->
     fullname = ''
-    for word in Meteor.user().profile.fullname.split(' ')
-      fullname += word[0].toUpperCase()
+    user = Meteor.user()
+    if user
+      for word in user.profile.fullname.split(' ')
+        fullname += word[0].toUpperCase()
     fullname


Make wesabe.canvas.geometry.Rect CommonJS-compatible.

--- application/chrome/content/wesabe/canvas/geometry/Rect.coffee
+++ application/chrome/content/wesabe/canvas/geometry/Rect.coffee
@@ -1,61 +1,55 @@
-wesabe.provide('canvas.geometry.Rect')
+Point     = require 'canvas/geometry/Point'
+Size      = require 'canvas/geometry/Size'
+Colorizer = require 'util/Colorizer'

-class wesabe.canvas.geometry.Rect
-  constructor: (origin, size) ->
-    @origin = origin
-    @size = size
+class Rect
+  constructor: (@origin, @size) ->

   contains: (point) ->
-    (@origin.x <= point.x) &&
-    (@origin.y <= point.y) &&
-    (point.x <= @origin.x + @size.width) &&
+    (@origin.x <= point.x) and
+    (@origin.y <= point.y) and
+    (point.x <= @origin.x + @size.width) and
     (point.y <= @origin.y + @size.height)

-  this::__defineGetter__ 'width', ->
+  @::__defineGetter__ 'width', ->
     @size.width

-  this::__defineGetter__ 'height', ->
+  @::__defineGetter__ 'height', ->
     @size.height

-  this::__defineGetter__ 'left', ->
+  @::__defineGetter__ 'left', ->
     @origin.x

-  this::__defineGetter__ 'top', ->
+  @::__defineGetter__ 'top', ->
     @origin.y

-  this::__defineGetter__ 'right', ->
+  @::__defineGetter__ 'right', ->
     @origin.x + @size.width

-  this::__defineGetter__ 'bottom', ->
+  @::__defineGetter__ 'bottom', ->
     @origin.y + @size.height

-  this::__defineGetter__ 'center', ->
-    new wesabe.canvas.geometry.Point(@origin.x + @size.width / 2, @origin.y + @size.height / 2)
+  @::__defineGetter__ 'center', ->
+    new Point @origin.x + @size.width / 2, @origin.y + @size.height / 2

   @make: (x, y, w, h) ->
-    new this(
-      new wesabe.canvas.geometry.Point(x, y),
-      new wesabe.canvas.geometry.Size(w, h))
+    new this new Point(x, y), new Size(w, h)

   @fromPoints: (p1, p2) ->
-    origin = new wesabe.canvas.geometry.Point(
-      Math.min(p1.x, p2.x),
-      Math.min(p1.y, p2.y))
-    size = new wesabe.canvas.geometry.Size(
-      Math.max(p1.x, p2.x) - origin.x,
-      Math.max(p1.y, p2.y) - origin.y)
+    origin = new Point Math.min(p1.x, p2.x), Math.min(p1.y, p2.y)
+    size = new Size Math.max(p1.x, p2.x) - origin.x, Math.max(p1.y, p2.y) - origin.y

-    new this(origin, size)
+    new this origin, size

   @__defineGetter__ 'ZeroRect', ->
-    @make(0, 0, 0, 0)
+    @make 0, 0, 0, 0

   inspect: (refs, color, tainted) ->
-    s = new wesabe.util.Colorizer()
+    s = new Colorizer()
     s.disabled = !color
     s
       .yellow('#<')
-      .bold(@constructor?.__module__?.name || 'Object')
+      .bold(@constructor?.__module__?.name or 'Object')
       .print(' ')
       .yellow('{')
       .print(@left)
@@ -68,3 +62,6 @@
       .print('}')
       .yellow('>')
       .toString()
+
+
+module.exports = Rect


Added conditional to skip render model if model in doc root is not component

--- bokehjs/src/coffee/common/embed.coffee
+++ bokehjs/src/coffee/common/embed.coffee
@@ -48,6 +48,8 @@
   # the views we create.
   views = {}
   render_model = (model) ->
+    if not model.default_view?
+      return
     view = _create_view(model)
     views[model.id] = view
     $(element).append(view.$el)


:bug: Fix broken access to minimap for editor when editor is undefined

Fixes #200

--- lib/main-v4.coffee
+++ lib/main-v4.coffee
@@ -29,7 +29,7 @@
       @toggled = true
       @initSubscriptions()

-  minimapForEditor: (editor) -> @editorsMinimaps[editor.id]
+  minimapForEditor: (editor) -> @editorsMinimaps[editor.id] if editor?

   observeMinimaps: (iterator) ->
     return unless iterator?
@@ -46,7 +46,7 @@

     @subscriptions.add atom.workspace.observeTextEditors (textEditor) =>
       return if @editorsMinimaps[textEditor.id]?
-
+
       minimap = new Minimap({textEditor})
       @editorsMinimaps[textEditor.id] = minimap


Use Spree.url() to add tokens to API calls to stock_locations

On 2.0, stock_locations AJAX class are getting a 401 on http://localhost:3000/admin/stock_transfers/new since securing the stock locations API end points.

Fixes #3828

--- backend/app/assets/javascripts/admin/stock_transfer.js.coffee
+++ backend/app/assets/javascripts/admin/stock_transfer.js.coffee
@@ -30,7 +30,7 @@

       $('#transfer_receive_stock').change (event) => @receive_stock_change(event)

-      $.getJSON Spree.routes.stock_locations_api, (data) =>
+      $.getJSON Spree.url(Spree.routes.stock_locations_api), (data) =>
         @locations = (location for location in data.stock_locations)
         @force_receive_stock() if @locations.length < 2

@@ -92,13 +92,13 @@
       if @cached_variants?
         @populate_select @cached_variants
       else
-        $.getJSON Spree.routes.variants_api, (data) =>
+        $.getJSON Spree.url(Spree.routes.variants_api), (data) =>
           @cached_variants = _.map(data.variants, (variant) -> new TransferVariant(variant))
           @populate_select @cached_variants

     _refresh_transfer_stock_items: ->
       stock_location_id = $('#transfer_source_location_id').val()
-      $.getJSON Spree.routes.stock_locations_api + "/#{stock_location_id}/stock_items", (data) =>
+      $.getJSON Spree.url(Spree.routes.stock_locations_api + "/#{stock_location_id}/stock_items"), (data) =>
         @populate_select _.map(data.stock_items, (stock_item) -> new TransferStockItem(stock_item))

     populate_select: (variants) ->
@@ -177,5 +177,3 @@
     transfer_locations = new TransferLocations
     transfer_variants = new TransferVariants
     transfer_add_variants = new TransferAddVariants
-
-


fix #49 Don't try open file if no file opened

--- lib/related.coffee
+++ lib/related.coffee
@@ -56,6 +56,9 @@
     atom.commands.add 'atom-workspace', 'titanium:closeAllRelatedPanes': => @closeAllRelatedPanes()

   openFile: (type, options = {})->
+    editor = atom.workspace.getActiveTextEditor()
+    return unless editor
+
     options.searchAllPanes = true
     atom.workspace.open getTargetPath(type), options

@@ -92,6 +95,8 @@
     editor = atom.workspace.getActiveTextEditor()
     relatedFilePaths = getRelatedFilePath(editor.getPath())
     allEditors = atom.workspace.getTextEditors()
+
+    return unless editor

     # find and close
     for editor in allEditors


Remove unused routes

Remove unused routes

--- lib/routing.coffee
+++ lib/routing.coffee
@@ -7,9 +7,3 @@
 	Router.route '/',
 		action: ->
 			@render 'home'
-	Router.route '/recommended',
-		action: ->
-			@render 'recommended'
-	Router.route '/bookmarks',
-		action: ->
-			@render 'bookmarks'


fixed post event

fixed post event

--- lib/scrapfy.coffee
+++ lib/scrapfy.coffee
@@ -68,6 +68,8 @@
     scrapfyViewState: @scrapfyView.serialize()

   create: ->
+    that = this
+
     atom.workspace.observeTextEditors (editor) ->
       grammar = editor.getGrammar().scopeName
       selection = editor.getSelectedText()
@@ -76,7 +78,7 @@
       content = (if selection then selection else allText)
       lang = langsMap[grammar]

-      @post(
+      that.post(
         content: content
         lang: lang
       )


requiring request

requiring request

--- lib/storage.coffee
+++ lib/storage.coffee
@@ -1,6 +1,7 @@
 exec          = (require 'child_process').exec
 fs            = require 'fs-extra'
 path          = require 'path'
+request       = require 'request'

 module.exports = (agent, mqtt, config) ->
   remoteFs = (cmd, payload, cb) ->