Dependencies changed.

Dependencies changed.

--- picoCTF-web/web/coffee/dependencies.coffee
+++ picoCTF-web/web/coffee/dependencies.coffee
@@ -1,33 +1,12 @@
-window.handle_submit = (prob_id) ->
-  $.ajax(
-    type: "POST"
-    cache: false
-    url: "/api/submit"
-    dataType: "json"
-    data:
-      pid: prob_id
-      key: $("#" + prob_id).val()
-      viewer: "basic"
-  ).done (data) ->
-    prob_msg = $("#msg_" + prob_id)
-    alert_class = ""
-    if data["status"] is 0
-      alert_class = "alert-error"
-    else if data["status"] is 1
-      alert_class = "alert-success"
-    prob_msg.hide().html("
" + data["message"] + "
").slideDown("normal")
-    setTimeout (->
-      prob_msg.slideUp "normal", ->
-        prob_msg.html("").show()
-        window.location.reload() if data["status"] is 1
-    ), 2500
+window.apiCall = (type, url, data) ->
+  $.ajax {url: url, type: type, data: data, cache: false}
+  .fail (jqXHR, text) ->
+    alert "API is offline."

-window.redirect_if_not_logged_in = ->
-  $.ajax(
-    type: "GET"
-    url: "/api/isloggedin"
-    cache: false
-  ).done((data) ->
-    window.location.href = "/login" if data["status"] is 0
-  ).fail ->
-    window.location.href = "/"    +window.redirectIfNotLoggedIn = ->
+  apiCall "GET", "/api/user/isloggedin", {}
+  .done (data) ->
+    switch data["status"]
+      when 0
+        window.location.href = "/login"
+


Form: Fix .hint param, add classes, new methods.

- Fixed .hint parameter when used with non-String.
- Add named classes for "tr" and "row": "cui-form-tr--<data-field-name>"
- New methods: addClassToField, removeClassFromField, use to
  set row mounted classes per field.

--- src/elements/Form/Form.coffee
+++ src/elements/Form/Form.coffee
@@ -618,7 +618,7 @@
 					if isString(field._form.hint)
 						hint_div.appendChild(new Label(class: "cui-form-hint-label", icon: field._form.hint_icon, text: field._form.hint, multiline: true, markdown: true).DOM)
 					else
-						DUI.DOM.append(hint_div, field._form.hint)
+						CUI.DOM.append(hint_div, field._form.hint)

 				if field._form.right
 					add_hint_div()
@@ -627,8 +627,6 @@
 					# append deprecated stuff to the hint div
 					# you should use ".hint" instead
 					append(get_append(field._form.right), hint_div)
-
-

 			if field instanceof Form and field.renderAsBlock()
 				level = parseInt(CUI.DOM.getAttribute(@DOM, "cui-form-depth"))+1
@@ -706,8 +704,17 @@

 				append(table)

+			name = field.getName()
+			if name
+				if table_has_left
+					cls = " cui-form-tr--"+name
+				else
+					cls = " cui-form-row--"+name
+			else
+				cls = ""
+
 			if table_has_left
-				tr = CUI.DOM.element("DIV", class: "cui-form-tr", "data-for-field": field.getUniqueId())
+				tr = CUI.DOM.element("DIV", class: "cui-form-tr"+cls, "data-for-field": field.getUniqueId())

 				td = CUI.DOM.element("DIV", class: "cui-form-td cui-form-key")
 				append(get_label(field), td)
@@ -726,7 +733,7 @@

 				table.appendChild(tr)
 			else
-				row = CUI.DOM.element("DIV", class: "cui-form-row", "data-for-field": field.getUniqueId())
+				row = CUI.DOM.element("DIV", class: "cui-form-row"+cls, "data-for-field": field.getUniqueId())
 				row.appendChild(get_append(field))
 				append(get_append(field), row)
 				append(hint_div, row)
@@ -837,6 +844,24 @@
 			type: "content-resize"
 			node: els[0]
 		@
+
+	__setClassOnField: (field_name, cls, add_remove) ->
+		for field in @getFieldsByName(field_name)
+			row = CUI.DOM.closest(field.DOM, "[data-for-field]")
+			if not row
+				continue
+
+			if add_remove
+				CUI.DOM.addClass(row, cls)
+			else
+				CUI.DOM.removeClass(row, cls)
+		@
+
+	addClassToField: (field_name, cls) ->
+		@__setClassOnField(field_name, cls, true)
+
+	removeClassFromField: (field_name, cls) ->
+		@__setClassOnField(field_name, cls, false)

 	getFieldsByName: (name, found_fields = []) ->
 		assert(isString(name), "#{@__cls}.getFieldsByName", "name must be String.", name: name)


tweaks coding style

tweaks coding style

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -202,12 +202,11 @@
       @isClicked = false
     , 377

-  onScrollViewResized: =>
-    @updateMinimapView()
+  onScrollViewResized: => @updateMinimapView()

   onDragStart: (e) =>
     # only supports for left-click
-    return unless e.which is 1
+    return if e.which isnt 1
     @isPressed = true
     @on 'mousemove.visible-area', @onMove
     @on 'mouseup.visible-area', @onDragEnd
@@ -221,8 +220,7 @@

   # OTHER PRIVATE METHODS

-  activeTabSupportMinimap: ->
-    @getEditor()
+  activeTabSupportMinimap: -> @getEditor()

   scale: (x=1,y=1) -> "scale(#{x}, #{y}) "
   translateY: (y=0) -> "translate3d(0, #{y}px, 0)"


#3 добавил в класс пропущенную функцию _makeDir

--- copy-files.coffee
+++ copy-files.coffee
@@ -54,6 +54,14 @@
       else rollback.push(command: "rm"   , path: ctx.dst)
     _genRollback(ctxList[0])
     return rollback
+
+  _makeDir: (path, ctx = null, mode = 0755) ->
+    parent = dirname(path)
+    unless existsSync(parent)
+      return false unless @_makeDir(parent, ctx, mode)
+    return false unless @_mkdirSync(path, mode)
+    ctx.createdFolders.push(path) if ctx?
+    return true

   _copy: (src, dst, callback) ->
     #throw "Error: #{src} not found!" unless existsSync(src)


Use remove() instead of hide() for button group styling

--- lib/package-card.coffee
+++ lib/package-card.coffee
@@ -68,11 +68,11 @@

     # themes have no status and cannot be dis/enabled
     if @type is 'theme'
-      @statusIndicator.hide()
-      @enablementButton.hide()
+      @statusIndicator.remove()
+      @enablementButton.remove()

     unless @hasSettings(@pack)
-      @settingsButton.hide()
+      @settingsButton.remove()

     # The package is not bundled with Atom and is not installed so we'll have
     # to find a package version that is compatible with this Atom version.


Fix scroll horizontal and vertical

Fix scroll horizontal and vertical

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -74,10 +74,6 @@
 				x: @layer.x - @_correctedLayerStartPoint.x
 				y: @layer.y - @_correctedLayerStartPoint.y

-	# TODO: what to do with this?
-	# Should there be a tap event?
-	# @define "multipleDraggables", @simpleProperty("multipleDraggables", false)
-
 	constructor: (@layer) ->

 		options = Defaults.getDefaults("LayerDraggable", {})
@@ -188,7 +184,7 @@
 			y: touchEvent.clientY
 			t: Date.now() # We don't use timeStamp because it's different on Chrome/Safari

-		point = {}
+		point = _.clone(@_point)

 		scaleX = (1 / @layer.canvasScaleX() * @layer.scale * @layer.scaleX)
 		scaleY = (1 / @layer.canvasScaleY() * @layer.scale * @layer.scaleY)


Likely fix for simulation null error

Likely fix for simulation null error

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -504,8 +504,8 @@
 	_stopSimulation: =>
 		@_isAnimating = false
 		return unless @_simulation
-		@_simulation.x?.stop()
-		@_simulation.y?.stop()
+		@_simulation.x.stop() if @_simulation.x
+		@_simulation.y.stop() if @_simulation.y
 		@emit(Events.Move, @layer.point)
 		@emit(Events.DragAnimationDidEnd)
 		@_simulation = null


Add mixed support.

Add mixed support.

--- src/ground/GroundModel.coffee
+++ src/ground/GroundModel.coffee
@@ -47,6 +47,7 @@
       when _.isArray v then 'array'
       when not _.isFunction(v) and not v.type? then 'json'
       when v is mongoose.Schema.Types.Mixed then 'json'
+      when _.isFunction(v) and v.name is 'Mixed' then 'json'
       else {}

   sailsSchema: (schema) ->


Fixes #3124 - Nested Tree Select value not translatable.

--- app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.coffee
+++ app/assets/javascripts/app/controllers/_ui_element/_application_ui_element.coffee
@@ -42,15 +42,22 @@
     else
       attribute.options[''] = '-'

-  @getConfigOptionList: (attribute) ->
+  @getConfigOptionListArray: (attribute, selection) ->
+    result = []
+    for row in selection
+      if attribute.translate
+        row.name = App.i18n.translateInline(row.name)
+        if !_.isEmpty(row.children)
+          row.children = @getConfigOptionListArray(attribute, row.children)
+      result.push row
+    result
+
+  @getConfigOptionList: (attribute, children = false) ->
     return if _.isEmpty(attribute.options)
     selection = attribute.options
     attribute.options = []
     if _.isArray(selection)
-      for row in selection
-        if attribute.translate
-          row.name = App.i18n.translateInline(row.name)
-        attribute.options.push row
+      attribute.options = @getConfigOptionListArray(attribute, selection)
     else
       forceString = (s) ->
         return if !selection[s] || !selection[s].toString then '' else selection[s].toString()


It runs on my machine

It runs on my machine

--- tests/mocha/apollos/create.coffee
+++ tests/mocha/apollos/create.coffee
@@ -4,64 +4,82 @@

   if Meteor.isClient

-    describe 'Apollos', ->
-      describe 'create on the client', ->
+    _waitForEvent = (eventFunc, callback) ->
+      success = eventFunc()
+
+      if success
+        callback()
+      else
+        _wait ->
+          _waitForEvent eventFunc, callback
+
+    _waitForLogin = (callback) ->
+      _waitForEvent ->
+        return Meteor.userId()
+      , callback
+
+    _wait = (func) ->
+      Meteor.setTimeout func, 250
+
+    _logout = (callback) ->
+      Tracker.autorun (handle) ->
+        if not Meteor.userId()
+          callback()
+          handle.stop()
+      Meteor.logout()
+
+    describe "Apollos", ->
+      describe "create on the client", ->
+
+        beforeEach (done) ->
+          @.timeout 10000
+          _logout done
+
+        describe "when email does not exist in system", ->
+
+          @.timeout 10000
+
+          it "should create user and login", (done) ->
+            email = "apolloscreateclient@newspring.cc"
+            Apollos.user.create email, "testPassword", (error) ->
+              assert.isUndefined error
+              _waitForLogin ->
+                assert.equal Meteor.user().emails[0].address, email
+                done()
+
+        describe "when email is in system", ->
+
+          @.timeout 10000
+
+          it "should not create user", (done) ->
+            email = "apolloscreateclient@newspring.cc"
+            Apollos.user.create email, "testPassword", (error) ->
+              assert.isDefined error
+              done()
+
+  else
+
+    describe "Apollos", ->
+      describe "create on the server", ->

         beforeEach ->
           Meteor.flush()

-        before (done) ->
-          if Meteor.isClient
-            Meteor.logout()
-            Meteor.autorun ->
-              user = Apollos.user()
-              done() if Object.keys(user).length is 0
-          else
-            done()
+        describe "when email does not exist in system", ->

-        describe 'when email does not exist in system', ->
-
-          @.timeout 5000
-
-          it 'should create user and login', (done) ->
-            email = "apolloscreateclient@newspring.cc"
-            Apollos.user.create email, 'testPassword', (error) ->
-              assert.isUndefined error
-              assert.equal Meteor.user().emails[0].address, email
-              done()
-
-        describe 'when email is in system', ->
-
-          it 'should not create user', (done) ->
+          it "should create user", ->
             userCount = Apollos.users.find().count()
-            Apollos.user.create 'apolloscreateclient@newspring.cc', 'testPassword', (error) ->
-              assert.isDefined error
-              assert.equal Apollos.users.find().count(), userCount
-              done()
-
-  else
-
-    describe 'Apollos', ->
-      describe 'create on the server', ->
-
-        beforeEach ->
-          Meteor.flush()
-
-        describe 'when email does not exist in system', ->
-
-          it 'should create user', ->
-            userCount = Apollos.users.find().count()
-            userId = Apollos.user.create 'apolloscreateserver@newspring.cc', 'testPassword'
+            userId = Apollos.user.create "apolloscreateserver@newspring.cc", "testPassword"
             assert.isString userId
             assert.isTrue userId.length > 0

-        describe 'when email is in system', ->
+        describe "when email is in system", ->

-          it 'should not create user', (done) ->
+          it "should not create user", (done) ->
             userCount = Apollos.users.find().count()
             try
-              Apollos.user.create 'apolloscreateserver@newspring.cc', 'testPassword'
-              assert.isFalse true # Shouldn't get here because an error should be thrown
+              Apollos.user.create "apolloscreateserver@newspring.cc", "testPassword"
+              assert.isFalse true # Shouldn"t get here because an error should be thrown
             catch error
               assert.equal error.reason, "Email already exists."
               done()


fix undefined kira value

fix undefined kira value

--- parts/prophet-info.cjsx
+++ parts/prophet-info.cjsx
@@ -20,6 +20,7 @@
         else
           item = window._slotitems[itemId]
         itemType = item?.api_type?[3]
+        kiraValue = item.api_level ? item.api_level : 0

           {
             if itemType?
@@ -27,7 +28,7 @@
           }

             {window.i18n.resources.__ item.api_name}
-              {if @props.owner != 1 && item.api_level > 0 then  ★{item.api_level} else ''}
+              {if @props.owner != 1 then  ★{kiraValue} else ''}
                 {
                 if item.api_alv? and 1 <= item.api_alv <= 7


Guard against missing theme

Guard against missing theme

--- lib/themes-panel.coffee
+++ lib/themes-panel.coffee
@@ -159,17 +159,21 @@
   handleCurrentThemeButtons: ->
     @currentUiThemeSettings.on 'click', (event) =>
       event.stopPropagation()
-      @parents('.settings-view').view()?.showPanel(@activeUiTheme, {
-        back: 'Themes',
-        pack: atom.themes.getActiveThemes().filter((theme) -> theme.metadata.theme is 'ui')[0].metadata
-      })
+      activeUiTheme = atom.themes.getActiveThemes().filter((theme) -> theme.metadata.theme is 'ui')[0]?.metadata
+      if activeUiTheme?
+        @parents('.settings-view').view()?.showPanel(@activeUiTheme, {
+          back: 'Themes',
+          pack: activeUiTheme
+        })

     @currentSyntaxThemeSettings.on 'click', (event) =>
       event.stopPropagation()
-      @parents('.settings-view').view()?.showPanel(@activeSyntaxTheme, {
-        back: 'Themes',
-        pack: atom.themes.getActiveThemes().filter((theme) -> theme.metadata.theme is 'ui')[0].metadata
-      })
+      activeSyntaxTheme = atom.themes.getActiveThemes().filter((theme) -> theme.metadata.theme is 'syntax')[0]?.metadata
+      if activeSyntaxTheme?
+        @parents('.settings-view').view()?.showPanel(@activeSyntaxTheme, {
+          back: 'Themes',
+          pack: activeSyntaxTheme
+        })

   toggleCurrentThemeButtons: ->
     if atom.config.get(@activeUiTheme)?
@@ -181,7 +185,6 @@
       @currentSyntaxThemeSettings.show()
     else
       @currentSyntaxThemeSettings.hide()
-

   # Populate the theme menus from the theme manager's active themes
   populateThemeMenus: ->


Fix message of log: complete to create new release

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -171,7 +171,7 @@
           d.promise

         .then (id) ->
-          console.log 'complete to create new release: #{id}'
+          console.log "complete to create new release: #{id}"

           Q
             .all dirs.map ({zip}) ->


Making clean task sync

Making clean task sync

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -86,9 +86,7 @@
     browserSync.reload(file.path) if file.type is "changed"

 gulp.task 'clean', ->
-  return del([
-    'dist'
-  ])
+  return del.sync(['dist'])

 gulp.task 'build', ->
   runSequence 'clean', ['style', 'lint', 'src', 'html']


Make ActionLogs observable

Make ActionLogs observable

--- js/inner.coffee
+++ js/inner.coffee
@@ -101,30 +101,39 @@

-class ActionLog extends Base
+class ActionLog extends Obj
+  observables: =>
+    timedout: null
+    end_time: null
+    exit_code: null
+    minimize: true
+    out: []
+
   constructor: (json) ->
-    super json, {bash_command: null, start_time: null, command: null, timedout: null, exit_code: 0, out: null, minimize: true}, ["end_time", "timedout", "exit_code", "run_time_millis", "out", "start_time", "bash_command"]
-
-    @status = if @end_time == null
+    super json, {bash_command: null, start_time: null, command: null, timedout: null, exit_code: 0, out: [], minimize: true}
+
+    @status = @komp =>
+      if @end_time() == null
         "running"
-      else if @timedout
+      else if @timedout()
         "timedout"

-      else if (@exit_code == null || @exit_code == 0)
+      else if (@exit_code() == null || @exit_code() == 0)
         "success"
       else
         "failed"

-    @success = @status == "success"
+    @success = @komp =>
+      @status() == "success"

     # Expand failing actions
-    @minimize(@success)
-
-    @has_content = () =>
-      @out or @bash_command
+    @minimize(@success())
+
+    @has_content = @komp =>
+      (@out().length > 0) or @bash_command

     @action_header_style = @komp =>
-      css = @status
+      css = @status()

       result =
         minimize: @minimize()
@@ -148,8 +157,8 @@
     str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')

   log_output: =>
-    return "" unless @out
-    x = for o in @out
+    return "" unless @out()
+    x = for o in @out()
       "#{@htmlEscape(o.message)}"
     x.join ""


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -100,6 +100,6 @@

 # TODO: tests to make sure value isn't overwritten if the instance variable already exists
 # TODO: tests to make sure value is defaulted to 0, but only if the instance variable doesn't exist
-test "initialize property to 0 i"
+test "initialize property to 0 if no "

 module()


refactor unit tests

refactor unit tests


Social: add a new bridge function for querying channel participation of a user

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -255,6 +255,13 @@
   if not data.name
     return callback { message: "name should be set"}
   url = "/channel/name/#{data.name}"
+  get url, data, callback
+
+checkChannelParticipation = (data, callback)->
+  if not data.name or not data.type
+    return callback { message: "request is not valid" }
+
+  url = "/channel/checkparticipation"
   get url, data, callback

 post = (url, data, callback)->
@@ -288,6 +295,7 @@
     , wrapCallback callback

 module.exports = {
+  checkChannelParticipation
   messageById
   channelById
   channelByName


Wait for open promise before showing panel

Closes #285


Add descriptions for config settings

Add descriptions for config settings


avoid unnecessary guard

avoid unnecessary guard


Implement build tests

Implement build tests

--- test/node_gyp.coffee
+++ test/node_gyp.coffee
@@ -73,14 +73,39 @@
 				if err then done() else done(new Error 'expected configure to fail')

 	describe 'build', ->
-		it 'should build a release build by default', ->
-			;
+		it 'should build a release build by default', (done) ->
+			linkBindingGyp()

-		it 'should build a debug build if the debug option is passed', ->
-			;
+			execGruntTask 'configure', (err) ->
+				return done(err) if err
+
+				execGruntTask 'build', (err) ->
+					return done(err) if err

-		it 'should fail if there are no build files', ->
-			;
+					if !fs.existsSync './build/Release/hello_world.node'
+						return done(new Error 'expected Release/hello_world.node to exist')
+
+					done()
+
+		it 'should build a debug build if the debug option is passed', (done) ->
+			linkBindingGyp()
+
+			execGruntTask 'configure', (err) ->
+				return done(err) if err
+
+				execGruntTask 'buildDebug', (err) ->
+					return done(err) if err
+
+					if !fs.existsSync './build/Debug/hello_world.node'
+						return done(new Error 'expected Debug/hello_world.node to exist')
+
+					done()
+
+		it 'should fail if there are no build files', (done) ->
+			rmBuildFiles()
+
+			execGruntTask 'build', (err) ->
+				if err then done() else done(new Error 'expected build to fail')

 	describe 'clean', ->
 		it 'should remove the build directory', ->


Add some debug log

Add some debug log

--- lib/config.coffee
+++ lib/config.coffee
@@ -15,12 +15,18 @@
 try
   fs.accessSync configPath, fs.R_OK | fs.W_OK
   config = CSON.parseCSONFile configPath
+  dbg.log "Config loaded from: #{configPath}"
+catch e
+  dbg.log e

 # Read saved config
 if !config?
   try
     fs.accessSync defaultConfigPath, fs.R_OK | fs.W_OK
     config = CSON.parseCSONFile defaultConfigPath
+    dbg.log "Config loaded from: #{defaultConfigPath}"
+  catch e
+    dbg.log e

 if !config?
   config = {}


OPCE19: test the merge of selections with style

--- test/rte-test.coffee
+++ test/rte-test.coffee
@@ -225,12 +225,18 @@

   it 'merge correctly the selections [merge]', ->
     rte = new Rte "Zero One two three four five"
-    sel = new Selection 0, 1, rte
-    sel2 = new Selection 1, 10, rte
-
-    sel.merge sel2, rte
-    rte._rte.selections.length.should.equal 1
-    rte._rte.selections[0].equals(sel2).should.be.true
+    sel0 = new Selection 0, 1, rte, {foo: "bar"}
+    sel1 = new Selection 0, 1, rte, {mergeMe: "I'm famous"}
+    sel2 = new Selection 1, 10, rte, {foo: "bar"}
+
+
+    sel0.merge sel0, sel1 # sel and sel1 not merging!
+    rte._rte.selections.length.should.equal 3
+
+    sel0.merge sel2, rte
+    rte._rte.selections.length.should.equal 2
+    rte._rte.selections.indexOf(sel).should.equal -1
+

     leftWord = rte.getWord 0
     rightWord = rte.getWord 2


Improve thread list view tests

Improve thread list view tests

An obsolete test case was passing because the assertions were too weak,
so the assertions are improved and the test case removed.

--- common/static/coffee/spec/discussion/view/discussion_thread_list_view_spec.coffee
+++ common/static/coffee/spec/discussion/view/discussion_thread_list_view_spec.coffee
@@ -475,20 +475,12 @@
         testSelectionRequest = (callback, itemText) ->
           setupAjax(callback)
           $(".forum-nav-browse-title:contains(#{itemText})").click()
+          expect($.ajax).toHaveBeenCalled()

         it "should get all discussions", ->
           testSelectionRequest(
             (params) -> expect(params.url.path()).toEqual(DiscussionUtil.urlFor("threads")),
             "All"
-          )
-
-        it "should get flagged threads", ->
-          testSelectionRequest(
-            (params) ->
-              expect(params.url.path()).toEqual(DiscussionUtil.urlFor("search"))
-              expect(params.data.flagged).toEqual(true)
-            ,
-            "Flagged"
           )

         it "should get followed threads", ->


Fix __dirname and __filename in asar: protocol.

--- atom/renderer/lib/init.coffee
+++ atom/renderer/lib/init.coffee
@@ -24,8 +24,8 @@
 global.require = require
 global.module = module

-# Set the __filename to the path of html file if it's file:// protocol.
-if window.location.protocol is 'file:'
+# Set the __filename to the path of html file if it's file: or asar: protocol.
+if window.location.protocol in ['file:', 'asar:']
   pathname =
     if process.platform is 'win32'
       window.location.pathname.substr 1


Pane: Make header.title as a separate object

--- client/landing/Workspace/panes/pane.coffee
+++ client/landing/Workspace/panes/pane.coffee
@@ -20,7 +20,12 @@
     @header    = new KDCustomHTMLView
       tagName  : "span"  if title is ''
       cssClass : "ws-header inner-header"
-      partial  : "<h4>#{title}</h4>"
+
+    @header.title = new KDCustomHTMLView
+      partial : "#{title}"
+      tagName : "h4"
+
+    @header.addSubView @header.title

   createButtons: ->
     # TODO: c/p from panel, should refactor both of them.


Simplify NotificationService

Simplify NotificationService

--- client/services/notificationservice.coffee
+++ client/services/notificationservice.coffee
@@ -12,23 +12,18 @@
       {
         title: title,
         message: message,
-      }, [
-        {
-          event: 'yes',
-          callback: yesCallback,
-        },
-        {
-          event: 'no',
-          callback: noCallback,
-        },
-      ])
+      }, {
+        "yes": yesCallback
+        "no": noCallback
+      }
+    )

   # Show a Bootstrap modal based on a template that gets rendered on-the-fly
   _showModal: (templateName, data, callbacks) ->
     html = Blaze.toHTMLWithData(Template[templateName], data)
     $modal = $(html)
     $modal.modal({
-      callbacks: callbacks,
+      callbacks: R.map((([key, value]) -> {"event": key, callback: value}), R.toPairs(callbacks))
     })
     $(".modal-default").focus()
     # After the modal is hidden, remove the DOM node


Update accountProfile.coffee

Update accountProfile.coffee

--- client/views/account/accountProfile.coffee
+++ client/views/account/accountProfile.coffee
@@ -99,7 +99,7 @@
 Template.accountProfile.onRendered ->
 	Tracker.afterFlush ->
 		# this should throw an error-template
-		FlowRouter.go("home") if !RocketChat.settings.get("Accounts_AllowUserAvatarChange")
+		FlowRouter.go("home") if !RocketChat.settings.get("Accounts_AllowUserProfileChange")
 		SideNav.setFlex "accountFlex"
 		SideNav.openFlex()


Update the directive to use the newer check() response

--- directive.coffee
+++ directive.coffee
@@ -18,11 +18,11 @@

 	# Prop-style settings
 	params: [
-		'in-viewport-active'
-		'in-viewport-once'
-		'in-viewport-class'
-		'in-viewport-offset-top'
-		'in-viewport-offset-bottom'
+		'inViewportActive'
+		'inViewportOnce'
+		'inViewportClass'
+		'inViewportOffsetTop'
+		'inViewportOffsetBottom'
 	]

 	# Set default param values and start listening for scrolls
@@ -75,6 +75,7 @@
 		$(@el).toggleClass(@params.inViewportClass, visible)

 	# Check if element is in viewport
-	isInViewport: -> check @el,
-		offsetTop:    @params.inViewportOffsetTop
-		offsetBottom: @params.inViewportOffsetBottom
+	isInViewport: -> check(@el,
+			offsetTop:    @params.inViewportOffsetTop
+			offsetBottom: @params.inViewportOffsetBottom
+		).inViewport


Add tokens property to Project model

Add tokens property to Project model

--- services/web/app/coffee/models/Project.coffee
+++ services/web/app/coffee/models/Project.coffee
@@ -33,6 +33,9 @@
 	deletedDocs       : [DeletedDocSchema]
 	imageName         : { type: String }
 	track_changes     : { type: Object }
+	tokens            :
+		readOnly        : { type: String }
+		readAndWrite    : { type: String }
 	overleaf          :
 		id              : { type: Number }
 		imported_at_ver_id : { type: Number }


Move test into separate section

Move test into separate section

--- test/elvis-backbone-test.coffee
+++ test/elvis-backbone-test.coffee
@@ -58,7 +58,7 @@
     model.set(baz: 'xuuq')
     expect(element.innerHTML).to.equal('rab xuuq')

-  it 'can transform space separated attributes', ->
+  it 'can transform multiple attributes', ->
     model = new Backbone.Model(income: 9000, expenses: 7000)
     displayProfit = (i, e) -> "Profit: " + (i - e)
     binding = model.bindTo(['income', 'expenses']).get(displayProfit)
@@ -102,6 +102,8 @@
     expect(model.get('firstName')).to.equal('John')
     expect(model.get('lastName')).to.equal('Doe')

+
+describe 'Elvis Backbone.View', ->
   it 'can bind using bindTo in a Backbone View', ->
     class View extends Backbone.View
       render: ->


Change default log level to INFO

Change default log level to INFO

--- logger.coffee
+++ logger.coffee
@@ -87,6 +87,6 @@
   fatal: (str, raw=false) ->
     api.msg api.LEVEL_FATAL, str, raw

-logLevel = api.LEVEL_DEBUG
+logLevel = api.LEVEL_INFO  # default verbosity

 module.exports = api


refactor: Remove leftover change

refactor: Remove leftover change

--- test/integration/helpers.coffee
+++ test/integration/helpers.coffee
@@ -103,8 +103,7 @@
   app.use(bodyParserInstance)
   app.use((req, res, next) ->
     recordServerRequest(serverRuntimeInfo, req)
-    res.type('json')
-    res.status(200) # sensible defaults, can be overriden
+    res.type('json').status(200) # sensible defaults, can be overriden
     next()
   )
   app = https.createServer(getSSLCredentials(), app) if protocol is 'https'


Only process criteria and description with markdown if they are present

--- models/badge.coffee
+++ models/badge.coffee
@@ -19,8 +19,10 @@
   issued_count: { type: Number, default: 0}

 BadgeSchema.pre 'save', (next)->
-  @criteria = markdown.parse(@criteria, markdown.flags.autolink)
-  @description = markdown.parse(@description, markdown.flags.autolink)
+  if @criteria?
+    @criteria = markdown.parse(@criteria, markdown.flags.autolink)
+  if @description?
+    @description = markdown.parse(@description, markdown.flags.autolink)
   next()

 BadgeSchema.plugin(timestamps);


lets try not using the image proxy for a bit and see if it feels too slow

--- models/block.coffee
+++ models/block.coffee
@@ -29,10 +29,11 @@

   resizeImage: (width = 330, height = 330, source = 'display')->
     # ignore gifs
-    if @getImageSize('display').split('.').pop() == 'gif'
-      @getImageSize('display')
-    else
-      "#{sd.IMAGE_PROXY_URL}/resize/#{width}/#{height}/#{encodeURIComponent(@getImageSize(source))}"
+    # if @getImageSize('display').split('.').pop() == 'gif'
+    #   @getImageSize('display')
+    # else
+    #   "#{sd.IMAGE_PROXY_URL}/resize/#{width}/#{height}/#{encodeURIComponent(@getImageSize(source))}"
+    @getImageSize('display')

   getImageSize: (size) ->
     if @has('image')


PSR2 defect

PSR2 defect

--- MediaAdminBundle/Resources/public/coffee/orchestraLib.coffee
+++ MediaAdminBundle/Resources/public/coffee/orchestraLib.coffee
@@ -13,4 +13,4 @@
       view.delegateEvents()
       return
     return
-  initTinyMCE()+  initTinyMCE()


Fix annotations positioning

Fix annotations positioning

--- app/assets/javascripts/application/annotations.js.coffee
+++ app/assets/javascripts/application/annotations.js.coffee
@@ -5,8 +5,8 @@
     place_annotations_undo()

 place_annotations_do = () ->
-  if $('.annotations').length > 0
-    $('.annotations').addClass('side-annotations')
+  if $('#annotations').length > 0
+    $('#annotations').addClass('side-annotations')
     annotations = $('li[id^=annotation-]')
     minimum = $('.body').offset().top
     if $('#single_map:visible').length > 0 then minimum += $('#single_map').outerHeight(true)
@@ -40,7 +40,7 @@
       maximum = new_top

 place_annotations_undo = () ->
-  $('.annotations').removeClass('side-annotations')
+  $('#annotations').removeClass('side-annotations')
   $('li[id*=annotation-]').css('top', '0')

 window.Nembrot.place_annotations = place_annotations


Implement state machine for chat state

Implement state machine for chat state

--- app/assets/javascripts/interaction_web_tools/chat.coffee
+++ app/assets/javascripts/interaction_web_tools/chat.coffee
@@ -2,8 +2,15 @@
 window.InteractionWebTools.Chat ||= {}

 class InteractionWebTools.Chat.Client
+  stateEnum = {
+    INITIAL: 0
+    START_PENDING: 1,
+    STARTED: 2,
+    KILLED: 3
+  }
+
   constructor: ->
-    @started = false
+    @state = stateEnum.INITIAL
     @queuedMessages = []
     @endpoint = '/interaction_web_tools/events'
     @chatUI = $('#chat-ui')
@@ -20,20 +27,20 @@

   send: (message) ->
     console.log "Chat Client wants to send message", message
-    @startChat() unless @started
+    @startChat() unless @state == stateEnum.STARTED
     @sendMessage(message)

   startChat: ->
-    return if @startPending
+    return if @state == stateEnum.START_PENDING
+    @state = stateEnum.START_PENDING
     @pollMessages()
-    @startPending = true
-    console.log "Chat Client Chat Started"
+    console.log "Chat Client Chat Start Peding"

   pollMessages: ->
-    return if @killed
+    return if @state == stateEnum.KILLED
     console.log "Chat Client is polling for messages"
     $.get @endpoint, (data) =>
-      @started = true
+      @state = stateEnum.STARTED
       @dismissWelcome()
       @dequeueMessages()
       @renderMessages data.events
@@ -42,15 +49,15 @@
         if (event.type == 'participantStateChanged' &&
             event.state == 'disconnected' &&
             event.participant_type == 'WebUser')
-          @started = false
+          @state = stateEnum.KILLED

       setTimeout () =>
         @pollMessages()
-      , 1000 if !@killed && @started
+      , 1000 if @state == stateEnum.STARTED || @state == stateEnum.START_PENDING

   sendMessage: (message) ->
     return unless message
-    return @queueMessage(message) unless @started
+    return @queueMessage(message) unless @state == stateEnum.STARTED

     $.post @endpoint, { event: { content: message } }, (data) =>
       @renderMessages data.events
@@ -90,8 +97,7 @@
     $.ajax
       url: @endpoint
       type: 'DELETE'
+    console.log "Chat Terminated"

-    @killed = true
-    @started = false
-    @startPending = false
+    @state = stateEnum.KILLED
     console.log "Chat Client Chat Terminated"


Remove task reload in route

Remove task reload in route

- Server side events/polling will keep the tasks updated in the future

--- app/assets/javascripts/routes/paper_task_route.js.coffee
+++ app/assets/javascripts/routes/paper_task_route.js.coffee
@@ -2,7 +2,7 @@
   model: (params) ->
     paperTasks = _.flatten @modelFor('paper').get('phases').mapProperty('tasks.content')
     task = paperTasks.findBy('id', params.task_id)
-    task.reload()
+    task

   setupController: (controller, model) ->
     # FIXME: Rename AdHocTask to Task (here, in views, and in templates)
@@ -19,7 +19,6 @@
     if @controllerFor('application').get('overlayRedirect')
       taskController.set('onClose', 'redirect')

-
   renderTemplate: ->
     @render @get('baseObjectName'),
       into: 'application'
@@ -27,7 +26,6 @@
       controller: @get('taskController')
     @render(@controllerFor('application').get('overlayBackground'))

-
   deactivate: ->
     @send('closeOverlay')
     @controllerFor('application').setProperties(overlayRedirect: null, overlayBackground: null)


uploading fail handling

uploading fail handling

--- app/assets/javascripts/uploadcare/files/object.js.coffee
+++ app/assets/javascripts/uploadcare/files/object.js.coffee
@@ -67,6 +67,9 @@
           @__multipartComplete(data.uuid).done (data) =>
             @__handleFileData(data)
             @__completeUpload()
+          .fail @__uploadDf.reject
+        .fail @__uploadDf.reject
+      .fail @__uploadDf.reject

     __multipartStart: ->
       data =


:memo: how to use moveCursorCountTimes

:memo: how to use moveCursorCountTimes

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -146,6 +146,11 @@
     else
       setBufferRow(cursor, row, options)

+  # [NOTE]
+  # Since this function checks cursor position change, a cursor position MUST be
+  # updated IN callback(=fn)
+  # Updating point only in callback is wrong-use of this funciton,
+  # since it stops immediately because of not cursor position change.
   moveCursorCountTimes: (cursor, fn) ->
     oldPosition = cursor.getBufferPosition()
     @countTimes (state) =>


Fix javascript bug when requested intervention is deleted (Fixes #1421)

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -337,6 +337,9 @@
         }
         success: (data, status, request) ->

+          interventionsIds.forEach (intervention_id) ->
+            $('#interventions-list tr[id*="'+intervention_id+'"]').remove()
+
           selectedTasks = instance.getTaskboard().getSelectedTasksByColumnSelector(columnSelector)
           selectedTasks.remove()


Support multiple hero header in the same page

--- app/assets/javascripts/neighborly/neighborly.js.coffee
+++ app/assets/javascripts/neighborly/neighborly.js.coffee
@@ -89,5 +89,7 @@

   backstretch:
     init: ->
-      if $('header.hero').not('.no-image').data('image-url') != null && $('header.hero').not('.no-image').data('image-url') != ''
-        $('header.hero').backstretch($('header.hero').data('image-url'), {fade: 'normal'})
+      has_image = -> $(header).data('image-url')?
+      for header in $('header.hero:not(.no-image)') when has_image() then do (header) =>
+        header = $(header)
+        header.backstretch(header.data('image-url'), {fade: 'normal'})


Bugfix: Update seriesOrder if the new timeframe gives us new series.

--- app/assets/javascripts/pgdatagraph/datagraph.js.coffee
+++ app/assets/javascripts/pgdatagraph/datagraph.js.coffee
@@ -91,7 +91,7 @@
         initialOrder = _.without initialOrder, name
       initialOrder = seriesOrder.concat(initialOrder)

-    @seriesOrder = initialOrder unless @seriesOrder?
+    @seriesOrder = initialOrder unless @seriesOrder? && @seriesOrder.slice(0).sort() == initialOrder.slice(0).sort()

     for name in @seriesOrder
       seriesData = _.map data[name], (s) -> { x: s[0], y: s[1] }


Added call to Cart.init() before returning service

--- app/assets/javascripts/sprangular/services/cart.coffee
+++ app/assets/javascripts/sprangular/services/cart.coffee
@@ -97,6 +97,6 @@
     hasVariant: (variant)    -> @current.hasVariant(variant)
     isEmpty:                 -> @current.isEmpty()

-  service.clear()
+  service.init()
   service.reload()
   service


fix ie 8 conversations loading bug, fixes #9627

ie 8 and below don't gracefully handle coffeescript trying to splat
something that isn't an array. specifically, "{}..." results in a
"[].slice.call({})", which gives "Number expected". because modelize
didn't correctly handle empty arrays, we would splat an object when there
were no conversations to load, resulting in a js error and a spinner that
would never go away

this affected people on ie 8 with no conversations, or who had selected
a conversation filter (e.g. "Unread") that resulted in no matches

test plan:
1. in ie 8, go to conversations
2. if you have conversations, they should load correctly
3. if you have no conversations, "You have no messages" should appear
4. if you select a filter/scope with no results (e.g. "Unread"), "You
   have no [filter] messages" should appear
5. there should be no js errors

Change-Id: I3376460694293c4c2204157400322cf29b370253
Reviewed-on: https://gerrit.instructure.com/12627
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Zach Pendleton <514dbb4d54d4f29e454990df7d075678f43f92aa@instructure.com>

--- app/coffeescripts/util/ScrollableListDataSource.coffee
+++ app/coffeescripts/util/ScrollableListDataSource.coffee
@@ -71,7 +71,7 @@
         item.list = @list
         item

-      if data.length
+      if data.length?
         (modelize(item) for item in data)
       else
         modelize(data)


Wrote specification for the (yet to be implemented) distance option)

--- spec/draggable_spec.coffee
+++ spec/draggable_spec.coffee
@@ -10,6 +10,8 @@
   scrollOffsetVariants:
     'no': 0
     'a non-zero': 50
+  distanceConfig:
+    'distance': 10
   cursorAtConfigVariants:
     'single':
       left: { left: 5 }
@@ -126,6 +128,60 @@
     it 'should possess the default draggable class', ->
       expect(@$draggable).toHaveClass $.draggable::defaults['draggableClass']

+  describe 'configured using the distance option', ->
+
+    beforeEach ->
+      loadFixtures 'draggable_static.html'
+      @$draggable = $('#draggable_static').draggable(options.distanceConfig)
+
+    describe 'when dragged less than the drag distance threshold', ->
+
+      beforeEach ->
+        # Save the original position
+        @originalPosition = @$draggable.offset()
+
+        # Start at the top corner of the draggable
+        center = SpecHelper.mouseDownInCenterOf @$draggable
+
+        # Move it by less than the drag distance
+        lessThanDistance = options.distanceConfig.distance - 0.1
+        delta = Math.floor Math.sqrt((lessThanDistance*lessThanDistance)/2)
+        $(document).simulate 'mousemove',
+          clientX: center.x + delta
+          clientY: center.y + delta
+
+      it 'should not have moved from original position', ->
+        expect(@$draggable.offset()).toEqual @originalPosition
+
+      it 'should not possess the default draggable class', ->
+        expect(@$draggable).not.toHaveClass $.draggable::defaults['draggingClass']
+
+    describe 'when dragged exactly or greater than the drag distance threshold', ->
+
+      beforeEach ->
+        # Save the original position
+        @originalPosition = @$draggable.offset()
+
+        # Start at the top corner of the draggable
+        center = SpecHelper.mouseDownInCenterOf @$draggable
+
+        # Move it by at least the drag distance
+        distance = options.distanceConfig.distance
+        @delta = Math.ceil Math.sqrt((distance*distance)/2)
+        $(document).simulate 'mousemove',
+          clientX: center.x + @delta
+          clientY: center.y + @delta
+
+      it 'should have moved the drag distance from the original position', ->
+        expectedPosition =
+          left: @originalPosition.left + @delta
+          top: @originalPosition.top + @delta
+
+        expect(@$draggable.offset()).toEqual expectedPosition
+
+      it 'should possess the default draggable class', ->
+        expect(@$draggable).toHaveClass $.draggable::defaults['draggingClass']
+
   describe 'configured using the draggableClass option', ->

     beforeEach ->


moveCursorDown -> moveDown

moveCursorDown -> moveDown

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -431,11 +431,11 @@
       expect(editor.getSelectedBufferRange()).toEqual [[1, 6], [1, 10]]

     it "replaces results counter with number of results found when user moves the cursor", ->
-      editor.moveCursorDown()
+      editor.moveDown()
       expect(findView.resultCounter.text()).toBe '6 found'

     it "replaces results counter x of y text when user selects a marked range", ->
-      editor.moveCursorDown()
+      editor.moveDown()
       editor.setSelectedBufferRange([[2, 34], [2, 39]])
       expect(findView.resultCounter.text()).toEqual('3 of 6')


Remove unneeded failed count check

Remove unneeded failed count check

--- spec/jasmine-helper.coffee
+++ spec/jasmine-helper.coffee
@@ -30,7 +30,7 @@

           if grim.getDeprecationsLength() > 0
             grim.logDeprecations()
-            return atom.exit(1) if runner.results().failedCount is 0
+            return atom.exit(1)

         if runner.results().failedCount > 0
           atom.exit(1)


Test for unexpected warning/error notifications

--- spec/list-edit-spec.coffee
+++ spec/list-edit-spec.coffee
@@ -1,4 +1,9 @@
 ListEdit = require '../lib/list-edit'
+
+expectNoErrorsOrWarnings = ->
+  expect(atom.notifications.addWarning).not.toHaveBeenCalled();
+  expect(atom.notifications.addError).not.toHaveBeenCalled();
+  expect(atom.notifications.addFatalError).not.toHaveBeenCalled();

 describe 'ListEdit', ->
   [editor, editorView] = []
@@ -18,11 +23,18 @@
         #       we need to dispatch to atom.views.getView(editor) instead of atom.views.getView(atom.workspace)
         atom.commands.dispatch editorView, 'list-edit:copy'
         # Activate package with a neutral list-edit:copy, so the test edit commands don't have to deal with activation.
+        spyOn atom.notifications, 'addWarning'
+        spyOn atom.notifications, 'addError'
+        spyOn atom.notifications, 'addFatalError'
         activationPromise
         # TODO: Would be nice if we could do this just once, instead of for each spec, but waitsForPromise does
         #       not seem to work in describe (unlike what the documentation suggests.)

+
   describe 'list-copy', ->
+    afterEach ->
+      expectNoErrorsOrWarnings()
+
     it 'copies the selected range to the clipboard', ->
       #               01234567890123456789012345678901234567890
       editor.setText '[   Blinky ,  Pinky ,  Inky ,  Clyde   ]'
@@ -38,6 +50,9 @@
         })

   describe 'list-cut', ->
+    afterEach ->
+      expectNoErrorsOrWarnings()
+
     it 'Cursor inside element in singleton list cuts element and whitespace', ->
       editor.setText '[ Single ]'
       editor.setCursorBufferPosition [0, 3]
@@ -57,6 +72,9 @@
       expect(editor.getText()).toBe '[Blinky, Inky, Clyde]'

   describe 'list-paste', ->
+    afterEach ->
+      expectNoErrorsOrWarnings()
+
     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]'
@@ -86,6 +104,8 @@
       expect(editor.getText()).toBe '[Blinky, NewGhost, Inky, Clyde]'

   describe 'list-copy/paste between lists', ->
+    afterEach ->
+      expectNoErrorsOrWarnings()

   # 0123456789012345678
     verticalListTxt = '''
@@ -139,9 +159,6 @@
       expect(editor.getText()).toBe '[   Clyde   ]'

   describe 'separator defaulting', ->
-    beforeEach ->
-      spyOn(atom.notifications, 'addWarning')
-
     it 'warns when a default separator is used', ->
       listCopyFrom '[NoSep]', 0, 3
       editor.setText '{NoSepEither}'
@@ -149,6 +166,8 @@
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '{NoSep; NoSepEither}'
       expect(atom.notifications.addWarning).toHaveBeenCalledWith('Separator unknown, using default: \';\'');
+      expect(atom.notifications.addError).not.toHaveBeenCalled();
+      expect(atom.notifications.addFatalError).not.toHaveBeenCalled();

     it 'does not warn when a default separator isn\'t used because target range wasn\'t empty', ->
       listCopyFrom '[NoSep]', 0, 3
@@ -156,7 +175,7 @@
       editor.setCursorBufferPosition [0, 7] # paste on two
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '{one, NoSep, three}'
-      expect(atom.notifications.addWarning).not.toHaveBeenCalled();
+      expectNoErrorsOrWarnings()

     it 'does not warn when a default separator isn\'t used because target list was empty', ->
       listCopyFrom '[NoSep]', 0, 3
@@ -164,7 +183,7 @@
       editor.setCursorBufferPosition [0, 1] # paste in empty list
       atom.commands.dispatch editorView, 'list-edit:paste'
       expect(editor.getText()).toBe '{NoSep}'
-      expect(atom.notifications.addWarning).not.toHaveBeenCalled();
+      expectNoErrorsOrWarnings()

 describe 'ListEdit on a file with a JavaScript grammar', ->
   [editor, editorView] = []
@@ -182,7 +201,13 @@
         #       we need to dispatch to atom.views.getView(editor) instead of atom.views.getView(atom.workspace)
         atom.commands.dispatch editorView, 'list-edit:copy'
         # Activate package with a neutral list-edit:copy, so the test edit commands don't have to deal with activation.
+        spyOn atom.notifications, 'addWarning'
+        spyOn atom.notifications, 'addError'
+        spyOn atom.notifications, 'addFatalError'
         activationPromise
+
+  afterEach ->
+    expectNoErrorsOrWarnings()

   it 'correctly scans strings and comments', ->
     #               01234567890123456789 01234567 890123456789012 34567890123456789012345


Refactor jquery api

Refactor jquery api

--- src/jquery.superslides.coffee
+++ src/jquery.superslides.coffee
@@ -219,6 +219,7 @@
     $(el).removeData()

   @size = =>
+    # console.log $container
     $container.children().length

   @stop = =>
@@ -266,20 +267,18 @@
   initialize()

 # Plugin
-name = 'superslides'
-$.fn[name] = (option, args) ->
-  if typeof option is "string"
-    $this = $(this)
-    data = $this.data(name)
-
-    method = data[option]
-    if typeof method == 'function'
-      method = method.call($this, args)
-    return method
-
+plugin = 'superslides'
+$.fn[plugin] = (option, args) ->
+  result = []
   @each ->
-    $this = $(this)
-    data = $this.data(name)
+    $this   = $(this)
+    data    = $this.data(plugin)
     options = typeof option == 'object' && option

-    $this.data name, (data = new Superslides(this, options)) unless data+    result = $this.data plugin, (data = new Superslides(this, options)) unless data
+
+    if typeof option == "string"
+      result = data[option]
+      if typeof result == 'function'
+        result = result.call(this, args)
+  result


:bug: Fix broken test cases

:bug: Fix broken test cases

These test cases were not running due to indentation errors.
The tests for commands 'dj' and 'dk' now correctly fail.

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -278,55 +278,61 @@

     describe "when followed by an j", ->
       beforeEach ->
-        originalText = "12345\nabcde\nABCDE"
-        editor.setText(originalText)
-
-        describe "on the beginning of the file", ->
+        @originalText = "12345\nabcde\nABCDE"
+        editor.setText(@originalText)
+
+      describe "on the beginning of the file", ->
+        beforeEach ->
           editor.setCursorScreenPosition([0, 0])
-          it "deletes the next two lines", ->
-            keydown('d')
-            keydown('j')
-            expect(editor.getText()).toBe("ABCDE")
-
-        describe "on the end of the file", ->
-          editor.setCursorScreenPosition([4,2])
-          it "deletes nothing", ->
-            keydown('d')
-            keydown('j')
-            expect(editor.getText()).toBe(originalText)
-
-        describe "on the middle of second line", ->
-          editor.setCursorScreenPosition([2,1])
-          it "deletes the last two lines", ->
-            keydown('d')
-            keydown('j')
-            expect(editor.getText()).toBe("12345")
+        it "deletes the next two lines", ->
+          keydown('d')
+          keydown('j')
+          expect(editor.getText()).toBe("ABCDE")
+
+      describe "on the end of the file", ->
+        beforeEach ->
+          editor.setCursorScreenPosition([2, 4])
+        it "deletes nothing", ->
+          keydown('d')
+          keydown('j')
+          expect(editor.getText()).toBe(@originalText)
+
+      describe "on the middle of second line", ->
+        beforeEach ->
+          editor.setCursorScreenPosition([1,2])
+        it "deletes the last two lines", ->
+          keydown('d')
+          keydown('j')
+          expect(editor.getText()).toBe("12345")

     describe "when followed by an k", ->
       beforeEach ->
-        originalText = "12345\nabcde\nABCDE"
-        editor.setText(originalText)
-
-        describe "on the end of the file", ->
-          editor.setCursorScreenPosition([4, 2])
-          it "deletes the bottom two lines", ->
-            keydown('d')
-            keydown('k')
-            expect(editor.getText()).toBe("ABCDE")
-
-        describe "on the beginning of the file", ->
+        @originalText = "12345\nabcde\nABCDE"
+        editor.setText(@originalText)
+
+      describe "on the end of the file", ->
+        beforeEach ->
+          editor.setCursorScreenPosition([2,4])
+        it "deletes the bottom two lines", ->
+          keydown('d')
+          keydown('k')
+          expect(editor.getText()).toBe("ABCDE")
+
+      describe "on the beginning of the file", ->
+        beforeEach ->
           editor.setCursorScreenPosition([0,0])
-          it "deletes nothing", ->
-            keydown('d')
-            keydown('k')
-            expect(editor.getText()).toBe(originalText)
-
-        describe "when on the middle of second line", ->
-          editor.setCursorScreenPosition([2,1])
-          it "deletes the first two lines", ->
-            keydown('d')
-            keydown('k')
-            expect(editor.getText()).toBe("12345")
+        it "deletes nothing", ->
+          keydown('d')
+          keydown('k')
+          expect(editor.getText()).toBe(@originalText)
+
+      describe "when on the middle of second line", ->
+        beforeEach ->
+          editor.setCursorScreenPosition([1,2])
+        it "deletes the first two lines", ->
+          keydown('d')
+          keydown('k')
+          expect(editor.getText()).toBe("12345")

   describe "the D keybinding", ->
@@ -479,11 +485,11 @@
         keydown('y')
         keydown('h')

-        it "saves the left letter to the default register", ->
-          expect(vimState.getRegister('"').text).toBe " "
-
-        it "moves the cursor position to the left", ->
-          expect(editor.getCursorScreenPosition()).toEqual [0, 3]
+      it "saves the left letter to the default register", ->
+        expect(vimState.getRegister('"').text).toBe " "
+
+      it "moves the cursor position to the left", ->
+        expect(editor.getCursorScreenPosition()).toEqual [0, 3]

     describe "with a down motion", ->
       beforeEach ->


Tweak Document's model creation to distinguish old vs. new models

--- bokehjs/src/coffee/common/document.coffee
+++ bokehjs/src/coffee/common/document.coffee
@@ -106,7 +106,7 @@

   # given a JSON representation of all models in a graph, return a
   # dict of new model objects
-  @_instantiate_references_json: (references_json) ->
+  @_instantiate_references_json: (references_json, existing_models) ->
       # Create all instances, but without setting their props
       references = {}
       for obj in references_json
@@ -115,32 +115,46 @@
           if 'subtype' in obj
             obj_type = obj['subtype']

-          instance = new HasProperties({ id: obj_id }) # TODO instantiate actual model type
+          if obj_id of existing_models
+            instance = existing_models[obj_id]
+          else
+            instance = new HasProperties({ id: obj_id }) # TODO instantiate actual model type
           references[instance.id] = instance

       references

-  # given a JSOn representation of all models in a graph and new
+  # given a JSON representation of all models in a graph and new
   # model instances, set the properties on the models from the
   # JSON
-  @_initialize_references_json: (references_json, references) ->
+  @_initialize_references_json: (references_json, old_references, new_references) ->
     for obj in references_json
       obj_id = obj['id']
       obj_attrs = obj['attributes']

-      instance = references[obj_id]
+      was_new = false
+      instance =
+        if obj_id of old_references
+          old_references[obj_id]
+        else
+          was_new = true
+          new_references[obj_id]

       # replace references with actual instances in obj_attrs
       changes = {}
       for k, v in obj_attrs
-        if 'id' of v and v['id'] of references
-          changes[k] = references[v['id']]
+        if 'id' of v
+          if v['id'] of old_references
+            changes[k] = old_references[v['id']]
+          else if v['id'] of new_references
+            changes[k] = new_references[v['id']]

       for k, v in changes
         obj_attrs[k] = v

       # set all properties on the instance
       instance.set(obj_attrs)
+      if was_new
+        instance.initialize(obj_attrs)

   to_json_string : () ->
     JSON.stringify(@to_json())
@@ -170,8 +184,8 @@
     root_ids = roots_json['root_ids']
     references_json = roots_json['references']

-    references = Document._instantiate_references_json(references_json)
-    Document._initialize_references_json(references_json, references)
+    references = Document._instantiate_references_json(references_json, {})
+    Document._initialize_references_json(references_json, {}, references)

     doc = new Document()
     for r in root_ids
@@ -240,25 +254,25 @@
   apply_json_patch: (patch) ->
     references_json = patch['references']
     events_json = patch['events']
-    references = Document._instantiate_references_json(references_json)
-
-    # Use our existing model instances whenever we have them
-    existing = []
-    for id, obj of references
-      if id of @_all_models
-        existing.push(@_all_models[id])
+    references = Document._instantiate_references_json(references_json, @_all_models)

     # The model being changed isn't always in references so add it in
     for event_json in events_json
       if 'model' of event_json
         model_id = event_json['model']['id']
         if model_id of @_all_models
-          existing.push(@_all_models[model_id])
-
-    for e in existing
-      references[e.id] = e
-
-    Document._initialize_references_json(references_json, references)
+          references[model_id] = @_all_models[model_id]
+
+    # split references into old and new so we know whether to initialize or update
+    old_references = {}
+    new_references = {}
+    for id, value of references
+      if id not of @_all_models
+        old_references[id] = value
+      else
+        new_references[id] = value
+
+    Document._initialize_references_json(references_json, old_references, new_references)

     for event_json in events_json
       if event_json['kind'] == 'ModelChanged'


Add explicit check/x marks

Add explicit check/x marks

--- src/js/github-friction.coffee
+++ src/js/github-friction.coffee
@@ -130,6 +130,7 @@
       link.attr('target','_blank')
       link.text(friction_check.name)
       data_cell.text('')
+      data_cell.append('\u2612 ')
       data_cell.append(link)

 mark_done = (repo_id, name, file_name) ->
@@ -140,10 +141,10 @@
   branch = $("##{repo_id} > .branch").text()
   link.attr('href',"#{repo_url}/blob/#{branch}/#{file_name}")
   link.attr('target','_blank')
-  link.text(data_cell.text())
+  link.text(data_cell.text().replace('\u2610 ',''))
   data_cell.text('')
+  data_cell.append('\u2611 ')
   data_cell.append(link)
-

 check_friction = (repo_id, repo, branch) ->
   console.log("check_friction for #{branch} of #{repo_id}") if github_friction_debug
@@ -200,7 +201,7 @@
         repo_row.append(repo_div)
         repo_row.append($('<td>').attr('class','active text-center').text('master').addClass('branch'))
         for name, friction_check of friction_checks
-          repo_row.append($('<td>').attr('class','info text-center').text(friction_check.name).addClass(name))
+          repo_row.append($('<td>').attr('class','info text-center').text('\u2610 ' + friction_check.name).addClass(name))
         $('#repo_list').append(repo_row)
         github.getRepo(repo.owner.login, repo.name).listBranches (err, branches) =>
           console.log(repo.name) if github_friction_debug


try specs

try specs

--- spec/uninstall-spec.coffee
+++ spec/uninstall-spec.coffee
@@ -2,6 +2,18 @@
 fs = require 'fs-plus'
 temp = require 'temp'
 apm = require '../lib/apm-cli'
+
+createPackage = (packageName, includeDev=false) ->
+  atomHome = temp.mkdirSync('apm-home-dir-')
+  packagePath = path.join(atomHome, 'packages', packageName)
+  fs.makeTreeSync(path.join(packagePath, 'lib'))
+  fs.writeFileSync(path.join(packagePath, 'package.json'), "{}")
+  if includeDev
+    devPackagePath = path.join(atomHome, 'dev', 'packages', packageName)
+    fs.makeTreeSync(path.join(devPackagePath, 'lib'))
+    fs.writeFileSync(path.join(devPackagePath, 'package.json'), "{}")
+  process.env.ATOM_HOME = atomHome
+  return {packagePath, devPackagePath}

 describe 'apm uninstall', ->
   beforeEach ->
@@ -34,11 +46,7 @@

   describe 'when the package is installed', ->
     it 'deletes the package', ->
-      atomHome = temp.mkdirSync('apm-home-dir-')
-      packagePath = path.join(atomHome, 'packages', 'test-package')
-      fs.makeTreeSync(path.join(packagePath, 'lib'))
-      fs.writeFileSync(path.join(packagePath, 'package.json'), "{}")
-      process.env.ATOM_HOME = atomHome
+      {packagePath} = createPackage('test-package')

       expect(fs.existsSync(packagePath)).toBeTruthy()
       callback = jasmine.createSpy('callback')
@@ -50,16 +58,28 @@
       runs ->
         expect(fs.existsSync(packagePath)).toBeFalsy()

+    describe 'when . is specified as the package name', ->
+      it 'resolves to the basename of the cwd', ->
+        {packagePath} = createPackage('test-package')
+
+        expect(fs.existsSync(packagePath)).toBeTruthy()
+
+        oldCwd = process.cwd()
+        process.chdir(packagePath)
+
+        callback = jasmine.createSpy('callback')
+        apm.run(['uninstall', '.'], callback)
+
+        waitsFor 'waiting for command to complete', ->
+          callback.callCount > 0
+
+        runs ->
+          expect(fs.existsSync(packagePath)).toBeFalsy()
+          process.chdir(oldCwd)
+
     describe "--dev", ->
       it "deletes the packages from the dev packages folder", ->
-        atomHome = temp.mkdirSync('apm-home-dir-')
-        packagePath = path.join(atomHome, 'packages', 'test-package')
-        fs.makeTreeSync(path.join(packagePath, 'lib'))
-        fs.writeFileSync(path.join(packagePath, 'package.json'), "{}")
-        devPackagePath = path.join(atomHome, 'dev', 'packages', 'test-package')
-        fs.makeTreeSync(path.join(devPackagePath, 'lib'))
-        fs.writeFileSync(path.join(devPackagePath, 'package.json'), "{}")
-        process.env.ATOM_HOME = atomHome
+        {packagePath, devPackagePath} = createPackage('test-package', true)

         expect(fs.existsSync(packagePath)).toBeTruthy()
         callback = jasmine.createSpy('callback')


model bypass test

model bypass test

--- coffee/test/model.spec.coffee
+++ coffee/test/model.spec.coffee
@@ -136,7 +136,7 @@
       expect(model.shellData()).toEqual {shellid:'acorn.EmptyShell'}

-  describe 'acorn.Model.sync', ->
+  xdescribe 'acorn.Model.sync', ->

     #TODO expand this into comprehensive tests.
     #TODO find a way to mock the server in the future.


YT-1300 pilots

YT-1300 pilots

-Translated the Yt-1300 pilots from HotR

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -812,12 +812,15 @@
         "Poe Dameron (PS9)":
             text: """When attacking or defending, if you have a focus token, you may change 1 of your %FOCUS% results to a %HIT% or %EVADE% result."""
             ship: "T-70 Ala-X"
+        "Resistance Sympathizer":
+            name: "Simpatizante de la Resistencia"
+            ship: "Yt-1300"
         "Rey":
-            text: """When attacking or defending, if the enemy ship is inside of your firing arc, you may reroll up to 2 of your blank results."""
+            text: """Cuando ataques o te defiendas, si la nave enemiga está dentro de tu arco de fuego, puedes volver a tirar hasta 2 dados en los que hayas sacado caras vacías."""
         'Han Solo (TFA)':
-            text: '''When you are placed during setup, you can be placed anywhere in the play area beyond Range 3 of enemy ships.'''
+            text: '''En el momento de desplegarte durante la preparación de la partida, se te puede colocar en cualquier parte de la zona de juego que esté más allá de alcance 3 de las naves enemigas.'''
         'Chewbacca (TFA)':
-            text: '''After another friendly ship at Range 1-3 is destroyed (but has not fled the battlefield), you may perform an attack.'''
+            text: '''Después de que otra nave aliada que tengas a alcance 1-3 sea destruida (pero no hay aabandonado el campo de batalla), puedes efectuar un ataque.'''
         'Norra Wexley':
             ship: "ARC-170"
             text: '''Cuando ataques o te defiendas, puedes gastar una ficha de Blanco fijado que tengas sobre la nave enemiga para añadir 1 resultado %FOCUS% a tu tirada.'''


TIE Reaper article.

TIE Reaper article.

--- coffeescripts/cards-hu.coffee
+++ coffeescripts/cards-hu.coffee
@@ -558,6 +558,10 @@
             text: '''Védekezéskor, ha a támadónak van zavarás jelzője, adj egy %EVADE% eredményt a dobásodhoz.'''
         '"Vizier"':
             text: '''Miután egy baráti hajó végrehajt egy 1-es sebességű manővert, ha 1-es távolságra van és nem került átfedésbe egy másik hajóval, átadhatod neki egy %FOCUS% vagy %EVADE% jelződet.'''
+        'Magva Yarro':
+            text: '''Mikor egy másik baráti hajó 1-2 távolságban vedekezik, a támadója nem dobhatja újra csupán csak egy kockáját.'''
+        'Edrio Two-Tubes':
+            text: '''Mikor az aktivációs fázisban aktiválódik a hajód, ha van legalább 1 fókusz jelződ, végrehajthatsz egy ingyenes akciót.'''

     upgrade_translations =
         "Ion Cannon Turret":
@@ -1109,6 +1113,8 @@
             text: '''Csak birodalmiak.%LINEBREAK%Az akciósávod megkapja a %COORDINATE% akciót.'''
         'ISB Slicer':
             text: '''Miután végrehajtottál egy zavarás akciót egy elleséges hajó ellen, választhatsz egy attól 1-es távolságban lévő hajót amin nincs zavarás jelző és adhatsz neki egyet.'''
+        'Thrust Corrector':
+            text: '''Védekezéskor, ha 3 vagy kevesebb stressz jelződ van, kaphatsz 1 stressz jelzőt, hogy érvénytelenítsd az összes dobásod. Ha így teszel, adj 1 %EVADE% eredményt a dobásaidhoz. A kockáid nem módosíthatók újra ezen támadás alatt.%LINEBREAK%Ez a fejlesztés csak akkor szerelhető fel ha a szerkeszeti erősséged (hull) 4 vagy kisebb.'''

     modification_translations =
         "Stealth Device":
@@ -1184,6 +1190,8 @@
             text: '''Csak B/SF-17 Bomber.%LINEBREAK%Mikor egy baráti bomba felrobban, nem kell elszenvedned a hatását. Ha így teszel, dobbj egy támadó kockával. %HIT% eredménynél dobd el ezt a kártyát.'''
         'Servomotor S-Foils':
             text: '''Csak T-65 X-Wing. Kettős kártya.%LINEBREAK%A oldal (Attack):Az akciósávod megkapja a %BARRELROLL% ikont.  Ha nem vagy stresszes, mikor felfedsz egy  (%TURNLEFT% 3) vagy (3 %TURNRIGHT%) manővert, kezelheted úgy mint piros (%TROLLLEFT% 3) vagy (%TROLLRIGHT% 3) a megfeleltethető irányban.%LINEBREAK%Az aktivációs fázis kezdetén átfordíthatod ezt a kártyát.%LINEBREAK%B oldal (Closed):Csökkentsd az elsődleges támadási értékedet eggyel.  Az akciósávod megkapja a %BOOST% ikont.  A (%BANKLEFT% 2) és (%BANKRIGHT% 2 ) mozgást kezeld zöldként.%LINEBREAK%Az aktivációs fázis kezdetén átfordíthatod ezt a kártyát.'''
+        'Multi-spectral Camouflage':
+            text: '''Csak kis hajók.%LINEBREAK%Miután kapsz egy piros célpontbemérő jelzőt, ha csak 1 ilyen jelződ van, dobj egy védekező kockával. %EVADE% dobás esetén vegyél le egy piros célpontbemérő jelzőt a hajódról.'''

     title_translations =
         "Slave I":


Collection object to track user's collection.

--- coffeescripts/manifest.coffee
+++ coffeescripts/manifest.coffee
@@ -1472,3 +1472,41 @@
             count: 3
         }
     ]
+
+class exportObj.Collection
+    # collection = new exportObj.Collection
+    #   expansions:
+    #     "Core": 2
+    #     "TIE Fighter Expansion Pack": 4
+    #     "B-Wing Expansion Pack": 2
+    #
+    # collection.use "pilot", "Red Squadron Pilot"
+    # collection.use "upgrade", "R2-D2"
+    # collection.use "upgrade", "Ion Pulse Missiles" # returns false
+    #
+    # collection.release "pilot", "Red Squadron Pilot"
+    # collection.release "pilot", "Sigma Squadron Pilot" # returns false
+
+    constructor: (args) ->
+        @shelf = {}
+        @table = {}
+        for expansion, count of args.expansions
+            for card in exportObj.manifestByExpansion[expansion]
+                for _ in [1..card.count]
+                    ((@shelf[card.type] ?= {})[card.name] ?= []).push expansion
+
+    use: (type, name) ->
+        card = @shelf[type][name].pop()
+        if card?
+            ((@table[type] ?= {})[name] ?= []).push card
+            true
+        else
+            false
+
+    release: (type, name) ->
+        card = @table[type][name].pop()
+        if card?
+            ((@shelf[type] ?= {})[name] ?= []).push card
+            true
+        else
+            false


Add unfollow button to profile if already following

--- assets/javascripts/views/profile_follow_button.js.coffee
+++ assets/javascripts/views/profile_follow_button.js.coffee
@@ -10,20 +10,35 @@
     }, (followings, xhr) =>
       return unless xhr.status == 200
       if followings.length
+        @following_id = followings[0].id
         @setFollowing()

     @$el.on 'submit', @submit

   submit: (e) =>
     e.preventDefault()
+    if @is_following
+      return unless confirm("Unfollow?")
+      path = "/#{@following_id}"
+    else
+      path = ''
     entity = TentStatus.config.domain_entity.toString()
     @buttons.submit.attr 'disabled', 'disabled'
-    new HTTP 'POST', "#{TentStatus.config.tent_api_root}/followings", { entity: entity }, (following, xhr) =>
+    method = if @is_following then 'DELETE' else 'POST'
+    new HTTP method, "#{TentStatus.config.tent_api_root}/followings#{path}", { entity: entity }, (following, xhr) =>
       unless xhr.status == 200
         @buttons.submit.removeAttr 'disabled'
         return
-      @setFollowing()
+      if @is_following then @unsetFollowing() else @setFollowing()

   setFollowing: =>
-    @buttons.submit.val 'Following'
-    @buttons.submit.attr 'disabled', 'disabled'
+    @is_following = true
+    @buttons.submit.val 'Unfollow'
+    @buttons.submit.removeClass('btn-success').addClass('btn-danger')
+    @buttons.submit.removeAttr 'disabled'
+
+  unsetFollowing: =>
+    @is_following = false
+    @buttons.submit.val 'Follow'
+    @buttons.submit.removeClass('btn-danger').addClass('btn-success')
+    @buttons.submit.removeAttr 'disabled'


Move CSS parsers into class methods

Move CSS parsers into class methods

--- vendor/assets/javascripts/chosen/chosen.coffee
+++ vendor/assets/javascripts/chosen/chosen.coffee
@@ -68,12 +68,12 @@

     container_props =
       class: container_classes.join ' '
-      css: if @width then { width: @width } else @getCSSProperties(@target, ["width", "min-width", "max-width"])
+      css: if @width then { width: @width } else Chosen.getCSSProperties(@target, ["width", "min-width", "max-width"])

     @$target.addClass("chosen")
     @$target.addClass("ios") if is_ios

-    attrs = @getCSSProperties(@target, ["height"])
+    attrs = Chosen.getCSSProperties(@target, ["height"])

     container_props.css['min-height'] = attrs.height if attrs.height
     container_props.title = @target.title if @target.title.length
@@ -112,35 +112,8 @@
     @$dropdown.$list.$no_results = $("<li />", class: "chosen-noresults")
     @$dropdown.$list.suggestion = null

-  getCSSProperties: (node, properties)->
-    attrs = {}
-    return attrs if Chosen.is_crappy_browser(8)
-
-    sheets = document.styleSheets
-    node.matches = node.matches or node.webkitMatchesSelector or node.mozMatchesSelector or node.msMatchesSelector or node.oMatchesSelector
-    matches = []
-
-    for i of sheets
-      try
-        rules = sheets[i].rules or sheets[i].cssRules
-      catch
-        continue
-
-      for r of rules
-        try
-          if node.matches(rules[r].selectorText)
-            matches.push rules[r].cssText
-        catch
-          continue
-
-    for p in properties
-      for m in matches
-        match = m.match(new RegExp("(?:\\s|;|^)#{p}:\\s*([^;]+)"))
-
-        if match
-          attrs[p] = match[1]
-
-    attrs
+  getCSSProperties: (node, properties) ->
+    Chosen.getCSSProperties(arguments...)

   bind_events: ->
     if @target.id.length
@@ -639,6 +612,45 @@

     return @

+  @getCSSProperties: (node, properties) ->
+    attrs = {}
+    return attrs if Chosen.is_crappy_browser(8)
+
+    matches = []
+    sheets = document.styleSheets
+    node.matches = node.matches or node.webkitMatchesSelector or
+      node.mozMatchesSelector or node.msMatchesSelector or node.oMatchesSelector
+
+    for i of sheets
+      try
+        rules = sheets[i].rules or sheets[i].cssRules
+      catch
+        continue
+
+      Chosen.scanCSSRules(node, rules, matches)
+
+    for p in properties
+      for m in matches
+        match = m.match(new RegExp("(?:\\s|;|^)#{p}:\\s*([^;]+)"))
+
+        if match
+          attrs[p] = match[1]
+
+    attrs
+
+  @scanCSSRules: (node, rules, matches) ->
+    for r of rules
+      try
+        switch rules[r].type
+          when 1
+            if node.matches(rules[r].selectorText)
+              matches.push rules[r].cssText
+          when 4
+            if window.matchMedia and window.matchMedia(rules[r].media.mediaText).matches
+              Chosen.scanCSSRules(node, rules[r].rules or rules[r].cssRules, matches)
+      catch
+        continue
+
   @is_ios: ->
     /iPad|iPhone|iPod/.test(navigator.userAgent) and not window.MSStream


Use UI classes for prompt

Use UI classes for prompt

Adds the `tool-panel` and `panel-bottom` classes to the prompt, to match other Atom panels and enable UI themes to work. Also altered the capitalisation of "abbreviation" in the placeholder to match other placeholders in Atom.

--- lib/prompt.coffee
+++ lib/prompt.coffee
@@ -10,7 +10,7 @@
 	@attach: -> new PromptView

 	@content: ->
-		@div class: 'emmet-prompt mini', =>
+		@div class: 'emmet-prompt tool-panel panel-bottom', =>
 			# @label class: 'emmet-prompt__label', outlet: 'label'
 			@div class: 'emmet-prompt__input', =>
 				@subview 'panelInput', new EditorView(mini: true)
@@ -26,7 +26,7 @@
 	show: (@delegate={}) ->
 		@editor = @delegate.editor
 		@editorView = @delegate.editorView
-		@panelInput.setPlaceholderText @delegate.label or 'Enter Abbreviation'
+		@panelInput.setPlaceholderText @delegate.label or 'Enter abbreviation'
 		@updated = no

 		@attach()
@@ -79,4 +79,4 @@
 		atom.workspaceView.prependToBottom(this)
 		@panelInput.focus()
 		@trigger 'attach'
-		method(@delegate, 'show')()+		method(@delegate, 'show')()


Attempt to fix sometimes-failing classroom travis test

Rather than assuming the first course will always be the first one created,
find the specific course in the classroom that was updated and test that.

--- spec/server/functional/classrooms.spec.coffee
+++ spec/server/functional/classrooms.spec.coffee
@@ -712,7 +712,7 @@
     done()

   describe 'addNewCoursesOnly', ->
-    it 'only adds new courses, but leaves existing courses intact', utils.wrap (done) ->
+    it 'only adds new courses, but leaves existing courses intact', utils.wrap ->
       yield utils.clearModels [User, Classroom, Course, Level, Campaign]

       admin = yield utils.initAdmin()
@@ -722,7 +722,7 @@
       yield utils.loginUser(admin)
       levels = yield _.times(3, -> utils.makeLevel())
       firstCampaign = yield utils.makeCampaign({}, {levels: [levels[0]]})
-      yield utils.makeCourse({releasePhase: 'released'}, {campaign: firstCampaign})
+      firstCourse = yield utils.makeCourse({releasePhase: 'released'}, {campaign: firstCampaign})

       # make a classroom, make sure it has the one course
       yield utils.loginUser(teacher)
@@ -752,16 +752,19 @@
       yield utils.loginUser(teacher)
       [res, body] = yield request.postAsync { uri: classroomsURL + "/#{classroom.id}/update-courses", json: { addNewCoursesOnly:true } }
       expect(body.courses.length).toBe(2)
-      expect(body.courses[0].levels.length).toBe(1)
+      course = _.find(body.courses, {_id: firstCourse.id})
+      expect(course.levels.length).toBe(1)
       classroom = yield Classroom.findById(res.body._id)
       expect(classroom.get('courses').length).toBe(2)
-      expect(classroom.get('courses')[0].levels.length).toBe(1)
+      course = _.find(classroom.get('courses'), (course) => course._id.equals(firstCourse._id))
+      expect(course.levels.length).toBe(1)

       # update without addNewCoursesOnly, make sure first course still updates
       [res, body] = yield request.postAsync { uri: classroomsURL + "/#{classroom.id}/update-courses", json: true }
       expect(body.courses.length).toBe(2)
-      expect(body.courses[0].levels.length).toBe(2)
+      course = _.find(body.courses, {_id: firstCourse.id})
+      expect(course.levels.length).toBe(2)
       classroom = yield Classroom.findById(res.body._id)
       expect(classroom.get('courses').length).toBe(2)
-      expect(classroom.get('courses')[0].levels.length).toBe(2)
-      done()
+      course = _.find(classroom.get('courses'), (course) => course._id.equals(firstCourse._id))
+      expect(course.levels.length).toBe(2)


Redo plugin access

Redo plugin access

--- webpack/ugly.coffee
+++ webpack/ugly.coffee
@@ -2,7 +2,8 @@
 Patch webpack's UglifyJS plugin
 ###

-ujs = require 'webpack/lib/optimize/UglifyJsPlugin'
+ujs = require 'webpack'
+  .optimize.UglifyJsPlugin

 module.exports = (options)->
   apply: (compiler)->


Client/Tests: added hubspot into parallel tests

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -22,6 +22,7 @@
 module.exports = [

   [
+    { name: 'hubspot' }
     { name: 'register' }
     { name: 'login' }
     { name: 'logout' }


added back check for Null Grammar when new file has no selection. leaving Plain Text in case of selection or autoselected to that type.

--- lib/script.coffee
+++ lib/script.coffee
@@ -48,7 +48,7 @@
     err = null;

     # Determine if no language is selected
-    if grammar.name == "Plain Text"
+    if grammar.name == "Null Grammar" or grammar.name == "Plain Text"
       err =
         "Must select a language in the lower left or " +
         "save the file with an appropriate extension."


RemoteLogin: Fix trying to use client-secret when not set

After we moved to Webpack, the conditional check for client_secret
started evaluating to true even if it actually empty

--- components/RemoteLogin.coffee
+++ components/RemoteLogin.coffee
@@ -22,10 +22,14 @@
         return callback null, null

       callback null, token_found
+
+  config =
+    client_secret: '$NOFLO_OAUTH_CLIENT_SECRET'
+
   # get token directly from provider
-  if '$NOFLO_OAUTH_CLIENT_SECRET' isnt ''
+  if config.client_secret
     redirect = params.redirect or window.location.href
-    post_params = "code=#{code[1]}&client_id=#{params.clientid}&grant_type=authorization_code&client_secret=$NOFLO_OAUTH_CLIENT_SECRET&redirect_uri=#{encodeURIComponent(redirect)}"
+    post_params = "code=#{code[1]}&client_id=#{params.clientid}&grant_type=authorization_code&client_secret=#{config.client_secret}&redirect_uri=#{encodeURIComponent(redirect)}"
     req.open 'POST', "#{params.site}$NOFLO_OAUTH_ENDPOINT_TOKEN", true
     # Set headers required for POST request
     req.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
@@ -34,9 +38,10 @@
     # Send data
     req.send post_params
   # get token from oauth2 gate
-  if '$NOFLO_OAUTH_CLIENT_SECRET' is '' or null
+  else
     req.open 'GET', "#{params.gatekeeper}$NOFLO_OAUTH_ENDPOINT_AUTHENTICATE/#{code[1]}", true
     req.send null
+

 exports.getComponent = ->
   c = new noflo.Component


Fix jira on subdomains

Fix jira on subdomains

--- plugins/jira/index.coffee
+++ plugins/jira/index.coffee
@@ -4,7 +4,7 @@
 class Jira extends NotificationPlugin
   stacktraceLines = (stacktrace) ->
     ("#{line.file}:#{line.lineNumber} - #{line.method}" for line in stacktrace when line.inProject)
-
+
   jiraBody = (event) ->
     """
     h1. #{event.trigger.message} in #{event.project.name}
@@ -22,7 +22,7 @@

     [View full stacktrace|#{event.error.url}]
     """
-
+
   @receiveEvent: (config, event, callback) ->
     # Build the ticket payload
     payload =
@@ -42,9 +42,12 @@
     if config.customFields
       Object.merge(payload.fields, JSON.parse(config.customFields))

+    # https://example.com/jira becomes https://example.com/jira/
+    config.host += "/" unless /\/$/.test(config.host)
+
     # Send the request
     @request
-      .post(url.resolve(config.host, "/rest/api/2/issue"))
+      .post(url.resolve(config.host, "rest/api/2/issue"))
       .timeout(4000)
       .auth(config.username, config.password)
       .set('Accept', 'application/json')
@@ -57,6 +60,6 @@
         callback null,
           id: res.body.id
           key: res.body.key
-          url: url.resolve(config.host, "/browse/#{res.body.key}")
-
-module.exports = Jira+          url: url.resolve(config.host, "browse/#{res.body.key}")
+
+module.exports = Jira


<debug> Various errors in tests.

<debug> Various errors in tests.

--- tests/controllers/sensor.coffee
+++ tests/controllers/sensor.coffee
@@ -77,48 +77,43 @@
             # expect(@body).to.not.exist
             expect(@response.statusCode).to.equal 500
             expect(@body).to.exist
-            expect(@body.error).to.exist
             expect(@body.error).to.equal 'Device not supported'

     describe 'When we get a Sensor (GET /sensors/:id) which exists', ->

-        id = store.sensorId
-
-        it 'should allow requests', (done) ->
+        it 'should allow requests', (done) ->
+            @client.get "sensors/#{store.sensorId}", done
+
+        it 'should reply with the corresponding sensor', ->
+            expect(@err).to.not.exist
+            expect(@body.customId).to.equal fixturesSensor.supportedSensor1.customId
+            expect(@body.name).to.equal store.sensorName
+            expect(@body.driverId).to.equal store.driver.id
+            expect(@body.id).to.equal store.sensorId
+
+
+    describe 'When we get a Sensor (GET /sensors/:id) which doesn\'t exist', ->
+
+
+        it 'should allow requests', (done) ->
+            id = store.sensorId + 404 # since "store.sensorId" is the only correct ID in DB, "store.sensorId + 404" is not.
             @client.get "sensors/#{id}", done

-        it 'should reply with the corresponding sensor', ->
-            expect(@err).to.not.exist
-            expect(@body.customId).to.equal fixturesSensor.supportedSensor1.customId
-            expect(@body.name).to.equal fixturesSensor.supportedSensor1.sensorName
-            expect(@body.driverId).to.equal store.driver.id
-            expect(@body.id).to.equal store.sensorId
-
-
-    describe 'When we get a Sensor (GET /sensors/:id) which doesn\'t exist', ->
-
-        id = store.sensorId + 404 # since "store.sensorId" is the only correct ID in DB, "store.sensorId + 404" is not.
-
-        it 'should allow requests', (done) ->
-            @client.get "sensors/#{id}", done
-
-        it 'should return an error', ->
-            expect(@body).to.not.exist
+        it 'should return an error', ->
             expect(@response.statusCode).to.equal 404
-            expect(@err).to.equal 'Sensor not found'
+            expect(@body).to.exist
+            expect(@body.error).to.equal 'Sensor not found'

     describe 'When we update a Sensor (PUT /sensors/:id) with data considered valid by its Driver', ->

-        update = fixturesSensor.validUpdateForTestSensor
-
-        it 'should allow requests', (done) ->
-            @client.put "sensors/#{store.sensorId}", update, done
+        it 'should allow requests', (done) ->
+            @client.put "sensors/#{store.sensorId}", fixturesSensor.validUpdateForTestSensor, done

         it 'should reply with the updated sensor', ->
             expect(@err).to.not.exist
             expect(@response.statusCode).to.equal 200
             expect(@body.customId).to.equal fixturesSensor.validUpdateForTestSensor.customId
-            expect(@body.name).to.equal fixturesSensor.validUpdateForTestSensor.sensorName
+            expect(@body.name).to.equal fixturesSensor.validUpdateForTestSensor.name
             expect(@body.driverId).to.equal store.driver.id
             expect(@body.id).to.equal store.sensorId

@@ -144,7 +139,7 @@
             expect(@err).to.not.exist
             expect(@response.statusCode).to.equal 200
             expect(@body.customId).to.equal fixturesSensor.validUpdateForTestSensor.customId
-            expect(@body.name).to.equal fixturesSensor.validUpdateForTestSensor.sensorName
+            expect(@body.name).to.equal fixturesSensor.validUpdateForTestSensor.name
             expect(@body.driverId).to.equal store.driver.id
             expect(@body.id).to.equal store.sensorId

@@ -197,7 +192,7 @@
         it 'should return it', ->
             expect(@err).to.not.exist
             expect(@body.customId).to.equal fixturesSensor.supportedSensor1.customId
-            expect(@body.name).to.equal fixturesSensor.supportedSensor1.sensorName
+            expect(@body.name).to.equal fixturesSensor.supportedSensor1.name
             expect(@body.driverId).to.equal store.driver.id
             expect(@body.id).to.equal @sensor.id


Preview is on https

Preview is on https

--- app/index.coffee
+++ app/index.coffee
@@ -47,7 +47,7 @@
     ExperimentServer.resetExperimentalFlags()
     Geordi.logEvent 'logout'

-[host, proxyPath] = if location.origin is 'http://preview.zooniverse.org'
+[host, proxyPath] = if location.origin is 'https://preview.zooniverse.org'
   ['https://dev.zooniverse.org', '/proxy']
 else if +location.port < 1024
   ['https://www.snapshotserengeti.org', '/_ouroboros_api/proxy']


Teams: Team Settings: Make consistency about text of image/logo upload buttons

--- client/admin/lib/views/groupgeneralsettingsview.coffee
+++ client/admin/lib/views/groupgeneralsettingsview.coffee
@@ -95,7 +95,7 @@

     section.addSubView @uploadButton = new KDButtonView
       cssClass : 'compact solid green upload'
-      title    : 'UPLOAD IMAGE'
+      title    : 'UPLOAD LOGO'
       loader   : yes

     section.addSubView @removeLogoButton = new KDButtonView


Sidebar: Eliminate `MainController::ready` listener

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -477,10 +477,9 @@

     super

-    kd.getSingleton('mainController').ready =>
-      @addMachineList()
-      @addFollowedTopics()
-      @addMessages()
+    @addMachineList()
+    @addFollowedTopics()
+    @addMessages()

   initiateFakeCounter: ->


statusbarmenu: display dynamic shortcuts data

--- client/ide/lib/views/statusbar/idestatusbarmenu.coffee
+++ client/ide/lib/views/statusbar/idestatusbarmenu.coffee
@@ -2,82 +2,74 @@
 KDContextMenu = kd.ContextMenu
 IDEStatusBarMenuItem = require './idestatusbarmenuitem'
 IDESyntaxSelectorMenuItem = require './idesyntaxselectormenuitem'
-isNavigatorApple = require 'app/util/isNavigatorApple'
+{ presentBinding } = require 'app/shortcutscontroller'
+_ = require 'lodash'

+module.exports =

-module.exports = class IDEStatusBarMenu extends KDContextMenu
+class IDEStatusBarMenu extends KDContextMenu

-  constructor: (options = {}) ->
+  constructor: (options) ->

-    menuItems             = @getMenuItems options
-    {delegate}            = options
-    options.menuWidth     = 220
-    options.x             = delegate.getX() - 5
-    options.y             = delegate.getY() + 20
-    options.cssClass      = 'status-bar-menu'
-    options.treeItemClass = IDEStatusBarMenuItem
+    { delegate } = options

-    super options, menuItems
+    defaults =
+      menuWidth     : 220
+      x             : delegate.getX() - 5
+      y             : delegate.getY() + 20
+      cssClass      : 'status-bar-menu'
+      treeItemClass : IDEStatusBarMenuItem
+
+    super _.extend(defaults, options), @getItems()

     @on 'ContextMenuItemReceivedClick', (view, event) =>
       unless event.target.parentNode.classList.contains 'kdselectbox'
         @destroy()

-  getMenuItems: ->
-    navigatorIsApple = isNavigatorApple()
+
+  getItems: ->
+
+    { shortcuts, appManager } = kd.singletons
+
+    collection = shortcuts.toCollection()
+
+    subcollections =
+      editor: collection.find _key: 'editor'
+      workspace: collection.find _key: 'workspace'

     @syntaxSelector = new IDESyntaxSelectorMenuItem

-    list = [
-      # name                   # shortcut      # cmd
-      [ 'Save'               , 'Meta+S'      , 'saveFile' ]
-      [ 'Save As...'         , 'Meta+Shift+S', 'saveAs' ]
-      [ 'Save All'           , 'Ctrl+Alt+S'  , 'saveAllFiles' ]
-      [ 'Syntax'             , @syntaxSelector ]
-      [ 'Preview'            , 'Ctrl+Alt+P'  , 'previewFile' ]
-      [ 'Find...'            , 'Meta+F'      , 'showFindReplaceView' ]
-      [ 'Find and replace...', 'Meta+Shift+F', 'showFindReplaceViewWithReplaceMode' ]
-      [ 'Find in files...'   , 'Ctrl+Alt+F'  , 'showContentSearch' ]
-      [ 'Jump to file...'    , 'Ctrl+Alt+O'  , 'showFileFinder' ]
-      [ 'Go to line...'      , 'Meta+G'      , 'goToLine' ]
-    ]
+    _
+      .chain [
+        # Shortcut                 # IDE method
+        'editor.save'              , 'saveFile'
+        'editor.saveas'            , 'saveAs'
+        'workspace.saveallfiles'   , 'saveAllFiles'
+        'Syntax'                   , @syntaxSelector # Title/Instance
+        'workspace.previewfile'    , 'previewFile'
+        'editor.find'              , 'showFindReplaceView'
+        'editor.replace'           , 'showFindReplaceViewWithReplaceMode'
+        'workspace.searchallfiles' , 'showContentSearch'
+        'workspace.findfilebyname' , 'showFileFinder'
+        'editor.gotoline'          , 'goToLine'
+      ]
+      .chunk 2
+      .reduce (acc, pair) ->
+        [ key, value ] = pair

-    # it is safe to display unicode definitions on mac
-    macKeysUnicodeMapping =
-      'Shift': '⇧'
-      'Meta' : '⌘'
-      'Alt'  : '⌥'
-      'Ctrl' : '^'
+        if _.isString value
+          [ collectionName, modelName ] = key.split '.'
+          { description, binding } = subcollections[collectionName].find name: modelName
+          obj =
+            shortcut: presentBinding _.first binding
+            callback: appManager.tell.bind appManager, 'IDE', value
+        else
+          obj =
+            type: 'customView'
+            view: value

-    winKeysMapping =
-      'Meta': 'Ctrl'
+        acc[description or key] = obj

-    appManager = kd.getSingleton 'appManager'
-
-    items = {}
-
-    while (item = list.shift())?
-      isCustomView = typeof item[1] isnt 'string'
-      key = item[0]
-
-      unless isCustomView
-
-        if navigatorIsApple
-          for own k, v of macKeysUnicodeMapping
-            item[1] = item[1].replace(k, v)
-          # by tradition osx is not displaying + for shortcuts
-          item[1] = item[1].replace(/\+/g, '')
-        else
-          for own k, v of winKeysMapping
-            item[1] = item[1].replace(k, v)
-
-        items[key] =
-          shortcut: item[1]
-          callback: appManager.tell.bind appManager, 'IDE', item[2]
-
-      else
-        items[key] =
-          type: 'customView'
-          view: item[1]
-
-    return items
+        acc
+      , {}
+      .value()


default-apps: Added createExtensionToAppMap and fetchUserDefaultAppConfig methods to KodingAppCcontroller.

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -27,6 +27,9 @@
     @kiteController = @getSingleton "kiteController"
     @manifests      = KodingAppsController.manifests
     @getPublishedApps()
+
+    @createExtensionToAppMap()
+    @fetchUserDefaultAppConfig()

   getAppPath:(manifest, escaped=no)->

@@ -598,6 +601,29 @@
             @appManager.open manifest.name
             modal.destroy()
             @skipUpdate = no
+
+  createExtensionToAppMap: ->
+    @extensionToApp = map = {}
+    @fetchApps (err, res) =>
+      # TODO: Find a better way to add Ace extensions to this map. (fatihacet)
+      res.Ace =
+        name     : "Ace"
+        fileTypes: [
+          "php", "pl", "py", "jsp", "asp", "aspx", "htm", "html", "phtml","shtml",
+          "sh", "cgi", "htaccess","fcgi","wsgi","mvc","xml","sql","rhtml", "diff",
+          "js","json", "coffee", "css","styl","sass", "scss", "less", "txt"
+        ]
+      for key, app of res
+        fileTypes = app.fileTypes
+        if fileTypes
+          for type in fileTypes
+            map[type] = [] unless map[type]
+            map[type].push app.name
+
+  fetchUserDefaultAppConfig: ->
+    appConfigStorage = new AppStorage "DefaultAppConfig", "1.0"
+    appConfigStorage.fetchStorage (storage) ->
+      log storage

   defaultManifest = (type, name)->
     {profile} = KD.whoami()


Fix StartTab default App shortcuts issues.

Instead of creating storage in constructor just create it when we need.

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -64,17 +64,15 @@
     super

     @kiteController = @getSingleton('kiteController')
-    @appStorage = new AppStorage 'KodingApps', '1.0'
-
-    @appStorage.fetchStorage (storage)=>
-      if not @appStorage.getValue 'shortcuts'
-        @putDefaultShortcutsToAppStorage()

   # #
   # FETCHERS
   # #

   fetchApps:(callback)->
+
+    if KD.isLoggedIn() and not @appStorage?
+      @appStorage = new AppStorage 'KodingApps', '1.0'

     if Object.keys(@constructor.manifests).length isnt 0
       callback null, @constructor.manifests
@@ -141,12 +139,23 @@

   fetchAppsFromDb:(callback)->

-    @appStorage.fetchValue 'apps', (apps)=>
-      if apps and Object.keys(apps).length > 0
-        @constructor.manifests = apps
-        callback null, apps
+    @appStorage.fetchStorage (storage)=>
+
+      apps = @appStorage.getValue 'apps'
+      shortcuts = @appStorage.getValue 'shortcuts'
+
+      justFetchApps = =>
+        if apps and Object.keys(apps).length > 0
+          @constructor.manifests = apps
+          callback null, apps
+        else
+          callback new Error "There are no apps in the app storage."
+
+      if not shortcuts
+        @putDefaultShortcutsToAppStorage =>
+          justFetchApps()
       else
-        callback new Error "There are no apps in the app storage."
+        justFetchApps()

   fetchCompiledApp:(manifest, callback)->

@@ -177,7 +186,7 @@
       @appStorage.setValue 'shortcuts', shortcuts, (err)=>
         callback err

-  putDefaultShortcutsToAppStorage:->
+  putDefaultShortcutsToAppStorage:(callback)->

     shortcuts       =
       Ace           :
@@ -212,7 +221,8 @@
         description : 'Image Editor'
         author      : 'Autodesk'

-    @appStorage.setValue 'shortcuts', shortcuts
+    @appStorage.reset()
+    @appStorage.setValue 'shortcuts', shortcuts, callback

   putAppsToAppStorage:(apps)->


Die curlies, die.

Die curlies, die.

--- application/chrome/content/wesabe/download/Controller.coffee
+++ application/chrome/content/wesabe/download/Controller.coffee
@@ -236,7 +236,10 @@
   eval: (data) ->
     try
       script = data.script
-      @scope ||= {logger: Logger.loggerForFile('repl'), __filename: 'repl', __dirname: '.'}
+      @scope ||=
+        logger: Logger.loggerForFile('repl'),
+        __filename: 'repl',
+        __dirname: '.'
       @scope.job = @job

       if data.type is 'text/coffeescript'


Remove warning about empty element

Remove warning about empty element

--- src/stateful_widget.js.coffee
+++ src/stateful_widget.js.coffee
@@ -52,8 +52,6 @@
     constructor: ( selector ) ->
       @_uid ?= @_generateUID()
       @$el = $( selector )
-      if console?.warn? and @$el.length is 0
-        console.warn "No DOM elements found with selector '#{selector}'"
       @el = @$el[0]
       @initState @_states[0] if @_states?[0]?
       @_bindEvents()


Activity: list item avatar view origins corrected as for the new data

--- client/Social/Activity/ListItems/activitylistitem.coffee
+++ client/Social/Activity/ListItems/activitylistitem.coffee
@@ -11,8 +11,8 @@
     data   = @getData()
     list   = @getDelegate()
     origin =
-      constructorName : data.originType
-      id              : data.originId
+      constructorName : data.account.constructorName
+      id              : data.account._id

     @avatar = new AvatarView
       size       :


Notification controller issues fixed pointed in #7140065591077

--- client/landing/app/MainApp/notificationcontroller.coffee
+++ client/landing/app/MainApp/notificationcontroller.coffee
@@ -106,11 +106,20 @@

     return  unless actor

+    fetchSubjectObj = (callback)=>
+      if not subject or subject.constructorName is "JPrivateMessage"
+        return callback null
+      if subject.constructorName in ["JComment", "JOpinion"]
+        method = 'fetchRelated'
+        args   = subject.id
+      else
+        method = 'one'
+        args   = _id: subject.id
+      KD.remote.api[subject.constructorName][method] args, callback
+
     KD.remote.cacheable actor.constructorName, actor.id, (err, actorAccount)=>
-      KD.remote.api[subject.constructorName].one _id: subject.id, (err, subjectObj)=>
-
+      fetchSubjectObj (err, subjectObj)=>
         actorName = KD.utils.getFullnameFromAccount actorAccount
-
         options.title = switch actionType
           when "reply", "opinion"
             if isMine
@@ -151,13 +160,12 @@

         if subject
           options.click = ->
-            view = @
+            view = this
             if subject.constructorName is "JPrivateMessage"
-              KD.getSingleton("appManager").openApplication "Inbox"
-            else if subject.constructorName in ["JComment", "JOpinion"]
-              KD.remote.api[subject.constructorName].fetchRelated subject.id, (err, post) ->
-                KD.getSingleton('router').handleRoute "/Activity/#{post.slug}", state:post
-                # appManager.tell "Activity", "createContentDisplay", post
+              KD.getSingleton('router').handleRoute "/Inbox"
+            else if subjectObj.constructor.name is "JOpinion"
+              KD.remote.api.JOpinion.fetchRelated subjectObj._id, (err, post) ->
+                KD.getSingleton('router').handleRoute "/Activity/#{post.slug}"
                 view.destroy()
             else if subject.constructorName is 'JGroup'
               suffix = ''
@@ -165,8 +173,7 @@
               KD.getSingleton('router').handleRoute "/#{subjectObj.slug}#{suffix}"
               view.destroy()
             else
-              # appManager.tell "Activity", "createContentDisplay", post
-              KD.getSingleton('router').handleRoute "/Activity/#{subjectObj.slug}", state:post
+              KD.getSingleton('router').handleRoute "/Activity/#{subjectObj.slug}"
               view.destroy()

         options.type  = actionType or actorType or ''
@@ -186,5 +193,3 @@
       content  : options.content  or null

     notification.once 'click', options.click
-
-


Set tree view node placeholder only if node is valid

--- src/components/tree/treeviewcontroller.coffee
+++ src/components/tree/treeviewcontroller.coffee
@@ -106,8 +106,6 @@
     nodeData[idPath]   = "#{@getNodeId nodeData}"
     nodeData[pIdPath]  = if @getNodePId nodeData then "#{@getNodePId nodeData}" else "0"

-    @nodes[@getNodeId nodeData] = {}
-
     if options.putDepthInfo
       if @nodes[nodeData[pIdPath]]?.getData
         nodeData.depth = @nodes[nodeData[pIdPath]].getData().depth + 1
@@ -116,6 +114,9 @@

     if nodeData[pIdPath] isnt "0" and not @nodes[nodeData[pIdPath]]
       if options.addOrphansToRoot then nodeData[pIdPath] = "0" else nodeData = no
+
+    if nodeData
+      @nodes[@getNodeId nodeData] = {}

     return nodeData


only use redux dev tools on dev

only use redux dev tools on dev

--- app/store.coffee
+++ app/store.coffee
@@ -9,7 +9,10 @@
 actions = require 'actions'
 history = require 'myhistory'

-compose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? compose
+# Activate dev tools.
+if process.env.NODE_ENV is 'development'
+  compose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? compose
+
 store = createStore reducers, compose applyMiddleware(
   thunk
   routerMiddleware history


Removed ambiguity in example query in browser.

--- community/browser/app/scripts/init/defaultContent.coffee
+++ community/browser/app/scripts/init/defaultContent.coffee
@@ -44,6 +44,7 @@
         content: """
 // What is related, and how
 MATCH (a)-[r]->(b)
+WHERE labels(a) <> [] AND labels(b) <> []
 RETURN DISTINCT head(labels(a)) AS This, type(r) as To, head(labels(b)) AS That
 LIMIT 10
         """