Use author indices to eliminate nested loops when parsing

--- src/scripts/models/search-results.coffee
+++ src/scripts/models/search-results.coffee
@@ -69,7 +69,7 @@

       response.results.auxiliary or= {}

-      authors = new Backbone.Collection(response.results.auxiliary.authors)
+      authors = response.results.auxiliary.authors
       types = new Backbone.Collection(response.results.auxiliary.types)

       # Add natural language translation alongside tags
@@ -78,20 +78,15 @@

         if limit.tag is 'authorID'
           limit.name = 'Author ID'
-          author = authors.get(limit.value).toJSON()
+          author = authors[limit.index]
           limit.displayValue = "#{author.fullname} (#{author.id})"
-
-      # Substitute author IDs in results with author objects
-      _.each response.results.items, (item) ->
-        _.each item.authors, (author, index) ->
-          item.authors[index] = authors.get(author).toJSON()

       _.each response.results.limits, (limit) ->
         limit.name = FILTER_NAMES[limit.tag] # Add natural language translation alongside tags

         if limit.tag is 'authorID'
           _.each limit.values, (value) ->
-            author = authors.get(value.value).toJSON()
+            author = authors[value.index]
             value.displayValue = author.fullname

         else if limit.tag is 'type'


fix hostname

fix hostname

--- src/smc-hub/kucalc/compute-client.coffee
+++ src/smc-hub/kucalc/compute-client.coffee
@@ -44,7 +44,7 @@

 class Project extends Dbg
     constructor: (@compute_server, @project_id, @logger) ->
-        @host = "project-{@project_id}"
+        @host = "project-#{@project_id}"

     free: () =>


Do not update app script if not provided

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -215,7 +215,7 @@

   defineApp:(name, script)->

-    KDApps[name] = script
+    KDApps[name] = script if script

   getAppScript:(manifest, callback = noop)->


Client: remove followbutton

Client: remove followbutton

--- client/app/lib/commonviews/followbutton.coffee
+++ client/app/lib/commonviews/followbutton.coffee
@@ -1,68 +0,0 @@
-$ = require 'jquery'
-requireMembership = require '../util/requireMembership'
-showError = require '../util/showError'
-whoami = require '../util/whoami'
-kd = require 'kd'
-KDToggleButton = kd.ToggleButton
-
-
-module.exports = class FollowButton extends KDToggleButton
-
-  constructor:(options = {}, data)->
-
-    options.cssClass = kd.utils.curry "follow-btn", options.cssClass
-    options = $.extend
-      defaultState : if data.followee then "Following" else "Follow"
-      bind         : 'mouseenter mouseleave'
-      dataPath     : "followee"
-      loader       :
-        color      : "#333333"
-      states       : [
-        title      : "Follow"
-        cssClass   : options.stateOptions?.follow?.cssClass
-        callback   : (cb)=>
-          requireMembership
-            tryAgain  : yes
-            onFailMsg : "Login required to follow"
-            onFail    : => cb yes
-            callback  : =>
-              account = @getData()
-              account.follow (err, response) ->
-                account.followee = response
-                cb? err
-      ,
-        title      : "Following"
-        cssClass   : options.stateOptions?.unfollow?.cssClass
-        callback   : (cb)=>
-          @getData().unfollow (err, response)=>
-            showError err, options.errorMessages
-            @getData().followee = response
-            cb? err
-      ]
-
-    , options
-
-    super options, data
-
-  viewAppended:->
-    super
-
-    unless @getData().followee
-      {dataType} = @getOptions()
-      return  unless dataType
-
-      whoami().isFollowing? @getData().getId(), dataType, \
-      (err, following) =>
-        @getData().followee = following
-        @setState "Following", no  if following
-
-  mouseEnter:->
-    if @getTitle() is "Following"
-      @setTitle "Unfollow"
-
-  mouseLeave:->
-    if @getTitle() is "Unfollow"
-      @setTitle "Following"
-
-
-


sidebar: use new getters cc /@alex-ionochkin

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -16,11 +16,9 @@

   getDataBindings: ->
     return {
-      publicChannels          : getters.followedPublicChannelThreads
+      publicChannels          : getters.followedPublicChannelThreadsWithSelectedChannel
       privateChannels         : getters.followedPrivateChannelThreads
       selectedThreadId        : getters.selectedChannelThreadId
-      filteredPublicChannels  : getters.filteredPublicChannels
-      filteredPrivateChannels : getters.filteredPrivateChannels
       stacks                  : EnvironmentFlux.getters.stacks
       ownMachines             : EnvironmentFlux.getters.ownMachines
       sharedMachines          : EnvironmentFlux.getters.sharedMachines
@@ -67,14 +65,14 @@
     <SidebarChannelsSection
       previewCount={PREVIEW_COUNT}
       selectedId={@state.selectedThreadId}
-      threads={@state.filteredPublicChannels.followed} />
+      threads={@state.publicChannels} />

   renderMessages: ->
     <SidebarMessagesSection
       previewCount={PREVIEW_COUNT}
       selectedId={@state.selectedThreadId}
-      threads={@state.filteredPrivateChannels.followed} />
+      threads={@state.privateChannels} />

   render: ->


Environment: Remove unneeded variable definitions

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -288,22 +288,22 @@

 setMachineListItem = (id, machineListItem) ->

-  { reactor, computeController } = kd.singletons
+  { reactor } = kd.singletons

   reactor.dispatch actions.MACHINE_LIST_ITEM_CREATED, { id ,machineListItem }

 unsetMachineListItem = (id, machineListItem) ->

-  { reactor, computeController } = kd.singletons
+  { reactor } = kd.singletons

   reactor.dispatch actions.MACHINE_LIST_ITEM_DELETED, { id ,machineListItem }

 setActiveInvitationMachineId = (options={}) ->

-  { machine, forceUpdate }       = options
-  { reactor, computeController } = kd.singletons
+  { machine, forceUpdate }  = options
+  { reactor }               = kd.singletons

   id = null

@@ -317,7 +317,7 @@

 setActiveLeavingSharedMachineId = (id) ->

-  { reactor, computeController } = kd.singletons
+  { reactor } = kd.singletons

   reactor.dispatch actions.SET_ACTIVE_LEAVING_SHARED_MACHINE_ID, { id }


video/model: remove defaults from enable video

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -339,8 +339,6 @@
       @handlePublishSuccess publisher
       callbacks.success? publisher

-    defaults = { publishAudio: @isMySession(), publishVideo: @isMySession() }
-    options  = _.assign {}, defaults, options
     @startPublishing options,
       success : success
       error   : callbacks.error


spelling

spelling

--- client/lanes/components/shared/FieldMixin.cjsx
+++ client/lanes/components/shared/FieldMixin.cjsx
@@ -70,7 +70,7 @@
     _fieldMixinRenderFormGroup: (child, props, options) ->
         if (invalidMsg = @fieldInvalidValueMessage())
             msg = <BS.HelpBlock>{invalidMsg}</BS.HelpBlock>
-        unless @props.unlabeld
+        unless @props.unlabeled
             label =
                 <BS.ControlLabel>
                     {@_fieldMixinGetLabelValue()}


always set status_if_new

always set status_if_new

--- services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
+++ services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
@@ -59,16 +59,17 @@

 buildOptions = (user, is_subscribed)->
 	subscriber_hash = hashEmail(user.email)
+	status = if is_subscribed then "subscribed" else "unsubscribed"
 	opts =
 		method: "PUT"
 		path: "/lists/#{Settings.mailchimp?.list_id}/members/#{subscriber_hash}"
 		body:
 			email_address:user.email
+			status_if_new: status

-	status = if is_subscribed then "subscribed" else "unsubscribed"
+	#only set status if we explictly want to set it
 	if is_subscribed?
 		opts.body.status = status
-		opts.body.status_if_new = status

 	if user._id?
 		opts.body.merge_fields =


Disabling form submission in interventions while compute is not done.

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -168,10 +168,14 @@
           data: form.serialize()
           beforeSend: ->
             computing.prop 'state', 'waiting'
+            $("#new_intervention").submit(false)
+            $('.form-actions .primary').attr("disabled", true)
           error: (request, status, error) ->
             computing.prop 'state', 'ready'
             false
           success: (data, status, request) ->
+            $("#new_intervention").submit(null)
+            $('.form-actions .primary').attr("disabled", null)
             console.group('Unserialize intervention updated by ' + updaterId)
             # Updates elements with new values
             E.interventions.toggleHandlers(form, data.handlers, 'intervention_')


Added westcampus redirect

Added westcampus redirect

--- app/assets/javascripts/components/router.js.jsx.coffee
+++ app/assets/javascripts/components/router.js.jsx.coffee
@@ -31,6 +31,7 @@
     for sectionPath, sectionDetails of SECTIONS
       page sectionPath, @createRouteHandlerForPath sectionPath

+    page '/westcampus', '/'
     page '*', @on404NotFound

     # Start tracking user clicks


refactor addTimeAgoSupport to be more like Google+

--- app/assets/javascripts/socializer/activities.js.coffee
+++ app/assets/javascripts/socializer/activities.js.coffee
@@ -3,18 +3,44 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/

 @addTimeAgoSupport = ->
-  # Use moment.js to mimic the Rails time time_ago_in_words helper
+  # Use moment.js to mimic Google+
   $('time').each ->
     timeago  = $(this).data('time-ago')

     # This could also be timeago?, but this is more explicit
-    if timeago is "moment.js"
-      datetime = $(this).attr('datetime')
+    if timeago is 'moment.js'
       locale   = $('body').data('locale')
-
       moment.locale locale

-      $(this).text moment(datetime).fromNow()
+      datetime = moment($(this).attr('datetime'))
+
+      $(this).text distance_of_time(datetime)
+
+distance_of_time = (past_date) ->
+  past_date ?= 0
+  today = moment()
+  days_difference = today.diff(past_date, 'days')
+
+  moment.locale 'en',
+    calendar:
+      sameDay: "LT"
+      nextDay: '[Tomorrow] LT'
+      nextWeek: 'll'
+      lastDay: '[Yesterday] LT'
+      lastWeek: 'll'
+      sameElse: 'll'
+
+  moment.locale 'fr',
+    calendar:
+      sameDay: "LT"
+      nextDay: '[Demain] LT'
+      nextWeek: 'll'
+      lastDay: '[Hier] LT'
+      lastWeek: 'll'
+      sameElse: 'll'
+
+  moment(past_date).calendar()
+

 @addTooltipSupport = (jQueryElement) ->
   jQueryElement.qtip


store genera when saving report

store genera when saving report

--- app/assets/javascripts/stores/questionnaire_store.cjsx
+++ app/assets/javascripts/stores/questionnaire_store.cjsx
@@ -17,7 +17,8 @@
   report = {
     id: null,
     answers: {},
-    state: "in_progress"
+    state: "in_progress",
+    genera: {}
   }

   constructor: ->
@@ -171,6 +172,8 @@
   addVisibilityListener: (callback) => @on(VISIBILITY_EVENT, callback)

   saveOrUpdateReport: (callback) =>
+    @storeGenera()
+
     if report.id?
       @reportRequest("/reports/#{report.id}", "PUT", (response) =>
         @setNotification("success", "Report updated")
@@ -183,6 +186,24 @@

         callback?(response.headers.get('Location'))
       )
+
+  storeGenera: ->
+    genera = {
+      live: [],
+      dead: [],
+      parts: report.answers?.genus_parts?.selected || []
+    }
+
+    for ape in (report.answers?.live || []) when ape.genus_live?.selected
+      if genera.live.indexOf(ape.genus_live.selected) == -1
+        genera.live.push(ape.genus_live.selected)
+
+    for ape in (report.answers?.dead || []) when ape.genus_dead?.selected
+      if genera.dead.indexOf(ape.genus_dead.selected) == -1
+        genera.dead.push(ape.genus_dead.selected)
+
+    report.genera = genera
+

   reportRequest: (path, method, callback) ->
     token = document.getElementsByName("csrf-token")[0].content
@@ -195,7 +216,7 @@
       },
       credentials: 'include',
       body: JSON.stringify({
-        report: {data: {answers: report.answers, state: report.state}}
+        report: {data: {genera: report.genera, answers: report.answers, state: report.state}}
       })
     }).then(callback)


don't mark as read if an entry is collapsed

Change-Id: Ie902071fba4de070a63f297821dd07b3db01ff09
Reviewed-on: https://gerrit.instructure.com/9693
Reviewed-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>
Tested-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>

--- app/coffeescripts/discussions/MarkAsReadWatcher.coffee
+++ app/coffeescripts/discussions/MarkAsReadWatcher.coffee
@@ -25,6 +25,12 @@
     # @param {EntryView} view
     constructor: (@view) ->
       MarkAsReadWatcher.unread.push this
+      @view.model.bind 'change:collapsedView', (model, collapsedView) =>
+        @ignore = collapsedView
+        if collapsedView
+          @clearTimer()
+        else
+          @createTimer()

     createTimer: ->
       @timer ||= setTimeout @markAsRead, MS_UNTIL_READ
@@ -48,6 +54,7 @@
       topOfViewport = $window.scrollTop()
       bottomOfViewport = topOfViewport + $window.height()
       for entry in @unread
+        continue if entry.ignore
         topOfElement = entry.view.$el.offset().top
         inView = (topOfElement < bottomOfViewport) &&
                  (topOfElement + entry.view.$el.height() > topOfViewport)


handle more than one trailing slash

handle more than one trailing slash

--- src/chaplin/lib/route.coffee
+++ src/chaplin/lib/route.coffee
@@ -179,7 +179,7 @@

     # Create the actual regular expression, match until the end of the URL,
     # trailing slash or the begin of query string.
-    @regExp = ///^#{pattern}(?=\/?(?=\?|$))///
+    @regExp = ///^#{pattern}(?=\/*(?=\?|$))///

   parseOptionalPortion: (match, optionalPortion) =>
     # Extract and replace params.


Updated cohtml-definition (CohtmlTextNode)

--- src/cohtml-definition.coffee
+++ src/cohtml-definition.coffee
@@ -6,7 +6,13 @@
     @childrenList = []
     return

+class CohtmlTextNode
+
+  constructor: (@innerText = null)->
+    return
+
 @CohtmlNode = CohtmlNode
+@CohtmlTextNode = CohtmlTextNode


Fix file mask

Fix file mask

--- tasks/coffee.coffee
+++ tasks/coffee.coffee
@@ -6,13 +6,13 @@
     coffee: 'coffee:compile'
   watch:
     coffee:
-      files: 'js/*.coffee'
+      files: 'js/**/*.coffee'
       tasks: ['coffee:compile']
   coffee:
     compile:
       expand: true,
       flatten: true,
       cwd: "#{__dirname}",
-      src: ['js/*.coffee'],
+      src: ['js/**/*.coffee'],
       dest: 'public/js/',
       ext: '.js'


language can now be null and not only undefined

--- BackofficeBundle/Resources/public/coffee/table/tableviewLoader.coffee
+++ BackofficeBundle/Resources/public/coffee/table/tableviewLoader.coffee
@@ -32,7 +32,7 @@
       values = response[collection_name][0]
       element = new TableviewModel
       element.set values
-      if language != undefined
+      if language != undefined && language != null
         link = element.get('links')._self_without_parameters + '?language=' + language
         link = link + '&source_language=' + sourceLanguage if sourceLanguage != undefined
         link = link + '&version=' + version if version != undefined


<model> Actuator - create, delete, update (copy of Sensor)

--- server/models/actuator.coffee
+++ server/models/actuator.coffee
@@ -11,3 +11,131 @@
     customId: 	type : String		# not Empty
     name: 		type : String		# not Empty
     type: 		type : String		# not Empty
+
+	###
+	# destroyFromDBAndDriver
+	# ====
+	# Deletes the Actuator, both from the DB and Driver
+	# @param actuatorsDrivers (Driver[]): 			List of drivers supported by the system
+	# @param callback (Function(Error):null):		Callback
+	###
+	@destroyFromDBAndDriver = (actuatorsDrivers, callback) ->
+		actuatorsDrivers[@type].remove @customId, (err2) ->
+			if err2
+				callback err2
+				return
+			# Remove from DB:
+			@destroy (err3) ->
+				if err3:
+					# Cancelling Modif:
+					actuatorsDrivers[@type].add prevActuator.customId, id, (err2) ->
+						if err2
+							callback 'Device removed from system but not DB. Contact Admin'
+							return
+						callback err3
+						return
+				callback null
+				return
+			return
+		return
+
+	###
+	# updateAttributesForDBAndDriver
+	# ====
+	# Updates data about the Actuator, both for the DB and Driver
+	# @param data (dictionary): 						New data
+	# @param actuatorsDrivers (Driver[]): 				List of drivers supported by the system
+	# @param callback (Function(Error, Actuator):null):	Callback
+	###
+	# @todo Conver special case if "type" is changed -> Then the driver taking care of this device must be changed too!
+	@updateAttributesForDBAndDriver = (data, actuatorsDrivers, callback) ->
+		prevData =
+			customId: @customId
+			name: @name
+			type: @type
+		# Update DB:
+		@updateAttributes data, (err, actuator) ->
+			if err
+				callback err, actuator
+				return
+			# Update Driver:
+			actuatorsDrivers[@type].update prevData.customId, data.customId, (err2) ->
+				if err2:
+					# Cancelling Modif:
+					@updateAttributes prevData, (err3, actuator2) ->
+						if err3
+							callback 'Can't update Actuator in Driver & Can't reverse update in DB. Contact Admin', actuator2
+							return
+						callback err2, actuator2
+						return
+				callback null, actuator
+				return
+			return
+		return
+
+
+
+###
+# byId
+# ====
+# Gets a Actuator using its ID.
+# @param id (ID): 							ID
+# @param callback (Function(Error, Actuator):null): 	Callback
+###
+ActuatorModel.byId = (id, callback) ->
+    param =
+        key: [id]
+    ActuatorModel.request 'byId', param, callback
+
+###
+# getOrCreate
+# ====
+# Gets a Actuator, or creates it if not found.
+# @param data (Object): 							Data defining the actuator
+# @param callback (Function(Actuator, bool):null): 	Callback function. First parameter if the found or created Actuator; Second parameter is a boolean set true if created / false if found.
+###
+ActuatorModel.getOrCreate = (data, callback) ->
+	# customId + type is a primary key.
+	params = key: [accountID, type]
+	ActuatorModel.request "byCustomIdAndType", params, (err, actuators)->
+	if err
+		log.error err
+		callbackCreate = (actuator) ->
+			callback actuator, true
+		ActuatorModel.create data, callbackCreate
+	else if actuators.length is 0
+		callbackCreate = (actuator) ->
+			callback actuator, true
+		ActuatorModel.create data, callbackCreate
+	else # Actuator already exists.
+		callback actuators[0], false
+
+###
+# createIfDriver
+# ====
+# Adds a actuator to the DB and system, if there is a driver to handle it.
+# @param data (Object): 						Data defining the actuator
+# @param actuatorsDrivers (Driver[]): 			List of drivers supported by the system
+# @param callback (Function(Error, Actuator):null): 	Callback
+###
+ActuatorModel.createIfDriver = (data, actuatorsDrivers, callback) ->
+	if actuatorsDrivers[type] # If this kind of device is supported:
+		# Check if this actuator isn't already added (the combination type + customId should be unique):
+		Actuator.getOrCreate data, (actuator, created, err) ->
+			if !created
+				callback 'Device already added', actuator
+				return
+			# Let the driver handle the integration of the device to the system:
+			actuatorsDrivers[type].add customId, actuator.id, (err) ->
+				if err
+					# Cancelling modif:
+					Actuator.requestDestroy "all", {key: actuator.id}, (err) ->
+						callback err, null
+						return
+				else
+					callback null, actuator
+				return
+			return
+	else
+		callback 'Device not supported', null
+	return


done with coffee n' output in json

done with coffee n' output in json


stup getCurrentPathname on router

stup getCurrentPathname on router

--- tutor/test/components/helpers/component-testing.coffee
+++ tutor/test/components/helpers/component-testing.coffee
@@ -21,6 +21,7 @@
   ROUTER.transitionTo = sandbox.spy()
   ROUTER.getCurrentPath = sandbox.spy( -> CURRENT_ROUTER_PATH )
   ROUTER.getCurrentQuery = sandbox.spy( -> CURRENT_ROUTER_QUERY )
+  ROUTER.getCurrentPathname = sandbox.spy( -> '/testing' )
   ROUTER.getLocation = sandbox.spy( ->
     addChangeListener: sandbox.spy()
     removeChangeListener: sandbox.spy()


Use line height instead of char height

Use line height instead of char height

--- src/packages/bracket-matcher/index.coffee
+++ src/packages/bracket-matcher/index.coffee
@@ -49,7 +49,7 @@
     view = $$ -> @div class: 'bracket-matcher'
     view.data('bufferPosition', bufferPosition)
     view.css('top', pixelPosition.top).css('left', pixelPosition.left)
-    view.width(editor.charWidth).height(editor.charHeight)
+    view.width(editor.charWidth).height(editor.lineHeight)

   findCurrentPair: (editor, buffer, matches) ->
     position = editor.getCursorBufferPosition()


Add tests in imperial weight system

Add tests in imperial weight system

--- spec/javascripts/unit/admin/products/services/unit_prices_spec.js.coffee
+++ spec/javascripts/unit/admin/products/services/unit_prices_spec.js.coffee
@@ -4,7 +4,7 @@
   beforeEach ->
     module "admin.products"
     module ($provide)->
-      $provide.value "availableUnits", "g,kg,T,mL,L,kL"
+      $provide.value "availableUnits", "g,kg,T,mL,L,kL,oz,lb"
       null
     inject (_UnitPrices_) ->
       UnitPrices = _UnitPrices_
@@ -114,3 +114,21 @@
       unit_value = 1
       expect(UnitPrices.price(price, scale, unit_type, unit_value)).toEqual 10
       expect(UnitPrices.unit(scale, unit_type)).toEqual "item"
+
+
+  describe "get correct unit price duo unit/value for weight in imperial system", ->
+    unit_type = "weight"
+
+    it "with price: 1 and scale/unit_value: 28.35 (OZ)", ->
+      price = 1
+      scale = 28.35
+      unit_value = 28.35
+      expect(UnitPrices.price(price, scale, unit_type, unit_value)).toEqual 16
+      expect(UnitPrices.unit(scale, unit_type)).toEqual "lb"
+
+    it "with price: 1 and scale/unit_value: 453.6 (LB)", ->
+      price = 1
+      scale = 453.6
+      unit_value = 453.6
+      expect(UnitPrices.price(price, scale, unit_type, unit_value)).toEqual 1
+      expect(UnitPrices.unit(scale, unit_type)).toEqual "lb"


Improved "create" Backbone.sync code for SyncableModel (now matches values to attribute name for INSERT)

--- www/coffee/backbone/lib/syncable_model.coffee
+++ www/coffee/backbone/lib/syncable_model.coffee
@@ -20,26 +20,22 @@
     sql = ""
     switch method
       when "create"
+        fields = []
         values = []
-        # Retrieve all values as an array to be inserted in DB
-
-        sql = []
-        sql.push(
-          """
-            INSERT INTO #{model.constructor.name}
-            VALUES (
-          """
-        )

         for attr, val of attrs
           if _.isArray(val) or _.isObject(val)
             val = JSON.stringify(val)

-          sql.push("\"#{val}\", ")
+          fields.push(attr)
+          values.push(val)

-        sql.push(")")
-
-        sql = sql.join(" ")
+        sql =
+          """
+            INSERT INTO #{model.constructor.name}
+            ( #{fields.join(", ")} )
+            VALUES ( #{values.join(", ")} )
+          """
       when "update"
         sql = []
         for attr, val of attrs


is not isn't isnt, so use isnt to mean it's not

--- server/controllers/deploy.coffee
+++ server/controllers/deploy.coffee
@@ -6,7 +6,7 @@
 tagRefersToServer = (tag, serverTags) ->
   deploymentTags = tag.split('-')[0]
   deploymentTags = deploymentTags.split(',')
-  return _.intersection(deploymentTags, serverTags).length is not 0
+  return _.intersection(deploymentTags, serverTags).length isnt 0

 getIpFromRequest = (req) ->
   req.headers['x-real-ip'] or req.connection.remoteAddress


Fix mode switching in discussion.

Fix mode switching in discussion.

Allow switching to mode which all maps are deleted by falling back to plain `_.last` when there's no active maps available.

--- resources/assets/coffee/_classes/beatmap-helper.coffee
+++ resources/assets/coffee/_classes/beatmap-helper.coffee
@@ -19,7 +19,7 @@
 class @BeatmapHelper
   @default: ({group, items, mode}) =>
     if items?
-      return _.findLast items, (i) -> !i.deleted_at?
+      return _.findLast(items, (i) -> !i.deleted_at?) ? _.last(items)

     return unless group?


removed .lift from runtime.coffee

removed .lift from runtime.coffee

--- source/runtime.coffee
+++ source/runtime.coffee
@@ -35,7 +35,7 @@
       update(value)
       return

-    observable = Observable.lift(value)
+    observable = Observable.(value)

     observable.observe update

@@ -49,7 +49,7 @@
       node.nodeValue = value
       return

-    observable = Observable.lift(value)
+    observable = Observable.(value)

     update = (newValue) ->
       node.nodeValue = newValue
@@ -80,7 +80,7 @@
       ; # TODO self.filters[name](content)

     __each: (items, fn) ->
-      items = Observable.lift(items)
+      items = Observable.(items)
       elements = []
       parent = lastParent()

@@ -121,7 +121,7 @@
     __with: (item, fn) ->
       element = null

-      item = Observable.lift(item)
+      item = Observable.(item)

       item.observe (newValue) ->
         replace element, newValue


pagination xs

pagination xs

--- public/views/widget_view.coffee
+++ public/views/widget_view.coffee
@@ -9,11 +9,11 @@

-

+

-

+

 """


set default yUpper/yLower values

set default yUpper/yLower values

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -12,15 +12,21 @@
 STROKE_WIDTH = 1.5
 SELECTED_STROKE_WIDTH = 2.5

+DEFAULT_HEIGHT = 100
+
 module.exports = React.createClass
   displayName: 'TextRowTool'

   statics:
     defaultValues: ({x, y}) ->
-      {x, y}
+      {x, y} # this will be the initial click position (not super useful as of yet)
+      yUpper: y - DEFAULT_HEIGHT/2
+      yLower: y + DEFAULT_HEIGHT/2

     initMove: ({x, y}) ->
       {x, y}
+      yUpper: y - DEFAULT_HEIGHT/2 # not sure if these are needed here
+      yLower: y + DEFAULT_HEIGHT/2

   getDeleteButtonPosition: ->
     theta = (DELETE_BUTTON_ANGLE) * (Math.PI / 180)


reword new-post-view

reword new-post-view

--- lib/new-post-view.coffee
+++ lib/new-post-view.coffee
@@ -11,7 +11,7 @@
     @div class: "md-writer md-writer-new-post overlay from-top", =>
       @label "Add New Post", class: "icon icon-file-add"
       @div =>
-        @label "Path", class: "message"
+        @label "Directory", class: "message"
         @subview "pathEditor", new EditorView(mini: true)
         @label "Date", class: "message"
         @subview "dateEditor", new EditorView(mini: true)
@@ -34,7 +34,7 @@
     super

   updatePath: ->
-    @message.text @getPostPath()
+    @message.text "Create Post: #{@getPostPath()}"

   display: ->
     @previouslyFocusedElement = $(':focus')
@@ -59,11 +59,10 @@

   getFullPath: ->
     localDir = atom.config.get("md-writer.siteLocalDir")
-    fullPath = path.join(localDir, @getPostPath())
-    return atom.project.resolve(fullPath)
+    return path.join(localDir, @getPostPath())

   getPostPath: ->
-    path.join @pathEditor.getText(), @getFileName()
+    return path.join(@pathEditor.getText(), @getFileName())

   getFileName: ->
     date = @dateEditor.getText()
@@ -73,11 +72,11 @@

   generateFrontMatters: ->
     """
-    ---
-    layout: post
-    title: '#{@titleEditor.getText()}'
-    date: '#{@dateEditor.getText()} #{utils.getTimeStr()}'
-    ---
+---
+layout: post
+title: '#{@titleEditor.getText()}'
+date: '#{@dateEditor.getText()} #{utils.getTimeStr()}'
+---
     """

   convertTitle: (title) ->


#1065 - Can't add a question inside a group (on staging)

--- jsapp/xlform_model_view/view.rowSelector.coffee
+++ jsapp/xlform_model_view/view.rowSelector.coffee
@@ -15,7 +15,6 @@
   class viewRowSelector.RowSelector extends $baseView
     events:
       "click .js-close-row-selector": "shrink"
-      "submit .row__questiontypes__form": "show_picker"
       "click .questiontypelist__item": "selectMenuItem"
     initialize: (opts)->
       @options = opts
@@ -28,6 +27,11 @@

     expand: ->
       @show_namer()
+      $namer_form = @$el.find('.row__questiontypes__form')
+      $namer_form.on 'submit', _.bind @show_picker, @
+      $namer_form.find('button').on 'click', (evt) ->
+        evt.preventDefault()
+        $namer_form.submit()
       @$('input').eq(0).focus()

     show_namer: () ->


Add number of instances scaled to

Add number of instances scaled to

--- SingularityUI/app/views/requestActionExpirations.coffee
+++ SingularityUI/app/views/requestActionExpirations.coffee
@@ -18,7 +18,7 @@

         if request.expiringScale and (request.expiringScale.startMillis + request.expiringScale.expiringAPIRequestObject.durationMillis) > new Date().getTime()
             expirations.push
-                action: 'Scale'
+                action: "Scale (to #{request.request.instances} instances)"
                 user: request.expiringScale.user.split('@')[0]
                 endMillis: request.expiringScale.startMillis + request.expiringScale.expiringAPIRequestObject.durationMillis
                 canRevert: true


Scroll to error after appending

Scroll to error after appending

--- lib/package-panel.coffee
+++ lib/package-panel.coffee
@@ -26,6 +26,7 @@
             @span class: 'badge pull-right', outlet: 'availableCount'

       @subview 'packageFilter', new Editor(mini: true, attributes: {id: 'package-filter'})
+      @div class: 'errors', outlet: 'errors'
       @div outlet: 'installedPackages'
       @div outlet: 'availablePackages'

@@ -44,17 +45,13 @@

     @packageEventEmitter.on 'package-installed', (error, pack) =>
       if error?
-        console.error(error.stack ? error)
-        errorView = @createErrorView(error.message, error.stderr)
-        errorView.insertAfter(@packageFilter)
+        @showPackageError(error)
       else
         @addInstalledPackage(pack)

     @packageEventEmitter.on 'package-uninstalled', (error, pack) =>
       if error?
-        console.error(error.stack ? error)
-        errorView = @createErrorView(error.message, error.stderr)
-        errorView.insertAfter(@packageFilter)
+        @showPackageError(error)
       else
         @removeInstalledPackage(pack)

@@ -94,6 +91,12 @@
           @availablePackages.append(view)

       @updateAvailableCount()
+
+  showPackageError: (error) ->
+    console.error(error.stack ? error)
+    errorView = @createErrorView(error.message, error.stderr)
+    @errors.append(errorView)
+    @parent().scrollTop(errorView.offset().top - @offset().top)

   createLoadingView: (text) ->
     $$ ->


worker --> mcts_worker

worker --> mcts_worker

--- ui/javascripts/main.coffee
+++ ui/javascripts/main.coffee
@@ -4,7 +4,7 @@
 COLOR_TO_CLASS[WHITE] = 'white'

 board = null
-worker = null
+mcts_worker = null

 scaleBoardTo = (number)->
   stretchRowsTo = (number)->
@@ -38,7 +38,7 @@

 start_mcts = (board)->
   console.log print_board(board)
-  worker.postMessage(board: board, max_playouts: 100)
+  mcts_worker.postMessage(board: board, max_playouts: 100)

 set_move_on_ui_board = (move)->
   $target_cell = $("table.go-board tr:nth-child(#{move.y + 1}) td:nth-child(#{move.x + 1})")
@@ -68,8 +68,8 @@
   board = initBoard(board_size)
   console.log board

-  worker = new Worker 'lib/javascripts/worker/tree_worker.js'
-  worker.addEventListener 'message', (message)->
+  mcts_worker = new Worker 'lib/javascripts/worker/tree_worker.js'
+  mcts_worker.addEventListener 'message', (message)->
     if message.type == 'Error'
       console.log message.data.message
     else
@@ -80,7 +80,7 @@
       else
         console.log message.data

-  worker.addEventListener('error', (message)->
+  mcts_worker.addEventListener('error', (message)->
     console.log 'Worker error message:' + message.data)

   $('.go-board td').click ->


spec: asar: fs.realpathSync

spec: asar: fs.realpathSync

--- spec/asar-spec.coffee
+++ spec/asar-spec.coffee
@@ -166,6 +166,43 @@
           assert.equal err.code, 'ENOENT'
           done()

+    describe 'fs.realpathSync', ->
+      it 'returns real path root', ->
+        parent = fs.realpathSync path.join(fixtures, 'asar')
+        p = 'a.asar'
+        r = fs.realpathSync path.join(parent, p)
+        assert.equal r, path.join(parent, p)
+
+      it 'returns real path of a normal file', ->
+        parent = fs.realpathSync path.join(fixtures, 'asar')
+        p = path.join 'a.asar', 'file1'
+        r = fs.realpathSync path.join(parent, p)
+        assert.equal r, path.join(parent, p)
+
+      it 'returns real path of a normal directory', ->
+        parent = fs.realpathSync path.join(fixtures, 'asar')
+        p = path.join 'a.asar', 'dir1'
+        r = fs.realpathSync path.join(parent, p)
+        assert.equal r, path.join(parent, p)
+
+      it 'returns real path of a linked file', ->
+        parent = fs.realpathSync path.join(fixtures, 'asar')
+        p = path.join 'a.asar', 'link2', 'link1'
+        r = fs.realpathSync path.join(parent, p)
+        assert.equal r, path.join(parent, 'a.asar', 'file1')
+
+      it 'returns real path of a linked directory', ->
+        parent = fs.realpathSync path.join(fixtures, 'asar')
+        p = path.join 'a.asar', 'link2', 'link2'
+        r = fs.realpathSync path.join(parent, p)
+        assert.equal r, path.join(parent, 'a.asar', 'dir1')
+
+      it 'throws ENOENT error when can not find file', ->
+        parent = fs.realpathSync path.join(fixtures, 'asar')
+        p = path.join 'a.asar', 'not-exist'
+        throws = -> fs.realpathSync path.join(parent, p)
+        assert.throws throws, /ENOENT/
+
     describe 'fs.readdirSync', ->
       it 'reads dirs from root', ->
         p = path.join fixtures, 'asar', 'a.asar'


Remove unused require

Remove unused require

--- spec/cson-spec.coffee
+++ spec/cson-spec.coffee
@@ -1,5 +1,4 @@
 path = require 'path'
-util = require 'util'
 fs = require 'fs'
 temp = require 'temp'
 CSON = require '../lib/cson'


Fixed bug with anchors of children on empty Item

--- src/renderer/impl/base/level1/anchors.coffee
+++ src/renderer/impl/base/level1/anchors.coffee
@@ -304,7 +304,7 @@
                     item.onParentChange onParentChange, @
                     onParentChange.call @, null
                 when 'children'
-                    @targetItem = item._children
+                    @targetItem = item.children
                     item.onChildrenChange onChildrenChange, @
                     child = @targetItem.firstChild
                     while child
@@ -338,7 +338,7 @@
                 when 'parent'
                     targetItem = @item._parent
                 when 'children'
-                    targetItem = @item._children
+                    targetItem = @item.children
                 when 'nextSibling'
                     targetItem = @item._nextSibling
                 when 'previousSibling'
@@ -350,8 +350,16 @@

             if targetItem
                 `//<development>`
-                if @item._parent and targetItem isnt @item._children and @item._parent isnt targetItem and @item._parent isnt targetItem._parent
-                    log.error "You can anchor only to a parent or sibling. Item '#{@item.toString()}.anchors.#{@source}: #{@target}'"
+                fails = @item._parent
+                fails &&= targetItem isnt @item._children
+                fails &&= @item._parent isnt targetItem
+                fails &&= @item._parent isnt targetItem._parent
+                if fails
+                    log.error """
+                        Invalid anchor point; \
+                        you can anchor only to a parent or a sibling; \
+                        item '#{@item.toString()}.anchors.#{@source}: #{@target}'
+                    """
                 `//</development>`

                 r = @getSourceValue(@item) + @getTargetValue(targetItem)
@@ -363,7 +371,10 @@
             @updateLoops++
             @item[@prop] = r
             if @updateLoops is MAX_LOOPS
-                log.error "Potential anchors loop detected. Recalculating on this anchor (#{@}) has been disabled."
+                log.error """
+                    Potential anchors loop detected; \
+                    recalculating on this anchor (#{@}) has been disabled
+                """
                 @updateLoops++
             else if @updateLoops < MAX_LOOPS
                 @updateLoops--


update spec to new format

update spec to new format

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -9,7 +9,7 @@
   [[startRow, 0], [endRow + 1, 0]]

 toggleOverwrite = (target) ->
-  dispatch(target, 'vim-mode-plus-user:toggle-overwrite')
+  dispatch(target, 'vim-mode-plus-user:move-selected-text-toggle-overwrite')

 {getVimState, TextData, dispatch} = requireFrom 'vim-mode-plus', 'spec/spec-helper'

@@ -62,10 +62,10 @@
     it "toggle command toggle overwrite config value", ->
       expect(atom.config.get(overwriteConfig)).toBe(false)

-      dispatch(editorElement, 'vim-mode-plus-user:toggle-overwrite')
+      dispatch(editorElement, 'vim-mode-plus-user:move-selected-text-toggle-overwrite')
       expect(atom.config.get(overwriteConfig)).toBe(true)

-      dispatch(editorElement, 'vim-mode-plus-user:toggle-overwrite')
+      dispatch(editorElement, 'vim-mode-plus-user:move-selected-text-toggle-overwrite')
       expect(atom.config.get(overwriteConfig)).toBe(false)

     describe "when overwrite config is toggled in false -> true -> false", ->
@@ -134,37 +134,37 @@
       it "[case-1] one line", ->
         ensureLinewiseMove = getEnsurerForLinewiseMove textData,
           selectedText: "line0\n", selectionIsReversed: false
-        ensureLinewiseMove 'V',         rows: [0, 1, 2]
-        ensureLinewiseMove {ctrl: 'j'}, rows: [1, 0, 2]
-        ensureLinewiseMove {ctrl: 'j'}, rows: [1, 2, 0]
-        ensureLinewiseMove {ctrl: 'k'}, rows: [1, 0, 2]
-        ensureLinewiseMove {ctrl: 'k'}, rows: [0, 1, 2]
+        ensureLinewiseMove 'V', rows: [0, 1, 2]
+        ensureLinewiseMove 'ctrl-j', rows: [1, 0, 2]
+        ensureLinewiseMove 'ctrl-j', rows: [1, 2, 0]
+        ensureLinewiseMove 'ctrl-k', rows: [1, 0, 2]
+        ensureLinewiseMove 'ctrl-k', rows: [0, 1, 2]
       it "[case-2] two line", ->
         ensureLinewiseMove = getEnsurerForLinewiseMove textData,
           selectedText: "line0\nline1\n", selectionIsReversed: false
-        ensureLinewiseMove 'Vj',        rows: [0, 1, 2]
-        ensureLinewiseMove {ctrl: 'j'}, rows: [2, 0, 1]
-        ensureLinewiseMove {ctrl: 'k'}, rows: [0, 1, 2]
+        ensureLinewiseMove 'V j', rows: [0, 1, 2]
+        ensureLinewiseMove 'ctrl-j', rows: [2, 0, 1]
+        ensureLinewiseMove 'ctrl-k', rows: [0, 1, 2]
       it "[case-3] two line, selection is reversed: keep reversed state", ->
         ensureLinewiseMove = getEnsurerForLinewiseMove textData,
           selectedText: "line0\nline1\n", selectionIsReversed: true
         set cursor: [1, 0]
-        ensureLinewiseMove 'Vk',        rows: [0, 1, 2]
-        ensureLinewiseMove {ctrl: 'j'}, rows: [2, 0, 1]
-        ensureLinewiseMove {ctrl: 'k'}, rows: [0, 1, 2]
+        ensureLinewiseMove 'V k', rows: [0, 1, 2]
+        ensureLinewiseMove 'ctrl-j', rows: [2, 0, 1]
+        ensureLinewiseMove 'ctrl-k', rows: [0, 1, 2]
       it "extends final row when move down", ->
         ensureLinewiseMove = getEnsurerForLinewiseMove textData,
           selectedText: "line2\n", selectionIsReversed: false
         set cursor: [2, 0]
         ensureLinewiseMove 'V', rows: [0, 1, 2]
-        ensureLinewiseMove {ctrl: 'j'}, text: [
+        ensureLinewiseMove 'ctrl-j', text: [
           "line0"
           "line1"
           ""
           "line2"
           ""
         ].join("\n")
-        ensureLinewiseMove {ctrl: 'j'}, text: [
+        ensureLinewiseMove 'ctrl-j', text: [
           "line0"
           "line1"
           ""
@@ -175,9 +175,9 @@
       it "support count", ->
         ensureLinewiseMove = getEnsurerForLinewiseMove textData,
           selectedText: "line0\n", selectionIsReversed: false
-        ensureLinewiseMove 'V',         rows: [0, 1, 2]
-        ensureLinewiseMove ["2", {ctrl: 'j'}], rows: [1, 2, 0]
-        ensureLinewiseMove ["2", {ctrl: 'k'}], rows: [0, 1, 2]
+        ensureLinewiseMove 'V', rows: [0, 1, 2]
+        ensureLinewiseMove '2 ctrl-j', rows: [1, 2, 0]
+        ensureLinewiseMove '2 ctrl-k', rows: [0, 1, 2]

     describe "characterwise", ->
       beforeEach ->
@@ -191,14 +191,14 @@
           """
           cursor: [[0, 1], [2, 1]]
       it "move characterwise, support multiple selection", ->
-        ensure 'vl',
+        ensure 'v l',
           mode: ['visual', 'characterwise']
           selectedTextOrdered: ['oo', 'YY']
           selectedBufferRange: [
             [[0, 1], [0, 3]]
             [[2, 1], [2, 3]]
           ]
-        ensure {ctrl: 'j'},
+        ensure 'ctrl-j',
           selectedTextOrdered: ['oo', 'YY']
           selectedBufferRange: [
             [[1, 1], [1, 3]]
@@ -211,7 +211,7 @@
           ZYY

           """
-        ensure {ctrl: 'j'},
+        ensure 'ctrl-j',
           selectedTextOrdered: ['oo', 'YY']
           text: """
           oxx
@@ -238,24 +238,23 @@

       it "indent/outdent", ->
         selectedText = "line0\nline1\n"
-        selectionIsReversed = false
-        ensure "Vj", {selectedText, selectionIsReversed}
-        ensure {ctrl: 'l'}, {
+        ensure "V j", {selectedText, selectionIsReversed: false}
+        ensure 'ctrl-l',
           selectedText: "  line0\n  line1\n"
-          selectionIsReversed
-        }
-        ensure ["2", {ctrl: 'l'}], {
+          selectionIsReversed: false
+
+        ensure '2 ctrl-l',
           selectedText: "      line0\n      line1\n"
-          selectionIsReversed
-        }
-        ensure ["2", {ctrl: 'h'}], {
+          selectionIsReversed: false
+
+        ensure '2 ctrl-h',
           selectedText: "  line0\n  line1\n"
-          selectionIsReversed
-        }
-        ensure {ctrl: 'h'}, {
+          selectionIsReversed: false
+
+        ensure 'ctrl-h',
           selectedText: "line0\nline1\n"
-          selectionIsReversed
-        }
+          selectionIsReversed: false
+
       it "[case-2] indent/outdent", ->
         text = """
           line0
@@ -265,7 +264,7 @@

           """
         set {text}
-        ensure "V3j", selectedText: text
+        ensure "V 3 j", selectedText: text
         newText = """
           line0
           line1
@@ -273,7 +272,7 @@
           line3

           """
-        ensure ['10', {ctrl: 'h'}], {text: newText, selectedText: newText}
+        ensure '1 0 ctrl-h', text: newText, selectedText: newText

   describe "duplicate up/down", ->
     beforeEach ->
@@ -289,12 +288,12 @@
     describe "linewise", ->
       it "duplicate single line", ->
         ensure 'V', selectedText: 'line0\n'
-        ensure {cmd: 'J'},
+        ensure 'cmd-J',
           selectedBufferRange: rowRange(1, 1)
           text: 'line0\nline0\nline1\nline2\n'
       it "duplicate 2 selected line", ->
-        ensure 'Vj', selectedText: 'line0\nline1\n'
-        ensure {cmd: 'J'},
+        ensure 'V j', selectedText: 'line0\nline1\n'
+        ensure 'cmd-J',
           selectedBufferRange: rowRange(2, 3)
           text: """
           line0
@@ -304,7 +303,7 @@
           line2

           """
-        ensure {cmd: 'K'},
+        ensure 'cmd-K',
           selectedBufferRange: rowRange(2, 3)
           text: """
           line0
@@ -318,7 +317,7 @@
           """
       it "suport count", ->
         ensure 'V', selectedText: 'line0\n'
-        ensure ['2', {cmd: 'J'}],
+        ensure '2 cmd-J',
           selectedBufferRange: rowRange(1, 2)
           text: """
           line0
@@ -328,7 +327,7 @@
           line2

           """
-        ensure ['2', {cmd: 'K'}],
+        ensure '2 cmd-K',
           selectedBufferRange: rowRange(1, 4)
           text: """
           line0
@@ -348,14 +347,14 @@
           toggleOverwrite(editorElement)
         it "overrite lines down", ->
           ensure 'V', selectedBufferRange: rowRange(0, 0)
-          ensure {cmd: 'J'},
+          ensure 'cmd-J',
             text: """
             line0
             line0
             line2

             """
-          ensure {cmd: 'J'},
+          ensure 'cmd-J',
             text: """
             line0
             line0
@@ -363,7 +362,7 @@

             """
             selectedBufferRange: rowRange(2, 2)
-          ensure ["2", {cmd: 'J'}],
+          ensure '2 cmd-J',
             text: """
             line0
             line0
@@ -375,8 +374,8 @@
             selectedBufferRange: rowRange(3, 4)

         it "overrite lines up", ->
-          ensure 'jjV', selectedBufferRange: rowRange(2, 2)
-          ensure ["2", {cmd: 'K'}],
+          ensure 'j j V', selectedBufferRange: rowRange(2, 2)
+          ensure '2 cmd-K',
             selectedBufferRange: rowRange(0, 1)
             text: """
             line2
@@ -396,8 +395,8 @@
             """
             cursor: [3, 0]

-          ensure 'Vj', selectedBufferRange: rowRange(3, 4)
-          ensure ["10", {cmd: 'K'}],
+          ensure 'V j', selectedBufferRange: rowRange(3, 4)
+          ensure '1 0 cmd-K',
             selectedBufferRange: rowRange(1, 2)
             text: """
             0
@@ -423,8 +422,7 @@
           cursor: [0, 0]

       it "duplicate linewise right", ->
-        keystroke 'Vjj'
-        ensure {cmd: 'L'},
+        ensure 'V j j cmd-L',
           selectedBufferRange: rowRange(0, 2)
           text: """
           0 |_0 |_
@@ -433,8 +431,7 @@

           """
       it "count support", ->
-        keystroke 'Vjj'
-        ensure ['2', {cmd: 'L'}],
+        ensure 'V j j 2 cmd-L',
           selectedBufferRange: rowRange(0, 2)
           text: """
           0 |_0 |_0 |_
@@ -447,8 +444,7 @@
           toggleOverwrite(editorElement)

         it "duplicate linewise right(no behavior diff)", ->
-          keystroke 'Vjj'
-          ensure {cmd: 'L'},
+          ensure 'V j j cmd-L',
             selectedBufferRange: rowRange(0, 2)
             text: """
             0 |_0 |_
@@ -457,14 +453,12 @@

             """
         it "duplicate linewise left do nothing", ->
-          keystroke 'Vjj'
-          ensure {cmd: 'H'},
+          ensure 'V j j cmd-H',
             selectedBufferRange: rowRange(0, 2)
             text: originalText

       it "duplicate linewise left(identical behavior to right)", ->
-        keystroke 'Vjj'
-        ensure {cmd: 'H'},
+        ensure 'V j j cmd-H',
           selectedBufferRange: rowRange(0, 2)
           text: """
           0 |_0 |_


Fix exception when adding and removing tags

Only happens if adding and removing tags within the same session.

Resolves: #229

--- lib/db.coffee
+++ lib/db.coffee
@@ -445,9 +445,17 @@
   if (update['$unset']?)
     optimizeProjection(update['$unset'])

+  if update['$addToSet'] and update['$pull']
+    # Mongo doesn't allow $addToSet and $pull at the same time
+    update2 = {'$pull': update['$pull']}
+    delete update['$pull']
+
   devicesCollection.update({'_id' : deviceId}, update, {upsert: isNew}, (err, result) ->
     if not err and result.result.n != 1
       return callback(new Error("Device #{deviceId} not found in database"))
+
+    if update2
+      return devicesCollection.update({'_id': deviceId}, update2, callback)

     return callback(err)
   )


:lipstick:

:lipstick:

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -306,7 +306,7 @@

     describe "when text is removed from the mini-editor", ->
       it "reloads the match list based on the mini-editor's text", ->
-        editor.getBuffer().insert([10,0] ,"t")
+        editor.getBuffer().insert([10,0], "t")
         editor.setCursorBufferPosition([10,0])
         autocomplete.attach()


Remove console.log statement in BaseChartView

--- app/assets/javascripts/lib/views/base_chart_view.coffee
+++ app/assets/javascripts/lib/views/base_chart_view.coffee
@@ -29,7 +29,6 @@
   # Triggered by the willReplace event; removes action buttons and shows a
   # loading block while a new chart is loaded.
   prepare_replace: =>
-    console.log ('prepare_replace')
     loading_el = $('<div class="loading" />')
     canvas = @$el.find('.chart_canvas')


Adds disabled to view button in new files

This makes it so that when clicking on it without files
selected (in a SR) will not cause an error.

fixes CNVS-18565

Test Plan:
   - Go to New Files
   - Using a screenreader the view button should appear
     as disabled when nothing is selected.

Change-Id: Ic663ebda52a91a76fec90b4d57eca5b62b3bc7e1
Reviewed-on: https://gerrit.instructure.com/48883
Tested-by: Jenkins
Product-Review: Aaron Cannon <25919c673621c006b735dcd5a28df63325948e2b@instructure.com>
Reviewed-by: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>

--- app/coffeescripts/react_files/components/Toolbar.coffee
+++ app/coffeescripts/react_files/components/Toolbar.coffee
@@ -114,6 +114,8 @@
               role: 'button'
               'aria-label': I18n.t('view', 'View')
               'data-tooltip': ''
+              'aria-disabled': !showingButtons
+              disabled: !showingButtons
               tabIndex: -1 unless showingButtons # This is to make it okay for keyboard-nav when hidden.
             },
             i className: 'icon-eye'


Add pattern-matching for "goto" labels

Add pattern-matching for "goto" labels

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -78,6 +78,7 @@
 		patterns: [
 			{ include: "#definition" }
 			{ include: "#comment"    }
+			{ include: "#label"      }
 			{ include: "#strings"    }
 			{ include: "#float"      }
 			{ include: "#int"        }
@@ -172,6 +173,17 @@
 		patterns: [{
 			name: "storage.type.name.apl"
 			match: "[A-Za-z∆⍙_]+[A-Za-z0-9∆⍙_¯]*"
+		}]
+
+
+	# Labels
+	label:
+		patterns: [{
+			name: "meta.label.apl"
+			match: "^\\s*([A-Za-z_∆⍙]+[\\w∆⍙¯]*)(:)"
+			captures:
+				1: {name: "entity.label.name.apl"}
+				2: {name: "punctuation.definition.label.end.apl"}
 		}]


Get CommandPalette specs to pass without getActiveEditor

--- spec/command-palette-spec.coffee
+++ spec/command-palette-spec.coffee
@@ -19,8 +19,8 @@

   describe "when command-palette:toggle is triggered on the root view", ->
     it "shows a list of all valid command descriptions, names, and keybindings for the previously focused element", ->
-      keyBindings = _.losslessInvert(keymap.bindingsForElement(rootView.getActiveEditor()))
-      for eventName, description of rootView.getActiveEditor().events()
+      keyBindings = _.losslessInvert(keymap.bindingsForElement(rootView.getActiveView()))
+      for eventName, description of rootView.getActiveView().events()
         eventLi = palette.list.children("[data-event-name='#{eventName}']")
         if description
           expect(eventLi).toExist()
@@ -32,7 +32,7 @@
           expect(eventLi).not.toExist()

     it "displays all commands registerd on the window", ->
-      editorEvents = rootView.getActiveEditor().events()
+      editorEvents = rootView.getActiveView().events()
       windowEvents = $(window).events()
       expect(_.isEmpty(windowEvents)).toBeFalsy()
       for eventName, description of windowEvents
@@ -60,19 +60,19 @@
       expect(palette.hasParent()).toBeTruthy()
       palette.trigger 'command-palette:toggle'
       expect(palette.hasParent()).toBeFalsy()
-      expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+      expect(rootView.getActiveView().isFocused).toBeTruthy()

   describe "when the command palette is cancelled", ->
     it "focuses the root view and detaches the command palette", ->
       expect(palette.hasParent()).toBeTruthy()
       palette.cancel()
       expect(palette.hasParent()).toBeFalsy()
-      expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+      expect(rootView.getActiveView().isFocused).toBeTruthy()

   describe "when an command selection is confirmed", ->
     it "detaches the palette, then focuses the previously focused element and emits the selected command on it", ->
       eventHandler = jasmine.createSpy 'eventHandler'
-      activeEditor = rootView.getActiveEditor()
+      activeEditor = rootView.getActiveView()
       {eventName} = palette.array[5]
       activeEditor.preempt eventName, eventHandler


Allow up-tooltip to tooltipify an existing title attribute

--- lib/assets/javascripts/up/tooltip.js.coffee
+++ lib/assets/javascripts/up/tooltip.js.coffee
@@ -51,7 +51,7 @@
   ###
   open = (linkOrSelector, options = {}) ->
     $link = $(linkOrSelector)
-    html = u.option(options.html, $link.attr('up-tooltip'))
+    html = u.option(options.html, $link.attr('up-tooltip'), $link.attr('title'))
     origin = u.option(options.origin, $link.attr('up-origin'), 'top')
     animation = u.option(options.animation, $link.attr('up-animation'), 'fade-in')
     close()
@@ -78,6 +78,10 @@
   Displays a tooltip when hovering the mouse over this element:

       Decks
+
+  You can also make an existing `title` attribute appear as a tooltip:
+
+      Decks

   @method [up-tooltip]
   @ujs


remove comment

remove comment

--- src/canvas.coffee
+++ src/canvas.coffee
@@ -232,7 +232,6 @@
       pinchDistance = getPinchDistance touches
       delta = pinchDistance / @startPinchDistance
       @scale = @startScale * delta
-      # TODO: Change `@translatePos` during zoom to keep it centered on the same spot
       @draw()

     canvas.addEventListener "touchmove", (e) =>


first cut at adding cursors

first cut at adding cursors

--- src/editor.coffee
+++ src/editor.coffee
@@ -32,18 +32,17 @@
     this.reset(true)
     this.enable() if @options.enabled

-  makeCursor: ->
-    span = @doc.root.ownerDocument.createElement('span')
-    span.classList.add('cursor')
-    span.classList.add(Tandem.Constants.SPECIAL_CLASSES.EXTERNAL)
-
   addCursor: (index) ->
-    cursor = this.makeCursor()
+    cursor = @doc.root.ownerDocument.createElement('span')
+    cursor.classList.add('cursor')
+    cursor.classList.add(Tandem.Constants.SPECIAL_CLASSES.EXTERNAL)
     cursor.id = _.uniqueId(TandemEditor.CURSOR_PREFIX)
+    this.moveCursor(cursor)
     return cursor.id

-  moveCursor: (id) ->
-    cursor = @doc.root.ownerDocument.getElementById(id)
+  moveCursor: (cursor) ->
+    cursor = @doc.root.ownerDocument.getElementById(cursor) if _.isString(cursor)
+    Tandem.Utils.insertExternal(new Tandem.Position(this, index), cursor)

   destroy: ->
     this.disable()


fleshed out hasher

fleshed out hasher

--- src/hasher.coffee
+++ src/hasher.coffee
@@ -0,0 +1,21 @@
+#
+#     _|      _|  _|              _|
+#     _|_|  _|_|        _|_|_|  _|_|_|_|
+#     _|  _|  _|  _|  _|_|        _|
+#     _|      _|  _|      _|_|    _|
+#     _|      _|  _|  _|_|_|        _|_|
+#
+#             MIST BUILD SYSTEM
+# Copyright (c) 2015 On Demand Solutions, inc.
+
+xxhash = require 'xxhash'
+
+xxHashSeed = (parseInt 'AEROMIST', 30) >> 2
+
+module.exports.hash = (str, first = yes)->
+  cmdHash = xxhash.hash (new Buffer str), xxHashSeed
+  v = cmdHash.toString 36
+  if first
+    m = str.match /^\s*([a-z0-9\-\_]+)/i
+    if m and m[1] then v = "#{m[1]}_#{v}"
+  return v


moveToPrompt initially looks less ugly on refresh

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -245,6 +245,7 @@
     @grammar.activate()
     @insertQuery(@query)
     @providerPanel.show()
+    @moveToPrompt()

     @disposables.add(
       @registerCommands()
@@ -263,8 +264,6 @@
           column = 0
         @editor.setCursorBufferPosition([row, column])
         @preview()
-      else
-        @moveToPrompt()

       if @activate
         @preview() if @query and @autoPreview


typedefs for AVDL

typedefs for AVDL

--- protocol/bin/go.iced
+++ protocol/bin/go.iced
@@ -47,7 +47,25 @@
     else "ERROR"
     { type , optional }

+
+  #
+  # An example of an AVDL "typdef":
+  #
+  # record {
+  #    string __typedef__;
+  # }
+  #
+  emit_typedef : (json) ->
+    if json.fields.length is 1 and json.fields[0].name is "__typedef__"
+      @output "type #{json.name} #{@emit_field_type(json.fields[0].type).type}"
+      true
+    else
+      false
+
   emit_record : (json, {wrapper} ) ->
+    # Typdef's are disguised as records in our hacked-up AVDL
+    return if @emit_typedef json
+
     @output "type #{@go_export_case(json.name)} struct {"
     @tab()
     @emit_wrapper_record_first() if wrapper
@@ -63,7 +81,7 @@
     @output "}"

   emit_fixed : (t) ->
-    @output "type #{t.name  } [#{t.size}]byte"
+    @output "type #{t.name} [#{t.size}]byte"

   emit_types : (json) ->
     for t in json


prevent back to the top after close modal

--- vendor/assets/javascripts/lazy/admin.coffee
+++ vendor/assets/javascripts/lazy/admin.coffee
@@ -205,10 +205,10 @@

   close_modal: ->
     modal = @_modal_dialog()
-    if $('.items-list, .no-items').length > 0
-      $.ajax
-        url: location.href
-        dataType: 'script'
+    # if $('.items-list, .no-items').length > 0
+    #   $.ajax
+    #     url: location.href
+    #     dataType: 'script'
     modal.find('[data-tinymce]').each (idx, el) ->
       tinyMCELoader.remove(el.id) if el.id?
     modal.removeClass('open')


Extract method _set_throws_for_current_signature

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -139,7 +139,7 @@
   throws: (error) ->
     _check_throws_usage(@, error)
     _check_for_duplicate_signatures(@)
-    _current_signature(@).throws = error
+    _set_throws_for_current_signature(@, error)
     _set_state(@, "throws")
     @

@@ -213,6 +213,9 @@
 _set_return_for_current_signature = (mock, value) ->
   _current_signature(mock).returns = value

+_set_throws_for_current_signature = (mock, error) ->
+  _current_signature(mock).throws = error
+
 _throw_expects_usage = ->
   throw "you need to supply a method name to .expects(), e.g. my_mock.expects('my_method')"


cleaning up the tab controller.

cleaning up the tab controller.

--- src/ui.tab.coffee
+++ src/ui.tab.coffee
@@ -57,8 +57,6 @@
   render: =>
     @$el.html '

'
     bar = new BarView collection: @collection
-    # TODO: allow this to be set.
-

     @$el.append bar.render()

@@ -74,8 +72,7 @@

   set: (controllerViews...) =>
     @collection.reset [], silent: true
-    # if controllersViews is empty then set the collection as empty and trigger
-    # the reset event which causes the `TabBarView` to redraw.
+
     if controllerViews.length == 0
       @collection.trigger 'reset'
       return
@@ -88,8 +85,6 @@
     @selectedIndex 0

   selectedIndex: (i) =>
-    # try to do a bit of a getter/ setter thing here. If you don't pass an
-    # index then simply return the currently selected Index.
     if not i?
       return i = @_selectedIndex


So is server.close

So is server.close

--- spec/clean-spec.coffee
+++ spec/clean-spec.coffee
@@ -41,7 +41,9 @@
     waitsFor -> live

   afterEach ->
-    server.close()
+    done = false
+    server.close -> done = true
+    waitsFor -> done

   it 'uninstalls any packages not referenced in the package.json', ->
     removedPath = path.join(moduleDirectory, 'node_modules', 'will-be-removed')


Persist headline on page

Persist headline on page

--- server/models/page.coffee
+++ server/models/page.coffee
@@ -11,6 +11,7 @@
   parent_type: String
   sections: [SectionSchema]
   is_draft: type: Boolean, default: false
+  headline: mongoose.Schema.Types.Mixed
 )

 _.extend(pageSchema.statics, sectionNestingModel)


fix bug: returns array of interval in rangeSearch in any situation

--- src/lib/interval-tree.coffee
+++ src/lib/interval-tree.coffee
@@ -230,7 +230,7 @@

                 resultsById[point.id] = @intervalsById[point.id]

-            return (interval for id, interval of resultsById)
+        return (interval for id, interval of resultsById)


Added option to return map from filterByIds().

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -284,13 +284,21 @@

   # @param {Meteor.Collection|Cursor|Array} docs
   # @param {Array.<Strings>} ids
-  # @returns {Array.<Object>} The given documents which match the given IDs. This is typically
-  # more efficient than calling find({_id: {$in: ids}}) for a large number of ids.
-  filterByIds: (docs, ids) ->
+  # @param {Object} [options]
+  # @param {Boolean} [options.returnMap=false] - Whether to return a map of filtered IDs to
+  #     documents instead of an array.
+  # @returns {Array.<Object>|Object.<String, Object>} The given documents which match the given IDs.
+  # This is typically more efficient than calling find({_id: {$in: ids}}) for a large
+  # number of ids.
+  filterByIds: (docs, ids, options) ->
     docs = @getItems(docs)
     idMap = {}
     _.each ids, (id) -> idMap[id] = true
-    _.filter docs, (doc) -> idMap[doc._id]?
+    docs = _.filter docs, (doc) ->
+      exists = idMap[doc._id]?
+      if options?.returnMap then idMap[doc._id] = doc
+      exists
+    if options?.returnMap then idMap else docs

 ####################################################################################################
 # VALIDATION


Fix event methods' chainability

Fix event methods' chainability

--- src/marbles/events.js.coffee
+++ src/marbles/events.js.coffee
@@ -35,12 +35,14 @@
     events = events?.split(event_splitter) unless _.isArray(events)

     if !events
-      return @_events = {}
+      return @ # chainable
+
+    @_events ?= {}

     for name in events
       if !callback && !context
         delete @_events[name]
-        return
+        return @ # chainable

       return unless bindings = @_events[name]


Updated router to make a fresh "sendRequest" promise for each route

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -101,35 +101,39 @@
 				# on failure
 				handleServerError ctx, reason
 		else
-			promise = sendRequest ctx, route, options
-			.then (response) ->
-				routeObj = {}
-				routeObj.name = route.name
-				routeObj.request =
-					path: path
-					headers: ctx.request.header
-					querystring: ctx.request.querystring
-					method: ctx.request.method
-
-				if response.headers?['content-type']?.indexOf('application/json+openhim') > -1
-					# handle mediator reponse
-					responseObj = JSON.parse response.body
-					routeObj.orchestrations = responseObj.orchestrations
-					routeObj.properties = responseObj.properties
-					routeObj.response = responseObj.response
-				else
-					routeObj.response = response
-
-				ctx.routes = [] if not ctx.routes
-				ctx.routes.push routeObj
-			.fail (reason) ->
-				# on failure
-				handleServerError ctx, reason
+			promise = buildNonPrimarySendRequestPromise ctx, route, options, path

 		promises.push promise

 	(Q.all promises).then ->
 		next()
+
+# function to build fresh promise for transactions routes
+buildNonPrimarySendRequestPromise = (ctx, route, options, path) ->
+	sendRequest ctx, route, options
+	.then (response) ->
+		routeObj = {}
+		routeObj.name = route.name
+		routeObj.request =
+			path: path
+			headers: ctx.request.header
+			querystring: ctx.request.querystring
+			method: ctx.request.method
+
+		if response.headers?['content-type']?.indexOf('application/json+openhim') > -1
+			# handle mediator reponse
+			responseObj = JSON.parse response.body
+			routeObj.orchestrations = responseObj.orchestrations
+			routeObj.properties = responseObj.properties
+			routeObj.response = responseObj.response
+		else
+			routeObj.response = response
+
+		ctx.routes = [] if not ctx.routes
+		ctx.routes.push routeObj
+	.fail (reason) ->
+		# on failure
+		handleServerError ctx, reason

 sendRequest = (ctx, route, options) ->
 	if route.type is 'tcp'


Quick API fix.

Quick API fix.

by @sbellity

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -79,8 +79,8 @@

         onSuccess = (res)->
           setCookies(res.headers)
-          callback(res.response)
-          promise.resolve(res.response)
+          callback(res?.response || res)
+          promise.resolve(res?.response || res)

         onError = (err)->
           errback(err)


binary data is now returned without getting munged.  fixes jembi/openhim-core-js#171

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -98,12 +98,14 @@
 				when 'location' then responseDst.redirect(value)
 				else responseDst.header[key] = value

-		responseDst.body = ''
+		responseDst.body = new Buffer(0)
+		bufs = []
 		routeRes.on "data", (chunk) ->
-			responseDst.body += chunk
+                        bufs.push chunk

 		routeRes.on "end", ->
-			responseDst.timestamp = new Date()
+                        responseDst.timestamp = new Date()
+                        responseDst.body = Buffer.concat(bufs)
 			callback()

 	routeReq.on "error", (err) ->


Lookup correct compiler when importing v1 templates

bug: overleaf/sharelatex#981
Signed-off-by: Simon Detheridge <a0f1490a20d0211c997b44bc357e1972deab8ae3@sd.ai>

--- services/web/app/coffee/Features/Templates/TemplatesController.coffee
+++ services/web/app/coffee/Features/Templates/TemplatesController.coffee
@@ -12,6 +12,12 @@
 logger = require('logger-sharelatex')
 async = require("async")

+ENGINE_TO_COMPILER_MAP = {
+	latex_dvipdf: "latex"
+	pdflatex:     "pdflatex"
+	xelatex:      "xelatex"
+	lualatex:     "lualatex"
+}

 module.exports = TemplatesController =

@@ -25,7 +31,7 @@
 		data.templateVersionId = templateVersionId
 		data.templateId = templateId
 		data.name = req.query.templateName
-		data.compiler = req.query.latexEngine
+		data.compiler = ENGINE_TO_COMPILER_MAP[req.query.latexEngine]
 		data.mainFile = req.query.mainFile
 		res.render path.resolve(__dirname, "../../../views/project/editor/new_from_template"), data


Widget base class

Widget base class

--- js/u.widget.coffee
+++ js/u.widget.coffee
@@ -1,5 +1,19 @@

 define ["cs!u.dom","cs!u"], () ->
   u._widgets = { }
+
+  ###
+  Widget base class
+  ###
+  class u.Widget
+    constructor: () ->
+
+  ###
+  Widget factory method
+
+     @param string name
+     @param closure factory
+  ###
   u.widget = (name,factory) ->
     @_widgets[ name ] = factory
+


Google drive added

Google drive added


fixed import error

fixed import error

--- app/assets/javascripts/admin/models/team/paginated_team_collection.coffee
+++ app/assets/javascripts/admin/models/team/paginated_team_collection.coffee
@@ -6,3 +6,5 @@

   url : "/api/teams"
   model: TeamModel
+
+module.exports = PaginatedTeamCollection


explicitely show that `model.save()` has a return value #490

--- app/assets/javascripts/oxalis/view/action-bar/dataset_actions_view.coffee
+++ app/assets/javascripts/oxalis/view/action-bar/dataset_actions_view.coffee
@@ -100,7 +100,7 @@
     if evt
       evt.preventDefault()

-    @model.save()
+    return @model.save()

   mergeTracing : ->


Protect from unintended delete

Protect from unintended delete

--- src/cmd/remove.coffee
+++ src/cmd/remove.coffee
@@ -8,19 +8,29 @@

 @title = 'Remove Node.js'

-@args = "[all] #{install.args}"
+@args = "[all] #{install.args} [.]"

 @description = """
   Remove specified Node.js version or all installed versions.

-  NB: No additional confirmation(s) will be requested!
+  Without trailing dot command only shows candidate(s) to uninstall.
+
+  With trailing dot removing will proceed without additional confirmations!
   """

+danger = false
+
 @cmd = (args)->
+  danger = '.' in args
   if /^a/i.test args[0]
     all args.slice 1
   else
     one args
+  return if danger
+  echo """
+
+    To actually uninstall say `#{PACKAGE.mingzi} #{argv.join ' '} .`
+    """

 all = (args)->
   n = 0
@@ -28,7 +38,7 @@
   for r in locals.list() when semver.match r.id, filter
     remove r
     n++
-  echo "Node.js versions found & uninstalled: #{n}"
+  echo "\nNode.js versions found & uninstalled: #{n}" if danger

 one = (args)->
   filter = install.parse(args).local().z
@@ -38,6 +48,9 @@
   throw Error 'Specified Node.js version not installed!'

 remove = (x)->
-  echo "removing #{x.path}"
+  unless danger
+    echo "Would remove #{x.path}."
+    return
+  echo "Removing #{x.path}"
   fs.DeleteFolder fs.BuildPath install2, x.path
   junction.exec 'none' if x.active


Don't use log rotation as it has problem with process.exit.

See https://github.com/trentm/node-bunyan/pull/97

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -14,7 +14,7 @@
 logger = new Logger
   streams: [
     { level: 'error', stream: process.stderr }
-    { level: argv.logLevel, type: 'rotating-file', period: '1d', count: 90, path: "#{argv.logDir}/sphere-stock-sync-#{argv.projectKey}.log" }
+    { level: argv.logLevel, path: "#{argv.logDir}/sphere-stock-sync-#{argv.projectKey}.log" }
   ]

 options =


Use correct log file name

Use correct log file name

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -37,7 +37,7 @@
   name: "#{package_json.name}-#{package_json.version}"
   streams: [
     { level: 'error', stream: process.stderr }
-    { level: argv.logLevel, path: "#{argv.logDir}/sphere-order-xml-import_#{argv.projectKey}.log" }
+    { level: argv.logLevel, path: "#{argv.logDir}/sphere-order-xml-import.log" }
   ]
 logOptions.silent = argv.logSilent if argv.logSilent
 logger = new ExtendedLogger


cleaning up method used for getting counts

--- client/controllers/suggestedIncidents.coffee
+++ client/controllers/suggestedIncidents.coffee
@@ -54,7 +54,6 @@
   @incidentCollection = new Meteor.Collection(null)
   @loading = new ReactiveVar(true)
   @content = new ReactiveVar("")
-  @annotationCount = new ReactiveVar(null)
   Meteor.call("getArticleEnhancements", @data.article.url, (error, result) =>
     if error
       Modal.hide(@)
@@ -175,9 +174,10 @@
   loading: ->
     Template.instance().loading.get()
   annotatedCount: ->
-    if Template.instance().annotationCount.get()
-      count = Incidents.find({userEventId: Template.instance().data.userEventId}).fetch().length
-      count + " of " + Template.instance().annotationCount.get() + " incidents reviewed"
+    total = Template.instance().incidentCollection.find().fetch().length
+    if total
+      count = Template.instance().incidentCollection.find({accepted: true}).fetch().length
+      count + " of " + total + " incidents reviewed"

   annotatedContent: ->
     content = Template.instance().content.get()
@@ -195,7 +195,6 @@
         >#{Handlebars._escape(content.slice(start, end))}</span>"""
       )
       lastEnd = end
-    Template.instance().annotationCount.set(count)
     html += Handlebars._escape("#{content.slice(lastEnd)}")
     new Spacebars.SafeString(html)


rethink: option to not connect to all hosts; off by default; it causes trouble

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -103,6 +103,7 @@
             debug    : true
             driver   : 'native'    # dash or native
             pool     : if process.env.DEVEL then 1 else 100  # default number of connection to use in connection pool with native driver
+            all_hosts : false      # if true, finds all hosts based on querying the server then connects to them
             warning  : 15          # display warning and stop using connection if run takes this many seconds or more
             error    : 120         # kill any query that takes this long (and corresponding connection)
             concurrent_warn : 500  # if number of concurrent outstanding db queries exceeds this number, put a concurrent_warn message in the log.
@@ -115,6 +116,7 @@
         @_warning_thresh   = opts.warning
         @_error_thresh     = opts.error
         @_concurrent_warn  = opts.concurrent_warn
+        @_all_hosts        = opts.all_hosts

         if typeof(opts.hosts) == 'string'
             opts.hosts = [opts.hosts]
@@ -187,6 +189,9 @@
                     @_conn = {}  # initialize if not defined
                 @_conn[misc.uuid()] = conn  # save connection
                 if not first_conn
+                    cb(); return
+
+                if not @_all_hosts
                     cb(); return

                 # first connection, so we query for server_status to know all connected servers (in case not given in hosts option)


allow port override from env

allow port override from env

--- tastetyper.coffee
+++ tastetyper.coffee
@@ -27,7 +27,10 @@
   else
     server = module.createServer app

-  server.listen serverConfig.port, serverConfig.hostname, () ->
+  port = process.env.PORT
+  port ?= serverConfig.port
+
+  server.listen port, serverConfig.hostname, () ->
     address = server.address().address
     port = server.address().port
     debug "Server listening on #{serverConfig.protocol}://#{address}:#{port}"


Actually enable rich text

Actually enable rich text

--- services/web/app/coffee/infrastructure/Features.coffee
+++ services/web/app/coffee/infrastructure/Features.coffee
@@ -23,7 +23,7 @@
 			when 'affiliations'
 				return Settings?.apis?.v1?.url?
 			when 'rich-text'
-				isEnabled = false # Switch to false to disable
+				isEnabled = true # Switch to false to disable
 				Settings.overleaf? and isEnabled
 			else
 				throw new Error("unknown feature: #{feature}")


field != feild

field != feild

--- services/web/public/coffee/directives/asyncForm.coffee
+++ services/web/public/coffee/directives/asyncForm.coffee
@@ -107,7 +107,7 @@
 			opts = _.defaults(window.passwordStrengthOptions || {}, defaultPasswordOpts)
 			if opts.length.min == 1
 				opts.acceptRate = 0 #this allows basically anything to be a valid password
-			passField = new PassField.Field("passwordFeild", opts);
+			passField = new PassField.Field("passwordField", opts);

 			[asyncFormCtrl, ngModelCtrl] = ctrl

@@ -115,7 +115,7 @@
 				isValid = passField.validatePass()
 				if !isValid
 					scope.complexPasswordErrorMessage = passField.getPassValidationMessage()
-				else
+				else if asyncFormCtrl.getEmail()?
 					email = asyncFormCtrl.getEmail()
 					startOfEmail = email?.split("@")?[0]
 					if modelValue.indexOf(email) != -1 or modelValue.indexOf(startOfEmail) != -1


Handle indentation for multi-line blocks

--- src/controller.coffee
+++ src/controller.coffee
@@ -1193,25 +1193,40 @@
         )

         if @trackerPointIsInAce position
+          leadingWhitespaceRegex = /^(\s*)/
           # Get the line of text we're dropping into
           pos = @aceEditor.renderer.screenToTextCoordinates position.x, position.y
           line = @aceEditor.session.getLine pos.row
-          leadingWhitespace = /^(\s*)/.exec(line)[0]
+          currentIndentation = leadingWhitespaceRegex.exec(line)[0]
+
           prefix = ''
-          if pos.column == line.length and leadingWhitespace.length != line.length
+          indentation = currentIndentation
+          suffix = ''
+
+          if currentIndentation.length == line.length
+            # line is whitespace only.
+            # Append with a newline
+            suffix = '\n' + indentation
+          else if pos.column == line.length
             # We're at the end of a non-empty line.
-            # Stick our inserted text on a new line
-            # with the same indentation
-            prefix = '\n' + leadingWhitespace
+            # Insert a new line, and base our indentation off of the next line
+            prefix = '\n'
+            nextLine = @aceEditor.session.getLine(pos.row + 1)
+            indentation = leadingWhitespaceRegex.exec(nextLine)[0]
+          else

           # Call prepareNode, which may append with a semicolon
           @prepareNode @draggingBlock, null
-          text = prefix + @draggingBlock.stringify @mode
-
-          if not prefix and text[text.length - 1] == ';'
-            # Add a potentially indented new line if
-            # we didn't already do so.
-            text += '\n' + leadingWhitespace
+          text = @draggingBlock.stringify @mode
+
+          # Indent each line, unless it's the first line and wasn't placed on
+          # a newline
+          text = text.split('\n').map((line, index) =>
+            return (if index == 0 and prefix == '' then '' else indentation) + line
+          ).join('\n')
+
+          text = prefix + text + suffix
+
           @aceEditor.onTextInput text
       else if  @lastHighlight?


Activity: minor styling

Activity: minor styling

--- client/Social/Activity/views/privatemessage/listitem.coffee
+++ client/Social/Activity/views/privatemessage/listitem.coffee
@@ -11,13 +11,11 @@

     super options, data

-    {typeConstant} = @getData()
     @decorate()

+    {typeConstant, createdAt} = @getData()

-    {createdAt, deletedAt, updatedAt} = data
-
-    @likeView = new ReplyLikeView {}, data
+    @likeView = new ReplyLikeView {}, @getData()
     @timeView = new CommentTimeView timeFormat : 'h:MM TT', createdAt

     @commentBox.listPreviousLink.on 'ReachedToTheBeginning', @bound 'showParentPost'


Client: Teams: Added fetchPendingInvitatons method to fetch pending invitations and then notify users if any pending invitations exist

--- client/admin/lib/views/invitations/invitesomeoneview.coffee
+++ client/admin/lib/views/invitations/invitesomeoneview.coffee
@@ -200,6 +200,26 @@
         return new KDNotificationView { title, duration }

+  fetchPendingInvitations: (invites) ->
+
+    options = {}
+
+    new Promise (resolve, reject) ->
+
+      remote.api.JInvitation['some'] { status: 'pending' }, options, (err, pendings) =>
+        if err
+          reject err
+          return kd.warn err
+
+        pendingInvitations = []
+
+        if pendings.length
+          invites.map (inv, i) ->
+            invitations = pendings.filter (pending) -> inv.email is pending.email
+            pendingInvitations = pendingInvitations.concat invitations
+
+        resolve { pendingInvitations }
+
     remote.api.JInvitation.create invitations: invites, (err) =>
       if err
         return new KDNotificationView


Do not pass model name from setup

Do not pass model name from setup

--- desktop/apps/home/client/setup_home_page_modules.coffee
+++ desktop/apps/home/client/setup_home_page_modules.coffee
@@ -96,7 +96,6 @@
           $el: $el.find(".abrv-follow-button")
           module: module
           user: user
-          modelName: options.category ? 'gene' : 'artist'

          if contexts[module.key]?
             html = contexts[module.key](module)


FinderTreeController: Let the compile app works from app folder context menu

--- client/ide/filetree/controllers/findertreecontroller.coffee
+++ client/ide/filetree/controllers/findertreecontroller.coffee
@@ -365,23 +365,25 @@
         @refreshFolder @nodes[file.parentPath], =>
           @selectNode @nodes[response.path]

-  # compileApp:(nodeView, callback)->
-
-  #   folder = nodeView.getData()
-  #   folder.emit "fs.job.started"
-  #   kodingAppsController = KD.getSingleton('kodingAppsController')
-
-  #   manifest = KodingAppsController.getManifestFromPath folder.path
-
-  #   kodingAppsController.compileApp manifest.name, (err)=>
-  #     folder.emit "fs.job.finished"
-  #     if not err
-  #       @notify "App compiled!", "success"
-  #       @utils.wait 500, =>
-  #         @refreshFolder nodeView, =>
-  #           @utils.defer =>
-  #             @selectNode @nodes["#{folder.path}/index.js"]
-  #     callback? err
+  compileApp:(nodeView, callback)->
+
+    folder = nodeView.getData()
+    folder.emit "fs.job.started"
+
+    KodingAppsController.compileAppOnServer folder.path, (err, app)=>
+
+      folder.emit "fs.job.finished"
+      return warn err  if err
+
+      @notify "App compiled!", "success"
+
+      @utils.wait 500, =>
+        @refreshFolder nodeView, =>
+          @utils.defer =>
+            @selectNode @nodes["#{folder.path}/index.js"]
+
+      callback? err
+

   # runApp:(nodeView, callback)->

@@ -474,9 +476,9 @@
   cmGitHubClone:   (nodeView, contextMenuItem)-> @appManager.notify()
   cmOpenFile:      (nodeView, contextMenuItem)-> @openFile nodeView
   cmPreviewFile:   (nodeView, contextMenuItem)-> @previewFile nodeView
-  # cmCompile:       (nodeView, contextMenuItem)-> @compileApp nodeView
   # cmMakeNewApp:    (nodeView, contextMenuItem)-> @makeNewApp nodeView
   # cmPublish:       (nodeView, contextMenuItem)-> @publishApp nodeView
+  cmCompile:       (nodeView, contextMenuItem)-> @compileApp nodeView
   cmOpenFileWithApp: (nodeView, contextMenuItem)-> @openFileWithApp  nodeView, contextMenuItem
   cmCloneRepo:     (nodeView, contextMenuItem)-> @cloneRepo nodeView
   cmDropboxChooser:(nodeView, contextMenuItem)-> @chooseFromDropbox nodeView


minor

minor

--- src/client/embeds/sandbox.coffee
+++ src/client/embeds/sandbox.coffee
@@ -15,7 +15,7 @@
     @deployment = deployment

     @callbacks = []
-    @scopes = []
+    @_scopes = []

     @hull = assign({}, window.Hull, {
       getDocument      : @getDocument
@@ -71,7 +71,7 @@
     @iframe = iframe

     sandbox = Object.create(@)
-    @scopes.push sandbox
+    @_scopes.push sandbox
     sandbox.observe(iframe.contentDocument)
     sandbox.autoSizeInterval = null;
     sandbox.hull = assign(@hull, {
@@ -139,7 +139,8 @@
   get : ()-> @hull

   destroy: ()=>
-    scope.destroy() for scope in @scopes
+    scope.destroy() for scope in @_scopes
+    @_scopes = []
     @getObserver().destroy()

 module.exports = Sandbox


Width and height values are now rounded to reduce possible rendering glitches. Started work on improved scrollbar buttons. Started fixing item index 0 selection in thumb gallery.

--- src/coffee/jquery.rondell.coffee
+++ src/coffee/jquery.rondell.coffee
@@ -129,7 +129,7 @@
       $.extend true, @,
         _lastKeyEvent: 0
         _focusedIndex: @currentLayer
-        _itemIndices: {}
+        _itemIndices: { 0: 0 }
         autoRotation:
           _timer: -1
         controls:
@@ -343,11 +343,11 @@
         resizeable: isResizeable
         croppedSize: croppedSize
         sizeSmall:
-          width: smWidth
-          height: smHeight
+          width: Math.round smWidth
+          height: Math.round smHeight
         sizeFocused:
-          width: foWidth
-          height: foHeight
+          width: Math.round foWidth
+          height: Math.round foHeight

     _loadItem: (itemIndex, obj) =>
       icon = if obj.is('img') then obj else $('img:first', obj)
@@ -411,6 +411,7 @@

       # Move items to starting positions
       @_focusedItem ?= @_getItem @currentLayer
+      @_focusedItem.currentSlot = 0 if @switchIndices
       @shiftTo @currentLayer

     _onMobile: ->
@@ -794,13 +795,13 @@
       @setPosition @position, false, true

     _initControls: =>
-      @scrollLeftControl = $("
")
+      @scrollLeftControl = $("

")
         .bind "click", @scrollLeft

-      @scrollRightControl = $("
")
+      @scrollRightControl = $("

")
         .bind "click", @scrollRight

-      @scrollControl = $("
").css
+      @scrollControl = $("

").css
         left: @container.innerWidth() / 2

       @scrollBackground = $("
")


simple driver works

simple driver works

--- test/legacy/driver/simpleDriver.mocha.coffee
+++ test/legacy/driver/simpleDriver.mocha.coffee
@@ -334,7 +334,7 @@
       expect(err).to.not.exist
       expect(result.toJS()).to.deep.equal({
         prop: {
-          Max: new Date(1361919600000)
+          Max: 1361919600000 # ToDo: make this a date
         }
       })
       done()
@@ -347,7 +347,7 @@
       expect(err).to.not.exist
       expect(result.toJS()).to.deep.equal({
         prop: {
-          Min: new Date(1361836800000)
+          Min: 1361836800000 # ToDo: make this a date
         }
       })
       done()


 Merge branch 'master.activity.comment' into 'master.activity'

activity: set event argument as comment input value

--- client/Social/Activity/views/comments/inputform.coffee
+++ client/Social/Activity/views/comments/inputform.coffee
@@ -25,16 +25,13 @@
     @input.on 'blur', @bound 'inputBlured'

-  submit: ->  @emit 'Submit', @input.getValue()
-
-
-  enter: ->
+  enter: (value) ->

     kallback = =>

-      @submit()
+      @emit 'Submit', value

-      KD.mixpanel 'Comment activity, click', @input.getValue().length
+      KD.mixpanel 'Comment activity, click', value.length

       @input.setValue ''
       @input.resize()


profile: hiding customise button for non-activity views

--- client/app/MainApp/lazy/staticprofilecontroller.coffee
+++ client/app/MainApp/lazy/staticprofilecontroller.coffee
@@ -113,16 +113,18 @@
       @emit 'StaticProfileNavLinkClicked', 'CBlogPostActivity', 'static', =>
         @showWrapper @wrappers['static']
         @staticDefaultItem.show()
+        @staticPageSettingsButton.hide()
         callback()

     @on 'ActivityLinkClicked', (callback=->)=>
       @addLogic 'activity'
-      @showLoadingBar()
+      # @showLoadingBar()
       @emit 'StaticProfileNavLinkClicked', 'CBlogPostActivity', 'activity', =>
         @showWrapper @wrappers['activity']
         @displaySidebar yes
+        @staticPageSettingsButton.show()
         callback()

@@ -130,6 +132,7 @@
       @addLogic 'about', =>
         callback()
       @displaySidebar no
+      @staticPageSettingsButton.hide()

     @on 'CustomizeLinkClicked',=>
@@ -204,6 +207,67 @@
   reviveAdminViews:->

       # reviving ADMIN views (inline edits, feeder checkboxes)
+
+      @profileTitleNameView = new KDView
+        lazyDomId : 'profile-name'
+
+      @profileTitleBioView = new KDView
+        lazyDomId : 'profile-bio'
+
+      profileAdminCustomizeView = new KDView
+        lazyDomId : 'profile-admin-customize'
+
+      profileAdminCustomizeView.addSubView @staticPageSettingsButton = new CustomLinkView
+        title : 'Customize'
+        cssClass : 'static-page-settings-button clean-gray hidden'
+        click :(event)=>
+          @emit 'CustomizeLinkClicked'
+          event.stopPropagation()
+          event.preventDefault()
+
+      profileAdminCustomizeView.show()
+
+      profileAdminMessageView = new KDView
+        lazyDomId : 'profile-admin-message'
+
+      showPage = @profileUser.profile.staticPage?.show
+
+      profileAdminMessageView.addSubView disableLink = new CustomLinkView
+        title     : "#{if showPage is yes then 'Disable' else 'Enable'}"
+        cssClass  : 'message-disable'
+        click     : (event)=>
+          event?.stopPropagation()
+          event?.preventDefault()
+
+          if @profileUser.profile.staticPage?.show is yes
+            modal           = new KDModalView
+              cssClass      : 'disable-static-page-modal'
+              title         : 'Do you really want to disable your Public Page?'
+              content       : """
+

+
Disabling this feature will disable other people
+                  from publicly viewing your profile. You will still be
+                  able to access the page yourself.

+
Do you want to continue?

+

+                """
+              buttons       :
+                "Disable the Public Page" :
+                  cssClass  : 'modal-clean-red'
+                  callback  : =>
+                    modal.destroy()
+                    @profileUser.setStaticPageVisibility no, (err,res)=>
+                      if err then log err
+                      disableLink.updatePartial 'Enable'
+                Cancel      :
+                  cssClass  : 'modal-cancel'
+                  callback  : =>
+                    modal.destroy()
+          else
+            @profileUser.setStaticPageVisibility yes, (err,res)=>
+              if err then log err
+              disableLink.updatePartial 'Disable'
+
       handleLinks = {}

       handleLinks['twitter'] = new KDView
@@ -473,69 +537,9 @@
     @emit 'DecorateStaticNavLinks', @getAllowedTypes(@profileUser), 'CBlogPostActivity'

     unless user.getId() is KD.whoami().getId()
-      @addVisitorViews() if KD.isLoggedIn()
+      @reviveVisitorViews() if KD.isLoggedIn()

     else
-      @profileTitleNameView = new KDView
-        lazyDomId : 'profile-name'
-
-      @profileTitleBioView = new KDView
-        lazyDomId : 'profile-bio'
-
-      profileAdminCustomizeView = new KDView
-        lazyDomId : 'profile-admin-customize'
-
-      profileAdminCustomizeView.addSubView staticPageSettingsButton = new CustomLinkView
-        title : 'Customize'
-        cssClass : 'static-page-settings-button clean-gray'
-        click :(event)=>
-          @emit 'CustomizeLinkClicked'
-          event.stopPropagation()
-          event.preventDefault()
-
-      profileAdminCustomizeView.show()
-
-      profileAdminMessageView = new KDView
-        lazyDomId : 'profile-admin-message'
-
-      showPage = user.profile.staticPage?.show
-
-      profileAdminMessageView.addSubView disableLink = new CustomLinkView
-        title     : "#{if showPage is yes then 'Disable' else 'Enable'}"
-        cssClass  : 'message-disable'
-        click     : (event)=>
-          event?.stopPropagation()
-          event?.preventDefault()
-
-          if user.profile.staticPage?.show is yes
-            modal           = new KDModalView
-              cssClass      : 'disable-static-page-modal'
-              title         : 'Do you really want to disable your Public Page?'
-              content       : """
-

-
Disabling this feature will disable other people
-                  from publicly viewing your profile. You will still be
-                  able to access the page yourself.

-
Do you want to continue?

-

-                """
-              buttons       :
-                "Disable the Public Page" :
-                  cssClass  : 'modal-clean-red'
-                  callback  : =>
-                    modal.destroy()
-                    user.setStaticPageVisibility no, (err,res)=>
-                      if err then log err
-                      disableLink.updatePartial 'Enable'
-                Cancel      :
-                  cssClass  : 'modal-cancel'
-                  callback  : =>
-                    modal.destroy()
-          else
-            user.setStaticPageVisibility yes, (err,res)=>
-              if err then log err
-              disableLink.updatePartial 'Disable'
-
       @reviveAdminViews()

     console.timeEnd 'reviving page elements on userload.'
@@ -581,7 +585,7 @@
     # @profileLoaderView.show()

-  addVisitorViews:->
+  reviveVisitorViews:->

   getAllowedTypes:(@profileUser)->
@@ -691,8 +695,8 @@
     @profileShowMoreView.hide()

     for own name,wrapper of @wrappers
-      log name, wrapper
-      wrapper?.hide()
+      # log name, wrapper
+      wrapper?.hide?()

   showWrapper:(wrapper)->
     @hideWrappers()


client: remove checker for toggle channel feature in sidebar

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -595,9 +595,6 @@
       countSource: (callback) ->
         remote.api.SocialChannel.fetchFollowedChannelCount {}, callback

-    if kd.singletons.mainController.isFeatureDisabled 'channels'
-      @sections.channels.hide()
-

   addConversations: ->


Missing function invocation

Missing function invocation

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -231,6 +231,8 @@
             options.channelId = workspace.channelId
             show options

+      return show options
+
     else if @machine.isPermanent()
       options.type = 'shared machine'
       options.isApproved = @machine.isApproved()


BaseStackTemplateListItem: allow extended classes to add new menu items

--- client/app/lib/stacks/basestacktemplatelistitem.coffee
+++ client/app/lib/stacks/basestacktemplatelistitem.coffee
@@ -20,7 +20,6 @@
     @menu = {}

     @settings        = new KDButtonViewWithMenu
-      title          : ''
       cssClass       : 'stack-settings-menu'
       itemChildClass : ActivityItemMenuItem
       delegate       : this
@@ -30,13 +29,13 @@
       callback       : (event) => @settings.contextMenu event

-  addMenuItem: (title, callback) -> @menu[title] = {callback}
+  addMenuItem: (title, callback) -> @menu[title] = { callback }

   settingsMenu: ->

     delegate  = @getDelegate()
-    @menu     = {}
+    @menu    ?= {}

     @addMenuItem 'Show',    delegate.lazyBound 'showItemContent', this
     @addMenuItem 'Edit',    @bound 'updateStackTemplate'


prettify

prettify

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -97,12 +97,11 @@
     manifest = FSHelper.createFileFromPath "#{appsPath}#{appName}#{suffix}"
     manifest.fetchContents (err, response)=>
       # warn err  if err
-      return cb null  if err
-      return cb null  unless response
+      return cb null  if err or not response

       try
         manifest = JSON.parse response
-        manifests["#{manifest.name}"] = manifest
+        manifests[manifest.name] = manifest
       catch e
         warn "Manifest file is broken:", e
         return cb null


testing toHsl method

testing toHsl method

--- test/color.coffee
+++ test/color.coffee
@@ -202,4 +202,13 @@
     g: -> 0
     b: -> 0
     a: -> 1
-
+
+test "toHsl returns proper values", ->
+  color = Color(123, 43, 98)
+  hsl = color.toHsl()
+
+  equals hsl[0], 319
+  equals hsl[1].toFixed(2), 0.48
+  equals hsl[2].toFixed(2), 0.33
+
+


Social: register topic channels and forward their events to the SocialChannel instance

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -1,4 +1,5 @@
 class SocialApiController extends KDController
+
   constructor: (options = {}, data) ->
     super options, data

@@ -124,9 +125,34 @@
       # we need `channel-` here
       c._id = "channel-#{c.id}"

+      # bind all events
+      registerAndOpenChannel c
+
+      # push channel into stack
       revivedChannels.push c

     return revivedChannels
+
+  forwardEvents = (source, target,  events)->
+    events.forEach (event) ->
+      source.on event, (rest...) ->
+        target.emit event, rest...
+
+  registerAndOpenChannel = (socialApiChannel)->
+    getCurrentGroup (group)->
+      subscriptionData =
+        serviceType: 'socialapi'
+        group      : group.slug
+        channelType: socialApiChannel.typeConstant
+        channelName: socialApiChannel.name
+        isExclusive: yes
+
+      name = "socialapi.#{socialApiChannel.groupName}-#{socialApiChannel.typeConstant}-#{socialApiChannel.name}"
+      brokerChannel = KD.remote.subscribe name, subscriptionData
+      forwardEvents brokerChannel, socialApiChannel, [
+        "MessageAdded",
+        "MessageRemoved"
+      ]

   message:
     edit   :(rest...)-> messageApiMessageResFunc 'edit', rest...


Fix add comment serialization

Fix add comment serialization

--- lineman/app/models/comment_model.coffee
+++ lineman/app/models/comment_model.coffee
@@ -15,8 +15,8 @@
       @newAttachmentIds = []

     serialize: ->
-      data = baseSerialize()
-      data[new_attachment_ids] = @newAttachmentIds
+      data = @baseSerialize()
+      data['comment']['new_attachment_ids'] = @newAttachmentIds
       data

     group: ->


add al bundy to 'so good'

add al bundy to 'so good'

--- scripts/seal-of-approval.coffee
+++ scripts/seal-of-approval.coffee
@@ -48,7 +48,8 @@
  "http://i.imgur.com/3w1ej.gif",
  "http://i.imgur.com/JSBTl.gif",
  "http://i.imgur.com/NnBHS.gif",
- "http://i.imgur.com/59KTQ.gif"
+ "http://i.imgur.com/59KTQ.gif",
+ "http://i.imgur.com/d0l2c.gif"
 ]

 module.exports = (robot) ->


added html friendly chars

added html friendly chars

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

   robot.hear /(security|secure)/i, (msg) ->
-    msg.send "<!--- TODO: add security --->"+    msg.send "<!--- TODO: add security --->"


Encode URI component for the search

Encode URI component for the search

--- core/app/backbone/collections/search_collection.coffee
+++ core/app/backbone/collections/search_collection.coffee
@@ -9,6 +9,8 @@

   searchFor: (query) ->
     return if query == @query
+
+    query = encodeURIComponent query

     if query == ''
       @makeEmpty()


sequnce -> sequence

sequnce -> sequence

--- spec/chromium-spec.coffee
+++ spec/chromium-spec.coffee
@@ -210,7 +210,7 @@
       setImmediate ->
         called = false
         Promise.resolve().then ->
-          done(if called then undefined else new Error('wrong sequnce'))
+          done(if called then undefined else new Error('wrong sequence'))
         document.createElement 'x-element'
         called = true

@@ -224,6 +224,6 @@
       remote.getGlobal('setImmediate') ->
         called = false
         Promise.resolve().then ->
-          done(if called then undefined else new Error('wrong sequnce'))
+          done(if called then undefined else new Error('wrong sequence'))
         document.createElement 'y-element'
         called = true


Get all people.

Get all people.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -124,9 +124,6 @@

     robot.respond /scores$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
-            robot.brain.get points
-            points[username] ?= 0

-            for username in people
-                score = ["#{username} has #{points[username]}"]
+            score = ["#{username} has #{points[username]}" for username in people]
             msg.send "Stored scores: #{score}"


Pull a specific quote.

Pull a specific quote.

--- scripts/quotes.coffee
+++ scripts/quotes.coffee
@@ -10,6 +10,7 @@
 #   Dave McNally

 quotes = []
+pick = 0

 save = (robot) ->
     robot.brain.data.quotes = quotes
@@ -28,3 +29,8 @@
     robot.respond /quote$/i, (msg) ->
         pulled = quotes[Math.floor(Math.random() * quotes.length)]
         msg.send '"' + pulled + '"'
+
+    # Pull a specific quote.
+    robot.respond /quote (\d+)$/i, (msg) ->
+        pick = msg.match[1]
+        msg.send quotes[pick -1]


setPath -> setPaths

setPath -> setPaths

--- spec/git-diff-spec.coffee
+++ spec/git-diff-spec.coffee
@@ -12,7 +12,7 @@
     projectPath = temp.mkdirSync('git-diff-spec-')
     fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath)
     fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
-    atom.project.setPath(projectPath)
+    atom.project.setPaths([projectPath])

     atom.workspaceView = new WorkspaceView
     atom.workspaceView.attachToDom()