If vm has shared workspaces more than 1 remove workspace from in shared vm while the participant is leaving from collaboration.

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -941,7 +941,15 @@

   removeMachineNode: ->

-    kd.singletons.mainView.activitySidebar.removeMachineNode @mountedMachine
+    { activitySidebar } = kd.singletons.mainView
+
+    machineBox = activitySidebar.getMachineBoxByMachineUId @mountedMachineUId
+
+    if machineBox.listController.getItemCount() > 1
+      machineBox.removeWorkspace @workspaceData.getId()
+    else
+      activitySidebar.removeMachineNode @mountedMachine
+
     environmentDataProvider.removeCollaborationMachine @mountedMachine


collaboration: controller add state machine

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -1,3 +1,4 @@
+machina                       = require 'machina'
 remote                        = require('app/remote').getInstance()
 dateFormat                    = require 'dateformat'
 sinkrow                       = require 'sinkrow'
@@ -685,6 +686,38 @@
     IDEMetrics.collect 'StatusBar.collaboration_button', 'shown'

+  initCollaborationStateMachine: ->
+
+    @stateMachine = new machina.Fsm
+      initialState: 'uninitialized'
+      states:
+        uninitialized:
+          _onEnter: @bound 'onCollaborationUninitialized'
+        loading:
+          _onEnter: @bound 'onCollaborationLoading'
+        active:
+          _onEnter: @bound 'onCollaborationActive'
+        terminated:
+          _onEnter: @bound 'onCollaborationTerminated'
+        notAuthorized:
+          _onEnter: @bound 'onCollaborationNotAuthorized'
+
+
+  onCollaborationUninitialized: ->
+
+
+  onCollaborationLoading: ->
+
+
+  onCollaborationActive: ->
+
+
+  onCollaborationTerminated: ->
+
+
+  onCollaborationNotAuthorized: ->
+
+
   prepareCollaboration: ->

     @rtm        = new RealtimeManager


alphabetical order fix

alphabetical order fix

--- client/landing/app/MainApp/fs/fshelper.coffee
+++ client/landing/app/MainApp/fs/fshelper.coffee
@@ -20,7 +20,8 @@

     sortedFiles = []
     for p in [yes, no]
-      z = (x for x in files when x.isDir is p).sort (x,y)-> x.name > y.name
+      z = (x for x in files when x.isDir is p).sort (x,y)->
+        x.name.toLowerCase() > y.name.toLowerCase()
       sortedFiles.push x for x in z

     nickname = KD.nick()


Use listCopy in other tests + Different wording for spec labels

--- spec/list-edit-spec.coffee
+++ spec/list-edit-spec.coffee
@@ -2,6 +2,11 @@

 describe 'ListEdit', ->
   [editor, editorView] = []
+
+  listCopyFrom = (txt, startRow, startCol, endRow=startRow, endCol=startCol) ->
+    editor.setText txt
+    editor.setSelectedBufferRange [[startRow, startCol], [endRow, endCol]]
+    atom.commands.dispatch editorView, 'list-edit:copy'

   beforeEach ->
     waitsForPromise ->
@@ -37,39 +42,35 @@
       expect(editor.getText()).toBe '[Blinky, Inky, Clyde]'

   describe 'list-paste', ->
-    it 'Cursor before first element of multi-element list pastes clipboard element with correct whitespace and separator', ->
-      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+    it 'pastes clipboard element with correct whitespace and separator when cursor is before first element of multi-element list', ->
+      listCopyFrom '[NewGhost]', 0, 2
       editor.setText '[Blinky, Pinky, Inky, Clyde]'
       editor.setCursorBufferPosition [0, 1]
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '[NewGhost, Blinky, Pinky, Inky, Clyde]'

-    it 'Cursor before between elements of multi-element list pastes clipboard element with correct whitespace and separator', ->
-      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+    it 'pastes clipboard element with correct whitespace and separator when cursor is before between elements of multi-element list', ->
+      listCopyFrom '[NewGhost]', 0, 2
       editor.setText '[Blinky, Pinky, Inky, Clyde]'
       editor.setCursorBufferPosition [0, 7]
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '[Blinky, NewGhost, Pinky, Inky, Clyde]'

-    it 'Cursor after last element of multi-element list pastes clipboard element with correct whitespace and separator', ->
-      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+    it 'pastes clipboard element with correct whitespace and separator when cursor is after last element of multi-element list', ->
+      listCopyFrom '[NewGhost]', 0, 2
       editor.setText '[Blinky, Pinky, Inky, Clyde]'
       editor.setCursorBufferPosition [0, 27]
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '[Blinky, Pinky, Inky, Clyde, NewGhost]'

-    it 'Cursor inside element of multi-element list replaces element with clipboard element', ->
-      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+    it 'replaces element with clipboard element when cursor is inside element of multi-element list', ->
+      listCopyFrom '[NewGhost]', 0, 2
       editor.setText '[Blinky, Pinky, Inky, Clyde]'
       editor.setCursorBufferPosition [0, 10]
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '[Blinky, NewGhost, Inky, Clyde]'

   describe 'list-copy/paste between lists', ->
-    listCopyFrom = (txt, row, col) ->
-      editor.setText txt
-      editor.setCursorBufferPosition [row, col]
-      atom.commands.dispatch editorView, 'list-edit:copy'

   # 0123456789012345678
     verticalListTxt = '''


deal with corrupt data in repo history

deal with corrupt data in repo history

--- scripts/gh-book/session.coffee
+++ scripts/gh-book/session.coffee
@@ -76,8 +76,11 @@
       @getClient().getRepo(repoUser, repoName) if repoUser and repoName

     getHistory: ->
-      history = JSON.parse(localStorage.oerRepoHistory)
-      history = [] if not history?.length
+      try
+        history = JSON.parse(localStorage.oerRepoHistory)
+        history = [] if not history?.length
+      catch err
+        history = []

       history


Experiment with bumping up the timeout

Experiment with bumping up the timeout

--- spec/pane-view-spec.coffee
+++ spec/pane-view-spec.coffee
@@ -164,7 +164,7 @@
       expect(pane.items).toHaveLength(5)

       fs.removeSync(filePath)
-      waitsFor ->
+      waitsFor 30000, ->
         pane.items.length == 4

   describe "when a pane is destroyed", ->


add comment about ZPOPMIN

add comment about ZPOPMIN

--- services/document-updater/app/coffee/RedisManager.coffee
+++ services/document-updater/app/coffee/RedisManager.coffee
@@ -305,6 +305,7 @@
 			return callback() if !reply?.length # return if no projects ready to be processed
 			# pop the oldest entry (get and remove in a multi)
 			multi = rclient.multi()
+			# Poor man's version of ZPOPMIN, which is only available in Redis 5.
 			multi.zrange keys.flushAndDeleteQueue(), 0, 0, "WITHSCORES"
 			multi.zremrangebyrank keys.flushAndDeleteQueue(), 0, 0
 			multi.zcard keys.flushAndDeleteQueue() # the total length of the queue (for metrics)


Added Bootstrap classes to Dialog #52

Added Bootstrap classes to Dialog #52

--- wzk/ui/Dialog.coffee
+++ wzk/ui/Dialog.coffee
@@ -3,6 +3,7 @@
 goog.require 'goog.ui.Dialog.ButtonSet'
 goog.require 'goog.string'
 goog.require 'goog.ui.Dialog.DefaultButtonKeys'
+goog.require 'wzk.ui.CloseIcon'

 class wzk.ui.Dialog extends goog.ui.Dialog

@@ -30,3 +31,85 @@

   open: ->
     @setVisible true
+
+  ###*
+    @override
+  ###
+  setVisible: (visible) ->
+    super visible
+    goog.style.setStyle @getElement(), 'display', 'block'
+    goog.style.setElementShown @contentWrapperEl, visible
+
+  ###*
+    @protected
+  ###
+  buildHeader: ->
+    @titleTextEl_ = @dom_.createDom('h4', 'modal-title', @title_)
+    attrs = {'className': 'modal-header', 'id': @getId()}
+    @titleEl_ = @dom_.createDom('div', attrs)
+    if @hasTitleCloseButton_
+      icon = new wzk.ui.CloseIcon dom: @dom_
+      icon.render @titleEl_
+      @titleCloseEl_ = icon.getElement()
+    @titleEl_.appendChild @titleTextEl_
+    @titleId_ = @titleEl_.id
+
+  ###*
+    @protected
+  ###
+  buildFooter: ->
+    @buttonEl_ = @dom_.createDom 'div', 'modal-footer'
+    if @buttons_
+      @buttons_.attachToElement @buttonEl_
+
+    goog.style.setElementShown @buttonEl_, !!@buttons_
+
+  ###*
+    @protected
+  ###
+  decorateAria: ->
+    goog.a11y.aria.setRole @contentWrapperEl, @getPreferredAriaRole()
+    goog.a11y.aria.setState @contentWrapperEl, goog.a11y.aria.State.LABELLEDBY, @titleId_ || ''
+
+  ###*
+    @protected
+  ###
+  manageBgMask: ->
+    @manageBackgroundDom_()
+    @createTabCatcher_()
+    goog.dom.classes.set @bgEl_, 'modal-backdrop fade in'
+
+  ###*
+    @protected
+  ###
+  applyContent: ->
+    # If setContent() was called before createDom(), make sure the inner HTML of
+    # the content element is initialized.
+    if @content_
+      @contentEl_.innerHTML = @content_
+
+  ###*
+    @override
+  ###
+  createDom: ->
+    dom = @getDomHelper()
+    @element_ = dom.el 'div', 'modal fade in'
+    main = dom.el 'div', 'modal-dialog', @element_
+    @contentWrapperEl = el = dom.el 'div', 'modal-content', main
+
+    goog.dom.classes.add el, @getCssClass()
+    goog.dom.setFocusableTabIndex el, true
+    goog.style.setElementShown el, false
+
+    @manageBgMask()
+
+    @buildHeader()
+    @contentEl_ = dom.createDom 'div', 'modal-body'
+    @buildFooter()
+
+    goog.dom.append el, @titleEl_, @contentEl_, @buttonEl_
+
+    @decorateAria()
+    @applyContent()
+
+    @setBackgroundElementOpacity @backgroundElementOpacity_


Remove unneeded calls to rootView.deactivate

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -20,9 +20,6 @@
     sampleTxt = treeView.find('.file:contains(tree-view.txt)')

     expect(treeView.root.directory.subscriptionCount()).toBeGreaterThan 0
-
-  afterEach ->
-    rootView.deactivate()

   describe ".initialize(project)", ->
     it "renders the root of the project and its contents alphabetically with subdirectories first in a collapsed state", ->
@@ -616,7 +613,6 @@
       fileView = treeView.find('.file:contains(test-file.txt)').view()

     afterEach ->
-      rootView.deactivate()
       fs.remove(rootDirPath) if fs.exists(rootDirPath)

     describe "tree-view:add", ->


Use `atom.activatePackage` directly in specs

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -13,7 +13,7 @@
     project.setPath(project.resolve('tree-view'))
     window.rootView = new RootView

-    window.loadPackage("tree-view")
+    atom.activatePackage("tree-view")
     rootView.trigger 'tree-view:toggle'
     treeView = rootView.find(".tree-view").view()
     treeView.root = treeView.find('ol > li:first').view()
@@ -50,7 +50,7 @@
         rootView.deactivate()
         window.rootView = new RootView()
         rootView.open()
-        treeView = window.loadPackage("tree-view").mainModule.createView()
+        treeView = atom.activatePackage("tree-view").mainModule.createView()

       it "does not attach to the root view or create a root node when initialized", ->
         expect(treeView.hasParent()).toBeFalsy()
@@ -76,13 +76,13 @@
         rootView.deactivate()
         window.rootView = new RootView
         rootView.open('tree-view.js')
-        treeView = window.loadPackage("tree-view").mainModule.createView()
+        treeView = atom.activatePackage("tree-view").mainModule.createView()
         expect(treeView.hasParent()).toBeFalsy()
         expect(treeView.root).toExist()

     describe "when the root view is opened to a directory", ->
       it "attaches to the root view", ->
-        treeView = window.loadPackage("tree-view").mainModule.createView()
+        treeView = atom.activatePackage("tree-view").mainModule.createView()
         expect(treeView.hasParent()).toBeTruthy()
         expect(treeView.root).toExist()

@@ -94,7 +94,7 @@
       rootViewState = rootView.serialize()
       rootView.deactivate()
       window.rootView = RootView.deserialize(rootViewState)
-      window.loadPackage("tree-view")
+      atom.activatePackage("tree-view")
       treeView = rootView.find(".tree-view").view()

       expect(treeView).toExist()
@@ -111,7 +111,7 @@
       window.rootView = RootView.deserialize(rootViewState)

       rootView.attachToDom()
-      window.loadPackage("tree-view")
+      atom.activatePackage("tree-view")
       treeView = rootView.find(".tree-view").view()
       expect(treeView.find(".tree-view")).toMatchSelector ':focus'

@@ -612,7 +612,7 @@

       project.setPath(rootDirPath)
       window.rootView = new RootView(rootDirPath)
-      window.loadPackage('tree-view')
+      atom.activatePackage('tree-view')
       rootView.trigger 'tree-view:toggle'
       treeView = rootView.find(".tree-view").view()
       dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()


Add some debug + add date to title for model

--- fabricator/shapeways.coffee
+++ fabricator/shapeways.coffee
@@ -82,6 +82,7 @@
       model_upload = fs.readFile file, (err, fileData) =>
         fileData = encodeURIComponent fileData.toString('base64')
         params.file = fileData;
+        params.title = params.title+" ("+(new Date().toString())+")"
         params.fileName = file;
         @oa.post(
           API_HOST+"/models/v1",
@@ -89,6 +90,7 @@
           oauth_access_token_secret,
           JSON.stringify(params),
           (error, data, response) ->
+            console.log error, data, response
             if error
               callback error, null
             else


axis labels

axis labels

--- app/controllers/Scatterplot.coffee
+++ app/controllers/Scatterplot.coffee
@@ -53,10 +53,14 @@

     $("##{@channel} .x-axis").change (e) =>
       @xAxisKey = e.currentTarget.value
+      @graph.xAxis
+        .axisLabel(@xAxisKey)
       @addData()

     $("##{@channel} .y-axis").change (e) =>
       @yAxisKey = e.currentTarget.value
+      @graph.yAxis
+        .axisLabel(@yAxisKey)
       @addData()

   addData: (options) ->


`host.controller`.

`host.controller`.

--- src/spec.coffee
+++ src/spec.coffee
@@ -195,7 +195,20 @@

           address: get('address')

-          controller: get('controller')
+          controller: @dynamic (
+            ->
+              for ref in @generator.resident_VMs
+                UUID = refsToUUIDs[ref]
+                VM = @collection.get UUID
+                return UUID if VM?.type is 'VM-controller'
+              null
+          ), {
+            'VM-controller': {
+              enter: (controller, UUID) ->
+                if controller.$container is @value.UUID
+                  @field = UUID
+            }
+          }

           CPUs: [] # TODO


fix #68

fix #68

--- app/js/controllers/transfer.coffee
+++ app/js/controllers/transfer.coffee
@@ -145,15 +145,19 @@
             my_transfer_form?.payto.tip_registered = ""
             $scope.account_registration_date = ""
             Growl.notice "", "Transfer transaction broadcasted"
-        , (error) ->
-            if error.data.error.code == 20005
+        , (err) ->
+            error = err.data?.error || err.response?.data?.error
+            code  = error.code
+            error_message = error.locale_message || error.message
+
+            if error.code == 20005
                 $translate('account.unknown').then (val) ->
                     my_transfer_form.payto.error_message = val
-            else if error.data.error.code == 20010
+            else if error.code == 20010
                 $translate('market.tip.insufficient_balances').then (val) ->
                     my_transfer_form.amount.error_message = val
             else
-                my_transfer_form.payto.error_message = Utils.formatAssertException(error.data.error.message)
+                my_transfer_form.payto.error_message = Utils.formatAssertException error_message

     $scope.send = ->
         my_transfer_form.amount.error_message = null


Fix typo

Fix typo

--- app/pages/lab/project-details.cjsx
+++ app/pages/lab/project-details.cjsx
@@ -28,7 +28,7 @@

         <p>
           <label>
-            <input type="checkbox" name="configuration.user_choose_workflow" checked={@props.project.configuration?.user_choose_workflow} onChange={@handleChange} />
+            <input type="checkbox" name="configuration.user_chooses_workflow" checked={@props.project.configuration?.user_chooses_workflow} onChange={@handleChange} />
             Volunteers can choose which workflow they work on
           </label>
         </p>


teapot changed to unauthorized

teapot changed to unauthorized

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -217,7 +217,7 @@
         key     : key
       , (err, kodingKey)=>
         if err or not kodingKey
-          res.send 418 # I'm a teapot :P
+          res.send 401
         else
           res.status 200
           res.send "OK"


adding verification

adding verification

--- server/models/konnector.coffee
+++ server/models/konnector.coffee
@@ -20,9 +20,12 @@
         for name, val of parsedPasswords
             @fieldValues[name] = val
     catch error
-        console.log "injecting encrypted fields : JSON.parse error : #{error}"
+        console.log "Injecting encrypted fields : JSON.parse error : #{error}"

 Konnector::removeEncryptedFields = (fields) ->
+
+    if not fields?
+        console.log "Removing encrypted fields : error : fields variable undefined"

     password = {}
     for name, type of fields
@@ -49,6 +52,7 @@
             @injectEncryptedFields()
             konnectorModule.fetch @fieldValues, (err) =>
                 @removeEncryptedFields fields
+
                 if err
                     data =
                         isImporting: false


make meteor-roles group optional

make meteor-roles group optional

--- server/boot.coffee
+++ server/boot.coffee
@@ -29,7 +29,12 @@
       if not Meteor.user()
         return false

-      if Voltage.settings.adminRole and not Roles.userIsInRole(Meteor.user(), Voltage.settings.adminRole, Voltage.settings.adminGroup)
+      # if role AND group is passed
+      if Voltage.settings.adminRole and Voltage.settings.adminGroup and not Roles.userIsInRole(Meteor.user(), Voltage.settings.adminRole, Voltage.settings.adminGroup)
+        return false
+
+      # if only role is passed
+      if Voltage.settings.adminRole and not Roles.userIsInRole(Meteor.user(), Voltage.settings.adminRole)
         return false

       true


just use the normal event handler

just use the normal event handler

--- app/pages/admin/user-settings.cjsx
+++ app/pages/admin/user-settings.cjsx
@@ -67,37 +67,17 @@

   getDefaultProps: ->
     editUser: null
-    invalidLimit: "invalidLimit"

   getInitialState: ->
     error: null
-
-  validLimit: (limit) ->
-    if limit == ""
-      false
-    else
-      n = ~ ~Number(limit)
-      String(n) == limit and n >= 0
-
-  updateLimit: (e) ->
-    _subject_limit = this.refs.subjectLimit.value
-    if @validLimit(_subject_limit)
-      handleInputChange.call(@props.editUser, e)
-    else
-      @setState(error: @props.invalidLimit)
-
-  errorMessage: ->
-    if @state.error == @props.invalidLimit
-      "Must be a positive integer"
-    else if @state.error == @props.invalidLimit
-      "kasjdhkdjshf"

   render: ->

       <AutoSave resource={@props.editUser}>
         Subject Limit:{' '}
-        <input type="number" min="1" ref="subjectLimit" defaultValue={@props.editUser.subject_limit} onBlur={@updateLimit} onChange={handleInputChange.bind @props.editUser} />
-        { @errorMessage() }
+        <input type="number" min="1" ref="subjectLimit" name="subject_limit"
+          value={@props.editUser.subject_limit}
+          onChange={handleInputChange.bind @props.editUser} />
       </AutoSave>


Adding in markdown toolbar to Announcements textbox (#3318)

* Adding in markdown  toolbar to Announcements textbox

took a stab at adding the markdown editing toolbar to the Announcements textbox

* fixed p to div

fixed p to div

--- app/pages/lab/project-details.cjsx
+++ app/pages/lab/project-details.cjsx
@@ -123,14 +123,14 @@
             Add text here when you have multiple workflows and want to help your volunteers decide which one they should do. <CharLimit limit={500} string={@props.project.workflow_description ? ''} />

-

+

             <AutoSave resource={@props.project}>
               Announcement Banner

-              <textarea className="standard-input full" name="configuration.announcement" value={@props.project.configuration?.announcement} onChange={handleInputChange.bind @props.project} />
+              <MarkdownEditor className="full" name="configuration.announcement" rows="2" value={@props.project.configuration?.announcement} project={@props.project} onChange={handleInputChange.bind @props.project} onHelp={-> alert <MarkdownHelp/>}/>
             </AutoSave>
             This text will appear as a banner at the top of all your project's pages. Only use this when you've got a big important announcement to make!
-

+

             <AutoSave resource={@props.project}>


Activity: minor widget sidebarupdate

Activity: minor widget sidebarupdate

--- client/Social/Activity/views/universitywidget.coffee
+++ client/Social/Activity/views/universitywidget.coffee
@@ -19,6 +19,6 @@

Getting started with IDE Workspaces

What ports are open on my VM?

-
More guides on Koding University...
-

+        More guides on Koding University...
+

     """


Client: Activity: Updated componentWillUpdate event handler to set @isMessageBeingSubmitted prop

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -21,8 +21,7 @@
     return  unless nextProps?.thread

     { thread } = nextProps
-    isMessageBeingSubmitted = thread.getIn ['flags', 'isMessageBeingSubmitted']
-    @shouldScrollToBottom = yes  if isMessageBeingSubmitted
+    @isMessageBeingSubmitted = thread.getIn ['flags', 'isMessageBeingSubmitted']

   onTopThresholdReached: -> @props.onLoadMore()


fix sort

fix sort

--- app/scripts/CellsFormSelect.coffee
+++ app/scripts/CellsFormSelect.coffee
@@ -31,10 +31,16 @@
       for k in Object.keys(@values).sort(
         (a,b) ->
           if (a == "")
-            return b == "" ? 0 : -1
+            if b == "" then return 0 else return -1
           if (b == "")
             return 1
-          return a - b
+
+          nA = parseInt(a, 10)
+          nB = parseInt(b, 10)
+          if (isNaN(nA) || isNaN(nB))
+            return a - b
+          else
+            return nA - nB
       )
         option = $('<option></option>').val(k).html(@values[k])
         @select.append option


Fix mail

Fix mail

--- server/mail.coffee
+++ server/mail.coffee
@@ -4,7 +4,9 @@
   secret = Meteor.settings.mandrillSecret
   if !secret?
     throw new Error('You must define mandrillSecret in Meteor\'s settings')
-  process.env.MAIL_URL = "smtp://#{encodeURIComponent("arve.knudsen@gmail.com")}:" +
+  process.env.MAIL_URL = "smtp://#{encodeURIComponent("musitechub@gmail.com")}:" +
     "#{encodeURIComponent(secret)}@#{encodeURIComponent("smtp.mandrillapp.com")}:587"
-  logger.debug("SMTP server URL: '#{process.env.MAIL_URL}'")
+  loggableUrl = "smtp://#{encodeURIComponent("musitechub@gmail.com")}:" +
+    "SECRET@#{encodeURIComponent("smtp.mandrillapp.com")}:587"
+  logger.debug("SMTP server URL: '#{loggableUrl}'")
 )


Imrove invalid responses body error messages.

--- src/test.coffee
+++ src/test.coffee
@@ -72,9 +72,14 @@
     assert.isNotNull body
     if @response.schema
       schema = @parseSchema @response.schema
-      assert.jsonSchema (JSON.parse body), schema, 'Response Body'
+      json = JSON.parse body
+      assert.jsonSchema json, schema, """
+        Got unexpected response body:
+        #{JSON.stringify(json, null, 4)}
+        Error
+      """

       # Update @response
-      @response.body = JSON.parse body
+      @response.body = json

 module.exports = Test


update delete command on remove

update delete command on remove

--- lib/daemon-item-collection.coffee
+++ lib/daemon-item-collection.coffee
@@ -1,17 +1,5 @@
 DaemonItem = require "./daemon-item"
 #atom.project.rootDirectories[0].path
-
-clone = (obj) ->
-  return obj if (null == obj || "object" != typeof obj)
-  copy = obj.constructor()
-  for attr of obj
-    console.log "clone #{obj[attr]}"
-    copy[attr] = obj[attr] if (obj.hasOwnProperty(attr))
-  # for (var attr in obj) {
-  #     if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
-  # }
-  return copy
-

 module.exports =
 class DaemonItemCollection
@@ -31,8 +19,8 @@
   constructor: (@eventBus,data) ->
     {@items,@checks}=data
     # data=undefined #reset list
-    console.log @items
-    console.log @checks
+    # console.log @items
+    # console.log @checks
     @reqEventBus()
     if not data?
       @items = {} =
@@ -93,10 +81,10 @@

     #update check item
     if @checks[item.cmdCheck]?
-      console.log "check:"
+      # console.log "check:"
       for i,key in @checks[item.cmdCheck]
         if i.id==item.id
-          console.log "found"
+          # console.log "found"
           @checks[item.cmdCheck].splice key,1
           break;
       @checks[item.cmdCheck].push item
@@ -130,10 +118,11 @@
       item.checkAdded = item.cmdCheck

   remove: (item) =>
-    console.log item
+    # console.log item
     #if daemonItem is in collection, +check name?
     if item.id?
       #if item is on top
+      item.command.dispose()
       if @items.inc == (@items.inc-1)
         delete @items[item.id]
       #else swap deletet with top item
@@ -159,6 +148,6 @@
     return false

   addCommands: (item) ->
-    atom.commands.add 'atom-workspace',"smart-daemon-control:configure-#{item.name}", =>
+    item.command = atom.commands.add 'atom-workspace',"smart-daemon-control:configure-#{item.name}", =>
       # console.log @items
       @eventBus.emit 'daemon-item-configure-view:show', @items[item.id]


Multiplayer levels go to ladder when you click Scores instead of to a blank single-player leaderboard.

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -290,7 +290,6 @@
     count

   showLeaderboard: (levelSlug) ->
-    #levelSlug ?= 'siege-of-stonehold'  # Testing
     leaderboardModal = new LeaderboardModal supermodel: @supermodel, levelSlug: levelSlug
     @openModalView leaderboardModal

@@ -463,7 +462,11 @@
   onClickViewSolutions: (e) ->
     levelElement = $(e.target).parents('.level-info-container')
     levelSlug = levelElement.data('level-slug')
-    @showLeaderboard levelSlug
+    level = _.find _.values(@campaign.get('levels')), slug: levelSlug
+    if level.type is 'hero-ladder'
+      Backbone.Mediator.publish 'router:navigate', route: "/play/ladder/#{levelSlug}", viewClass: 'views/ladder/LadderView', viewArgs: [{supermodel: @supermodel}, levelSlug]
+    else
+      @showLeaderboard levelSlug

   adjustLevelInfoPosition: (e) ->
     return unless @$levelInfo


Enabled Forgetful Gemsmith in the world map.

--- app/views/play/WorldMapView.coffee
+++ app/views/play/WorldMapView.coffee
@@ -620,23 +620,22 @@
     x: 32
     y: 15.5
     nextLevels:
-      more_practice: 'gem-grabber'
+      more_practice: 'forgetful-gemsmith'
       continue: 'shadow-guard'
       skip_ahead: 'true-names'
   }
   {
-    name: 'Gem Grabber'
-    type: 'hero'
-    difficulty: 1
-    id: 'gem-grabber'
-    original: '54174347844506ae0195a0b8'
+    name: 'Forgetful Gemsmith'
+    type: 'hero'
+    difficulty: 1
+    id: 'forgetful-gemsmith'
+    original: '544a98f62d002f0000fe331a'
     description: 'Grab even more gems as you practice moving.'
     x: 35.49
     y: 24.61
     nextLevels:
       continue: 'shadow-guard'
     practice: true
-    disabled: true
   }
   {
     name: 'Shadow Guard'


Use fs-plus to get home directory

Use fs-plus to get home directory

--- lib/package-generator-view.coffee
+++ lib/package-generator-view.coffee
@@ -60,7 +60,7 @@
   getPackagesDirectory: ->
     atom.config.get('core.projectHome') or
       process.env.ATOM_REPOS_HOME or
-      path.join(process.env.HOME, 'github')
+      path.join(fs.getHomeDirectory(), 'github')

   validPackagePath: ->
     if fs.existsSync(@getPackagePath())


Don't attempt to delete comments without a description (closes #2)

--- lib/services/tracker-utils.coffee
+++ lib/services/tracker-utils.coffee
@@ -78,7 +78,8 @@
       finishState = 'accepted'
     @updateStory story, {current_state: finishState},
       "Failed to finish story \"#{story.name}\".", ->
-        FileUtils.eraseComment story
+        if story.description
+          FileUtils.eraseComment story
         atom.notifications.addSuccess "Finished story \"#{story.name}\""

   updateStory: (story, new_state, errorMsg, success) ->


Parsing current SSID

Parsing current SSID

--- lib/views/select-wifi-view.coffee
+++ lib/views/select-wifi-view.coffee
@@ -65,23 +65,9 @@
     cp.exec '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I', (error, stdout, stderr) =>
       ssid = null
       if stdout != ''
-        # TODO: Get current WiFi
-        #     agrCtlRSSI: -59
-        #     agrExtRSSI: 0
-        #    agrCtlNoise: -92
-        #    agrExtNoise: 0
-        #          state: running
-        #        op mode: station
-        #     lastTxRate: 585
-        #        maxRate: 1300
-        # lastAssocStatus: 0
-        #    802.11 auth: open
-        #      link auth: wpa2-psk
-        #          BSSID: c8:d7:19:39:a6:75
-        #           SSID: ISSUESTAND
-        #            MCS: 7
-        #        channel: 36,80
-        console.log stdout
+        ssid = stdout.match(/\sSSID\:\s(.*)/)
+        if !!ssid
+          ssid = ssid[1]

       cp.exec '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s', (error, stdout, stderr) =>
         # TODO: Parse networks list


Key bindings; document Commands.generateKeyStateMapping().

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -101,6 +101,7 @@
     @keyToCommandRegistry = {}
     @mapKeyToCommand { key, command } for own key, command of defaultKeyMappings

+  # This generates a nested key-to-command mapping structure. There is an example in mode_key_handler.coffee.
   generateKeyStateMapping: ->
     # Keys are either literal characters, or "named" - for example <a-b> (alt+b), <left> (left arrow) or <f12>
     # This regular expression captures two groups: the first is a named key, the second is the remainder of


Export the new enums

Export the new enums

--- bokehjs/src/coffee/core/properties.coffee
+++ bokehjs/src/coffee/core/properties.coffee
@@ -285,6 +285,8 @@
   String: String
   TextAlign: TextAlign
   TextBaseline: TextBaseline
+  Distribution: Distribution
+  TransformStepMode: TransformStepMode

   AngleSpec: AngleSpec
   ColorSpec: ColorSpec


Update embedded copy of Normalize.css in HTMLDoc snippet

Refs Alhadis/Snippets@893310e5.

--- .config/snippets.cson
+++ .config/snippets.cson
@@ -22,7 +22,7 @@
 "*":
 	"HTML Document":
 		prefix: "htmldoc"
-		body: '<!DOCTYPE html>\n\n\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n<meta http-equiv="X-UA-Compatible" content="IE=edge" />\n<meta name="viewport" content="initial-scale=1, minimum-scale=1" />\n<title>$1</title>\n\n\n\n\n\n\n\t\n\t$2\n\t\n\n'
+		body: '<!DOCTYPE html>\n\n\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n<meta http-equiv="X-UA-Compatible" content="IE=edge" />\n<meta name="viewport" content="initial-scale=1, minimum-scale=1" />\n<title>$1</title>\n\n\n\n\n\n\n\t\n\t$2\n\t\n\n'

 	"Node Script":
 		prefix: "node"


replace fake email address validator by something better.

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -1471,15 +1471,30 @@
 # Other account Management functionality shared between client and server
 #################################################

-#check = require('validator').check
+reValidEmail = (() ->
+    sQtext = "[^\\x0d\\x22\\x5c\\x80-\\xff]"
+    sDtext = "[^\\x0d\\x5b-\\x5d\\x80-\\xff]"
+    sAtom = "[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+"
+    sQuotedPair = "\\x5c[\\x00-\\x7f]"
+    sDomainLiteral = "\\x5b(" + sDtext + "|" + sQuotedPair + ")*\\x5d"
+    sQuotedString = "\\x22(" + sQtext + "|" + sQuotedPair + ")*\\x22"
+    sDomain_ref = sAtom
+    sSubDomain = "(" + sDomain_ref + "|" + sDomainLiteral + ")"
+    sWord = "(" + sAtom + "|" + sQuotedString + ")"
+    sDomain = sSubDomain + "(\\x2e" + sSubDomain + ")*"
+    sLocalPart = sWord + "(\\x2e" + sWord + ")*"
+    sAddrSpec = sLocalPart + "\\x40" + sDomain # complete RFC822 email address spec
+    sValidEmail = "^" + sAddrSpec + "$" # as whole string
+    return new RegExp(sValidEmail)
+)()

 exports.is_valid_email_address = (email) ->
-    return "@" in email # TODO: currently validator disabled due to issue browserify-ing validator
-    #try
-    #    check(email).isEmail()
-    #    return true
-    #catch err
-    #    return false
+    # From http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
+    # but converted to Javascript; it's near the middle but claims to be exactly RFC822.
+    if reValidEmail.test(email)
+        return true
+    else
+        return false

 exports.is_valid_password = (password) ->
     if password.length >= 6 and password.length <= 64


fix template test

fix template test

--- components/artwork_filter/test/view.coffee
+++ components/artwork_filter/test/view.coffee
@@ -39,7 +39,9 @@

     describe '#render', ->
       it 'renders the container template', ->
+        @view.$el.html().should.containEql '
Works
'
         @view.$el.html().should.containEql 'artwork-filter'
+        @view.$el.html().should.containEql 'artwork-filter-selection'
         @view.$el.html().should.containEql 'artwork-section'

     it 'fetches the filter', ->
@@ -78,7 +80,7 @@
       Backbone.sync.args[0][2].success fabricate2 'filter_artworks'

     it 'renders the filter template', ->
-      @view.$filter.html().should.containEql '
Works
'
+      @view.$filter.html().should.containEql 'artwork-filter-for-sale'

   describe '#handleState', ->
     describe '#handleFilterState', ->


Update file icons when service is reset

Update file icons when service is reset

--- lib/main.coffee
+++ lib/main.coffee
@@ -33,7 +33,9 @@

   consumeFileIcons: (service) ->
     FileIcons.setService(service)
-    @fileIconsDisposable = service.onWillDeactivate -> FileIcons.resetService()
+    @fileIconsDisposable = service.onWillDeactivate ->
+      FileIcons.resetService()
+      @treeView?.updateRoots()
     @treeView?.updateRoots()

   serialize: ->


Update configuration schema to reflect that we expect an array of BibTeX file names.

--- lib/main.coffee
+++ lib/main.coffee
@@ -3,8 +3,10 @@
 module.exports =
   config:
     bibtex:
-      type: 'string'
-      default: ''
+      type: 'array'
+      default: []
+      items:
+        type: 'string'
     scope:
       type: 'string'
       default: '.source.gfm'


add note to main.coffee

add note to main.coffee

--- lib/main.coffee
+++ lib/main.coffee
@@ -11,6 +11,7 @@

   constructor: ->
     LOCALE = atom.config.get('atom-i18n.locale')
+    # BUG when running spec, LOCALE is not initialized
     @defM = CSON.load path.join __dirname, "../def", LOCALE, "menu_#{process.platform}.cson"
     @defC = CSON.load path.join __dirname, "../def", LOCALE, "context.cson"
     @defS = CSON.load path.join __dirname, "../def", LOCALE, "settings.cson"


:art: Simplify error message

:art: Simplify error message

--- lib/main.coffee
+++ lib/main.coffee
@@ -54,13 +54,10 @@
           parameters.push('--extract', 'always')
         parameters.push('-')
         return helpers.execNode(@executablePath, parameters, {stdin: text}).then (output) ->
-          try
-            output = JSON.parse(output).result
-          catch error
-            atom.notifications.addError("Invalid Result received from JSHint",
-              {detail: "Check your console for more info. It's a known bug on OSX. See https://github.com/AtomLinter/Linter/issues/726", dismissable: true})
-            console.log('JSHint Result:', output)
+          unless output.length
+            atom.notifications.addError("Error Executing JSHint executable", {detail: "It's a known bug on OSX. See https://github.com/AtomLinter/Linter/issues/726", dismissable: true})
             return []
+          output = JSON.parse(output).result
           output = output.filter((entry) -> entry.error.id)
           return output.map (entry) ->
             error = entry.error


fix FixedCostStore.update -> PUT /fixed_costs/:id

--- src/node_modules/fixed-costs-store/fixed-costs-store.coffee
+++ src/node_modules/fixed-costs-store/fixed-costs-store.coffee
@@ -1,17 +1,13 @@
-module.exports = (Restangular, $http, config) ->
+module.exports = (Restangular) ->

   create: (fixedCost) ->
     Restangular.all('fixed_costs').post(fixedCost)

   update: (fixedCost) ->
-    $http(
-      url: config.apiEndpoint + '/fixed_costs/' + fixedCost.id
-      method: "PUT"
-      data: fixedCost
-      headers:
-        Accept: "application/json"
-      withCredentials: true
-    )
+    Restangular.one('fixed_costs', fixedCost.id)
+    .customPUT(fixedCost, '',
+      {fixed_cost: fixedCost},
+      {Accept: "application/json"})

   save: (fixedCost) ->
     if fixedCost.id


Use package instead of package's view in specs

Event logs are now stored on the package and passed
to the view when toggled.

--- src/packages/command-logger/spec/command-logger-spec.coffee
+++ src/packages/command-logger/spec/command-logger-spec.coffee
@@ -6,9 +6,8 @@

   beforeEach ->
     rootView = new RootView(require.resolve('fixtures/sample.js'))
-    atom.loadPackage('command-logger').getInstance()
+    commandLogger = atom.loadPackage('command-logger')
     editor = rootView.getActiveEditor()
-    commandLogger = CommandLogger.instance

   afterEach ->
     rootView.deactivate()
@@ -44,9 +43,11 @@

   describe "when an event is ignored", ->
     it "does not create a node for that event", ->
-      commandLogger.ignoredEvents.push 'editor:delete-line'
+      commandLoggerView = commandLogger.getInstance()
+      commandLoggerView.ignoredEvents.push 'editor:delete-line'
       editor.trigger 'editor:delete-line'
-      nodes = commandLogger.createNodes()
+      commandLoggerView.eventLog = commandLogger.eventLog
+      nodes = commandLoggerView.createNodes()
       for node in nodes
         continue unless node.name is 'Editor'
         for child in node.children


Re-enable all the unit tests

Re-enable all the unit tests

--- src/resources/apps/xforms-sandbox/samples/unit-tests.coffee
+++ src/resources/apps/xforms-sandbox/samples/unit-tests.coffee
@@ -3,32 +3,32 @@

 # Test pages to run
 TestManager.setPages [
-#    "test-underscore"
-#    "test-message"
-#    "test-orbeon-dom"
-#    "test-xforms-controls"
-#    "test-control-xhtml-area"
-#    "test-deferred-client-events"
-#    "test-trigger-modal"
-#    "test-upload-replace-instance"
-#    "test-setbasepaths"
-#    "test-output-update"
-#    "test-custom-mips"
-#    "test-repeat"
-#    "test-error-ajax"
-#    "test-bug-checkbox-update"
-#    "test-do-update"
-#    "test-loading-indicator"
+    "test-underscore"
+    "test-message"
+    "test-orbeon-dom"
+    "test-xforms-controls"
+    "test-control-xhtml-area"
+    "test-deferred-client-events"
+    "test-trigger-modal"
+    "test-upload-replace-instance"
+    "test-setbasepaths"
+    "test-output-update"
+    "test-custom-mips"
+    "test-repeat"
+    "test-error-ajax"
+    "test-bug-checkbox-update"
+    "test-do-update"
+    "test-loading-indicator"
     "test-disabled-nested"
-#    "test-bug-checkbox-update"
-#    "test-repeat-setvalue"
-#    "xbl/orbeon/accordion/accordion-unittest"
-#    "xbl/orbeon/datatable/datatable-unittest"
-#    "xbl/orbeon/datatable/datatable-structure-unittest"
-#    "xbl/orbeon/datatable/datatable-dynamic-unittest"
-#    "xbl/orbeon/autocomplete/autocomplete-unittest"
-#    "xbl/orbeon/currency/currency-unittest"
-#    "xbl/orbeon/button/button-unittest"
+    "test-bug-checkbox-update"
+    "test-repeat-setvalue"
+    "xbl/orbeon/accordion/accordion-unittest"
+    "xbl/orbeon/datatable/datatable-unittest"
+    "xbl/orbeon/datatable/datatable-structure-unittest"
+    "xbl/orbeon/datatable/datatable-dynamic-unittest"
+    "xbl/orbeon/autocomplete/autocomplete-unittest"
+    "xbl/orbeon/currency/currency-unittest"
+    "xbl/orbeon/button/button-unittest"
 ]

 ORBEON.xforms.Events.orbeonLoadedEvent.subscribe () ->


kipling related-article define spec test

--- test/specs/spec-simplerng.coffee
+++ test/specs/spec-simplerng.coffee
@@ -51,7 +51,20 @@

       before ->
         loader = new RNGParser()
+        loader.process kipling

       it "does not throw exceptions", ->
         expect(0)
-        loader.process kipling
+
+      it "finds the amusing pair of start options", ->
+        assert(loader.start.toString()).isEqualTo("(article-idp2181872 | hr-idp2536272)", "start is article OR hr")
+
+      it "handles the related-article define properly", ->
+        relatedArticle = loader.defines["related-article-idp2366528"]
+        relatedToString = """related-article-idp2366528 = element related-article { (empty | attribute id { http://www.w3.org/2001/XMLSchema-datatypes:ID }), attribute related-article-type { text }, (empty | attribute ext-link-type { text }), (empty | attribute vol { text }), (empty | attribute page { text }), (empty | attribute issue { text }), (empty | attribute elocation-id { text }), (empty | attribute journal-id { text }), (empty | attribute journal-id-type { text }), (empty | attribute http://www.w3.org/1999/xlink:type { token "simple" }), (empty | attribute http://www.w3.org/1999/xlink:href { text }), text }"""
+        assert(relatedArticle.toString()).isEqualTo(relatedToString, "related-article is defined correctly overall")
+        assert(relatedArticle.constructor.name).isEqualTo("Define")
+        assert(relatedArticle.pattern.constructor.name).isEqualTo("Element")
+        assert(relatedArticle.pattern.pattern.constructor.name).isEqualTo("Group")
+
+


WIP: Check dimensions

WIP: Check dimensions

--- spec/GetBorderlessBox.coffee
+++ spec/GetBorderlessBox.coffee
@@ -302,6 +302,7 @@
           y: 0
           width: 480
           height: 360
+        console.log res
         checkSimilar chai, res, expected, 3
         done()


Add a simple IIP test

Add a simple IIP test

--- spec/NetworkLifecycle.coffee
+++ spec/NetworkLifecycle.coffee
@@ -142,6 +142,55 @@
       loader.registerComponent 'process', 'Generator', processGenerator
       loader.registerComponent 'legacy', 'Sync', legacyBasic
       done()
+
+  describe 'with single Process API component', ->
+    c = null
+    out = null
+    before (done) ->
+      fbpData = "
+      OUTPORT=Pc.OUT:OUT
+      'hello' -> IN Pc(process/Async)
+      "
+      noflo.graph.loadFBP fbpData, (err, g) ->
+        return done err if err
+        loader.registerComponent 'scope', 'Connected', g
+        loader.load 'scope/Connected', (err, instance) ->
+          return done err if err
+          c = instance
+          done()
+    beforeEach ->
+      out = noflo.internalSocket.createSocket()
+      c.outPorts.out.attach out
+    afterEach (done) ->
+      c.outPorts.out.detach out
+      out = null
+      c.shutdown done
+    it 'should execute and finish', (done) ->
+      expected = [
+        'DATA helloPc'
+      ]
+      received = []
+      out.on 'ip', (ip) ->
+        switch ip.type
+          when 'openBracket'
+            received.push "< #{ip.data}"
+          when 'data'
+            received.push "DATA #{ip.data}"
+          when 'closeBracket'
+            received.push '>'
+      wasStarted = false
+      checkStart = ->
+        chai.expect(wasStarted).to.equal false
+        wasStarted = true
+      checkEnd = ->
+        chai.expect(received).to.eql expected
+        chai.expect(wasStarted).to.equal true
+        done()
+      c.network.once 'start', checkStart
+      c.network.once 'end', checkEnd
+
+      c.start (err) ->
+        return done err if err

   describe 'with WirePattern sending to Process API', ->
     c = null


Linting.

Linting.

--- spec/body-parser-spec.coffee
+++ spec/body-parser-spec.coffee
@@ -205,13 +205,13 @@
     """

     expect(bodyTree).toEqual [
-      {index: 4, content: [] },
+      {index: 4, content: []},
       'console.',
-      {index: 3, content: ['log'] },
+      {index: 3, content: ['log']},
       '(\'',
       {
         index: 2, content: [
-          { index: 1, content: [] }
+          {index: 1, content: []}
         ]
       },
       '\', ',
@@ -226,14 +226,14 @@
     """

     expect(bodyTree).toEqual [
-      {index: 4, content: [] },
+      {index: 4, content: []},
       'console.',
-      {index: 3, content: ['log'] },
+      {index: 3, content: ['log']},
       '(\'',
       {
         index: 2, content: [
           'uh ',
-          { index: 1, content: [] }
+          {index: 1, content: []}
         ]
       },
       '\', ',


Spec test for setMaxScreenLineLength

Spec test for setMaxScreenLineLength

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -5918,3 +5918,12 @@
   describe "::getElement", ->
     it "returns an element", ->
       expect(editor.getElement() instanceof HTMLElement).toBe(true)
+
+  describe 'setMaxScreenLineLength', ->
+    it "sets the maximum line length in the editor before soft wrapping is forced", ->
+      expect(editor.maxScreenLineLength()).toBe 500
+      editor.update({
+        maxScreenLineLength: 1500
+      })
+      expect(editor.maxScreenLineLength()).toBe 1500
+


Check for branch in pull requests

Check for branch in pull requests

--- scripts/github-hook.coffee
+++ scripts/github-hook.coffee
@@ -74,13 +74,16 @@
     data = event.data
     switch data.action
       when 'opened', 'closed', 'reopened'
-        {pull_request, sender, repository} = data
+        {pull_request, base, sender, repository} = data
+        return null unless base?.ref in ["master", "develop"]
         verb = if pull_request.merged? then pull_request.merged else data.action

         """
-        [#{sender.login}](https://github.com/#{sender.login}) #{verb} Pull Request ##{pull_request.number} on [#{repository.full_name}](https://github.com/#{repository.full_name})
+        [#{sender.login}](https://github.com/#{sender.login}) #{verb} Pull Request ##{pull_request.number} on [#{repository.full_name}##{base.ref}](https://github.com/#{repository.full_name}/tree/#{base.ref})
+
+        [#{pullRequest.title}](#{pullRequest.html_url})

-        [#{issue.title}](#{issue.html_url})
+
         """
       else null

@@ -106,7 +109,7 @@
     branchName = refs.split('/')[2]

     """
-    [#{pusher.name}](https://github.com/#{pusher.name}) pushed #{ordinal(commits.length, 'commit')} to [#{repository.full_name}/#{branchName}](https://github.com/#{repository.full_name}/tree/#{branchName})
+    [#{pusher.name}](https://github.com/#{pusher.name}) pushed #{ordinal(commits.length, 'commit')} to [#{repository.full_name}##{branchName}](https://github.com/#{repository.full_name}/tree/#{branchName})

     #{('*' + commit.message.split("\n")[0]) for commit in commits}


stub out project flusher for unit tests

stub out project flusher for unit tests

--- test/unit/coffee/HttpController/HttpControllerTests.coffee
+++ test/unit/coffee/HttpController/HttpControllerTests.coffee
@@ -13,6 +13,7 @@
 				flushProjectChangesAsync: sinon.stub()
 			"./ProjectManager": @ProjectManager = {}
 			"logger-sharelatex" : @logger = { log: sinon.stub() }
+			"./ProjectFlusher": {flushAllProjects:->}
 			"./Metrics": @Metrics = {}
 			"./Errors" : Errors
 		@Metrics.Timer = class Timer


correct a bug where we were using the wrong time for earliest seudat shelishit when Shabbat is 1st day of yom tob

--- source/javascripts/index/holidayTableFactory.coffee
+++ source/javascripts/index/holidayTableFactory.coffee
@@ -93,7 +93,7 @@
       rows.push(description: "Fast begins", time: zmanim.sunset().format('h:mm'))
     if hebrewDate.is1stDayOfYomTob() && hebrewDate.isShabbat()
       seudatShelishitAtHomeDescription = "Begin סְעוּדַת שְׁלִישִׁית at home before"
-      rows.push(description: "#{seudatShelishitAtHomeDescription}", time: zmanim.sunset().format('h:mm'))
+      rows.push(description: "#{seudatShelishitAtHomeDescription}", time: zmanim.samuchLeminchaKetana().format('h:mm'))
     if (hebrewDate.isShabbat() || hebrewDate.isYomTob()) && !hebrewDate.is1stDayOfPesach()  && !hebrewDate.is2ndDayOfPesach() && !hebrewDate.is1stDayOfShabuot() && !hebrewDate.isYomKippur()
       rows.push(description: "Afternoon Shiurim", time: afternoonShiurimTimes(hebrewDate, mincha))
     if hebrewDate.isRoshHashana() && hebrewDate.is1stDayOfYomTob()


fix(prod build): prevent removing space from rb-icon and rba-code when minifying html

--- rapid-build.cson
+++ rapid-build.cson
@@ -43,6 +43,12 @@
 		server:
 			js:
 				es6: true
+		html:
+			options:
+				ignoreCustomFragments: [
+					/<rba-code(\s*?.*?)*?rba-code>/gi
+					/\s?<rb-icon\s|<\/rb-icon>\s?/gi
+				]

 	order: # build will merge this with dev or prod
 		scripts:


debug

debug

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -26,7 +26,7 @@
         @_div = "

"
         menustr = ""
         for disp in @_menuitem
-            menustr += "
"+disp+"
"
+            menustr += "

"+disp+"

"
         @_div = @_div.replace(//, menustr)

         if ($(@_viewSelector+"_menu").length)


add focus method on JSTextView

add focus method on JSTextView

--- JSKit/04_JSTextView.coffee
+++ JSKit/04_JSTextView.coffee
@@ -48,6 +48,11 @@
                 $(@_viewSelector+"_textarea").html(disp)
             @setFrame(@_frame)

+    setFocus:->
+        setTimeout =>
+            $(@_viewSelector+"_textarea").focus()
+        , 0
+
     setTextSize: (@_textSize) ->
         if ($(@_viewSelector+"_textarea").length)
             $(@_viewSelector+"_textarea").css("font-size", @_textSize+"pt")


Formatting fixes

Formatting fixes

--- app/assets/javascripts/dabs.coffee
+++ app/assets/javascripts/dabs.coffee
@@ -3,11 +3,15 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/

 bell = new Howl({
-  urls: ['https://s3.amazonaws.com/glassy-collections/sounds/bell.webm', 'https://s3.amazonaws.com/glassy-collections/sounds/bell.ogg', 'https://s3.amazonaws.com/glassy-collections/sounds/bell.mp3']
+  urls: [
+    'https://s3.amazonaws.com/glassy-collections/sounds/bell.webm',
+    'https://s3.amazonaws.com/glassy-collections/sounds/bell.ogg',
+    'https://s3.amazonaws.com/glassy-collections/sounds/bell.mp3'
+  ]
 })

 reset_background = ->
-  $('html').css('background', 'white');
+  $('html').css('background', 'white')

 timer_finished = ->
   seconds = $('.dab .seconds')
@@ -35,8 +39,8 @@
   refresh_seconds()

 root.set_seconds_size = ->
-  fontSize = parseInt($('.page-content .seconds').height())+"px";
-  $('.page-content .seconds').css('font-size', fontSize);
+  fontSize = parseInt($('.page-content .seconds').height())+"px"
+  $('.page-content .seconds').css('font-size', fontSize)

 $('html').on 'change', 'form.new_dab select#dab_nail_id', (event)->
   select = $('form.new_dab select#dab_nail_id')


not assigning sections to rest of method.

--- client/src/lib/fake_reports.coffee
+++ client/src/lib/fake_reports.coffee
@@ -3,7 +3,6 @@

 class Backbone.Faker.Reports
   @create: =>
-    sections =
     new Backbone.Models.Report(
       title: "#{@getRandomReportContent('title_prefix')} #{@getRandomReportContent('title_suffix')}"
       brief: @getRandomReportContent('brief')


BF-178

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -89,6 +89,7 @@
     $(window).on("bucketloaded", => cam2d.hasChanged = true; cam2d.newBuckets = [true, true, true])

   animate : ->
+
     @renderFunction()

     window.requestAnimationFrame => @animate()
@@ -179,22 +180,12 @@

         # only the main planes
         if i<=3
-          if  cam2d.needsUpdate(plane.planeID)
-            # TODO: Why don't those lines work? it would get rid of that huge switch statement
-            #
-            #plane.props.getFkt(cam2d.getGlobalPos(), cam2d.getZoomStep(plane.props.planeID)).done (buffer) ->
-            #  plane.texture.image.data.set(buffer)
-            switch plane.planeID
-              when PLANE_XY
-                Model.Binary.getXY(cam2d.getGlobalPos(), cam2d.getZoomStep(PLANE_XY)).done (buffer) ->
-                  plane.texture.image.data.set(buffer)
-              when PLANE_YZ
-                Model.Binary.getXY(cam2d.getGlobalPos(), cam2d.getZoomStep(PLANE_XY)).done (buffer) ->
-                  plane.texture.image.data.set(buffer)
-              when PLANE_XZ
-                Model.Binary.getXY(cam2d.getGlobalPos(), cam2d.getZoomStep(PLANE_XY)).done (buffer) ->
-                  plane.texture.image.data.set(buffer)
+          if cam2d.needsUpdate plane.planeID
             cam2d.notifyNewTexture plane.planeID
+
+          Model.Binary.get(cam2d.getTexturePosition(plane.planeID), cam2d.getZoomStep(plane.planeID), [120, 120, 150, 150], plane.planeID).done (buffer) ->
+            if buffer
+              plane.texture.image.data.set(buffer)

         #only for border planes
         else if i>=7 then plane.position = new THREE.Vector3(globalPosVec.x-1, globalPosVec.y-1, globalPosVec.z-1)


Assert against config for font-size changing events instead of editor

--- spec/app/root-view-spec.coffee
+++ spec/app/root-view-spec.coffee
@@ -205,26 +205,21 @@
           expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}"

   describe "font size adjustment", ->
-    editor = null
-    beforeEach ->
-      editor = rootView.getActiveEditor()
-      editor.attachToDom()
-
     it "increases/decreases font size when increase/decrease-font-size events are triggered", ->
-      fontSizeBefore = editor.getFontSize()
+      fontSizeBefore = config.get('editor.fontSize')
       rootView.trigger 'window:increase-font-size'
-      expect(editor.getFontSize()).toBe fontSizeBefore + 1
+      expect(config.get('editor.fontSize')).toBe fontSizeBefore + 1
       rootView.trigger 'window:increase-font-size'
-      expect(editor.getFontSize()).toBe fontSizeBefore + 2
+      expect(config.get('editor.fontSize')).toBe fontSizeBefore + 2
       rootView.trigger 'window:decrease-font-size'
-      expect(editor.getFontSize()).toBe fontSizeBefore + 1
+      expect(config.get('editor.fontSize')).toBe fontSizeBefore + 1
       rootView.trigger 'window:decrease-font-size'
-      expect(editor.getFontSize()).toBe fontSizeBefore
+      expect(config.get('editor.fontSize')).toBe fontSizeBefore

     it "does not allow the font size to be less than 1", ->
       config.set("editor.fontSize", 1)
       rootView.trigger 'window:decrease-font-size'
-      expect(editor.getFontSize()).toBe 1
+      expect(config.get('editor.fontSize')).toBe 1

   describe ".open(path, options)", ->
     describe "when there is no active pane", ->


don't increase rate limit when tasks are failing

--- app/coffee/RateLimitManager.coffee
+++ app/coffee/RateLimitManager.coffee
@@ -34,5 +34,6 @@
 				@_adjustLimitDown()
 		else
 			logger.log {active: @ActiveWorkerCount, currentLimit: Math.ceil(@CurrentWorkerLimit)}, "hit rate limit"
-			@_trackAndRun task, callback # only return after task completes
-			@_adjustLimitUp()
+			@_trackAndRun task, (err) =>
+				@_adjustLimitUp() if !err? # don't increment rate limit if there was an error
+				callback(err) # only return after task completes


:art: Improve spec description

:art: Improve spec description

--- spec/atom-portable-spec.coffee
+++ spec/atom-portable-spec.coffee
@@ -19,7 +19,7 @@
       rimraf.sync(portableAtomHomePath) if fs.existsSync(portableAtomHomePath)
     rimraf.sync(portableAtomHomeBackupPath) if fs.existsSync(portableAtomHomeBackupPath)

-  it "creates portable home directory", ->
+  it "creates a portable home directory", ->
     expect(fs.existsSync(portableAtomHomePath)).toBe false

     AtomPortable.setPortable(process.env.ATOM_HOME)


Add missing path require

Add missing path require

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -1,5 +1,6 @@
 $ = require 'jquery'
 fs = require 'fs-plus'
+path = require 'path'
 temp = require('temp').track()
 {Notification} = require 'atom'
 NotificationElement = require '../lib/notification-element'


only unmount if @component is present

only unmount if @component is present

--- coach/src/concept-coach/index.cjsx
+++ coach/src/concept-coach/index.cjsx
@@ -101,7 +101,8 @@
     @removeAllListeners()

   remove: ->
-    coachWrapped.unmount()
+    coachWrapped.unmount() if @component
+    @component = null

   setOptions: (options) ->
     isSame = _.isEqual(_.pick(options, PROPS), _.pick(componentModel, PROPS))


Add waits to spec

Add waits to spec

--- spec/settings-view-spec.coffee
+++ spec/settings-view-spec.coffee
@@ -83,17 +83,20 @@
         waitsForPromise ->
           atom.workspace.open('atom://config').then (s) -> settingsView = s

+        waits 1
         runs ->
           expect(settingsView.activePanelName).toBe 'Settings'

         waitsForPromise ->
           atom.workspace.open('atom://config/themes').then (s) -> settingsView = s

+        waits 1
         runs ->
           expect(settingsView.activePanelName).toBe 'Themes'

         waitsForPromise ->
           atom.workspace.open('atom://config/install').then (s) -> settingsView = s

+        waits 1
         runs ->
           expect(settingsView.activePanelName).toBe 'Install'


Add alternate loop via setInterval.

Add alternate loop via setInterval.

--- player.coffee
+++ player.coffee
@@ -249,6 +249,7 @@
   start: ->
     @running = true
     @frame()
+    # setInterval @core.run, @fpsInterval

   stop: ->
     @running = false


Fix form validation and closing animation of cards

--- app/assets/javascripts/card.js.coffee
+++ app/assets/javascripts/card.js.coffee
@@ -35,11 +35,12 @@
     event.preventDefault()
     hideFormContainer()

-  $(document).on "click", ".card-form > .submit", (event) ->
+  $(document).on "click", ".card-form .submit", (event) ->
     validateForm event

   $(document).on "submit", ".card-form", ->
     $(this).find(".submit").hide()
+    $.fancybox.close()

   $(document).on "ajax:beforeSend", ".new-card", (xhr, data) ->
     $.fancybox.showLoading()
@@ -61,7 +62,7 @@
     $.fancybox.open
       padding: 0
       href: "#card-form-container"
-      afterClose: () ->
+      afterClose: ->
         console.log "Card closed."

   $(document).on "ajax:success", ".edit-card", (xhr, data) ->


Minor fix.

Minor fix.

--- src/MappedCollection2.coffee
+++ src/MappedCollection2.coffee
@@ -290,6 +290,7 @@
     n = hooks.length
     while not stopped and i < n
       hook.call ctx, event
+      ++i

     # TODO: Is exception handling necessary to have the wanted
     # behavior?


Simplify coffeescript syntax

Simplify coffeescript syntax

--- app/js/loader/loader_common.coffee
+++ app/js/loader/loader_common.coffee
@@ -62,7 +62,7 @@
   jslib_jail_doc = jail_window.document

   jslib_jail_doc.open()
-  jslib_jail_doc.write """<!DOCTYPE html><title></title>"""
+  jslib_jail_doc.write '<!DOCTYPE html><title></title>'
   jslib_jail_doc.close()

   script_tag = jslib_jail_doc.createElement 'script'


Add Refresh Alias

Add Refresh Alias

--- BackofficeBundle/Resources/public/coffee/adminFormView.coffee
+++ BackofficeBundle/Resources/public/coffee/adminFormView.coffee
@@ -1,25 +1,26 @@
 adminFormView = Backbone.View.extend(
   el: '#OrchestraBOModal'
   events:
-    'blur input#node_name' : 'refreshAlias'
+    'keyup input#none' : 'refreshAlias'
   initialize: (options) ->
     @url = options.url
     @method = if options.method then options.method else 'GET'
+    _.bindAll this, "refreshAlias"
     @call()
     return
   call: ->
-    current = this
+    viewContext = this
     displayLoader('.modal-body')
     $("#OrchestraBOModal").modal "show"
     $.ajax
       url: @url
       method: @method
       success: (response) ->
-        current.render(
+        viewContext.render(
           html: response
         )
       error: ->
-        $('.modal-body', current.el).html 'Erreur durant le chargement'
+        $('.modal-body', viewContext.el).html 'Erreur durant le chargement'
     return
   render: (options) ->
     @html = options.html
@@ -29,19 +30,25 @@
       PO.formPrototypes.addPrototype $(this)
       return
     @addEventOnForm()
+    @bindEvent()
   addEventOnForm: ->
-    current = this
+    viewContext = this
     $("form", @$el).on "submit", (e) ->
       e.preventDefault() # prevent native submit
       $(this).ajaxSubmit
         statusCode:
           200: (response) ->
-            view = current.render(
+            view = viewContext.render(
               html: response
             )
             Backbone.history.loadUrl(Backbone.history.fragment)
     return
   refreshAlias: (event) ->
+    console.log 'RefreshAlias'
+    $('input#node_alias').val(event.target.value.replace(/[^a-z0-9]/gi,'_'))
+  bindEvent: ->
     if $('input#node_alias').val() is ''
-        $('input#node_alias').val(event.target.value.replace(/[^a-z0-9]/gi,'_'))
+      @events['keyup input#node_name'] = "refreshAlias"
+      _.bindAll this, "refreshAlias"
+    console.log @events
 )


remove remnants of merging conflict

remove remnants of merging conflict

--- app/assets/javascripts/profile.coffee
+++ app/assets/javascripts/profile.coffee
@@ -56,7 +56,7 @@
      else
        $('#pass-lecture-' + lectureId).hide()
      return
-=======
+
   $('input:checkbox[name^="user[lecture-"]').on 'change',  ->
     courseId = this.dataset.course
     lectureId = this.dataset.lecture
@@ -74,6 +74,5 @@
       $(this).append($('#course-card-' + course))
       $('#course-card-' + course).show()
     return
-
->>>>>>> master
-  return+
+  return


trying bit:text:edit focus

trying bit:text:edit focus

--- app/assets/javascripts/root.js.coffee
+++ app/assets/javascripts/root.js.coffee
@@ -101,11 +101,11 @@

 		# only edit if it's a text bit
 		if $(root.hoveredBit).hasClass('text')
-			showMenu()
+			# showMenu()

 			$(root.hoveredBit).find('.front.face').load "/bits/#{  root.hoveredBitIDNumber }/edit"
 			$(root.hoveredBit).addClass('editing')
-			$('#bit_content').focus()
+			$("#bit_content textarea").focus() # TODO: not working ?

 		else
 			m = showNotification "not a text bit - can't edit ", "warning"


Dedup zoom button code

Dedup zoom button code

--- coffee/react-gui/components.coffee
+++ coffee/react-gui/components.coffee
@@ -180,59 +180,42 @@
     {div} = React.DOM
     {lc} = @props
     (div {className: 'lc-zoom'},
-      LC.React.ZoomOutButton({lc}),
-      LC.React.ZoomInButton({lc}),
-    )
-
-
-LC.React.ZoomOutButton = React.createClass
-  displayName: 'ZoomOutButton'
-
-  getState: -> {isEnabled: @props.lc.scale > 0.6}
+      createZoomButton(lc, 'out')(),
+      createZoomButton(lc, 'in')(),
+    )
+
+
+createZoomButton = (lc, inOrOut) -> React.createClass
+  displayName: if inOrOut == 'in' then 'ZoomInButton' else 'ZoomOutButton'
+
+  getState: -> {
+    isEnabled: switch
+      when inOrOut == 'in' then lc.scale < 4.0
+      when inOrOut == 'out' then  lc.scale > 0.6
+  }
   getInitialState: -> @getState()
   componentDidMount: ->
     @subscriber = => @setState @getState()
-    @props.lc.on 'zoom', @subscriber
+    lc.on 'zoom', @subscriber
   componentWillUnmount: ->
-    @props.lc.removeEventListener('zoom', @subscriber)
-
-  render: ->
-    {div} = React.DOM
-    {lc} = @props
-
-    className = React.addons.classSet
-      'lc-zoom-out': true
+    lc.removeEventListener('zoom', @subscriber)
+
+  render: ->
+    {div} = React.DOM
+
+    className = "lc-zoom-#{inOrOut} " + React.addons.classSet
       'toolbar-button': true
       'thin-button': true
       'disabled': not @state.isEnabled
-    onClick = if @state.isEnabled then (=> lc.zoom(-0.2)) else ->
-
-    (div {className, onClick}, "-")
-
-
-LC.React.ZoomInButton = React.createClass
-  displayName: 'ZoomInButton'
-
-  getState: -> {isEnabled: @props.lc.scale < 4.0}
-  getInitialState: -> @getState()
-  componentDidMount: ->
-    @subscriber = => @setState @getState()
-    @props.lc.on 'zoom', @subscriber
-  componentWillUnmount: ->
-    @props.lc.removeEventListener('zoom', @subscriber)
-
-  render: ->
-    {div} = React.DOM
-    {lc} = @props
-
-    className = React.addons.classSet
-      'lc-zoom-in': true
-      'toolbar-button': true
-      'thin-button': true
-      'disabled': not @state.isEnabled
-    onClick = if @state.isEnabled then (=> lc.zoom(0.2)) else ->
-
-    (div {className, onClick}, "+")
+    zoom = switch
+      when inOrOut == 'in' then -> lc.zoom(0.2)
+      when inOrOut == 'out' then -> lc.zoom(-0.2)
+    onClick = if @state.isEnabled then zoom else ->
+
+    (div {className, onClick}, switch
+      when inOrOut == 'in' then '+'
+      when inOrOut == 'out' then '-'
+    )

 LC.React.ClearButton = React.createClass


fixed mailcheck popover

fixed mailcheck popover

--- app/assets/javascripts/util.js.coffee
+++ app/assets/javascripts/util.js.coffee
@@ -7,6 +7,7 @@
     email: $(target).val()
     suggested: (suggestion) ->
       $(target).popover({
+        trigger: "manual"
         content: "Did you mean " + suggestion.full + "?"
       }).popover("show")


payment: fix bug where payment didn't work

--- workers/social/lib/social/models/socialapi/payment.coffee
+++ workers/social/lib/social/models/socialapi/payment.coffee
@@ -72,6 +72,8 @@
     requiredParams = [ "token" , "provider"]

     validateParams requiredParams, data, (err)->
+      return callback err  if err
+
       data.accountId = getAccountId client
       url = "/payments/creditcard/update"

@@ -83,5 +85,7 @@
       if not data[param]
         return callback {message: "#{param} is required"}

+    callback null
+
   getAccountId = (client)->
     return client.connection.delegate.getId()


deepened the movie category for heather

deepened the movie category for heather

--- src/app/common/tags.coffee
+++ src/app/common/tags.coffee
@@ -60,12 +60,23 @@
     {"name" : "lord of the rings", "regex" : /(lotr)|(lord ?of ?the ?rings)|(potatoes)/},
     {"name" : "hunger games", "regex" : /((the ?)?hunger ?games)|(thg)/},
     {"name" : "harry potter" , "regex" :/(harry ?potter)/},
+    {"name" : "star wars" , "regex" :/(star ?wars)/},
     {"name": "pacific rim", "regex" : /pacific ?rim/},
+    {"name": "indiana jones", "regex" : /indiana ?jones/},
+    {"name": "the matrix", "regex" : /the ?matrix/},
+    {"name": "die hard", "regex" : /die ?hard/},
+    {"name": "pulp fiction", "regex" : /pulp ?fiction/},
+    {"name": "kill bill", "regex" : /kill ?bill/},
+    {"name": "austin powers", "regex" : /austin ?powers/},
+    {"name": "jurassic park", "regex" : /jurassic ?park/},
     {"name": "rushmore", "regex" : /rushmore/},
+    {"name": "shrek", "regex" : /shrek/},
     {"name": "the royal tenenbaums", "regex" : /(the)? ?royal ?tenenbaums/},
     {"name": "the hunger games", "regex" : /((the ?)?hunger ?games)|(thg)/},
-    {"name": "pitch perfect", "regex" : /pitch ? perfect/},
-    {"name": "the dark knight", "regex" : /(the)? ?dark ?knight/}
+    {"name": "pitch perfect", "regex" : /pitch ?perfect/},
+    {"name": "toy story", "regex" : /toy ?story/},
+    {"name": "james bond", "regex" : /james ?bond/},
+    {"name": "the dark knight", "regex" : /(the)? ?dark ?knight|batman/}
   ],
   'video games' : [
     {"name": "harvest moon", "regex" : /harvest ?moon/},


components: base: GioFileLoadContent: check input uri type

--- components/base/GioFileLoadContent.coffee
+++ components/base/GioFileLoadContent.coffee
@@ -21,6 +21,9 @@
     async: true
   , (data, groups, out, callback) ->
     return unless out.isAttached()
+    unless data?
+      c.error new Error 'Invalid filename', groups
+      return
     file = Gio.File.new_for_uri data
     file.load_contents_async null, (src, res) ->
       try


Perform whitespace check after scopes check

--- src/app/screen-line.coffee
+++ src/app/screen-line.coffee
@@ -104,8 +104,8 @@

   isComment: ->
     for token in @tokens
+      continue if token.scopes.length is 1
       continue if token.isOnlyWhitespace()
-      continue if token.scopes.length is 1
       for scope in token.scopes
         return true if _.contains(scope.split('.'), 'comment')
       break


close edit view when role changes

close edit view when role changes

--- app/coffee/modules/admin/roles.coffee
+++ app/coffee/modules/admin/roles.coffee
@@ -154,6 +154,10 @@
             else if event.keyCode == 27  # ESC key
                 toggleView()

+        $scope.$on "role:changed", ->
+            if $el.find('.edit-role').is(":visible")
+                toggleView()
+
         $scope.$on "$destroy", ->
             $el.off()


Make an API call if subject.favorite isn't present

--- app/collections/favorites-button.cjsx
+++ app/collections/favorites-button.cjsx
@@ -39,18 +39,29 @@
       .get({project_ids: @props.project?.id, favorite: true, owner: @props.user.login})
       .then ([favorites]) -> if favorites? then favorites else null

+  findSubjectInCollection: (favorites) ->
+    if @props.subject.hasOwnProperty 'favorite'
+      Promise.resolve @props.subject.favorite
+    else if favorites?
+      favorites.get('subjects', id: @props.subject.id)
+        .then ([subject]) -> subject?
+    else
+      Promise.resolve false
+
   componentWillMount: ->
-    favorited = @props.subject.favorite
-    @setState {favorited}
     @findFavoriteCollection()
       .then (favorites) =>
         @setState {favorites}
+        @findSubjectInCollection(favorites)
+        .then (favorited) =>
+          @setState {favorited}

   componentDidUpdate: (prevProps) ->
     if prevProps.subject isnt @props.subject
-      favorited = @props.subject.favorite
-      @setState {favorited}
+      @findSubjectInCollection(@state.favorites)
+      .then (favorited) =>
+        @setState {favorited}

   addSubjectTo: (collection) ->
     @setState favorited: true


updated mechanistic word report to include subheading check

--- server/methods/word.coffee
+++ server/methods/word.coffee
@@ -47,6 +47,7 @@
                                             {sort: {sortIdx: 1}}).fetch()
         data.sections.push({"description": section.sectionDesc, "children": children})
         for child in children
+            child.hasSubheading = child.subheading? and child.subheading isnt ""
             getReferences(child)
             getChildren(child)


Modified IP

Modified IP

--- community/browser/Gruntfile.coffee
+++ community/browser/Gruntfile.coffee
@@ -88,7 +88,7 @@
       proxies: [
           {
               context: '/db',
-              host: '107.170.35.162',
+              host: '0.0.0.0',
               port: 7474,
               https: false,
               changeOrigin: false


SendGraphChanges: listen to metadata changes on edges

--- components/SendGraphChanges.coffee
+++ components/SendGraphChanges.coffee
@@ -45,6 +45,7 @@
     @graph.on 'renameNode', @renameNode
     @graph.on 'addEdge', @addEdge
     @graph.on 'removeEdge', @removeEdge
+    @graph.on 'changeEdge', @changeEdge
     @graph.on 'addInitial', @addInitial
     @graph.on 'removeInitial', @removeInitial
     @graph.on 'addInport', @addInport
@@ -67,6 +68,7 @@
     @graph.removeListener 'renameNode', @renameNode
     @graph.removeListener 'addEdge', @addEdge
     @graph.removeListener 'removeEdge', @removeEdge
+    @graph.removeListener 'changeEdge', @changeEdge
     @graph.removeListener 'addInitial', @addInitial
     @graph.removeListener 'removeInitial', @removeInitial
     @graph.removeListener 'addInport', @addInport
@@ -130,6 +132,17 @@
       metadata: edge.metadata
       graph: @graph.properties.id

+  changeEdge: (edge) =>
+    @registerChange 'changeedge',
+      src:
+        node: edge.from.node
+        port: edge.from.port
+      tgt:
+        node: edge.to.node
+        port: edge.to.port
+      metadata: edge.metadata
+      graph: @graph.properties.id
+
   addInitial: (iip) =>
     @registerChange 'addinitial',
       src:


Fix bad use of Number function in the number control

--- src/coffee/cilantro/ui/field/controls/number.coffee
+++ src/coffee/cilantro/ui/field/controls/number.coffee
@@ -82,10 +82,10 @@

         getValue: ->
             from_text = @ui.lowerBound.val()
-            from_num = new Number(from_text)
+            from_num = Number(from_text)

             to_text = @ui.upperBound.val()
-            to_num = new Number(to_text)
+            to_num = Number(to_text)

             value = []


use human numbers not 0-index numbers for frame tags in analytics

--- app/controllers/subject_viewer.coffee
+++ app/controllers/subject_viewer.coffee
@@ -154,7 +154,7 @@

   onClickToggle: ({currentTarget}) =>
     selectedIndex = $(currentTarget).val()
-    AnalyticsLogger.logEvent 'frame'+selectedIndex, @classification.id, null, @classification.subject.zooniverseId
+    AnalyticsLogger.logEvent 'frame'+(selectedIndex+1), @classification.id, null, @classification.subject.zooniverseId
     @activate selectedIndex

   onClickSatellite: ->


--- app/controllers/subject_viewer.coffee
+++ app/controllers/subject_viewer.coffee
@@ -147,6 +147,7 @@

   onClickPlay: ->
     AnalyticsLogger.logEvent 'play', @classification.id, null, @classification.subject.zooniverseId
+    console.log 'playing'
     @play()

   onClickPause: ->
@@ -154,7 +155,7 @@

   onClickToggle: ({currentTarget}) =>
     selectedIndex = $(currentTarget).val()
-    friendlyIndex = parseInt $(currentTarget).val()+1
+    friendlyIndex = 1 + parseInt ($(currentTarget).val())
     AnalyticsLogger.logEvent 'frame'+friendlyIndex, @classification.id, null, @classification.subject.zooniverseId
     @activate selectedIndex


currency filter added for total_amount

currency filter added for total_amount

--- app/scripts/directives/d3Donut.coffee
+++ app/scripts/directives/d3Donut.coffee
@@ -3,7 +3,7 @@
 # contributor is an object with the following properties: total_amount, name

 angular.module('appApp.directives')
-  .directive 'd3Donut', ['ApiGet', (ApiGet)->
+  .directive 'd3Donut', ['ApiGet', '$filter', (ApiGet, $filter)->
     restrict: 'E'
     scope:
       scale: '='
@@ -57,7 +57,7 @@
           .classed("d3Donut-company", true)
           .attr("width", scope.radius)
           .attr("transform", "translate(#{scope.radius},#{scope.radius * 2 + 25})")
-          .text(scope.contributor.total_amount)
+          .text($filter('currency')(scope.contributor.total_amount))
         # create the paths for each donute section
         arc = d3.svg.arc()
           .innerRadius(scope.radius - 5)


Tests: Fixed followTopic test.

Tests: Fixed followTopic test.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -197,17 +197,20 @@

   doFollowTopic: (browser) ->

-    hashtag  = @sendHashtagActivity(browser)
-    selector = activitySelector + ' .has-markdown p a:first-child'
-
-    browser
-      .waitForElementVisible   selector, 5000
+    hashtag      = @sendHashtagActivity(browser)
+    selector     = activitySelector + ' .has-markdown p a:first-child'
+    topicLink    = '[testpath=main-sidebar] [testpath="public-feed-link/Activity/Topic/' + hashtag.replace('#', '') + '"]'
+    channelTitle = '[testpath=channel-title]'
+
+    browser
+      .waitForElementVisible   selector, 25000
       .click                   selector
-      .pause                   3000 # really required
-      .assert.containsText     '[testpath=channel-title]', hashtag # Assertion
-      .waitForElementVisible   '[testpath=channel-title]' + ' .follow', 2000
-      .click                   '[testpath=channel-title]' + ' .follow'
-      .waitForElementVisible   '[testpath=channel-title]' + ' .following', 2000 # Assertion
+      .waitForElementVisible   topicLink, 25000
+      .waitForElementVisible   channelTitle, 25000
+      .assert.containsText     channelTitle, hashtag # Assertion
+      .waitForElementVisible   channelTitle + ' .follow', 25000
+      .click                   channelTitle + ' .follow'
+      .waitForElementVisible   channelTitle + ' .following', 25000 # Assertion

     return hashtag


was checking if path existed but path was undefined, switched to lpath since that is the path that is defined

--- lib/util.coffee
+++ lib/util.coffee
@@ -22,5 +22,5 @@
         path = join lpath, 'index.' + key
         return path if existsSync path
     else # Specific file
-      return lpath if existsSync path
+      return lpath if existsSync lpath
     return


Use Util.isDirectory instead of FS.statSync

Does the same under the hood

--- lib/util.coffee
+++ lib/util.coffee
@@ -64,7 +64,7 @@
         sbc = Util.parseSandboxConfig("#{rootPath}#{sep}cabal.sandbox.config")
         if sbc?['install-dirs']?['bindir']?
           sandbox = sbc['install-dirs']['bindir']
-          try if FS.statSync(sandbox).isDirectory()
+          if Util.isDirectory(sandbox)
             sbd = true
             apd.unshift sandbox
       if atom.config.get('haskell-ghc-mod.stackSandbox')


Those keybindings are redundant or unused by now

--- keymaps/git-plus.cson
+++ keymaps/git-plus.cson
@@ -13,7 +13,7 @@
   'cmd-shift-c': 'git-plus:commit'
   'cmd-shift-a s': 'git-plus:status'
   'cmd-shift-a a': 'git-plus:add-all-and-commit'
-
+
 '.platform-win32, .platform-linux':
   'ctrl-shift-h': 'git-plus:menu'
   'ctrl-shift-x': 'git-plus:commit'
@@ -23,11 +23,7 @@
 '.platform-darwin .editor':
   'cmd-shift-a': 'git-plus:add'
   'cmd-shift-a c': 'git-plus:add-and-commit'
-  'cmd-shift-a a': 'git-plus:add-all-and-commit'
-  'cmd-alt-.': 'git-plus:hide-output'

 '.platform-win32 .editor, .platform-linux .editor':
   'ctrl-shift-a': 'git-plus:add'
   'ctrl-shift-a c': 'git-plus:add-and-commit'
-  'ctrl-shift-a a': 'git-plus:add-all-and-commit'
-  'ctrl-alt-.': 'git-plus:hide-output'


Update sort-css.cson

Update sort-css.cson

--- keymaps/sort-css.cson
+++ keymaps/sort-css.cson
@@ -1,2 +1,2 @@
 'atom-text-editor:not([mini])':
-  'f5': 'sort-css:sort'
+  'f6': 'sort-css:sort'


Remove log statement

Remove log statement

--- app/assets/javascripts/admin/extensions/display_errors.js.coffee
+++ app/assets/javascripts/admin/extensions/display_errors.js.coffee
@@ -1,6 +1,5 @@
 Backbone.View = Backbone.View.extend(
   displayErrors: (model, xhr, options) ->
-    console.log "Errors present"
     errors = $.parseJSON(xhr.responseText).errors.join(", ")
     Augury.Flash.error "Please fix the following errors: " + errors
 )


restore tooltips

restore tooltips

--- app/assets/javascripts/trade/views/shipment_table_view.js.coffee
+++ app/assets/javascripts/trade/views/shipment_table_view.js.coffee
@@ -5,4 +5,4 @@
   layoutName: 'trade/editable-table/table_row'

   didInsertElement: ->
-    #$('td > span.t, td i').tooltip()
+    $('td > span.t, td i').tooltip()


Nix extra details

Nix extra details

--- server/config.coffee
+++ server/config.coffee
@@ -15,8 +15,6 @@
   production:
     smtp:
       service: 'Mandrill'
-      port: 587
-      host: 'smtp.mandrillapp.com'
       auth:
         user: process.env.MANDRILL_APIKEY
         pass: process.env.MANDRILL_USERNAME


Use alert-error class for styling.

Use alert-error class for styling.

--- frontend/app/assets/javascripts/spree/frontend/checkout/payment.js.coffee
+++ frontend/app/assets/javascripts/spree/frontend/checkout/payment.js.coffee
@@ -71,7 +71,7 @@
               return true
             error: (xhr) ->
               handler = JSON.parse(xhr.responseText)
-              coupon_status.addClass("error").html(handler["error"])
+              coupon_status.addClass("alert-error").html(handler["error"])
               $('.continue').attr('disabled', false)
               return false
           })


Toolbox.inspect() fixes

Toolbox.inspect() fixes

--- src/Toolbox.coffee
+++ src/Toolbox.coffee
@@ -53,15 +53,15 @@
     @_forceEmitWithSeverity Observatory.LOGLEVEL[severity], message, object, module, 'profile'

-  inspect: (obj, long = true, print = false)->
+  inspect: (obj, long = false, print = false)->
     ret =
       functions: []
       objects: []
       vars: []
     for k,v of obj
       switch typeof v
-        when 'function' then ret.functions.push key: k, value: v
-        when 'object' then ret.objects.push key: k, value: v
+        when 'function' then ret.functions.push key: k, value: v.toString()
+        when 'object' then ret.objects.push key: k, (if long then value: v else value: "Object")
         else ret.vars.push key: k, value: v
     if print
       for t in ['functions','objects','vars']


Fix popup bind error

Fix popup bind error

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -53,13 +53,8 @@
   # Object disclosure for debugging
   @log("Application: %o", App)

-  @popupSuccess: @_popupMessage.bind(this, "success", "log", null)
-  @popupInfo:    @_popupMessage.bind(this, "info", "info", "info")
-  @popupWarning: @_popupMessage.bind(this, "warning", "warning", "warning")
-  @popupError:   @_popupMessage.bind(this, "danger", "error", "warning")
-
   @_popupMessage: (ntype, etype, icon, message, title) ->
-    this[etype]("Popup message: %o", {message: message, title: title})
+    this[etype]("Popup message: #{JSON.stringify({message: message, title: title})}")
     return new Notifier({
       icon: if icon? then "glyphicon glyphicon-#{icon}-sign" else undefined
       title: title
@@ -72,6 +67,11 @@
       offset: {x: 20, y: 50}
       showProgressbar: true
     }).show()
+
+  @popupSuccess: @_popupMessage.bind(this, "success", "log", null)
+  @popupInfo:    @_popupMessage.bind(this, "info", "info", "info")
+  @popupWarning: @_popupMessage.bind(this, "warning", "warn", "warning")
+  @popupError:   @_popupMessage.bind(this, "danger", "error", "warning")

   ###*
   @static


Exec: Fix class comparison in equals

Exec: Fix class comparison in equals

--- Src/coffeescript/cql-execution/src/util/comparison.coffee
+++ Src/coffeescript/cql-execution/src/util/comparison.coffee
@@ -52,7 +52,7 @@

   # If one is a Quantity, use the Quantity equals function
   return a.equals b if a?.constructor?.name == 'Quantity'
-
+
   # If one is an Uncertainty, convert the other to an Uncertainty
   if a instanceof Uncertainty then b = Uncertainty.from(b)
   else if b instanceof Uncertainty then a = Uncertainty.from(a)
@@ -79,7 +79,7 @@
       return a.length is b.length and a.every (item, i) -> equals(item, b[i])
     when '[object Object]'
       # Return false if they are instances of different classes
-      return false unless b instanceof a.constructor
+      return false unless b instanceof a.constructor and a instanceof b.constructor
       # Do deep comparison of keys and values
       aKeys = (key for key of a unless typeof(key) is 'function')
       bKeys = (key for key of b unless typeof(key) is 'function')


changed prefix for random key generator

changed prefix for random key generator


fixed stupid spelling mistake in creating bucket

--- src/aws-sdk-helpers.coffee
+++ src/aws-sdk-helpers.coffee
@@ -36,7 +36,7 @@
         URI: 'http://acs.amazonaws.com/groups/global/AllUsers'
         Type: 'Group'

-    awsSdkS3.client.putBucketAcl
+    s3client.putBucketAcl
       Bucket: name
       AccessControlPolicy: pars
     , callback


[#49202869] Don't submit shipments if there are unsaved changes

--- app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
@@ -125,10 +125,13 @@

   actions:
     submitShipments: ()->
-      $.post '/trade/annual_report_uploads/'+@get('id')+'/submit', {}, (data) ->
-        console.log(data)
-        'json'
-      @transitionToRoute('annual_report_uploads')
+      if @get('content.isDirty')
+        alert "You have unsaved changes, please save those before submitting your shipments"
+      else
+        $.post '/trade/annual_report_uploads/'+@get('id')+'/submit', {}, (data) ->
+          console.log(data)
+          'json'
+        @transitionToRoute('annual_report_uploads')

     setVisibleShipments: (shipments) ->
       @resetFilters()
@@ -161,4 +164,4 @@
       currentShipments.forEach (shipment) ->
         for columnName, value of valuesToUpdate
           shipment.set(columnName, value)
-      @set('tableController.shipments', currentShipments)+      @set('tableController.shipments', currentShipments)


caching jQuery selectors

caching jQuery selectors

--- new_project/app/client/app.coffee
+++ new_project/app/client/app.coffee
@@ -1,13 +1,15 @@
 # Client-side Code

+message = $('#message')
+
 # Bind to events
-SS.socket.on 'disconnect', ->  $('#message').text('SocketStream server is down :-(')
-SS.socket.on 'connect', ->     $('#message').text('SocketStream server is up :-)')
+SS.socket.on 'disconnect', ->  message.text('SocketStream server is down :-(')
+SS.socket.on 'connect', ->     message.text('SocketStream server is up :-)')

 # This method is called automatically when the websocket connection is established. Do not rename/delete
 exports.init = ->

   # Make a call to the server to retrieve a message
   SS.server.app.init (response) ->
-    $('#message').text(response)
+    message.text(response)


Fixed not shown initial ticket customer avatar.

--- app/assets/javascripts/app/controllers/ticket_zoom.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.coffee
@@ -351,7 +351,7 @@
     # rerender whole sidebar if customer or organization has changed
     if @ticketLastAttributes.customer_id isnt @ticket.customer_id || @ticketLastAttributes.organization_id isnt @ticket.organization_id
       new App.WidgetAvatar(
-        el:      @$('.ticketZoom-header .js-avatar')
+        el:      elLocal.find('.ticketZoom-header .js-avatar')
         user_id: @ticket.customer_id
         size:    50
       )
@@ -381,12 +381,12 @@
       )

     # scroll to article if given
-    if @article_id && document.getElementById( 'article-' + @article_id )
-      offset = document.getElementById( 'article-' + @article_id ).offsetTop
+    if @article_id && document.getElementById('article-' + @article_id)
+      offset = document.getElementById('article-' + @article_id).offsetTop
       offset = offset - 45
       scrollTo = ->
-        @scrollTo( 0, offset )
-      @delay( scrollTo, 100, false )
+        @scrollTo(0, offset)
+      @delay(scrollTo, 100, false)

     @ticketLastAttributes = @ticket.attributes()

@@ -476,8 +476,8 @@
     resetButton   = @$('.js-reset')

     params         = {}
-    params.ticket  = @formParam( ticketForm )
-    params.article = @formParam( articleForm )
+    params.ticket  = @formParam(ticketForm)
+    params.article = @formParam(articleForm)
     #console.log('markFormDiff', diff, params)

     # clear all changes
@@ -731,6 +731,6 @@
       show:       true
     )

-App.Config.set( 'ticket/zoom/:ticket_id', TicketZoomRouter, 'Routes' )
-App.Config.set( 'ticket/zoom/:ticket_id/nav/:nav', TicketZoomRouter, 'Routes' )
-App.Config.set( 'ticket/zoom/:ticket_id/:article_id', TicketZoomRouter, 'Routes' )
+App.Config.set('ticket/zoom/:ticket_id', TicketZoomRouter, 'Routes')
+App.Config.set('ticket/zoom/:ticket_id/nav/:nav', TicketZoomRouter, 'Routes')
+App.Config.set('ticket/zoom/:ticket_id/:article_id', TicketZoomRouter, 'Routes')


Namespace beforeunload listeners by file name.

This prevents us from accidentally removing all of the upload
listeners when one of them is completed.

--- app/assets/javascripts/mixins/file_upload_mixin.js.coffee
+++ app/assets/javascripts/mixins/file_upload_mixin.js.coffee
@@ -8,8 +8,10 @@
   isUploading: Em.computed.notEmpty 'uploads'

   uploadStarted: (data, fileUploadXHR) ->
-    @get('uploads').pushObject ETahi.FileUpload.create(file: data.files[0], xhr: fileUploadXHR)
-    $(window).on 'beforeunload.cancelUploads', ->
+    file = data.files[0]
+    filename = file.name
+    @get('uploads').pushObject ETahi.FileUpload.create(file: file, xhr: fileUploadXHR)
+    $(window).on "beforeunload.cancelUploads.#{filename}", ->
       return 'You are uploading, are you sure you want to cancel?'

   uploadProgress: (data) ->
@@ -21,7 +23,7 @@
     uploads = @get('uploads')
     newUpload = uploads.findBy('file.name', filename)
     uploads.removeObject newUpload
-    $(window).off 'beforeunload.cancelUploads'
+    $(window).off "beforeunload.cancelUploads.#{filename}"

   cancelUploads: ->
     @get('uploads').invoke('abort')


add error handler in tests

add error handler in tests

--- test/acceptance/coffee/helpers/TrackChangesClient.coffee
+++ test/acceptance/coffee/helpers/TrackChangesClient.coffee
@@ -106,6 +106,7 @@
 		options.encoding = null
 		request.get options, (err, res, body) ->
 			return callback(error) if error?
+			return callback(new Error("empty response from s3")) if not body?
 			zlib.gunzip body, (err, result) ->
 				return callback(err) if err?
 				callback(null, JSON.parse(result.toString()))


Add rotation to flame, and scale both smoothly

--- app/scripts/components/player/PlayerControlledShip.coffee
+++ app/scripts/components/player/PlayerControlledShip.coffee
@@ -23,33 +23,30 @@
     @secondaryWeapon = undefined
     @superUsed = 0
     @weaponsEnabled = yes
+    @currentRenderedSpeed = 0

   _updateFlyingSpeed: (newSpeed, dt) ->
-    if newSpeed <= 0
-      @backFire.attr(alpha: 0)
+    if newSpeed < 50
+      correction = newSpeed / 2
     else
-      if newSpeed < 50
-        correction = newSpeed / 2
-      else
-        correction = 25 + ((newSpeed / 700) * 100)
-
-      w = correction
-
-      @backFire.timing += (newSpeed / dt)
-      if @backFire.timing > 200
-        @backFire.attr(alpha: 0.6, timing: -40)
-        w = correction - 10
-      else if @backFire.timing > 0
-        @backFire.attr(alpha: 0.9)
-
-
-      h = w / 3
-      @backFire.attr(
-        x: @x - w + 9
-        y: @y + 20 - (h // 2)
-        w: w
-        h: h
-      )
+      correction = 25 + ((newSpeed / 700) * 100)
+
+    if @currentRenderedSpeed > correction
+      @currentRenderedSpeed -= 4
+    else if @currentRenderedSpeed < correction
+      @currentRenderedSpeed += 4
+    if @currentRenderedSpeed < 0
+      @currentRenderedSpeed = 0
+
+    w = @currentRenderedSpeed
+
+    h = w / 3
+    @backFire.attr(
+      x: @x - w + 9
+      y: @y + 20 - (h // 2)
+      w: w
+      h: h
+    )

   start: ->
     @backFire = Crafty.e('2D, WebGL, shipEngineFire')
@@ -85,8 +82,16 @@
       motionX = (@_forcedSpeed.x / 1000.0) * fd.dt
       motionY = (@_forcedSpeed.y / 1000.0) * fd.dt

-      shipSpeed = @_forcedSpeed.x + @vx
-      @_updateFlyingSpeed shipSpeed, fd.dt
+      shipSpeedX = @_forcedSpeed.x + @vx
+      shipSpeedY = @_forcedSpeed.y + @vy
+      @_updateFlyingSpeed shipSpeedX, fd.dt
+
+      r = @rotation
+      newR = shipSpeedY / 20
+      if r < newR
+        @rotation += 1
+      else if r > newR
+        @rotation -= 1

       @x += motionX
       @y += motionY
@@ -94,6 +99,7 @@
       if @hit('Edge') or @hit('Solid')
         @x -= motionX
         @y -= motionY
+        @rotation = r

       # still hitting an object? then we where forced in
       # and are crashed (squashed probably)


activity: fix createdAt, deletedAt, and updatedAt references of `SocialMessage` instance in `CommentListItemView` class

--- client/Social/Activity/views/comments/commentlistitemview.coffee
+++ client/Social/Activity/views/comments/commentlistitemview.coffee
@@ -9,10 +9,7 @@
     {message}  = @getData()
     @message   = message

-    @parsedDates =
-      deletedAt : Date.parse @message.deletedAt
-      createdAt : Date.parse @message.createdAt
-      updatedAt : Date.parse @message.updatedAt
+    {createdAt, deletedAt, updatedAt} = data

     {JAccount} = KD.remote.api

@@ -36,7 +33,7 @@
       cssClass: "hidden edited"
       pistachio: "edited"

-    if @parsedDates.updatedAt > @parsedDates.createdAt then @editInfo.show()
+    if updatedAt > createdAt then @editInfo.show()

     if deleterId? and deleterId isnt originId
       @deleter = new ProfileLinkView {}, data.getAt('deletedBy')
@@ -80,7 +77,7 @@
       @replyView = new KDView
         tagName  : "span"

-    @timeAgoView = new KDTimeAgoView {}, @message.createdAt
+    @timeAgoView = new KDTimeAgoView {}, createdAt

     # TODO: ??
     # data.on 'ContentMarkedAsLowQuality', @bound 'hide' unless KD.checkFlag 'exempt'
@@ -108,7 +105,7 @@
     @settings.show()
     @editComment.destroy()
     @body.show()
-    @editInfo.show() if @parsedDates.updatedAt > @parsedDates.createdAt
+    @editInfo.show() if data.meta.updatedAt > data.meta.createdAt
     @editCommentWrapper.hide()

   showEditCommentForm:(data)->
@@ -190,7 +187,9 @@
     #  to the problem of statically not being able to find pistachios unless
     #  they are contained inside a property called "pistachio".

-    if @parsedDates.deletedAt > @parsedDates.createdAt
+    {meta: {createdAt, deletedAt}} = @getData()
+
+    if deletedAt > createdAt
       {type} = @getOptions()
       @setClass "deleted"
       if @deleter


add test prompts

add test prompts

--- apps/editorial_features/test/components/eoy/client.coffee
+++ apps/editorial_features/test/components/eoy/client.coffee
@@ -141,3 +141,21 @@
     it 'returns the section closest to where user scrolls', ->
       @view.closestSection(0, @view.getScrollZones()).should.equal 0
       @view.closestSection(3000, @view.getScrollZones()).should.equal 5
+
+  describe '#deferredLoadBody', ->
+    it 'loads the body contents', ->
+
+  describe '#watchWindow', ->
+    it 'resets section boundaries when window changes size', ->
+
+  describe '#animateBody', ->
+    it 'adds a class to the closest section', ->
+
+  describe '#playVideo', ->
+    it 'hides and shows the controls, plays the video, knows if the video is already playing', ->
+
+  describe '#setVideoWaypoints', ->
+    it 'calls play video for each video', ->
+
+  describe '#setImages', ->
+    it 'uses a cloudfront src for each image', ->


i guess 'and' is more appropriate in coffeescript

--- source/assets/javascripts/konsole.js.coffee
+++ source/assets/javascripts/konsole.js.coffee
@@ -103,7 +103,7 @@
     e.preventDefault()
     code = e.keyCode or e.which
     # my firefox (linux) returns code 0 for oe
-    if code == 0 && e.key == 'ö'
+    if code == 0 and e.key == 'ö'
       code = 192
     key = $('[data-code=' + code + ']')

@@ -407,7 +407,7 @@
     e.preventDefault()
     code = e.keyCode or e.which
     # my firefox (linux) returns code 0 for oe
-    if code == 0 && e.key == 'ö'
+    if code == 0 and e.key == 'ö'
       code = 192
     key = $('[data-code=' + code + ']')


Make refresh work

Make refresh work

--- app/js/controllers/newcontact.coffee
+++ app/js/controllers/newcontact.coffee
@@ -1,9 +1,10 @@
-angular.module("app").controller "NewContactController", ($scope, $modalInstance, Wallet, refresh) ->
+angular.module("app").controller "NewContactController", ($scope, $modalInstance, Wallet) ->

     $scope.cancel = ->
         $modalInstance.dismiss "cancel"

     $scope.ok = ->
         Wallet.wallet_add_contact_account($scope.name, $scope.address).then (response) ->
+            Wallet.refresh_accounts()
             $modalInstance.close("ok")
-            refresh()
+


Remove unused tutorial step

Remove unused tutorial step

--- app/lib/small_tutorial_slides.coffee
+++ app/lib/small_tutorial_slides.coffee
@@ -3,27 +3,22 @@
 module?.exports = [
   {
     image: "http://placehold.it/320X200&text=tutorial+step+1"
-    title: translate "Welcome to Snapshot Chicago"
-    content: translate "Welcome to Chicago Wildlife Watch! In this project, we are asking you to identify animals found in camera trap photos from around the city. Your help allows us to continue our research into urban biodiversity, making the city a better home for both humans and animals."
+    title: translate "span", "Welcome to Snapshot Chicago"
+    content: translate "span", "Welcome to Chicago Wildlife Watch! In this project, we are asking you to identify animals found in camera trap photos from around the city. Your help allows us to continue our research into urban biodiversity, making the city a better home for both humans and animals."
   },
   {
     image: 'http://placehold.it/320X200&text=tutorial+step+2'
-    title: translate "Step 2"
-    content: translate "You are presented with a photo on the left and a list of potential animals on the right. When you spot an animal within a photo, simply click the corresponding animal on the right. You will be asked to supply some followup info, such as number of animals found in the image."
+    title: translate "span", "Step 2"
+    content: translate "span", "You are presented with a photo on the left and a list of potential animals on the right. When you spot an animal within a photo, simply click the corresponding animal on the right. You will be asked to supply some followup info, such as number of animals found in the image."
   },
   {
     image: "http://placehold.it/320X200&text=tutorial+step+3"
-    title: translate "Step 3"
-    content: translate "Occasionally it is difficult to disambiguate which animal is in the photo. Just above the list of animals is a variety of methods to filter the animal list. Selecting different options reduces the number of choices, making your job easier."
-  },
-  {
-    image: 'http://placehold.it/320X200&text=tutorial+step+4'
-    title: translate "Step 4"
-    content: translate "Feel like a step 4 is necessary, but I'm unsure what to put here."
+    title: translate "span", "Step 3"
+    content: translate "span", "Occasionally it is difficult to disambiguate which animal is in the photo. Just above the list of animals is a variety of methods to filter the animal list. Selecting different options reduces the number of choices, making your job easier."
   },
   {
     image: 'http://placehold.it/320X200&text=tutorial+step+5'
-    title: translate "Step 5"
-    content: translate "That's it! You may catchup on project updates either on the blog or Talk. "
+    title: translate "span", "Step 4"
+    content: translate "span", "That's it! You may catchup on project updates either on the blog or Talk."
   }
 ]