Make sure tooltips are initialized where needed

--- app/assets/javascripts/admin/admin_in_place_editor.js.coffee
+++ app/assets/javascripts/admin/admin_in_place_editor.js.coffee
@@ -24,6 +24,7 @@
       @clearModalForm($(@))
     @initModals()
     @initSearchTypeahead()
+    $("[rel='tooltip']").tooltip()

   clearModalForm: (modal) ->
     form = modal.find('form')
@@ -218,10 +219,7 @@

 class ListingChangesEditor extends AdminEditor
   init: () ->
-    @initEditors()
     @initForm()
-
-  initEditors: () ->
     $("[rel='tooltip']").tooltip()

   initForm: () ->
@@ -295,7 +293,6 @@
     @nonSuperInit()

   nonSuperInit: () ->
-    $("[rel='tooltip']").tooltip()
     $(".nav-tabs.new-reference-tabs a").click (e) ->
       e.preventDefault()
       window.adminEditor.clearModalForm $("#admin-new-taxon_concept_reference-form")
@@ -369,7 +366,7 @@
       options.push {}
       selection = $(@)
       if selection.hasClass('taxon_concept')
-        options[i]['select2'] =
+        options[i]['select2'] =
           ajax:
             url: "/api/v1/auto_complete_taxon_concepts.json"
             dataType: "json"
@@ -394,7 +391,7 @@
           formatSelection: (item) ->
             item.name
       else if selection.hasClass('term')
-        options[i]['select2'] =
+        options[i]['select2'] =
           data:{ results: terms, text: 'code' },
           placeholder: "Select Term"
           allowClear: true


Fix cancel button on connections

Fix cancel button on connections

--- app/assets/javascripts/admin/views/connections/new.js.coffee
+++ app/assets/javascripts/admin/views/connections/new.js.coffee
@@ -50,8 +50,9 @@
         url.show()
         @$el.find("input.url").hide()

-  cancel: ->
-    window.location.href = "/admin/"
+  cancel: (e) ->
+    e.preventDefault()
+    Backbone.history.navigate '/connections', trigger: true

   set_url: ->
     env = @$el.find("[name='env']").val()


Check if ticket has beed updated only every 30 min.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -36,7 +36,9 @@
       @load(cache)
     update = =>
       @fetch( @ticket_id, false )
-    @interval( update, 450000, 'pull_check' )
+
+    # check if ticket has beed updated every 30 min
+    @interval( update, 1800000, 'pull_check' )

     # fetch new data if triggered
     @bind(


Improved collection handling.

Improved collection handling.

--- app/assets/javascripts/app/lib/app_post/collection.js.coffee
+++ app/assets/javascripts/app/lib/app_post/collection.js.coffee
@@ -21,42 +21,13 @@

     # add trigger - bind new events
     App.Event.bind 'loadAssets', (data) =>
-      if data
-        for type, collections of data
-          if type is 'users'
-            type = 'User'
-            throw "BREAK, users"
-          if type is 'tickets'
-            type = 'Ticket'
-            throw "BREAK, tickets"
-          if type is 'ticket_article'
-            type = 'TicketArticle'
-            throw "BREAK, ticket_article"
-          if type is 'organization'
-            type = 'Organization'
-            throw "BREAK, organization"
-          if type is 'history_object'
-            type = 'HistoryObject'
-            throw "BREAK, history_object"
-          if type is 'history_type'
-            type = 'HistoryType'
-            throw "BREAK, history_type"
-          if type is 'history_attribute'
-            type = 'HistoryAttribute'
-            throw "BREAK, history_attribute"
+      if !data
+        @log 'error', 'loadAssets:trigger, got no data, cant load assets'
+        return

-          @log 'debug', 'loadCollection:trigger', type, collections
-          @load( localStorage: data.localStorage, type: type, data: collections )
-
-    # add trigger - bind new events
-    App.Event.bind 'loadCollection', (data) =>
-
-      # load collections
-      if data.collections
-        for type of data.collections
-
-          @log 'debug', 'loadCollection:trigger', type, data.collections[type]
-          @load( localStorage: data.localStorage, type: type, data: data.collections[type] )
+      for type, collections of data
+        @log 'debug', 'loadCollection:trigger', type, collections
+        @load( localStorage: data.localStorage, type: type, data: collections )

     # add trigger - bind new events
     App.Event.bind 'resetCollection', (data) =>
@@ -69,62 +40,73 @@
           @reset( localStorage: data.localStorage, type: type, data: data.collections[type] )

     # find collections to load
-    @_loadCollectionAll()
+    @_loadObjectsFromLocalStore()

-  _loadCollectionAll: ->
+  _loadObjectsFromLocalStore: ->
     list = App.Store.list()
     for key in list
       parts = key.split('::')
       if parts[0] is 'collection'
         data = App.Store.get( key )
-        if data && data.localStorage
-          @log 'debug', 'load INIT', data
-          @load( data )
+        data['type']         = parts[1]
+        data['localStorage'] = true
+
+        @log 'debug', 'load INIT', data
+        @load( data )

   reset: (params) ->
     if !App[ params.type ]
       @log 'error', 'reset', 'no such collection', params
       return
+
     @log 'debug', 'reset', params

-    # empty in-memory
-    App[ params.type ].refresh( [], { clear: true } )
+    # remove permanent storage
+    @localDelete( params.type )

-    # remove permanent storage
-    list = App.Store.list()
-    for key in list
-      parts = key.split('::')
-      if parts[0] is 'collection' && parts[1] is params.type
-        App.Store.delete(key)
+    # reset in-memory
+    App[ params.type ].refresh( params.data, { clear: true } )

-    # load with new data
-    @load(params)
+    # remember in store if not already requested from local storage
+    for object in params.data
+      @localStore( params.type, object )

   load: (params) ->
     @log 'debug', 'load', params

     return if _.isEmpty( params.data )

+    if !App[ params.type ]
+      @log 'error', 'reset', 'no such collection', params
+      return
+
     localStorage = params.localStorage

     # load full array once
     if _.isArray( params.data )
-      if !params.refresh && App[ params.type ]
-        App[ params.type ].refresh( params.data )
+      App[ params.type ].refresh( params.data )

       # remember in store if not already requested from local storage
       if !localStorage
         for object in params.data
-          App.Store.write( 'collection::' + params.type + '::' + object.id, { type: params.type, localStorage: true, data: [ object ] } )
+          @localStore( params.type, object )
       return

     # load data from object
-#    if _.isObject( params.data )
     for key, object of params.data
       if !params.refresh && App[ params.type ]
         App[ params.type ].refresh( object )

       # remember in store if not already requested from local storage
       if !localStorage
-        App.Store.write( 'collection::' + params.type + '::' + object.id, { type: params.type, localStorage: true, data: [ object ] } )
+        @localStore( params.type, object)

+  localDelete: (type) ->
+    list = App.Store.list()
+    for key in list
+      parts = key.split('::')
+      if parts[0] is 'collection' && parts[1] is type
+        App.Store.delete(key)
+
+  localStore: (type, object) ->
+    App.Store.write( 'collection::' + type + '::' + object.id, { data: [ object ] } )


Prevent default submit behavior for form.

--- app/assets/javascripts/components/activity_new.js.jsx.coffee
+++ app/assets/javascripts/components/activity_new.js.jsx.coffee
@@ -5,10 +5,14 @@
   handleChange: (e) ->
     @setState userInput: e.target.value

-  startTracking: ->
+  startTracking: (e) ->
+    e.preventDefault()
     model = new ActivityModel description: @state.userInput
-    model.save().success =>
+    model.save().success(=>
       @props.collection.fetch()
+    ).fail(=>
+      console.log(arguments)
+    )
     return false #for form submission

   render: ->


Better whitespace processing

Better whitespace processing

--- webpack/cmdize.coffee
+++ webpack/cmdize.coffee
@@ -32,13 +32,19 @@
           break

       fs.writeFile path.format(x), toANSI """
-        #{prolog}#{word bat.command}"%~f0" #{word bat.args}#{target}
+        #{prolog}#{sword bat.command}"%~f0"#{word bat.args}#{word target}
         #{yml[':epilog']}#{do z.source}

         """, ->
     return

 word = (s)->
+  if s
+    " #{s}"
+  else
+    ''
+
+sword = (s)->
   if s
     "#{s} "
   else


course rewrite: when searching, show how many things are not being shown.

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -24,7 +24,7 @@
 ###
 TODO:

-- [ ] (0:30?) when searching show how many things are not being shown.
+- [x] (0:30?) (0:18) when searching, show how many things are not being shown.
 - [ ] (0:45?) delete student; show deleted students; permanently delete students
 - [ ] (0:45?) delete assignment; show deleted assignments; permanently delete assignment
 - [ ] (1:00?) help page -- integrate info
@@ -34,6 +34,7 @@
 - [ ] (2:00?) make everything look pretty
         - triangles for show/hide assignment info like for students
         - error messages in assignment page -- make hidable and truncate-able
+        - escape to clear search boxes
 - [ ] (3:00?) bug searching / testing / debugging
 - [ ] (1:00?) (0:19+) fix bugs in opening directories in different projects using actions -- completely busted right now due to refactor of directory listing stuff....

@@ -829,10 +830,10 @@
         if @state.err
             <ErrorDisplay error={@state.err} onClose={=>@setState(err:undefined)} />

-    render_header: ->
+    render_header: (num_omitted) ->

             <Row>
-                <Col md=5>
+                <Col md=3>
                     <Input
                         ref         = 'student_search_input'
                         type        = 'text'
@@ -842,7 +843,10 @@
                         onChange    = {=>@setState(search:@refs.student_search_input.getValue())}
                     />
                 </Col>
-                <Col md=5 mdOffset=2>
+                <Col md=3>
+                    {
(Omitting {num_omitted} students)
 if num_omitted}
+                </Col>
+                <Col md=5 mdOffset=1>
                     <form onSubmit={@do_add_search}>
                         <Input
                             ref         = 'student_add_input'
@@ -859,9 +863,8 @@
             {@render_error()}

-    render_students: ->
-        if not @props.students? or not @props.user_map?
-            return
+    compute_student_list: ->
+        # TODO: good place to cache something...
         # turn map of students into a list
         v = immutable_to_list(@props.students, 'student_id')
         # fill in names, for use in sorting and searching (TODO: caching)
@@ -876,22 +879,29 @@

         v.sort (a,b) ->
             return misc.cmp(a.sort, b.sort)
+        num_omitted = 0
         if @state.search
             words  = misc.split(@state.search.toLowerCase())
             search = (a) -> ((a.last_name ? '') + (a.first_name ? '') + (a.email_address ? '')).toLowerCase()
             match  = (s) ->
                 for word in words
                     if s.indexOf(word) == -1
+                        num_omitted += 1
                         return false
                 return true
             v = (x for x in v when match(search(x)))
-        for x in v
+
+        return {students:v, num_omitted:num_omitted}
+
+    render_students: (students) ->
+        for x in students
             <Student key={x.student_id} student_id={x.student_id} student={@props.students.get(x.student_id)}
                      user_map={@props.user_map} flux={@props.flux} name={@props.name} />

     render :->
-        <Panel header={@render_header()}>
-            {@render_students()}
+        {students, num_omitted} = @compute_student_list()
+        <Panel header={@render_header(num_omitted)}>
+            {@render_students(students)}
         </Panel>

@@ -1140,7 +1150,6 @@
             project_id : @props.project_id
             query      : "*#{search}*"
             cb         : (err, resp) =>
-                console.log(resp)
                 if err
                     @setState(add_searching:false, err:err, add_select:undefined)
                     return
@@ -1206,10 +1215,10 @@
         if @state.err
             <ErrorDisplay error={@state.err} onClose={=>@setState(err:undefined)} />

-    render_header: ->
+    render_header: (num_omitted) ->

             <Row>
-                <Col md=5>
+                <Col md=3>
                     <Input
                         ref         = 'assignment_search_input'
                         type        = 'text'
@@ -1219,7 +1228,10 @@
                         onChange    = {=>@setState(search:@refs.assignment_search_input.getValue())}
                     />
                 </Col>
-                <Col md=5 mdOffset=2>
+                <Col md=3>
+                    {
(Omitting {num_omitted} assignments)
 if num_omitted}
+                </Col>
+                <Col md=5 mdOffset=1>
                     <form onSubmit={@do_add_search}>
                         <Input
                             ref         = 'assignment_add_input'
@@ -1236,23 +1248,26 @@
             {@render_error()}

-    render_assignments: ->
-        if not @props.assignments?
-            return
+    compute_assignment_list: ->
         v = immutable_to_list(@props.assignments, 'assignment_id')
         search = (@state.search ? '').trim().toLowerCase()
+        num_omitted = 0
         if search
             words = misc.split(search)
             matches = (x) ->  # TODO: refactor with student search, etc.
                 k = x.path.toLowerCase()
                 for w in words
                     if k.indexOf(w) == -1
+                        num_omitted += 1
                         return false
                 return true
             v = (x for x in v when matches(x))
         v.sort (a,b) ->
             return misc.cmp(a.path.toLowerCase(), b.path.toLowerCase())
-        for x in v
+        return {assignments:v, num_omitted:num_omitted}
+
+    render_assignments: (assignments) ->
+        for x in assignments
             <Assignment key={x.assignment_id} assignment={@props.assignments.get(x.assignment_id)}
                     project_id={@props.project_id}  flux={@props.flux}
                     students={@props.students} user_map={@props.user_map}
@@ -1260,8 +1275,9 @@
                     />

     render :->
-        <Panel header={@render_header()}>
-            {@render_assignments()}
+        {assignments, num_omitted} = @compute_assignment_list()
+        <Panel header={@render_header(num_omitted)}>
+            {@render_assignments(assignments)}
         </Panel>

 Settings = rclass


Call prevent default so links don't trigger

--- src/components/surety-guard.cjsx
+++ src/components/surety-guard.cjsx
@@ -41,6 +41,7 @@
     </BS.Popover>

   maybeShow: (ev) ->
+    ev.preventDefault()
     @onConfirm(ev) if @props.onlyPromptIf and not @props.onlyPromptIf()

   render: ->


Update seeds.coffee for issuer field

Update seeds.coffee for issuer field

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- db/seeds.coffee
+++ db/seeds.coffee
@@ -27,7 +27,7 @@
       player: albireox
       server: gemcraft
       reason: 'advertising obsidiancraft and griefing'
-      by: dyrus
+      issuer: dyrus
       date: new Date("2013-04-03 13:23:15")
       end: new Date("2013-04-04 13:23:15")
       global: true


add comma just in case

add comma just in case

--- src/daily-report/states/preview/controller.coffee
+++ src/daily-report/states/preview/controller.coffee
@@ -38,7 +38,7 @@
       previousGoals,
       todaysGoals,
       sections,
-      d3.select('#bdcgraph')[0][0].firstChild
+      d3.select('#bdcgraph')[0][0].firstChild,
       true
     )
     .then (message) ->


Commands on invalid controls are ignored with a rejection

--- lib/Control.coffee
+++ lib/Control.coffee
@@ -1,4 +1,5 @@

+Q = require('q')
 _ = require('underscore')
 Backbone = require('backbone')

@@ -47,8 +48,12 @@
     _.find(@get('memberships'), (membership) -> _.isEqual(membership.path, path))

   executeCommand: (verb, params) ->
-    @log 'debug', "Executing command #{verb} with params: #{JSON.stringify(params)}"
-    @commands[verb](this, params)
+    if @isValid()
+      @log 'debug', "Executing command #{verb} with params: #{JSON.stringify(params)}"
+      @commands[verb](this, params)
+    else
+      @log 'warn', "Ignoring #{verb} command; not valid"
+      Q.fcall(-> throw new Error("Invalid"))

   hasCommand: (verb) ->
     @commands[verb]?


Return content when disable_cors is set

Return content when disable_cors is set

--- src/trans-xhr.coffee
+++ src/trans-xhr.coffee
@@ -58,7 +58,7 @@

     xhr_cors: (req, res, content) ->
         if @options.disable_cors
-            return
+            return content

         if !req.headers['origin']
             origin = '*'


Remove excess console.log

Remove excess console.log

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -794,7 +794,6 @@
         ## ()
         ## which prevents it from being separated, while still allowing KaTeX
         ## to do its automatic line breaking.
-        console.log out
         out = out.replace '', (match) ->
           """#{match}#{leftPunct}"""
     if rightPunct


lib/gearman: clean up log

lib/gearman: clean up log

--- lib/gearman.coffee
+++ lib/gearman.coffee
@@ -86,14 +86,13 @@
     @connecting = true
     console.log "GEARMAN #{@uid}: connecting..." if @debug
     @reconnecter = reconnect (socket) =>
-      console.log 'socket', socket
+      console.log "GEARMAN #{@uid}: connected!" if @debug
       @socket = socket
       @socket.on "error", @errorHandler.bind @
       @socket.on "data", @receive.bind @
       @socket.setKeepAlive true
       @connecting = false
       @connected = true
-      console.log "GEARMAN #{@uid}: connected!" if @debug
       @emit "connect"
       @processCommandQueue()
     @reconnecter.on 'reconnect', ->


Renamed functions.

Renamed functions.

--- lib/network.coffee
+++ lib/network.coffee
@@ -2,29 +2,33 @@
 command = require './command.coffee'

 ###
+	* Lists bridged network interfaces.
+	*
 	* @param {function(?err, result)} callback
 ###
-exports.bridged_ifs = (callback) ->
+exports.list_bridged_ifs = (callback) ->
 	command.exec 'list', 'bridgedifs', (err, code, output) ->
 		return callback err if err
 		return callback new Error "cannot list bridged ifs" if code > 0
 		return callback null, parse.linebreak_list(output) if callback
-
+
 ###
+	* Lists hostonly network interfaces.
+	*
 	* @param {function(?err, result)} callback
 ###
-exports.hostonly_ifs = (callback) ->
+exports.list_hostonly_ifs = (callback) ->
 	command.exec 'list', 'hostonlyifs', (err, code, output) ->
 		return callback err if err
 		return callback new Error "cannot list hostonly ifs" if code > 0
 		return callback null, parse.linebreak_list(output) if callback
-
+
 ###
-	* @param {string} name
+	* @param {string} vm
 	* @param {function(?err, result)} callback
 ###
-exports.list_adaptors = (name, callback) ->
-	machine.properties name, (err, result) ->
+exports.list_adaptors = (vm, callback) ->
+	machine.properties vm, (err, result) ->
 		return callback err if err

 		adaptors = {}
@@ -45,7 +49,7 @@
 			ref[path] = val.value

 		callback null, adaptors if callback
-
+
 ###
 	* Sets hostonly network adaptor on vm.
 	*


no method

no method

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -13,7 +13,7 @@
     @debugger.activate()

   deactivate: ->
-    mod.deactivate() for mod in [@modules, @console, @workspace, @frontend]
+    mod.deactivate() for mod in [@modules, @console, @frontend]

   consumeInk: (ink) ->
     @evaluation.ink = ink


Silently ignore unrecognized TransferComplete

--- lib/session.coffee
+++ lib/session.coffee
@@ -162,9 +162,11 @@
 transferComplete = (sessionData, rpcReq, callback) ->
   commandKey = rpcReq.commandKey
   operation = sessionData.operations[commandKey]
+
   if not operation?
-    # TODO Show a warning instead
-    throw new Error('Invalid command key')
+    # TODO Show a warning
+    return callback(null, {type : 'TransferCompleteResponse'})
+
   instance = operation.args.instance

   delete sessionData.operations[commandKey]


Removed prepareButton

Removed prepareButton

--- lib/toolbar.coffee
+++ lib/toolbar.coffee
@@ -34,20 +34,6 @@
       default: '24px'
       enum: ['16px', '24px']

-  prepareButton: (icon, callback, tooltip, iconset) ->
-    iconClass = if !iconset then 'icon-' + icon else iconset + '-' + icon
-
-    button = $$ ->
-      @div class: 'icon ' + iconClass, title: tooltip
-
-    button.on 'click', =>
-      if typeof(callback) == 'string'
-        atom.workspaceView.trigger callback
-      else
-        callback()
-
-    button
-
   prependButton: (icon, callback, tooltip=null, iconset=null) ->
     button = new ToolbarButtonView icon, callback, tooltip, iconset
     @toolbarView.prepend button


Fix utxo sort

Fix utxo sort

--- app/src/models/output.coffee
+++ app/src/models/output.coffee
@@ -26,7 +26,9 @@
     @findOrCreate(uid: uid, base, context)

   @utxo: (context = ledger.database.contexts.main) ->
-    output for output in @find(path: {$ne: undefined}, context).data() when _.isEmpty(Input.find(uid: output.get('uid'), context).data())
+    result = (output for output in @find(path: {$ne: undefined}, context).data() when _.isEmpty(Input.find(uid: output.get('uid'), context).data()))
+    result.sort (a, b) ->
+      b.get('confirmations') - a.get('confirmations')

   get: (key) ->
     transaction = =>


Split keybindings by platform

Split keybindings by platform

--- keymaps/symbols-view.cson
+++ keymaps/symbols-view.cson
@@ -1,6 +1,13 @@
-'.editor':
-  'meta-r': 'symbols-view:toggle-file-symbols'
-  'meta-alt-down': 'symbols-view:go-to-declaration'
+'.platform-darwin .editor':
+  'cmd-r': 'symbols-view:toggle-file-symbols'
+  'cmd-alt-down': 'symbols-view:go-to-declaration'

-'body':
-  'meta-R': 'symbols-view:toggle-project-symbols'
+'.platform-win32 .editor':
+  'ctrl-r': 'symbols-view:toggle-file-symbols'
+  'ctrl-alt-down': 'symbols-view:go-to-declaration'
+
+'.platform-darwin':
+  'cmd-R': 'symbols-view:toggle-project-symbols'
+
+'.platform-win32':
+  'ctrl-R': 'symbols-view:toggle-project-symbols'


Extract ReadUsers code into generic algorithm

--- lib/Task/ActivityTask/Read/ReadUsers.coffee
+++ lib/Task/ActivityTask/Read/ReadUsers.coffee
@@ -1,49 +1,9 @@
 _ = require "underscore"
 Promise = require "bluebird"
-Read = require "../../../../core/lib/Task/ActivityTask/Read"
+OptimisticLookahead = require "../../../../core/lib/Task/ActivityTask/Read/OptimisticLookahead"

-class ReadUsers extends Read
-  constructor: (options, dependencies) ->
-    _.defaults options,
-      chapterSize: 10
-      chapterStart: 1
-    super
-  execute: ->
-    new Promise (resolve, reject) =>
-      @reject = reject
-      @resolve = resolve
-      @chapterPromises = []
-      @jumpToChapter(@chapterStart)
-      @readChapter()
-      return null # don't leak Promise; will resolve manually
-  readChapter: ->
-    promises = @getChapterPromises()
-    promises[0] = promises[0].spread (response, body) ->
-#      return if @isErrorEmitted # should we return here if some other promise has been rejected?
-      if _.isArray(body) and body.length # the last page of current chapter was full of data, so we should read next chapter
-        @jumpToChapter(@chapterStart + @chapterSize)
-        @readChapter()
-      else
-        @end()
-    @chapterPromises.push(
-      Promise.all(promises).bind(@)
-      .catch (error) ->
-        @reject(error)
-    )
-  end: ->
-    Promise.all(@chapterPromises).bind(@)
-    .then -> @output.end()
-    .then @resolve
-    return null # break infinite loop
-  jumpToChapter: (chapterStart) ->
-    @chapterStart = chapterStart
-    @chapterEnd = chapterStart + @chapterSize - 1
-  getChapterPromises: ->
-    @readPage(page) for page in [@chapterEnd..@chapterStart] # reverse order, for faster feedback on whether we should read the next chapter
-  readPage: (page) ->
-    @binding.getUsers({page: page}).bind(@)
-    .spread (response, body) ->
-      @output.write(object) for object in body
-      [response, body]
+class ReadUsers extends OptimisticLookahead
+  shouldReadNextChapter: (response, body) -> _.isArray(body) and body.length
+  getPage: (page) -> @binding.getUsers({page: page})

 module.exports = ReadUsers


Add support for Safari Nightly 537.38+ build (addresses issue #6)

--- log.coffee
+++ log.coffee
@@ -76,8 +76,15 @@
 isSafari = -> /Safari/.test(navigator.userAgent) and /Apple Computer/.test(navigator.vendor)
 isIE = -> /MSIE/.test(navigator.userAgent)

+# Safari starting supporting stylized logs in Nightly 537.38+
+# See https://github.com/adamschwartz/log/issues/6
+safariSupport = ->
+    m = navigator.userAgent.match /AppleWebKit\/(\d+)\.(\d+)(\.|\+|\s)/
+    return false unless m
+    return 537.38 >= parseInt(m[1], 10) + (parseInt(m[2], 10) / 100)
+
 # Export
-if isSafari() or isIE()
+if (isSafari() and not safariSupport()) or isIE()
     window.log = _log
 else
     window.log = log


Removed CUI.CSS (new CSSLoader) as it is not needed by CUI itself anymore.

--- src/base/CUI.coffee
+++ src/base/CUI.coffee
@@ -22,8 +22,6 @@

 	@start: ->

-		@CSS = new CUI.CSSLoader()
-
 		trigger_viewport_resize = =>
 			console.info("CUI: trigger viewport resize.")
 			CUI.Events.trigger
@@ -34,7 +32,7 @@
 			node: window
 			call: (ev, info) =>
 				console.info("CUI: caught window resize event.")
-				if CUI.__ng__ && !CUI.browser.ie
+				if !CUI.browser.ie
 					trigger_viewport_resize()
 				else
 					CUI.scheduleCallback(ms: 500, call: trigger_viewport_resize)


copy noscript tag list by slicing so that removeNoscriptTags works with multiple noscript tags.

in google chrome 24.0.1312.57 the tag list changes while we iterate over it and delete tags.
in order to make this function work with multiple no script tags we have to copy the original tag list.

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -94,7 +94,8 @@
     parentNode.insertBefore copy, nextSibling

 removeNoscriptTags = ->
-  noscript.parentNode.removeChild noscript for noscript in document.body.getElementsByTagName 'noscript'
+  noscriptTags = Array::slice.call document.body.getElementsByTagName 'noscript'
+  noscript.parentNode.removeChild noscript for noscript in noscriptTags

 reflectNewUrl = (url) ->
   if url isnt document.location.href


Fixes #924. Beautify On Save changes the TextEditor not the file

It seems that in a recent update of Atom, Atom-Beautify's beautify on
save feature would actually crash Atom and in some cases even delete all
of the contents of the file!

After some investigation, it looks like writing directly to the file
with fs.write may have been the culprit and crashing Atom.

--- src/beautify.coffee
+++ src/beautify.coffee
@@ -72,112 +72,117 @@
     atom.notifications?.addError(error.message, {
       stack, detail, dismissable : true})

-beautify = ({onSave}) ->
-
-  plugin.checkUnsupportedOptions()
-
-  # Continue beautifying
-  path ?= require("path")
-  forceEntireFile = onSave and atom.config.get("atom-beautify.general.beautifyEntireFileOnSave")
-
-  # Get the path to the config file
-  # All of the options
-  # Listed in order from default (base) to the one with the highest priority
-  # Left = Default, Right = Will override the left.
-  # Atom Editor
-  #
-  # User's Home path
-  # Project path
-  # Asynchronously and callback-style
-  beautifyCompleted = (text) ->
-
-    if not text?
-      # Do nothing, is undefined
-      # console.log 'beautifyCompleted'
-    else if text instanceof Error
-      showError(text)
-    else if typeof text is "string"
-      if oldText isnt text
-
-        # console.log "Replacing current editor's text with new text"
-        posArray = getCursors(editor)
-
-        # console.log "posArray:
-        origScrollTop = getScrollTop(editor)
-
-        # console.log "origScrollTop:
-        if not forceEntireFile and isSelection
-          selectedBufferRange = editor.getSelectedBufferRange()
-
-          # console.log "selectedBufferRange:
-          editor.setTextInBufferRange selectedBufferRange, text
-        else
-
-          # console.log "setText"
-          editor.setText text
-
-        # console.log "setCursors"
-        setCursors editor, posArray
-
-        # console.log "Done setCursors"
-        # Let the scrollTop setting run after all the save related stuff is run,
-        # otherwise setScrollTop is not working, probably because the cursor
-        # addition happens asynchronously
-        setTimeout ( ->
-
-          # console.log "setScrollTop"
-          setScrollTop editor, origScrollTop
-          return
-        ), 0
+beautify = ({editor, onSave}) ->
+  return new Promise((resolve, reject) ->
+
+    plugin.checkUnsupportedOptions()
+
+    # Continue beautifying
+    path ?= require("path")
+    forceEntireFile = onSave and atom.config.get("atom-beautify.general.beautifyEntireFileOnSave")
+
+    # Get the path to the config file
+    # All of the options
+    # Listed in order from default (base) to the one with the highest priority
+    # Left = Default, Right = Will override the left.
+    # Atom Editor
+    #
+    # User's Home path
+    # Project path
+    # Asynchronously and callback-style
+    beautifyCompleted = (text) ->
+
+      if not text?
+        # Do nothing, is undefined
+        # console.log 'beautifyCompleted'
+      else if text instanceof Error
+        showError(text)
+        return reject(text)
+      else if typeof text is "string"
+        if oldText isnt text
+
+          # console.log "Replacing current editor's text with new text"
+          posArray = getCursors(editor)
+
+          # console.log "posArray:
+          origScrollTop = getScrollTop(editor)
+
+          # console.log "origScrollTop:
+          if not forceEntireFile and isSelection
+            selectedBufferRange = editor.getSelectedBufferRange()
+
+            # console.log "selectedBufferRange:
+            editor.setTextInBufferRange selectedBufferRange, text
+          else
+
+            # console.log "setText"
+            editor.setText text
+
+          # console.log "setCursors"
+          setCursors editor, posArray
+
+          # console.log "Done setCursors"
+          # Let the scrollTop setting run after all the save related stuff is run,
+          # otherwise setScrollTop is not working, probably because the cursor
+          # addition happens asynchronously
+          setTimeout ( ->
+
+            # console.log "setScrollTop"
+            setScrollTop editor, origScrollTop
+            return resolve(text)
+          ), 0
+      else
+        error = new Error("Unsupported beautification result '#{text}'.")
+        showError(error)
+        return reject(error)
+
+      # else
+      # console.log "Already Beautiful!"
+      return
+
+    # console.log 'Beautify time!'
+    #
+    # Get current editor
+    editor = editor ? atom.workspace.getActiveTextEditor()
+
+
+    # Check if there is an active editor
+    if not editor?
+      return showError( new Error("Active Editor not found. "
+        "Please select a Text Editor first to beautify."))
+    isSelection = !!editor.getSelectedText()
+
+
+    # Get editor path and configurations for paths
+    editedFilePath = editor.getPath()
+
+
+    # Get all options
+    allOptions = beautifier.getOptionsForPath(editedFilePath, editor)
+
+
+    # Get current editor's text
+    text = undefined
+    if not forceEntireFile and isSelection
+      text = editor.getSelectedText()
     else
-      showError( new Error("Unsupported beautification result '#{text}'."))
-
-    # else
-    # console.log "Already Beautiful!"
+      text = editor.getText()
+    oldText = text
+
+
+    # Get Grammar
+    grammarName = editor.getGrammar().name
+
+
+    # Finally, beautify!
+    try
+      beautifier.beautify(text, allOptions, grammarName, editedFilePath, onSave : onSave)
+      .then(beautifyCompleted)
+      .catch(beautifyCompleted)
+    catch e
+      showError(e)
     return
-
-  # console.log 'Beautify time!'
-  #
-  # Get current editor
-  editor = atom.workspace.getActiveTextEditor()
-
-
-  # Check if there is an active editor
-  if not editor?
-    return showError( new Error("Active Editor not found. "
-      "Please select a Text Editor first to beautify."))
-  isSelection = !!editor.getSelectedText()
-
-
-  # Get editor path and configurations for paths
-  editedFilePath = editor.getPath()
-
-
-  # Get all options
-  allOptions = beautifier.getOptionsForPath(editedFilePath, editor)
-
-
-  # Get current editor's text
-  text = undefined
-  if not forceEntireFile and isSelection
-    text = editor.getSelectedText()
-  else
-    text = editor.getText()
-  oldText = text
-
-
-  # Get Grammar
-  grammarName = editor.getGrammar().name
-
-
-  # Finally, beautify!
-  try
-    beautifier.beautify(text, allOptions, grammarName, editedFilePath, onSave : onSave)
-    .then(beautifyCompleted)
-    .catch(beautifyCompleted)
-  catch e
-    showError(e)
-  return
+  )

 beautifyFilePath = (filePath, callback) ->
   logger.verbose('beautifyFilePath', filePath)
@@ -500,25 +505,17 @@
       beautifyOnSave = atom.config.get(key)
       logger.verbose('save editor positions', key, beautifyOnSave)
       if beautifyOnSave
-        posArray = getCursors(editor)
-        origScrollTop = getScrollTop(editor)
         logger.verbose('Beautifying file', filePath)
-        beautifyFilePath(filePath, ->
+        beautify({editor, onSave: true})
+        .then(() ->
           logger.verbose('Done beautifying file', filePath)
           if editor.isAlive() is true
-            logger.verbose('Reloading TextEditor buffer...')
-            buffer.reload()
-            logger.verbose('Reloaded TextEditor buffer.')
-            # Let the scrollTop setting run after all the save related stuff is run,
-            # otherwise setScrollTop is not working, probably because the cursor
-            # addition happens asynchronously
-            setTimeout ( ->
-              logger.verbose('restore editor positions', posArray,origScrollTop)
-              setCursors(editor, posArray)
-              setScrollTop(editor, origScrollTop)
-              # console.log "setScrollTop"
-              return
-            ), 0
+            logger.verbose('Saving TextEditor...')
+            editor.save()
+            logger.verbose('Saved TextEditor.')
+        )
+        .catch((error) ->
+          return showError(error)
         )
       )
     plugin.subscriptions.add disposable


Fix error if filename matches an existing branch

--- lib/models/git-checkout-current-file.coffee
+++ lib/models/git-checkout-current-file.coffee
@@ -5,7 +5,7 @@
 gitCheckoutCurrentFile = ->
   currentFile = atom.project.getRepo().relativize atom.workspace.getActiveEditor()?.getPath()
   git(
-    ['checkout', currentFile],
+    ['checkout', '--', currentFile],
     (data) -> new StatusView(type: 'success', message: data.toString())
   )


bind => on

bind => on

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -21,6 +21,8 @@
 ###
 Bindable = (I={}, self) ->
   eventCallbacks = {}
+
+  bind: ->

   ###*
   Adds a function as an event listener.


server/index: fix old proxy adress to the new one

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -181,7 +181,7 @@
                   rabbitkey : key

                 apiServer   = 'kontrol.in.koding.com'
-                proxyServer = 'proxy.in.koding.com'
+                proxyServer = 'proxy-2.in.koding.com'
                 # local development
                 # apiServer   = 'localhost:8000'
                 # proxyServer = 'mahlika.local'


added kite name into rabbitmq user creation to limit permissions

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -115,8 +115,8 @@
     if err? or !kite?
       res.send 401
       return
-
-    rabbitAPI.newUser req.query.key, req.query.secret, (err, data) =>
+    console.log kite.kiteName
+    rabbitAPI.newUser req.query.key, req.query.secret, kite.kiteName, (err, data) =>
       creds =
         protocol  : 'amqp'
         host      : mq.host


Fixed fuzzy-finder's file regex

Fixed fuzzy-finder's file regex

--- src/packages/fuzzy-finder/src/fuzzy-finder.coffee
+++ src/packages/fuzzy-finder/src/fuzzy-finder.coffee
@@ -6,7 +6,7 @@

 module.exports =
 class FuzzyFinder extends SelectList
-  filenameRegex: /([^\w\.\-\/\\])/
+  filenameRegex: /[\w\.\-\/\\]+/

   @activate: (rootView) ->
     @instance = new FuzzyFinder(rootView)


Callback refactoring

Callback refactoring

--- src/callback.coffee
+++ src/callback.coffee
@@ -15,11 +15,11 @@

       result

-    @run = ->
-      do (properties = @properties())->
-        for obj in properties
-          eval('var ' + obj + ' = (' + factorySource.toString() + ')(' + '"' + obj + '"' + ');')
+    @run = do (properties = @properties())-> ->
+      for obj in properties
+        this[obj] = eval("(#{factorySource})('#{obj}')")
+        eval("#{obj} = this.#{obj};")

-        eval('(' + fn.toString() + ')')()
+      fn.call(this)

     this


Sort the output of `balena devices supported` alphabetically by slug

Change-type: patch
Signed-off-by: Paulo Castro <fd077434a7c3095bfe440741787d02f6a7bab07e@balena.io>

--- lib/actions/device.coffee
+++ lib/actions/device.coffee
@@ -130,10 +130,8 @@
 		visuals = require('resin-cli-visuals')

 		balena.models.config.getDeviceTypes().then (deviceTypes) ->
-			console.log visuals.table.horizontal deviceTypes, [
-				'slug'
-				'name'
-			]
+			fields = ['slug', 'name']
+			console.log visuals.table.horizontal(_.sortBy(deviceTypes, fields), fields)
 		.nodeify(done)

 exports.register =


Removed logs from ToxWorker::getPeerInfo

--- lib/atox-toxWorker.coffee
+++ lib/atox-toxWorker.coffee
@@ -252,10 +252,9 @@

     if fID < 0
       e.cb {key: key, fID: -1, name: name, color: "#AAA"} if e.cb?
-      return @inf "Peer #{e.peer} in GC #{e.gID} is '#{name}' and NOT A CONTACT (#{key})"
+      return

     e.cb {key: key, fID: fID, name: name, color: @friends[fID].color} if e.cb?
-    @inf "Peer #{e.peer} in GC #{e.gID} is '#{name}' (#{key})"
     #catch err
     #  console.log err
     #  return @err "Failed to get peer (#{e.peer}) info in group #{e.gID}"


Proper flats and sharps.

Proper flats and sharps.

--- lib/compiler/nodes.coffee
+++ lib/compiler/nodes.coffee
@@ -11,8 +11,13 @@
     toHtml: -> throw new Error 'override me please!!!'

-# Some simple methods
-chordsLine = (line) -> '
' + line + '

'
+chordsLine = (line) ->
+    # Do proper flat and sharp symbols.
+    line = line.replace /b/g, '♭'
+    line = line.replace /#/g, '♯'
+
+    '
' + line + '

'
+
 lyricsLine = (line) -> '
' + line + '

'
 verseBlock = (lines) -> '' + lines.join("\n") + ''

@@ -41,12 +46,14 @@
         verseBlock lines

+# This verse just has chords.
 class VerseChords extends Node
     toHtml: ->
         lines = _.map @section, chordsLine
         verseBlock lines

+# This verse just has lyrics.
 class VerseLyrics extends Node
     toHtml: ->
         lines = _.map @section, lyricsLine


Dont mark booleans as used

Dont mark booleans as used

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -3283,7 +3283,7 @@

 nameTranslate = (ctx, atom, symbol, type) ->
   id = ctx.declarationId symbol
-  ctx.addToUsedNames symbol unless ctx.isMacroDeclared symbol
+  use = no
   translation =
     if isConst atom
       atom.label = 'const'
@@ -3291,9 +3291,12 @@
         when 'True' then 'true'
         when 'False' then 'false'
         else
+          use = yes
           (jsAccess (validIdentifier symbol), "_value")
     else
+      use = yes
       (irReference symbol, type, ctx.currentScopeIndex())
+  ctx.addToUsedNames symbol if use and not (ctx.isMacroDeclared symbol)
   {id, type, translation}

 namespacedNameCompile = (ctx, atom, symbol) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -5,15 +5,14 @@
   self.bind "update", ->
     for name, cooldownOptions of I.cooldowns
       {approachBy, target} = cooldownOptions
-
+
       I[name] = I[name].approach(target, approachBy)

   cooldown: (name, options={target: 0, approachBy: 1, value: null}) ->
     unless I.cooldowns[name]
       I.cooldowns[name] = options
-
+
       if options.value?
         I[name] = options.value
       else
         I[name] = 100 unless I[name]
-


Code cleaned up by introducing _getParamName function to Dropzone as per suggestion by enyo. "npm test" passed.

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -610,7 +610,13 @@

     @emit "totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent

-
+  # @options.paramName can be a function taking one parameter rather than a string.
+  # A parameter name for a file is obtained simply by calling this with an index number.
+  _getParamName: (n) ->
+    if typeof @options.paramName is "function"
+      @options.paramName n
+    else
+      "#{@options.paramName}#{if @options.uploadMultiple then "[]" else ""}"

   # Returns a form that can be used as fallback if the browser does not support DragnDrop
   #
@@ -618,12 +624,10 @@
   # This code has to pass in IE7 :(
   getFallbackForm: ->
     return existingFallback if existingFallback = @getExistingFallback()
-    pn = @options.paramName
-    pnVal = if typeof pn is "function" then pn 0 else "#{pn}#{if @options.uploadMultiple then "[]" else ""}"

     fieldsString = """
"""
     fieldsString += """
#{@options.dictFallbackText}
""" if @options.dictFallbackText
-    fieldsString += """<input type="file" name="#{pnVal}" #{if @options.uploadMultiple then 'multiple="multiple"' } /><input type="submit" value="Upload!">
"""
+    fieldsString += """<input type="file" name="#{@_getParamName 0}" #{if @options.uploadMultiple then 'multiple="multiple"' } /><input type="submit" value="Upload!">
"""

     fields = Dropzone.createElement fieldsString
     if @element.tagName isnt "FORM"
@@ -1056,10 +1060,7 @@
     # Finally add the file
     # Has to be last because some servers (eg: S3) expect the file to be the
     # last parameter
-    for i in [0..files.length-1]
-      pn = @options.paramName
-      pnVal = if typeof pn is "function" then pn i else "#{pn}#{if @options.uploadMultiple then "[]" else ""}"
-      formData.append pnVal, files[i], files[i].name
+    formData.append @_getParamName(i), files[i], files[i].name for i in [0..files.length-1]

     xhr.send formData


Run only one of streaming connection end/close listeners.

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -75,9 +75,13 @@
         for message in messages
           @emit 'message', message
       res.on "close", =>
-        console.log "Connection terminated. Restart your connection to get back online."
+        return if @connectionClosing == true
+        @connectionClosing = true
+        console.log "Connection to Flowdock Streaming API closed. Restart your connection to get back online."
       res.on "end", =>
-        console.log 'Connection ended.'
+        return if @connectionClosing == true
+        @connectionClosing = true
+        console.log "Connection to Flowdock Streaming API ended. Restart your connection to get back online."
     req.end()

     return req


Removed SystemProperty Test from java comamnd

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -258,7 +258,6 @@
       class_name = args[0]
       class_args = args[1..]
     rs = new runtime.RuntimeState(stdout, user_input, bs_cl)
-    jvm.system_properties['hello'] = 'world' # HACK TEST TEST
     jvm.run_class(rs, class_name, class_args, -> controller.reprompt())
     return null  # no reprompt, because we handle it ourselves
   test: (args) ->


trying something else out

trying something else out

--- src/keymanager.iced
+++ src/keymanager.iced
@@ -166,7 +166,7 @@

   #--------

-  export_keys : (opts) ->
+  export_keys : (opts, cb) ->
     packets = [ @primary._pgp.export_framed(opts), @userid_packet().write(), @self_sig ]
     opts.subkey = true
     for subkey in @subkeys
@@ -174,7 +174,13 @@
     buf = Buffer.concat(packets)
     mt = C.message_types
     type = if opts.private then mt.private_key else mt.public_key
-    encode type, Buffer.concat(packets)
+    msg = Buffer.concat packets
+    err = ret = null
+    if (tsenc = opts.tsenc)?
+      await tsenc.run { data : msg, progress_hook : opts.asp?.progress_hook }, defer err, ret
+    else
+      ret = encode type, msg
+    cb err, ret

   #--------

@@ -240,6 +246,11 @@

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

+  @import_from_triplesec_pgp : ({raw, base}, cb) ->
+    await util.asyncify read_base64(raw), esc defer bin
+
+  #--------------
+
   # Import from a dearmored/decoded PGP message.
   @import_from_pgp_message : ({msg, raw, asp, userid}, cb) ->
     bundle = null
@@ -304,13 +315,16 @@

   # A private export consists of:
   #   1. The PGP public key block
-  #   2. The keybase message (Public and private keys, triplesec'ed)
+  #   2. The PGP private key block (Public and private keys, triplesec'ed)
   export_private_to_server : ({tsenc, asp}, cb) ->
     err = ret = null
-    if not (err = @_assert_signed())?
-      pgp = @pgp.export_keys { private : false }
-      await @keybase.export_keys { private : true, tsenc, asp }, defer err, keybase
-    ret = if err? then null else { pgp, keybase : box(keybase).toString('base64') }
+    unless (err = @_assert_signed())?
+      await @pgp.export_keys { private : false }, defer err, pub
+    unless err?
+      await @pgp.export_keys { private : true, tsenc, asp }, defer err, priv
+    unless err?
+      priv = priv.toString('base64')
+    ret = if err? then null else { pub, priv }
     cb err, ret

   #-----
@@ -322,7 +336,7 @@
     passphrase = bufferify passphrase if passphrase?
     if not regen? and (msg = @armored_pgp_private) then #noop
     else if not (err = @_assert_signed())?
-      msg = @pgp.export_keys({private : true, passphrase})
+      await @pgp.export_keys({private : true, passphrase}), defer err, msg
     cb err, msg

   #-----
@@ -331,8 +345,8 @@
   # to the client...
   export_pgp_public : ({asp, regen}, cb) ->
     msg = @armored_pgp_public unless regen
-    msg = @pgp.export_keys({private : false}) unless msg?
-    cb null, msg
+    await @pgp.export_keys({private : false}), defer err, msg unless msg?
+    cb err, msg

   #-----


add remark that "paypal is possible"

add remark that "paypal is possible"

--- salvus/page/billing.cjsx
+++ salvus/page/billing.cjsx
@@ -357,23 +357,28 @@
             @render_payment_method_field(field, control())

     render_payment_method_buttons : ->
-        <Row>
-            <Col sm=4>
-                Powered by Stripe
-            </Col>
-            <Col sm=8>
-                <ButtonToolbar className='pull-right'>
-                    <Button
-                        onClick  = {@submit_payment_method}
-                        bsStyle  = 'primary'
-                        disabled = {not @valid() or @state.submitting}
-                    >
-                        Add Credit Card
-                    </Button>
-                    <Button onClick={@props.on_close}>Cancel</Button>
-                </ButtonToolbar>
-            </Col>
-        </Row>
+

+            <Row>
+                <Col sm=4>
+                    Powered by Stripe
+                </Col>
+                <Col sm=8>
+                    <ButtonToolbar className='pull-right'>
+                        <Button
+                            onClick  = {@submit_payment_method}
+                            bsStyle  = 'primary'
+                            disabled = {not @valid() or @state.submitting}
+                        >
+                            Add Credit Card
+                        </Button>
+                        <Button onClick={@props.on_close}>Cancel</Button>
+                    </ButtonToolbar>
+                </Col>
+            </Row>
+

+                (Using PayPal is also possible -- email <HelpEmailLink/>.)
+

+

     render_error : ->
         if @state.error


syncstring: set the timestamp interval back up for multi-machine use.

--- salvus/syncstring.coffee
+++ salvus/syncstring.coffee
@@ -80,9 +80,9 @@
 # propogate after this long may be lost.  This way we still eventually see
 # patches that were written to the database, but that we missed
 # due to them not being propogates to all data centers.
-
-TIMESTAMP_OVERLAP      = 60000
-TIMESTAMP_OVERLAP      = 8000
+# Also, the poll interval is relevant.
+
+TIMESTAMP_OVERLAP      = 60000    # 1 minute.

 # If there are more than DB_PATCH_SQUASH_THRESH patches for a given string,
 # we squash the old patch history into a single patch the first time
@@ -902,7 +902,7 @@
 #---------------------------------------------------------------------

 ###
-x={};require('bup_server').global_client(cb:(e,c)->x.c=c; x.s = require('syncstring'); x.s.init_syncstring_db(x.c.database); x.ss=x.s.syncdb(string_id:'c26db83a-7fa2-44a4-832b-579c18fac65f', cb:(e,t)->x.t=t))
+id='c26db83a-7fa2-44a4-832b-579c18fac65f';x={};require('bup_server').global_client(cb:(e,c)->x.c=c; x.s = require('syncstring'); x.s.init_syncstring_db(x.c.database); x.ss=x.s.syncdb(string_id:id, cb:(e,t)->x.t=t))
 ###
 _syncdb_cache = {}
 _syncdb_callbacks = {}


fixes #15

fixes #15

--- lib/ensime-startup.coffee
+++ lib/ensime-startup.coffee
@@ -102,7 +102,7 @@
   console.log("sbt: " + cmd)

   # run sbt "saveClasspath" "clean"
-  pid = spawn("#{atom.config.get('Ensime.sbtExec')}", ['saveClasspath', 'clean'], {cwd: tempdir})
+  pid = spawn("#{atom.config.get('Ensime.sbtExec')}", ['-Dsbt.log.noformat=true', 'saveClasspath', 'clean'], {cwd: tempdir})
   pid.stdout.on 'data', (chunk) -> log(chunk.toString('utf8'))
   pid.stderr.on 'data', (chunk) -> log('ensime startup exec error: ' + chunk.toString('utf8'))


Rationalise the order of these tests.

Rationalise the order of these tests.

The tests make more sense in this order.

--- lib/keyboard_utils.coffee
+++ lib/keyboard_utils.coffee
@@ -31,17 +31,15 @@
       else if key.length == 1 and not event.shiftKey
         key = key.toLowerCase()

-    if key of @keyNames
+    # It appears that key is not always defined (see #2453).
+    unless key
+      ""
+    else if key of @keyNames
       @keyNames[key]
-    # It appears that key is not always defined (see #2453).
-    else if not key?
-      ""
     else if key.length == 1
       key
-    else if 1 < key.length
+    else
       key.toLowerCase()
-    else
-      ""

   getKeyCharString: (event) ->
     if keyChar = @getKeyChar event


Larger version of pic / parse dogs & rodents also

--- API/src/scraper.coffee
+++ API/src/scraper.coffee
@@ -9,13 +9,16 @@
 cache = new NodeCache()

 tierfreunde_url = "http://www.tierheim-koeln-zollstock.de/tiervermittlung/katzen.html"
+zollstock_urls = ["http://www.tierheim-koeln-zollstock.de/tiervermittlung/katzen.html",
+              "http://www.tierheim-koeln-zollstock.de/tiervermittlung/hunde.html",
+              "http://www.tierheim-koeln-zollstock.de/tiervermittlung/nagetiere.html"];
 tierfreunde_base_url = "http://www.tierheim-koeln-zollstock.de/"

 tierschutz_url = "http://www.tierschutz-chemnitz.de/vm_hunde.php"
 tierschutz_splitpos = tierschutz_url.lastIndexOf '/'
 tierschutz_base_url = tierschutz_url.slice 0, tierschutz_splitpos+1

-get_tierfreunde = (url)->
+get_zollstock_tier = (url)->
     new Promise (f, r) ->
         request url, (err, response, body) ->
             if err
@@ -23,8 +26,8 @@
             $ = cheerio.load body
             content = $('.animalDetail')
             name = content.find('h1').text()
-            img = content.find('img').attr('src')
-            pic = tierfreunde_base_url + content.find('img').attr('src')
+            img = content.find('.lightbox-image').attr('href')
+            pic = tierfreunde_base_url + img
             id = content.find('h1').attr('id')
             content.find('h1').remove()
             content.find('a').remove()
@@ -40,7 +43,7 @@
                     .trim()
             f details

-get_tierfreundeUrls = (url) ->
+get_zollstock_urls = (url) ->
     new Promise (f, r) ->
         request url, (err, response, body) ->
             if err
@@ -102,18 +105,18 @@
                         urls.push detail_url
             f urls

-get_tierfreundedata = ->
-    new Promise (f, r) ->
-        get_tierfreundeUrls tierfreunde_url
-            .then (urls) ->
-                p = []
-                for url in urls
-                    p.push get_tierfreunde url
-                Promise.all p
-            .then (values) ->
-                f values
-            .catch (err) ->
-                r err
+get_zollstockdata = (tier_url)->
+    new Promise (f, r) ->
+          get_zollstock_urls tier_url
+              .then (urls) ->
+                  p = []
+                  for url in urls
+                      p.push get_zollstock_tier url
+                  Promise.all p
+              .then (values) ->
+                  f values
+              .catch (err) ->
+                  r err

 get_tierschutzdata = ->
     new Promise (f, r) ->
@@ -133,7 +136,11 @@
         #values = cache.get('tiere')
         #if not values
             #Promise.all [get_tierschutzdata(), get_tierfreundedata()]
-            Promise.all [ get_tierfreundedata()]
+            #Promise.all [ get_tierfreundedata()]
+            p = []
+            for url in zollstock_urls
+                p.push get_zollstockdata url
+            Promise.all p
                 .then (list_of_values) ->
                     values = _.union.apply null, list_of_values
                     cache.set('tiere', values, 60*60*24)


Fix typo in mongoose-store.coffee

Fix typo in mongoose-store.coffee

--- lib/mongoose-store.coffee
+++ lib/mongoose-store.coffee
@@ -6,7 +6,7 @@
   class MongooseStore extends session.Store
     constructor: (@options = {}) ->
       mongoose.connect @options.url if mongoose.connection.readyState is 0
-      @SessionModel = require('./model-session') options.ttl
+      @SessionModel = require('./model-session') @options.ttl

     get: (sid, callback) ->
@@ -36,4 +36,4 @@

     destroy: (sid, callback) ->
-      @SessionModel.remove(sid: sid).exec callback+      @SessionModel.remove(sid: sid).exec callback


scrollToColumZero when narrow-editor was not active text-editor

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -156,6 +156,7 @@
         @withIgnoreChange => @setQuery(text)
         @refreshWithDelay force: true, 100, =>
           @moveToSearchedWordOrBeginningOfSelectedItem()
+          @scrollToColumnZero() unless @isActive()
           @flashCursorLine()

   clearHistroy: ->
@@ -216,7 +217,13 @@
       @withIgnoreChange => @setQuery(word)
       @refresh(force: true).then =>
         @moveToSearchedWordOrBeginningOfSelectedItem()
+        @scrollToColumnZero() unless @isActive()
         @flashCursorLine()
+
+  scrollToColumnZero: ->
+    point = @editor.getCursorBufferPosition()
+    point.column = 0
+    @editor.scrollToBufferPosition(point, center: true)

   setModifiedState: (state) ->
     return if state is @modifiedState


also no zalgo on errors please

also no zalgo on errors please

--- lib/transforms/map.coffee
+++ lib/transforms/map.coffee
@@ -11,7 +11,8 @@
     debug "_transform #{JSON.stringify chunk}"
     if @options._async
       @options.fn chunk, (err, result) =>
-        return cb err if err
+        if err?
+          return setImmediate -> cb err
         @push result
         setImmediate cb
     else


copy paste from log working

copy paste from log working

--- lib/views/log-view.coffee
+++ lib/views/log-view.coffee
@@ -5,10 +5,9 @@
 class LogView extends ScrollView

   @content: (params) ->
-    @div class: 'ensime-log-view scroll-view', tabIndex: -1, =>
-      @div class: 'log', tabIndex: -1, =>
-        #@h1 class: 'panel-heading', params.title
-        @ul class: 'list-group padded', tabIndex: -1, outlet: "list"
+    @div class: 'ensime-log-view scroll-view native-key-bindings', tabIndex: -1, =>
+      #@h1 class: 'panel-heading', params.title
+      @ul class: 'list-group padded', outlet: "list"
   initialize: (params) ->
     @title = params.title


Remove resizing for the moment

Remove resizing for the moment

--- lib/views/terminal.coffee
+++ lib/views/terminal.coffee
@@ -31,10 +31,10 @@
     @openColor = @term.element.style.color

   handleEvents: ->
-    @on 'focus', =>
-      @resizeTerminal()
-    @on 'mousedown', '.terminal-view-resize-handle', (e) =>
-      @resizeStarted(e)
+    #@on 'focus', =>
+      #@resizeTerminal()
+    #@on 'mousedown', '.terminal-view-resize-handle', (e) =>
+      #@resizeStarted(e)

     @term.on 'data', (data) =>
       ipc.send 'terminal-data', data


Cookies should work when no window open.

--- lib/zombie/browser.coffee
+++ lib/zombie/browser.coffee
@@ -817,7 +817,11 @@
   # Returns all the cookies for this domain/path. Domain defaults to hostname of currently open page. Path defaults to
   # "/".
   cookies: (domain, path)->
-    return @_cookies.access(domain || @location.hostname, path || @location.pathname)
+    if !domain && location = @location
+      domain = @location.hostname
+    if !path && location = @location
+      path = @location.pathname
+    return @_cookies.access(domain || "localhost", path || "/")

   # Save cookies to a text string.  You can use this to load them back later on using `browser.loadCookies`.
   saveCookies: ->


Atom: Use Fira Code

Atom: Use Fira Code

--- files/.atom/config.cson
+++ files/.atom/config.cson
@@ -21,6 +21,7 @@
     useTreeSitterParsers: true
   editor:
     autoIndentOnPaste: false
+    fontFamily: "'FiraCode-Retina', 'SF Mono', Menlo, Consolas, DejaVu Sans Mono, monospace"
     invisibles: {}
     showIndentGuide: true
     softWrap: true


game started now sends gameLength

game started now sends gameLength

--- models/game.coffee
+++ models/game.coffee
@@ -19,7 +19,7 @@
       console.log "Starting Game"
       @start = true
       @timerRunning = true
-      @io.sockets.emit "game started"
+      @io.sockets.emit "game started", { gameLength: @gameLength }
       callback = () =>
         @timerRunning = false
         @gameOver()


Add some more greetings for hello respond

--- scripts/nostalgia.coffee
+++ scripts/nostalgia.coffee
@@ -437,7 +437,7 @@
     robot.respond /give up/i, giveUpRespond

     robot.hear /.*that's a bug.*/i, bobRossRespond
-    robot.hear /.*((good morning)|morning|sup|hey|hello|howdy|greetings|yo|hiya|welcome|bonjour|buenas dias|buenas noches|good day|what's up|what's happening|how goes it|howdy do|shalom),? +@?nostalgiabot!?.*/i, greetingRespond
+    robot.hear /.*(guten tag|hallo|hola|Góðan daginn|good morning|morning|sup|hey|hello|howdy|greetings|yo|hiya|welcome|bonjour|buenas dias|buenas noches|good day|what's up|what's happening|how goes it|howdy do|shalom),? +@?nostalgiabot!?.*/i, greetingRespond

     # Schedule a quote of the day
     schedule.scheduleJob process.env.QOTD_SCHEDULE, quoteOfTheDaySend(robot)


search submit now works

search submit now works

--- _assets/js/_search.coffee
+++ _assets/js/_search.coffee
@@ -34,6 +34,18 @@
     isSelected = false
     return

+  $('.search-submit').hover (->
+    isSelected = true
+    $('.search-submit').click () ->
+      selected = document.querySelector('#search-results a.selected')
+      url = selected.getAttribute('href') if selected
+      window.open url, '_self'
+      return
+    return
+  ), ->
+    isSelected = false
+    return
+
   $('#search-posts').focusout ->
     if !isSelected and $('#search-posts').val().length > 0
       $('#search-results').css('display', 'none')


remove In fn

remove In fn

--- app/builtins.coffee
+++ app/builtins.coffee
@@ -83,8 +83,6 @@

     KeyBindingComponent keys: all_keys, commands: CodeMirror.commands

-  fn 'In', 'Gets previous input', (ctx, n) ->
-    Context.value ctx.get_input_value n

   ObjectComponent = React.createClass
     displayName: 'ObjectComponent'


remove extra logging in migration and change file paths in migration 2

--- server-ce/migrations/2_doc_lines_delete_from_project.coffee
+++ server-ce/migrations/2_doc_lines_delete_from_project.coffee
@@ -2,15 +2,14 @@
 fs = require("fs")
 mongojs = require("mongojs")
 ObjectId = mongojs.ObjectId
-console.log Settings.mongo.url
 db = mongojs(Settings.mongo.url, ['projects', 'docs'])
 _ = require("lodash")
 async = require("async")
 exec = require("child_process").exec

-finished_projects_path = "./finished-projects"
-all_projects_path = "./all-projects"
-unmigrated_docs_path = "./unmigrated"
+finished_projects_path = "/tmp/finished-projects-2"
+all_projects_path = "/tmp/all-projects-2"
+unmigrated_docs_path = "/tmp/unmigrated-2"

 printProgress = ->
@@ -108,7 +107,6 @@
 		return callback(null, true)
 	db.docs.find({_id: ObjectId(doc._id+"")}, {_id: 1}).limit 1, (err, foundDocs)->
 		exists = foundDocs.length > 0
-		console.log doc._id, "Exits = "+exists
 		callback err, exists

 getWhichDocsCanBeDeleted = (docs, callback = (err, docsToBeDeleted, unmigratedDocs)->)->
@@ -131,8 +129,6 @@
 		$unset: {}
 	update.$unset["#{mongoPath}.lines"] = ""
 	update.$unset["#{mongoPath}.rev"] = ""
-	console.log "project id = #{ObjectId(project_id+'')}", update
-	#callback()
 	db.projects.update _id: ObjectId(project_id+''), update, callback


refer to project state as projectStateHash

--- services/web/app/coffee/Features/Compile/ClsiManager.coffee
+++ services/web/app/coffee/Features/Compile/ClsiManager.coffee
@@ -123,13 +123,13 @@
 			if project.compiler not in ClsiManager.VALID_COMPILERS
 				project.compiler = "pdflatex"

-			ClsiStateManager.checkProjectStateMatch project_id, project, (error, stateOk, projectState) ->
+			ClsiStateManager.checkProjectStateMatch project_id, project, (error, stateOk, projectStateHash) ->
 				return callback(error) if error?
 				logger.log project_id: project_id, checkState: stateOk, "checked project state"
-				if stateOk and options.syncType isnt "full" # incremental
+				# see if we can send an incremental update to the CLSI
+				if stateOk and options.syncType isnt "full"
 					ClsiManager._getContentFromDocUpdater project_id, (error, docUpdaterDocs) ->
 						return callback(error) if error?
-						# make this incremental
 						ProjectEntityHandler.getAllDocPathsFromProject project, (error, docPath) ->
 							return callback(error) if error?
 							docs = {}
@@ -137,19 +137,20 @@
 								path = docPath[doc._id]
 								docs[path] = doc
 							options.syncType = "incremental"
-							options.syncState = projectState
+							options.syncState = projectStateHash
+							# send new docs but not files as those are already on the clsi
 							ClsiManager._finaliseRequest project_id, options, project, docs, [], callback
 				else
 					ClsiManager._getContentFromMongo project_id, (error, docs, files) ->
 						return callback(error) if error?
 						# FIXME want to store state after project has been sent to
 						# clsi, but need to do it here.
-						ClsiStateManager.setProjectState project_id, project, (error, projectState) ->
+						ClsiStateManager.setProjectState project_id, project, (error, projectStateHash) ->
 							if error?
 								logger.err err:error, project_id:project_id, "error storing state in redis"
 								#return callback(error)
 							options.syncType = "full"
-							options.syncState = projectState
+							options.syncState = projectStateHash
 							ClsiManager._finaliseRequest project_id, options, project, docs, files, callback

 	_getContentFromDocUpdater: (project_id, callback = (error, docs) ->) ->


Add some comments on how to use the new listeners

--- src/listener.coffee
+++ src/listener.coffee
@@ -9,6 +9,10 @@
   # matcher  - A Function that determines if this listener should trigger the
   #            callback. The matcher takes a SlackRawMessage.
   # callback - A Function that is triggered if the incoming message matches.
+  #
+  # To use this listener in your own script, you can say
+  #
+  #     robot.listeners.push new SlackRawListener(robot, matcher, callback)
   constructor: (@robot, @matcher, @callback) ->

   # Public: Invokes super only for instances of SlackRawMessage
@@ -27,6 +31,10 @@
   # regex    - A Regex that determines if this listener should trigger the
   #            callback.
   # callback - A Function that is triggered if the incoming message matches.
+  #
+  # To use this listener in your own script, you can say
+  #
+  #     robot.listeners.push new SlackBotListener(robot, regex, callback)
   constructor: (@robot, @regex, @callback) ->
     @matcher = (message) =>
       if message instanceof SlackBotMessage


you can input number of lies allowed

you can input number of lies allowed

--- src/react/components/Main.coffee
+++ src/react/components/Main.coffee
@@ -9,16 +9,16 @@

   getInitialState: ->
     set: [] # truth set and lie set
-    maxLies: 2 # maximal number of lies

     # input values
     range: 1000
-    qa: 1
-    qb: 500
-    qx: No
+    maxLies: 2 # maximal number of lies
+    qa: 1 # query start
+    qb: 500 # query end
+    qx: No # query answer

   componentDidMount: ->
-    @handlePlayButton()
+    # @handlePlayButton()

   handleInputNumberChange: (event)->
     value = parseInt(event.target.value)
@@ -37,14 +37,14 @@
     @setState baru

   handleResetButton: ->
-    @setState
-      set: []
+    @setState set: []

   handlePlayButton: ->
-    {range} = @state
-    if range isnt ''
+    {range, maxLies} = @state
+    if range isnt '' && maxLies isnt ''
       @setState
         set: [[[1, range, Yes], [1, range, 0]]]
+        maxLies: min(16, max(0, maxLies))

   handleSubmitButton: ->
     if not @queryValidator()
@@ -83,27 +83,39 @@
   render: ->
     dom 'section', className: 'row',
       dom 'span', className: 'five columns',
-        dom 'div', {},
-          dom 'label', {}, 'Range [1-n]'
-          dom 'input',
-            type: 'text'
-            name: 'range'
-            onChange: @handleInputNumberChange
-            value: @state.range
-            disabled: @state.set.length isnt 0
-          dom 'i', {}, ' '
-          if @state.set.length is 0
-            dom 'button',
-              type: 'button'
-              className: 'button-primary'
-              onClick: @handlePlayButton
-              'Start'
-          else
-            dom 'button',
-              type: 'button'
-              className: 'button-danger'
-              onClick: @handleResetButton
-              'Restart'
+        dom 'div', className: 'row',
+          dom 'div', className: 'five columns',
+            dom 'label', {}, 'Range [1-n]'
+            dom 'input',
+              type: 'text'
+              className: 'u-full-width'
+              name: 'range'
+              onChange: @handleInputNumberChange
+              value: @state.range
+              disabled: @state.set.length isnt 0
+          dom 'div', className: 'two columns',
+            dom 'label', {}, 'Lie(s)'
+            dom 'input',
+              type: 'text'
+              className: 'u-full-width'
+              name: 'maxLies'
+              onChange: @handleInputNumberChange
+              value: @state.maxLies
+              disabled: @state.set.length isnt 0
+          dom 'div', className: 'four columns',
+            dom 'label', className: 'u-invisible', 'i'
+            if @state.set.length is 0
+              dom 'button',
+                type: 'button'
+                className: 'button-primary'
+                onClick: @handlePlayButton
+                'Start'
+            else
+              dom 'button',
+                type: 'button'
+                className: 'button-danger'
+                onClick: @handleResetButton
+                'Restart'

         # Query game
         dom 'hr'
@@ -128,7 +140,7 @@
               value: @state.qb
               disabled: @state.set.length is 0

-          dom 'div', className: 'three columns',
+          dom 'div', className: 'two columns',
             dom 'label', {}, 'Answer'
             dom 'select',
               className: 'u-full-width'
@@ -144,7 +156,7 @@
             dom 'label', className: 'u-invisible', 'i'
             dom 'button',
               type: 'button'
-              className: 'button-primary u-full-width'
+              className: 'button-primary'
               disabled: @state.set.length is 0
               onClick: @handleSubmitButton
               'Submit'
@@ -176,7 +188,7 @@
                 dom 'span', key: j,
                   dom 'span',
                     className: "set color-#{(set[2] * 5) % 12}-muted",
-                    "#{set[0]} - #{set[1]}"
+                    "#{set[0]} - #{set[1]} (#{set[2]})"
                   dom 'i', {}, ' '

 module.exports = Main


Fix issue in class methods for Scaffold

Fix issue in class methods for Scaffold

--- cake/lib/scaffold.coffee
+++ cake/lib/scaffold.coffee
@@ -4,11 +4,11 @@
 module.exports = class Scaffold

   @generate: ->
-    scaffold = new Scaffold
+    scaffold = new @constructor
     scaffold.generate arguments...

   @destroy: ->
-    scaffold = new Scaffold
+    scaffold = new @constructor
     scaffold.destroy arguments...

   generate: (name = '', callback = ->) ->


Check for null when there are no images

Check for null when there are no images

if I did something like "hubot image me ablederfl33wo", I would get an error like:

/opt/hubot/scripts/google-images.coffee:47
      images = images.responseData.results;
                                  ^
TypeError: Cannot read property 'results' of null
    at imageMe (/opt/hubot/scripts/google-images.coffee:47:35)
    at IncomingMessage.ScopedClient.request (/opt/hubot/node_modules/hubot/node_modules/scoped-http-client/lib/index.js:61:20)
    at IncomingMessage.EventEmitter.emit (events.js:126:20)
    at IncomingMessage._emitEnd (http.js:366:10)
    at HTTPParser.parserOnMessageComplete [as onMessageComplete] (http.js:149:23)
    at Socket.socketOnEnd [as onend] (http.js:1356:12)
    at TCP.onread (net.js:418:26)

--- src/scripts/google-images.coffee
+++ src/scripts/google-images.coffee
@@ -37,8 +37,8 @@
     .query(q)
     .get() (err, res, body) ->
       images = JSON.parse(body)
-      images = images.responseData.results
-      if images.length > 0
+      images = images.responseData?.results
+      if images?.length > 0
         image  = msg.random images
         cb "#{image.unescapedUrl}#.png"


Fix avatar distortion issue on guaranty shop

Also fix some trailing whitespaces

--- src/plugins/partner_sku_reviews.coffee
+++ src/plugins/partner_sku_reviews.coffee
@@ -226,7 +226,7 @@
   # verification mark
   verificationMark = (purchased) ->
     return '' unless purchased
-
+
     """
       <div class="sa-verification-mark">
         @@translations.partner_sku_reviews.verified_purchase
@@ -1141,7 +1141,7 @@
     ##{flavor}-product-reviews-extended .sa-review-avatar {
       overflow: hidden;
       min-width: 40px;
-      height: 40px;
+      height: 40px !important;
       margin: 0 10px 0 0;
       border: 1px solid #e8e8e8;
       border-radius: 50%;


fixing indentation

fixing indentation

--- src/scripts/modules/media/media.coffee
+++ src/scripts/modules/media/media.coffee
@@ -51,11 +51,11 @@
       @regions.media.append(new MediaNavView({model: @model, hideProgress: true}))

     updateUrl: () ->
-       path = window.location.pathname
-       url = path.split('/')
-       pathArray = url[3]
-       collectionTitle = @model.get('title')
-       if collectionTitle? and not pathArray?
+      path = window.location.pathname
+      url = path.split('/')
+      pathArray = url[3]
+      collectionTitle = @model.get('title')
+      if collectionTitle? and not pathArray?
          newUrl = path + '/' + collectionTitle.replace(/\ /g,'_').substring(0,30)
          history.pushState {}, '', newUrl


Fix switching bugs for staff and unauthorised profiles.

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -193,20 +193,18 @@

 # :todo: more flexible implementation that checks group membership and stuff
 checkSwitchUserRights = (req, res, next) ->
-  # Staff can (still) switch into any profile.
-  if req.user.real.isStaff
-    return next()
-
-  # Otherwise check the canBeReally field of the target user.
   switchingTo = req.params.username
   console.log "SWITCH #{req.user.effective.shortName} -> #{switchingTo}"
   User.findByShortName switchingTo, (err, user) ->
     if err? or not user?
       return resp.send 500, err
-    if user.canBeReally and req.user.real.shortName in user.canBeReally
+    # Staff can (still) switch into any profile.
+    # Otherwise check the canBeReally field of the target user.
+    if req.user.real.isStaff or
+     (user.canBeReally and req.user.real.shortName in user.canBeReally)
       req.switchingTo = user
       return next()
-    return resp.send 403, { error:
+    return res.send 403, { error:
       "#{req.user.real.shortName} cannot switch to #{switchingTo}"}

 # Render the main client side app


change to job runner to remove job imports count error

--- server/job.coffee
+++ server/job.coffee
@@ -317,7 +317,7 @@

   if imports.length
     job_process.insert imports
-    job.reused = job.count - job.imports.length if job.count isnt job.imports.length
+    job.reused = job.count - imports.length if job.count isnt imports.length
   else
     job.done = true

@@ -568,16 +568,16 @@
     job_process.insert pn
   else if proc.order
     job_process.update {job: proc.job, order: proc.order+1}, {available:true}
-  #try
-  #  for jb in proc.job
-  #    if job_process.find(job:proc.job)?
+  try
+    for jb in proc.job
+      if not job_process.find(job:proc.job)?
         # note this only lets us know when the last one was run, so if we run progress on first process, then cap for 15 minutes,
         # and the whole job finishes within 15 minutes, no other progress check would run
         # need a way to run this IN 15 minutes, but only once - don't need to run multiples if already waiting to run
-  #      cap = API.job.capped 1, '15m', 'job_progress_' + jb
-  #      API.job.progress(jb) if cap?.capped isnt true
-  #    else
-  #      API.job.progress jb
+        #cap = API.job.capped 1, '15m', 'job_progress_' + jb
+        #API.job.progress(jb) if cap?.capped isnt true
+      #else
+        API.job.progress jb
   try
     if proc.callback
       cb = API


checking job interval

checking job interval

--- server/job.coffee
+++ server/job.coffee
@@ -375,7 +375,6 @@
     if v <= now
       delete API.job._ignoregroups[k]
   for s, t of API.job._ignoreids
-    console.log s, t, now, t <= now, API.job._ignoreids[s]
     if t <= now
       delete API.job._ignoreids[s]
   if (API.settings.job?.concurrency ?= 1000000000) <= job_processing.count()
@@ -388,7 +387,7 @@
     API.log {msg:'Checking for jobs to run', ignores: {groups:API.job._ignoregroups,ids:API.job._ignoreids}, function:'API.job.next', level:'debug'}
     match = must_not:[{term:{available:false}}] # TODO check this will get matched properly to something where available = false
     match.must_not.push({term: 'group.exact':g}) for g of API.job._ignoregroups
-    match.must_not.push({term: '_id.exact':m}) for m of API.job._ignoreids
+    match.must_not.push({term: '_id':m}) for m of API.job._ignoreids
     console.log(JSON.stringify match) if API.settings.log?.level is "all"
     p = job_process.find match, {sort:{priority:{order:'desc'}}, random:true} # TODO check if random sort works - may have to be more complex
     if p?


:new_moon::clock830: Updated at https://danielx.net/editor/

--- pixie.cson
+++ pixie.cson
@@ -9,7 +9,7 @@
   matrix: "distri/matrix:v0.3.1"
   observable: "distri/observable:v0.3.7"
   point: "distri/point:v0.2.0"
-  postmaster: "distri/postmaster:v0.5.0"
+  postmaster: "distri/postmaster:v0.5.2"
   runtime: "distri/runtime:v0.3.0"
   size: "distri/size:v0.1.4"
   "touch-canvas": "distri/touch-canvas:v0.4.2"


Router: Define latest workspace getter

Router: Define latest workspace getter

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -65,6 +65,12 @@
       sameUser  = m.getOwner() is username

       return m  if sameLabel and sameUser
+
+
+  getLatestWorkspace = ->
+
+    storage = KD.getSingleton('localStorageController').storage 'IDE'
+    return storage.getValue 'LatestWorkspace'

   loadIDE = (data) ->


one tiny typo

one tiny typo

--- client/locales/en.coffee
+++ client/locales/en.coffee
@@ -16,7 +16,7 @@
     "reset instructions": "After a password reset, you must reconfigure your confidential data like your mailbox or your bank account."
     "reset password placeholder": "Your new password"
     "reset button": "Change my password"
-    "reset success message": "Password surcessfully reset."
+    "reset success message": "Password successfully reset."

     # register page
     "register title": "Cozy - Sign up"


Remove IP address logging

Remove IP address logging

--- client/src/app/io.coffee
+++ client/src/app/io.coffee
@@ -747,21 +747,9 @@

     playerInfo = null # Current state of player
     snapshotFrameCounter = 0 ## Number of frames since last snapshot
-    playerIpAddress = null
     recordingData = false

     configIsValid = -> options.metrics and options.metrics.gameVersionId and options.metrics.host
-
-    updatePlayerIpAddress = ->
-      $.get("/api/ip").done (data, textStatus) -> playerIpAddress = data.ip
-
-    makeExtendedPlayerInfo = ->
-      extendedData = RW.cloneData(playerInfo)
-      if not extendedData.customData
-        extendedData.customData = {}
-      extendedData.customData.ipAddress = playerIpAddress
-
-      return extendedData

     beginGameSession = ->
       redmetrics.connect
@@ -827,7 +815,7 @@
         # Update player info if necessary
         if newPlayerInfo
           playerInfo = newPlayerInfo
-          redmetrics.updatePlayer(makeExtendedPlayerInfo(newPlayerInfo))
+          redmetrics.updatePlayer(newPlayerInfo)

         if createNewGameSession
           endGameSession().then(beginGameSession())
@@ -836,7 +824,6 @@

       destroy: -> # NOP

-    updatePlayerIpAddress()
     return io

 # The location buffer provides information about the URL and its different parts


Find all products

Find all products

--- server/routes/get.coffee
+++ server/routes/get.coffee
@@ -16,7 +16,7 @@
     #       downvotedProducts.push downvote.product

     # Do the product query
-    Product.find(displayDay: $gte: startOfDay)
+    Product.find()
       .populate('posterUser')
       .sort(downvotes: -1)
       .exec (err, products) ->


errata.

errata.

--- snippets/css.cson
+++ snippets/css.cson
@@ -7,7 +7,7 @@
   "Flexbox - align-content: flex-start;":
     leftLabelHTML: "Ⓢ"
     rightLabelHTML: "Flexbox align-content: flex-start"
-    prefix: "flexbox:flexcont"
+    prefix: "flexbox:align-content"
     body: "align-content: ${1:flex-start|flex-end|center|space-between|space-around|stretch};"
   "Flexbox - align-items: flex-start;":
     leftLabelHTML: "Ⓢ"


make startup message consistent

make startup message consistent

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -104,7 +104,7 @@
 	res.send error?.statusCode || 500

 app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) ->
-	logger.log "CLSI listening on #{host}:#{port}"
+	logger.info "CLSI starting up, listening on #{host}:#{port}"

 setInterval () ->
 	ProjectPersistenceManager.clearExpiredProjects()