Fixed ids

Fixed ids

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -226,7 +226,7 @@
       throw new Error "already defining, forgot to leaveDefinition?"
     @_scope().definition =
       name: pattern?.symbol
-      id: pattern?.symbol and @freshId()
+      id: pattern?.symbol and (@currentDeclarationId pattern.symbol) or @freshId()
       pattern: pattern
       inside: 0
       late: no
@@ -611,6 +611,9 @@

   deferredBindings: ->
     @_scope().deferredBindings
+
+  currentDeclarationId: (name) ->
+    (@_declarationInCurrentScope name)?.id

   declarationId: (name) ->
     (@_declaration name)?.id
@@ -1497,7 +1500,7 @@
       # log "adding types", (map _symbol, params), paramTypes
       ctx.declareTyped paramNames, paramTypes
       for param in params
-        param.id = ctx.declarationId _symbol param
+        param.id = ctx.currentDeclarationId _symbol param

       # Declare wheres first so they properly shadow parent scope
       preDeclarePatterns ctx, _fst unzip pairs wheres
@@ -2619,7 +2622,7 @@
         pattern: []
     else
       atom.label = 'name'
-      id = (ctx.definitionId()) ? (ctx.declarationId symbol) ? ctx.freshId()
+      id = (ctx.definitionId()) ? (ctx.currentDeclarationId symbol) ? ctx.freshId()
       type = toConstrained ctx.freshTypeVariable star
       # ctx.bindTypeVariables [type.type.name]
       ctx.addToDefinedNames {name: symbol, id: id, type: type}
@@ -4450,7 +4453,7 @@
   typeOrConstraint

 mutateMarkingOrigin = (typeOrConstraint, expression) ->
-  typeOrConstraint.origin = expression
+  # typeOrConstraint.origin = expression

 # Clones constrained types and parts of them
 # Class constraint arguments are not cloned


Dont pull constraints from deferred defining definitions

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1361,7 +1361,7 @@
         currentType
         scopeIndex
       if not success
-        return
+        return []
       [deferredConstraints, retainedConstraints] = success
       # Finalizing type again after possibly added substitution when defer constraints
       currentType = type#substitute ctx.substitution, type
@@ -1481,8 +1481,8 @@
           # log "done unifying one"

         # have to promote constraints from just compiled dependencies
-        depConstraints = concat (for dep in binding.deps or [] when cononicalType = ctx.finalType dep.id, dep.scopeIndex
-          constraintsFromCanonicalType ctx, cononicalType, dep.type)
+        depConstraints = concat (for dep in binding.deps or [] when not dep.defining and depCanonicalType = ctx.finalType dep.id, dep.scopeIndex
+          constraintsFromCanonicalType ctx, depCanonicalType, dep.type)
         allConstraints = (join binding.constraints or [], depConstraints)

         # If the thing is not declared it must be coming from an outer scope
@@ -1657,7 +1657,7 @@
           #   return #{compiledBody};
           # })"""
           (irFunction
-            name: (ctx.definitionName() if ctx.isAtSimpleDefinition())
+            name: name
             params: paramNames
             body: (join nonLiftedWheres, [(jsReturn compiledBody)]))
             # (jsCall "λ#{paramNames.length}", [


Major fixes

Major fixes

--- demo/server/main.coffee
+++ demo/server/main.coffee
@@ -22,24 +22,26 @@
         updatedAt = +new Date

         for row in graph
-          if row?.n and not nodes?[row.n.id]
-            if row.n?.start
-              edge =
-                id: row.n.id
-                from: row.n.start
-                to: row.n.end
-                type: row.n.type
-                label: row.n.type
-                arrows: 'to'
-                group: row.n.type
-              edges[row.n.id] = _.extend edge, row.n
+          if row?.n
+            if row.n?.start or row.n?.end
+              unless edges?[row.n.id]
+                edge =
+                  id: row.n.id
+                  from: row.n.start
+                  to: row.n.end
+                  type: row.n.type
+                  label: row.n.type
+                  arrows: 'to'
+                  group: row.n.type
+                edges[row.n.id] = _.extend edge, row.n
             else
-              node =
-                id: row.n.id
-                labels: row.n.labels
-                label: row.n.name
-                group: row.n.labels[0]
-              nodes[row.n.id] = _.extend node, row.n
+              unless nodes?[row.n.id]
+                node =
+                  id: row.n.id
+                  labels: row.n.labels
+                  label: row.n.name
+                  group: row.n.labels[0]
+                nodes[row.n.id] = _.extend node, row.n

         visGraph.edges = (value for key, value of edges)
         visGraph.nodes = (value for key, value of nodes)
@@ -114,29 +116,30 @@
       We will just wait for long-polling updates on client
       ###
       updatedAt = +new Date
-      unless oldRel.property 'removed'
-        n1 = db.nodes form.from
-        n2 = db.nodes form.to
-        if form.type isnt oldRel.get().type
-          oldRel.setProperties
-            removed: true
-            updatedAt: updatedAt
+      if oldRel.get()
+        unless oldRel.property 'removed'
+          n1 = db.nodes form.from
+          n2 = db.nodes form.to
+          if form.type isnt oldRel.get().type
+            oldRel.setProperties
+              removed: true
+              updatedAt: updatedAt

-          Meteor.setTimeout ->
-            r = db.getRelation form.id
-            r.delete() if r?.get?()
-          , 15000
+            Meteor.setTimeout ->
+              r = db.getRelation form.id
+              r.delete() if r?.get?()
+            , 15000

-          r = n1.to(n2, form.type, {description: form.description, updatedAt}).get()
-        else
-          r = oldRel.setProperties({description: form.description, updatedAt}).get()
+            r = n1.to(n2, form.type, {description: form.description, updatedAt}).get()
+          else
+            r = oldRel.setProperties({description: form.description, updatedAt}).get()

-        r.from    = r.start
-        r.to      = r.end
-        r.label   = r.type
-        r.group   = r.type
-        r.arrows  = 'to'
-        r
+          r.from    = r.start
+          r.to      = r.end
+          r.label   = r.type
+          r.group   = r.type
+          r.arrows  = 'to'
+          r
       else
         true

@@ -150,9 +153,8 @@
       ###
       updatedAt = +new Date
       r = db.getRelation id
-      _r = r.get()
-
-      unless r.property 'removed'
+
+      if r.get() and not r.property 'removed'
         r.setProperties
           removed: true
           updatedAt: updatedAt


adding in a link to edit published exercises and removing ability to edit exercises

--- src/components/exercise.cjsx
+++ src/components/exercise.cjsx
@@ -30,6 +30,10 @@

   getId: ->
     @props.id or @state.id
+
+  getDraftId: ->
+    id = if @props.id.indexOf("@") is -1 then @props.id else @props.id.split("@")[0]
+    "#{id}@d"

   saveExercise: ->
     if confirm('Are you sure you want to save?')
@@ -69,8 +73,7 @@

-        Exercise Number
-        <input onChange={@updateNumber} value={ExerciseStore.getNumber(id)}/>
+        Exercise Number: {ExerciseStore.getNumber(id)}

         Exercise Stimulus
         <textarea onChange={@updateStimulus} defaultValue={ExerciseStore.getStimulus(id)}>
@@ -112,15 +115,20 @@

           Published: {ExerciseStore.getPublishedDate(id)}

+      editLink =
+

+          Edit Exercise
+

     else
       form = @renderForm(id)

     <BS.Grid>
       <BS.Row><BS.Col xs={5} className="exercise-editor">

-          Exercise ID {ExerciseStore.getId(id)}
+          Exercise ID: {ExerciseStore.getId(id)}

         {publishedLabel}
+        {editLink}
         {form}
       </BS.Col><BS.Col xs={6} className="pull-right">
         {preview}


<tests> Correcting filepath of fixture.

<tests> Correcting filepath of fixture.

--- tests/fixtures/driver.coffee
+++ tests/fixtures/driver.coffee
@@ -8,7 +8,7 @@
 module.exports =
 	basicSensorDriver:
 		name: "BasicDriver"
-		file: "./basicDriver.coffee"
+		file: __dirname + "/basicDriver.coffee"

 	unexistingSensorDriver:
 		name: "Unexisting Driver"


adding in surety guard for confirmation when student removes a task

--- tutor/src/components/student-dashboard/event-row.cjsx
+++ tutor/src/components/student-dashboard/event-row.cjsx
@@ -4,6 +4,7 @@
 {StudentDashboardStore, StudentDashboardActions} = require '../../flux/student-dashboard'
 EventInfoIcon = require './event-info-icon'
 {Instructions} = require '../task/details'
+{SuretyGuard} = require 'shared'
 classnames = require 'classnames'

 module.exports = React.createClass
@@ -26,9 +27,11 @@
       {courseId:@props.courseId, id: @props.event.id, stepIndex: 1}

   hideTask: (event) ->
-    event.stopPropagation()
     StudentDashboardActions.hide(@props.event.id)
     StudentDashboardStore.on('hidden', @hidden)
+
+  stopEventPropagation: (event) ->
+    event.stopPropagation()

   hidden: -> @setState({hidden: true})

@@ -41,9 +44,25 @@
     classes = classnames("task row #{@props.className}", {workable, deleted})

     if deleted
-      hideButton = <BS.Button className="hide-task" onClick={@hideTask}>
-
-      </BS.Button>
+      message =

+

+          If you remove this assignment, you will lose any progress or feedback you have received.
+

+
Do you wish to continue?

+

+
+      hideButton =
+        <SuretyGuard
+          onConfirm={@hideTask}
+          okButtonLabel='Yes'
+          placement='top'
+          message={message}
+        >
+          <BS.Button className="hide-task" onClick={@stopEventPropagation}>
+
+          </BS.Button>
+        </SuretyGuard>
+
       feedback = Withdrawn
     else
       time = <Time date={@props.event.due_at} format='concise'/>


Remove console.log call.

Remove console.log call.

--- src/drawer/DrawerView.coffee
+++ src/drawer/DrawerView.coffee
@@ -129,8 +129,6 @@
   constructor: (init, events) ->
     defaults init, {tagName: TAG_NAME}
     super arguments...
-
-    console.log @container, @viewport

     # Don't immediately show the view.
     @options.showOnRender = false


Fix error causing web dev levels to not finish properly

--- app/views/play/level/tome/SpellView.coffee
+++ app/views/play/level/tome/SpellView.coffee
@@ -1433,7 +1433,7 @@
     $(window).off 'resize', @onWindowResize
     window.clearTimeout @saveSpadeTimeout
     @saveSpadeTimeout = null
-    @autocomplete.destroy()
+    @autocomplete?.destroy()
     super()

 # Note: These need to be double-escaped for insertion into regexes


Remove duplicate C-l c keybindings

Remove duplicate C-l c keybindings

--- keymaps/latextools.cson
+++ keymaps/latextools.cson
@@ -17,7 +17,6 @@

 "body:not([class~='platform-darwin']) atom-text-editor[data-grammar~='latex']":
   'ctrl-l ctrl-l': 'editor:select-line'
-  'ctrl-l c': 'latextools:clear-log'
   'ctrl-l escape': 'latextools:hide-ltconsole'
   'ctrl-l shift-escape': 'latextools:show-ltconsole'
   'ctrl-l backspace': 'latextools:delete-temp-files'
@@ -37,7 +36,6 @@

 "body.platform-darwin atom-text-editor[data-grammar~='latex']":
   'cmd-l cmd-l': 'editor:select-line'
-  'cmd-l c': 'latextools:clear-log'
   'cmd-l escape': 'latextools:hide-ltconsole'
   'cmd-l shift-escape': 'latextools:show-ltconsole'
   'cmd-l backspace': 'latextools:delete-temp-files'


Add more base keybinds #1171

Add more base keybinds #1171

--- keymaps/remote-ftp.cson
+++ keymaps/remote-ftp.cson
@@ -3,3 +3,10 @@
   'ctrl-alt-d': 'remote-ftp:disconnect'
   'ctrl-alt-c': 'remote-ftp:connect'
   'cmd-shift-u': 'remote-ftp:upload-selected'
+
+'.remote-ftp-view':
+  'a': 'remote-ftp:add-file'
+  'shift-a': 'remote-ftp:add-folder'
+  'd': 'remote-ftp:move-selected'
+  'delete': 'remote-ftp:delete-selected'
+  'backspace': 'remote-ftp:delete-selected'


check socket io connection before adding file attachment

--- www/js/controllers/msg.coffee
+++ www/js/controllers/msg.coffee
@@ -64,16 +64,30 @@
 				_.last(collection.models)?.$isNew()
 			addFile: (files) ->
 				if files?.length != 0
-					msg = $scope.addMsg()
-					msg.local = files[0]
-					msg.file = _.extend url: URL.createObjectURL(files[0]), _.pick(files[0], 'name', 'type'), base: files[0].name, ext: path.extname(files[0].name)
-					msg.file_inode = contentType: files[0].type
-					$scope.$apply 'collection.models'
+					$scope.addMsg().then (msg) ->
+						msg.local = files[0]
+						msg.file =
+							url: if files[0] instanceof MediaFile then files[0].localURL else URL.createObjectURL files[0]
+							name: files[0].name
+							type: files[0].type
+							base: files[0].name
+							ext: path.extname files[0].name
+						msg.file_inode = contentType: files[0].type
+						$scope.$apply 'collection.models'
 			addMsg: ->
 				msg = new resource.Msg type: type, to: chat.jid, body: ''
-				collection.add msg
-				$ionicScrollDelegate.scrollTop true
-				msg
+				connected = new Promise (resolve, reject) ->
+					if io.socket?.connected
+						resolve()
+					else
+						io.socket?.once 'connect', resolve
+						io.socket?.once 'error', reject
+				connected
+					.then ->
+						collection.add msg
+						$ionicScrollDelegate.scrollTop true
+						msg
+					.catch $log.error
 			recording: false
 			copy: (msg) ->
 				if env.isNative()


groups controller: code cleanup

groups controller: code cleanup

--- client/Main/maincontroller/groupscontroller.coffee
+++ client/Main/maincontroller/groupscontroller.coffee
@@ -40,9 +40,7 @@

     @groupChannel.once 'setSecretNames', callback

-  changeGroup:(groupName='', callback=->)->
-
-    groupName or= 'koding' # KD.defaultSlug
+  changeGroup:(groupName = 'koding', callback = (->))->
     return callback()  if @currentGroupName is groupName

     oldGroupName        = @currentGroupName


Call info and update the latestState after each operation

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -2,18 +2,11 @@

   constructor:->
     super
+
     @kc = KD.singletons.kiteController

-  _cbWrapper:(callback, emitStateChanged)->
-    return callback  unless emitStateChanged
-
-    kallback = (rest...)=>
-      @info (err, info)=>
-        warn "[VM]", err  if err
-        @emit 'StateChanged', err, info
-      callback? rest...
-
-    return kallback
+    @lastState =
+      state    : 'STOPPED'

   run:(command, callback, emitStateChanged=yes)->
     @kc.run
@@ -31,4 +24,16 @@
     @run 'vm.reinitialize', callback

   info:(callback)->
-    @run 'vm.info', callback, no+    @run 'vm.info', (err, info)=>
+      unless err then @lastState = info
+      else warn "[VM]", err
+
+      @emit 'StateChanged', err, info
+      callback? err, info
+    , no
+
+  _cbWrapper:(callback, emitStateChanged)->
+    return callback  unless emitStateChanged
+    return (rest...)=>
+      @info callback? rest...
+


Kill unused require

Kill unused require

--- src/pane-container-model.coffee
+++ src/pane-container-model.coffee
@@ -1,6 +1,5 @@
 {Model} = require 'theorist'
 Serializable = require 'serializable'
-{find} = require 'underscore-plus'
 PaneModel = require './pane-model'

 module.exports =


Removing environment variable unused

Removing environment variable unused

--- src/processors/job/index.coffee
+++ src/processors/job/index.coffee
@@ -1,8 +1,6 @@
 _ = require("lodash")
 JobProcessor = require "./job.processor"
 RequestProcessor = require "../request.processor"
-
-MAX_DEQUEUE_COUNT = process.env.MAX_DEQUEUE_COUNT

 _normalizeHeaders = (headers) ->
   _(headers)


Run actual test files and collect results

--- lib/barista_test.coffee
+++ lib/barista_test.coffee
@@ -1,10 +1,39 @@
+sys = require "sys"
+fs = require "fs"
+assert_module = require "assert"
+coffee = require "coffee-script"
+helpers = require("coffee-script/helpers").helpers
+
 # Parse our command-line arguments.
 test_file_paths = process.argv[1...process.argv.length]

-# Pretend to run our test files.
+# Set up a basic test environment.  This code is based on the code at
+# http://github.com/jashkenas/coffee-script/blob/master/Cakefile ,
+# which implements the standard Coffee
+passed = failed = 0
+helpers.extend global, assert_module
+global.ok = (args...) ->
+  res = assert_module.ok(args...)
+  passed += 1
+  res
+
+# Called when the tests are done.
+testsDone = ->
+  tag = if failed > 0 then "ERR" else "OK"
+  sys.puts "#{tag}: #{passed} passed, #{failed} failed"
+  if failed > 0
+    process.exit(1)
+
+# Pretend to run our test files.  Again, this is based on the standard
+# Cakefile.
+files_left = test_file_paths.length
 for path in test_file_paths
-  if path.match(/failing/)
-    process.exit 1
-
-# Exit cleanly if no error occurred.
-process.exit 0
+  fs.readFile path, (err, data) ->
+    files_left -= 1
+    try
+      coffee.run data.toString(), fileName: path
+    catch err
+      failed += 1
+      sys.puts err.stack.toString()
+    if files_left is 0
+      testsDone()


removed mapquest support

removed mapquest support

--- vendor/assets/gmaps4rails.bing.js.coffee
+++ vendor/assets/gmaps4rails.bing.js.coffee
@@ -1,145 +0,0 @@
-######################################################################################################
-##############################################  Bing Maps  ##########################################
-######################################################################################################
-
-#// http://wiki.openstreetmap.org/wiki/OpenLayers
-#// http://openlayers.org/dev/examples
-#//http://docs.openlayers.org/contents.html
-
-class @Gmaps4RailsBing extends Gmaps4Rails
-
-  constructor: ->
-    super
-    @map_options =
-      type:  "road"   # aerial, auto, birdseye, collinsBart, mercator, ordnanceSurvey, road
-    @markers_conf =
-      infobox:  "description" #description or htmlContent
-
-    @mergeWithDefault("map_options")
-    @mergeWithDefault("markers_conf")
-
-  #////////////////////////////////////////////////////
-  #/////////////// Basic Objects         //////////////
-  #////////////////////////////////////////////////////
-
-  # getMapType: ->
-  #   switch @map_options.type
-  #     when "road"           then return Microsoft.Maps.MapTypeId.road
-  #     when "aerial"         then return Microsoft.Maps.MapTypeId.aerial
-  #     when "auto"           then return Microsoft.Maps.MapTypeId.auto
-  #     when "birdseye"       then return Microsoft.Maps.MapTypeId.birdseye
-  #     when "collinsBart"    then return Microsoft.Maps.MapTypeId.collinsBart
-  #     when "mercator"       then return Microsoft.Maps.MapTypeId.mercator
-  #     when "ordnanceSurvey" then return Microsoft.Maps.MapTypeId.ordnanceSurvey
-  #     else return Microsoft.Maps.MapTypeId.auto
-
-
-
-  #////////////////////////////////////////////////////
-  #////////////////////// Markers /////////////////////
-  #////////////////////////////////////////////////////
-
-  createMarker: (args) ->
-    # markerLatLng = @createLatLng(args.Lat, args.Lng)
-    # marker
-    # #// Marker sizes are expressed as a Size of X,Y
-    # if args.marker_picture == ""
-    #   marker = new Microsoft.Maps.Pushpin(@createLatLng(args.Lat, args.Lng), {
-    #     draggable: args.marker_draggable,
-    #     anchor:    @createImageAnchorPosition(args.Lat, args.Lng),
-    #     text:      args.marker_title
-    #     }
-    #   );
-    # else
-    #   marker = new Microsoft.Maps.Pushpin(@createLatLng(args.Lat, args.Lng), {
-    #     draggable: args.marker_draggable,
-    #     anchor:    @createImageAnchorPosition(args.Lat, args.Lng),
-    #     icon:      args.marker_picture,
-    #     height:    args.marker_height,
-    #     text:      args.marker_title,
-    #     width:     args.marker_width
-    #     }
-    #   );
-    # @addToMap(marker)
-    # return marker
-
-
-  # clearMarker: (marker) ->
-  #   @removeFromMap(marker.serviceObject)
-
-
-  # showMarker: (marker) ->
-  #   marker.serviceObject.setOptions({ visible: true })
-
-  # hideMarker: (marker) ->
-  #   marker.serviceObject.setOptions({ visible: false })
-
-  extendBoundsWithMarkers: ->
-    locationsArray = []
-    for marker in @markers
-      locationsArray.push(marker.serviceObject.getLocation())
-    @boundsObject = Microsoft.Maps.LocationRect.fromLocations(locationsArray)
-
-  #////////////////////////////////////////////////////
-  #/////////////////// Clusterer //////////////////////
-  #////////////////////////////////////////////////////
-
-  createClusterer: (markers_array) ->
-
-  clearClusterer: ->
-
-  #//creates clusters
-  clusterize: ->
-
-  #////////////////////////////////////////////////////
-  #/////////////////// INFO WINDOW ////////////////////
-  #////////////////////////////////////////////////////
-
-  #// creates infowindows
-  createInfoWindow: (marker_container) ->
-    if marker_container.description?
-      #//create the infowindow
-      if @markers_conf.infobox == "description"
-        marker_container.info_window = new Microsoft.Maps.Infobox(marker_container.serviceObject.getLocation(), { description: marker_container.description, visible: false, showCloseButton: true})
-      else
-        marker_container.info_window = new Microsoft.Maps.Infobox(marker_container.serviceObject.getLocation(), { htmlContent: marker_container.description, visible: false})
-
-      #//add the listener associated
-      currentMap = this
-      Microsoft.Maps.Events.addHandler(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.info_window))
-      @addToMap(marker_container.info_window)
-
-  openInfoWindow: (currentMap, infoWindow) ->
-    return ->
-      # Close the latest selected marker before opening the current one.
-      if currentMap.visibleInfoWindow
-        currentMap.visibleInfoWindow.setOptions({ visible: false })
-      infoWindow.setOptions({ visible:true })
-      currentMap.visibleInfoWindow = infoWindow
-
-  #////////////////////////////////////////////////////
-  #/////////////////// Other methods //////////////////
-  #////////////////////////////////////////////////////
-
-  fitBounds: ->
-    @serviceObject.setView({bounds: @boundsObject})
-
-  # addToMap: (object)->
-  #   @serviceObject.entities.push(object)
-
-  # removeFromMap: (object)->
-  #   @serviceObject.entities.remove(object)
-
-  centerMapOnUser: ->
-    @serviceObject.setView({ center: @userLocation})
-
-  updateBoundsWithPolylines: ()->
-
-  updateBoundsWithPolygons: ()->
-
-  updateBoundsWithCircles: ()->
-
-  extendMapBounds :->
-
-  adaptMapToBounds: ->
-    @fitBounds()


'repair' label should have higher priority than 'supply' label

--- views/components/ship-parts/utils.coffee
+++ views/components/ship-parts/utils.coffee
@@ -52,12 +52,12 @@
     # retreat status
     if shipId == escapeId || shipId == towId
       return status = 0
+    # repairing
+    else if shipId in _ndocks
+      return status = 1
     # supply
     else if miniFlag and (Math.min _ships[shipId].api_fuel / _ships[shipId].api_fuel_max * 100, _ships[shipId].api_bull / _ships[shipId].api_bull_max * 100) < 100
       return status = 6
-    # repairing
-    if shipId in _ndocks
-      return status = 1
     # special 1 locked phase 1
     else if _ships[shipId].api_sally_area == 1
       return status = 2


commands/run: comment useless instruction

--- lib/commands/run.coffee
+++ lib/commands/run.coffee
@@ -57,9 +57,10 @@
       process.exit()
     hostlength = 20
     for s in config.nodes then hostlength = Math.max(hostlength, (s.shortname or s.host).length+2)
-    multihost = params.hosts?.length isnt 1 and config.nodes.length isnt 1
+    # multihost = params.hosts?.length isnt 1 and config.nodes.length isnt 1
+    # Disable mono host mode
+    multihost = true
     times = {}
-    multihost = true
     init()
     for k, v of config.styles
       styles[k] = if typeof v is 'string' then colors[v] else v


Updated AMD tests

Updated AMD tests

--- test/core/test-latest-amd.coffee
+++ test/core/test-latest-amd.coffee
@@ -13,4 +13,4 @@
   # library and dependencies
   require ['underscore', 'underscore-awesomer', 'qunit_test_runner'], (_, __, runner) ->
     window._ = null # force each test to require dependencies synchronously
-    require ['./test'], -> runner.start()+    runner.start(); require ['./test'], ->


Allow hiding web interface via env variable.

--- src/app.coffee
+++ src/app.coffee
@@ -36,10 +36,12 @@
     console.log 'saved', filename
     res.end()

-app.get '/', (req, res, next) ->
+root = process.env.MINI_BREAKPAD_SERVER_ROOT ? ''
+
+app.get "/#{root}", (req, res, next) ->
   res.render 'index', title: 'Crash Reports', records: db.getAllRecords()

-app.get '/view/:id', (req, res, next) ->
+app.get "/#{root}/view/:id", (req, res, next) ->
   db.restoreRecord req.params.id, (err, record) ->
     return next err if err?


Fix rule deletion

Fix rule deletion

--- src/app.coffee
+++ src/app.coffee
@@ -76,7 +76,7 @@
 		# Create TETHER iptables chain (will silently fail if it already exists)
 		iptables.createChain 'nat', 'TETHER', ->
 			# Ensure no rules exist from an unclean shutdown
-			iptables.delete iptablesRules()[0], ->
+			iptables.delete { table: 'nat', rule: "PREROUTING -i tether -j TETHER"}, ->
 				iptables.flush 'nat', 'TETHER', ->
 					if !properties.connected
 						console.log("Trying to join wifi")


Add a fail-safe timeout in case the WebSocket doesn't emit a wserror event, and we also haven't heard from the server in a couple of minutes. This can happen in cases where a malfunctioning or misconfigured application-aware proxy/firewall sits between the client and server.

--- src/bot.coffee
+++ src/bot.coffee
@@ -42,6 +42,7 @@
     @lastHeartbeat   = Date.now()
     @lastActivity    = Date.now()
     @clientId        = Date.now() + '-0.59633534294921572'
+    @disconnectInterval = 120000
     @presenceInterval = 10000
     @_msgId          = 0
     @_cmds           = []
@@ -151,10 +152,24 @@
           # TODO: I don't like setInterval !
           if @_intervalId
             clearInterval(@_intervalId)
-          @_intervalId = setInterval(@updatePresence.bind(@), @presenceInterval)
+          @_intervalId = setInterval(@maintainPresence.bind(@), @presenceInterval)
           @emit 'ready'
       @callback()
       @_isConnected = true
+
+
+  maintainPresence: ->
+    if @lastHeartbeat > @lastActivity
+      activity = @lastHeartbeat
+    else
+      activity = @lastActivity
+    if @_isConnected and (Date.now() - activity) > @disconnectInterval
+      @_isAuthenticated = false
+      @_isConnected = false
+      @log 'No response from server; is there a proxy/firewall problem?'
+      @onError new Error 'No response from server'
+    else
+      @updatePresence()

   extractPacketJson: (packet) ->


Strings are not preserved when passed to the Ruby script.

--- src/cli.coffee
+++ src/cli.coffee
@@ -14,8 +14,8 @@

   cmd = synt_rb

-  cmd += " -c " + o.compare if o.compare
-  cmd += " -t " + o.to if o.to
+  cmd += " -c \"#{o.compare}\"" if o.compare
+  cmd += " -t \"#{o.to}\"" if o.to
   cmd += " -d " + o.threshold if o.threshold
   cmd += " -n " + o.ngram if o.ngram
   cmd += " -a " + o.algorithm if o.algorithm


Add output argument

Add output argument

--- src/cli.coffee
+++ src/cli.coffee
@@ -5,7 +5,8 @@

 module.exports = ->
   cli = optimist.describe('help', 'Show this message').alias('h', 'help')
-                .describe('scope', 'Scope name of the grammar to use').alias('s', 'scopeName')
+                .describe('scope', 'Scope name of the grammar to use').alias('s', 'scope')
+                .describe('output', 'File path to write HTML output to').alias('o', 'output')
   optimist.usage """
     Usage: highlights file

@@ -27,7 +28,11 @@
       return

     html = new Highlights().highlightSync({filePath, scopeName: cli.argv.scope})
-    console.log(html)
+    if cli.argv.output
+      outputPath = path.resolve(cli.argv.output)
+      fs.writeFileSync(outputPath, html)
+    else
+      console.log(html)
   else
     process.stdin.resume()
     process.stdin.setEncoding('utf8')


Log room messages at the debug level

Log room messages at the debug level

It looks like in this singular case, room logs are logged at the info level.  In the default hubot configuration, channel logs are ending up in the hubot log, which may not be desired from a privacy standpoint unless explicitly enabled by changing the log level.

--- src/irc.coffee
+++ src/irc.coffee
@@ -248,7 +248,7 @@

       user = self.createUser to, from
       if user.room
-        logger.info "#{to} <#{from}> #{message}"
+        logger.debug "#{to} <#{from}> #{message}"
       else
         unless message.indexOf(to) == 0
           message = "#{to}: #{message}"


Merging fix for NICK check and setting bots name so it responds to its own name

--- src/irc.coffee
+++ src/irc.coffee
@@ -74,7 +74,7 @@
     @bot.send command, strings...

   checkCanStart: ->
-    if not (process.env.HUBOT_IRC_NICK or @robot.name)
+    if not process.env.HUBOT_IRC_NICK or not @robot.name
       throw new Error("HUBOT_IRC_NICK is not defined; try: export HUBOT_IRC_NICK='mybot'")
     else if not process.env.HUBOT_IRC_ROOMS
       throw new Error("HUBOT_IRC_ROOMS is not defined; try: export HUBOT_IRC_ROOMS='#myroom'")


/to gets astrolin - no trailing slash required

--- web/app.coffee
+++ web/app.coffee
@@ -29,8 +29,8 @@
   @res.render "projects", layout: no

 # Project page
-router.param 'project', /(\w*)/
-router.get "/to/:project", (prj) ->
+router.param 'project', /\/?(\w*)/
+router.get "/to:project", (prj) ->
   prj = "astrolin" if prj is ''
   @res.render "project",
     title: prj


Formatting method with optional year

Formatting method with optional year

--- models/partner_show.coffee
+++ models/partner_show.coffee
@@ -138,6 +138,14 @@
   date: (attr) ->
     moment(@get attr)

+  isSameYear: (date) ->
+    date.year() is moment().year()
+
+  dateFormatted: (attr, formatString = 'MMM D') ->
+    date = @date attr
+    formatString += ', YYYY' unless @isSameYear(date)
+    date.format formatString
+
   fetchInstallShots: (callbacks) ->
     throw "You must pass a success callback" unless callbacks?.success? and _.isFunction(callbacks.success)
     @installShots = new Backbone.Collection [], { model: InstallShot }


This way?

This way?

--- lib/comic.coffee
+++ lib/comic.coffee
@@ -1,5 +1,5 @@
 rek = require 'rekuire'
-redis = rek 'redis-connection'
+redis = rek 'lib/redis-connection'

 # The Comic data model.
 module.exports = class Comic


fix indent with continued

fix indent with continued

--- lib/line_wrapper.coffee
+++ lib/line_wrapper.coffee
@@ -34,6 +34,8 @@
       @once 'line', =>
         @document.x -= indent
         @lineWidth += indent
+        if options.continued and not @continuedX
+          @continuedX = @indent
         @continuedX = 0 unless options.continued

     # handle left aligning last lines of paragraphs


add todo

add todo

--- lib/messageProxy.coffee
+++ lib/messageProxy.coffee
@@ -1,28 +1,19 @@
 do (window) ->
   messageProxy =
-    on: (sources, listener) ->
+    on: (name, listener) ->
+      return  unless typeof name is 'string'
       return  unless typeof listener is 'function'
-      sources = [ sources ]  if typeof sources is 'string'
-      if sources = _parseToArray(sources)
-        window.addEventListener "message", (event) ->
-          if event.origin in sources
-            listener(event.data)
-        , false
+      window.addEventListener "message", (event) ->
+        [eventName, msg] = event.data.split(_splitCode)
+        listener(msg)  if eventName is name
+      , false

-    emit: (targets, message) ->
-      if arguments.length is 1
-        message = arguments[0]
-        targets = [ '*' ]
-      return  unless typeof message is 'string' #todo 应支持任意obj
-      if targets = _parseToArray(targets)
-        window.postMessage message, target for target in targets
-      #window.frames[0].postMessage('123','http://localhost:30000')
+    emit: (name, message) ->
+      return  unless typeof name is 'string'
+      return  unless typeof message is 'string' #todo support obj
+      for frame in window.frames
+        frame.postMessage name + _splitCode + message, '*' #todo 若要需要防止恶意iframe监听消息, 可考虑加密msg内容

-  _isArray = (obj) ->
-    Object.prototype.toString.call(obj) is '[object Array]'
-
-  _parseToArray = (obj) ->
-    obj = [ obj ] if typeof handler is 'string'
-    if _isArray(obj) then obj else false
+  _splitCode = '|messageProxy|' #todo 使用转为object再序列化/反序列化方式将更安全

   window.messageProxy = window.messageProxy || messageProxy


Text nodes should be updated with `nodeValue`

Text nodes do not necessarily support `innerText` or `textContent` in
all browsers. The most correct and cross-platform compatible way is by
using the `nodeValue` attribute.

--- lib/elvis.coffee
+++ lib/elvis.coffee
@@ -252,8 +252,11 @@
             el.appendChild(value)
           else if value instanceof Array
             exports.appendChildren(el, value)
+        else if attr is 'text' and isText(el)
+          el.nodeValue = value
         else
           el[directAttr] = value
+  null

 class SafeString extends exports.Element


Fixes the problem where the link to the CC dashboard doesn't get highlighted when it's the current page.  Added a separate cc_dashboard entry in current-user.coffee's ROUTES map that links to the correct react route.

--- src/flux/current-user.coffee
+++ src/flux/current-user.coffee
@@ -29,8 +29,16 @@
 ROUTES =
   dashboard:
     label: 'Dashboard'
+    allowedForCourse: (course) -> not course?.is_concept_coach is true
     roles:
       teacher: 'taskplans'
+      student: 'viewStudentDashboard'
+      default: 'app'
+  cc_dashboard:
+    label: 'Dashboard'
+    allowedForCourse: (course) -> course?.is_concept_coach is true
+    roles:
+      teacher: 'cc-dashboard'
       student: 'viewStudentDashboard'
       default: 'app'
   guide:
@@ -152,7 +160,11 @@

     getDashboardRoute: (courseId, silent = true) ->
       menuRole = @_getCourseRole(courseId, silent)
-      @_getRouteByRole('dashboard', menuRole)
+      course = CourseStore.get(courseId)
+      if course?.is_concept_coach
+        @_getRouteByRole('cc_dashboard', menuRole)
+      else
+        @_getRouteByRole('dashboard', menuRole)

     getHelpLink: (courseId) ->
       course = CourseStore.get(courseId)


fix typo

fix typo

--- src/fs_utils/generate.coffee
+++ src/fs_utils/generate.coffee
@@ -73,7 +73,7 @@
   root.prepend definition(path, root.sourceContents) if type is 'javascript'
   aliases?.forEach (alias) ->
     key = Object.keys(alias)[0]
-    root.add "require.alias('#{key}', '#{alias[key]});"
+    root.add "require.alias('#{key}', '#{alias[key]}');"

   root.toStringWithSourceMap file: path


get rid of not-necessary margin above circle avatar letter

--- src/smc-webapp/other-users.cjsx
+++ src/smc-webapp/other-users.cjsx
@@ -48,7 +48,6 @@
     display      : 'block'
     borderRadius : '50%'
     fontFamily   : 'sans-serif'
-    marginTop    : '2px'

 exports.Avatar = Avatar = rclass
     displayName: "Avatar"


Only add keybindings when command is present

--- src/space-pen-extensions.coffee
+++ src/space-pen-extensions.coffee
@@ -39,10 +39,10 @@

   tooltipOptions = {title: tooltipOptions} if _.isString(tooltipOptions)

-  bindings = if commandElement
-    atom.keymap.keyBindingsForCommandMatchingElement(command, commandElement)
-  else
-    atom.keymap.keyBindingsForCommand(command)
+  if commandElement
+    bindings = atom.keymap.keyBindingsForCommandMatchingElement(command, commandElement)
+  else if command
+    bindings = atom.keymap.keyBindingsForCommand(command)

   tooltipOptions.title = "#{tooltipOptions.title} #{getKeystroke(bindings)}"


Fix upload js error setting name

Fix upload js error setting name

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -592,7 +592,7 @@
         router.http(router.controllers.AssetApi.uploadStart, slot.volume.id,
             filename: file.name
             size: file.size
-          ).then (res) ->
+          ).then (res) =>
             file.uniqueIdentifier = res.data
             file.resume()
             ### jshint ignore:start ###


Remove unused code.

Remove unused code.

--- app/client/js/firebase.coffee
+++ app/client/js/firebase.coffee
@@ -220,99 +220,3 @@
         uid: uid
         displayName: displayName
         message: message
-
-  # ###*
-  #   @param {app.songs.Song} song
-  # ###
-  # publishSong: (song) ->
-  #   json = song.toJson()
-  #   json.publisher = @userStore.user.uid
-  #   url = "#{json.urlArtist}/#{json.urlName}"
-  #
-  #   payload =
-  #     'byUpdated': @Firebase_.ServerValue.TIMESTAMP
-  #     'byUrl': url
-  #     'byName': este.string.removeDiacritics json.name.toLowerCase()
-  #     'byArtist': este.string.removeDiacritics json.artist.toLowerCase()
-  #
-  #   promises = for path, priority of payload
-  #     new goog.Promise (resolve, reject) =>
-  #       @songsRef
-  #         .child path
-  #         .child json.id
-  #         .setWithPriority json, priority, (e) ->
-  #           if e then reject e else resolve()
-  #
-  #   goog.Promise.all promises
-  #     .then (value) =>
-  #       @userStore.addPublishedSong json.id, url
-  #     .thenCatch (reason) =>
-  #       alert reason
-  #
-  # ###*
-  #   @param {app.songs.Song} song
-  # ###
-  # unpublishSong: (song) ->
-  #   json = song.toJson()
-  #   paths = ['byUpdated', 'byUrl', 'byName', 'byArtist']
-  #
-  #   promises = for path in paths
-  #     new goog.Promise (resolve, reject) =>
-  #       @songsRef
-  #         .child path
-  #         .child json.id
-  #         .remove (e) ->
-  #           if e then reject e else resolve()
-  #
-  #   goog.Promise.all promises
-  #     .then (value) =>
-  #       @userStore.removePublishedSong json.id
-  #     .thenCatch (reason) =>
-  #       alert reason
-
-  ###*
-    @param {string} url
-    @return {goog.Promise}
-  ###
-  getSongByUrl: (url) ->
-    new goog.Promise (resolve, reject) =>
-      @songsRef
-        .child 'byUrl'
-        .equalTo url
-        .once 'value', (snapshot) ->
-          resolve snapshot.val()
-        , (err) ->
-          reject err
-
-  ###*
-    @return {goog.Promise}
-  ###
-  getLastTenSongs: ->
-    new goog.Promise (resolve, reject) =>
-      @songsRef
-        # TODO: Just last ten songs.
-        .child 'byUpdated'
-        .once 'value', (snapshot) ->
-          resolve snapshot.val()
-        , (err) ->
-          reject err
-
-  ###*
-    @param {este.Route} route
-    @param {este.Routes} routes
-    @param {Object} params
-    @return {goog.Promise}
-  ###
-  loadByRoute: (route, routes, params) ->
-    switch route
-      when routes.song
-        return @getSongByUrl params.urlArtist + '/' + params.urlName
-          .then (value) =>
-            throw goog.net.HttpStatus.NOT_FOUND if !value
-            @songsStore.fromJson songsByUrl: value
-      when routes.songs
-        return @getLastTenSongs()
-          .then (value) =>
-            throw goog.net.HttpStatus.NOT_FOUND if !value
-            @songsStore.fromJson lastTenSongs: value
-    null


合并来自 master 的冲突

合并来自 master 的冲突

--- lib/model.coffee
+++ lib/model.coffee
@@ -156,7 +156,7 @@
   return unless params.includes
   ret = _.filter(params.includes.split(','), (x) -> includes[x])
   return if ret.length is 0
-  _.map(ret, (x) -> includes[x])
+  _.map(ret, (x) -> _.clone(includes[x]))

 ###
 # 处理分页参数


put a 5 second timeout in getting redis lock

--- app/coffee/LockManager.coffee
+++ app/coffee/LockManager.coffee
@@ -13,6 +13,8 @@
 PID = process.pid
 RND = crypto.randomBytes(4).toString('hex')
 COUNT = 0
+
+MAX_REDIS_REQUEST_LENGTH = 5000 # 5 seconds

 module.exports = LockManager =
 	LOCK_TEST_INTERVAL: 50 # 50ms between each test of the lock
@@ -37,8 +39,11 @@
 			return callback(err) if err?
 			if gotLock == "OK"
 				metrics.inc "doc-not-blocking"
-				profile.log("got lock").end()
-				callback err, true, lockValue
+				timeTaken = profile.log("got lock").end()
+				if timeTaken > MAX_REDIS_REQUEST_LENGTH
+					callback err, false # took too long to get the lock, bail out
+				else
+					callback err, true, lockValue
 			else
 				metrics.inc "doc-blocking"
 				profile.log("doc is locked").end()


memoize checksum calc

memoize checksum calc

--- lib/model.coffee
+++ lib/model.coffee
@@ -1,14 +1,14 @@
 events   = require 'events'
-checksum = require 'keycode-checksum'
 defined  = require 'defined'
 _        = require 'lodash'
+checksum = require 'keycode-checksum'
+checksum = _.memoize checksum

 module.exports =

 class Model extends events.EventEmitter

   constructor: (value={}) ->
-
     throw new Error 'missing name'  unless value.name

     @name = value.name
@@ -52,8 +52,6 @@
   getMacKeys:     -> @binding[1]

-  # XXX: cache
-
   getWinChecksum: -> checksum @binding[0]

   getMacChecksum: -> checksum @binding[1]


Have karma.conf use testing platform instead of solo.

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -5,13 +5,13 @@
 files = {}

 files.source   = snockets.scan('src/batman.coffee', async: false).getChain('src/batman.coffee')
-files.platform = snockets.scan('src/platform/solo.coffee', async: false).getChain('src/platform/solo.coffee')
+files.platform = snockets.scan('src/platform/testing.coffee', async: false).getChain('src/platform/testing.coffee')
 files.helpers  = snockets.scan('tests/batman/test_requires.coffee', async: false).getChain('tests/batman/test_requires.coffee')
 files.tests    = snockets.scan('tests/batman/tests.coffee', async: false).getChain('tests/batman/tests.coffee')
 files.docs     = snockets.scan('docs/docs.coffee', async: false).getChain('docs/docs.coffee')

 files.lib = [
-  'lib/es5-shim.js'
+  'lib/polyfills/es5-shim.js'
   'tests/lib/json2.js',
   'tests/lib/jquery.js',
   'tests/lib/sinon.js'


Update .tab-bar to use custom element tabs-bar

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -8,8 +8,13 @@

 module.exports =
 class TabBarView extends View
-  @content: ->
-    @ul tabindex: -1, class: "list-inline tab-bar inset-panel"
+  constructor: ->
+    # space-pen does not support custom elements, so this is a hack
+    rootElement = document.createElement('tabs-bar')
+    rootElement.setAttribute('tabindex', -1)
+    rootElement.classList.add('list-inline', 'tab-bar', 'inset-panel')
+    @element = rootElement
+    super

   initialize: (@pane) ->
     @subscriptions = new CompositeDisposable


fix for when indentation of first line matters

--- lib/parse.coffee
+++ lib/parse.coffee
@@ -16,7 +16,7 @@
     code.replace(/'\.\//g, "'#{@escape(dir)}/")
         .replace(/'\.\.\//g, "'#{@escape(dir)}/../")

-  insertHeader: (header, code) -> "#{header}\n(#{code}\n)"
+  insertHeader: (header, code) -> "#{header}\n(\n#{code}\n)"

   walkBack: (ls, i, indent) ->
     r = new RegExp "^" + indent + "[^\\s]"
@@ -51,7 +51,7 @@
     isClass: isClass

   parsekey: (code) ->
-    match = code.match /^\s*@?(\w*):\s*([^]*)/
+    match = code.match /^\s*@?(\w*):\s*?\n?([^]*)/
     isStatic = code.match(/^\s*@/)?
     return {code: code} unless match?
     [_, key, code] = match


Push all nodes instead of looping

Push all nodes instead of looping

--- src/trix/views/text_view.coffee
+++ src/trix/views/text_view.coffee
@@ -12,5 +12,5 @@
     objects = Trix.ObjectGroup.groupObjects(pieces)
     for object in objects
       view = @findOrCreateCachedChildView(Trix.PieceView, object, {@textConfig})
-      nodes.push(node) for node in view.getNodes()
+      nodes.push(view.getNodes()...)
     nodes


Just in case

Just in case

--- lib/proxy.coffee
+++ lib/proxy.coffee
@@ -1,4 +1,27 @@

+mongoose = require('mongoose');
+mongoose.connect(process.env["MONGO_URL"])
+
+printMe = (arg) ->
+    console.log "print", arg
+
+
+
+MongoClient = require('mongodb').MongoClient
+MedBookDB = null
+MongoClient.connect process.env["MONGO_URL"], (err, db) ->
+    if err
+        throw err
+    MedBookDB = db
+
+lookupUser = (email, good, bad) ->
+    query = {"emails.address":email}
+    users = MedBookDB.collection('users');
+    users.find(query).toArray (err, docs) ->
+      console.dir(docs)
+      if docs.length > 0
+          next()
+      else

 httpProxy = require('http-proxy')
@@ -23,8 +46,10 @@
   # return res.redirect('/authproxy/google')

 testVerify = (accessToken, refreshToken, profile, done) ->
-  process.nextTick ->
-    return done(null, profile)
+  console.log "testVerify", accessToken, refreshToken, profile
+  lookupUser profile.email,
+      process.nextTick ->
+        return done(null, profile)

 randomUpstream = (upstreams) ->
   upstreams[Math.floor((Math.random()*upstreams.length))]


Return the rcb object.

Return the rcb object.

--- app/javascripts/breast.coffee
+++ app/javascripts/breast.coffee
@@ -135,11 +135,11 @@
         else
           return 3

-      # If the RCB object exists, return it extended with the index and class.
+      # If the RCB object exists, then return it extended with the
+      # index and class properties.
       rcb = tumor.rcb
       if rcb?
         rcb.index = rcbIndex(tumor.extent, tumor.rcb)
         rcb.class = rcbClass(rcb.index)
-      else
-        return null
+        rcb
   ]


proper route

proper route

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -35,7 +35,7 @@

   # Add up front middleware such as redirecting to Martsy and CORS support for login
   app.use redirectMobile
-  app.use '/users/sign_in', cors()
+  app.use '/force/users/sign_in', cors()

   # Setup Artsy XAPP middleware
   app.use artsyXappMiddlware(


no cache for now

no cache for now

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -36,7 +36,7 @@

   Backbone.sync = require "backbone-super-sync"
   console.log 'REDIS_URL', REDIS_URL
-  backboneCacheSync(Backbone.sync, REDIS_URL, 3000, NODE_ENV) if REDIS_URL
+  # backboneCacheSync(Backbone.sync, REDIS_URL, 3000, NODE_ENV) if REDIS_URL

   # Mount sharify
   app.use sharify


pendingGuestCreation is removed

pendingGuestCreation is removed

--- atom/renderer/lib/web-view/web-view.coffee
+++ atom/renderer/lib/web-view/web-view.coffee
@@ -13,7 +13,6 @@
   constructor: (@webviewNode) ->
     v8Util.setHiddenValue @webviewNode, 'internal', this
     @attached = false
-    @pendingGuestCreation = false
     @elementAttached = false

     @beforeFirstNavigation = true
@@ -122,16 +121,10 @@
       @dispatchEvent webViewEvent

   createGuest: ->
-    return if @pendingGuestCreation
     params =
       storagePartitionId: @attributes[webViewConstants.ATTRIBUTE_PARTITION].getValue()
     guestViewInternal.createGuest 'webview', params, (guestInstanceId) =>
-      @pendingGuestCreation = false
-      unless @elementAttached
-        guestViewInternal.destroyGuest guestInstanceId
-        return
       @attachWindow guestInstanceId
-    @pendingGuestCreation = true

   dispatchEvent: (webViewEvent) ->
     @webviewNode.dispatchEvent webViewEvent


Added: permission to proxy to track application usage

--- lib/token.coffee
+++ lib/token.coffee
@@ -123,6 +123,7 @@
         "user": "authorized"
         "cozyinstance": "authorized"
         "device": "authorized"
+        "usetracker": "authorized"
     callback null


Utils is now parse.coffee.

Utils is now parse.coffee.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -1,24 +0,0 @@
-###
-	* @param {string} info
-###
-exports.parse_info = (info) ->
-	to_item = (line) ->
-		line = line.replace(/^(.+)=/, '"$1"=') if line[0] != '"'
-		line = line.replace(/^(".+")=/, '$1:')
-
-		return JSON.parse("{#{line}}")
-
-	to_value = (value) ->
-		return switch value
-			when 'none' then null
-			when 'on' then true
-			when 'off' then false
-			else value
-
-	to_object = (previous, current) ->
-		previous[key] = to_value(val) for key, val of current if current
-
-		return previous
-
-	return info.split('\n').map(to_item).reduce(to_object)
-


reset facebook for heroku

reset facebook for heroku

--- app/assets/javascripts/facebook.js.coffee
+++ app/assets/javascripts/facebook.js.coffee
@@ -8,11 +8,11 @@

 window.fbAsyncInit = ->
-  # localhost, testing
-  FB.init(appId: '1740995652791642', cookie: true)
+  # # localhost, testing
+  # FB.init(appId: '1740995652791642', cookie: true)

-  # # Heroku
-  # FB.init(appId: '1737505913140616', cookie: true)
+  # Heroku
+  FB.init(appId: '1737505913140616', cookie: true)

   $('#sign_in').click (e) ->
     e.preventDefault()


More iOS hacks

More iOS hacks

--- app/assets/javascripts/masthead.js.coffee
+++ app/assets/javascripts/masthead.js.coffee
@@ -20,8 +20,15 @@

     # Close on click outside the searchbox
     $('body > *').not('#malmo-masthead').one 'click', (event) ->
-      # event.preventDefault()
       hideSearch()
+
+    if $("body.touch").length
+      times = 0
+      i = setInterval ->
+        window.scrollTo(0, $mastheadSearch.offset().top)
+        if times++ > 100
+          clearInterval(i)
+      , 5

   $("#search-trigger").click (event) ->
     if $mastheadSearch.is(":hidden") then showSearch() else hideSearch()
@@ -30,3 +37,14 @@
   $(document).on 'keyup', (event) ->
     if event.which is 27
       hideSearch()
+
+
+  $mastheadSearch.find(".q").focus ->
+    if $("body.touch").length and not $mastheadSearch.hasClass("boxed")
+      $malmoMasthead = $("#malmo-masthead")
+      times = 0
+      i = setInterval ->
+        window.scrollTo(0, $malmoMasthead.offset().top)
+        if times++ > 100
+          clearInterval(i)
+      , 5


Change text of archive toggle on toggle, and flash to indicate change.

--- app/assets/javascripts/postings.js.coffee
+++ app/assets/javascripts/postings.js.coffee
@@ -86,5 +86,10 @@

     # Fade out posting listing when archived
     $(".archive-toggle").on "ajax:complete", ->
+      $(this).fadeOut(100).fadeIn(300)
+      if $(this).find("span").text() is "Unarchive"
+        $(this).find("span").text "Archive"
+      else
+        $(this).find("span").text "Unarchive"
       $("##{$(this).data('id')}").fadeOut 500, =>
         $("##{$(this).data('id')}").remove()


Fix modal handler.

Fix modal handler.

--- app/assets/javascripts/undersky.js.coffee
+++ app/assets/javascripts/undersky.js.coffee
@@ -271,7 +271,7 @@
         comments.find('.comments-button.delete-comment').remove()
         form.append(comments)
       container.append(form)
-      container.modal(show: true)
+      container.modal('show')
       container.bind('hidden', -> container.remove())
       container.find('textarea').focus()

@@ -301,7 +301,7 @@

     @hide: (e) ->
       e && e.preventDefault()
-      $(this).parents('.modal.create-comment').modal(show: false)
+      $(this).parents('.modal.create-comment').modal('hide')

     @handler:
       beforeSend: (e) ->
@@ -311,7 +311,7 @@
         self = $(this)
         panel = do ->
           container = self.parents('.modal.create-comment')
-          container.modal(show: false)
+          container.modal('hide')
           $('[data-id="' + container.data('id') + '"]')
         return if panel.size() == 0
         container = panel.find('.comments-data')


make Regex accept more URLs and full paths


refresh projects after import

refresh projects after import

--- app/coffee/modules/common/importer.coffee
+++ app/coffee/modules/common/importer.coffee
@@ -92,17 +92,19 @@
             loader = $confirm.loader($translate.instant("PROJECT.IMPORT.UPLOADING_FILE"))

             onSuccess = (result) ->
-                loader.stop()
-                if result.status == 202 # Async mode
-                    title = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_TITLE")
-                    message = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_MESSAGE")
-                    $confirm.success(title, message)
+                currentUserService.loadProjects().then () ->
+                    loader.stop()

-                else # result.status == 201 # Sync mode
-                    ctx = {project: result.data.slug}
-                    $location.path($navUrls.resolve("project-admin-project-profile-details", ctx))
-                    msg = $translate.instant("PROJECT.IMPORT.SYNC_SUCCESS")
-                    $confirm.notify("success", msg)
+                    if result.status == 202 # Async mode
+                        title = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_TITLE")
+                        message = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_MESSAGE")
+                        $confirm.success(title, message)
+
+                    else # result.status == 201 # Sync mode
+                        ctx = {project: result.data.slug}
+                        $location.path($navUrls.resolve("project-admin-project-profile-details", ctx))
+                        msg = $translate.instant("PROJECT.IMPORT.SYNC_SUCCESS")
+                        $confirm.notify("success", msg)

             onError = (result) ->
                 $tgAuth.refresh().then () ->


fixing indentation

fixing indentation

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -69,8 +69,8 @@
     # Update page title
     updateTitle: () ->
       if @pageTitle
-       @addCanonicalMetaDataToDerivedCopies()
-       document.title = settings.titlePrefix + @pageTitle
+        @addCanonicalMetaDataToDerivedCopies()
+        document.title = settings.titlePrefix + @pageTitle

     getTemplate: () -> @template?(@getTemplateData()) or @template

@@ -90,17 +90,17 @@
       return data

     addCanonicalMetaDataToDerivedCopies: () ->
-       parentId = @getTemplateData().parentId
-       headTag = document.getElementsByTagName('head')[0]
-       links = headTag.querySelectorAll('link')
-       numberOfLinks = links.length
-       if numberOfLinks
+      parentId = @getTemplateData().parentId
+      headTag = document.getElementsByTagName('head')[0]
+      links = headTag.querySelectorAll('link')
+      numberOfLinks = links.length
+      if numberOfLinks
         i = 0
-       while i < numberOfLinks
-        el = links[i]
-        el.parentElement.removeChild el  if el.getAttribute('rel') is 'canonical'
+        while i < numberOfLinks
+         el = links[i]
+         el.parentElement.removeChild el  if el.getAttribute('rel') is 'canonical'
         i++
-       if parentId?
+      if parentId?
         canonicalLink = document.createElement('link')
         canonicalLink.rel = 'canonical'
         canonicalLink.href = location.hostname + '/' + parentId


Add healthcheck route

Add healthcheck route

--- server/routes/index.coffee
+++ server/routes/index.coffee
@@ -1,2 +1,36 @@
 module.exports.setup = (app) ->
-  app.get('/db/products', require('./db/product').get)+  app.get '/db/products', require('./db/product').get
+
+  app.get '/healthcheck', (req, res) ->
+    try
+      async = require 'async'
+      User = require '../users/User'
+      async.waterfall [
+        (callback) ->
+          User.find({}).limit(1).exec(callback)
+        , (last, callback) ->
+          return("No users found") unless callback.length > 0
+          User.findOne(slug: 'healthcheck').exec(callback)
+        , (hcuser, callback) ->
+          # Create health check user if it doesnt exist
+          return callback(null, hcuser) if hcuser
+          user = new User
+            anonymous: false
+            name: 'healthcheck'
+            nameLower: 'healthcheck'
+            slug: 'healthcheck'
+            email: 'rob+healthcheck@codecombat.com'
+            emailLower: 'rob+healthcheck@codecombat.com'
+          user.set 'testGroupNumber', Math.floor(Math.random() * 256)  # also in app/core/auth
+          user.save (err) ->
+            return callback(err) if err
+            callback(null, user)
+
+        , (hcuser, callback) ->
+          activity = hcuser.trackActivity 'healthcheck', 1
+          hcuser.update {activity: activity}, callback
+      ], (err) ->
+        return res.status(500).send(err.toString()) if err
+        res.send("OK")
+    catch error
+      res.status(500).send(error.toString())


comment

comment

--- public/scripts/backbone-schedule/collections/friends.coffee
+++ public/scripts/backbone-schedule/collections/friends.coffee
@@ -17,7 +17,7 @@

     initialize: ->
       @.bind 'all', () ->
-        console.log arguments #DEBUG
+        console.log arguments
       @.bind 'add', (friend) ->
         @addFriend friend
       @.bind 'remove', (friend) ->


Cleaned up web prelude

Cleaned up web prelude

--- src/client/web-prelude.coffee
+++ src/client/web-prelude.coffee
@@ -1,8 +1,7 @@
 # This file is included at the top of the compiled client-side javascript

 # This way all the modules can add stuff to exports, and for the web client they'll all get exported.
-window.sharejs = exports =
-  'version': '0.6.0'
+window.sharejs = exports = version: '0.7.0'

 # This is compiled out when compiled with uglifyjs, but its important for the share.uncompressed.js.
 #


forgot to add heights/widths to gridplots

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -158,6 +158,9 @@
           "left:#{xpos}px; top:#{ypos}px")
         plot_wrapper.append(view.$el)
         @$el.append(plot_wrapper)
+    height = @viewstate.get('outerheight')
+    width = @viewstate.get('outerwidth')
+    @$el.attr('style', "height:#{height}px;width:#{width}px")
     @render_end()

 class ActiveToolManager


Remove initial animation- drop delay

Remove initial animation- drop delay

--- src/coffee/controllers.coffee
+++ src/coffee/controllers.coffee
@@ -1,7 +1,6 @@

 eob_controllers = angular.module 'eob.controllers', []

-DROP_DELAY = 0
 BERLIN_POS = new google.maps.LatLng 52.5170423, 13.4018519
 MOBILE_BP = 760
 MENU_BP = 600
@@ -323,32 +322,23 @@
     mc = null
     markers = []

-    addMarkersFrom = (index) ->
-        index = 0 if index is null
-
-        if index >= 0 and index < $scope.places.length
-            place = $scope.places[index]
-            eob_imgCache.load(_.pick(MARKER_ICONS, place.foodtype)).then ->
+    addMarkersToMap = () ->
+        eob_imgCache.load(MARKER_ICONS).then ->
+            $scope.places.forEach (place, index) ->
                 image =
                     url: MARKER_ICONS[place.foodtype].url
                     size: new google.maps.Size(70, 85)
                     scaledSize: new google.maps.Size(70, 85)
-
                 marker = new google.maps.Marker
                     position: new google.maps.LatLng place.lat, place.lng
                     map: map
                     title: place.name
                     icon: image
                     animation: google.maps.Animation.DROP
-
                 markers.push marker
                 google.maps.event.addListener marker, "click", ->
                     $scope.openPlace place.slug
-                    return
-
-                setTimeout _.partial(addMarkersFrom, index + 1), DROP_DELAY
-                return
-        else if index is $scope.places.length
+
             $scope.findMe false
             mc = new MarkerClusterer(map, markers, mcOptions)
             mc.setCalculator (markers, styles) ->
@@ -399,7 +389,7 @@

     # do something only the first time the map is loaded
     google.maps.event.addListenerOnce map, 'tilesloaded', ->
-        eob_data.placesPromise.then _.partial(addMarkersFrom, 0)
+        eob_data.placesPromise.then addMarkersToMap

     return


Need to update the bounds calculation with the new transformation nodes

--- src/compile.asm.bounds.coffee
+++ src/compile.asm.bounds.coffee
@@ -53,6 +53,14 @@
       node.bounds[0][i] += node.attr.offset[i] for i in [0..2]
       node.bounds[1][i] += node.attr.offset[i] for i in [0..2]
       stack[0].nodes.push node
+    rotate: (stack, node, flags) ->
+      node.bounds = collectChildren node.nodes, flags
+      # TODO...
+      stack[0].nodes.push node
+    scale: (stack, node, flags) ->
+      node.bounds = collectChildren node.nodes, flags
+      # TODO...
+      stack[0].nodes.push node
     halfspace: (stack, node, flags) ->
       node.bounds = [[-Infinity, -Infinity, -Infinity], [Infinity, Infinity, Infinity]]
       node.bounds[if flags.invert then 1 else 0][node.attr.axis] = node.attr.val


Missing comma in call to _trigger_event

Missing comma in call to _trigger_event

--- bokehjs/src/coffee/common/ui_events.coffee
+++ bokehjs/src/coffee/common/ui_events.coffee
@@ -98,7 +98,7 @@
     base_event_type = event_type.split(":")[0]
     gestures = tm.get('gestures')
     active = gestures[base_event_type].active
-    @_trigger_event event_type, active e
+    @_trigger_event event_type, active, e

   _trigger_event: (event_type, active, e)->
     if active?


specify expected errors

specify expected errors

--- workers/social/lib/social/models/user/index.test.coffee
+++ workers/social/lib/social/models/user/index.test.coffee
@@ -639,10 +639,10 @@

         ->
           # sending a different email address, username will remain same(duplicate)
-          userFormData.email = 'kodingtestuser@koding.com'
+          userFormData.email = generateRandomEmail()

           JUser.convert client, userFormData, (err) ->
-            expect(err.message).to.exist
+            expect(err.message).to.be.equal 'Errors were encountered during validation'
             queue.next()

         -> done()
@@ -663,10 +663,10 @@

         ->
           # sending a different username, email address will remain same(duplicate)
-          userFormData.username = 'kodingtestuser'
+          userFormData.username = generateRandomUsername()

           JUser.convert client, userFormData, (err) ->
-            expect(err.message).to.exist
+            expect(err?.message).to.be.equal 'Email is already in use!'
             queue.next()

         -> done()


Default the last question to 'incomplete' #63

--- task-defaults.coffee
+++ task-defaults.coffee
@@ -4,4 +4,5 @@
 {decisionTree} = classifyPage

 classifyPage.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task}})->
-  task.reset 'yes' if task.key is 'animalsPresent'+  task.reset 'yes' if task.key is 'animalsPresent'
+  task.reset 'incomplete' if task.key is 'allPenguinsMarked'


refactored ide.coffee

refactored ide.coffee

--- client/ide.coffee
+++ client/ide.coffee
@@ -2,10 +2,7 @@
   Dyalog.idePage = ->
     {socket} = Dyalog
     $('body').html """
-

-
-        #{Dyalog.lbarHTML}
-

+
#{Dyalog.lbarHTML}

@@ -13,79 +10,67 @@

     """

-    wins =
+    wins = # mapping between window ids and widget instances (Dyalog.Session or Dyalog.Editor)
       0: session = Dyalog.Session $('.ui-layout-center'),
         edit: (s, i) -> socket.emit 'Edit', win: 0, pos: i, text: s
         exec: (lines, trace) -> (for s in lines then socket.emit 'Execute', text: (s + '\n'), trace: trace); return
         autocomplete: (s, i) -> socket.emit 'Autocomplete', line: s, pos: i, token: 0

-    socket.on 'UpdateDisplayName', ({displayName}) -> $('title').text displayName
-    socket.on 'EchoInput', ({input}) -> session.add input
-    socket.on 'AppendSessionOutput', ({result}) -> session.add result
-    socket.on 'NotAtInputPrompt', -> session.prompt null
-    socket.on 'AtInputPrompt', ({why}) -> session.prompt why
-    socket.on 'FocusWindow', ({win}) -> wins[win].focus()
-    socket.on 'WindowTypeChanged', ({win, tracer}) -> wins[win].setDebugger tracer
-
-    socket.on 'open', (name, text, token, bugger, breakpoints) ->
-      layout.open dir = if bugger then 'south' else 'east'
-      wins[token] = Dyalog.Editor '.ui-layout-' + dir,
-        debugger: bugger
-        save: (s, bs)   -> socket.emit 'SaveChanges',    win: token, text: s, attributes: stop: bs
-        close:          -> socket.emit 'CloseWindow',    win: token
-        over:           -> socket.emit 'RunCurrentLine', win: token
-        into:           -> socket.emit 'StepInto',       win: token
-        back:           -> socket.emit 'TraceBackward',  win: token
-        skip:           -> socket.emit 'TraceForward',   win: token
-        continueTrace:  -> socket.emit 'ContinueTrace',  win: token
-        continueExec:   -> socket.emit 'Continue',       win: token
-        restartThreads: -> socket.emit 'RestartThreads', win: token
-        edit:    (s, p) -> socket.emit 'Edit',           win: token, text: s, pos: p
-        interrupt:      -> socket.emit 'WeakInterrupt'
-        cutback:        -> socket.emit 'Cutback',        win: token
-        autocomplete: (s, i) -> socket.emit 'autocomplete', s, i, token
-      wins[token].open name, text, breakpoints
-      session.scrollCursorIntoView()
-
-    socket.on 'update', (name, text, token, bugger, breakpoints) ->
-      wins[token].open name, text, breakpoints
-      session.scrollCursorIntoView()
-
-    socket.on 'CloseWindow', ({win}) ->
-      $c = $ wins[win].getContainer()
-      for d in ['east', 'south'] when $c.hasClass 'ui-layout-' + d then layout.close d
-      delete wins[win]
-      session.scrollCursorIntoView()
-
-    socket.on 'autocomplete', (token, skip, options) -> wins[token].autocomplete skip, options
-    socket.on 'highlight', (win, line) -> wins[win].highlight line
-    socket.on 'end', -> alert 'Interpreter disconnected'
+    socket
+      .on 'UpdateDisplayName', ({displayName}) -> $('title').text displayName
+      .on 'EchoInput', ({input}) -> session.add input
+      .on 'AppendSessionOutput', ({result}) -> session.add result
+      .on 'NotAtInputPrompt', -> session.prompt null
+      .on 'AtInputPrompt', ({why}) -> session.prompt why
+      .on 'FocusWindow', ({win}) -> wins[win].focus()
+      .on 'WindowTypeChanged', ({win, tracer}) -> wins[win].setDebugger tracer
+      .on 'autocomplete', (token, skip, options) -> wins[token].autocomplete skip, options
+      .on 'highlight', (win, line) -> wins[win].highlight line
+      .on 'end', -> alert 'Interpreter disconnected'
+      .on 'update', (name, text, token, bugger, breakpoints) -> wins[token].open name, text, breakpoints; session.scrollCursorIntoView()
+      .on 'CloseWindow', ({win}) ->
+        $c = $ wins[win].getContainer(); delete wins[win]
+        for d in ['east', 'south'] when $c.hasClass 'ui-layout-' + d then layout.close d
+        session.scrollCursorIntoView()
+      .on 'open', (name, text, token, bugger, breakpoints) ->
+        layout.open dir = if bugger then 'south' else 'east'
+        wins[token] = Dyalog.Editor '.ui-layout-' + dir,
+          debugger: bugger
+          save: (s, bs)   -> socket.emit 'SaveChanges',    win: token, text: s, attributes: stop: bs
+          close:          -> socket.emit 'CloseWindow',    win: token
+          over:           -> socket.emit 'RunCurrentLine', win: token
+          into:           -> socket.emit 'StepInto',       win: token
+          back:           -> socket.emit 'TraceBackward',  win: token
+          skip:           -> socket.emit 'TraceForward',   win: token
+          continueTrace:  -> socket.emit 'ContinueTrace',  win: token
+          continueExec:   -> socket.emit 'Continue',       win: token
+          restartThreads: -> socket.emit 'RestartThreads', win: token
+          edit:    (s, p) -> socket.emit 'Edit',           win: token, text: s, pos: p
+          interrupt:      -> socket.emit 'WeakInterrupt'
+          cutback:        -> socket.emit 'Cutback',        win: token
+          autocomplete: (s, i) -> socket.emit 'autocomplete', s, i, token
+        wins[token].open name, text, breakpoints
+        session.scrollCursorIntoView()

     # language bar
-    $('.lbar').on 'mousedown', -> false
-    $('.lbar b').on 'mousedown', (e) -> (for _, x of wins when x.hasFocus() then x.insert $(e.target).text()); false
     $('.lbar-close').on 'click', -> layout.close 'north'; false
+    $tip = $ '.lbar-tip'; $tipDesc = $ '.lbar-tip-desc'; $tipText = $ '.lbar-tip-text'; $tipTriangle = $ '.lbar-tip-triangle'
     ttid = null # tooltip timeout id
-    $tip = $ '.lbar-tip'; $tipDesc = $ '.lbar-tip-desc'; $tipText = $ '.lbar-tip-text'; $tipTriangle = $ '.lbar-tip-triangle'
-    $('.lbar b').on
-      mouseout: -> clearTimeout ttid; ttid = null; $tip.hide(); $tipTriangle.hide(); return
-      mouseover: (e) ->
-        clearTimeout ttid
+    $('.lbar')
+      .on 'mousedown', -> false
+      .on 'mousedown', 'b', (e) -> (for _, x of wins when x.hasFocus() then x.insert $(e.target).text()); false
+      .on 'mouseout', 'b', -> clearTimeout ttid; ttid = null; $tip.add($tipTriangle).hide(); return
+      .on 'mouseover', 'b', (e) ->
+        clearTimeout ttid; $t = $ e.target; p = $t.position(); x = $t.text()
         ttid = setTimeout(
           ->
-            ttid = null
-            $t = $ e.target; p = $t.position(); x = $t.text()
-            key = Dyalog.reverseKeyMap[x] || ''
-            keyText = key && "Keyboard: `#{key}\n\n"
+            ttid = null; key = Dyalog.reverseKeyMap[x] || ''; keyText = key && "Keyboard: `#{key}\n\n"
             h = Dyalog.lbarTips[x] or [x, '']; $tipDesc.text h[0]; $tipText.text keyText + h[1]
             $tipTriangle.css(left: 3 + p.left + ($t.width() - $tipTriangle.width()) / 2, top: p.top + $t.height() + 2).show()
-            x0 = p.left - 21
-            x1 = x0 + $tip.width()
-            y0 = p.top + $t.height()
-            if x1 > $(document).width()
-              $tip.css(left: '', right: 0, top: y0).show()
-            else
-              $tip.css(left: Math.max(0, x0), right: '', top: y0).show()
+            x0 = p.left - 21; x1 = x0 + $tip.width(); y0 = p.top + $t.height()
+            if x1 > $(document).width() then $tip.css(left: '', right: 0, top: y0).show()
+            else $tip.css(left: Math.max(0, x0), right: '', top: y0).show()
+            return
           200
         )
         return
@@ -96,5 +81,5 @@
       east:  spacing_closed: 0, size: '0%', resizable: 1, togglerLength_open: 0
       south: spacing_closed: 0, size: '0%', resizable: 1, togglerLength_open: 0
       center: onresize: -> (for _, x of wins then x.updateSize()); session.scrollCursorIntoView(); return
-    for d in ['east', 'south'] then layout.close d;  layout.sizePane d, '50%'
+    for d in ['east', 'south'] then layout.close d; layout.sizePane d, '50%'
     session.updateSize()


koding-watcher done

koding-watcher done

--- node_modules/koding-watcher/watcher.coffee
+++ node_modules/koding-watcher/watcher.coffee
@@ -1,9 +1,11 @@
 fs = require("fs")
 path = require 'path'
+{EventEmitter}  = require 'events'

 walk = (options, callback) ->
-  {dir,compilers} = options
+  {dir,groupName} = options
   results = []
+  console.log "asdf",options,dir
   fs.readdir dir, (err, list) ->
     return callback(err)  if err
     pending = list.length
@@ -12,57 +14,59 @@
       file = dir + "/" + file
       fs.stat file, (err, stat) ->
         if stat and stat.isDirectory()
-          walk file, (err, res) ->
+          walk {dir:file}, (err, res) ->
             results = results.concat(res)
             callback null, results  unless --pending
         else
-          p = path.normalize(file)
-          e = path.extname(file)
-
-          f =
-            file    : p
-            extension : e
-            stat    : stat
-            content : fs.readFileSync p,'utf8'
-
-          results.push f
+          results.push {file  : path.normalize(file), mtime : stat.mtime, groupName}
           callback null, results  unless --pending

-class Watcher
+class Watcher extends EventEmitter

-  constructor:(options)->
+  constructor:(@options)->
+
+    @init @options
+    @options.interval ?= 1000
+    @on "ready",(watchList)=>
+      @watchList = watchList
+      setInterval =>
+        @startWatching()
+      ,@options.interval

+  init:(options)->
     for groupName,val of options.groups
-      do (groupName,val) ->
+      do (groupName,val) =>
         for folder in val.folders
           # console.log folder
-          do (groupName,folder)->
-            walk {group:val,folder},(err,res)->
-              console.log res
+          do (groupName,folder)=>
+            walk {dir:folder,groupName},(err,res)=>
+              watchList = {}
+              for file in res
+                watchList[file.file] = {mtime : file.mtime, groupName}
+              @emit "ready",watchList
+
+  startWatching:(fileList = @watchList) ->
+    watcher = @
+    for path,info of fileList
+      do (path,info) ->
+        fs.stat path, (err, stat) ->
+          if stat.mtime > info.mtime
+            change = {path,groupName:info.groupName}
+            watcher.emit "change", change
+            watcher.options.groups[info.groupName].onChange? change.path
+            watcher.options.onChange? change
+            info.mtime = stat.mtime

-
-  initialize:(options,callback)->

 a = new Watcher
   groups:
     a :
-      folders : ['../../client/app/Helpers/']
+      folders : ['../../client']
       onRead  : (file,callback) ->
-      onChange : (change) ->
-    # b:
-    #   folders
-    # onChange: (groupObject)->
+      onChange : (path) ->
+        console.log "#{path} has changed."
+    onChange: (change)->
+      console.log "#{change.path} has changed on group #{change.groupName}"

-
-
-
-  # compilers :
-  #   '.coffee' : (content,callback) ->
-  #     callback null, cs.compile content,bare:yes
-
-  # fileExtensions :
-  #   coffee :
-
-


Nodejs: metrics: remove unused start parameter

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -346,7 +346,6 @@
         @update node

   handleRequest:(secretName, request, callback)->
-    start = new Date()
     { message, client } = request

     @emit 'message', { secretName, message }


Bongo: retire messageBusEvent

Bongo: retire messageBusEvent

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -193,12 +193,9 @@
   handleEvent:(type, ctx, event, params)->
     switch type
       when 'instance'
-        if event is 'messageBusEvent'
-          @publishEventToExchange params[0]
-        else
-          payload     = JSON.stringify params[0] or null
-          routingKey  = getInstanceRoutingKey ctx, event
-          @enqueueUpdateInstancesEvent routingKey, payload
+        payload     = JSON.stringify params[0] or null
+        routingKey  = getInstanceRoutingKey ctx, event
+        @enqueueUpdateInstancesEvent routingKey, payload
       when 'static'
         if event is 'broadcast'
           [secretChannelId, message] = params


bongo: don't kill the process in handleError

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -63,7 +63,7 @@

   @throwIt = (it) ->
     console.error it  if it?
-    process.kill process.pid, 'SIGTERM'
+    # process.kill process.pid, 'SIGTERM'

   constructor:(options)->
     {@mq, @mongo, @resourceName, @root, @fetchClient, @verbose, models, @precompiledApi} = options
@@ -77,7 +77,9 @@
     #@initBlacklist options.blacklist
     @loadModels models
     @on 'error', Bongo.throwIt
-    @mq?.ready => @mq.connection?.on 'error', Bongo.throwIt
+    @mq?.ready => @mq.connection?.on 'error', (err) ->
+      Bongo.throwIt err
+      process.exit 0
     @setMaxListeners 0
     @emit 'newInstance', this

@@ -347,7 +349,9 @@
         # a given model, and provides a wrapper for it; otherwise, search for
         # the method with the exact name.
         fn = ctx["#{method}$"] ? ctx[method]
-      bongo.handleError "unknown method! #{method}"  unless fn?
+      unless fn?
+        bongo.handleError "unknown method! #{method}"
+        return
       if fn.isSecurityEnabled
         bongo.fetchClient sessionToken, userArea, (client) ->
           args = [client].concat args


reorder logic around forcing login before ordering

--- src/desktop/apps/artwork/components/commercial/view.coffee
+++ src/desktop/apps/artwork/components/commercial/view.coffee
@@ -71,14 +71,7 @@
     if @artwork.get('edition_sets')?.length && @artwork.get('edition_sets').length == 1
       editionSetId = @artwork.get('edition_sets')[0] && @artwork.get('edition_sets')[0].id

-    if not loggedInUser
-      return mediator.trigger 'open:auth',
-        intent: 'buy now'
-        signupIntent: 'buy now'
-        mode: 'login'
-        trigger: 'click'
-        redirectTo: location.href
-    else
+    if loggedInUser
       $target.attr 'data-state', 'loading'
       createOrder
         artworkId: @artwork.get('_id')
@@ -97,6 +90,13 @@
         console.error('createOrder', err)
         $target.attr 'data-state', 'loaded'
         errorModal.render()
+    else
+      return mediator.trigger 'open:auth',
+        intent: 'buy now'
+        signupIntent: 'buy now'
+        mode: 'login'
+        trigger: 'click'
+        redirectTo: location.href

   inquire: (e) =>
     e.preventDefault() if e


Remove check for callback.

Remove check for callback.

--- lib/download.coffee
+++ lib/download.coffee
@@ -42,9 +42,9 @@

 			stream = fs.createWriteStream file

-			stream.on 'error', (error) -> callback new Error "cannot write to file #{file} for download #{url}" if callback
-			response.on 'error', (error) -> callback new Error "cannot download from url #{url} to file #{file}" if callback
-			response.on 'end', () -> callback null if callback
+			stream.on 'error', (error) -> callback new Error "cannot write to file #{file} for download #{url}"
+			response.on 'error', (error) -> callback new Error "cannot download from url #{url} to file #{file}"
+			response.on 'end', () -> callback null
 			response.pipe stream

 		socket.on 'error', (error) -> callback error if callback


also block "delete component" messages from sentry

--- services/document-updater/app/coffee/DispatchManager.coffee
+++ services/document-updater/app/coffee/DispatchManager.coffee
@@ -25,8 +25,13 @@
 					backgroundTask = (cb) ->
 						UpdateManager.processOutstandingUpdatesWithLock project_id, doc_id, (error) ->
 							# log everything except OpRangeNotAvailable errors, these are normal
-							if error? and not (error instanceof Errors.OpRangeNotAvailableError)
-								logger.error err: error, project_id: project_id, doc_id: doc_id, "error processing update"
+							if error?
+								# downgrade OpRangeNotAvailable and "Delete component" errors so they are not sent to sentry
+								logAsWarning = (error instanceof Errors.OpRangeNotAvailableError) || error.message?.match(/^Delete component/)
+								if logAsWarning
+									logger.warn err: error, project_id: project_id, doc_id: doc_id, "error processing update"
+								else
+									logger.error err: error, project_id: project_id, doc_id: doc_id, "error processing update"
 							cb()
 					RateLimiter.run backgroundTask, callback


coraleda footprint: render keepout as surface (#79)

Co-authored-by: King Kévin <d7b5faff4dd39d37ce32400ff2be656ed08f9b9b@cuvoodoo.info>

--- src/coraleda-generator.coffee
+++ src/coraleda-generator.coffee
@@ -335,19 +335,23 @@
           when 'Courtyard'
             fs.writeSync(fd, "     purpose = ko.courtyard\n")
         fs.writeSync(fd, "     li:objects {\n")
+        polygon_lines = [] # to store line to convert to polygon
         for shape in pattern.shapes
           continue if !shape.layer.includes(side+type)
           continue if shape.visible? and !shape.visible
           switch shape.kind
             when 'line'
-              fs.writeSync(fd, "      ha:line.#{id++} {\n")
-              fs.writeSync(fd, sprintf("       x1 = #{@f}mm\n", shape.x1))
-              fs.writeSync(fd, sprintf("       y1 = #{@f}mm\n", shape.y1))
-              fs.writeSync(fd, sprintf("       x2 = #{@f}mm\n", shape.x2))
-              fs.writeSync(fd, sprintf("       y2 = #{@f}mm\n", shape.y2))
-              fs.writeSync(fd, sprintf("       thickness = #{@f}mm\n", shape.lineWidth || pattern.settings.lineWidth[type.toLowerCase()]))
-              fs.writeSync(fd, "       clearance = 0\n")
-              fs.writeSync(fd, "      }\n") # end line
+              if 'Courtyard' == type # courtyards should be polygons
+                polygon_lines.push shape # collect all lines
+              else
+                fs.writeSync(fd, "      ha:line.#{id++} {\n")
+                fs.writeSync(fd, sprintf("       x1 = #{@f}mm\n", shape.x1))
+                fs.writeSync(fd, sprintf("       y1 = #{@f}mm\n", shape.y1))
+                fs.writeSync(fd, sprintf("       x2 = #{@f}mm\n", shape.x2))
+                fs.writeSync(fd, sprintf("       y2 = #{@f}mm\n", shape.y2))
+                fs.writeSync(fd, sprintf("       thickness = #{@f}mm\n", shape.lineWidth || pattern.settings.lineWidth[type.toLowerCase()]))
+                fs.writeSync(fd, "       clearance = 0\n")
+                fs.writeSync(fd, "      }\n") # end line
             when 'circle'
               fs.writeSync(fd, "      ha:arc.#{id++} {\n")
               fs.writeSync(fd, sprintf("       x = #{@f}mm\n", shape.x))
@@ -381,6 +385,34 @@
               fs.writeSync(fd, "         dyntext = 1\n") if shape.name == 'refDes'
               fs.writeSync(fd, "       }\n") # end flags
               fs.writeSync(fd, "      }\n") # end text
+        # end shapes
+        if polygon_lines.length > 0 # convert lines to polygon
+          points = []
+          points.push({x: polygon_lines[0].x1, y: polygon_lines[0].y1})
+          closed = false
+          next_found = true
+          while next_found and !closed
+            next_found = false
+            start_point = points[points.length - 1]
+            for line in polygon_lines
+              continue if not line?
+              if start_point.x == line.x1 and start_point.y == line.y1
+                end_point = {x: line.x2, y: line.y2}
+                if points[0].x == end_point.x and points[0].y == end_point.y
+                  closed = true
+                else
+                  points.push(end_point)
+                  next_found = true
+                polygon_lines = polygon_lines.filter (l) -> (l != line)
+          if closed
+            fs.writeSync(fd, "      ha:polygon.#{id++} {\n")
+            fs.writeSync(fd, "       li:geometry {\n")
+            fs.writeSync(fd, "        ta:contour {\n")
+            for point in points
+              fs.writeSync(fd, sprintf("         { #{@f}mm; #{@f}mm }\n", point.x, point.y))
+            fs.writeSync(fd, "        }\n") # end contour
+            fs.writeSync(fd, "       }\n") # end geometry
+            fs.writeSync(fd, "      }\n") # end polygon
         fs.writeSync(fd, "     }\n") # end objects
         fs.writeSync(fd, "    }\n") # end layer
         lid += 1


Remove debugger on route

Remove debugger on route

--- apps/articles/routes.coffee
+++ apps/articles/routes.coffee
@@ -31,7 +31,6 @@
     accessToken: req.user?.get('accessToken')
     error: res.backboneError
     success: (data) ->
-      debugger
       if req.params.slug isnt data.article.get('slug')
         return res.redirect "/article/#{data.article.get 'slug'}"
       res.locals.sd.SLIDESHOW_ARTWORKS = data.slideshowArtworks?.toJSON()


Add pandoc markdown support

Add pandoc markdown support

Using panzer wrapper

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -284,6 +284,11 @@
       command: "ocaml"
       args: (context) -> [context.filepath]

+  'Pandoc Markdown':
+    "File Based":
+      command: "panzer"
+      args: (context) -> [context.filepath, "--output=" + context.filepath + ".pdf"]
+
   PHP:
     "Selection Based":
       command: "php"


Fix chord decoration.

Fix chord decoration.

--- openchordcharts/static/coffee/chart.coffee
+++ openchordcharts/static/coffee/chart.coffee
@@ -45,8 +45,9 @@
     newPart = name: partName, chords: []
     for chord, chordIndex in chart.parts[partName]
       if chordIndex > 0 and previousChord == chord or chart.structure.indexOf(partName) != partIndex
-        chord = "—"
-      newPart.chords.push(renderChord(chord))
+        newPart.chords.push "—"
+      else
+        newPart.chords.push(renderChord(chord))
       previousChord = chord
     parts.push newPart
   parts


Fixing getHgStatus.

Fixing getHgStatus.

--- lib/hg-utils.coffee
+++ lib/hg-utils.coffee
@@ -333,8 +333,8 @@
     if entries
       for entry in entries
         parts = entry.split(' ')
-        path = parts[0] || null
-        status = parts[1] || null
+        status = parts[0] || null
+        path = parts[1] || null
         if path? && status?
           items.push({
             'path': path
@@ -362,8 +362,8 @@
     if entries
       for entry in entries
         parts = entry.split(' ')
-        path = parts[0] || null
-        status = parts[1] || null
+        status = parts[0] || null
+        path = parts[1] || null
         if status?
           status |= @mapHgStatus(status)
       return status


:new: Add support for editor-scoped messages in MessageRegistry

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -10,22 +10,31 @@
 class MessageRegistry
   constructor: (@linter)->
     @count = File: 0, Project: 0
-    @messages = new Map() # Messages = Map<Linter, Array<messages>>
+    @editorMessages = new Map() # Map<Editor, Map<Linter, Array<message>>
+    @messages = new Map() # Messages = Map<Linter, Array<message>>
     @emitter = new Emitter
     @subscriptions = new CompositeDisposable
     @subscriptions.add atom.workspace.onDidChangeActivePaneItem =>
       @messages.forEach (messages) => @classifyMessages(messages)
+      @editorMessages.forEach (singleEditorMessages) =>
+        singleEditorMessages.forEach (messages) => @classifyMessages(messages)
       @emitter.emit 'did-classify'

-  set: (linter, messages) ->
+  set: (linter, messages, editor = null) ->
     Helpers.validateMessages(messages)
     @classifyMessages(messages)
-    @messages.set(linter, messages)
+    if editor
+      @editorMessages.set(editor, new Map()) unless @editorMessages.has(editor)
+      @editorMessages.get(editor).set(linter, messages)
+    else
+      @messages.set(linter, messages)
     @emitter.emit 'did-classify'
     @emitter.emit 'did-change', @messages

   delete: (linter) ->
     @messages.delete(linter)
+    @editorMessages.forEach (messages)->
+      messages.delete(linter)
     @emitter.emit 'did-change', @messages

   getCount: ->
@@ -35,12 +44,18 @@
     toReturn = []
     @messages.forEach (messages) =>
       toReturn = toReturn.concat(messages)
+    @editorMessages.forEach (singleEditorMessages) =>
+      singleEditorMessages.forEach (messages) =>
+        toReturn = toReturn.concat(messages)
     return toReturn

   getActiveFileMessages: ->
     toReturn = []
     @messages.forEach (messages) =>
       toReturn = toReturn.concat(messages.filter((message) -> message.currentFile))
+    @editorMessages.forEach (singleEditorMessages) =>
+      singleEditorMessages.forEach (messages) =>
+        toReturn = toReturn.concat(messages.filter((message) -> message.currentFile))
     return toReturn

   getActiveFileMessagesForActiveRow: ->
@@ -52,6 +67,11 @@
       toReturn = toReturn.concat messages.filter((message) ->
         message.currentFile and message.range?.intersectsRow row
       )
+    @editorMessages.forEach (singleEditorMessages) =>
+      singleEditorMessages.forEach (messages) =>
+        toReturn = toReturn.concat messages.filter((message) ->
+          message.currentFile and message.range?.intersectsRow row
+        )
     return toReturn

   onDidChange: (callback) ->


Add Rspec support

Add Rspec support

Fixes #3

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -1,5 +1,5 @@
 module.exports =
-  selector: ['.source.ruby', '.source.ruby.rails']
+  selector: ['.source.ruby', '.source.ruby.rails', '.source.ruby.rspec']
   id: 'aligner-ruby' # package name
   config:
     '=>-alignment':


slighty improve ui reactiveness

slighty improve ui reactiveness

--- knockout-tinymce.coffee
+++ knockout-tinymce.coffee
@@ -79,13 +79,15 @@
       # Ensure the valueAccessor state to achieve a realtime responsive UI.
       editor.on "change keyup nodechange", (e) ->

-        # Update the view model
-        writeValueToProperty(args[1](), args[2], "tinymce", editor.getContent())
+        setTimeout (->
+          # Update the view model
+          writeValueToProperty(args[1](), args[2], "tinymce", editor.getContent())

-        # Run all applied extensions
-        for name of extensions
-          binding["extensions"][extensions[name]] editor, e, args[2], args[4]  if extensions.hasOwnProperty(name)
-        return
+          # Run all applied extensions
+          for name of extensions
+            binding["extensions"][extensions[name]] editor, e, args[2], args[4]  if extensions.hasOwnProperty(name)
+          return
+        ), 0

       return


also save the last used instance in the binding cache

--- knockout-tinymce.coffee
+++ knockout-tinymce.coffee
@@ -1,5 +1,6 @@
 (($, ko) ->
   cache = ""
+  cacheInstance = null
   binding =
     after: [
       "attr"
@@ -30,6 +31,7 @@
       ko.utils["domNodeDisposal"].addDisposeCallback element, ->
         tinymce = $(element).tinymce()
         tinymce.remove() if tinymce
+        cacheInstance = null unless tinymce != cacheInstance
         return

       controlsDescendantBindings: true
@@ -42,7 +44,9 @@
       # tiny mce crashes if value is null
       if value == null
         value = ""
-      if tinymce and cache != value
+      if tinymce and cacheInstance == tinymce and cache != value
+        cacheInstance = tinymce
+        cache = value
         if tinymce.getContent() isnt value
           tinymce.setContent value
       return
@@ -84,6 +88,7 @@
         setTimeout (->
           value = editor.getContent()
           cache = value
+          cacheInstance = editor

           # Update the view model
           writeValueToProperty args[1](), args[2], "tinymce", value


Ensure specs work independent of telemetry consent

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -56,9 +56,8 @@
   })

 shouldReport = (error) ->
+  return true if exports.alwaysReport # Used in specs
   return false unless atom.config.get('core.telemetryConsent') is 'limited'
-  return true if global.alwaysReportToBugsnag # Used to test reports in dev mode
-  return true if exports.alwaysReport # Used in specs
   return false if atom.inDevMode()

   if topFrame = parseStackTrace(error)[0]


:lipstick:

:lipstick:

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -26,9 +26,8 @@

     @viewNameForPaneItem: (item) ->
       name = item.getViewClass?().name ? item.constructor.name
-      return name unless item.getPath
+      itemPath = item.getPath?()

-      itemPath = item.getPath()
       return name unless path.dirname(itemPath) is atom.getConfigDirPath()

       extension = path.extname(itemPath)


Add support for finding the file through looking at the `main` field in the package.json file. Fixes #8 for all I care.

--- lib/resolver.coffee
+++ lib/resolver.coffee
@@ -82,6 +82,12 @@
     # req ends in valid folder ?
     continue if noTryFolder # already done this test
     return [found, dom, false] if found = @finder(@domains[dom], absReq + '/index')
+
+    packageJSONPath = path.join(absReq + '/package.json')
+    if @finder(@domains[dom], packageJSONPath)
+      json = JSON.parse(fs.readFileSync(path.join(@domains[dom], packageJSONPath)))
+      mainPath = path.join(absReq, json.main)
+      return [found, dom, false] if found = @finder(@domains[dom], mainPath)

   throw new Error("modul8::analysis could not resolve a require for #{reqStr} from #{domain} - looked in #{scannable}, trying extensions #{@exts[1...]}")


Converted to streams for individual segments in Sortable and did some refactoring.

--- lib/sortable.coffee
+++ lib/sortable.coffee
@@ -12,16 +12,13 @@
   require.paths.unshift __dirname
   { recur, resolve } = require('trampoline')
   { List }           = require('list')
+  { Stream }         = require('stream')
 else
-  { recur, resolve, List } = this.pazy
+  { recur, resolve, List, Stream } = this.pazy

 class Sortable
-  constructor: (@less, size, segments) ->
-    [@_size, @_segs] = if size?
-      [size, => val = segments(); (@_segs = -> val)()]
-    else
-      [0, -> null]
+  constructor: (@less, @_size = 0, @_segs = null) ->

   size: -> @_size

@@ -34,34 +31,20 @@
       else
         new List(seg, segs)

-    step = (s, args) ->
-      if args.length > 0
-        [x, a...] = args
-        newSegs = -> resolve addSeg(new List(x), s._segs(), s.size())
-        recur -> step(new Sortable(less, s.size() + 1, newSegs), a)
-      else
-        s
+    if arguments.length > 0
+      List.fromArray(arguments).reduce this, (s, x) ->
+        newSegs = resolve addSeg(new Stream(x), s._segs, s.size())
+        new Sortable(less, s.size() + 1, newSegs)
+    else
+      this

-    resolve step(this, arguments)
-
-  sort: ->
-    less = @less
-    step = (buf, segs) ->
-      if segs?
-        recur -> step(merge(less, buf, segs.first()), segs.rest())
-      else
-        buf
-    resolve step(null, @_segs())
+  sort: -> @_segs.reduce(null, (r, s) => merge(@less, r, s))

   merge = (less, xs, ys) ->
-    step = (r, xs, ys) ->
-      if xs and (not ys or less(xs.first(), ys.first()))
-        recur -> step(new List(xs.first(), r), xs.rest(), ys)
-      else if ys
-        recur -> step(new List(ys.first(), r), xs, ys.rest())
-      else
-        r
-    (resolve step(null, xs, ys))?.reverse()
+    if xs and (not ys or less(xs.first(), ys.first()))
+      new Stream(xs.first(), -> merge(less, xs.rest(), ys))
+    else if ys
+      new Stream(ys.first(), -> merge(less, xs, ys.rest()))

 # --------------------------------------------------------------------


Remove unused dependency

Remove unused dependency

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -1,4 +1,4 @@
-{$, fs, View} = require 'atom'
+{$, View} = require 'atom'
 path = require 'path'

 module.exports =


anther passing test

anther passing test

--- test/test_app/tests/jasmine/client/integration/Login.coffee
+++ test/test_app/tests/jasmine/client/integration/Login.coffee
@@ -32,7 +32,7 @@
       _setup_user()

 describe "Can't access Meteor unless logged in", ->
-  it "should send to login page",
+  it "not logged in, no admin? offer to crete admin",
     (done) ->
       setup {logged_in: false, as_admin: false, other_admin: false}, ->
         Houston._go "home"
@@ -43,13 +43,25 @@
           expect($signin_form.html()).toMatch(/Create an admin account/)
           done()

-  it "should offer to Claim Admin if logged in",
+  it "not logged in, and there's an admin? You should log in to admin!",
+    (done) ->
+      setup {logged_in: false, as_admin: false, other_admin: true}, ->
+        Houston._go "home"
+        url_changed = -> window.location.pathname is "/admin/login"
+        eventually url_changed, ->
+          $signin_form = $('#houston-sign-in-form')
+          expect($signin_form[0]).not.toBeUndefined()
+          expect($signin_form.html()).toMatch(/Log In/)
+          done()
+
+  it "logged in, no admin? should offer to Claim Admin",
     (done) ->
       setup {logged_in: true, as_admin: false, other_admin: false}, ->
         Houston._go "home"
         url_changed = -> window.location.pathname is "/admin/login"
         eventually url_changed, ->
-          expect($('#become-houston-admin')[0]).not.toBeUndefined()
+          expect($('#become-houston-admin').length).toEqual(1)
+          expect($('.form-heading').first().html()).toMatch(/You are not an Admin./)
           done()

   it "tells you to go away if an admin exists",


run: fix msg

run: fix msg

--- lib/commands/run.coffee
+++ lib/commands/run.coffee
@@ -25,10 +25,10 @@
       rl = readline.createInterface process.stdin, process.stdout
       rl.setPrompt ''
       rl.on 'SIGINT', process.exit
-    write = (msg) ->
+    write = (msg, err) ->
       unless rl
       then process.stdout.write msg
-      else rl.write "#{styles.final_status_error err.stack?.trim() or err.message}\n"
+      else msg
     refresh = (multi) ->
       return unless rl
       rl.cursor = 0
@@ -96,7 +96,7 @@
         line += "#{styles.time print_time time}"
         refresh(true)
         if err or status?
-          write line + '\n'
+          write line + '\n', err
       .on 'wait', (middleware) ->
         return if multihost
         line = ''


more half-assignment tests

more half-assignment tests

--- test/test_half_assignments.coffee
+++ test/test_half_assignments.coffee
@@ -33,4 +33,13 @@
 val: null
 val: ? 'value'

-ok val is 'value'+ok val is 'value'
+
+
+val: 6
+val: -(10)
+
+ok val is -10
+
+val: - (10)
+ok val is -20


Don't include npm docs

Don't include npm docs

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -49,6 +49,9 @@
       path.join('bootstrap', '_config.yml')
       path.join('bootstrap', '_includes')
       path.join('bootstrap', '_layouts')
+      path.join('npm', 'doc')
+      path.join('npm', 'html')
+      path.join('npm', 'man')
       path.join('pegjs', 'examples')
       path.join('jasmine-reporters', 'ext')
       path.join('jasmine-node', 'node_modules', 'gaze')


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -134,6 +134,17 @@

 @type Number
 @returns The sign of this number, 0 if the number is 0.
+
+

+   (-5).sign()
+=> -1
+
+   (0).sign()
+=> 0
+
+   (5).sign()
+=> 1
+

 ###
 Number::sign = ->
   if this > 0


Fix empty zoom views

Fix empty zoom views

--- apps/show/client/index.coffee
+++ apps/show/client/index.coffee
@@ -25,8 +25,8 @@
       imagesLoaded: true
     }, (instance) ->
       instance.cells.flickity.on 'staticClick', (event, pointer, cellElement, cellIndex) ->
-        src = $(cellElement).find('img').attr('src')
-        new ZoomView imgSrc: src
+        if src = $(cellElement).find('img').attr('src')
+          new ZoomView imgSrc: src

   setupSaveControls show.related().artworks
   attachFollowArtists show.related().artists


marked: added sane options and server-side syntax highlighting to marked call in JBlogPost

--- workers/social/lib/social/models/messages/blog/index.coffee
+++ workers/social/lib/social/models/messages/blog/index.coffee
@@ -14,7 +14,16 @@
   }

   @generateHTML=(content)->
-    require('marked') content
+    options =
+      gfm : yes
+      sanitize : yes
+      highlight : (code, lang)->
+        require('highlight.js').highlight(lang, code).value
+      breaks : yes
+      langPrefix : 'lang-'
+    marked = require('marked')
+    marked.setOptions options
+    marked content

   @generateChecksum=(content)->
     require('crypto')