Craft manual deprecation to get around stack depth limit

This allows the deprecation to be properly associated with the
package it originates from.

--- src/babel.coffee
+++ src/babel.coffee
@@ -140,19 +140,25 @@
   if sourceCode.startsWith('"use babel"') or sourceCode.startsWith("'use babel'")
     # Continue.
   else if sourceCode.startsWith('"use 6to5"') or sourceCode.startsWith("'use 6to5'")
-    packageName
-    directory = filePath
-    until packageName
-      directory = path.dirname(directory)
-      manifest = path.join(directory, 'package.json')
-      if fs.existsSync(manifest)
-        json = JSON.parse(fs.readFileSync(manifest))
-        packageName = json.name
-      else if isRoot(directory)
-        break
-
+    # Create a manual deprecation since the stack is too deep to use Grim
+    # which limits the depth to 3
     Grim ?= require 'grim'
-    Grim.deprecate("Use the 'use babel' pragma instead of 'use 6to5' in #{filePath}", {packageName})
+    stack = [
+      {
+        fileName: __filename
+        functionName: 'loadFile'
+        location: "#{__filename}:161:5"
+      }
+      {
+        fileName: filePath
+        functionName: '<unknown>'
+        location: "#{filePath}:1:1"
+      }
+    ]
+    deprecation =
+      message: "Use the 'use babel' pragma instead of 'use 6to5'"
+      stacks: [stack]
+    Grim.addSerializedDeprecation(deprecation)
   else
     return module._compile(sourceCode, filePath)


Don't include <hyperlinks /> node when empty

--- src/blobs.coffee
+++ src/blobs.coffee
@@ -153,11 +153,14 @@

     # Printed inside worksheet
     worksheetRels: (relationships) ->
-        out = "<hyperlinks>"
-        for rel, i in relationships
-            out += """<hyperlink ref="#{rel.cell}" r:id="rId#{i + 1}" />"""
-        out += "</hyperlinks>"
-        return out
+        if relationships.length > 0
+            out = "<hyperlinks>"
+            for rel, i in relationships
+                out += """<hyperlink ref="#{rel.cell}" r:id="rId#{i + 1}" />"""
+            out += "</hyperlinks>"
+            return out
+        else
+            return "";

     # Printed in xl/worksheets/_rels/sheet1.xml.rels
     externalWorksheetRels: (relationships) ->


this bug was noticed with a form with a question:
`"type": "today"`

Why is it different from the other SurveyDetails

--- jsapp/xlform/src/model.surveyFragment.coffee
+++ jsapp/xlform/src/model.surveyFragment.coffee
@@ -6,6 +6,7 @@
 $aliases = require './model.aliases'
 $utils = require './model.utils'
 $configs = require './model.configs'
+$surveyDetail = require './model.surveyDetail'

 module.exports = do ->

@@ -258,6 +259,8 @@
       throw new Error("Invalid type at this stage: #{type}")

     if type in formSettingsTypes
+      # e.g. "today"
+      throw new Error("#{type} is not properly handled as a SurveyDetail")
       $surveyDetail.SurveyDetail
     else if type is 'score'
       $row.Row


Email: templates: fix the koding logo in emails for both Sparrow and GMail web

--- workers/emailnotifications/templates.coffee
+++ workers/emailnotifications/templates.coffee
@@ -61,16 +61,16 @@

-

+

-
+

-
-
+
+

-
+


Fix non function node

Fix non function node

--- src/coppe.coffee
+++ src/coppe.coffee
@@ -14,9 +14,6 @@
   $ = (element, props, textOrBlock) ->
     node = null
     switch typeof textOrBlock
-      when 'string'
-        node = new Node element, props, textOrBlock
-        ctx.addChild node
       when 'function'
         node = new Node element, props, null
         ctx.addChild node
@@ -24,6 +21,9 @@
         ctx = node
         textOrBlock()
         ctx = _tmp # restore
+      else
+        node = new Node element, props, textOrBlock
+        ctx.addChild node
     null

   (block) ->


remove mark of shame

remove mark of shame

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -605,7 +605,6 @@

     getEnabledTaskings: (id) ->
       plan = @_getPlan(id)
-      console.log "Get Enabled", plan
       plan?.tasking_plans

     isStatsLoading: (id) -> @_asyncStatusStats[id] is 'loading'


(fix) Fix PDF loading.

(fix) Fix PDF loading.

--- app/scripts/pages/correction/correction.coffee
+++ app/scripts/pages/correction/correction.coffee
@@ -26,7 +26,7 @@
     @loadSolution = => #load the solution and pdf for the current exercise
       if !@exercise()
         @pages []
-        return
+        return Q.reject()

       getSolution = api.get.nextSolution @exercise().id
         .then (solution) => @solution solution
@@ -36,12 +36,12 @@
     @loadExercise = => #load the exercise and pdf for the current solution
       if !@solution()
         @pages []
-        return
+        return Q.reject()

       getExercise = api.get.exercise @solution().exercise
         .then (exercise) => @exercise exercise

-      Q.all([getExercise, @loadPdf()]).then => @initPages
+      getExercise.then(=> @loadPdf()).then => @initPages()

     @initPages = =>
       result = @solution().result
@@ -118,7 +118,7 @@
       .then (exercise) =>
         @exercise exercise
         @loadSolution()
-      .catch (e) ->
+      .catch (e) ->
         console.error(e)
         alert 'Could not load this solution.'
     else if @params.solutionId


Fix QuadView after changes changing properties

--- bokehjs/src/coffee/models/glyphs/quad.coffee
+++ bokehjs/src/coffee/models/glyphs/quad.coffee
@@ -9,9 +9,9 @@
   _index_data: () ->
     index = rbush()
     pts = []
-    for i in [0...@left.length]
-      if not isNaN(@left[i] + @right[i] + @top[i] + @bottom[i])
-        pts.push([@left[i], @bottom[i], @right[i], @top[i], {'i': i}])
+    for i in [0...@_left.length]
+      if not isNaN(@_left[i] + @_right[i] + @_top[i] + @_bottom[i])
+        pts.push([@_left[i], @_bottom[i], @_right[i], @_top[i], {'i': i}])
     index.load(pts)
     return index


Remove empty Private: comments

Remove empty Private: comments

--- src/select-list-view.coffee
+++ src/select-list-view.coffee
@@ -12,8 +12,6 @@
 # ```
 module.exports =
 class SelectListView extends View
-
-  # Private:
   @content: ->
     @div class: @viewClass(), =>
       @subview 'miniEditor', new EditorView(mini: true)
@@ -23,7 +21,6 @@
         @span class: 'badge', outlet: 'loadingBadge'
       @ol class: 'list-group', outlet: 'list'

-  # Private:
   @viewClass: -> 'select-list'

   maxItems: Infinity
@@ -59,7 +56,6 @@
       @confirmSelection() if $(e.target).closest('li').hasClass('selected')
       e.preventDefault()

-  # Private:
   schedulePopulateList: ->
     clearTimeout(@scheduleTimeout)
     populateCallback = =>
@@ -139,26 +135,22 @@
   # * filteredItemCount: The number of items that pass the fuzzy filter test.
   getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found'

-  # Private:
   selectPreviousItem: ->
     item = @getSelectedItem().prev()
     item = @list.find('li:last') unless item.length
     @selectItem(item)

-  # Private:
   selectNextItem: ->
     item = @getSelectedItem().next()
     item = @list.find('li:first') unless item.length
     @selectItem(item)

-  # Private:
   selectItem: (item) ->
     return unless item.length
     @list.find('.selected').removeClass('selected')
     item.addClass 'selected'
     @scrollToItem(item)

-  # Private:
   scrollToItem: (item) ->
     scrollTop = @list.scrollTop()
     desiredTop = item.position().top + scrollTop
@@ -181,7 +173,6 @@
   getSelectedElement: ->
     @getSelectedItem().data('select-list-element')

-  # Private:
   confirmSelection: ->
     element = @getSelectedElement()
     if element?
@@ -196,22 +187,18 @@
   # * element: The selected model element.
   confirmed: (element) ->

-  # Private:
   attach: ->
     @storeFocusedElement()

-  # Private:
   storeFocusedElement: ->
     @previouslyFocusedElement = $(':focus')

-  # Private:
   restoreFocus: ->
     if @previouslyFocusedElement?.isOnDom()
       @previouslyFocusedElement.focus()
     else
       atom.workspaceView.focus()

-  # Private:
   cancelled: ->
     @miniEditor.getEditor().setText('')
     @miniEditor.updateDisplay()


hub --> client message was listed as API message

--- src/smc-util/message.coffee
+++ src/smc-util/message.coffee
@@ -1189,7 +1189,7 @@
     results : required  # list of {first_name:, last_name:, account_id:} objects.

 # hub --> client
-API message
+message
     event : 'project_users'
     id    : undefined
     users : required   # list of {account_id:?, first_name:?, last_name:?, mode:?, state:?}


add note about api key workaround

add note about api key workaround

--- src/smc-webapp/api-key.cjsx
+++ src/smc-webapp/api-key.cjsx
@@ -151,6 +151,8 @@

+            NOTE: If you do not have a password set, there is a workaround to generate your API key.
+

             See the CoCalc API documentation to learn about the API.


webapp/library2: update sorted docs in two callbacks and weighting categories

--- src/smc-webapp/library.cjsx
+++ src/smc-webapp/library.cjsx
@@ -74,19 +74,30 @@
         @scroll_into_view = _.debounce((-> $(ReactDOM.findDOMNode(@refs.selector_list)).find('.active').scrollintoview()), 50)

     componentDidUpdate: (props, state) ->
-        @scroll_into_view()
-
-    componentWillMount: ->
-        meta = @props.library.getIn(['examples'])?.metadata
-        docs = @props.library.getIn(['examples'])?.documents
+
+    init_state: (props) ->
+        meta = props.library.getIn(['examples'])?.metadata
+        docs = props.library.getIn(['examples'])?.documents

         if docs?
-            sortfn = (doc) -> [meta.categories[doc.category], doc.title?.toLowerCase() ? doc.id]
+            # sort by a triplet
+            sortfn = (doc) -> [
+                meta.category_sortweight[doc.category] ? 0
+                meta.categories[doc.category].toLowerCase()
+                doc.title?.toLowerCase() ? doc.id
+            ]
             sdocs  = _.sortBy(docs, sortfn)
             @setState
                 copy        : false
                 sorted_docs : sdocs
                 metadata    : meta
+
+    componentDidMount: ->
+        @init_state(@props)
+
+    componentWillReceiveProps: (next) ->
+        return if @props.library.get('examples')? and (@props.library.get('examples') == next.library.get('examples'))
+        @init_state(next)

     target_path: ->
         doc = @props.library_selected
@@ -127,6 +138,7 @@
         idx     = ids.indexOf(@props.library_selected.id) + dx
         new_doc = @state.sorted_docs[idx %% @state.sorted_docs.length]
         @props.actions.setState(library_selected: new_doc)
+        @scroll_into_view()

         evt.preventDefault()
         evt.stopPropagation()
@@ -200,7 +212,7 @@

     details: ->
-        return null if (not @props.library_selected?)
+        return null if (not @props.library_selected?) or (not @state.metadata?)
         # example:
         # {"title":"Data science Python notebooks","id":"doc-6","license":"a20",
         # "src":"/ext/library/cocalc-examples/data-science-ipython-notebooks/",


zendesk: ticket listing has better styling and code makes more sense

--- src/smc-webapp/support.cjsx
+++ src/smc-webapp/support.cjsx
@@ -22,7 +22,7 @@
 underscore = _ = require('underscore')
 {React, ReactDOM, Actions, Store, rtypes, rclass, Redux, redux, COLOR}  = require('./smc-react')
 {Col, Row, Button, Input, Well, Alert, Modal} = require('react-bootstrap')
-{Icon, Loading, SearchInput, Space, ImmutablePureRenderMixin, Footer} = require('./r_misc')
+{Icon, Markdown, Loading, SearchInput, Space, ImmutablePureRenderMixin, Footer} = require('./r_misc')
 misc            = require('smc-util/misc')
 misc_page       = require('./misc_page')
 {top_navbar}    = require('./top_navbar')
@@ -202,25 +202,29 @@
             support_ticket_error : rtypes.string

     render_header : ->
-        <Row>
-            <Col md=1>ID</Col>
-            <Col md=1>Status</Col>
-            <Col md=3>Subject</Col>
-            <Col md=6>Description</Col>
-            <Col md=2></Col>
-        </Row>
+        if @props.support_tickets.length > 0
+            <Row style={fontWeight:"bold"}>
+                <Col md=1>ID</Col>
+                <Col md=1>Status</Col>
+                <Col md=3>Subject</Col>
+                <Col md=5>Description</Col>
+                <Col md=2></Col>
+            </Row>
+        else if @props.support_tickets?.length == 0
+            <Row>
+                <Col md=12>You do not have any support tickets.</Col>
+            </Row>

     open : (ticket_id) ->
         window.alert("open #{ticket_id}")

-    render_list : (tickets) ->
-
-        for i, ticket of tickets
+    render_body : ->
+        for i, ticket of @props.support_tickets
             <Row id={i}>
                 <Col md=1>{ticket.id}</Col>
                 <Col md=1>{ticket.status}</Col>
                 <Col md=3>{ticket.subject}</Col>
-                <Col md=6>{ticket.description}</Col>
+                <Col md=5><Markdown value={ticket.description} /></Col>
                 <Col md=2>
                     <Button bsStyle="info" onClick={=> @open(ticket.id)}>
                         Open {ticket.id}
@@ -228,15 +232,26 @@
                 </Col>
             </Row>

+    render_table : ->
+        if not @props.support_tickets?
+            return

+                        <Loading />
+

+
+

+            {@render_header()}
+            {@render_body()}
+

+
     render : ->
-        if not @props.support_tickets?
-            return <Loading />
-

-            <Col style={minHeight:"65vh"} md=12>
-                {@render_header()}
-                {@render_list(@props.support_tickets)}
-            </Col>
+
Support tickets

+

+                Check the status of your support tickets
+                or create a new <exports.ShowSupportLink />.
+

+

+            {@render_table()}


resource honed

resource honed

--- src/steroids/DolanDB.coffee
+++ src/steroids/DolanDB.coffee
@@ -68,7 +68,10 @@

     com = params.shift()

-    readConfig = () ->
+    getConfig = () ->
+      yaml.safeLoad readConfigFromFile()
+
+    readConfigFromFile = () ->
       try return fs.readFileSync(data_definition_path, 'utf8')
       catch e
         console.log "you must first init dolandb with command 'steroids dolandb init'"
@@ -76,7 +79,7 @@

     if com=='provision'

-      config = yaml.safeLoad(readConfig())
+      config = getConfig()

       if config.resourceProviderUid?
         console.log 'dolanddb provider exists already'
@@ -93,7 +96,6 @@
         config = yaml.safeLoad(fs.readFileSync(data_definition_path, 'utf8'))
         config.resourceProviderUid = obj['uid']

-        fs = require('fs')
         fs.writeFile(data_definition_path, yaml.safeDump(config), (err,data) ->
           console.log 'dolandb resource provider created'
         )
@@ -106,7 +108,8 @@
     if com=="resource"
       resource_name = params.shift()

-      config = yaml.safeLoad(fs.readFileSync(data_definition_path, 'utf8'))
+      config = getConfig()
+
       provider = config.resourceProviderUid
       bucket = config.bucket

@@ -134,25 +137,29 @@

     if com=="raml"
       @composer.headers["Accept"] = "text/yaml"
-      url = "/app/#{@getAppId()}/raml?identification_hash=74d6cf00e52215801b6f9968e916c4558da4a79fd4026268b3e5f2cb12e7e90f"
+      identification_hash = '74d6cf00e52215801b6f9968e916c4558da4a79fd4026268b3e5f2cb12e7e90f'
+      url = "/app/#{@getAppId()}/raml?identification_hash=#{identification_hash}"
+
       @composer.get(url, (err, req, res, obj) =>
         raml_file_content = res['body']

         console.log raml_file_content

-        stream = fs.createWriteStream('www/local.raml')
-        stream.once('open', (fd) ->
-          stream.write raml_file_content
-          stream.end()
+        fs.writeFile('www/local.raml', raml_file_content, (err,data) ->
+          console.log 'raml saved'
         )
+
+        #stream = fs.createWriteStream('www/local.raml')
+        #stream.once('open', (fd) ->
+        #  stream.write raml_file_content
+        #  stream.end()
+        #)
       )

     if com=='sync'
       raml = fs.readFileSync('www/local.raml', 'utf8')

-      console.log raml
-
-      doc = yaml.safeLoad(fs.readFileSync(data_definition_path, 'utf8'))
+      config = yaml.safeLoad(fs.readFileSync(data_definition_path, 'utf8'))
       if doc.browser_id?
         # browser instance exists
         DbBrowser.put("ramls/#{doc.browser_id}", {raml:{content:raml} }, (err, res, body) =>
@@ -166,10 +173,9 @@
           application_name: 'myapp'

         DbBrowser.post('ramls', { raml:post_data }, (err, res, body) =>
-          doc.browser_id = body.id
+          config.browser_id = body.id
           open URL.format("#{db_browser_url}/#browser/#{doc.browser_id}")
-          fs.writeFile(data_definition_path, yaml.safeDump(doc), (err,data) =>
-          )
+          fs.writeFileSync(data_definition_path, yaml.safeDump(config))
         )

     if com=='all'
@@ -315,7 +321,6 @@

     doc.resources.push res

-    fs = require('fs')
     fs.writeFile(data_definition_path, yaml.safeDump(doc), (err,data) ->
       console.log 'resource created'
     )
@@ -455,6 +460,7 @@
       deferred.resolve()
     )

+
     return deferred.promise

   openRamlBrowser: () =>


Interventions: listen for experiment messages (#4788)

Turns out notification is used by Talk for new message notifications.

--- app/main.cjsx
+++ app/main.cjsx
@@ -12,7 +12,7 @@
 `import { notify, injectSubjects } from './redux/ducks/interventions';`
 store = configureStore()

-sugarClient.on('notification', (message) => store.dispatch(notify(message)));
+sugarClient.on('experiment', (message) => store.dispatch(notify(message)));
 sugarClient.on('subject-queue', (message) => store.dispatch(injectSubjects(message)));

 # Redirect any old `/#/foo`-style URLs to `/foo`.


added Python support

added Python support

--- src/docco.coffee
+++ src/docco.coffee
@@ -129,6 +129,8 @@
     name: 'javascript', symbol: '//'
   '.rb':
     name: 'ruby', symbol: '#'
+  '.py':
+    name: 'python', symbol: '#'

 # Build out the appropriate matchers and delimiters for each language.
 for ext, l of languages
@@ -142,7 +144,8 @@

   # The mirror of `divider_text` that we expect Pygments to return. We can split
   # on this to recover the original sections.
-  l.divider_html = new RegExp('\\n*' + l.symbol + 'DIVIDER<\\/span>\\n*')
+  # Note: the class is "c" for Python and "c1" for the other languages
+  l.divider_html = new RegExp('\\n*' + l.symbol + 'DIVIDER<\\/span>\\n*')

 # Get the current language we're documenting, based on the extension.
 get_language = (source) -> languages[path.extname(source)]


Insert data back to the editor

Insert data back to the editor

Signed-off-by: Jessica González <52329a27ea5670aeec0efeeb0773bf1b4f1affa1@missallsunday.com>

--- src/summernoteDrafts.coffee
+++ src/summernoteDrafts.coffee
@@ -128,11 +128,16 @@
       $selectedDraft = @$dialog.find '.note-draft'
         .click (e) =>
           e.preventDefault
+          div = document.createElement 'div'
           key = $ this
             .data 'draft'
           data = drafts[key]

-          # if no data shomw some error or something
+          if data
+            div.innerHTML = data
+            context.invoke('editor.insertNode', div)
+
+          # if no data show some error or something
           false

     getDrafts = () ->


Pass config transform

Pass config transform

--- src/test/test_config.coffee
+++ src/test/test_config.coffee
@@ -16,7 +16,7 @@
   test 'config transform', (t) ->
     console.log world.doc.x
     t.equal 0, world.get('i'), 'i'
-    t.ok world.get('transform')
+    t.equal world.get('transform'), "translate(40,40) rotate(0)"
     t.end()

   test 'config children', (t) ->


svg text

svg text

--- src/test/test_render.coffee
+++ src/test/test_render.coffee
@@ -71,7 +71,7 @@

   test "render svg", (t) ->
     svg_dict = {paths: ['M0,0'], name: 'ME'}
-    body = render_mock(svg_dict ).body
+    body = render_mock(svg_dict).body
     t.equal body.tag, 'svg', 'svg tag'
     t.equal body.body.tag, 'g', 'g tag'
     children = body.body.body


Node/edge visualization mostly working.

Node/edge visualization mostly working.

--- src/util/graph_utils.coffee
+++ src/util/graph_utils.coffee
@@ -5,25 +5,25 @@
 utils.arrangeNodes = (tier, depth = 3) ->
     yOffset = 1
     for node in tier
-        newX = (3 - depth) * 150
-        newY = yOffset++ * 600 / tier.length - 600 / tier.length / 2
+        node.newX = (3 - depth) * 150
+        node.newY = yOffset++ * 600 / tier.length - 600 / tier.length / 2
         if depth < 1
             node.x = 900
-            node.y = newY
-        game.tweens.create(node).to({ x: newX, y: newY }).start(500)
+            node.y = node.newY
+        game.tweens.create(node).to({ x: node.newX, y: node.newY }).start(500)
     if depth > 0
         nextTier = []
         for child in tier
             nextTier.push child.getChildren()...
         utils.arrangeNodes nextTier, depth - 1
     # edges are confused between before and after locations of nodes
-    for node in tier
-        for child in node.getChildren()
-            game.tweens.create(child.edge).to({
-                width: dist node.x, node.y, child.x, child.y
-                rotation: angle node.x, node.y, child.x, child.y
-                x: node.x
-                y: node.y
-            }).start()
+        for node in tier
+            for child in node.getChildren()
+                game.tweens.create(child.edge).to({
+                    width: dist node.newX, node.newY, child.newX, child.newY
+                    rotation: angle node.newX, node.newY, child.newX, child.newY
+                    x: node.newX
+                    y: node.newY
+                }).start()

 module.exports = utils


remove make_sprite console.log

remove make_sprite console.log

--- src/layout/sprites.coffee
+++ src/layout/sprites.coffee
@@ -37,7 +37,6 @@
   _EXPORTS: ['make_sprite', 'delete_sprite', 'inspect']
   make_sprite: (world, sprite_dict) ->
     kind_authority = get_kind_authority sprite_dict, world.get('kinds')
-    console.log 'makes', kind_authority, sprite_dict
     dict = $.extend true, {}, kind_authority, sprite_dict
     child = world.add_child dict
     child.put 'dict', dict


Make navi header reset on service browse -> add.

--- src/views/navigation.coffee
+++ src/views/navigation.coffee
@@ -82,7 +82,7 @@
                 else if @openViewType == 'position'
                     @closeContents()
             @listenTo @selectedServices, 'add', (service) ->
-                #@closeContents() unless @openViewType == 'service-units'
+                @navigationHeaderView.updateClasses null
                 @service = service
                 @listenTo @service.get('units'), 'finished', =>
                     @change 'service-units'


fixing issue #497 try/catch/finally all empty.

--- rewriter.coffee
+++ rewriter.coffee
@@ -159,7 +159,8 @@
       if token[0] is 'ELSE' and prev[0] isnt 'OUTDENT'
         @tokens.splice i, 0, @indentation(token)...
         return 2
-      if token[0] is 'CATCH' and @tokens[i + 2][0] is 'TERMINATOR'
+      if token[0] is 'CATCH' and
+          (@tokens[i + 2][0] is 'TERMINATOR' or @tokens[i + 2][0] is 'FINALLY')
         @tokens.splice i + 2, 0, @indentation(token)...
         return 4
       return 1 unless include(SINGLE_LINERS, token[0]) and


liquify objects

liquify objects

--- src/liquid/context.coffee
+++ src/liquid/context.coffee
@@ -160,8 +160,8 @@
     delivered = false
     result = null

-    Liquid.Helpers.unfuture variable, (err, variable) ->
-      result = liquify(variable)
+    Liquid.Helpers.unfuture variable, (err, variable) =>
+      result = @liquify(variable)
       f.deliver(err, result)
       delivered = true

@@ -189,7 +189,7 @@
       return next() if object == null

       unfuture object, (err, unfuturedObject) =>
-        object = liquify(unfuturedObject)
+        object = @liquify(unfuturedObject)

         return next() if object == null

@@ -204,8 +204,8 @@
           # If object is a hash- or array-like object we look for the
           # presence of the key and if its available we return it
           if isArrayAccess or isObjectAccess
-            unfuture @lookupAndEvaluate(object, part), (err, result) ->
-              object = liquify(result)
+            unfuture @lookupAndEvaluate(object, part), (err, result) =>
+              object = @liquify(result)
               next()

           else
@@ -222,13 +222,13 @@
             if isSpecialAccess
               object = switch part
                 when "size"
-                  liquify(object.length)
+                  @liquify(object.length)
                 when "first"
-                  liquify(object[0])
+                  @liquify(object[0])
                 when "last"
-                  liquify(object[object.length-1])
+                  @liquify(object[object.length-1])
                 else
-                  liquify(object)
+                  @liquify(object)

               next()

@@ -277,11 +277,16 @@
           lastScope[key] = @lookupAndEvaluate(env, key)
           true

-  liquify = (object) ->
+  liquify: (object) ->
     return object unless object?

-    if object.toLiquid instanceof Function
-      object.toLiquid()
+    if typeof object.toLiquid == "function"
+      object = object.toLiquid()
     else
       # TODO: implement toLiquid for native types
-      object+      true
+
+    if object instanceof Liquid.Drop
+      object.context = @
+
+    object


Update exports.

Update exports.

--- src/index.coffee
+++ src/index.coffee
@@ -1,4 +1,5 @@
 module.exports =
   App:            require './app'
+  EventEmitter:   require './event-emitter'
+  Route:          require './route'
   View:           require './view'
-  EventEmitter:   require './event-emitter'


payload.save?

payload.save?

--- src/index.coffee
+++ src/index.coffee
@@ -68,7 +68,7 @@
         else if house is null
           payload.num_p = 1
           payload.last_updated = new Date().getTime()
-          new House payload
+          new House(payload)
           .save (err) ->
             if err
               socket.emit "new:house:ack", err


Super simple custom directory listing. Get rid of annoying favicon requests unless a favicon.ico exists.

--- src/index.coffee
+++ src/index.coffee
@@ -1,7 +1,20 @@
 path = require "path"
+fs = require "fs"

 connect = require "connect"
 colors = require "colors"
+
+directoryIndex = (directory, options) ->
+    return (req, res, next) ->
+        fs.stat path.join(directory, req.url), (err, stats) ->
+            if err then throw err
+            if stats.isDirectory()
+                fs.readdir path.join(directory, req.url), (err, files) ->
+                    for file in files
+                        res.write file + "\n"
+                    res.end()
+            else
+                next()

 module.exports = class takeapeek
     constructor: (@options) ->
@@ -16,18 +29,33 @@
         # Create the server
         @server = connect()

+        # Git rid of annoying favicon requests unless /favicon.ico exists
+        @server.use (req, res, next) ->
+            if req.url == "/favicon.ico"
+                fs.exists "favicon.ico", (exists) ->
+                    if exists
+                        next()
+                    else
+                        res.end()
+            else
+                next()
+
         # Setup the logger if we are in verbose mode
         if @options.verbose and not @options.quite
             @server.use connect.logger "dev"

         # Serve directory listings
         if @options.index
-            @server.use connect.directory(@options.directory, { hidden: @options.dotfiles })
+            #@server.use connect.directory(@options.directory, { hidden: @options.dotfiles })
+            @server.use directoryIndex(@options.directory, { hidden: @options.dotfiles })

         # Serve all files as text/plain if content-text
         if @options["content-text"]
             @server.use (req, res, next) ->
-                res.setHeader "Content-Type", "text/plain"
+                try
+                    res.setHeader "Content-Type", "text/plain"
+                catch error
+                    #console.log error
                 next()

         # Serve the files


don't try to use serviceworkers if served over http

--- src/index.coffee
+++ src/index.coffee
@@ -27,7 +27,7 @@
         @parent?.postMessage msg, origin
     })

-    if navigator.serviceWorker
+    if navigator.serviceWorker and window.location.protocol isnt 'http:'
       # only use service workers if current page has one
       @ready = navigator.serviceWorker.ready
       .catch -> null


add the option of an interval with -i or --interval to wait between CQL statements are executed

--- src/index.coffee
+++ src/index.coffee
@@ -8,6 +8,12 @@

 quietMode = false
 debugMode = false
+
+sleep = (ms) ->
+  new Promise((resolve) ->
+    setTimeout resolve, ms
+    return
+  )

 logError = (message, error) ->
   errorMessage = if error? then ": #{error}" else ''
@@ -176,7 +182,7 @@

 # Apply the first migration from the remaining, and move on to the next
-applyMigration = (config, client, keyspace, file, version) ->
+applyMigration = (config, client, keyspace, file, version, interval) ->
   logInfo "Applying migration: #{file}"

   queryStrings = _.trim(FS.readFileSync(file, 'utf-8')).split('---')
@@ -188,16 +194,16 @@
   queryStrings.push cql
   #logDebug "Queries:", queryStrings

-  queries = _(queryStrings)
-  .map (cql) ->
-    -> runQuery config, client, cql, version
-  .value()
-
-  queries.reduce(Q.when, Q(version))
+  queryStrings.reduce ((promiseChain, queryString) ->
+    promiseChain.then ->
+      runQuery(config, client, queryString, version).then ->
+        sleep(interval).then ->
+          version
+  ), Promise.resolve()

 # Run all of the migrations
-migrate = (config, client, keyspace, migrationFiles, schemaVersion) ->
+migrate = (config, client, keyspace, migrationFiles, schemaVersion, interval) ->
   migrations = _(migrationFiles)
   .filter ([file, version]) -> version > schemaVersion and version <= config.targetVersion
   .sortBy ([file, version]) -> version
@@ -214,7 +220,7 @@

     migrationFunctions = _(migrations)
     .map ([file, version]) ->
-      -> applyMigration config, client, keyspace, file, version
+      -> applyMigration config, client, keyspace, file, version, interval
     .value()

     migrationFunctions.reduce(Q.when, Q(schemaVersion))
@@ -238,6 +244,7 @@
     .option '-h, --hosts <hosts>', 'A comma separated list of cassandra hosts', parseCassandraHosts
     .option '-k, --keyspace <keyspace>', 'Cassandra keyspace used for migration and schema_version table'
     .option '-q, --quiet', 'Silence non-error output (default is false)'
+    .option '-i, --interval <interval>', 'Milliseconds to wait between migration queries are executed'
     .option '-t, --target-version <version>', 'Maximum migration version to apply (default runs all migrations)'
     .parse(process.argv)

@@ -256,6 +263,7 @@
     quietMode = config.quiet
     debugMode = config.debug
     keyspace = config.cassandra.keyspace
+    interval = program.interval ? 0

     if config.auth?
       logDebug "Connecting with simple user authentication."
@@ -268,7 +276,7 @@
       cassandraClient = client
       getSchemaVersion config, client, keyspace
       .then (schemaVersion) ->
-        migrate config, client, keyspace, migrationFiles, schemaVersion
+        migrate config, client, keyspace, migrationFiles, schemaVersion, interval
       .then (version) ->
         code = 0
       .catch (error) ->


Classes are no longer exported to the global scope

--- src/range.coffee
+++ src/range.coffee
@@ -1,6 +1,6 @@
 $ = jQuery

-this.Range = {}
+Range = {}

 Range.sniff = (r) ->
   if r.commonAncestorContainer?


on/off events aliases

on/off events aliases

--- src/spine.coffee
+++ src/spine.coffee
@@ -44,6 +44,9 @@
         @_callbacks[name] = list
         break
     this
+
+Events.on = Events.bind
+Events.off = Events.unbind

 Log =
   trace: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -24,4 +24,4 @@
         start: I[property]
         easing: "linear"
         startTime: I.age
-        endTime: I.age +
+        endTime: I.age + duration


broadcast_to_all doesn't exist (broadcast_to already broadcasts to self)

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -411,14 +411,6 @@
           else
             for k, v of args[0]
               socket.broadcast.to(room).emit.apply socket.broadcast, [k, v]
-        broadcast_to_all: (room, args...) ->
-          if typeof args[0] isnt 'object'
-            socket.broadcast.to(room).emit.apply socket.broadcast, args
-            socket.emit.apply socket, args
-          else
-            for k, v of args[0]
-              socket.broadcast.to(room).emit.apply socket.broadcast, [k, v]
-              socket.emit.apply socket, [k, v]

       apply_helpers ctx
       ctx


Fix url parsing (#149)

Fix url parsing (#149)

* Support to show ICS invite as attachments

ICS invites are currently not displayed correctly in Nylas.  This commit adds support to extract calendar files and store them as attachments.  The attachment is called `Event.ics`.

* FIX:  Only scroll vertically

Fixes #135

* Fix the URL parser to only parse URLs

Mike raised a valid issue where the parser was picking up the first word of each paragraph as a match.  This should resolve the issue.

1. Modified it to handle URLs without a scheme. (www.google.com)
2. Added ability to handle `mailto` and `tel`
3. Added support for punycode URLs.

Fixes #147

* Added the comments that were removed.


create now starts and deploys

create now starts and deploys

--- code/li.coffee
+++ code/li.coffee
@@ -52,9 +52,11 @@
   run: (args) ->
     process.stdout.write('Creating...\n')
     Linode.create args[2], (dummy_, item) ->
-      Linode.get item.name, (item) ->
-        process.stdout.write('Created!\n')
-        log_one_item item
+      Linode.get item.name, (instance) ->
+        instance.start ->
+          instance.run_hooks (code) ->
+            process.stdout.write('Created!\n')
+            log_one_item instance

 # args[2..] are the names of the instance to destroy
 command.destroy =


add stop and restart commands to li

add stop and restart commands to li

--- code/li.coffee
+++ code/li.coffee
@@ -17,6 +17,16 @@
   Linode._get args[2], (instance) ->
     instance.start ->
       process.stdout.write('started\n')
+
+stop = (args) ->
+  Linode._get args[2], (instance) ->
+    instance.stop ->
+      process.stdout.write('stopped\n')
+
+restart = (args) ->
+  Linode._get args[2], (instance) ->
+    instance.restart ->
+      process.stdout.write('restarted\n')

 create = (args) ->
   process.stdout.write('Creating...\n')
@@ -43,6 +53,8 @@
   switch args[1]
     when 'help' then help(args)
     when 'start' then start(args)
+    when 'stop' then stop(args)
+    when 'restart' then restart(args)
     when 'create' then create(args)
     when 'destroy' then destroy(args)
     when 'list' then list(args)


Remove redirectToSiteFeed method

Remove redirectToSiteFeed method

--- lib/assets/javascripts/tent_status.js.coffee
+++ lib/assets/javascripts/tent_status.js.coffee
@@ -75,10 +75,6 @@
     @_num_running_requests ?= 1
     @_num_running_requests -= 1
     Marbles.Views.loading_indicator.hide() if @_num_running_requests == 0
-
-  redirectToSiteFeed: =>
-    return unless TentStatus.config.app_domain
-    Marbles.history.navigate('/site-feed', {trigger:true})
 }

 TentStatus.trigger('config:ready') if TentStatus.config_ready


add z as  shorthand of "a z" in operator-pending

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -443,6 +443,7 @@
   'C': 'vim-mode-plus:inner-whole-word' # `y C` for `y i W`
   'p': 'vim-mode-plus:inner-paragraph'  # `y p` for `y i p`
   ',': 'vim-mode-plus:inner-arguments'  # `y ,` for `y i ,`
+  'z': 'vim-mode-plus:a-fold'           # `y z` for `y a z`

   # In this scope, all operator keys are safely remappable.
   # e.g. p, d, D, x, u, X, S, ., r


Tolerate empty strings in grade calculation

--- app/assets/javascripts/reviewEditor.js.coffee
+++ app/assets/javascripts/reviewEditor.js.coffee
@@ -309,8 +309,7 @@

     for grade in grades
       return undefined unless grade?
-
-      gradeSum += parseFloat(grade) unless isNaN(grade)
+      gradeSum += parseFloat(grade) if $.isNumeric(grade)

     return gradeSum


Revert "Merge branch 'console_io' of git://github.com/TrevorBurnham/coffee-script"

This reverts commit 2cb42b502cbb2f6c2457c597d86d6925dd0e2233, reversing
changes made to 64bd4b3f74deb00912c9bb503bf20fe1305b79af.

--- command.coffee
+++ command.coffee
@@ -16,10 +16,13 @@
 # Allow CoffeeScript to emit Node.js events.
 helpers.extend CoffeeScript, new EventEmitter

+printLine = (line) -> process.stdout.write line + '\n'
+printWarn = (line) -> process.binding('stdio').writeError line + '\n'
+
 # The help banner that is printed when `coffee` is called without arguments.
 BANNER = '''
   Usage: coffee [options] path/to/script.coffee
-
+
   If called without options, `coffee` will run your script.
          '''

@@ -127,19 +130,19 @@
     t = task = {file, input, options}
     CoffeeScript.emit 'compile', task
     if      o.tokens      then printTokens CoffeeScript.tokens t.input
-    else if o.nodes       then console.log CoffeeScript.nodes(t.input).toString().trim()
+    else if o.nodes       then printLine CoffeeScript.nodes(t.input).toString().trim()
     else if o.run         then CoffeeScript.run t.input, t.options
     else
       t.output = CoffeeScript.compile t.input, t.options
       CoffeeScript.emit 'success', task
-      if o.print          then console.log t.output.trim()
+      if o.print          then printLine t.output.trim()
       else if o.compile   then writeJs t.file, t.output, base
       else if o.lint      then lint t.file, t.output
   catch err
     CoffeeScript.emit 'failure', err, task
     return if CoffeeScript.listeners('failure').length
-    return console.log err.message if o.watch
-    console.error err instanceof Error and err.stack or "ERROR: #{err}"
+    return printLine err.message if o.watch
+    printWarn err instanceof Error and err.stack or "ERROR: #{err}"
     process.exit 1

 # Attach the appropriate listeners to compile scripts incoming over **stdin**,
@@ -188,7 +191,7 @@
     js = ' ' if js.length <= 0
     fs.writeFile jsPath, js, (err) ->
       if err
-        console.log err.message
+        printLine err.message
       else if opts.compile and opts.watch
         console.log "#{(new Date).toLocaleTimeString()} - compiled #{source}"
   path.exists dir, (exists) ->
@@ -197,7 +200,7 @@
 # Pipe compiled JS through JSLint (requires a working `jsl` command), printing
 # any errors or warnings that arise.
 lint = (file, js) ->
-  printIt = (buffer) -> console.log file + ':\t' + buffer.toString().trim()
+  printIt = (buffer) -> printLine file + ':\t' + buffer.toString().trim()
   conf = __dirname + '/../extras/jsl.conf'
   jsl = spawn 'jsl', ['-nologo', '-stdin', '-conf', conf]
   jsl.stdout.on 'data', printIt
@@ -210,7 +213,7 @@
   strings = for token in tokens
     [tag, value] = [token[0], token[1].toString().replace(/\n/, '\\n')]
     "[#{tag} #{value}]"
-  console.log strings.join(' ')
+  printLine strings.join(' ')

 # Use the [OptionParser module](optparse.html) to extract all options from
 # `process.argv` that are specified in `SWITCHES`.
@@ -239,8 +242,8 @@
 # Print the `--help` usage message and exit. Deprecated switches are not
 # shown.
 usage = ->
-  console.log (new optparse.OptionParser SWITCHES, BANNER).help()
+  printLine (new optparse.OptionParser SWITCHES, BANNER).help()

 # Print the `--version` message and exit.
 version = ->
-  console.log "CoffeeScript version #{CoffeeScript.VERSION}"
+  printLine "CoffeeScript version #{CoffeeScript.VERSION}"


#2490. A bit more of the style.

#2490. A bit more of the style.

--- command.coffee
+++ command.coffee
@@ -119,27 +119,31 @@
   try
     t = task = {file, input, options}
     CoffeeScript.emit 'compile', task
-    if      o.tokens      then printTokens CoffeeScript.tokens t.input, t.options
-    else if o.nodes       then printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
-    else if o.run         then CoffeeScript.run t.input, t.options
-    else if o.join and t.file isnt o.join
-      t.input = helpers.invertLiterate t.input if helpers.isLiterate file
-      sourceCode[sources.indexOf(t.file)] = t.input
-      compileJoin()
-    else
-      compiled = CoffeeScript.compile t.input, t.options
-      t.output = compiled
-      if o.map
-        t.output = compiled.js
-        t.sourceMap = compiled.v3SourceMap
-
-      CoffeeScript.emit 'success', task
-      if o.print
-        printLine t.output.trim()
-      else if o.compile || o.map
-        writeJs base, t.file, t.output, options.jsPath, t.sourceMap
-      else if o.lint
-        lint t.file, t.output
+    if
+      o.tokens
+        printTokens CoffeeScript.tokens t.input, t.options
+      o.nodes
+        printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
+      o.run
+        CoffeeScript.run t.input, t.options
+      o.join and t.file isnt o.join
+        t.input = helpers.invertLiterate t.input if helpers.isLiterate file
+        sourceCode[sources.indexOf(t.file)] = t.input
+        compileJoin()
+      else
+        compiled = CoffeeScript.compile t.input, t.options
+        t.output = compiled
+        if o.map
+          t.output = compiled.js
+          t.sourceMap = compiled.v3SourceMap
+        CoffeeScript.emit 'success', task
+        if
+          o.print
+            printLine t.output.trim()
+          o.compile or o.map
+            writeJs base, t.file, t.output, options.jsPath, t.sourceMap
+          o.lint
+            lint t.file, t.output
   catch err
     CoffeeScript.emit 'failure', err, task
     return if CoffeeScript.listeners('failure').length


insertをPromise化

insertをPromise化

--- public/coffee/gdrive.coffee
+++ public/coffee/gdrive.coffee
@@ -52,11 +52,10 @@
       callback(data)
     )

-  insert: (title, content, callback) ->
+  insert: (title, content) ->
     multipartRequestBody = this.requestBody(title, content)
-    # memo: gapi.client.requestはpromiseも使えるらしい
-    # https://developers.google.com/api-client-library/javascript/features/promises
-    request = gapi.client.request({
+    # Promise互換を返す
+    return gapi.client.request({
       'path': '/upload/drive/v2/files',
       'method': 'POST',
       'params': {'uploadType': 'multipart'},
@@ -65,10 +64,6 @@
         this.BOUNDARY + '"'
       },
       'body': multipartRequestBody})
-    if (!callback)
-      callback = (file) ->
-        console.log(file)
-    request.execute(callback)

   update: (fileId, title, content, callback) ->
     console.log('update file')
@@ -151,28 +146,26 @@
       callback(file, caller)
     )

-  upload: (title, content, callback, caller) ->
+  upload: (title, content) ->
     this.set({title: title, content: content})

     if this.get("file")
-      this.update(title, content, callback, caller)
+      this.update(title, content)
+      Promise.resolve('update')
     else
-      this.insert(title, content, callback, caller)
-
-  insert: (title, content, callback, caller) ->
-    that = this
-    # memo: gapi.client.loadはGDriveオブジェクトの方に移したい
-    # さらにclient.loadはcallback無しのときにpromiseが返るらしい
-    # https://developers.google.com/api-client-library/javascript/reference/referencedocs
-    gapi.client.load('drive', 'v2', ->
-      that.gdrive.insert(title, content, (file)->
+      this.insert(title, content)
+      Promise.resolve('insert')
+
+  insert: (title, content) ->
+    that = this
+    gapi.client.load('drive', 'v2')
+      .then () -> that.gdrive.insert(title, content)
+      .then (response) ->
         console.log("insert file")
-        console.log(file)
-        that.set("file", file)
-
-        callback(file, "insert", caller)
-        )
-    )
+        console.log(response.result)
+        that.set("file", response.result)
+
+        Promise.resolve(response.result)

   update: (title, content, callback, caller) ->
     that = this
@@ -196,11 +189,9 @@
     this.alertView = options.alertView # modelなどの特別以外は明示的に受け取る必要がある
     this.model_handler = options.modelHandler

-    this.listenTo(this.model, 'change', this.updateDocumentLink)
+    this.listenTo(this.model, 'change:file', this.updateDocumentLink)

   gapi_authorize: ()->
-    # memo: コールバックで戻ってきたときのauthorizeAlert()で自身のthisを使いたいので渡しておく
-    # このあたりはPromiseを使えばこんなややこしいことしなくても済みそう
     this.model.authorize()
       .then () =>
         this.alertView.show("success", "Success GoogleDrive authorization")
@@ -210,7 +201,12 @@
   upload: ()->
     title = this.model_handler.getEditorTitle()
     content = this.model_handler.getEditorContent()
-    this.model.upload(title, content, this.uploadFileAlert, this)
+
+    this.model.upload(title, content)
+      .then (methodName) =>
+        this.alertView.show("info", "Success " + methodName + "!")
+      .catch (methodName) =>
+        this.alertView.show("danger", "Fail " + methodName + "!")

   updateDocumentLink: ()->
     console.log("update_document_link")
@@ -218,16 +214,8 @@
     if file
       documentLink = $('#document-link')
       documentLink.text(file.title)
-      # TODO: file.alternateLinkで同じアドレス取れそう
-      documentLink.attr("href", "https://drive.google.com/file/d/" \
-       + file.id + "/view")
+      documentLink.attr("href", file.alternateLink)
       documentLink.removeClass("text-muted").addClass("text-primary")
-
-  uploadFileAlert: (file, methodName, caller)->
-    if !file.error
-      caller.alertView.show("info", "Success " + methodName + "!")
-    else
-      caller.alertView.show("danger", "Fail " + methodName + "!")

   fetchFileAlert: (file, caller)->
     if !file.error


tests almost complete (2 missing - need clearance

--- _src/test/mocha_test.coffee
+++ _src/test/mocha_test.coffee
@@ -43,7 +43,7 @@
 		it "set a key", (done) ->
 			localCache.set state.key, state.value, 0, (err, res) ->
 				state.n++
-				should(err).be.null()
+				should.not.exist err
 				# check stats (number of keys should be one more than before)
 				1.should.equal localCache.getStats().keys - state.start.keys
 				done()
@@ -53,7 +53,7 @@
 		it "get a key", (done) ->
 			localCache.get state.key, (err, res) ->
 				state.n++
-				should(err).be.null()
+				should.not.exist err
 				state.value.should.eql res
 				done()
 			return
@@ -61,7 +61,7 @@
 		it "get key names", (done) ->
 			localCache.keys (err, res) ->
 				state.n++
-				should(err).be.null()
+				should.not.exist err
 				[state.key].should.eql res
 				done()
 				return
@@ -70,7 +70,7 @@
 		it "try to get an undefined key", (done) ->
 			localCache.get "yxz", (err, res) ->
 				state.n++
-				should(err).be.null()
+				should.not.exist err
 				should(res).be.undefined()
 				done()
 				return
@@ -125,10 +125,10 @@
 			return

 		it "try to delete an undefined key", (done) ->
-			localCache.del "xxx", (err, res) ->
-				state.n++
-				should(err).be.null()
-				0.should.eql res
+			localCache.del "xxx", (err, count) ->
+				state.n++
+				should.not.exist err
+				0.should.eql count
 				done()
 				return
 			return
@@ -136,13 +136,13 @@
 		it "update key (and get it to check if the update worked)", (done) ->
 			localCache.set state.key, state.value2, 0, (err, res) ->
 				state.n++
-				should(err).be.null()
+				should.not.exist err
 				should(res).be.ok()

 				# check if update worked
 				localCache.get state.key, (err, res) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					state.value2.should.eql res

 					# check if stats didn't change
@@ -160,10 +160,10 @@
 				return

 			# delete the key
-			localCache.del state.key, (err, res) ->
-				state.n++
-				should(err).be.null()
-				1.should.eql res
+			localCache.del state.key, (err, count) ->
+				state.n++
+				should.not.exist err
+				1.should.eql count

 				# check key numbers
 				0.should.eql localCache.getStats().keys - state.start.keys
@@ -171,7 +171,7 @@
 				# check if key was deleted
 				localCache.get state.key, (err, res) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					should(res).be.undefined()
 					done()
 				return
@@ -180,7 +180,7 @@
 		it "set a key to 0", (done) ->
 			localCache.set "zero", 0, 0, (err, res) ->
 				state.n++
-				should(err).be.null()
+				should.not.exist err
 				should(res).be.ok()
 				done()
 				return
@@ -189,7 +189,7 @@
 		it "get previously set key", (done) ->
 			localCache.get "zero", (err, res) ->
 				state.n++
-				should(err).be.null()
+				should.not.exist err
 				0.should.eql res
 				done()
 				return
@@ -257,8 +257,8 @@
 			return

 		it "delete an undefined key", () ->
-			res = localCache.del "xxx"
-			0.should.eql res
+			count = localCache.del "xxx"
+			0.should.eql count
 			return

 		it "update key (and get it to check if the update worked)", () ->
@@ -278,8 +278,8 @@
 				state.key.should.eql key
 				state.value2.should.eql val
 				return
-			res = localCache.del state.key
-			1.should.eql res
+			count = localCache.del state.key
+			1.should.eql count

 			# check stats
 			0.should.eql localCache.getStats().keys - state.start.keys
@@ -298,8 +298,8 @@
 				state.value3.should.eql res
 				return
 			# delete 2 of those keys
-			res = localCache.del keys[0...2]
-			2.should.eql res
+			count = localCache.del keys[0...2]
+			2.should.eql count
 			# try to get the deleted keys
 			keys[0...2].forEach (key) ->
 				res = localCache.get key
@@ -309,14 +309,14 @@
 			res = localCache.get keys[2]
 			state.value3.should.eql res
 			# delete this key, too
-			res = localCache.del keys[2]
-			1.should.eql res
+			count = localCache.del keys[2]
+			1.should.eql count
 			# try get the deleted key
 			res = localCache.get keys[2]
 			should(res).be.undefined()
 			# re-deleting the keys should not have to delete an actual key
-			res = localCache.del keys
-			0.should.eql res
+			count = localCache.del keys
+			0.should.eql count
 			return

 		it "set a key to 0", () ->
@@ -366,7 +366,7 @@
 			state.keys.forEach (key) ->
 				localCache.set key, state.val, (err, res) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					return
 				return

@@ -453,7 +453,7 @@
 			for i in [0...state.count]
 				localCache.del state.keys[i], (err, count) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					1.should.eql count
 					return

@@ -464,7 +464,7 @@
 			for i in [0...state.count]
 				localCache.del state.keys[i], (err, count) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					0.should.eql count
 					return

@@ -493,7 +493,7 @@

 				localCache.set key, value, 0, (err, success) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					should(success).be.ok()
 					return
 			return
@@ -502,15 +502,15 @@
 			for i in [1..state.count]
 				localCache.get state.keys[i], (err, res) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					state.values[i].should.eql res
 					return

 			for i in [1..state.count]
-				localCache.del state.keys[i], (err, success) ->
-					state.n++
-					should(err).be.null()
-					success.should.be.ok()
+				localCache.del state.keys[i], (err, count) ->
+					state.n++
+					should.not.exist err
+					1.should.eql count
 					return

 			after = localCache.getStats()
@@ -527,7 +527,7 @@
 				# 4 char key should not exist
 				localCache.get "xxxx", (err, res) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					should(res).be.undefined()
 					return

@@ -535,7 +535,9 @@
 			diff = diffKeys after, state.start

 			diff.misses.should.eql 5
-
+			return
+
+		it "check successful runs", () ->
 			state.n.should.eql 5 * state.count
 			return
 		return
@@ -557,16 +559,16 @@
 			for key in state.keys
 				localCache.set key, state.value, 0, (err, res) ->
 					state.n++
-					should(err).be.null()
+					should.not.exist err
 					return
 			return

 		it "generate a sub-list of keys", () ->
 			state.getKeys = state.keys.splice 50, 5
+			return
+
+		it "generate prediction", () ->
 			state.prediction = {}
-			return
-
-		it "generate prediction", () ->
 			for key in state.getKeys
 				state.prediction[key] = state.value
 			return
@@ -574,13 +576,253 @@
 		it "try to mget with a single key", () ->
 			localCache.mget state.getKeys[0], (err, res) ->
 				state.n++
-				should(err).exist()
+				should.exist err
 				"Error".should.eql err.constructor.name
 				"EKEYSTYPE".should.eql err.name
 				should(res).be.undefined()
 				return
 			return

+		it "mget the sub-list", () ->
+			localCache.mget state.getKeys, (err, res) ->
+				state.n++
+				should.not.exist err
+				state.prediction.should.eql res
+				return
+			return
+
+		it "delete keys in the sub-list", () ->
+			localCache.del state.getKeys, (err, count) ->
+				state.n++
+				should.not.exist err
+				state.getKeys.length.should.eql count
+				return
+			return
+
+		it "try to mget the sub-list again", () ->
+			localCache.mget state.getKeys, (err, res) ->
+				state.n++
+				should.not.exist err
+				{}.should.eql res
+				return
+			return
+
+		it "check successful runs", () ->
+			state.n.should.eql state.count + 4
+			return
 		return

+
+	describe.only "ttl", () ->
+		before () ->
+			state =
+				n: 0
+				val: randomString 20
+				key1: "k1_#{randomString 20}"
+				key2: "k2_#{randomString 20}"
+				key3: "k3_#{randomString 20}"
+				key4: "k4_#{randomString 20}"
+				key5: "k5_#{randomString 20}"
+				now: Date.now()
+			state.keys = [state.key1, state.key2, state.key3, state.key4, state.key5]
+			return
+
+		it "set a key with ttl", () ->
+			localCache.set state.key1, state.val, 0.5, (err, res) ->
+				should.not.exist err
+				should(res).be.ok()
+
+				ts = localCache.getTtl state.key1
+				if state.now < ts < state.now + 300
+					throw new Error "Invalid timestamp"
+				return
+			return
+
+		it "check this key immediately", () ->
+			localCache.get state.key1, (err, res) ->
+				should.not.exist err
+				state.val.should.eql res
+				return
+			return
+
+		it "before it times out", (done) ->
+			setTimeout(() ->
+				state.n++
+				localCache.get state.key1, (err, res) ->
+					should.not.exist err
+					state.val.should.eql res
+					done()
+					return
+			, 400)
+			return
+
+		it "and after it timed out", (done) ->
+			setTimeout(() ->
+				ts = localCache.getTtl state.key1
+				should.not.exist ts
+
+				state.n++
+				localCache.get state.key1, (err, res) ->
+					should.not.exist err
+					should(res).be.undefined()
+					done()
+					return
+				return
+			, 200)
+			return
+
+		it "set another key with ttl", () ->
+			localCache.set state.key2, state.val, 0.3, (err, res) ->
+				should.not.exist err
+				should(res).be.ok()
+				return
+			return
+
+		it "check this key immediately", () ->
+			localCache.get state.key2, (err, res) ->
+				should.not.exist err
+				state.val.should.eql res
+				return
+			return
+
+		it "before it times out", (done) ->
+			setTimeout(() ->
+				state.n++
+
+				localCache.get state.key2, (err, ts) ->
+					if state.now < ts < state.now + 300
+						throw new Error "Invalid timestamp"
+					return
+
+				localCache.get state.key2, (err, res) ->
+					should.not.exist err
+					state.val.should.eql res
+					done()
+					return
+				return
+			, 250)
+			return
+
+		it "and after it timed out, too", (done) ->
+			setTimeout(() ->
+				ts = localCache.getTtl state.key2
+				should.not.exist ts
+
+				state.n++
+				localCache.get state.key2, (err, res) ->
+					should.not.exist err
+					should(res).be.undefined()
+					done()
+					return
+				return
+			, 100)
+			return
+
+		describe "test the automatic check", (done) ->
+			innerState = null
+
+			before (done) ->
+				setTimeout(() ->
+					innerState =
+						startKeys: localCache.getStats().keys
+						key: "autotest"
+						val: randomString 20
+
+					done()
+					return
+				, 1000)
+				return
+
+			it "set a key with ttl", (done) ->
+				localCache.once "set", (key) ->
+					innerState.key.should.eql key
+					return
+
+				localCache.set innerState.key, innerState.val, 0.5, (err, res) ->
+					should.not.exist err
+					should(res).be.ok()
+					(innerState.startKeys + 1).should.eql localCache.getStats().keys
+					# event handler should have been fired
+					0.should.eql localCache.listenerCount "set"
+					done()
+					return
+				return
+
+			it "and check it's existence", () ->
+				localCache.get innerState.key, (err, res) ->
+					should.not.exist err
+					innerState.val.should.eql res
+					return
+				return
+
+			it "wait for 'expired' event", (done) ->
+				localCache.once "expired", (key, val) ->
+					innerState.key.should.eql key
+					(key not in state.keys).should.eql true
+					should(localCache.data[key]).be.undefined()
+					done()
+					return
+
+				setTimeout(() ->
+					# trigger ttl check, which will trigger the `expired` event
+					localCache._checkData false
+					return
+				, 550)
+				return
+			return
+
+		describe "more ttl tests", () ->
+
+			it "set a third key with ttl", () ->
+				localCache.set state.key3, state.val, 100, (err, res) ->
+					should.not.exist null
+					should(res).be.ok()
+					return
+				return
+
+			it "check it immediately", () ->
+				localCache.get state.key3, (err, res) ->
+					should.not.exist err
+					state.val.should.eql res
+					return
+				return
+
+			it "set ttl to the invalid key", () ->
+				localCache.ttl "#{state.key3}false", 0.3, (err, wasSet) ->
+					should.not.exist err
+					false.should.eql wasSet
+					return
+				return
+
+			it "set ttl to the correct key", () ->
+				localCache.ttl state.key3, 0.3, (err, wasSet) ->
+					should.not.exist err
+					true.should.eql wasSet
+					return
+				return
+
+			it "check if the key still exists", () ->
+				localCache.get state.key3, (err, res) ->
+					should.not.exist err
+					state.val.should.eql res
+					return
+				return
+
+			it "wait until ttl has ended and check if the key was deleted", (done) ->
+				setTimeout(() ->
+					res = localCache.get state.key3
+					should(res).be.undefined()
+					should(localCache.data[state.key3]).be.undefined()
+					done()
+					return
+				, 500)
+				return
+
+			it "set key4 with ttl = 0"
+			it "set key5 with default ttl"
+
+			return
+
+		return
+
 	return


Nicer way of handling async.parallel's result array.

--- lib/formatselection.coffee
+++ lib/formatselection.coffee
@@ -48,12 +48,12 @@
       (next) ->  # parse each of haystack Content-Types
         async.map haystack, parse, next

-  ], (err, results) ->
+  ], (err, [needle, haystack]) ->
     return cb err if err

     # both needle and haystack are now transformed
     # from strings to parsed objects
-    findCandidates results[0], results[1], cb
+    findCandidates needle, haystack, cb

 module.exports = {


:lipstick: Make code slightly simpler by using the coffeescript question-mark operator and Editor.row.

--- lib/go-to-line-view.coffee
+++ lib/go-to-line-view.coffee
@@ -21,7 +21,7 @@
     @on 'core:cancel', => @detach()

     @miniEditor.getModel().on 'will-insert-text', ({cancel, text}) =>
-      cancel() unless text.match(/[0-9]|\:/)
+      cancel() unless text.match(/[0-9]|:/)

   toggle: ->
     if @hasParent()
@@ -49,9 +49,9 @@

     return unless editorView? and lineNumber.length

-    currentLineNum = editorView.getModel().getCursorBufferPosition().toArray()[0]
+    currentLineNum = editorView.getModel().getCursorBufferPosition().row
     lineAndCol = lineNumber.split(':')
-    if lineAndCol[0] and lineAndCol[0].length > 0
+    if lineAndCol[0]?.length > 0
       # Line number was specified
       lineNum = parseInt(lineAndCol[0]) - 1
     else
@@ -59,7 +59,7 @@
       # as where the cursor currently is (no change)
       lineNum = currentLineNum

-    if lineAndCol[1] and lineAndCol[1].length > 0
+    if lineAndCol[1]?.length > 0
       # Column number was specified
       colNum = parseInt(lineAndCol[1]) - 1
     else


use single socket (fixed single socket to properly cache sockets)

--- lib/models/terminal.coffee
+++ lib/models/terminal.coffee
@@ -3,7 +3,6 @@
 utf8      = require 'utf8'
 {EventEmitter} = require 'events'
 SingleSocket = require 'single-socket'
-Websocket = require('websocket').w3cwebsocket

 module.exports =
 class Terminal extends EventEmitter
@@ -17,44 +16,23 @@
     @setListeners()

   connect: () ->
-    @socket = new Websocket @ws_url
-
-    @socket.onopen = () ->
-      console.log('opened socket for terminal')
-
-    @socket.onmessage = (msg) =>
-      console.log('websocket encoded: ', {msg: msg.data})
-      decoded = utf8.decode(window.atob(msg.data))
-      console.log('websocket decoded: ', {decoded: decoded})
-      @emit 'terminal-message-received', msg.data
-
-    @socket.onclose = () ->
-      console.log('connection closed')
-
-    @socket.onerror = (e) ->
-      console.log('error on terminal connection')
-      console.error(e)
-
-
-    @singleSocket = new SingleSocket @ws_url,
+    @socket = new SingleSocket @ws_url,
       onopen: () ->
-        console.log('opened socket for single socket')
+        console.log('opened socket for terminal')
       onmessage: (msg) =>
-        console.log('singlesocket encoded: ', {msg: msg})
+        console.log('websocket encoded: ', {msg: msg})
         decoded = utf8.decode(window.atob(msg))
-        console.log('singlesocket decoded: ', {decoded: decoded})
-        # @emit 'terminal-message-received', msg
+        console.log('websocket decoded: ', {decoded: decoded})
+        @emit 'terminal-message-received', msg
       onclose: () ->
         console.log('connection closed')
       onerror: (e) ->
         console.log('error on terminal connection')
         console.error(e)

-
   send: (data) ->
     console.log('sending:::', data)
     @socket.send(data)
-    @singleSocket.send(data)

   # updateConnectionState: (state) ->
     # if state == 'closed'


closes #13

closes #13

--- lib/project-manager.coffee
+++ lib/project-manager.coffee
@@ -15,7 +15,10 @@

   getProject: =>
     @CSON ?= require 'season'
-    @projects = @CSON.readFileSync(@file()) || {}
+    try
+      @projects = @CSON.readFileSync(@file()) || {}
+    catch
+      return false
     projectPaths = atom.project.getPaths()
     found = false
     for title, project of @projects


input rpc

input rpc

--- lib/runtime/console.coffee
+++ lib/runtime/console.coffee
@@ -28,6 +28,8 @@
         views.ink.tree.toggle view
       @c.result view,
         error: result.type == 'error'
+
+    client.handle 'input', => @input()

     process.onStdout (s) => @c.stdout s
     process.onStderr (s) => @c.stderr s
@@ -72,3 +74,9 @@
     {name: 'help', prefix: '?', icon: 'question', grammar: 'source.julia'}
     {name: 'shell', prefix: ';', icon: 'terminal', grammar: 'source.shell'}
   ]
+
+  input: ->
+    new Promise (resolve) =>
+      @c.output type: 'input', icon: 'file-text', eval: ->
+        resolve @editor.getText()
+      @c.emitter.emit 'focus-input'


Fix wrong poi route

Fix wrong poi route

--- storage/lib/poi/rest.coffee
+++ storage/lib/poi/rest.coffee
@@ -60,7 +60,7 @@
         res.set 'Content-Type', 'text/csv'
         handleCsv(stream).pipe res

-    app.get '/poi.xml', async (req, res, next) ->
+    app.get '/poi.gpx', async (req, res, next) ->
         stream = yield geocache.getStream req.query, true
         res.set 'Content-Type', 'application/gpx+xml'
         handleXml(stream).pipe res


More tweaks to votes import.

More tweaks to votes import.

--- jobs/import-votes.coffee
+++ jobs/import-votes.coffee
@@ -17,9 +17,7 @@
 ifSuccessful = helpers.ifSuccessful

 soap = require 'soap'
-MongoClient = require('mongodb').MongoClient
-mongoUrl = "mongodb://127.0.0.1:27017/galegis-api-dev"
-
+await = require 'await'
 votesSvcUri = "./wsdl/Votes.svc.xml"

 mapVoteMemberIds = (session, votesCast, db, callback) ->
@@ -32,7 +30,7 @@
     callback(votesCast)

 persistVote = (session, vote, db, callback) ->
-  assemblyIdForVote = vote.VoteId
+  assemblyIdForVote = Number(vote.VoteId)

   votesCast =
     yea: [],
@@ -43,7 +41,7 @@

   vote.Votes?.MemberVote.forEach (memberVote) ->
     voteName = memberVote.MemberVoted.toLowerCase()
-    votesCast[voteName].push(memberVote.Member.Id)
+    votesCast[voteName].push(Number(memberVote.Member.Id))

   # Sometimes the date in the data that comes downwire is a string and other times
   # it is a proper date. Let's get things into a uniform format in our DB.
@@ -58,6 +56,7 @@
       description: vote.Description,
       chamber: vote.Branch.toLowerCase(),
       votes: votes
+

     db.collection("votes").update
       assemblyId: assemblyIdForVote


remove console.log

remove console.log

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -540,7 +540,6 @@
       .contest "contest-multiply", [
         multiply.bind @, 2
       ], (c, results) ->
-        console.log 'score method called'
         paths = results.map (r) -> r.path
         idx = paths.indexOf 'root-tripled-then'
         results[idx]


intros in reading content were not rendering since related_content does not exist

--- tutor/src/components/task-step/step-with-reading-content.cjsx
+++ tutor/src/components/task-step/step-with-reading-content.cjsx
@@ -40,16 +40,34 @@

   shouldOpenNewTab: -> true
+
+  getContentChapterSection: ->
+    {chapter_section, related_content} = TaskStepStore.get(@props.id)
+    (related_content && related_content[0]) || chapter_section || []
+
+  getContentChapter: ->
+    @getContentChapterSection()?[0]
+
+  getContentSection: ->
+    @getContentChapterSection()?[1]
+
+  getContentTitle: ->
+    {related_content} = TaskStepStore.get(@props.id)
+    related_content && related_content[0] && related_content[0].title # || look up by @getContentChapterSection()
+
   render: ->
     {id, stepType} = @props

-    {content_html, related_content} = TaskStepStore.get(id)
+    {content_html} = TaskStepStore.get(id)
     {courseId} = Router.currentParams()

-        <RelatedContent contentId={id} {...related_content?[0]} />
+        <RelatedContent contentId={id}
+          chapter_section={@getContentChapterSection()}
+          title={@getContentTitle()}
+        />
         <ArbitraryHtmlAndMath
           className='book-content'
           shouldExcludeFrame={@shouldExcludeFrame}
@@ -59,9 +77,9 @@

       <AnnotationWidget
         courseId={courseId}
-        chapter={related_content[0].chapter_section[0]}
-        section={related_content[0].chapter_section[1]}
-        title={related_content[0].title}
+        chapter={@getContentChapter()}
+        section={@getContentSection()}
+        title={@getContentTitle()}
         documentId={@getCnxId()}
         pageType={stepType}
       />


remove canvas gradient

remove canvas gradient

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -232,12 +232,12 @@

     @drawHighlights()

-    # draw y-axis gradient
-    gradient = @ctx.createLinearGradient(0,0,60,0);
-    gradient.addColorStop(0,'rgba(0,0,0,1.0)');
-    gradient.addColorStop(1,'rgba(0,0,0,0.0');
-    @ctx.fillStyle = gradient
-    @ctx.fillRect(0,0,60,@canvas.height)
+    # # draw y-axis gradient
+    # gradient = @ctx.createLinearGradient(0,0,60,0);
+    # gradient.addColorStop(0,'rgba(0,0,0,1.0)');
+    # gradient.addColorStop(1,'rgba(0,0,0,0.0');
+    # @ctx.fillStyle = gradient
+    # @ctx.fillRect(0,0,60,@canvas.height)

     if $('#graph-container').hasClass('showing-prev-data')
       @showPrevMarks()


Add Geordi integration and basic interventions support.

--- app/lib/intervention.coffee
+++ app/lib/intervention.coffee
@@ -56,7 +56,7 @@
               experiment: intervention.experiment_name
               cohort: intervention.cohort_id
               userID: intervention.user_id
-              type: "delivered"
+              type: "interventionDelivered"
               subjectID: intervention.preconfigured_id # message id
               relatedID: intervention.id # intervention id
             }
@@ -70,7 +70,7 @@
               experiment: intervention.experiment_name
               cohort: intervention.cohort_id
               userID: intervention.user_id
-              type: "dismissed"
+              type: "interventionDismissed"
               subjectID: intervention.preconfigured_id # message id
               relatedID: intervention.id # intervention id
             }


Client: Activity: Deleted unnecessary lines

--- client/activity/lib/components/channelparticipantsdropdown/index.coffee
+++ client/activity/lib/components/channelparticipantsdropdown/index.coffee
@@ -5,7 +5,6 @@
 ActivityFlux                    = require 'activity/flux'
 Dropup                          = require 'activity/components/dropup'
 DropupWrapperMixin              = require 'activity/components/dropup/dropupwrappermixin'
-ImmutableRenderMixin            = require 'react-immutable-render-mixin'
 ChannelParticipantsDropdownItem = require 'activity/components/channelparticipantsdropdownitem'

@@ -18,7 +17,6 @@
     visible        : no
     selectedIndex  : 0
     selectedItem   : null
-    keyboardScroll : no

   componentDidUpdate: ->


Remove warning message

Remove warning message

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -130,8 +130,6 @@
               .pluck("Both_Sexes")
               .value())
         catch e
-          console.log("Warning: Failed to lookup population for " +
-            r + ", " + d + ", " + w)
           return -1

       result.lookup = lookup


click a link close the dropdown

click a link close the dropdown

--- vendor/assets/javascripts/rolodex_angular/src/dropdown.coffee
+++ vendor/assets/javascripts/rolodex_angular/src/dropdown.coffee
@@ -19,7 +19,7 @@
       dropdownScope.forceClose

         if dropdownScope.forceClose
-          openScope.isOpen = false
+          openScope.isOpen = false if openScope

         openScope = null
         $document.unbind 'click', closeDropdown
@@ -27,7 +27,6 @@

     closeDropdown = (evt) ->
       toggleElement = openScope.getToggleElement()
-
       if evt and toggleElement?[0].contains(evt.target) or
       evt?.target.nodeName.toLowerCase() is 'input' # Don't close if the drop down has an input interaction
         return
@@ -80,13 +79,23 @@
       return

     scope.$watch 'isOpen', (isOpen, wasOpen) ->
+      anchors = self.$element.find('a')
+
       if isOpen
         self.$element.attr(dropDownOpen, '')
         scope.focusToggleElement()
         dropdownService.open scope
+
+        if anchors.length
+          scope.forceClose = true
+          _.each anchors, (anchor) ->
+            angular.element(anchor).on 'click', (evt) ->
+              dropdownService.close(scope)
+
       else
         self.$element.removeAttr(dropDownOpen)
-        dropdownService.close scope
+        dropdownService.close(scope)
+
       setIsOpen $scope, isOpen
       if angular.isDefined(isOpen) and isOpen isnt wasOpen
         toggleInvoker $scope,


add a 'onSetContent' callback for behavors

--- vendor/assets/javascripts/nifty/dialog.coffee
+++ vendor/assets/javascripts/nifty/dialog.coffee
@@ -33,7 +33,6 @@
   #                 Behaviors can be setup using the Nifty.Dialog.addBehavior method.
   #
   open: (options={})->
-
     # set a dialog ID for this dialog
     dialogsOpen = $('div.niftyDialog').length
     dialogID = if dialogsOpen == 0 then this.startingID else (dialogsOpen * 10) + this.startingID
@@ -46,12 +45,15 @@
     insertedDialog = dialogTemplate.appendTo($('body'))
     insertedDialog.css('z-index', 2000 + dialogID)

+    # set the content on the dialog
+    insertedDialog.data('options', options)
+
     overlayClass = ''
     overlayClass = 'invisible' if dialogID > 1
     theOverlay = $("

").insertBefore(insertedDialog).css('z-index', 2000 + dialogID - 1)
     theOverlay.fadeIn('fast')

-      # if we have a width, set the width for the dialog
+    # if we have a width, set the width for the dialog
     if options.width?
       insertedDialog.css('width', "#{options.width}px")
       insertedDialog.css('margin-left', "-#{options.width / 2}px")
@@ -78,10 +80,10 @@
       insertedDialog.addClass 'loading'
       $.ajax
         url: options.url
-        success: (data)=> this.displayDialog(insertedDialog, data, options)
+        success: (data)=> this.displayDialog(insertedDialog, data)

     else if options.html?
-      this.displayDialog(insertedDialog, options.html, options)
+      this.displayDialog(insertedDialog, options.html)

     else
       # anything else won't work
@@ -99,10 +101,11 @@
       false

   # Complete the opening of a dialog with the given HTML
-  displayDialog: (dialog, content, options={})->
+  displayDialog: (dialog, content)->
     dialog.html(content)
     dialog.fadeIn('fast')
     dialog.removeClass 'loading'
+    options = dialog.data('options')
     if options.behavior? && behavior = this.behaviors[options.behavior]
       behavior.onLoad.call(null, dialog, options) if behavior.onLoad?
     options.afterLoad.call(null, dialog) if options.afterLoad?
@@ -112,8 +115,12 @@
   # given ID if one is given).
   setContent: (content, id = null)->
     dialog = if id == null then $('div.niftyDialog:last') else $("div.niftyDialog#dialog-#{id}")
-    dialog.html(content)
-    this.onSetContent(null, dialog) if this.onSetContent?
+    if dialog.length
+      dialog.html(content)
+      options = dialog.data('options')
+      if options.behavior? && behavior = this.behaviors[options.behavior]
+        behavior.onSetContent.call(null, dialog, options) if behavior.onSetContent?
+      this.onSetContent(null, dialog) if this.onSetContent?

   # Create a new overlay
   createOverlay: (options)->


Optimized left right move and merge

Optimized left right move and merge

--- js/main.coffee
+++ js/main.coffee
@@ -38,7 +38,7 @@

 mergeCells = (row, direction) ->

-  if direction is 'right'
+  merge = (row) ->
     for a in [3...0]
       for b in [a-1..0]

@@ -48,18 +48,12 @@
           row[b] = 0
           break
         else if row[b] isnt 0 then break
+    row

+  if direction is 'right'
+    merge row
   else if direction is 'left'
-    for a in [0...3]
-      for b in [a+1..3]
-
-        if row[a] is 0 then break
-        else if row[a] == row[b]
-          row[a] *= 2
-          row[b] = 0
-          break
-        else if row[b] isnt 0 then break
-  row
+    merge(row.reverse()).reverse()

 collapseCells = (row, direction) ->
   row = row.filter (ele) -> ele isnt 0
@@ -97,7 +91,10 @@
 showBoard = (board) ->
   for row in [0..3]
     for col in [0..3]
-      $(".r#{row}.c#{col} > div").html(board[row][col])
+      if board[row][col] is 0
+        $(".r#{row}.c#{col} > div").html("")
+      else
+        $(".r#{row}.c#{col} > div").html(board[row][col])

 printArray = (array) ->
   console.log "--- Start ---"


Added line-intersect-rect algorithm

Added line-intersect-rect algorithm

--- src/extra/geometryAlgorithm.coffee
+++ src/extra/geometryAlgorithm.coffee
@@ -45,6 +45,8 @@
 				G.getCrossPointOfTwoLines line, @
 			Bu.Line::isCrossWithLine = (line) ->
 				G.isTwoLinesCross line, @
+			Bu.Rectangle::intersectRect = (rect) ->
+				G.isLineIntersectRect @, rect

 		if 'circle' in shapes
 			Bu.Circle::_containsPoint = (point) ->
@@ -63,6 +65,8 @@
 		if 'rectangle' in shapes
 			Bu.Rectangle::containsPoint = (point) ->
 				G.pointInRectangle point, @
+			Bu.Rectangle::intersectLine = (line) ->
+				G.isLineIntersectRect line, @

 		if 'fan' in shapes
 			Bu.Fan::_containsPoint = (point) ->
@@ -170,7 +174,7 @@
 		else
 			return new Bu.Point x, y

-	# Point and Line
+	# Point with Line

 	twoPointsSameSideOfLine: (p1, p2, line) ->
 		pA = line.points[0]
@@ -219,6 +223,7 @@
 		x4 = line2.points[1].x
 		y4 = line2.points[1].y

+
 		d = (y2 - y1) * (x4 - x3) - (y4 - y3) * (x2 - x1)

 		if d == 0
@@ -226,10 +231,24 @@
 		else
 			x0 = ((x2 - x1) * (x4 - x3) * (y3 - y1) + (y2 - y1) * (x4 - x3) * x1 - (y4 - y3) * (x2 - x1) * x3) / d
 			y0 = ((y2 - y1) * (y4 - y3) * (x3 - x1) + (x2 - x1) * (y4 - y3) * y1 - (x4 - x3) * (y2 - y1) * y3) / -d
-		return (x0 - x1) * (x0 - x2) < 0 and
-						(x0 - x3) * (x0 - x4) < 0 and
-						(y0 - y1) * (y0 - y2) < 0 and
-						(y0 - y3) * (y0 - y4) < 0
+		return (x0 - x1) * (x0 - x2) <= 0 and
+						(x0 - x3) * (x0 - x4) <= 0 and
+						(y0 - y1) * (y0 - y2) <= 0 and
+						(y0 - y3) * (y0 - y4) <= 0
+
+	# Line with rectangle
+
+	isLineIntersectRect: (line, rect) ->
+		lines = [ new Bu.Line, new Bu.Line, new Bu.Line, new Bu.Line ]
+		lines[0].set rect.points[0], rect.points[1]
+		lines[1].set rect.points[1], rect.points[2]
+		lines[2].set rect.points[2], rect.points[3]
+		lines[3].set rect.points[3], rect.points[0]
+		# console.log line.points[0].x, line.points[0].y, rect.points[0].x, rect.points[0].y
+		G.isTwoLinesCross(line, lines[0]) or
+			G.isTwoLinesCross(line, lines[1]) or
+			G.isTwoLinesCross(line, lines[2]) or
+			G.isTwoLinesCross(line, lines[3])

 	# Polyline


Remove # Debug message

Remove # Debug message

--- server/helpers/cdnify/index.coffee
+++ server/helpers/cdnify/index.coffee
@@ -36,8 +36,6 @@
     def = Q.defer()

     _cdnKey = null
-
-    console.log url

     request(url)
       .then (content) ->


Server: removed deprecated activity widget template

--- workers/social/lib/social/render/loggedin/statuswidget.coffee
+++ workers/social/lib/social/render/loggedin/statuswidget.coffee
@@ -1,21 +0,0 @@
-module.exports = (account)->
-
-  {firstName} = account.profile
-  encoder     = require 'htmlencode'
-
-  """
-

-

-

-        <button class="kdbutton activity-status-context with-icon with-menu" id="kd-320">Status Update</button>
-

-

-

-          <form class="kdformview status-widget">
-
<input name="dummy" type="text" class="kdinput text status-update-input warn-on-unsaved-data" placeholder="What's new #{encoder.XSSEncode firstName}?">

-          </form>
-

-

-

-

-  """


Add auto-zoom

Add auto-zoom

--- src/frontend/modules/camera.coffee
+++ src/frontend/modules/camera.coffee
@@ -76,6 +76,8 @@
         video.addClass 'thumbnailVideo'
         video.attr 'rel', data.uid
         @emitter.trigger 'stream.display', video
+        if !data.local?
+            @zoom data.uid, video

     changeStreamState : (event, data) =>
         # Data.state = {audio : bool, video : bool}


might as well bring in is_default just in case

--- apps/sitemaps/routes.coffee
+++ apps/sitemaps/routes.coffee
@@ -116,6 +116,7 @@
           'id'
           'images.image_urls.large'
           'images.image_urls.larger'
+          'images.is_default'
           'title'
           'artist.name'
           'date'


WIP ~ RequireLogin branch

WIP ~ RequireLogin branch

--- core/KD.coffee
+++ core/KD.coffee
@@ -102,15 +102,28 @@
             return yes
     no

-  requireLogin:(errMsg, callback)->
-
-    [callback, errMsg] = [errMsg, callback] unless callback
-
-    if KD.whoami() instanceof KD.remote.api.JGuest
-      new KDNotificationView
-        title    : 'Access denied!'
-        content  : errMsg or 'You must log in to perform this action!'
-        duration : 3000
+  requireLogin:(options={})->
+
+    {callback, onFailMsg, onFail, silence, tryAgain} = options
+
+    unless KD.whoami() instanceof KD.remote.api.JAccount and KD.isLoggedIn()
+
+      if onFailMsg
+        new KDNotificationView
+          type     : 'mini'
+          cssClass : 'error'
+          title    : onFailMsg
+          duration : 5000
+
+      onFail?()
+
+      unless silence
+        @getSingleton('router').handleRoute "/Login", KD.config.entryPoint
+
+      if callback? and tryAgain
+        @getSingleton('mainController').once "accountChanged.to.loggedIn",\
+          -> callback()
+
     else
       callback?()


save peer ip

save peer ip

--- lib/actors/htracker.coffee
+++ lib/actors/htracker.coffee
@@ -121,7 +121,7 @@
             , @timeoutDelay)
         index++
       if existPeer isnt true
-        @peers.push {peerType:hMessage.payload.params.peerType, peerFullId:hMessage.publisher, peerId:hMessage.payload.params.peerId, peerStatus:hMessage.payload.params.peerStatus, peerInbox:hMessage.payload.params.peerInbox}
+        @peers.push _.extend({peerFullId: hMessage.publisher}, _.pick(hMessage.payload.params, ['peerType', 'peerId', 'peerStatus', 'peerInbox', 'peerIP']))
         @timerPeers[hMessage.publisher] = setTimeout(=>
           delete @timerPeers[hMessage.publisher]
           @stopAlert(hMessage.publisher)


array destructuring!

array destructuring!

--- countUp.coffee
+++ countUp.coffee
@@ -121,13 +121,11 @@
     @startVal = @framVal
     requestAnimationFrame @count

-  addCommas: (nStr) ->
-    nStr += ''
-    x = nStr.split('.')
-    x1 = x[0]
-    x2 = if x.length > 1 then "." + x[1] else ''
+  addCommas: (nStr='') ->
+    [x1, x2] = nStr.split('.')
+    x2 = if x2? then "." + x2 else ''
+
     rgx = /(\d+)(\d{3})/
-
     while rgx.test(x1)
       x1 = x1.replace(rgx, '$1' + ',' + '$2')


fixed matches not reloading

fixed matches not reloading

--- crawler.coffee
+++ crawler.coffee
@@ -26,7 +26,7 @@
             if err || !content.wvw_matches?.length
                 log 'Error: There was an error or empty response. Retrying in 10 seconds'
                 clearTimeout @_timeout if @_timeout
-                delay 10000, => @getMatches
+                delay 10000, => @getMatches()
             @matches = []
             nearestEndTime
             for match in content.wvw_matches
@@ -38,13 +38,13 @@
             clearTimeout @_timeout if @_timeout
             diff = Math.min(3600000, nearestEndTime - +new Date)

-            delay diff, => @getMatches
+            delay diff, => @getMatches()

             @emit 'matches', @matches
             log "done (#{@matches.length}, reloading matches in #{Math.round(diff/1000/60)}m)"

     getDetails: ( match ) ->
-        log 'Loading details for match ' + match.wvw_match_id
+        # log 'Loading details for match ' + match.wvw_match_id
         request { url: @detailUrl + '?match_id=' +  match.wvw_match_id, json:true }, ( err, response, content ) =>
             @emit 'details', match, content

@@ -54,4 +54,4 @@
         if @updateIndex >= @matches.length
             @updateIndex = 0

-module.exports = Crawler+module.exports = Crawler


Fix typo

Fix typo

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -38,7 +38,7 @@
   activate: (state) ->
     @process = new GhcModiProcess

-    unless atom.config.get('haskell-gch-mod.suppressStartupWarning')
+    unless atom.config.get('haskell-ghc-mod.suppressStartupWarning')
       setTimeout (->
         unless atom.packages.isPackageActive('ide-haskell')
           atom.notifications.addWarning "Haskell-ghc-mod package is intended to


tidy up

tidy up

--- vue/src/shared/record_store/base_model.coffee
+++ vue/src/shared/record_store/base_model.coffee
@@ -51,10 +51,7 @@
       else
         cloneAttributes[attr] = @[attr]
       true
-    cloneAttributes.cloney = true
-    cloneRecord = new @constructor(@recordsInterface, cloneAttributes)
-    cloneRecord.clonedFrom = @
-    cloneRecord
+    new @constructor(@recordsInterface, cloneAttributes)

   inCollection: =>
     @['$loki']# and @recordsInterface.collection.get(@['$loki'])
@@ -202,8 +199,6 @@
       throw data
     else
       @unmodified = pick(@, @attributeNames)
-      @clonedFrom = undefined
-      console.log 'aftersave unmodified', @unmodified
       data

   clearErrors: ->


test: Add another test case for formatDate

--- test/test.coffee
+++ test/test.coffee
@@ -44,6 +44,8 @@
         'June 9, 2013'
       assert.strictEqual bitterServer.formatDate('2020', '12', '31'),
         'December 31, 2020'
+      assert.strictEqual bitterServer.formatDate('2001', '01', '01'),
+        'January 1, 2001'

 describe 'server', ->
   before (done) ->


Specs for the startingPoint setting

Specs for the startingPoint setting

--- test/test.coffee
+++ test/test.coffee
@@ -199,6 +199,18 @@
           _internals.expects('transitionTo').once().withArgs(1, 0)
           $(@$carousel.circular('controls')[1]).click()
         @$carousel.circular({directJump: true})
+
+      it 'the starting point defaults to the slide of id "0"', (done) ->
+        @$carousel.on 'circular:init', =>
+          expect(@$carousel.circular('current').id).to.equal 0
+          done()
+        @$carousel.circular()
+
+      it 'the starting point can be overriden', (done) ->
+        @$carousel.on 'circular:init', =>
+          expect(@$carousel.circular('current').id).to.equal 1
+          done()
+        @$carousel.circular({startingPoint: 1})

       it 'autoStart is true by default', ->
         _resume = spy @$carousel.circular('_internals'), 'resume'


Spec for the beforeStart hook

Spec for the beforeStart hook

--- test/test.coffee
+++ test/test.coffee
@@ -225,6 +225,19 @@
         expect(_resume).to.not.have.been.called
         expect(@$carousel.circular('isRunning')).to.be.false
         @$carousel.circular('_internals').resume.restore()
+
+      it 'a beforeStart hook is available', (done) ->
+        hooks =
+          beforeStart: (current, slides) ->
+        _hooks = mock(hooks)
+        _hooks.expects('beforeStart').once()
+        @$carousel.on 'circular:init', =>
+          #current = @$carousel.circular('current')
+          #slides = @$carousel.circular('slides')
+          #_hooks.expects('beforeStart').once().on(@$carousel).withArgs(current, slides)
+          _hooks.verify()
+          done()
+        @$carousel.circular({beforeStart: hooks.beforeStart})

     it 'can be paused', (done) ->
       @$carousel.circular()


connect course creation

connect course creation

--- tutor/src/api/index.coffee
+++ tutor/src/api/index.coffee
@@ -53,6 +53,7 @@
 { default: User } = require '../models/user'
 { default: Courses } = require '../models/courses-map'
 { default: Offerings } = require '../models/offerings';
+{ default: CourseCreate } = require '../models/course/create';

 BOOTSTRAPED_MODELS = {
   user:    User.bootstrap,
@@ -214,10 +215,6 @@
   )

   connectRead(CourseListingActions, url: 'user/courses')
-  connectCreate(NewCourseActions,
-    pattern: 'courses/{id}/clone', trigger: 'clone', data: NewCourseStore.requestPayload
-  )
-  connectCreate(NewCourseActions, url: 'courses', data: NewCourseStore.requestPayload)

   connectRead(ReferenceBookActions, pattern: 'ecosystems/{id}/readings')
   connectRead(ReferenceBookPageActions, pattern: 'pages/{id}')
@@ -253,6 +250,7 @@
    )
   connectModelRead(Offerings.constructor, 'fetch', url: 'offerings', onSuccess: 'onLoaded')

+  connectModelCreate(CourseCreate, 'save', onSuccess: 'onCreated')

 start = (bootstrapData) ->
   for storeId, action of BOOTSTRAPED_MODELS


+ Number::ordinalize, + String::pluralize, - String::format

--- vendor/assets/javascripts/fustrate/_prototypes.coffee
+++ vendor/assets/javascripts/fustrate/_prototypes.coffee
@@ -49,9 +49,6 @@
 Number::accountingFormat = ->
   if @ < 0 then "($#{(@ * -1).toFixed(2)})" else "$#{@.toFixed(2)}"

-Number::truncate = (digits = 2) ->
-  @toFixed(digits).replace(/\.?0+$/, '')
-
 Number::bytesToString = ->
   return "#{@} B" if @ < 1000

@@ -61,26 +58,19 @@

   "#{(@ / 1000000000).truncate()} GB"

+Number::ordinalize = ->
+  s = ['th', 'st', 'nd', 'rd']
+  v = @ % 100
+  @ + (s[(v - 20) % 10] or s[v] or 'th')
+
+Number::truncate = (digits = 2) ->
+  @toFixed(digits).replace(/\.?0+$/, '')
+
 String::capitalize = ->
   @charAt(0).toUpperCase() + @slice(1)

 String::dasherize = ->
   @replace /_/g, '-'
-
-# Example:
-#   'hello {planet}'.format(planet: 'world') # => 'Hello world'
-String::format = ->
-  e = @toString()
-
-  return e if not arguments.length
-
-  n = if typeof(arguments[0]) in ['string', 'number']
-    Array.prototype.slice.call(arguments)
-  else
-    arguments[0]
-
-  e.replace /{([^}]+)}/g, (match, key) ->
-    if typeof n[key] is 'undefined' then match else n[key]

 String::humanize = ->
   @.replace(/[a-z][A-Z]/, (match) -> "#{match[0]} #{match[1]}")
@@ -104,6 +94,13 @@
     @replace /(\d{3})(\d{4})/, '$1-$2'
   else
     @
+
+# This is far too simple for most cases, but it works for the few things we need
+String::pluralize = ->
+  if @[@length - 1] is 'y'
+    return @substr(0, @length - 1) + 'ies'
+
+  @ + 's'

 String::presence = ->
   if @isBlank() then null else @


:bug: Prevent setting size of an undefined model

Fixes #373

--- lib/minimap-element.coffee
+++ lib/minimap-element.coffee
@@ -302,6 +302,7 @@
       @requestUpdate()

     @setAttribute('stand-alone', true) if @minimap.isStandAlone()
+    @minimap.setScreenHeightAndWidth(@height, @width) if @width? and @height?

     @minimap

@@ -416,7 +417,7 @@
     @width = @clientWidth
     canvasWidth = @width

-    @minimap.setScreenHeightAndWidth(@height, @width)
+    @minimap.setScreenHeightAndWidth(@height, @width) if @minimap?

     @requestForcedUpdate() if wasResized or visibilityChanged or forceUpdate


Add unit reporter abilities.

Add unit reporter abilities.

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -197,6 +197,10 @@

   constructor: -> super 'teamcity'

+class Xunit extends Reporter
+
+  constructor: -> super 'xunit'
+
 class Json extends Reporter

   constructor: -> super 'json'


:lipstick: remove unnecessary things

:lipstick: remove unnecessary things

--- spec/project-find-view-spec.coffee
+++ spec/project-find-view-spec.coffee
@@ -763,7 +763,6 @@

         runs ->
           editor = atom.workspace.getActiveEditor()
-          editorView = atom.views.getView(editor)
           expect(getResultDecorations('find-result')).toHaveLength 0

         runs ->
@@ -797,7 +796,7 @@
           initialSelectedRange = editor.getSelectedBufferRange()

           # now we can find next
-          atom.commands.dispatch editorView, 'find-and-replace:find-next'
+          atom.commands.dispatch atom.views.getView(editor), 'find-and-replace:find-next'
           expect(editor.getSelectedBufferRange()).not.toEqual initialSelectedRange

   describe "replacing", ->


Update spec

Update spec

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -1858,7 +1858,7 @@
               expect(callback).not.toHaveBeenCalled()

               expect(atom.notifications.getNotifications().length).toBe(1)
-              expect(atom.notifications.getNotifications()[0].getMessage()).toContain('Unable to paste')
+              expect(atom.notifications.getNotifications()[0].getMessage()).toContain('Failed to move')

           describe 'when the file is currently open', ->
             beforeEach ->


Prevented need for calling setValue() if the value didn't change on an input.

--- src/Templates.coffee
+++ src/Templates.coffee
@@ -67,7 +67,6 @@
       newValue = options.unmarshall(value)
       if newValue != reactiveVar.get()
         reactiveVar.set(newValue)
-      options.setValue.call($em, newValue)
     if options.debounce
       update = _.debounce(update, options.delay)
     $em.on(options.changeEvents, update)


Fix tests to conform with schema, give listcomponents more time

--- src/WebSocket.coffee
+++ src/WebSocket.coffee
@@ -344,17 +344,34 @@
         it 'should remove the node and its associated edges', (done) ->
           expects = [
             protocol: 'graph'
+            command: 'removeedge'
+            payload:
+              src:
+                node: 'Repeat1'
+                port: 'out'
+              tgt:
+                node: 'Drop1'
+                port: 'in'
+              graph: 'foo'
+          ,
+            protocol: 'graph'
             command: 'removenode'
             payload:
               id: 'Drop1'
               graph: 'foo'
           ]
-          connection.once 'message', (message) ->
-            data = JSON.parse message.utf8Data
-            validateSchema data, '/graph/output/removenode'
-            chai.expect(data.payload).to.eql expects[0].payload
-
-            done()
+          listener = (message) ->
+            data = JSON.parse message.utf8Data
+            validateSchema data, "/graph/output/#{data.command}"
+            return unless data.command is expects[0].command
+            expected = expects.shift()
+            chai.expect(data.payload).to.eql expected.payload
+            return if expects.length
+            # Received all packets
+            connection.removeListener 'message', listener
+            done()
+            return
+          connection.on 'message', listener

           send 'graph', 'removenode',
             id: 'Drop1'
@@ -371,7 +388,6 @@
               tgt:
                 node: 'Repeat1'
                 port: 'in'
-              metadata: {}
               graph: 'foo'
           ]
           connection.once 'message', (message) ->
@@ -744,6 +760,7 @@
     describe 'Component protocol', ->
       describe 'on requesting a component list', ->
         it 'should receive some known components', (done) ->
+          @timeout 20000
           listener = (message) ->
             data = JSON.parse message.utf8Data
             validateSchema data, '/component/output/list'


Move the select-next/prev logic into the handlers

--- lib/suggestion-list.coffee
+++ lib/suggestion-list.coffee
@@ -11,6 +11,14 @@
     @subscriptions.add atom.commands.add 'atom-text-editor.autocomplete-active',
       'autocomplete-plus:confirm': @confirmSelection,
       'autocomplete-plus:cancel': @cancel
+      'core:move-up': (event) =>
+        if @isActive() and @items?.length > 1
+          @selectPrevious()
+          event.stopImmediatePropagation()
+      'core:move-down': (event) =>
+        if @isActive() and @items?.length > 1
+          @selectNext()
+          event.stopImmediatePropagation()

   addKeyboardInteraction: ->
     @removeKeyboardInteraction()
@@ -22,35 +30,13 @@
     keys['tab'] = 'autocomplete-plus:confirm' if completionKey.indexOf('tab') > -1
     keys['enter'] = 'autocomplete-plus:confirm' if completionKey.indexOf('enter') > -1

-    @addKeyboardSelectionInteraction()
-
     @keymaps = atom.keymaps.add('atom-text-editor.autocomplete-active', {'atom-text-editor.autocomplete-active': keys})
     @subscriptions.add(@keymaps)

   removeKeyboardInteraction: ->
-    @removeKeyboardSelectionInteraction()
     @keymaps?.dispose()
     @keymaps = null
     @subscriptions.remove(@keymaps)
-
-  updateKeyboardSelectionInteraction: ->
-    return unless @items?
-    if @items.length isnt @previousItemsLength
-      @addKeyboardSelectionInteraction()
-
-  addKeyboardSelectionInteraction: ->
-    return unless @items?
-    @removeKeyboardSelectionInteraction()
-    if @items.length > 1
-      commands = {'core:move-down': @selectNext, 'core:move-up': @selectPrevious}
-      @selectionInteractionKeymaps = atom.commands.add('atom-text-editor.autocomplete-active', commands)
-      @subscriptions.add(@selectionInteractionKeymaps)
-    @previousItemsLength = @items.length
-
-  removeKeyboardSelectionInteraction: ->
-    @selectionInteractionKeymaps?.dispose()
-    @selectionInteractionKeymaps = null
-    @subscriptions.remove(@selectionInteractionKeymaps)

   confirmSelection: =>
     @emitter.emit('did-confirm-selection')
@@ -64,16 +50,14 @@
   onDidConfirm: (fn) ->
     @emitter.on('did-confirm', fn)

-  selectNext: (e) =>
+  selectNext: ->
     @emitter.emit('did-select-next')
-    e.stopImmediatePropagation()

   onDidSelectNext: (fn) ->
     @emitter.on('did-select-next', fn)

-  selectPrevious: (e) =>
+  selectPrevious: ->
     @emitter.emit('did-select-previous')
-    e.stopImmediatePropagation()

   onDidSelectPrevious: (fn) ->
     @emitter.on('did-select-previous', fn)
@@ -92,7 +76,6 @@
       @showAtCursorPosition(editor, options)
     else
       @showAtBeginningOfPrefix(editor, options)
-    @updateKeyboardSelectionInteraction()

   showAtBeginningOfPrefix: (editor, {prefix}) =>
     return unless editor?


race condition fix

race condition fix

--- lib/utils/multipart.coffee
+++ lib/utils/multipart.coffee
@@ -71,8 +71,6 @@

   parser.onHeadersEnd = () ->
     bodyStream = through2()
-    bodyStream.on 'end', () ->
-      bodyStream = null
     handler(headers, bodyStream, false, options)
     .then (object) ->
       if !objectStreamDone
@@ -86,6 +84,7 @@
       bodyStream?.write(b.slice(start, end))
     parser.onPartEnd = () ->
       bodyStream?.end()
+      bodyStream = null

   parser.onEnd = () ->
     if done


remove input cell for quiet

remove input cell for quiet

--- editor.coffee
+++ editor.coffee
@@ -142,6 +142,11 @@
   else
     return null

+remove_cell = (cell) ->
+  index = contexts.indexOf cell
+  cell.$el.remove()
+  contexts.splice index, 1
+
 add_context = (code='') ->
   cell = get_available_context()
   if cell?
@@ -274,9 +279,7 @@
     clear_output: -> clear_contexts()
     previously_run: -> context_at_offset(input_cell, -1).editor.getValue()
     hide_input: ->
-      # TODO this won't play nice with moving between contexts
-      if input_cell?
-        input_cell.hide()
+      remove_cell input_cell
     async: (fn) ->
       fn.call(run_context)


Activity: minor: do not throw if user wants to navigate to the same route twice

--- client/activity/lib/views/activitypane.coffee
+++ client/activity/lib/views/activitypane.coffee
@@ -77,6 +77,8 @@

     options = @getActiveContentOptions()

+    return  unless options
+
     @refreshContentPane options


Set initial settings once session is created

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -678,6 +678,8 @@

   onCollaborationCreated: ->

+    @setInitialSettings()
+
     @chat.settingsPane.startSession.updateProgress 100

     kd.utils.wait 500, => @stateMachine.transition 'Active'
@@ -1086,3 +1088,9 @@

     @initialSettings ?= {}
     @initialSettings[name] = value
+
+
+  setInitialSettings: ->
+
+    for own key, value of @initialSettings
+      @settings.set key, value


bugfix in commit d81971c

bugfix in commit d81971c

--- client/views/pages/dashboard/dashboard.coffee
+++ client/views/pages/dashboard/dashboard.coffee
@@ -187,7 +187,7 @@
 			for user in team.pending when user._id == Meteor.userId()
 				myShift = true

-		myShift && (@date >= today || @date == today && @end >= now || Session.get('showOlder') || missingReport)
+		myShift && (@date > today || @date == today && @end >= now || Session.get('showOlder') || missingReport)

 	showOlder: -> Session.get 'showOlder'


KDTokenizedInputView: pass event argument to super

--- components/inputs/KDTokenizedInputView.coffee
+++ components/inputs/KDTokenizedInputView.coffee
@@ -130,7 +130,7 @@
     @tokenInput.remove()

   keyDown: (event) ->
-    super
+    super event
     switch event.which
       when 9, 13, 27, 38, 40 # enter, tab, escape, up, down
         if @menu


dont show annoying beta message on mobile

--- components/message/client/message_view.coffee
+++ components/message/client/message_view.coffee
@@ -11,7 +11,7 @@

   initialize: ->
     # only render if user does not have cookie
-    @render() if not Cookies.get @model.id
+    @render() unless Cookies.get(@model.id) or $('body').hasClass 'is-mobile'

   render: ->
     # do this directly for now, thinking of different templates with the same view