Remove ton of useless spaces

Remove ton of useless spaces

--- static/scripts/main.coffee
+++ static/scripts/main.coffee
@@ -155,19 +155,14 @@
             alert error.type

         peer.on "open", (peerId) =>
-
             if who == "x"
-
                 peer.on "connection", (connection) =>
-
                     @connections.push connection
-
                     @listenForMessage connection

                     peerIds = @connectionsToPeerIds @connections

                     connection.on "open", =>
-
                         connectionsWithoutNewConnection = _.filter @connections, (c) -> c.peer != connection.peer
                         peerIdsWithoutNewConnection = @connectionsToPeerIds connectionsWithoutNewConnection

@@ -175,45 +170,31 @@
                             connection.send type: "newConnection", peerIds: peerIdsWithoutNewConnection

             if who != "x"
-
                 connection = peer.connect "x"

                 connection.on "error", (error) ->
                     alert error

                 connection.on "open", =>
-
                     @connections.push connection
-
                     @listenForMessage connection

                     connection.on "data", (data) =>
-
                         if data.type == "newConnection"
-
-                            peerIds = data.peerIds
-
-                            _.forEach peerIds, (peerId) =>
-
+                            _.forEach data.peerIds, (peerId) =>
                                 connection = peer.connect peerId

                                 do (connection) =>
-
                                     connection.on "error", (error) ->
                                         alert error.type

                                     connection.on "open", =>
-
                                         @connections.push connection
-
                                         @listenForMessage connection

                 peer.on "connection", (connection) =>
-
                     connection.on "open", =>
-
                         @connections.push connection
-
                         @listenForMessage connection

     render: ->


Build up querySpec projections lazily when tracking denormed reactives

--- models.coffee
+++ models.coffee
@@ -268,8 +268,7 @@
                 if reactiveSpec.denorm
                     J.assert @_id?

-                    if @_watcherReactiveName?
-                        # Treat an own-reactive access the same as any query
+                    if J._watchedQuerySpecSet.get()?
                         if reactiveSpec.watchable ? @modelClass.watchable
                             projection = _: false
                             projection[fieldOrReactiveSpec] = true
@@ -358,19 +357,16 @@
             if Meteor.isServer and J._watchedQuerySpecSet.get()?
                 J.assert @_id?

-                if @_watcherReactiveName?
-                    # Treat an own-field access the same as any query
-                    if fieldSpec.watchable ? @modelClass.watchable
-                        projection = _: false
-                        projection[fieldOrReactiveSpec] = true
-                        dummyQuerySpec = J.fetching.makeCanonicalQs
-                            modelName: @modelClass.name
-                            selector: @_id
-                            fields: projection
-                            limit: 1
-                        dummyQsString = J.fetching.stringifyQs dummyQuerySpec
-                        if @modelClass.name is 'Check' then console.log "adding dummy: ", dummyQsString
-                        J._watchedQuerySpecSet.get()[dummyQsString] = true
+                if fieldSpec.watchable ? @modelClass.watchable
+                    projection = _: false
+                    projection[fieldOrReactiveSpec] = true
+                    dummyQuerySpec = J.fetching.makeCanonicalQs
+                        modelName: @modelClass.name
+                        selector: @_id
+                        fields: projection
+                        limit: 1
+                    dummyQsString = J.fetching.stringifyQs dummyQuerySpec
+                    J._watchedQuerySpecSet.get()[dummyQsString] = true

                 if @_fields.tryGet(fieldName) is undefined
                     console.warn "Field <#{@modelClass.name} #{JSON.stringify @_id}>.#{fieldName}
@@ -904,29 +900,12 @@
                                     Use a combination of individual dot-paths instead."
                                 console.warn "    #{JSON.stringify querySpec, null, 4}"

-                        inclusionSet = J.fetching._projectionToInclusionSet @, querySpec.fields
-                        watchableInclusionSet = {}
-                        for includeSpec, include of inclusionSet
-                            fieldOrReactiveName = includeSpec.split('.')[0]
-                            watchable =
-                                if fieldOrReactiveName of @fieldSpecs
-                                    @fieldSpecs[fieldOrReactiveName].watchable ? @watchable
-                                else if fieldOrReactiveName of @reactiveSpecs
-                                    @reactiveSpecs[fieldOrReactiveName].watchable ? @watchable
-                                else
-                                    throw new Error "Unrecognized includeSpec for #{@name}.fetch: #{JSON.stringify includeSpec}"
-                            if watchable
-                                watchableInclusionSet[includeSpec] = true
-
-                        watchableProjection = _.extend(
-                            _: false
-                            watchableInclusionSet
-                        )
-                        watchableQuerySpec = _.clone querySpec
-                        watchableQuerySpec.fields = J.fetching._getCanonicalProjection @, watchableProjection
-
-                        if modelName is 'Check' then console.log "fetch operation adding: #{JSON.stringify watchableQuerySpec}"
-                        J._watchedQuerySpecSet.get()[J.fetching.stringifyQs watchableQuerySpec] = true
+                        # Track that we're doing a query for a set of _ids. We'll let the actual
+                        # field accessors build up the tracked projection.
+                        idOnlyQuerySpec = _.clone querySpec
+                        idOnlyQuerySpec.fields = _: false
+
+                        J._watchedQuerySpecSet.get()[J.fetching.stringifyQs idOnlyQuerySpec] = true

                     mongoFieldsArg = J.fetching.projectionToMongoFieldsArg @, options.fields ? {}


Init the post status in editor bar.

Init the post status in editor bar.

Initialize the post stats on page load, not on "update" event.

Kind of a messy hack but the assignment of these values in the editor bar don't have their own setters.  Didn't want to edit editor.js directly.

Signed-off-by: Doug Grubba <5d29b48b3e443cc45cc6f72664f03bd27c98a3cd@gmail.com>

--- app/assets/coffee/views/post.coffee
+++ app/assets/coffee/views/post.coffee
@@ -34,6 +34,9 @@
       ]
       @editor = new Editor(toolbar: toolbar)
       @editor.render(document.getElementById("content"))
+      $('.editor-statusbar .lines').html @editor.codemirror.lineCount()
+      $('.editor-statusbar .words').html @editor.codemirror.getValue().length
+      $('.editor-statusbar .cursorActivity').html @editor.codemirror.getCursor().line + ':' + @editor.codemirror.getCursor().ch;

     setUpTags: ->
       App.request "tag:entities", (tags) =>


Force-change location hash to #page=new-post if it wasn't #page=something

Signed-off-by: Defman21 <042dc4512fa3d391c5170cf3aa61e6a638f84342@defman.me>

--- app/assets/javascripts/admin.coffee
+++ app/assets/javascripts/admin.coffee
@@ -52,7 +52,6 @@
     publishStatus: (bool) ->
       if bool is true then 'published' else 'unpublished'
     openPost: (post) ->
-      console.log post
       return unless post?
       @post = post
       if post.published
@@ -71,8 +70,8 @@
   data = parseHash location.hash.substring 1
   if data.page?
     app.openPage data.page
-  else if data.post?
-    app.openPost $posts.find((post) -> post.id is data.post)
+  else
+    location.hash = '#page=new-post'

 window.addEventListener 'hashchange', ->
   data = parseHash location.hash.substring 1


Fix incorrect usage of vtex.js removeItems. Item must be identified by an index property.

--- src/script/portal-minicart.coffee
+++ src/script/portal-minicart.coffee
@@ -369,13 +369,11 @@
 	deleteItem: (item) =>
 		$(item).parent().find('.vtexsc-overlay').show()

-		orderItems =
+		removedItem =
 			index: $(item).data("index")
 			quantity: 0

-		item = @cartData.items[orderItems.index]
-
-		vtexjs.checkout.removeItems([item])
+		vtexjs.checkout.removeItems([removedItem])
 			.done (data) =>
 				@element.trigger 'vtex.minicart.updated' #DEPRECATED
 				@element.trigger 'minicartUpdated.vtex'


team: description field cleared when form is displayed

Signed-off-by: Vítor Avelino <1a73af9e7ae00182733b2292511b814be66f065f@vitoravelino.me>

--- app/assets/javascripts/teams.coffee
+++ app/assets/javascripts/teams.coffee
@@ -71,6 +71,7 @@

   $('#add_team_btn').on 'click', (event) ->
     $('#team_name').val('')
+    $('#team_description').val('')
     $('#add_team_form').toggle 400, "swing", ->
       if $('#add_team_form').is(':visible')
         $('#add_team_btn i').addClass("fa-minus-circle")


Fix "choose winner" button placement

Fix "choose winner" button placement

* Voting or unvoting for a suggestion was changing button placement

https://trello.com/c/sLsuLmKr

--- app/assets/javascripts/votes.coffee
+++ app/assets/javascripts/votes.coffee
@@ -29,8 +29,8 @@
   else
     $(this).find('input.vote').removeClass('vote').addClass('unvote')
   id = $(this).parent().data('id')
-  $(".vote-count[data-id=#{id}]").text(parseInt($(".vote-count[data-id=#{id}]").text()) + 1 )
-
+  newVoteCount = parseInt($(".vote-count[data-id=#{id}]").text(), 10) + 1
+  $(".vote-count[data-id=#{id}]").html("#{newVoteCount}")

 Scheddo.unvoteCallback = (data) ->
   $('body').removeClass('loading')
@@ -43,7 +43,8 @@
   else
     $(this).find('input.unvote').removeClass('unvote').addClass('vote')
   id = $(this).parent().data('id')
-  $(".vote-count[data-id=#{id}]").text(parseInt($(".vote-count[data-id=#{id}]").text()) - 1 )
+  newVoteCount = parseInt($(".vote-count[data-id=#{id}]").text(), 10) - 1
+  $(".vote-count[data-id=#{id}]").html("#{newVoteCount}")

 Scheddo.voteErrorCallback = (data) ->
   $('body').removeClass('loading')


Fix redirect on remove task.

Fix redirect on remove task.

--- app/coffee/controllers/tasks.coffee
+++ app/coffee/controllers/tasks.coffee
@@ -13,7 +13,7 @@
 # limitations under the License.

-TasksViewController = ($scope, $location, $rootScope, $routeParams, $q, rs, $data) ->
+TasksViewController = ($scope, $location, $rootScope, $routeParams, $q, $confirm, rs, $data) ->
     $rootScope.pageSection = 'tasks'
     $rootScope.pageBreadcrumb = [
         ["", ""],
@@ -78,10 +78,12 @@
         $scope.newAttachments = _.without($scope.newAttachments, attachment)

     $scope.removeTask = (task) ->
-        milestone = task.milestone
-        task.remove().then ->
-            $location.url("/project/#{projectId}/dashboard/#{milestone}/")
+        promise = $confirm.confirm("Are you sure?")
+        promise.then ->
+            milestone = task.milestone
+            task.remove().then ->
+                $location.url("/project/#{projectId}/taskboard/#{milestone}")

 module = angular.module("greenmine.controllers.tasks", [])
-module.controller("TasksViewController", ['$scope', '$location', '$rootScope', '$routeParams', '$q', 'resource', "$data", TasksViewController])
+module.controller("TasksViewController", ['$scope', '$location', '$rootScope', '$routeParams', '$q', '$confirm', 'resource', "$data", TasksViewController])


use the new ref_dates API format

use the new ref_dates API format

--- app/scripts/controllers/main.coffee
+++ app/scripts/controllers/main.coffee
@@ -15,14 +15,17 @@
           month : month
           year : year
         # Loads data from API
-        filter = 'where={"ref_date":"' + year + '"}'
-        (do (Restangular.all "articles?#{filter}").getList).then (data) =>
+        #filter = '?where={"ref_date":"' + year + '"}'
+        filter = ''
+        (do (Restangular.all "articles#{filter}").getList).then (data) =>
           $scope.days = []
-          _.map data[0], (article) =>
-            a_year = article['ref_date'][0]
+          _.map data._items, (article) =>
+            if not article.ref_dates[0]?
+              return
+            a_year = article.ref_dates[0].date[0]
             if a_year is year
-              a_month = article['ref_date'][1]
-              a_day = article['ref_date'][2]
+              a_month = article.ref_dates[0].date[1]
+              a_day = article.ref_dates[0].date[2]
               article['reference_date'] = new Date a_year, a_month, a_day
               if a_month is monthDigit
                 if a_day is day


Modified jQuery selector in bootstrap.js to make tooltips work for link elements with multiple "rel" attribute values.

--- app/assets/javascripts/bootstrap.js.coffee
+++ app/assets/javascripts/bootstrap.js.coffee
@@ -1,4 +1,4 @@
 jQuery ->
-  $("a[rel=popover]").popover()
+  $("a[rel~=popover]").popover()
   $(".tooltip").tooltip()
-  $("a[rel=tooltip]").tooltip()+  $("a[rel~=tooltip]").tooltip()


Reduce FPS to 30

Reduce FPS to 30

--- app/scripts/lib/bounce/index.coffee
+++ app/scripts/lib/bounce/index.coffee
@@ -7,7 +7,7 @@
   skew: require "./components/skew"

 class Bounce
-  @FPS: 60
+  @FPS: 30
   @counter: 1

   components: null


Remove js line

Remove js line

--- app/assets/javascripts/dashboard.js.coffee
+++ app/assets/javascripts/dashboard.js.coffee
@@ -27,4 +27,3 @@
         # Change url so if user reload a page - search results are saved
         history.replaceState {page: project_filter_url}, document.title, project_filter_url
       dataType: "json"
-    #uiBox.find("ul.projects-list li.bottom").hide()


correction du bug de déplacement

correction du bug de déplacement

--- app/assets/jax/models/level_core.js.coffee
+++ app/assets/jax/models/level_core.js.coffee
@@ -367,7 +367,10 @@
       @copyright = data.copyright
       @rows_number = data.rows_number
       @cols_number = data.cols_number
-      @grid = data.grid
+
+      for i in [0..@rows_number*@cols_number-1]
+        @grid[i] = data.grid.substr(i, 1)
+
       @initialize_level_properties()
     )


Remove a ;

Remove a ;

--- app/coffee/modules/common/analytics.coffee
+++ app/coffee/modules/common/analytics.coffee
@@ -47,7 +47,7 @@
         @win.ga("require", "displayfeatures")

         if @.trackRoutes and (not @.ignoreFirstPageLoad)
-            @win.ga("send", "pageview", @.getUrl());
+            @win.ga("send", "pageview", @.getUrl())

         # activates page tracking
         if @.trackRoutes


Add extra ec methods to analytics service

--- app/coffee/modules/common/analytics.coffee
+++ app/coffee/modules/common/analytics.coffee
@@ -102,10 +102,30 @@
         else if step == "plan-changed"
             stepId = 5

-        ga('ec:setAction','checkout', {
+        @win.ga('ec:setAction','checkout', {
             'step': stepId,
             'Option': option
         })

+    addEcImpression: (plan, page, position) ->
+        @win.ga('ec:addImpression', {
+           'id': plan.plan_id,
+           'name': plan.name,
+           'list': page,
+           'position': position,
+        })
+
+    setEcAddProduct: (plan) ->
+        @win.ga('ec:addProduct', {
+           'id': plan.plan_id,
+           'name': plan.name,
+           'position': 1,
+        })
+
+    setEcSetAction: (action, page) ->
+        @win.ga('ec:setAction', action, {
+            'list': page
+        })
+
 module.service("$tgAnalytics", AnalyticsService)


Better animal counts

Better animal counts

--- app/collections/zooniverse_subjects.coffee
+++ app/collections/zooniverse_subjects.coffee
@@ -92,6 +92,8 @@
     _(counters).chain().pairs()
       .filter((p) -> _.last(p) > 7.5)
       .map(_.first)
+      .map((d) -> d.split('-'))
+      .flatten()
       .value()

   serengeti: (subject) =>


And now, login/logout is sync across tabs/windows.

--- app/client/js/users/store.coffee
+++ app/client/js/users/store.coffee
@@ -93,10 +93,12 @@
   toJson: ->
     newSong: @newSong
     songs: @songs
+    user: @user

   ###*
     @override
   ###
   fromJson: (json) ->
     @newSong = @instanceFromJson app.songs.Song, json.newSong
+    @user = json.user
     @songs = json.songs.map @instanceFromJson app.songs.Song


remove spurious call to getAllDocIdsWithHistoryOps

--- app/coffee/UpdatesManager.coffee
+++ app/coffee/UpdatesManager.coffee
@@ -159,7 +159,6 @@
 				return callback(error) if error?
 				failedProjects = (x.project_id for x in result when x.failed)
 				succeededProjects = (x.project_id for x in result when not x.failed)
-				RedisManager.getAllDocIdsWithHistoryOps (error, doc_ids) ->
 				callback(null, {failed: failedProjects, succeeded: succeededProjects})

 	getDanglingUpdates: (callback = (error, doc_ids) ->) ->


Add a link

Add a link

--- app/controllers/team-page.coffee
+++ app/controllers/team-page.coffee
@@ -1,7 +1,7 @@
 Page = require './page'

 content = '''
-
The project is a collaborative effort spearheaded by seven partnering institutions. More information about the team and various roles of each team member can be found here

+
The project is a collaborative effort spearheaded by seven partnering institutions. More information about the team and various roles of each team member can be found here.

The project is supported in part by the National Science Foundation (see Award No. 114589, No. 1057418, and the Grainger Foundation.

The Field Museum


add return

add return

--- BackofficeBundle/Resources/public/coffee/inheritance/inheritance.coffee
+++ BackofficeBundle/Resources/public/coffee/inheritance/inheritance.coffee
@@ -1 +1 @@
-inheritance = {}+inheritance = {}


Switch playbill to program

Switch playbill to program

--- app/assets/javascripts/components/mark/index.cjsx
+++ app/assets/javascripts/components/mark/index.cjsx
@@ -269,7 +269,7 @@
                           if @getCurrentSubject() == @getCurrentSubjectSet().subjects[@getCurrentSubjectSet().subjects.length-1]
                             <button type="button" className="continue major-button" disabled={waitingForAnswer} onClick={@completeSubjectAssessment}>Next</button>
                           else
-                            <button type="button" className="continue major-button" disabled={waitingForAnswer} onClick={@completeSubjectAssessment}>Next Playbill</button>
+                            <button type="button" className="continue major-button" disabled={waitingForAnswer} onClick={@completeSubjectAssessment}>Next Program</button>
                         else
                           <button type="button" className="continue major-button" disabled={waitingForAnswer} onClick={@completeSubjectSet}>Done</button>
                     }


Listen to Pjax success on 'main

Listen to Pjax success on 'main

--- app/assets/javascripts/nembrot.document.js.coffee
+++ app/assets/javascripts/nembrot.document.js.coffee
@@ -192,7 +192,7 @@
   document_initializers()
   load_code()

-$(document).on 'pjax:end', ->
+$(document).on 'pjax:end', '#main', ->
   content_initializers()
   content_initializers_reload_only()
   load_code()


re-adds the singular upload_complete event

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -52,6 +52,7 @@
           $.post(to, content)

         data.context.remove() if data.context && settings.remove_completed_progress_bar # remove progress bar
+        $uploadForm.trigger("s3_upload_complete", [content])

         current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
         $uploadForm.trigger("s3_uploads_complete", [content]) unless current_files.length
@@ -72,7 +73,7 @@

         data[1].value = settings.path + data[1].value #the key
         data
-
+
   build_content_object = ($uploadForm, file, result) ->
     content = {}
     if result # Use the S3 response to set the URL to avoid character encodings bugs


fix syntax error

fix syntax error

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -36,9 +36,8 @@

   setUploadForm = ->
     $uploadForm.fileupload
-
-      if settings.dropzone_target
-        dropZone: settings.dropzone_target
+
+      dropZone: settings.dropzone_target

       add: (e, data) ->
         file = data.files[0]


fix issue #2956 - fix infinite loading spinner in the contacts tab with anonymous users

--- app/modules/profile/profile-contacts/profile-contacts.controller.coffee
+++ app/modules/profile/profile-contacts/profile-contacts.controller.coffee
@@ -5,15 +5,14 @@
     ]

     constructor: (@userService, @currentUserService) ->
-
-    loadContacts: () ->
         @.currentUser = @currentUserService.getUser()

         @.isCurrentUser = false

-        if @.currentUser.get("id") == @.user.get("id")
+        if @.currentUser && @.currentUser.get("id") == @.user.get("id")
             @.isCurrentUser = true

+    loadContacts: () ->
         @userService.getContacts(@.user.get("id"))
             .then (contacts) =>
                 @.contacts = contacts


Add level_progress header

Add level_progress header

--- src/layout/header.coffee
+++ src/layout/header.coffee
@@ -3,7 +3,7 @@
 {dialogs} = require './dialogs'

 exports.header = (level) ->
-
+
   track = (event, key) ->
     dict = { _LABEL: key, _EXPORTS: [event], name: (world) -> world.get(key) }
     dict[key] = level.get(key) or 0
@@ -11,7 +11,7 @@
       offset = if (args == -1) then -1 else 1
       world.put key, world.get(key) + offset
     dict
-
+
   metric = (event) ->
     key = "#{event}s"
     make.columns key, [
@@ -19,12 +19,13 @@
       track(event, key)
       "/ #{level.get('goal')[key]}"
     ]
-
+
   make.rows 'header', [
     make.columns 'progress', [
       "Level #{level.get('level_index')} of #{level.get('level_count')}"
       dialogs(level)
     ]
+    { name: 'Progress', _LABEL: 'level_progress' }
     make.columns 'stats', [
       metric 'click'
       metric 'brick'


don't focus editor on ReplyUpdateWindow

don't focus editor on ReplyUpdateWindow

--- client/editor.coffee
+++ client/editor.coffee
@@ -201,7 +201,7 @@
   updateSize: -> @cm.setSize @$e.width(), @$e.parent().height() - @$e.position().top - 28; return

   open: (ee) ->
-    @cm.setValue @oText = ee.text; @cm.clearHistory(); @cm.focus()
+    @cm.setValue @oText = ee.text; @cm.clearHistory()
     # Constants for entityType:
     # DefinedFunction    1
     # SimpleCharArray    2


Update team information

Update team information

--- client/landing/About/team.coffee
+++ client/landing/About/team.coffee
@@ -25,7 +25,7 @@
         username  : 'sent-hil'
         title     : 'Software Engineer'
       ,
-        username  : 'siesta'
+        username  : 'cihangirsavas'
         title     : 'Software Engineer'
       ,
         username  : 'geraint'
@@ -37,7 +37,7 @@
         username  : 'erdinc'
         title     : 'Software Engineer'
       ,
-        username  : 'sonmez'
+        username  : 'szkl'
         title     : 'Software Engineer'
       ,
         username  : 'samet'
@@ -46,7 +46,7 @@
         username  : 'cenk6'
         title     : 'Software Engineer'
       ,
-        username  : 'neoberg'
+        username  : 'burakcan'
         title     : 'CSS Wizard'
       ,
         username  : 'emred'
@@ -68,7 +68,7 @@
         username  : 'meelash'
         title     : 'Software Engineer'
       ,
-        username  : 'rgoodness'
+        username  : 'rgoodman'
         title     : 'Designer'
       ,
         username  : 'sntran'


only set name if null and billing address name exists

--- client/skr/models/Payment.coffee
+++ client/skr/models/Payment.coffee
@@ -51,6 +51,6 @@

     onSetVendor: ->
         return unless @vendor and @vendor.isPersistent()
-        @name = @vendor.billing_address.name
+        @name ?= @vendor.billing_address.name if @vendor.billing_address.name?
         @vendor.withAssociations(['billing_address']).then (v) =>
             @address = v.billing_address.toString()


Make runtime communications more robust

Make runtime communications more robust

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -5,6 +5,7 @@
     @editor = null
     @runtime = null
     @connected = false
+    @project = null
     @inPorts =
       editor: new noflo.Port 'object'
       project: new noflo.Port 'object'
@@ -13,16 +14,14 @@
     @outPorts =
       editor: new noflo.Port 'object'

-    @inPorts.editor.on 'connect', =>
-      @editor = null
     @inPorts.editor.on 'data', (@editor) =>
       @connect @editor, @runtime
       if @outPorts.editor.isAttached()
         @outPorts.editor.send @editor
         @outPorts.editor.disconnect()
-    @inPorts.project.on 'data', (data) =>
-      @runtime.on 'connected', =>
-        @sendProject @runtime, data
+      if @runtime
+        @editor = null
+    @inPorts.project.on 'data', (@project) =>
     @inPorts.newgraph.on 'data', (data) =>
       @sendGraph @runtime, data
     @inPorts.runtime.on 'connect', =>
@@ -30,7 +29,9 @@
     @inPorts.runtime.on 'data', (runtime) =>
       @runtime.stop() if @runtime
       @runtime = runtime
-      @connect @editor, @runtime
+      if @editor
+        @connect @editor, @runtime
+        @editor = null

   sendProject: (runtime, project) ->
     if project.components
@@ -38,7 +39,6 @@
         @sendComponent runtime, component
     if project.graphs
       for graph in project.graphs
-        continue if graph.id is project.main
         @sendGraph runtime, graph

   sendComponent: (runtime, component) ->
@@ -146,13 +146,11 @@
     return unless editor and runtime
     runtime.on 'connected', =>
       @connected = true
-      # TODO: Read basedir from graph?
-      runtime.sendComponent 'list', 'noflo-ui-preview'
-      @sendGraph runtime, editor.toJSON()
+      runtime.sendComponent 'list', ''
+      @sendProject @runtime, @project if @project
     runtime.on 'disconnected', =>
       @connected = false
-    graph = editor.toJSON()
-    @subscribeEditor graph.id, editor, runtime
+    @subscribeEditor editor.graph.id, editor, runtime

     runtime.on 'component', (message) ->
       if message.payload.name is 'Graph' or message.payload.name is 'ReadDocument'


Send info on which idx we're sending to

Send info on which idx we're sending to

--- components/DispatchAction.coffee
+++ components/DispatchAction.coffee
@@ -26,6 +26,8 @@
   c.outPorts.add 'handle',
     datatype: 'all'
     addressable: true
+  c.outPorts.add 'handling',
+    datatype: 'integer'

   routes = ''
   c.inPorts.routes.on 'data', (data) ->
@@ -39,6 +41,7 @@
     if handler is -1
       sentTo = c.outPorts.pass
     else
+      c.outPorts.handling.send handler
       sentTo = c.outPorts.handle
       sentToIdx = handler
     sentTo.send data, sentToIdx


fix a relative path issue on the geocode proxy

--- src/model-geocode.coffee
+++ src/model-geocode.coffee
@@ -6,7 +6,7 @@
   initialize: (options) ->
     @set "key", options.apiKey or null
     @set "baseUrl", "http://dev.virtualearth.net/REST/v1/Locations/"
-    @set "proxy", options.proxy or "/proxy"
+    @set "proxy", options.proxy or "proxy"

   # Utilize Bing Geocoding
   getLocation: (query, callback) ->


fix https://github.com/sagemathinc/smc/issues/1766 -- "course panels/add_folders: paths.map not always a function"

 - there was some data type confusion
 - there was some half-way change to the behavior of the UI wrt default selection,  which further broke things (probably from refactoring to add handouts...)

--- src/smc-webapp/course/common.cjsx
+++ src/smc-webapp/course/common.cjsx
@@ -1,3 +1,5 @@
+underscore = require('underscore')
+
 # SMC libraries
 misc = require('smc-util/misc')
 {defaults, required} = misc
@@ -377,14 +379,14 @@
         item_name        : 'result'

     getInitialState: ->
-        selected_items : '' # currently selected options
-        show_selector : false
+        selected_items : [] # currently selected options
+        show_selector  : false

     shouldComponentUpdate: (newProps, newState) ->
         return newProps.search_results != @props.search_results or
             newProps.item_name != @props.item_name or
             newProps.is_searching != @props.is_searching or
-            newState.selected_items != @state.selected_items
+            not underscore.isEqual(newState.selected_items, @state.selected_items)

     componentWillReceiveProps: (newProps) ->
         @setState
@@ -392,7 +394,7 @@

     clear_and_focus_search_input: ->
         @props.clear_search()
-        @setState(selected_items:'')
+        @setState(selected_items:[])
         @refs.search_input.clear_and_focus_search_input()

     search_button: ->
@@ -451,8 +453,7 @@
                 when 0 then "Select #{@props.item_name} above"
                 when 1 then "Add selected #{@props.item_name}"
                 else "Add #{num_items_selected} #{@props.item_name}s"
-        disabled = @props.search_results.size == 0 or (@props.search_results.size >= 2 and num_items_selected == 0)
-        <Button disabled={disabled} onClick={@add_button_clicked}><Icon name="plus" /> {btn_text}</Button>
+        <Button disabled={num_items_selected == 0} onClick={@add_button_clicked}><Icon name="plus" /> {btn_text}</Button>

     render: ->
         <div>
@@ -532,7 +533,12 @@
         return directories

     submit_selected: (path_list) ->
-        @props.add_folders(path_list)
+        if path_list?
+            # If nothing is selected and the user clicks the button to "Add handout (etc)" then
+            # path_list is undefined, hence don't do this.
+            # (NOTE: I'm also going to make it so that button is disabled, which fits our
+            # UI guidelines, so there's two reasons that path_list is defined here.)
+            @props.add_folders(path_list)
         @clear_add_search()

     clear_add_search: ->


webapp/files: cont. to work on fixing dialog layout

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -59,6 +59,7 @@
     color      : COLORS.GRAY
     height     : '22px'
     margin     : '5px 3px'
+

 {FileListing, TERM_MODE_CHAR} = require("./project/file-listing")

@@ -1819,46 +1820,58 @@
             {start_index, end_index} = pager_range(file_listing_page_size, @props.page_number)
             visible_listing = listing[start_index...end_index]

-        flex_row_style =
+        FLEX_ROW_STYLE =
             display: 'flex'
             flexFlow: 'row wrap'
             justifyContent: 'space-between'
             alignItems: 'stretch'

-

-            {if pay? then @render_course_payment_warning(pay)}
-            {@render_error()}
-            {@render_activity()}
-            {@render_control_row(public_view, visible_listing)}
-            {<AskNewFilename
-                actions            = {@props.actions}
-                current_path       = {@props.current_path}
-                ext_selection      = {@props.ext_selection}
-                new_filename       = {@props.new_filename}
-                other_settings     = {@props.other_settings}
-            /> if @props.ext_selection}
-            {@render_new()}
-
-

-

-                    {@render_files_actions(listing, public_view, project_is_running) if listing?}
+
+        # be careful with adding height:'100%'. it could cause flex to miscalc. see #3904
+

+

+                {if pay? then @render_course_payment_warning(pay)}
+                {@render_error()}
+                {@render_activity()}
+                {@render_control_row(public_view, visible_listing)}
+                {<AskNewFilename
+                    actions            = {@props.actions}
+                    current_path       = {@props.current_path}
+                    ext_selection      = {@props.ext_selection}
+                    new_filename       = {@props.new_filename}
+                    other_settings     = {@props.other_settings}
+                /> if @props.ext_selection}
+                {@render_new()}
+
+

+

+                        {@render_files_actions(listing, public_view, project_is_running) if listing?}
+

+                    {@render_project_files_buttons(public_view)}

-                {@render_project_files_buttons(public_view)}
+
+                {@render_custom_software_reset() if project_is_running}
+
+                {@render_library() if @props.show_library}
+
+                {if @props.checked_files.size > 0 and @props.file_action?
+                    <Row>
+                        {@render_files_action_box(file_map, public_view)}
+                    </Row>
+                }

-
-            {@render_custom_software_reset() if project_is_running}
-
-            {@render_library() if @props.show_library}
-
-            {if @props.checked_files.size > 0 and @props.file_action?
-                <Row>
-                    {@render_files_action_box(file_map, public_view)}
-                </Row>
-            }
-
-            {### Only show the access error if there is not another error. ###}
-            {@render_access_error() if public_view and not error}
-            {@render_file_listing(visible_listing, file_map, error, project_state, public_view)}
-            {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
+

+
+                {### Only show the access error if there is not another error. ###}
+                {@render_access_error() if public_view and not error}
+                {@render_file_listing(visible_listing, file_map, error, project_state, public_view)}
+                {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
+


Convert Assertion scenarios to use shoulds.

--- src/Witness/Scripts/specs/Assertion.coffee
+++ src/Witness/Scripts/specs/Assertion.coffee
@@ -9,9 +9,8 @@
 	given: ->
 		@assertion = new Witness.Assertion actionThatReturns true

-	then: [
-		-> @assertion.name == "assertion-name"
-	]
+	then:
+		assertion: name: should.be "assertion-name"
 },
 {
 	given: ->
@@ -20,10 +19,9 @@
 	when: ->
 		@assertion.run {}, (=> @doneCallbackCalled = true), (-> @failCallbackCalled = true)

-	then: [
-		-> @doneCallbackCalled == true
-		-> not @failCallbackCalled
-	]
+	then:
+		doneCallbackCalled: should.be true
+		failCallbackCalled: should.be undefined
 },
 {
 	given: ->
@@ -32,10 +30,9 @@
 	when: ->
 		@assertion.run {}, (=> @doneCallbackCalled = true), (=> @failCallbackCalled = true)

-	then: [
-		-> not @doneCallbackCalled
-		-> @failCallbackCalled == true
-	]
+	then:
+		doneCallbackCalled: should.be undefined
+		failCallbackCalled: should.be true
 },
 {
 	given: ->
@@ -44,8 +41,7 @@
 	when: ->
 		@assertion.run {}, (=> @doneCallbackCalled = true), ((error) => @error = error)

-	then: [
-		-> not @doneCallbackCalled
-		-> @error.message == "custom error"
-	]
+	then:
+		doneCallbackCalled: should.be undefined
+		error: message: should.be "custom error"
 }


cleanup case

cleanup case

--- apps/home/client/auth_router.coffee
+++ apps/home/client/auth_router.coffee
@@ -19,22 +19,13 @@

     if error
       msg = switch error
-        when 'already-signed-up'
-          "You've already signed up with your email address. " +
-          "Log in to link your Facebook account in your settings."
-        when 'facebook'
+        when 'already-signed-up', 'facebook'
           "You've already signed up with your email address. " +
           "Log in to link your Facebook account in your settings."
         when 'twitter'
           "You've already signed up with your email address. " +
           "Log in to link your Twitter account in your settings."
-        when 'account-not-found'
-          "We couldn't find your account. Please sign up."
-        when 'no-user-access-token'
-          "We couldn't find your account. Please sign up."
-        when 'no-user-access-token'
-          "We couldn't find your account. Please sign up."
-        when 'no-user'
+        when 'account-not-found', 'no-user-access-token', 'no-user'
           "We couldn't find your account. Please sign up."
         else
           "Unknown error."


Revert adding quotes to form-encoded payload

--- assets/js/panes/previewer.coffee
+++ assets/js/panes/previewer.coffee
@@ -126,7 +126,7 @@
         for filename, file of session.toJSON().files
           field = document.createElement("input")
           field.setAttribute "type", "hidden"
-          field.setAttribute "name", "files['#{filename}'][content]"
+          field.setAttribute "name", "files[#{filename}][content]"
           field.setAttribute "value", file.content

           form.appendChild(field)


fixed e provider error

fixed e provider error

--- app/assets/javascripts/angular/shared/submit_form.js.coffee
+++ app/assets/javascripts/angular/shared/submit_form.js.coffee
@@ -1,6 +1,6 @@
 #= require jquery.form.json.js.coffee

-appModule.service '$FormService', ($http) ->
+appModule.service '$FormService', ['$http', ($http)] ->
   class Form
     submit: ($form) ->
       $http


Custom survey animation will now only affect inputs on survey pages

--- app/assets/javascripts/cleantheme/surveyAnimation.js.coffee
+++ app/assets/javascripts/cleantheme/surveyAnimation.js.coffee
@@ -112,7 +112,7 @@
   $("[data-object~='reveal-next-input']").click (e) ->
     if this.checked then revealNextInput($(this).data('target'))

-  $('input:radio').click (event) ->
+  $('.survey-container input:radio').click (event) ->
     event.stopPropagation()
     unless $(this).data('secondary')
       $(this).prop "checked", true
@@ -126,7 +126,7 @@
       else
         assignQuestionDirect($(this).closest('.survey-container'))

-  $('input:text').click (event) ->
+  $('.survey-container input:text').click (event) ->
     event.stopPropagation()
     if $(this).data('secondary')
       setActive($(this).closest('.survey-container'))


Avoid console error in nodes without description

--- app/frontend/javascripts/views/visualization-info.js.coffee
+++ app/frontend/javascripts/views/visualization-info.js.coffee
@@ -34,7 +34,7 @@
       # set template attributes
       templateAttr = {
         name:         @model.get('name')
-        description:  @model.get('description').replace(/<iframe/g, '<iframe').replace(/<\/iframe>/g, '')
+        description:  if @model.get('description') then @model.get('description').replace(/<iframe/g, '<iframe').replace(/<\/iframe>/g, '') else @model.get('description')
         type:         @model.get('node_type')
         image:        @getImage @model
       }


don't let users to bind a domain to more than one vm

--- client/app/Applications/Environments.kdapplication/views/domains/domainsroutingview.coffee
+++ client/app/Applications/Environments.kdapplication/views/domains/domainsroutingview.coffee
@@ -87,6 +87,11 @@
     {hostnameAlias} = vm
     options         = {hostnameAlias}

+    if domain.hostnameAlias.length > 0
+      listItem.hideLoader()
+      return new KDNotificationView
+        title : "You cannot bind a domain to more than one vms."
+
     domain.bindVM options, (err)=>
       listItem.hideLoader()
       unless err
@@ -101,6 +106,8 @@
     vm              = listItem.getData()
     {hostnameAlias} = vm
     options         = {hostnameAlias}
+
+    listItem.showLoader()

     domain.unbindVM options, (err)=>
       listItem.hideLoader()


Remove syntax error

Remove syntax error

--- test/spec/updateGraph.coffee
+++ test/spec/updateGraph.coffee
@@ -10,17 +10,10 @@
             it "should be defined", ->
                 alchemy.edge.should.not.equal undefined

-            it "should contain one DOM object for each edge in dataset", ->
-                expect(alchemy.edge[0].length).to.equal 83
-
         describe "alchemy.node", ->
             alchemy.begin({'dataSource': 'sample_data/movies.json'})

             it "should be defined", ->
                 alchemy.node.should.not.equal undefined
-
-            it "should contain one DOM object for each node in dataset", ->
-                expect(alchemy.node[0].length).to.equal 69
-
         return
     return


JSAlertView update

JSAlertView update

--- JSKit/03_JSAlertView.coffee
+++ JSKit/03_JSAlertView.coffee
@@ -42,6 +42,7 @@
 				if (@delegate? && typeof @delegate.clickedButtonAtIndex == "function")
 					switch @_style
 						when "JSAlertViewStylePlainTextInput"
+							@cancel = true
 							arr = []
 							for i in [0...@_param.length]
 								t = $(@_viewSelector+"_textfield_"+i).val()
@@ -49,6 +50,7 @@
 							text = JSON.stringify(arr)
 							@delegate.clickedButtonAtIndex(text, @_self)
 						when "JSAlertViewStyleDefault"
+							@cancel = false
 							@delegate.clickedButtonAtIndex(1, @_self)
 				$(@_viewSelector+"_form").dialog("close")
 				@_self.removeFromSuperview()


convert js to coffee

convert js to coffee

--- atom/common/api/lib/bignum.coffee
+++ atom/common/api/lib/bignum.coffee
@@ -1,446 +1,354 @@
 cc = process.atomBinding 'bignum'

-var BigNum = cc.BigNum;
-
-module.exports = BigNum;
-
-BigNum.conditionArgs = function(num, base) {
-    if (typeof num !== 'string') num = num.toString(base || 10);
-
-    if (num.match(/e\+/)) { // positive exponent
-        if (!Number(num).toString().match(/e\+/)) {
-        return {
-            num: Math.floor(Number(num)).toString(),
-            base: 10
-        };
+BigNum = cc.BigNum
+module.exports = BigNum
+
+BigNum.conditionArgs = (num, base) ->
+  if typeof num != 'string'
+    num = num.toString(base or 10)
+  if num.match(/e\+/)
+    # positive exponent
+    if !Number(num).toString().match(/e\+/)
+      {
+        num: Math.floor(Number(num)).toString()
+        base: 10
+      }
+    else
+      pow = Math.ceil(Math.log(num) / Math.log(2))
+      n = (num / 2 ** pow).toString(2).replace(/^0/, '')
+      i = n.length - n.indexOf('.')
+      n = n.replace(/\./, '')
+      while i <= pow
+        n += '0'
+        i++
+      {
+        num: n
+        base: 2
+      }
+  else if num.match(/e\-/)
+    # negative exponent
+    {
+      num: Math.floor(Number(num)).toString()
+      base: base or 10
     }
-    else {
-        var pow = Math.ceil(Math.log(num) / Math.log(2));
-        var n = (num / Math.pow(2, pow)).toString(2)
-            .replace(/^0/,'');
-        var i = n.length - n.indexOf('.');
-        n = n.replace(/\./,'');
-
-        for (; i <= pow; i++) n += '0';
-           return {
-               num : n,
-               base : 2,
-           };
-        }
+  else
+    {
+      num: num
+      base: base or 10
     }
-    else if (num.match(/e\-/)) { // negative exponent
-        return {
-            num : Math.floor(Number(num)).toString(),
-            base : base || 10
-        };
-    }
-    else {
-        return {
-            num : num,
-            base : base || 10,
-        };
-    }
-};
-
-cc.setJSConditioner(BigNum.conditionArgs);
-
-BigNum.isBigNum = function(num) {
-    if (!num) {
-        return false;
-    }
-    for(var key in BigNum.prototype) {
-        if (!num[key]) {
-            return false;
-        }
-    }
-    return true;
-};
-
-BigNum.prototype.inspect = function () {
-    return '<BigNum ' + this.toString(10) + '>';
-};
-
-BigNum.prototype.toString = function (base) {
-    var value;
-    if (base) {
-        value = this.tostring(base);
-    } else {
-        value = this.tostring();
-    }
-    if (base > 10 && "string" === typeof value) {
-      value = value.toLowerCase();
-    }
-    return value;
-};
-
-BigNum.prototype.toNumber = function () {
-    return parseInt(this.toString(), 10);
-};
-
-[ 'add', 'sub', 'mul', 'div', 'mod' ].forEach(function (op) {
-    BigNum.prototype[op] = function (num) {
-        if (BigNum.isBigNum(num)) {
-            return this['b'+op](num);
-        }
-        else if (typeof num === 'number') {
-            if (num >= 0) {
-                return this['u'+op](num);
-            }
-            else if (op === 'add') {
-                return this.usub(-num);
-            }
-            else if (op === 'sub') {
-                return this.uadd(-num);
-            }
-            else {
-                var x = BigNum(num);
-                return this['b'+op](x);
-            }
-        }
-        else if (typeof num === 'string') {
-            var x = BigNum(num);
-            return this['b'+op](x);
-        }
-        else {
-            throw new TypeError('Unspecified operation for type '
-                + (typeof num) + ' for ' + op);
-        }
-    };
-});
-
-BigNum.prototype.abs = function () {
-    return this.babs();
-};
-
-BigNum.prototype.neg = function () {
-    return this.bneg();
-};
-
-BigNum.prototype.powm = function (num, mod) {
-    var m, res;
-
-    if ((typeof mod) === 'number' || (typeof mod) === 'string') {
-        m = BigNum(mod);
-    }
-    else if (BigNum.isBigNum(mod)) {
-        m = mod;
-    }
-
-    if ((typeof num) === 'number') {
-        return this.upowm(num, m);
-    }
-    else if ((typeof num) === 'string') {
-        var n = BigNum(num);
-        return this.bpowm(n, m);
-    }
-    else if (BigNum.isBigNum(num)) {
-        return this.bpowm(num, m);
-    }
-};
-
-BigNum.prototype.mod = function (num, mod) {
-    var m, res;
-
-    if ((typeof mod) === 'number' || (typeof mod) === 'string') {
-        m = BigNum(mod);
-    }
-    else if (BigNum.isBigNum(mod)) {
-        m = mod;
-    }
-
-    if ((typeof num) === 'number') {
-        return this.umod(num, m);
-    }
-    else if ((typeof num) === 'string') {
-        var n = BigNum(num);
-        return this.bmod(n, m);
-    }
-    else if (BigNum.isBigNum(num)) {
-        return this.bmod(num, m);
-    }
-};
-
-
-BigNum.prototype.pow = function (num) {
-    if (typeof num === 'number') {
-        if (num >= 0) {
-            return this.upow(num);
-        }
-        else {
-            return BigNum.prototype.powm.call(this, num, this);
-        }
-    }
-    else {
-        var x = parseInt(num.toString(), 10);
-        return BigNum.prototype.pow.call(this, x);
-    }
-};
-
-BigNum.prototype.shiftLeft = function (num) {
-    if (typeof num === 'number') {
-        if (num >= 0) {
-            return this.umul2exp(num);
-        }
-        else {
-            return this.shiftRight(-num);
-        }
-    }
-    else {
-        var x = parseInt(num.toString(), 10);
-        return BigNum.prototype.shiftLeft.call(this, x);
-    }
-};
-
-BigNum.prototype.shiftRight = function (num) {
-    if (typeof num === 'number') {
-        if (num >= 0) {
-            return this.udiv2exp(num);
-        }
-        else {
-            return this.shiftLeft(-num);
-        }
-    }
-    else {
-        var x = parseInt(num.toString(), 10);
-        return BigNum.prototype.shiftRight.call(this, x);
-    }
-};
-
-BigNum.prototype.cmp = function (num) {
-    if (BigNum.isBigNum(num)) {
-        return this.bcompare(num);
-    }
-    else if (typeof num === 'number') {
-        if (num < 0) {
-            return this.scompare(num);
-        }
-        else {
-            return this.ucompare(num);
-        }
-    }
-    else {
-        var x = BigNum(num);
-        return this.bcompare(x);
-    }
-};
-
-BigNum.prototype.gt = function (num) {
-    return this.cmp(num) > 0;
-};
-
-BigNum.prototype.ge = function (num) {
-    return this.cmp(num) >= 0;
-};
-
-BigNum.prototype.eq = function (num) {
-    return this.cmp(num) === 0;
-};
-
-BigNum.prototype.ne = function (num) {
-    return this.cmp(num) !== 0;
-};
-
-BigNum.prototype.lt = function (num) {
-    return this.cmp(num) < 0;
-};
-
-BigNum.prototype.le = function (num) {
-    return this.cmp(num) <= 0;
-};
-
-'and or xor'.split(' ').forEach(function (name) {
-    BigNum.prototype[name] = function (num) {
-        if (BigNum.isBigNum(num)) {
-            return this['b' + name](num);
-        }
-        else {
-            var x = BigNum(num);
-            return this['b' + name](x);
-        }
-    };
-});
-
-BigNum.prototype.sqrt = function() {
-    return this.bsqrt();
-};
-
-BigNum.prototype.root = function(num) {
-    if (BigNum.isBigNum(num)) {
-        return this.broot(num);
-    }
-    else {
-        var x = BigNum(num);
-        return this.broot(num);
-    }
-};
-
-BigNum.prototype.rand = function (to) {
-    if (to === undefined) {
-        if (this.toString() === '1') {
-            return BigNum(0);
-        }
-        else {
-            return this.brand0();
-        }
-    }
-    else {
-        var x = BigNum.isBigNum(to)
-            ? to.sub(this)
-            : BigNum(to).sub(this);
-        return x.brand0().add(this);
-    }
-};
-
-BigNum.prototype.invertm = function (mod) {
-    if (BigNum.isBigNum(mod)) {
-        return this.binvertm(mod);
-    }
-    else {
-        var x = BigNum(mod);
-        return this.binvertm(x);
-    }
-};
-
-BigNum.prime = function (bits, safe) {
-  if ("undefined" === typeof safe) {
-    safe = true;
-  }
-
-  // Force uint32
-  bits >>>= 0;
-
-  return BigNum.uprime0(bits, !!safe);
-};
-
-BigNum.prototype.probPrime = function (reps) {
-    var n = this.probprime(reps || 10);
-    return { 1 : true, 0 : false }[n];
-};
-
-BigNum.prototype.nextPrime = function () {
-    var num = this;
-    do {
-        num = num.add(1);
-    } while (!num.probPrime());
-    return num;
-};
-
-BigNum.prototype.isBitSet = function (n) {
-    return this.isbitset(n) === 1;
-};
-
-BigNum.fromBuffer = function (buf, opts) {
-    if (!opts) opts = {};
-
-    var endian = { 1 : 'big', '-1' : 'little' }[opts.endian]
-        || opts.endian || 'big'
-    ;
-
-    var size = opts.size === 'auto' ? Math.ceil(buf.length) : (opts.size || 1);
-
-    if (buf.length % size !== 0) {
-        throw new RangeError('Buffer length (' + buf.length + ')'
-            + ' must be a multiple of size (' + size + ')'
-        );
-    }
-
-    var hex = [];
-    for (var i = 0; i < buf.length; i += size) {
-        var chunk = [];
-        for (var j = 0; j < size; j++) {
-            chunk.push(buf[
-                i + (endian === 'big' ? j : (size - j - 1))
-            ]);
-        }
-
-        hex.push(chunk
-            .map(function (c) {
-                return (c < 16 ? '0' : '') + c.toString(16);
-            })
-            .join('')
-        );
-    }
-
-    return BigNum(hex.join(''), 16);
-};
-
-BigNum.prototype.toBuffer = function (opts) {
-    if (typeof opts === 'string') {
-        if (opts !== 'mpint') return 'Unsupported Buffer representation';
-
-        var abs = this.abs();
-        var buf = abs.toBuffer({ size : 1, endian : 'big' });
-        var len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length;
-        if (buf[0] & 0x80) len ++;
-
-        var ret = new Buffer(4 + len);
-        if (len > 0) buf.copy(ret, 4 + (buf[0] & 0x80 ? 1 : 0));
-        if (buf[0] & 0x80) ret[4] = 0;
-
-        ret[0] = len & (0xff << 24);
-        ret[1] = len & (0xff << 16);
-        ret[2] = len & (0xff << 8);
-        ret[3] = len & (0xff << 0);
-
-        // two's compliment for negative integers:
-        var isNeg = this.lt(0);
-        if (isNeg) {
-            for (var i = 4; i < ret.length; i++) {
-                ret[i] = 0xff - ret[i];
-            }
-        }
-        ret[4] = (ret[4] & 0x7f) | (isNeg ? 0x80 : 0);
-        if (isNeg) ret[ret.length - 1] ++;
-
-        return ret;
-    }
-
-    if (!opts) opts = {};
-
-    var endian = { 1 : 'big', '-1' : 'little' }[opts.endian]
-        || opts.endian || 'big'
-    ;
-
-    var hex = this.toString(16);
-    if (hex.charAt(0) === '-') throw new Error(
-        'converting negative numbers to Buffers not supported yet'
-    );
-
-    var size = opts.size === 'auto' ? Math.ceil(hex.length / 2) : (opts.size || 1);
-
-    var len = Math.ceil(hex.length / (2 * size)) * size;
-    var buf = new Buffer(len);
-
-    // zero-pad the hex string so the chunks are all `size` long
-    while (hex.length < 2 * len) hex = '0' + hex;
-
-    var hx = hex
-        .split(new RegExp('(.{' + (2 * size) + '})'))
-        .filter(function (s) { return s.length > 0 })
-    ;
-
-    hx.forEach(function (chunk, i) {
-        for (var j = 0; j < size; j++) {
-            var ix = i * size + (endian === 'big' ? j : size - j - 1);
-            buf[ix] = parseInt(chunk.slice(j*2,j*2+2), 16);
-        }
-    });
-
-    return buf;
-};
-
-Object.keys(BigNum.prototype).forEach(function (name) {
-    if (name === 'inspect' || name === 'toString') return;
-
-    BigNum[name] = function (num) {
-        var args = [].slice.call(arguments, 1);
-
-        if (BigNum.isBigNum(num)) {
-            return num[name].apply(num, args);
-        }
-        else {
-            var bigi = BigNum(num);
-            return bigi[name].apply(bigi, args);
-        }
-    };
-});
+
+cc.setJSConditioner BigNum.conditionArgs
+
+BigNum.isBigNum = (num) ->
+  if !num
+    return false
+  for key of BigNum.prototype
+    if !num[key]
+      return false
+  true
+
+BigNum::inspect = ->
+  '<BigNum ' + @toString(10) + '>'
+
+BigNum::toString = (base) ->
+  value = undefined
+  if base
+    value = @tostring(base)
+  else
+    value = @tostring()
+  if base > 10 and 'string' == typeof value
+    value = value.toLowerCase()
+  value
+
+BigNum::toNumber = ->
+  parseInt @toString(), 10
+
+[
+  'add'
+  'sub'
+  'mul'
+  'div'
+  'mod'
+].forEach (op) ->
+
+  BigNum.prototype[op] = (num) ->
+    `var x`
+    if BigNum.isBigNum(num)
+      return @['b' + op](num)
+    else if typeof num == 'number'
+      if num >= 0
+        return @['u' + op](num)
+      else if op == 'add'
+        return @usub(-num)
+      else if op == 'sub'
+        return @uadd(-num)
+      else
+        x = BigNum(num)
+        return @['b' + op](x)
+    else if typeof num == 'string'
+      x = BigNum(num)
+      return @['b' + op](x)
+    else
+      throw new TypeError('Unspecified operation for type ' + typeof num + ' for ' + op)
+    return
+
+  return
+
+BigNum::abs = ->
+  @babs()
+
+BigNum::neg = ->
+  @bneg()
+
+BigNum::powm = (num, mod) ->
+  m = undefined
+  res = undefined
+  if typeof mod == 'number' or typeof mod == 'string'
+    m = BigNum(mod)
+  else if BigNum.isBigNum(mod)
+    m = mod
+  if typeof num == 'number'
+    return @upowm(num, m)
+  else if typeof num == 'string'
+    n = BigNum(num)
+    return @bpowm(n, m)
+  else if BigNum.isBigNum(num)
+    return @bpowm(num, m)
+  return
+
+BigNum::mod = (num, mod) ->
+  m = undefined
+  res = undefined
+  if typeof mod == 'number' or typeof mod == 'string'
+    m = BigNum(mod)
+  else if BigNum.isBigNum(mod)
+    m = mod
+  if typeof num == 'number'
+    return @umod(num, m)
+  else if typeof num == 'string'
+    n = BigNum(num)
+    return @bmod(n, m)
+  else if BigNum.isBigNum(num)
+    return @bmod(num, m)
+  return
+
+BigNum::pow = (num) ->
+  if typeof num == 'number'
+    if num >= 0
+      @upow num
+    else
+      BigNum::powm.call this, num, this
+  else
+    x = parseInt(num.toString(), 10)
+    BigNum::pow.call this, x
+
+BigNum::shiftLeft = (num) ->
+  if typeof num == 'number'
+    if num >= 0
+      @umul2exp num
+    else
+      @shiftRight -num
+  else
+    x = parseInt(num.toString(), 10)
+    BigNum::shiftLeft.call this, x
+
+BigNum::shiftRight = (num) ->
+  if typeof num == 'number'
+    if num >= 0
+      @udiv2exp num
+    else
+      @shiftLeft -num
+  else
+    x = parseInt(num.toString(), 10)
+    BigNum::shiftRight.call this, x
+
+BigNum::cmp = (num) ->
+  if BigNum.isBigNum(num)
+    @bcompare num
+  else if typeof num == 'number'
+    if num < 0
+      @scompare num
+    else
+      @ucompare num
+  else
+    x = BigNum(num)
+    @bcompare x
+
+BigNum::gt = (num) ->
+  @cmp(num) > 0
+
+BigNum::ge = (num) ->
+  @cmp(num) >= 0
+
+BigNum::eq = (num) ->
+  @cmp(num) == 0
+
+BigNum::ne = (num) ->
+  @cmp(num) != 0
+
+BigNum::lt = (num) ->
+  @cmp(num) < 0
+
+BigNum::le = (num) ->
+  @cmp(num) <= 0
+
+'and or xor'.split(' ').forEach (name) ->
+
+  BigNum.prototype[name] = (num) ->
+    if BigNum.isBigNum(num)
+      @['b' + name] num
+    else
+      x = BigNum(num)
+      @['b' + name] x
+
+  return
+
+BigNum::sqrt = ->
+  @bsqrt()
+
+BigNum::root = (num) ->
+  if BigNum.isBigNum(num)
+    @broot num
+  else
+    x = BigNum(num)
+    @broot num
+
+BigNum::rand = (to) ->
+  if to == undefined
+    if @toString() == '1'
+      BigNum 0
+    else
+      @brand0()
+  else
+    x = if BigNum.isBigNum(to) then to.sub(this) else BigNum(to).sub(this)
+    x.brand0().add this
+
+BigNum::invertm = (mod) ->
+  if BigNum.isBigNum(mod)
+    @binvertm mod
+  else
+    x = BigNum(mod)
+    @binvertm x
+
+BigNum.prime = (bits, safe) ->
+  if 'undefined' == typeof safe
+    safe = true
+  # Force uint32
+  bits >>>= 0
+  BigNum.uprime0 bits, ! !safe
+
+BigNum::probPrime = (reps) ->
+  n = @probprime(reps or 10)
+  {
+    1: true
+    0: false
+  }[n]
+
+BigNum::nextPrime = ->
+  num = this
+  loop
+    num = num.add(1)
+    unless !num.probPrime()
+      break
+  num
+
+BigNum::isBitSet = (n) ->
+  @isbitset(n) == 1
+
+BigNum.fromBuffer = (buf, opts) ->
+  if !opts
+    opts = {}
+  endian = {
+    1: 'big'
+    '-1': 'little'
+  }[opts.endian] or opts.endian or 'big'
+  size = if opts.size == 'auto' then Math.ceil(buf.length) else opts.size or 1
+  if buf.length % size != 0
+    throw new RangeError('Buffer length (' + buf.length + ')' + ' must be a multiple of size (' + size + ')')
+  hex = []
+  i = 0
+  while i < buf.length
+    chunk = []
+    j = 0
+    while j < size
+      chunk.push buf[i + (if endian == 'big' then j else size - j - 1)]
+      j++
+    hex.push chunk.map((c) ->
+      (if c < 16 then '0' else '') + c.toString(16)
+    ).join('')
+    i += size
+  BigNum hex.join(''), 16
+
+BigNum::toBuffer = (opts) ->
+  `var buf`
+  `var len`
+  if typeof opts == 'string'
+    if opts != 'mpint'
+      return 'Unsupported Buffer representation'
+    abs = @abs()
+    buf = abs.toBuffer(
+      size: 1
+      endian: 'big')
+    len = if buf.length == 1 and buf[0] == 0 then 0 else buf.length
+    if buf[0] & 0x80
+      len++
+    ret = new Buffer(4 + len)
+    if len > 0
+      buf.copy ret, 4 + (if buf[0] & 0x80 then 1 else 0)
+    if buf[0] & 0x80
+      ret[4] = 0
+    ret[0] = len & 0xff << 24
+    ret[1] = len & 0xff << 16
+    ret[2] = len & 0xff << 8
+    ret[3] = len & 0xff << 0
+    # two's compliment for negative integers:
+    isNeg = @lt(0)
+    if isNeg
+      i = 4
+      while i < ret.length
+        ret[i] = 0xff - (ret[i])
+        i++
+    ret[4] = ret[4] & 0x7f | (if isNeg then 0x80 else 0)
+    if isNeg
+      ret[ret.length - 1]++
+    return ret
+  if !opts
+    opts = {}
+  endian = {
+    1: 'big'
+    '-1': 'little'
+  }[opts.endian] or opts.endian or 'big'
+  hex = @toString(16)
+  if hex.charAt(0) == '-'
+    throw new Error('converting negative numbers to Buffers not supported yet')
+  size = if opts.size == 'auto' then Math.ceil(hex.length / 2) else opts.size or 1
+  len = Math.ceil(hex.length / (2 * size)) * size
+  buf = new Buffer(len)
+  # zero-pad the hex string so the chunks are all `size` long
+  while hex.length < 2 * len
+    hex = '0' + hex
+  hx = hex.split(new RegExp('(.{' + 2 * size + '})')).filter((s) ->
+    s.length > 0
+  )
+  hx.forEach (chunk, i) ->
+    j = 0
+    while j < size
+      ix = i * size + (if endian == 'big' then j else size - j - 1)
+      buf[ix] = parseInt(chunk.slice(j * 2, j * 2 + 2), 16)
+      j++
+    return
+  buf
+
+Object.keys(BigNum.prototype).forEach (name) ->
+  if name == 'inspect' or name == 'toString'
+    return
+
+  BigNum[name] = (num) ->
+    args = [].slice.call(arguments, 1)
+    if BigNum.isBigNum(num)
+      num[name].apply num, args
+    else
+      bigi = BigNum(num)
+      bigi[name].apply bigi, args
+
+  return


browser: fix value of document.hidden

browser: fix value of document.hidden

--- atom/renderer/lib/override.coffee
+++ atom/renderer/lib/override.coffee
@@ -118,6 +118,12 @@
   get: ->
     getHistoryOperation 'length'

-# Make document.hidden return the correct value.
+# Make document.hidden and document.visibilityState return the correct value.
 Object.defineProperty document, 'hidden',
-  get: -> !remote.getCurrentWindow().isVisible()
+  get: ->
+    currentWindow = remote.getCurrentWindow()
+    !currentWindow.isFocused() || !currentWindow.isVisible()
+
+Object.defineProperty document, 'visibilityState',
+  get: ->
+    if document.hidden then "hidden" else "visible"


Lipstick

Lipstick

--- src/extensions/command-panel/spec/command-interpreter-spec.coffee
+++ src/extensions/command-panel/spec/command-interpreter-spec.coffee
@@ -185,7 +185,6 @@
           interpreter.eval('/Array', editSession)
         runs ->
           expect(interpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/Array/"
-

     describe "address range", ->
       describe "when two addresses are specified", ->


Added silent options to each view method

--- src/readmill/view.coffee
+++ src/readmill/view.coffee
@@ -75,26 +75,34 @@
   # @login() once the user has authenticated and the user object has been
   # retrieved from the server.
   #
+  # options - An object literal of method options.
+  #           silent - If true the method will not publish any events.
+  #
   # Examples
   #
   #   view.connect()
   #
   # Returns itself.
-  connect: ->
-    @publish "connect", [this]
+  connect: (options={}) ->
+    @publish "connect", [this] unless options.silent
+    this

   # Public: Triggers the "disconnect" event passing in the View instance to
   # all registered listeners. This is called when the user wishes to
   # logout of the Readmill service. The implmentor should then call
   # @logout() once the user has been logged out from Readmill.
   #
+  # options - An object literal of method options.
+  #           silent - If true the method will not publish any events.
+  #
   # Examples
   #
   #   view.disconnect()
   #
   # Returns itself.
-  disconnect: ->
-    @publish "disconnect", [this]
+  disconnect: (options={}) ->
+    @publish "disconnect", [this] unless options.silent
+    this

   # Public: Updates the view to the "logged in" state. Showing the log out
   # button and user details. This should be called once the user has been
@@ -102,7 +110,9 @@
   #
   # Publishes the "login" event once the view has updated.
   #
-  # user - The user object retrieved from Readmill.
+  # user    - The user object retrieved from Readmill.
+  # options - An object literal of method options.
+  #           silent - If true the method will not publish any events.
   #
   # Examples
   #
@@ -110,32 +120,40 @@
   #   view.login user
   #
   # Returns itself.
-  login: (user) ->
+  login: (user, options={}) ->
     @updateUser(user) if user
     @updateState(@states.START_READING)
     @element.addClass @classes.loggedIn
-    @publish "login", [this]
+    @publish("login", [this]) unless options.silent
+    this

   # Public: Switches the current view state to reading. This should be called
   # once the user decides to start a reading session.
   #
   # Publishes the "reading" event once the view has updated.
   #
-  # Returns itself.
-  reading: ->
+  # options - An object literal of method options.
+  #           silent - If true the method will not publish any events.
+  #
+  # Returns itself.
+  reading: (options={}) ->
     @updateState(@states.NOW_READING)
-    @publish "reading", [this]
+    @publish("reading", [this]) unless options.silent
+    this

   # Public: Switches the current view state to "finish". This should be called
   # once a user decides to end a reading session.
   #
   # Publishes the "finish" event once the view has updated.
   #
+  # options - An object literal of method options.
+  #           silent - If true the method will not publish any events.
+  #
   # param - comment
   #
   # Returns itself.
-  finish: ->
-    @publish "finish", [this]
+  finish: (options={}) ->
+    @publish("finish", [this]) unless options.silent
     @login()
     this

@@ -145,19 +163,23 @@
   #
   # Publishes the "logout" event once the view has updated.
   #
+  # options - An object literal of method options.
+  #           silent - If true the method will not publish any events.
+  #
   # Examples
   #
   #   view.on "disconnect" ->
   #     view.logout()
   #
   # Returns itself.
-  logout: ->
+  logout: (options={}) ->
     @element.removeClass(@classes.loggedIn).html(@template)

     @user = null
     @updateBook()

-    @publish "logout", [this]
+    @publish("logout", [this]) unless options.silent
+    this

   # Public: Updates the user elements within the view. Will update the @user
   # property or simply use the current value. This method will not transition


Tweaks to hound listeners

Tweaks to hound listeners

--- src/scripts/hound.coffee
+++ src/scripts/hound.coffee
@@ -103,9 +103,9 @@
         status += "recently (#{last.time.format('MMM Do, YYYY h:mma')})"
         Logger.debug status

-  robot.adapter.client.on 'userTyping', (user, channel) ->
+  robot.adapter.client.on 'userTyping', id: "hound.userTyping", (user, channel) ->
     hound user, channel
-  robot.adapter.client.on 'presenceChange', (user, status) ->
+  robot.adapter.client.on 'presenceChange', id: "hound.presenceChange", (user, status) ->
     hound user, { private: null, name: '' }

   # Every morning, reset hound status for each users
@@ -127,7 +127,7 @@
                 hound status for the morning"
     Logger.logToChannel response, 'ibizan-diagnostics'

-  robot.respond /hound\s*(.*)?$/i, id: 'hound', userRequired: true, (res) ->
+  robot.respond /hound\s*(.*)?$/i, id: 'hound.hound', userRequired: true, (res) ->
     user = Organization.getUserBySlackName res.message.user.name

     command = res.match[1]


Default string to ''

Default string to ''

CourseInformation.forAppearanceCode can call this with undefined when a
course's appearance code is missing

--- tutor/src/helpers/string.coffee
+++ tutor/src/helpers/string.coffee
@@ -26,7 +26,7 @@
     string.replace(/\W+/g, '').replace /([A-Z])/g, ($1) -> "-#{$1.toLowerCase()}"

   # originated from http://individed.com/code/to-title-case/
-  titleize: (string) ->
+  titleize: (string = '') ->
     string
       .replace(/_/g, ' ')
       .replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, (match, index, title) ->


Add batman replacement

Add batman replacement

--- src/substitutions.coffee
+++ src/substitutions.coffee
@@ -128,6 +128,10 @@
     ['Horsepower', 'Tons of horsemeat']
     ['HORSEPOWER', 'TONS OF HORSEMEAT']
     ['horsepower', 'tons of horsemeat']
+    # https://xkcd.com/1004
+    ["Batman", "A man dressed like a bat"]
+    ["BATMAN", "A MAN DRESSED LIKE A BAT"]
+    ["batman", "a man dressed like a bat"]
   ]

   ignore = ['STYLE', 'SCRIPT', 'NOSCRIPT', 'IFRAME', 'OBJECT', 'INPUT', 'FORM', 'TEXTAREA']


Added support for reactive defaults

Added support for reactive defaults

--- lib/bootstrap-magic-client.coffee
+++ lib/bootstrap-magic-client.coffee
@@ -1,4 +1,6 @@
-BootstrapMagicOverrides = new ReactiveDict
+reactive =
+  overrides : new ReactiveDict
+  defaults : new ReactiveDict

 @BootstrapMagic =
   on : (eventName, callback) ->
@@ -7,38 +9,56 @@
   off : (eventName) ->
     delete @[eventName]

-  overrides: ->
-    return BootstrapMagicOverrides.keys
-
-  setOverride : (key,val,noFire) ->
-    BootstrapMagicOverrides.set key, val
+  setOne: (dictionary, key, val) ->
+    reactive[dictionary].set key, val
     obj = {}
     obj[key] = val
-    unless noFire
-      @change obj if @change
+    @change obj if @change

-  setOverrides: (obj) ->
+  setMany: (dictionary, obj) ->
     for key,val of obj
-      BootstrapMagicOverrides.set key, val
+      reactive[dictionary].set key, val
+
+  dictionary: reactive
+
+  setOverride : (key, val) -> @setOne 'overrides', key, val
+  setOverrides : (obj) -> @setMany 'overrides', obj
+  setDefault : (key, val) -> @setOne 'defaults', key, val
+  setDefaults : (obj) -> @setMany 'defaults', obj

-UI.registerHelper 'BootstrapMagicOverride', -> BootstrapMagicOverrides.get(@key) || @value
+
+UI.registerHelper 'BootstrapMagicOverride', ->
+  reactive.overrides.get(@key) || reactive.defaults.get(@key)
+

 format = (str, del) -> str.replace(/\s+/g, del || '-').toLowerCase()

 Template._bootstrap_magic.helpers
-  "groups" : -> bootstrap_magic_variables
+  "groups" : ->
+    for group in bootstrap_magic_variables
+      for lessVar in group.data
+        lessVar.value = reactive.defaults.keys[lessVar.key] || lessVar.value
+    return bootstrap_magic_variables
+
+
   "previewTmpl" : -> Template["bootstrap_magic_preview_#{format @name, '_'}"] || null
   "inputTmpl" : -> Template["bootstrap_magic_input_#{@type}"] || null
   "formattedName" : -> format @name
   "typeIs" : (type) -> @type is type

+Template._bootstrap_magic.created = ->
+  # trigger start event
+  BootstrapMagic.start() if BootstrapMagic.start
+
 Template._bootstrap_magic.rendered = ->
   # colorpicker's changeColor fires too often!
-  $cpg = $('.color-picker-group')
-  $cpg.colorpicker()
+  $('.color-picker-group').colorpicker
+    horizontal: true
+
   .on 'showPicker', ->
     @startVal = $('input',@).val()
+
   .on 'hidePicker', ->
     $input = $('input',@)
     @endVal = $input.val()


commit

commit

--- lib/build/build-state-view.coffee
+++ lib/build/build-state-view.coffee
@@ -40,12 +40,13 @@
         @subview 'console', new ConsoleView()

       @div class: 'actions', =>
-        @div class: 'pull-left',outlet:"cancelbutton", =>
-          @button 'Cancel', click: 'CancelFun', class: 'inline-block-tight btn'
-        @div class: 'pull-left',outlet:"closebutton",style:"display:none", =>
-          @button 'Close', click: 'CloseFun', class: 'inline-block-tight btn'
+        @div class: 'pull-left',outlet:"closebutton", =>
+          @button 'Close', click: 'destroy', class: 'inline-block-tight btn'
+
         @div class: 'pull-right',outlet:"refreshbutton", =>
           @button 'Refresh', click: 'refreshTaskState', class: 'inline-block-tight btn'
+        @div class: 'pull-right',outlet:"cancelbutton", =>
+          @button 'Cancel', click: 'cancel', class: 'inline-block-tight btn'

   initialize: ->

@@ -92,14 +93,7 @@
     @socket.on 'stderr', (out) => @console.append(out, 'text-error')

   destroy: ->
-    if @task and @task.state != 'complete'
-      puzzleClient.deleteTask @task.id
-      .then ->
-        console.log "task #{@task.id} deleted."
-      .catch ->
-        console.error "failed to delete task #{@task.id}"
-
-    @detach()
+    @remove()

   setTask: (@task) ->
     @find('.task-id').text @task.id
@@ -157,25 +151,11 @@
         console.log "status:#{status}"
       )

-
-  CloseFun:->
-    console.log "CloseFun"
-    r=confirm("关闭该窗口，意味着将停止编译");
-    if r
-      @destroy()
-    else
-      console.log "cancle"
-
   CancelFun:->
-    console.log "CancelFun"
-    if @cancelbutton.attr('class') is "pull-left"
-      @refreshbutton.hide()
-      @cancelbutton.removeClass("pull-left")
-      @cancelbutton.addClass("pull-right")
-      @closebutton.show()
-    else
-      @refreshbutton.show()
-      @cancelbutton.removeClass("pull-right")
-      @cancelbutton.addClass("pull-left")
-      @closebutton.hide()
+    if @task and @task.state != 'complete'
+      puzzleClient.deleteTask @task.id
+      .then ->
+        console.log "task #{@task.id} deleted."
+      .catch ->
+        console.error "failed to delete task #{@task.id}"


Add LICENSE button when one is available

Closes #263

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -45,6 +45,7 @@
             @button outlet: 'issueButton', class: 'btn btn-default icon icon-bug', 'Report Issue'
             @button outlet: 'readmeButton', class: 'btn btn-default icon icon-book', 'README'
             @button outlet: 'changelogButton', class: 'btn btn-default icon icon-squirrel', 'CHANGELOG'
+            @button outlet: 'licenseButton', class: 'btn btn-default icon icon-law', 'LICENSE'
             @button outlet: 'openButton', class: 'btn btn-default icon icon-link-external', 'View Code'

           @div outlet: 'errors'
@@ -115,6 +116,10 @@
       @openMarkdownFile(@changelogPath) if @changelogPath
       false

+    @licenseButton.on 'click', =>
+      @openMarkdownFile(@licensePath) if @licensePath
+      false
+
     @openButton.on 'click', =>
       atom.open(pathsToOpen: [@pack.path]) if fs.existsSync(@pack.path)
       false
@@ -131,16 +136,19 @@

   updateFileButtons: ->
     @changelogPath = null
+    @licensePath = null
     @readmePath = null

     for child in fs.listSync(@pack.path)
       switch path.basename(child, path.extname(child)).toLowerCase()
         when 'changelog', 'history' then @changelogPath = child
+        when 'license', 'licence' then @licensePath = child
         when 'readme' then @readmePath = child

-      break if @readmePath and @changelogPath
+      break if @readmePath and @changelogPath and @licensePath

     if @changelogPath then @changelogButton.show() else @changelogButton.hide()
+    if @licensePath then @licenseButton.show() else @licenseButton.hide()
     if @readmePath then @readmeButton.show() else @readmeButton.hide()

   getStartupTime: ->


Add try/catch block and logging to on success of checkout in BranchListView

--- lib/views/branch-list-view.coffee
+++ lib/views/branch-list-view.coffee
@@ -54,9 +54,15 @@
     .then (message) =>
       notifier.addSuccess message
       atom.workspace.observeTextEditors (editor) =>
-        if filepath = editor.getPath()?.toString()
-          fs.exists filepath, (exists) =>
-            editor.destroy() if not exists
+        try
+          path = editor.getPath()
+          console.log "Git-plus: editor.getPath() returned '#{path}'"
+          if filepath = path?.toString?()
+            fs.exists filepath, (exists) =>
+              editor.destroy() if not exists
+        catch error
+          notifier.addWarning "There was an error closing windows for non-existing files after the checkout. Please check the dev console."
+          console.info "Git-plus: please take a screenshot of what has been printed in the console and add it to the issue on github at https://github.com/akonwi/git-plus/issues/139"
       git.refresh()
       @currentPane.activate()
     .catch (err) ->


Permissions

Permissions

--- app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
+++ app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
@@ -438,6 +438,25 @@
             ] # /tab1 elements
           },
           {
+            id: 'permissions',
+            label: 'Permissions',
+            elements: [
+              {
+                id: 'roles_mask',
+                type: 'checkbox',
+                label: 'Only visible to logged in users',
+                setup: (element) ->
+                  value = element.children('.menu-item').children("input[name$='[roles_mask]']").val()
+                  if value == '0' then this.setValue(true) else this.setValue(false)
+                commit: (element) ->
+                  if this.getValue() == true
+                    element.children('.menu-item').children("input[name$='[roles_mask]']").val(0)
+                  else
+                    element.children('.menu-item').children("input[name$='[roles_mask]']").val('')
+              }
+            ]
+          },
+          {
             id: 'advanced',
             label: 'Advanced'
             elements: [


correct test

correct test

--- desktop/apps/editorial_features/test/components/venice_2017/index.coffee
+++ desktop/apps/editorial_features/test/components/venice_2017/index.coffee
@@ -99,7 +99,7 @@
   it 'Sets up the footer carousel', ->
     $(@view.el).find('.venice-footer').html().should.containEql '
The Venice Biennale
'
     $(@view.el).find('.venice-footer .mgr-cell').length.should.eql 2
-    $(@view.el).find('.venice-footer .mgr-cells').should.containEql ''
+    $(@view.el).find('.venice-footer .mgr-cells').html().should.containEql ''

   it 'changes the section when flickity has settled #settleSection', ->
     @on.args[0][1]()


caring direct room

caring direct room

--- packages/rocketchat-channel-settings/client/views/channelSettings.coffee
+++ packages/rocketchat-channel-settings/client/views/channelSettings.coffee
@@ -108,7 +108,7 @@

 	'click #open_profile' : (e,t) ->
 		e.preventDefault()
-		window.open 'https://stage.ubegin.com/discover/projects/'+this.userDetail
+		window.open 'https://stage.ubegin.com/discover/people/'+this.userDetail

 Template.channelSettings.onCreated ->
 	@editing = new ReactiveVar


KDFlexSplit: fix tests for PhantomJS

KDFlexSplit: fix tests for PhantomJS

--- test/components/flexsplit/flexsplit.coffee
+++ test/components/flexsplit/flexsplit.coffee
@@ -51,7 +51,7 @@

     it 'should instantiate views with 50%-50% base by default', ->
       for view in [ @view1, @view2 ]
-        view.getAttribute('style').should.equal 'flex-basis: 50%;'
+        view.getAttribute('style').should.endWith 'flex-basis: 50%;'

     it 'should create a resizer', ->
       (@instance.resizer instanceof KDFlexSplitResizer).should.equal true
@@ -61,8 +61,8 @@
       @instance.on KDFlex.EVENT_EXPANDED, @callback

       @view1.emit KDFlex.EVENT_EXPAND
-      @view1.getAttribute('style').should.equal "flex-basis: #{KDFlex.MAX}%;"
-      @view2.getAttribute('style').should.equal "flex-basis: #{KDFlex.MIN}%;"
+      @view1.getAttribute('style').should.endWith "flex-basis: #{KDFlex.MAX}%;"
+      @view2.getAttribute('style').should.endWith "flex-basis: #{KDFlex.MIN}%;"

       @callback.calledOnce.should.equal true

@@ -71,13 +71,13 @@
       @instance.on KDFlex.EVENT_COLLAPSED, @callback

       @view1.emit KDFlex.EVENT_COLLAPSE
-      @view1.getAttribute('style').should.equal 'flex-basis: 50%;'
-      @view2.getAttribute('style').should.equal 'flex-basis: 50%;'
+      @view1.getAttribute('style').should.endWith 'flex-basis: 50%;'
+      @view2.getAttribute('style').should.endWith 'flex-basis: 50%;'

       @callback.calledOnce.should.equal true

     it 'should support custom resizing', ->

       @instance.setFractions [20, 80]
-      @view1.getAttribute('style').should.equal 'flex-basis: 20%;'
-      @view2.getAttribute('style').should.equal 'flex-basis: 80%;'
+      @view1.getAttribute('style').should.endWith 'flex-basis: 20%;'
+      @view2.getAttribute('style').should.endWith 'flex-basis: 80%;'


specify buttons for addthis

specify buttons for addthis

--- app/assets/javascripts/angular_blog/angular_app/services/social.js.coffee
+++ app/assets/javascripts/angular_blog/angular_app/services/social.js.coffee
@@ -12,10 +12,10 @@

   tags: (link,title) ->
     '

-
-
-
-
+
+
+
+

'


Remove a dead event

Remove a dead event

--- services/web/app/coffee/Features/TokenAccess/TokenAccessController.coffee
+++ services/web/app/coffee/Features/TokenAccess/TokenAccessController.coffee
@@ -62,13 +62,6 @@
 						logger.err {err, token, userId, projectId: project._id},
 							"[TokenAccess] error adding user to project with readAndWrite token"
 						return next(err)
-					# TODO: check if this is still needed by the client
-					setTimeout( () ->
-						EditorRealTimeController.emitToRoom(
-							'project:membership:changed',
-							{tokenMembers: true}
-						)
-					, 1000)
 					return TokenAccessController._loadEditor(project._id, req, res, next)

 	readOnlyToken: (req, res, next) ->


Adds upsert to client-side default callbacks

--- app/assets/javascripts/action_cable_notifications/default_callbacks.coffee
+++ app/assets/javascripts/action_cable_notifications/default_callbacks.coffee
@@ -18,6 +18,9 @@
     _.each packet.data, (fields) ->
       collection.update({id: fields.id}, fields)

+  upsert: (packet, collection) ->
+    collection.upsert({id: packet.id}, packet.data)
+
   upsert_many: (packet, collection) ->
     _.each packet.data, (fields) ->
       collection.upsert({id: fields.id}, fields)


Always start with stopped planeView & sceneController

--- app/assets/javascripts/oxalis/controller/viewmodes/plane_controller.coffee
+++ app/assets/javascripts/oxalis/controller/viewmodes/plane_controller.coffee
@@ -250,8 +250,8 @@
     if @isStarted
       @input.unbind()

-      @sceneController.stop()
-      @planeView.stop()
+    @planeView.stop()
+    @sceneController.stop()

     @isStarted = false


chat: remove participant from selected list

We were forgetting to sync up autocomplete controller's selected item data with chat's participant list data. This commit fixes that by manually removing the item and decreasing the count.

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -338,8 +338,24 @@
     return  unless participant
     return  unless @participantMap[participant._id]?

-    @participantMap[participant._id].destroy()
-    delete @participantMap[participant._id]
+    KD.remote.cacheable 'JAccount', participant._id, (err, account) =>
+
+      return warn err  if err
+
+      @participantMap[participant._id].destroy()
+      delete @participantMap[participant._id]
+
+      @removeFromAutoCompleteSelectedList account
+
+
+  removeFromAutoCompleteSelectedList: (account) ->
+
+    return  unless account in @autoComplete.getSelectedItemData()
+
+    # remove the account from autocomplete controller's
+    # selected list. So that it can show up in other searchs.
+    @autoComplete.removeSelectedItemData account
+    @autoComplete.selectedItemCounter--

   createPreviousLink: ->


activity: don't link back to parent

activity: don't link back to parent

--- client/activity/lib/components/messagelink/index.coffee
+++ client/activity/lib/components/messagelink/index.coffee
@@ -17,9 +17,6 @@
     return '#'  if @props.message.get 'isFake'

     { message } = @props
-
-    if message.get 'parent'
-      message = message.get 'parent'

     if @props.absolute
       channelId = message.get 'initialChannelId'


Fix variable name clash in collection page

--- app/collections/show.cjsx
+++ app/collections/show.cjsx
@@ -34,8 +34,8 @@

   render: ->
     <PromiseRenderer promise={@props.collection.get('owner')}>{(owner) =>
-      [owner, name] = @props.collection.split('/')
-      params = {owner, name}
+      [ownerName, name] = @props.collection.slug.split('/')
+      params = {owner: ownerName, name: name}

       isOwner = @props.user?.id is owner.id


translate Chinese (#4860)

translate Chinese (#4860)

* translate Chinese

* tramnslate

* translate

* translate English into Chinese

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -1,6 +1,6 @@
 module.exports = nativeDescription: "简体中文", englishDescription: "Chinese (Simplified)", translation:
   new_home:
-    slogan: "最引人入胜的编程游戏。" # {change}
+    slogan: "最引人入胜的编程游戏。"
     classroom_edition: "教育版："
     learn_to_code: "学习编程："
     play_now: "开始游戏"
@@ -24,13 +24,13 @@
     democratizing: "我们的核心理念是普及编程学习。每个人都应该有机会学习编程。"
     forgot_learning: "我觉得他们甚至忘了自己在学东西。" # {change}
     wanted_to_do: "写代码是我一直想做的事情。我从没想过我能在学校学习它。"
-#    builds_concepts_up: "I like how CodeCombat builds the concepts up. It's really easy to understand and fun to figure it out."
+    builds_concepts_up: "我喜欢CodeCombat创建的概念。这个游戏很容易理解，在计算的过程中可以产生乐趣。"
     why_games: "为什么通过游戏来学习编程很重要？"
     games_reward: "游戏能获得高效的奋斗。"
     encourage: "游戏是鼓励交流、发现、试错的媒介。好的游戏能够让玩家学习到技能，正如学生在学习过程中所做的那样。"
     excel: "游戏能提供奖励"
     struggle: "高效的奋斗"
-    kind_of_struggle: "这种奋斗能为学习提供动力并"
+    kind_of_struggle: "这种奋斗能为学习提供动力"
     motivating: "富有激励性"
     not_tedious: "而不单调乏味。"
     gaming_is_good: "研究表明游戏有益儿童大脑。（这是真的！）"
@@ -65,7 +65,7 @@

   nav:
     map: "地图"
-    play: "关卡选择" # The top nav bar entry where players choose which levels to play
+    play: "关卡选择"
     community: "社区"
     courses: "课程"
     blog: "博客"
@@ -80,7 +80,7 @@
     about: "关于"
     contact: "联系我们"
     twitter_follow: "关注"
-    my_classrooms: "我的班级" # {change}
+    my_classrooms: "我的班级"
     my_courses: "我的课程"
     careers: "招聘"
     facebook: "Facebook"
@@ -110,7 +110,7 @@
     page_not_found: "找不到网页"

   diplomat_suggestion:
-    title: "帮助我们翻译CodeCombat" # This shows up when a player switches to a non-English language using the language selector.
+    title: "帮助我们翻译CodeCombat"
     sub_heading: "我们需要您的语言技能"
     pitch_body: "我们开发了英文版的CodeCombat，但是CodeCombat的玩家遍布全球。对于不熟悉英语的中文玩家，他们希望能玩简体中文版的CodeCombat，如果您中英文都很熟练，请考虑加入我们“外交官”行列并参与CodeCombat的翻译，帮忙把CodeCombat网站和所有关卡翻译成简体中文。感谢！"
     missing_translations: "没被翻译的文字将以英文显示。"
@@ -119,8 +119,8 @@

   play:
     play_as: "扮演" # Ladder page
-#    get_course_for_class: "Assign Game Development and more to your classes!"
-#    request_licenses: "Contact our school specialists for details."
+    get_course_for_class: "把游戏的开发和更多细节分享给你的同学们吧"
+    request_licenses: "联系我们学校的专家获取详细信息吧"
     compete: "开战！" # Course details page
     spectate: "旁观他人的游戏" # Ladder page
     players: "玩家" # Hover over a level on /play
@@ -157,7 +157,7 @@
     replay: "重玩"
     back_to_classroom: "返回教室"
     teacher_button: "带到学校"
-#    get_more_codecombat: "Get More CodeCombat"
+    get_more_codecombat: "获取跟过CodeCombat游戏的信息"

   code:
     if: "如果" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -293,22 +293,22 @@
     select_your_starting_hero: "选择你的起始英雄："
     you_can_always_change_your_hero_later: "之后你可以随时更换英雄。"
     finish: "完成"
-#    teacher_ready_to_create_class: "You're ready to create your first class!"
-#    teacher_students_can_start_now: "Your students will be able to start playing the first course, Introduction to Computer Science, immediately."
-#    teacher_list_create_class: "On the next screen you will be able to create a new class."
-#    teacher_list_add_students: "Add students to the class by clicking the View Class link, then sending your students the Class Code or URL. You can also invite them via email if they have email addresses."
+    teacher_ready_to_create_class: "你已经准备好创建你的班级"
+    teacher_students_can_start_now: "学生将在第一堂课上开始玩游戏，立即开始介绍计算机科学吧！"
+    teacher_list_create_class: "在下一个屏幕上，你能否创建一个新的班级"
+    teacher_list_add_students: "通过点击‘查看班级’按钮来增加学生，之后将班级代码或者URL发送给你的学生。如果他们有邮箱，你也可以通过邮箱邀请他们。"
 #    teacher_list_resource_hub_1: "Check out the"
     teacher_list_resource_hub_2: "课程指南"
 #    teacher_list_resource_hub_3: "for solutions to every level, and the"
-#    teacher_list_resource_hub_4: "Resource Hub"
+    teacher_list_resource_hub_4: "资源仓库"
 #    teacher_list_resource_hub_5: "for curriculum guides, activities, and more!"
-    teacher_additional_questions: "就是这样！如果您还需要额外的帮助或者遇到了什么问题，请联系__supportEmail__。"
+    teacher_additional_questions: "就是这样！如果您还需要额外的帮助或者遇到了什么问题，请联系__supportEmail__。"
     dont_use_our_email_silly: "请不要在这里填写我们的邮箱地址！要填写你家长的邮箱地址。"
     want_codecombat_in_school: "想要一辈子都玩CodeCombat?"
-#    eu_confirmation: "I agree to allow CodeCombat to store my data on US servers."
-#    eu_confirmation_place_of_processing: "Learn more about the possible risks"
-#    eu_confirmation_student: "If you are not sure, ask your teacher."
-#    eu_confirmation_individual: "If you do not want us to store your data on US servers, you can always keep playing anonymously without saving your code."
+    eu_confirmation: "我同意CodeCombat游戏在美国的服务器上存储我的数据"
+    eu_confirmation_place_of_processing: "获取更多潜在危险的信息"
+    eu_confirmation_student: "如果你确定，询问你的老师"
+    eu_confirmation_individual: "如果你不想让我们把你的数据存储在美国的服务器，你可以一直匿名玩游戏（但是不能保存你的代码）"

   recover:
     recover_account_title: "找回账户"
@@ -439,7 +439,7 @@
     back_to_map: "返回地图"
     directions: "方向"
     edit_level: "编辑关卡"
-#    keep_learning: "Keep Learning"
+    keep_learning: "不断学习"
     explore_codecombat: "探索CodeCombat"
 #    finished_hoc: "I'm finished with my Hour of Code"
     get_certificate: "获取你的证书！"
@@ -456,7 +456,7 @@
     concept_challenge_complete: "概念挑战完成!"
     combo_challenge_complete: "组合挑战完成!"
 #    combo_challenge_complete_body: "Great job, it looks like you're well on your way to understanding __concept__!"
-#    replay_level: "Replay Level"
+    replay_level: "重玩关卡"
 #    combo_concepts_used: "__complete__/__total__ Concepts Used"
 #    combo_all_concepts_used: "You used all concepts possible to solve the challenge. Great job!"
 #    combo_not_all_concepts_used: "You used __complete__ out of the __total__ concepts possible to solve the challenge. Try to get all __total__ concepts next time!"
@@ -472,12 +472,12 @@
     restart: "重新开始"
     goals: "目标"
     goal: "目标"
-#    challenge_level_goals: "Challenge Level Goals"
-#    challenge_level_goal: "Challenge Level Goal"
-#    concept_challenge_goals: "Concept Challenge Goals"
-#    combo_challenge_goals: "Challenge Level Goals"
-#    concept_challenge_goal: "Concept Challenge Goal"
-#    combo_challenge_goal: "Challenge Level Goal"
+    challenge_level_goals: "挑战级目标"
+    challenge_level_goal: "挑战级目标"
+    concept_challenge_goals: "概念性挑战目标"
+    combo_challenge_goals: "挑战级目标"
+    concept_challenge_goal: "概念性挑战目标"
+    combo_challenge_goal: "挑战级目标"
     running: "运行中..."
     success: "成功！"
     incomplete: "未完成"
@@ -2135,8 +2135,8 @@
     verify_error: "验证时出了点问题 :("
 #    unsubscribe_from_marketing: "Unsubscribe __email__ from all CodeCombat marketing emails?"
 #    unsubscribe_button: "Yes, unsubscribe"
-#    unsubscribe_failed: "Failed"
-#    unsubscribe_success: "Success"
+    unsubscribe_failed: "失败"
+    unsubscribe_success: "成功"

   account_invoices:
     amount: "金额（美元）"


Teambuilder: Fix bug where first team is a bunch of text.

--- client/app/js/concerns/main_buttons.coffee
+++ client/app/js/concerns/main_buttons.coffee
@@ -23,7 +23,7 @@
   if allTeams?.length > 0
     renderCurrentTeam($mainButtons)
   else
-    $selectTeam = $('.select-team')
+    $selectTeam = $mainButtons.find('.select-team')
     $selectTeam.html("You have no teams!")

   $mainButtons.find('.select-team').click (e) ->


Client: Activity: Tests: Changed test file path

--- client/testrunner/lib/require-tests.coffee
+++ client/testrunner/lib/require-tests.coffee
@@ -1,3 +1,3 @@
 require '../../app/test'
-require '../../activity/test'
+require '../../activity/lib/test'
 require '../../ide/test'


Fixed updateUser

Fixed updateUser


Require login for evaluation

Require login for evaluation

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -46,6 +46,8 @@
         controller: 'EvaluationCtrl'
         controllerAs: 'evaluation'
         css: 'styles/evaluation.css'
+        data:
+          authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor]
       .otherwise
         redirectTo: '/'
   .config(['$httpProvider', ($httpProvider) ->


Revert "Set css overflow auto for badge's iframe"

This reverts commit 5d82ea53de1de9c5d038b1e26797e785dfe6cb42.

--- src/plugins/badge.coffee
+++ src/plugins/badge.coffee
@@ -226,7 +226,8 @@
     height: 100%;
     z-index: 2;
     -webkit-overflow-scrolling: touch;
-    overflow: auto;
+    overflow-x: hidden;
+    overflow-y: auto;
   }

   #sa-badge-modal #sa-badge-modal-inner.sa-badge-spinner::before {


Try to get the tests to run

Try to get the tests to run

--- tests/mocha/apollos/user/login/f1/_.coffee
+++ tests/mocha/apollos/user/login/f1/_.coffee
@@ -39,9 +39,7 @@
           assert.isBoolean success
           done()

-      return
-
-    if Meteor.isServer
+    else
       it "should make an OAuth request syncronously", ->
         success = Apollos.user.login.f1 "bob@example.org", "password123"
         assert.isBoolean success


Fix typo

Fix typo

--- conda_ui/static/conda_ui/installed.coffee
+++ conda_ui/static/conda_ui/installed.coffee
@@ -46,7 +46,7 @@
                         console.log e.stack
                         continue
                     if newer.length > 0
-                        if name is python
+                        if name is 'python'
                             # Don't tell user to update major versions
                             major = info.version.slice(0, 2)
                             if _.any(newer, (pkg) -> pkg.version.slice(0, 2) is major)


updating how we set the default feedback for homeworks, so homework feedback settings can be updated

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -64,7 +64,7 @@
     if @_local[planId]?.type is PLAN_TYPES.HOMEWORK or @_changed[planId]?.type is PLAN_TYPES.HOMEWORK
       @_changed[planId] ?= {}
       # need to default final posting json's is feedback immediate to false
-      @_changed[planId].is_feedback_immediate ?= false
+      @_local[planId].is_feedback_immediate ?= false
       @_local[planId].settings.exercise_ids ?= []
       @_local[planId].settings.exercises_count_dynamic ?= TUTOR_SELECTIONS.default


fix plugin config in bin

fix plugin config in bin

--- src/bin/docpad-hapi-server.coffee
+++ src/bin/docpad-hapi-server.coffee
@@ -29,7 +29,7 @@
 	Async.parallel([
 		# Start Hapi Server
 		(next) ->
-			config = docpad.getConfig()
+			config = if docpad.getConfig().plugins?.hapi? then docpad.getConfig().plugins.hapi else {}

 			port = process.env.PORT ? config.port ? process.env.VCAP_APP_PORT ? process.env.VMC_APP_PORT ? 9778


avoid starting ssh probe in parallel, by using setTimeout instead of setInterval

--- code/instance.coffee
+++ code/instance.coffee
@@ -95,30 +95,20 @@
   # Connect via SSH and execute command
   # TODO: proper callbacks?
   _ssh: (key, command, callback) ->
-    @_wait_for_sshd =>
+    @_wait_for_sshd key, =>
       args = [
         '-o', 'LogLevel=ERROR', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-i', key, "root@#{@ip_address}", '-o', 'IdentitiesOnly=yes' ]
-      args = args.concat(command.split ' ')
+      args = args.concat command.split ' '

-      tries = 0
-      again = ->
-        if tries
-          console.log 'Trying ssh again'
-        ssh = spawn 'ssh', args
-        ssh.stdout.on 'data', (data) -> process.stdout.write 'ssh: ' + data.toString('ascii')
-        ssh.stderr.on 'data', (data) -> process.stderr.write 'ssh: ' + data.toString('ascii')
-        ssh.on 'exit', (rc) ->
-          if rc == 255
-            tries += 1
-            again()
-          else
-            callback rc
-      again()
+      ssh = spawn 'ssh', args
+      ssh.stdout.on 'data', (data) -> process.stdout.write 'ssh: ' + data.toString('ascii')
+      ssh.stderr.on 'data', (data) -> process.stderr.write 'ssh: ' + data.toString('ascii')
+      ssh.on 'exit', callback

   # Copy files via SCP
   # TODO: factor out into SSH & SCP class, proper callbacks?
   _scp: (key, files, callback) ->
-    @_wait_for_sshd =>
+    @_wait_for_sshd key, =>
       args = [
         '-o', 'LogLevel=ERROR', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-o', 'IdentitiesOnly=yes', '-i', key ]
       args = args.concat files
@@ -129,15 +119,21 @@
       ssh.stderr.on 'data', (data) -> console.log data.toString('ascii')
       ssh.on 'exit', callback

-  # Wait until we can connect to port 22 of instance
-  _wait_for_sshd: (callback) ->
-    f = =>
-        client = net.connect 22, @ip_address, ->
-          clearInterval int
+  # Wait until we can connect to ssh and run the command "exit 99".
+  _wait_for_sshd: (key, callback) ->
+    again = =>
+      args = [
+        '-o', 'LogLevel=ERROR', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-i', key, "root@#{@ip_address}", '-o', 'IdentitiesOnly=yes' ]
+      args = args.concat "exit 99"
+      ssh = spawn 'ssh', args
+      ssh.stdout.on 'data', (data) -> process.stdout.write 'ssh?: ' + data.toString('ascii')
+      ssh.stderr.on 'data', (data) -> process.stderr.write 'ssh?: ' + data.toString('ascii')
+      ssh.on 'exit', (rc) ->
+        if rc == 99
           callback()
-        client.on 'error', ->
-
-    int = setInterval f, 1000
+        else
+          setTimeout again, 999
+    again()

   # Spawn a command locally, needs to be executable
   # TODO: put somewhere more sane, name it properly: exec?


use json-stringify-safe to fix #43

use json-stringify-safe to fix #43

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -15,6 +15,9 @@
 # use OS agnostic and fiber friendly versions of fs and path
 fs = Plugin.fs
 path = Plugin.path
+
+# avoid circular ref's error
+stringify = npm.require 'json-stringify-safe'

 # # # NOTES FOR getNpmDir()
 #
@@ -182,6 +185,7 @@

     return

+
   applyTransforms: (browserify, browserifyOptions) ->

     # extract envify tranform's options so it isn't used in loop
@@ -239,7 +243,7 @@
     """
     #{moduleCheckMessage}
     Browserify options:
-    >  #{JSON.stringify browserifyOptions, null, '>  '}
+    >  #{stringify browserifyOptions, null, '>  '}
     """


fix issue #1 by renaming local stream var

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -9,10 +9,10 @@
 Plugin.registerSourceHandler 'browserify.js', (step) ->
   # TODO: how to know if it's production or dev? change value of debug...
   # TODO: inputPath may include directories we need to strip for basedir
-  stream = new stream.PassThrough()   # hold Buffer as Readable stream
-  stream.end step.read()              # put Buffer into stream
+  readable = new stream.PassThrough()   # hold Buffer as Readable stream
+  readable.end step.read()              # put Buffer into stream
   basedir = step.fullInputPath.slice(0, -(step.inputPath.length)) + '.npm/package'
-  browserify = Browserify [stream], debug:true, basedir:basedir
+  browserify = Browserify [readable], debug:true, basedir:basedir
   bundle = browserify.bundle()
   bundle.setEncoding('utf8')          # work with strings
   string = Meteor.wrapAsync(getString)(bundle) # wrap for sync use and call it
@@ -21,4 +21,3 @@
     sourcePath: step.inputPath
     data:       string
     bare:       step?.fileOptions?.bare
-