Cleanup checks; move _messageParent checks to messageParent

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -525,17 +525,11 @@
 _messageParent = (child, parent, position = null, oldParent = true, importing = false) ->
   ## oldParent is an internal option for server only; true means "search for/
   ## deal with old parent", while null means we assert there is no old parent.
-  check Meteor.userId(), String  ## should be done by 'canEdit'
-  check parent, String if parent != null
-  check position, Number if position?
-  #check oldParent, Boolean
-  #check importing, Boolean

   cmsg = Messages.findOne child
   unless cmsg?
     throw new Meteor.Error 'messageParent.noChild',
       "Missing child message #{child} to reparent"
-
   if parent?
     pmsg = Messages.findOne parent
     unless pmsg?
@@ -668,8 +662,8 @@

   messageNew: (group, parent = null, position = null, message = {}) ->
     #check Meteor.userId(), String  ## should be done by 'canPost'
-    check parent, String if parent?
-    check position, Number if position?
+    check parent, Match.OneOf String, null
+    check position, Match.Maybe Number
     check group, String
     check message,
       title: Match.Optional String
@@ -749,6 +743,9 @@
   messageParent: (child, parent, position = null) ->
     ## Notably, disabling oldParent search and importing options are not
     ## allowed from client, only internal to server.
+    check Meteor.userId(), String  ## should be done by 'canEdit'
+    check parent, Match.OneOf String, null
+    check position, Match.Maybe Number
     _messageParent child, parent, position

   messageEditStart: (id) ->
@@ -787,7 +784,7 @@
   messageImport: (group, parent, message, diffs) ->
     #check Meteor.userId(), String  ## should be done by 'canImport'
     check group, String
-    check parent, String if parent?
+    check parent, Match.OneOf String, null
     check message,
       title: Match.Optional String
       body: Match.Optional String


improve

improve

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -4,7 +4,7 @@
 {Point, Range, CompositeDisposable, BufferedProcess} = require 'atom'

 {
-  haveSomeSelection, getVimEofBufferPosition
+  haveSomeSelection
   moveCursorLeft, moveCursorRight
   highlightRanges, getNewTextRangeFromCheckpoint
   isEndsWithNewLineForBufferRow
@@ -174,7 +174,6 @@
   @extend()
   requireTarget: false
   recordable: false
-
   @description: "Select last selected visual area in current buffer"
   execute: ->
     {properties, submode} = @vimState.modeManager.getPreviousSelectionInfo()
@@ -439,6 +438,7 @@

   execute: ->
     # NEVER be executed since operationStack is replaced with selected transformer
+    throw new Error("#{@getName()} should not be executed")

 class TransformWordBySelectList extends TransformStringBySelectList
   @extend()
@@ -471,27 +471,23 @@
   @extend()
   hover: icon: ':indent:', emoji: ':point_right:'
   stayOnLinewise: false
+  indentFunction: "indentSelectedRows"

   mutateSelection: (selection) ->
-    @indent(selection)
+    selection[@indentFunction]()
     @restorePoint(selection)
     unless @needStay()
       selection.cursor.moveToFirstCharacterOfLine()

-  indent: (selection) ->
-    selection.indentSelectedRows()
-
 class Outdent extends Indent
   @extend()
   hover: icon: ':outdent:', emoji: ':point_left:'
-  indent: (selection) ->
-    selection.outdentSelectedRows()
+  indentFunction: "outdentSelectedRows"

 class AutoIndent extends Indent
   @extend()
   hover: icon: ':auto-indent:', emoji: ':open_hands:'
-  indent: (selection) ->
-    selection.autoIndentSelectedRows()
+  indentFunction: "autoIndentSelectedRows"

 # -------------------------
 class ToggleLineComments extends TransformString
@@ -671,14 +667,20 @@
 # FIXME
 # Currently native editor.joinLines() is better for cursor position setting
 # So I use native methods for a meanwhile.
-class Join extends Operator
-  @extend()
-  requireTarget: false
-  execute: ->
-    @editor.transact =>
-      @countTimes =>
-        @editor.joinLines()
-    @activateMode('normal')
+class Join extends TransformString
+  @extend()
+  target: "MoveToRelativeLine"
+  flashTarget: false
+
+  needStay: -> false
+
+  mutateSelection: (selection) ->
+    if swrap(selection).isLinewise()
+      range = selection.getBufferRange()
+      selection.setBufferRange(range.translate([0, 0], [-1, Infinity]))
+    selection.joinLines()
+    end = selection.getBufferRange().end
+    selection.cursor.setBufferPosition(end.translate([0, -1]))

 class JoinWithKeepingSpace extends TransformString
   @extend()
@@ -766,6 +768,7 @@
           operation.execute()

 # -------------------------
+# [FIXME] this is not operator
 class Mark extends Operator
   @extend()
   hover: icon: ':mark:', emoji: ':round_pushpin:'


Fixed a bug where the lexer/parser would not close out any indentation that was remaining at the EOF

--- source/lexer.coffee
+++ source/lexer.coffee
@@ -26,17 +26,7 @@
       @error "invalid token '#{@code[index..index+15]}...'" unless text?
       value = parse_token[@type] text
       if last_token_type is 'NEWLINE' #check for indent/dedent
-        indentation = if type is 'WHITESPACE' then text.length else 0
-        if indentation > @indent
-          @indents.push @indent
-          @indent = indentation
-          @tokens.push text:text, line:@line, value:'', type:'INDENT'
-        else if indentation < @indent
-          while @indents.length > 0 and indentation < @indent
-            @indent = @indents.pop()
-            @error 'indentation is misaligned' if indentation > @indent
-            @tokens.push text:text, line:@line, value:'', type:'DEDENT'
-          @error 'indentation is misaligned' if indentation isnt @indent
+        @handleIndentation type, text
       if type is 'COMMENT'
         @comments.push text:text, line:@line, value:value, type:type
       else if type isnt 'WHITESPACE'
@@ -44,6 +34,21 @@
       index += text.length
       @line += /\n/.exec(text)?[0].length or 0
       last_token_type = type
+    @handleIndentation 'NEWLINE', '' #clear up any remaining indents at the end of the file
+
+  handleIndentation: (type, text) ->
+    indentation = if type is 'WHITESPACE' then text.length else 0
+    if indentation > @indent
+      @indents.push @indent
+      @indent = indentation
+      @tokens.push text:text, line:@line, value:'', type:'INDENT'
+    else if indentation < @indent
+      while @indents.length > 0 and indentation < @indent
+        @indent = @indents.pop()
+        @error 'indentation is misaligned' if indentation > @indent
+        @tokens.push text:text, line:@line, value:'', type:'DEDENT'
+      @error 'indentation is misaligned' if indentation isnt @indent
+
   error: (message) ->
     throw message


question

question

--- src/driver/driverUtil.coffee
+++ src/driver/driverUtil.coffee
@@ -4,7 +4,7 @@
 timezoneJS = require('timezone-js', 'timezoneJS')
 tz_info = require('../utils/timezone', 'mmx_tz_info')

-if timezoneJS.timezone
+if timezoneJS?.timezone
   tz = timezoneJS.timezone
   tz.loadingScheme = tz.loadingSchemes.MANUAL_LOAD
   tz.loadZoneDataFromObject(tz_info)


added isPropValueIn

added isPropValueIn

--- src/driver/driverUtil.coffee
+++ src/driver/driverUtil.coffee
@@ -105,13 +105,22 @@
 #
 # @param {Object} pv1, first prop value
 # @param {Object} pv2, second prop value
-# @return {Boolean} are they equal
+# @return {Boolean} are they equal?
 exports.isPropValueEqual = (pv1, pv2) ->
   if Array.isArray(pv1) and pv1.length is 2
     return false unless Array.isArray(pv2) and pv2.length is 2
     return pv1[0].valueOf() is pv2[0].valueOf() and pv1[1].valueOf() is pv2[1].valueOf()
   else
     return pv1 is pv2
+
+
+# Checks for the presence of prop value in the given list
+#
+# @param {Object} propValue, the prop value to search
+# @param {Array} propValueList, the list to examine
+# @return {Boolean} is it in?
+exports.isPropValueIn = (propValue, propValueList) ->
+  return propValueList.some((pv) -> isPropValueEqual(propValue, pv))

 # Join several arrays of results


Provide a ::getUnparsedSnippets method

Provide a ::getUnparsedSnippets method

So that we can use it in SettingsView

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -334,7 +334,11 @@
       snippet = new Snippet({name: '__anonymous', prefix: '', bodyTree, bodyText: snippet})
     new SnippetExpansion(snippet, editor, cursor, this)

+  getUnparsedSnippets: ->
+    _.deepClone(@scopedPropertyStore.propertySets)
+
   provideSnippets: ->
     bundledSnippetsLoaded: => @loaded
     insertSnippet: @insert.bind(this)
     snippetsForScopes: @parsedSnippetsForScopes.bind(this)
+    getUnparsedSnippets: @getUnparsedSnippets.bind(this)


Guard `loadAtomSnippets` against non-existent snippet directories

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -37,6 +37,7 @@

   loadAtomSnippets: (path, done) ->
     snippetsDirPath = fsUtils.join(path, 'snippets')
+    return done() unless fsUtils.isDirectory(snippetsDirPath)

     loadSnippetFile = (filename, done) =>
       return done() if filename.indexOf('.') is 0
@@ -53,6 +54,7 @@

   loadTextMateSnippets: (path, done) ->
     snippetsDirPath = fsUtils.join(path, 'Snippets')
+    return done() unless fsUtils.isDirectory(snippetsDirPath)

     loadSnippetFile = (filename, done) =>
       return done() if filename.indexOf('.') is 0
@@ -83,7 +85,6 @@
         logError(err)
         done()

-    return done() unless fsUtils.isDirectory(snippetsDirPath)
     fs.readdir snippetsDirPath, (err, paths) ->
       if err
         console.warn err


Extra liiiiiiiiines

Extra liiiiiiiiines

--- lib/vim-mode.coffee
+++ lib/vim-mode.coffee
@@ -9,7 +9,6 @@
     atom.workspace.vimState.registers ||= {}
     atom.workspace.vimState.searchHistory ||= []

-
   activate: (state) ->
     @_initializeWorkspaceState()
     atom.workspaceView.eachEditorView (editorView) =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -53,7 +53,7 @@
     fadeIn: (options={}) ->
       self.cameras().invoke('fadeIn', options)
     fadeOut: (options={}) ->
-      self.cameras.invoke('fadeOut', options)
+      self.cameras().invoke('fadeOut', options)
     flash: (options={}) ->
       self.camera(options.camera).flash(options)
     objects: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -17,7 +17,7 @@
     # Handle state change
     if requestedState?
       I.currentState.trigger "exit", requestedState
-      self.trigger ''
+      self.trigger 'stateEntere'

       previousState = I.currentState
       I.currentState = requestedState


Make #style test pass in PhantomJS 1.6

Make #style test pass in PhantomJS 1.6

--- spec/queries.coffee
+++ spec/queries.coffee
@@ -110,7 +110,7 @@
   describe '#style', ->
     it "returns element's CSS style", ->
       $('#styledAttribute').style('color').should.equal 'rgb(0, 128, 0)'
-      $('#styledAttribute').style('background-color').should.equal 'transparent'
+      $('#styledAttribute').style('background-color').should.match /(transparent|rgba\(0, 0, 0, 0\))/

     it "returns element's CSS style from a stylesheet", ->
       $('#styledStylesheet').style('color').should.equal 'rgb(128, 0, 128)'


Fix code loading

Fix code loading

--- app/assets/javascripts/application/code.js.coffee
+++ app/assets/javascripts/application/code.js.coffee
@@ -10,11 +10,13 @@

 open_modal = () ->
   $("#code").show('slide', { direction: 'right' }, 500)
+  $("#code").data('open', true)
   load_code()
   # ga('send', 'pageview', file)

 close_modal = () ->
   $("#code").hide('slide', { direction: 'right' }, 500)
+  $("#code").data('open', false)

 load_code = () ->
   current_controller_and_action = $('#main > div').attr('class')
@@ -38,19 +40,16 @@

 singularize = (word) ->
   word.replace(/s$/, '')
-  word

 pluralize = (word) ->
   "#{word}s".replace(/ss$/, 's')

 normalize_model = (word) ->
-  singularize(word)
+  word = singularize(word)
   word.replace(/(colophon|home)/, 'note')
-  word

 normalize_controller = (word) ->
   word.replace(/(features|citations|links)/, 'notes')
-  word

 normalize_action = (word, controller) ->
   if controller == 'pantography'
@@ -91,4 +90,5 @@
       close_modal()

   $(document).on 'pjax:success', '#main', (data) ->
-    load_code()
+    if $("#code").data('open')
+      load_code()


Implement find method in store

Implement find method in store

--- app/assets/javascripts/rglossa/react/store.coffee
+++ app/assets/javascripts/rglossa/react/store.coffee
@@ -19,4 +19,24 @@
     $.when.apply(null, promises).done(=> @storeChangedHandler(@))

+  find: (model, id) ->
+    plural = @getPlural(model)
+    data = @models[plural][id]
+    return data if data?  # Store already contains the model
+
+    # Store does not already contain the model, so fetch it
+    url = "#{plural}/#{id}"
+    $.getJSON(url)
+      .done( (res) =>
+        @models[plural][id] = res[model]
+        # Notify the client that the model has been loaded
+        @storeChangedHandler(@))
+      .fail -> alert('Error fetching data from the server. Please reload the page.')
+
+    # Return null since the store did not contain the model. When the model has been
+    # loaded, @storeChangedHandler will be called, and then the client can call `find`
+    # again to retrieve the model.
+    null
+
+
 window.Store = Store


Scope binding to object rather than document

All of the other callbacks are bound to the object, changing "s3_uploads_complete" to be consistent

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -55,8 +55,7 @@
         $uploadForm.trigger("s3_upload_complete", [content])

         current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
-        if current_files.length == 0
-          $(document).trigger("s3_uploads_complete")
+        $uploadForm.trigger("s3_uploads_complete") unless current_files.length

       fail: (e, data) ->
         content = build_content_object $uploadForm, data.files[0], data.result


updating proptype for accordion and getting a better name for expanded

--- src/components/task-plan/select-topics.cjsx
+++ src/components/task-plan/select-topics.cjsx
@@ -45,6 +45,7 @@
     chapter: React.PropTypes.object.isRequired
     hide: React.PropTypes.func.isRequired
     selected: React.PropTypes.array
+    expanded: React.PropTypes.bool

   renderSections: (section) ->
     active = TaskPlanStore.hasTopic(@props.planId, section.id)
@@ -67,10 +68,10 @@
     @props.selected.indexOf(section.id) >= 0 or anySelected

   render: ->
-    {chapter, expand} = @props
+    {chapter, expanded} = @props
     sections = _.map(chapter.children, @renderSections)
     allChecked = _.reduce(chapter.children, @areAllSectionsSelected, true)
-    expandAccordion = _.reduce(chapter.children, @areAnySectionsSelected, false) or expand
+    expandAccordion = _.reduce(chapter.children, @areAnySectionsSelected, false) or expanded

     activeKey = chapter.id if expandAccordion

@@ -101,8 +102,8 @@
     selected: React.PropTypes.array

   renderChapterPanels: (chapter, i) ->
-    expand = not @props.selected?.length and i is 0
-    <ChapterAccordion {...@props} expand={expand} chapter={chapter}/>
+    expanded = not @props.selected?.length and i is 0
+    <ChapterAccordion {...@props} expanded={expanded} chapter={chapter}/>

   renderDialog: ->
     {courseId, planId, selected, hide, header, primary} = @props


Remove url from config.

Remove url from config.

url already defaults to this

--- app/js/publisher_poc/publisher_plugin.user.coffee
+++ app/js/publisher_poc/publisher_plugin.user.coffee
@@ -15,7 +15,6 @@
   api: "http://localhost:3000"
   lib: "http://localhost:8000/lib/dist"
   srcPath: "/factlink.core.js"
-  url: window.location.href
   minified: ''

 # testserverConfig =


Fix asynchronous tests

Fix asynchronous tests

--- test/test.coffee
+++ test/test.coffee
@@ -5,20 +5,22 @@
 {assert} = require 'chai'

 describe "API Blueprint parser", ->
-  it 'parses API name', ->
+
+  it 'parses API name', (done) ->
     protagonist.parse '# My API', (err, result) ->
-
-      assert.isNull err
+      assert.isNull err
+
       assert.isDefined result
       assert.strictEqual result.ast.name, 'My API'

-  it 'parses API description', ->
+      done()
+
+  it 'parses API description', (done) ->
     source = """
 **description**

 """
     protagonist.parse source, (err, result) ->
-
       assert.isNull err

       assert.isDefined result
@@ -26,16 +28,15 @@
       assert.isDefined result.ast.description
       assert.strictEqual result.ast.description, '**description**\n'

-  it 'parses resource group', ->
-    source = """
----
-
+      done()
+
+  it 'parses resource group', (done) ->
+    source = """
 # Group Name
 _description_

 """
     protagonist.parse source, (err, result) ->
-
       assert.isNull err

       assert.isDefined result.ast.resourceGroups
@@ -48,25 +49,19 @@
       assert.isDefined resourceGroup.description
       assert.strictEqual resourceGroup.description, '_description_\n'

-  it 'parses resource', ->
+      done()
+
+  it 'parses resource', (done) ->
     source = """
 # My Resource [/resource]
 Resource description

-+ Headers
-
-        X-Resource-Header: Metaverse
-
-+ Resource Object (text/plain)
++ Model (text/plain)

         Hello World

 ## Retrieve Resource [GET]
 Method description
-
-+ Headers
-
-        X-Method-Header: Pizza delivery

 + Response 200 (text/plain)

@@ -88,7 +83,7 @@

 + Response 200

-    [Resource][]
+    [My Resource][]

 """

@@ -114,13 +109,10 @@
       assert.strictEqual resource.description, 'Resource description\n\n'
       assert.isDefined resource.headers

-      assert.strictEqual Object.keys(resource.headers).length, 1
-      assert.isDefined resource.headers['X-Resource-Header']
-      assert.isDefined resource.headers['X-Resource-Header'].value
-      assert.strictEqual resource.headers['X-Resource-Header'].value, 'Metaverse'
+      assert.strictEqual Object.keys(resource.headers).length, 0

       assert.isDefined resource.model
-      assert.strictEqual resource.model.name, 'Resource'
+      assert.strictEqual resource.model.name, 'My Resource'
       assert.isDefined resource.model.description
       assert.strictEqual resource.model.description.length, 0
       assert.isDefined resource.model.body
@@ -143,10 +135,7 @@
       assert.isDefined action.description
       assert.strictEqual action.description, 'Method description\n\n'
       assert.isDefined action.headers
-      assert.strictEqual Object.keys(action.headers).length, 1
-      assert.isDefined action.headers['X-Method-Header']
-      assert.isDefined action.headers['X-Method-Header'].value
-      assert.strictEqual action.headers['X-Method-Header'].value, 'Pizza delivery'
+      assert.strictEqual Object.keys(action.headers).length, 0

       assert.isDefined action.examples
       assert.strictEqual action.examples.length, 1
@@ -204,7 +193,9 @@
       assert.isDefined response.headers['Content-Type'].value
       assert.strictEqual response.headers['Content-Type'].value, 'text/plain'

-  it 'fails to parse blueprint with tabs', ->
+      done()
+
+  it 'fails to parse blueprint with tabs', (done) ->
     source = """
 # /resource
 # GET
@@ -219,7 +210,9 @@
       assert err.code != 0
       assert.isDefined err.location

-  it 'parses blueprint with warnings', ->
+      done()
+
+  it 'parses blueprint with warnings', (done) ->
     source = """
 API description

@@ -229,7 +222,6 @@

 """
     protagonist.parse source, (err, result) ->
-
       assert.isNull err

       assert.isDefined result.warnings
@@ -241,7 +233,9 @@

       assert.isDefined result.ast

-  it 'parses blueprint metadata', ->
+      done()
+
+  it 'parses blueprint metadata', (done) ->
     source = """
 A: 1
 B: 2
@@ -250,7 +244,6 @@
 # API Name
 """
     protagonist.parse source, (err, result) ->
-
       assert.isNull err

       assert.isDefined result.warnings
@@ -273,7 +266,9 @@
       assert.isDefined metadata.C.value
       assert.strictEqual metadata.C.value, '3'

-  it 'accepts options', ->
+      done()
+
+  it 'accepts options', (done) ->
     source = """
 **description**

@@ -289,7 +284,9 @@
       assert err.code != 0
       assert.isDefined err.location

-  it 'parses resource parameters', ->
+      done()
+
+  it 'parses resource parameters', (done) ->
     source = """
 # /machine{?limit}

@@ -304,6 +301,7 @@

 + Response 204
 """
+
     protagonist.parse source, (err, result) ->
       assert.isNull err

@@ -347,7 +345,9 @@
       assert.isDefined values[2]
       assert.strictEqual values[2], "53"

-  it 'parses action parameters', ->
+      done()
+
+  it 'parses action parameters', (done) ->
     source = """
 # GET /coupons/{id}

@@ -390,3 +390,4 @@
       assert.isDefined action.parameters.id.values
       assert.strictEqual action.parameters.id.values.length, 0

+      done()


remove console.log

remove console.log

--- app/assets/javascripts/autocomplete.coffee
+++ app/assets/javascripts/autocomplete.coffee
@@ -131,7 +131,6 @@

 $(document).ready(ready)
 $(document).on('mousedown', (event) ->
-  console.log('mouse down')
   return if (
     event.button == 2 ||
     $(event.target).hasClass('js-autocomplete-suggestion-item') ||


Fix loot drop

Fix loot drop

--- app/scripts/lazerscripts/stage1/stage1boss.coffee
+++ app/scripts/lazerscripts/stage1/stage1boss.coffee
@@ -41,6 +41,8 @@

   execute: ->
     @bindSequence 'Hit', @fase2, => @entity.health < 150000
+    @inventoryAdd 'item', 'lasers', ->
+      Crafty.e('PowerUp').powerUp(contains: 'lasers', marking: 'L')

     @sequence(
       @animate 'slow', -1, 'eye'
@@ -162,6 +164,8 @@

   execute: ->
     @bindSequence 'Hit', @leaveScreen, => @entity.health < 133000
+    @inventoryAdd 'item', 'lasers', ->
+      Crafty.e('PowerUp').powerUp(contains: 'lasers', marking: 'L')

     @sequence(
       @animate 'slow', -1, 'eye'


export top-level React object from app file

--- app/assets/javascripts/components/app.cjsx
+++ app/assets/javascripts/components/app.cjsx
@@ -1,8 +1,11 @@
 # @cjsx React.DOM

-# React = require 'react'
+React = require 'react'
 {Router, Routes, Route, Link} = require 'react-router'
 DynamicRouter = require './dynamic-router'
+
+# IMPORTANT!
+window.React = React

 App = React.createClass
   displayname: 'app'


Fixed waterfall

Fixed waterfall

--- app/assets/javascripts/d3/waterfall.coffee
+++ app/assets/javascripts/d3/waterfall.coffee
@@ -7,25 +7,28 @@

     can_be_shown_as_table: -> true

+    margins:
+      top: 20
+      bottom: 20
+      left: 20
+      right: 40
+
     draw: =>
-      margins =
-        top: 20
-        bottom: 80
-        left: 20
-        right: 40
+      @width  = @available_width()  - (@margins.left + @margins.right)
+      @height = @available_height() - (@margins.top + @margins.bottom)

-      @width = @$el.width() - (margins.left + margins.right)
-      @height = 360 - (margins.top + margins.bottom)
-      # height of the series section
-      @series_height = 190
+      labels_height = 90
+      labels_margin = 15
+      @series_height = @height - labels_height - labels_margin
       @series_width = @width - 15
+
       @column_width = @series_width / (@series.length + 1) * 0.6
       @svg = d3.select(@container_selector())
         .append("svg:svg")
-        .attr("height", @height + margins.top + margins.bottom)
-        .attr("width", @width + margins.left + margins.right)
+        .attr("height", @height + @margins.top + @margins.bottom)
+        .attr("width", @width + @margins.left + @margins.right)
         .append("svg:g")
-        .attr("transform", "translate(#{margins.left}, #{margins.top})")
+        .attr("transform", "translate(#{@margins.left}, #{@margins.top})")

       @y = d3.scale.linear().range([@series_height, 0]).domain([-10, 10])

@@ -58,7 +61,7 @@
       offset = @column_width / 2
       @svg.selectAll('.x_axis text')
         .attr('text-anchor', 'end')
-        .attr('transform', "rotate(-90) translate(-10, #{-offset})")
+        .attr('transform', "rotate(-90) translate(#{-labels_margin}, #{-offset})")

       @svg.selectAll('rect.serie')
         .data(@prepare_data(), (d) -> d.key)


UpdateDisaptch docs

UpdateDisaptch docs

--- app/coffee/services/update_dispatch.coffee
+++ app/coffee/services/update_dispatch.coffee
@@ -1,21 +1,21 @@

-# TODO - document
-# https://github.com/cordova-sms/cordova-sms-plugin
+# SMS Service sends texts on behalf of Tack
+# Used to send a ShareProfile to a new contact that has been
+# added manually in the application
+# Leverages the Cordova SMS plugin - https://github.com/cordova-sms/cordova-sms-plugin
 class SMSService extends Marionette.Service

   radioRequests:
     'sms send': 'send'

-  # # # # #
-
-  send: (number='5162876345', message='Hello from cordova') ->
+  # Sends text & message
+  send: (number, message) ->
     return new Promise (resolve, reject) =>

       # Options
       options =
         replaceLineBreaks: false
-        android: {} # => doesn't use default app
-          # intent: 'INTENT' # Uses default sms app
+        android: {}

       # Callbacks, resolves/rejects Promise
       onSuccess = -> return resolve()
@@ -25,26 +25,21 @@
       sms.send(number, message, options, onSuccess, onError)
       return true

-  # # # # #
+  # Ensures that the application has
+  # the approved permissions to send text messages
+  ensurePermission: ->
+    return new Promise (resolve, reject) =>

-  # TODO - should return a Promise, rename to ensurePermission()
-  # TODO - should be added to service
-  checkSMSPermission: ->
+    # Callbacks
+    onSuccess = (hasPermission) =>
+      return resolve() if hasPermission
+      return reject()

-    success = (hasPermission) =>
-      if hasPermission
-        console.log 'HAS SMS PERMISSION!'
-        @sendSms()
-      else
-        # show a helpful message to explain why you need to require the permission to send a SMS
-        # read http://developer.android.com/training/permissions/requesting.html#explain for more best practices
-      return
+    onError = (e) -> return reject(e)

-    error = (e) ->
-      alert('Something went wrong:' + e)
-      return
-
-    sms.hasPermission(success, error)
+    # Checks permissions
+    sms.hasPermission(onSuccess, onError)
+    return true

 # # # # #


hide events for appointments that a student hasn't signed up for

fixes #CNVS-1881

for calendar2
event still shows up on the calendar for students who are signed
up for the appointment and on the calendar for anyone who can
manage the appointment group

test plan
-as a teacher, create an appointment group
-as a student, sign up for a slot in the appoinment group
-as that same student, ensure that an event appears on the calendar
 for the appointment
-as a different student who is not signed up for the appointment
 group, ensure that an event does not appear on the calendar for
 the appointment
-as a teacher, ensure that an event appears on the calendar for
 the appointment

Change-Id: I458c849cd675dadf3384b545c1162f00905f6018
Reviewed-on: https://gerrit.instructure.com/16452
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Mark Ericksen <776ab05fc6b68e82d2b2c63873bf56edde131809@instructure.com>
QA-Review: Marc LeGendre <d33c80bc45d65303e33ca83108a9952b745af9ef@instructure.com>

--- app/coffeescripts/calendar/Calendar.coffee
+++ app/coffeescripts/calendar/Calendar.coffee
@@ -162,9 +162,10 @@
                 # If there is not a reserve_url set, then it is an
                 # actual, scheduled event and not just a placeholder.
                 keep = true
-              else if event.calendarEvent.child_events_count > 0 && !event.calendarEvent.reserved
+              else if event.calendarEvent.child_events_count > 0 && !event.calendarEvent.reserved && event.can_edit
                 # If this *is* a placeholder, and it has child events, and it's not reserved by me,
-                # that means people have signed up for it, so we want to display it.
+                # that means people have signed up for it, so we want to display it if I am able to
+                #  manage it (as a teacher or TA might)
                 keep = true
               else
                 keep = false


Fix typo

Fix typo

--- index.cjsx
+++ index.cjsx
@@ -2,9 +2,9 @@
 Promise = require 'bluebird'
 async = Promise.coroutine
 request = Promise.promisifyAll require 'request'
-REPORTER_VERSION = '2.2.1'
+REPORTER_VERSION = '2.2.2'

-if config.get('plugin.ShipInfo.enable', true)
+if config.get('plugin.Reporter.enable', true)
   # Quest
   knownQuests = []
   questReportEnabled = false


i18n ship name

i18n ship name

--- index.cjsx
+++ index.cjsx
@@ -130,7 +130,7 @@
     ship.hp.max = _ship.api_maxhp
     ship.lv = _ship.api_lv
     ship.cond = _ship.api_cond
-    ship.name = _ship.api_name
+    ship.name = window.i18n.resources.__ _ship.api_name
     ship.slot = _ship.api_slot.concat(_ship.api_slot_ex || -1)
     ship.owner = 0


Fix typo

Fix typo

--- index.cjsx
+++ index.cjsx
@@ -366,7 +366,7 @@
     componentWillUnmount: ->
       window.removeEventListener 'game.response', @handleResponse
     render: ->
-

+

         <link rel='stylesheet' href={join(__dirname, 'assets', 'expedition.css')} />
         <Grid>
           <Row>


Update leopard.coffee

Update leopard.coffee

--- app/lib/field-guide-content/leopard.coffee
+++ app/lib/field-guide-content/leopard.coffee
@@ -3,7 +3,7 @@
     The leopard is a medium-sized wildcat that is easily distinguished by its beautiful black-spotted coat. Spots are grouped into rosettes on the torso and upper limbs and appear smaller and more scattered on the lower limbs and head. There is large variation in coat color, pattern and body size. The leopard has a long, well-muscled body, thick, short limbs, broad paws, and a long tail. Like other feline species, the leopard has retractable claws.
   '''

-  scientificName: '(Panthera pardus)'
+  scientificName: 'Panthera pardus'
   mainImage: 'assets/fieldguide-content/mammals/leopard/leopard-feature.jpg'

   conservationStatus: 'Near Threatened' # Options are Least Concern, Near Threatened, Vulnerable, and Endangered; all have their own little icons as well.


QUALITY: Add warning to resolve load students issue

Sometimes unit.students isn't loaded

--- src/app/common/services/unit-service.coffee
+++ src/app/common/services/unit-service.coffee
@@ -97,6 +97,8 @@
       student?.enrolled

     unit.findStudent = (id) ->
+      unless unit.students?
+        throw Error "Students not yet mapped to unit (unit.students is undefined)"
       _.find unit.students, (s) -> s.project_id == id

     unit.addStudent = (student) ->


fix if statement

fix if statement

--- lib/atom-ternjs-type.coffee
+++ lib/atom-ternjs-type.coffee
@@ -133,7 +133,7 @@
         data.type = @manager.helper.formatType(data)
         type = data.type.substring(data.type.indexOf('(') + 1, data.type.length)
         matches = type.match(@manager.regExp.params)
-        if (matches?[matches.length - 1]).startsWith(' :')
+        if matches?[matches.length - 1].startsWith(' :')
           matches.splice(matches.length - 1)
         if matches?[paramPosition]
           offsetFix = if paramPosition > 0 then ' ' else ''


Rid execution permission

Rid execution permission


Added a id field which is based on the title.

--- src/Models/Recipe.coffee
+++ src/Models/Recipe.coffee
@@ -4,4 +4,6 @@
 #				instructions(str)
 #				tags(Tag)

-class Recipe extends Backbone.Model+class Recipe extends Backbone.Model
+	initialize: =>
+		@id = @get("title").toLowerCase().replace(/\ /, "-")


naming

naming

--- src/mixinfactory.spec.coffee
+++ src/mixinfactory.spec.coffee
@@ -1,4 +1,4 @@
-fdescribe 'mixinfactory', ->
+fdescribe 'mix.it.mixinfactory', ->

   Mixin = require './mixinfactory'


Correct init for download buttons on home page

--- app/assets/javascripts/controllers/home.js.coffee
+++ app/assets/javascripts/controllers/home.js.coffee
@@ -1,5 +1,5 @@
 initialiseFilterDownloads = ->
-  $downloadBtns = $('.download-type-dropdown a')
+  $downloadBtns = $('nav.sub-nav .download-type-dropdown a')
   return false if $downloadBtns.length == 0

   $downloadBtns.on('click', (e) ->
@@ -46,7 +46,9 @@

 ready = ->
   new ProtectedPlanet.Map($('#map')).render()
-  new ProtectedPlanet.Dropdown($('.btn-search-download'), $('.download-type-dropdown'))
+
+  new ProtectedPlanet.Dropdown($('.btn-search-download'), $('nav.sub-nav .download-type-dropdown'))
+  new ProtectedPlanet.Dropdown($('.btn-map-download'), $('main .download-type-dropdown'))

   setupSearchBar()
   setupFiltersConcertina()


OX-248 almost working

OX-248 almost working

--- app/assets/javascripts/oxalis/model/binary.coffee
+++ app/assets/javascripts/oxalis/model/binary.coffee
@@ -66,7 +66,7 @@
       @lastPosition = position.slice()
       @lastOptions = options.slice()

-      #console.log "ping", @queue.roundTripTime, @queue.bucketsPerSecond
+      console.log "ping", @queue.roundTripTime, @queue.bucketsPerSecond

       #console.time "ping"
       @queue.clear()


Start work on local move/rename

Start work on local move/rename

--- lib/models/synced-fs.coffee
+++ lib/models/synced-fs.coffee
@@ -8,6 +8,7 @@
       atom.workspace.open(file)

     @handleEvents()
+    @treeViewEventQueue = []

   handleEvents: ->
     atom.workspace.observeTextEditors (editor) =>
@@ -18,6 +19,8 @@
       editor.onDidSave =>
         if this.formatFilePath(file.path).match(/\.atom\/code/)
           @sendSave(project, file, buffer)
+      editor.onDidChangePath =>
+        console.log('PATH CHANGED')

     atom.commands.onDidDispatch (e) =>
       if e.type == 'tree-view:remove'
@@ -35,9 +38,32 @@
             path: this.formatFilePath(path)
           }
         })
-      else if e.type == 'tree-view:add-file' || e.type == 'tree-view:add-folder' || e.type == 'core:confirm'
-        true
-        # No good way yet to handle creations until a file is written
+      else if e.type == 'tree-view:add-file'
+        console.log(e)
+        # Add event to queue
+      else if e.type == 'tree-view:move'
+        window.currentEvent = e
+        @treeViewEventQueue.push
+          type: 'move'
+          event: e
+      else if e.type == 'core:cancel'
+        @treeViewEventQueue = []
+      else if e.type == 'core:confirm'
+        # do whatever's necessary to confirm event in queue
+
+        window.confirmedEvent = e
+        confirmedEvent = @treeViewEventQueue.shift()
+
+        switch confirmedEvent.type
+          when 'move'
+            event = confirmedEvent.event
+            from = event.target.getAttribute('data-name')
+            fromPath = event.target.getAttribute('data-path')
+          when 'addFile'
+            true
+        console.log(e)
+      else
+        console.log(e.type)

   sendSave: (project, file, buffer) ->
     ipc.send 'fs-local-save', JSON.stringify({


Mark non-required contests (fix #164)

Mark non-required contests (fix #164)

--- server/materials/data/mainLevels.coffee
+++ server/materials/data/mainLevels.coffee
@@ -1,4 +1,6 @@
 seedrandom = require('seedrandom')
+
+import isContestRequired from '../../../client/lib/isContestRequired'

 import label from "../lib/label"
 import level from "../lib/level"
@@ -225,6 +227,21 @@
         throw "Bad idx: #{idx}, #{problems.length}"
     return result

+requiredNote = (topics) ->
+    allRequired  = true
+    for topic in topics
+        t = topic()
+        if t.title
+            hasProblems = false
+            for m in t.submaterials || []
+                if m?()?.testSystem
+                    hasProblems = true
+            if hasProblems
+                allRequired = allRequired and isContestRequired(t.title)
+    if not allRequired
+        return " (кроме контестов со звездочкой)"
+    return ""
+
 minorLevel = (generator, id) ->
     allTopics = []
     topicsCount = 0
@@ -252,7 +269,7 @@
         return null
     return {
         level: level(id, [
-                label("
Чтобы перейти на следующий уровень, надо решить все задачи.
"),
+                label("
Чтобы перейти на следующий уровень, надо решить все задачи#{requiredNote(allTopics)}.
"),
                 allTopics...
             ]),
         advancedTopics: allAdvancedTopics
@@ -274,7 +291,7 @@
     advancedTopics = [(levelA?.advancedTopics || [])..., (levelB?.advancedTopics || [])..., contestsFromAdvancedProblems(id, advancedProblems)...]
     if advancedTopics.length != 0
         subLevels.push level("#{id}В", [
-            label("
Чтобы перейти на следующий уровень, надо решить минимум половину задач. Когда вы их решите, я рекомендую вам переходить на следующий уровень, чтобы не откладывать изучение новой теории. К оставшимся задачам этого уровня возвращайтесь позже время от времени и постарайтесь со временем все-таки дорешать почти все их до конца.
"),
+            label("
Чтобы перейти на следующий уровень, надо решить минимум половину задач#{requiredNote(advancedTopics)}. Когда вы их решите, я рекомендую вам переходить на следующий уровень, чтобы не откладывать изучение новой теории. К оставшимся задачам этого уровня возвращайтесь позже время от времени и постарайтесь со временем все-таки дорешать почти все их до конца.
"),
             advancedTopics...
         ])
     if "#{id}Г" of ADDITIONAL_LEVELS


fix refresh slow in some cases

fix refresh slow in some cases

--- server/processes/mailbox_refresh.coffee
+++ server/processes/mailbox_refresh.coffee
@@ -25,6 +25,7 @@
             @refreshFast (err) =>
                 if err and err is MailboxRefreshFast.algorithmFailure
                     log.warn "refreshFast fail, trying deep"
+                    @options.storeHighestModSeq = true
                     @refreshDeep callback
                 else if err
                     callback err


Make OFXPlayer more coffeescripty.

Make OFXPlayer more coffeescripty.

--- application/chrome/content/wesabe/download/OFXPlayer.coffee
+++ application/chrome/content/wesabe/download/OFXPlayer.coffee
@@ -42,13 +42,12 @@
     # tell the user we're logging in
     @job.update('auth.creds')

-    @buildRequest().requestAccountInfo({
+    @buildRequest().requestAccountInfo
       success: (response) =>
         @onGetAccounts(response)

       failure: (response) =>
         @onGetAccountsFailure(response)
-    })

   #
   # Handles the response containing the account list.
@@ -59,7 +58,7 @@
     @accounts = wesabe.taint(response.accounts)
     wesabe.debug('accounts=', @accounts)

-    if !@accounts.length
+    if @accounts.length == 0
       wesabe.warn('There are no accounts! This might not be right...')

     # start downloading accounts in serial
@@ -85,7 +84,7 @@

     job.update('account.download')
     wesabe.tryThrow 'OFXPlayer#processAccounts', (log) =>
-      if !@accounts.length
+      if @accounts.length == 0
         # no more accounts, we're done
         job.succeed()
         return
@@ -120,7 +119,7 @@
     else
       args = ["Skipping account=", @account]
     wesabe.warn.apply(wesabe, args)
-    delete @account;
+    delete @account
     @processAccounts()

   #
@@ -137,7 +136,7 @@
         return
       else if response.ofx.isAuthorizationError()
         this.job.fail(403, 'auth.noaccess')
-        return;
+        return
       # doh! didn't recognize any status
     else
       # wow, this wasn't even an OFX error, it was some sort of


Debug

Debug

--- src/caching/index.coffee
+++ src/caching/index.coffee
@@ -9,6 +9,9 @@
 root.cacheExpired = (cacheUntil, now=new Date) ->
   if typeof(cacheUntil) == 'string'
     cacheUntil = new Date(cacheUntil + " +00")
+
+  console.log cacheUntil
+  console.log now

   cacheUntil < now


Fix location parsing

Fix location parsing

--- lib/view/body/parser.coffee
+++ lib/view/body/parser.coffee
@@ -12,10 +12,10 @@
 parseOccurence = (str) ->
     result = str.match regexOccurence
     if result
-        rowStart = if parseInt result[2] then parseInt result[2] else parseInt result[6]
-        rowEnd   = if parseInt result[4] then parseInt result[4] else parseInt result[6]
-        colStart = if parseInt result[3] then parseInt result[3] else parseInt result[7]
-        colEnd   = if parseInt result[5] then parseInt result[5] else parseInt result[8]
+        rowStart = if parseInt result[3] then parseInt result[3] else parseInt result[7]
+        rowEnd   = if parseInt result[5] then parseInt result[5] else parseInt result[7]
+        colStart = if parseInt result[4] then parseInt result[4] else parseInt result[8]
+        colEnd   = if parseInt result[6] then parseInt result[6] else parseInt result[9]
         range = new Range [rowStart - 1, colStart - 1], [rowEnd - 1, colEnd - 1]

         body: result[1]


(minor) whitespacing

(minor) whitespacing

--- js-library/app/js/loader/loader_common.coffee
+++ js-library/app/js/loader/loader_common.coffee
@@ -8,8 +8,8 @@

 storedMethodCalls = []

-storedMethodFactory = (name) -> ->
-  storedMethodCalls.push(name: name, arguments: arguments) && undefined
+storedMethodFactory = (name) ->
+  -> storedMethodCalls.push(name: name, arguments: arguments) && undefined

 for name in methods
   window.FACTLINK[name] =


DS errors are managed by custom methods, with a fallback if not defined

--- src/client/widget.coffee
+++ src/client/widget.coffee
@@ -94,9 +94,6 @@

       renderError: ->

-      dataError: ->
-        console.log(arguments)
-
       log: (msg)=>
         if @options.debug
           console.warn(@options.name, ":", @options.id, msg)
@@ -104,6 +101,8 @@
           console.warn("[DEBUG] #{@options.name}", msg, @)

       buildContext: =>
+        onDataError = (datasourceName, err)->
+          console.log "An error occurred with datasource #{datasourceName}", err
         @_renderCount ?= 0
         @_renderCount++
         ret       = {}
@@ -111,15 +110,16 @@
         try
           keys      = _.keys(@datasources)
           promises  = _.map keys, (k)=>
-            dfd = @sandbox.data.deferred()
+            promiseDfd = @sandbox.data.deferred()
             ds = @datasources[k]
             ds.parse(_.extend({}, @, @options || {}))
             ds.fetch().then (res)->
-              dfd.resolve(res)
+              promiseDfd.resolve(res)
             , (err)=>
-              dfd.resolve(false)
-              @dataError k, err
-              dfd
+              handler = @["on#{_.string.capitalize(_.string.camelize(k))}Error"]
+              handler = onDataError.bind(@, k) unless _.isFunction(handler)
+              promiseDfd.resolve(handler err)
+            promiseDfd

           widgetDeferred = @sandbox.data.when.apply(undefined, promises)
           templateDeferred = @sandbox.template.load(@templates, @ref)


Add comments

Add comments

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -10,9 +10,11 @@
   transitionAnimation: true
   speed: 0.3

-  #
-  #
-  #
+  # Public: Instantiate Flight and set any options.
+  #
+  # options - A Hash of options for flight.
+  #
+  # Returns nothing.
   constructor: (options = {}) ->
     @[key] = value for key, value of options

@@ -32,9 +34,9 @@
     document.addEventListener "webkitTransitionEnd", (e) =>
       @hideTransitionedPage e.target

-  #
-  #
-  #
+  # Private: Get a Hash of browser user agent information.
+  #
+  # Returns a Hash of user agent information.
   detectUserAgent: ->
     userAgent = window.navigator.userAgent
     @os = {}
@@ -44,9 +46,6 @@
       result = userAgent.match(/Android (\d+(?:\.\d+)+)/)
       @os.version = result[1]

-  #
-  #
-  #
   setupForAndroid: ->
     head = document.getElementsByTagName('head')[0]
     androidCSS = document.createElement 'link'
@@ -62,17 +61,16 @@
     document.body.className = "old-android"
     @transitionAnimation = false

-  #
-  #
-  #
   hideUrlBar: ->
     setTimeout ->
       window.scrollTo 0, 1
     , 50

-  #
-  #
-  #
+  # Public: Go to a specific page.
+  #
+  # targetPage - A String of the element ID or existing element.
+  #
+  # Returns nothing.
   goto: (targetPage) =>
     hook() for hook in @hooks['before:goto']

@@ -123,9 +121,9 @@

     hook() for hook in @hooks['after:goto']

-  #
-  #
-  #
+  # Private: Perform a slide transition.
+  #
+  # Returns nothing.
   slide: (targetPage, currentPage) ->
     targetPage.style.display = "-webkit-box"

@@ -151,9 +149,9 @@
       @back = false
     , 0

-  #
-  #
-  #
+  # Private: Perform a slide from bottom transition.
+  #
+  # Returns nothing.
   slideUp: (targetPage, currentPage) ->
     targetPage.style.display = "-webkit-box"
     screenHeight = window.innerHeight + 'px'
@@ -171,17 +169,22 @@

     @back = false

-  #
-  #
-  #
+  # Private: Translate page on a specified axis.
+  #
+  # page     - An Element of the page.
+  # axis     - A String of the axis.
+  # distance - A String of the distance.
+  # duration - A String of the duration, defaults to speed.
+  #
+  # Returns nothing.
   translate: (page, axis, distance, duration) ->
     duration = @speed + "s" unless duration?
     page.style.webkitTransition = "#{duration} cubic-bezier(.10, .10, .25, .90)"
     page.style.webkitTransform = "translate#{axis}(#{distance})"

-  #
-  #
-  #
+  # Private: Show the current page without transition
+  #
+  # Returns nothing.
   displayPage: (targetPage, currentPage) ->
     targetPage.style.display = "-webkit-box"
     currentPage.style.display = "none"
@@ -191,9 +194,11 @@

     if @back is true then @back = false

-  #
-  #
-  #
+  # Private: Hide DOM that has just been transitioned
+  #
+  # page    - The page element that has just been moved outside of view
+  #
+  # Returns nothing
   hideTransitionedPage: (page) =>
     if @hasClass(page,'page')
       page.style.display = "none" unless page.id is @targetPage.id
@@ -201,45 +206,48 @@
     if @isAndroid() and @os.version < '4'
       @currentPage.style.webkitTransform = "none"

-  #
-  #
-  #
+  # Private: Check if element has a class
+  #
+  # el        - DOM element to be checked
+  # cssClass  - A string of the class name
+  #
+  # Returns true if element has the specified class and false if not
   hasClass: (el, cssClass) ->
     if el.className isnt ''
       el.className && new RegExp("(^|\\s)#{cssClass}(\\s|$)").test(el.className)
     else
       false

-  #
-  #
-  #
+  # Private: Is the device touch enabled.
+  #
+  # Returns True if the device is touch enabled, else False.
   isTouch: ->
     if @isAndroid()
       !!('ontouchstart' of window)
     else
       window.Touch?

-  #
-  #
-  #
+  # Public: Is the device running iOS
+  #
+  # Returns True if the device is running iOS, else False.
   isIOS: ->
     @os.ios

-  #
-  #
-  #
+  # Public: Is the device running Android
+  #
+  # Returns True if the device is running Android, else False.
   isAndroid: ->
     @os.android

-  #
-  #
-  #
+  # Public: Get the version of the OS running on the device.
+  #
+  # Returns a String of the OS version.
   osVersion: ->
     @os.version.toString()

-  #
-  #
-  #
+  # Private: Handle touch events to apply pressed class to anchors
+  #
+  # Returns nothing.
   handleEvents: (e) =>
     if @isTouch()
       switch e.type
@@ -254,9 +262,6 @@
         when 'mousedown'
           @onTouchStart e

-  #
-  #
-  #
   onTouchStart: (e) =>
     if @isTouch()
       if @isAndroid()
@@ -280,21 +285,12 @@
     @theTarget.addEventListener 'mouseup', @onTouchEnd, false
     @theTarget.addEventListener 'touchcancel', @onTouchcancel, false

-  #
-  #
-  #
   onTouchMove: (e) =>
     @theTarget.className = @theTarget.className.replace(/( )? pressed/gi, '')

-  #
-  #
-  #
   onTouchEnd: (e) =>
     @theTarget.className = @theTarget.className.replace(/( )? pressed/gi, '')

-  #
-  #
-  #
   onTouchCancel: (e) =>
     @theTarget.className = @theTarget.className.replace(/( )? pressed/gi, '')


Use env vars to configure the sub-app container

--- src/application.coffee
+++ src/application.coffee
@@ -138,17 +138,11 @@
 	return if !app.imageId?
 	volumes =
 		'/data': {}
-		'/lib/modules': {}
-		'/lib/firmware': {}
-		'/run/dbus': {}
 	binds = [
 		'/resin-data/' + app.appId + ':/data'
-		'/lib/modules:/lib/modules'
-		'/lib/firmware:/lib/firmware'
-		'/run/dbus:/run/dbus'
-		'/run/dbus:/host_run/dbus'
-		'/etc/resolv.conf:/etc/resolv.conf:rw'
 	]
+	netMode = 'bridge'
+	privileged = false
 	Promise.try ->
 		# Parse the env vars before trying to access them, that's because they have to be stringified for knex..
 		JSON.parse(app.env)
@@ -158,6 +152,13 @@
 			.split(',')
 			.map((port) -> port.trim())
 			.filter(isValidPort)
+
+		if env.MULTIVISOR_BINDS?
+			binds = _.union(binds, env.MULTIVISOR_BINDS.split(','))
+		if env.MULTIVISOR_NETWORK_MODE?
+			netMode = env.MULTIVISOR_NETWORK_MODE
+		if env.MULTIVISOR_PRIVILEGED == '1'
+			privileged = true

 		if app.containerId?
 			# If we have a container id then check it exists and if so use it.
@@ -211,8 +212,8 @@
 				portList.forEach (port) ->
 					ports[port + '/tcp'] = [ HostPort: port ]
 			container.startAsync(
-				Privileged: true
-				NetworkMode: 'host'
+				Privileged: privileged
+				NetworkMode: netMode
 				PortBindings: ports
 				Binds: binds
 			)


Remove socket.io

Remove socket.io

--- src/application.coffee
+++ src/application.coffee
@@ -3,7 +3,6 @@
 body_parser           = require 'body-parser'
 errorhandler          = require 'errorhandler'
 http                  = require 'http'
-socketio              = require 'socket.io'
 path                  = require 'path'
 orm                   = require 'orm'
 Cookies               = require 'cookies'
@@ -25,14 +24,12 @@
 # development only
 app.use errorhandler() if 'development' == app.get('env')

-
 orm.connect "mysql://root:@localhost/rdio_sync", (err, database) ->
   throw err if err?
   AccountTable = database.define 'accounts', Account.schema

   database.sync (error) ->
     server = http.Server(app)
-    io     = socketio server

     # Instantiate controllers
     accounts_controller     = new AccountsController account_table: AccountTable, io: io


base_linter: unknown level now returns the rule name

If you generate an unknown level error, it now returns the name of the
rule that has the bad level setting.

--- src/base_linter.coffee
+++ src/base_linter.coffee
@@ -19,13 +19,12 @@
     # Create an error object for the given rule with the given
     # attributes.
     createError: (ruleName, attrs = {}) ->
-
         # Level should default to what's in the config, but can be overridden.
         attrs.level ?= @config[ruleName].level

         level = attrs.level
         if level not in ['ignore', 'warn', 'error']
-            throw new Error("unknown level #{level}")
+            throw new Error("unknown level #{level} for rule: #{ruleName}")

         if level in ['error', 'warn']
             attrs.rule = ruleName
@@ -46,7 +45,7 @@
                 if @acceptRule(rule)
                     @rules.push rule
             else if level isnt 'ignore'
-                throw new Error("unknown level #{level}")
+                throw new Error("unknown level #{level} for rule: #{rule}")

     normalizeResult: (p, result) ->
         if result is true


Fix type id

Fix type id

--- app/api/article.coffee
+++ app/api/article.coffee
@@ -70,9 +70,10 @@
   photo = req.files.photo
   image.resize photo, 640
   .then () ->
+    console.log req.body
     template =
       group: req.user.group.id
-      type: parseInt param req, 'type'
+      type: parseInt(param(req, 'type'))
       name: param req, 'name'
       description: param req, 'description'
       canAdd: param req, 'canAdd'


Updated routes.coffee

Updated routes.coffee

removed navigation from a few screens

--- app/both/routes.coffee
+++ app/both/routes.coffee
@@ -16,6 +16,7 @@

   @route "splash",
     path: "/splash"
+    layoutTemplate: "splash"

   @route "controls",
     path: "/controls"
@@ -23,6 +24,7 @@

   @route "remote",
     path: "/remote"
+    layoutTemplate: "remote"

   @route "mobile",
     path: "/mobile"


added comment describing graph initialization

--- app/coffee/main.coffee
+++ app/coffee/main.coffee
@@ -115,6 +115,7 @@
       .links graph.links
       .start()

+    # pre-renders the graph, then freezes the nodes
     setTimeout ->
       node.fixed = true for node in graph.nodes
       setTimeout(->


Remove debug code and correctly set the URL loop flag

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -122,8 +122,6 @@
     catch e
       return

-    console.log @$loop, @
-
     @$loop.toggleButton (if options.loop then "on" else "off"), silent: true

     @playAnimation bounceObject: bounce, updateURL: false
@@ -154,7 +152,7 @@

   _encodeURL: (serialized) ->
     encoded = {}
-    encoded.l = 1 if @$loop.prop("checked")
+    encoded.l = 1 if @$loop.toggleButton("isOn")
     encoded.s = for options in serialized
       shortKeys = {}
       for key, value of options


Split keywords into smaller groupings according to role.

--- grammars/powershell.cson
+++ grammars/powershell.cson
@@ -124,8 +124,13 @@
     'name': 'constant.numeric.integer.decimal.powershell'
   }
   {
+    'comment': 'keywords that delimit special blocks'
+    'match': '\\b(?i:(begin|data|dynamicparam|end|filter|inlinescript|parallel|process|sequence|workflow))\\b'
+    'name': 'keyword.control.flow.powershell'
+  }
+  {
     'comment': 'keywords that delimit flow blocks'
-    'match': '\\b(?i:(begin|catch|data|do|dynamicparam|else|elseif|end|filter|finally|for|foreach|from|if|in|inlinescript|parallel|param|process|sequence|switch|trap|try|until|while|workflow))\\b'
+    'match': '\\b(?i:(catch|do|else|elseif|finally|for|foreach|if|in|switch|trap|try|until|while))\\b'
     'name': 'keyword.control.flow.powershell'
   }
   {
@@ -151,7 +156,7 @@
       '1':
         'name': 'keyword.other.powershell'
     'comment': 'keywords that haven\'t fit into other groups (yet).'
-    'match': '\\b(?i:(param))\\b'
+    'match': '\\b(?i:(from|param))\\b'
   }
   {
     'match': '\\+\\=|-\\=|\\*\\=|/\\=|//\\=|%\\=|&\\=|\\|\\=|\\^\\=|>>\\=|<<\\=|\\*\\*\\='


Core#send

Core#send

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -153,7 +153,7 @@
       for Module in modules
         Module = Module.constantize() if Module.isString?()
         self.extend Module(I, self)
-
+
       return self

     send: (name, args...) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -148,7 +148,8 @@
           Module = Module.constantize()
         else
           for key, value of root
-            if value is
+            if value is Module
+              module

         unless I.included_modules.include moduleName
           I.included_modules.push moduleName


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -95,7 +95,7 @@
     else if namespace
       # No event given
       # Select only the callbacks that do not have this namespace metadata
-      # for any
+      # for any events bound
       for key, callbacks of eventCallbacks
         eventCallbacks[key] = callbacks.select (callback) ->
           !callback.__PIXIE?[namespace]?


Print start/end time of program

Print start/end time of program

--- src/chinachu.coffee
+++ src/chinachu.coffee
@@ -31,7 +31,13 @@
   userAgent: process.env.HUBOT_CHINACHU_USERAGENT
 });

-format_program_data = (data) -> "#{data.channel.name} - #{data.title} (#{data.id})"
+format_program_data = (data) ->
+  zp2 = (num) -> ("0" + num).slice -2
+  start = new Date data.start
+  startStr = "#{zp2 (start.getMonth()+1)}/#{zp2 start.getDate()} #{zp2 start.getHours()}:#{zp2 start.getMinutes()}"
+  end = new Date data.end
+  endStr = "#{zp2 end.getHours()}:#{zp2 end.getMinutes()}"
+  return "#{data.channel.name} #{startStr}-#{endStr} - #{data.title} (#{data.id})"

 module.exports = (robot) ->
   robot.respond /chinachu\s+now$/i, (msg) ->


Fix use strict in expression

Fix use strict in expression

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

 topLevelExpressionInModule = (compiledDefinitions) -> (ctx, expression) ->
   (iife (concat [
-    toJsString 'use strict;'
+    toJsString 'use strict'
     compiledDefinitions
     [(jsReturn (topLevelExpression ctx, expression))]]))


Fixed compilation of quoted consts from current module

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -3500,9 +3500,10 @@
     type = -> mapOrigin (freshInstance ctx, (ctx.typeInTopScope symbol)), atom
     fromCurrentModule = atom.builtinDefinition or
       (modulePathsEqual atom.modulePath, ctx.typedModulePath.names)
+    atomIsConst = isConst atom
     if fromCurrentModule
       if ctx.assignTo()
-        if isConst atom
+        if atomIsConst
           type: type()
           pattern: constPattern ctx, symbol, (jsAccess '_', validSymbol)
         else
@@ -3515,7 +3516,12 @@
           nameCompile ctx, atom, symbol
         else
           type: type()
-          translation: (jsAccess '_', validSymbol)
+          translation:
+            # TODO: really need to clean this up using nameCompile
+            ((if atomIsConst
+                constTranslate
+              else
+                id) (jsAccess '_', validSymbol))
     else
       moduleName = (modulePathToName atom.modulePath)
       # TODO: pretty big hack as we are referencing lookupCompiledModule from
@@ -3524,7 +3530,7 @@
       forallType = (lookupInMap referencedModule, symbol).type
       type: mapOrigin (freshInstance ctx, forallType), atom
       translation:
-        ((if isConst atom
+        ((if atomIsConst
             constTranslate
           else
             id) (irImportInline symbol, moduleName))


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -10,7 +10,7 @@

   cooldown: (name, options={}) ->
     options.target ||= 0
-    options.approachBy unless options.approachBy
+    options.approachBy = 1unless options.approachBy

     unless I.cooldowns[name]
       I.cooldowns[name] = options


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -10,6 +10,9 @@
       target: 100

     player.update(1)
+
+    player.I.health
+

     # Shoot Timeout
     player = GameObject()


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -15,6 +15,7 @@
     unless I.cooldowns[name]
       I.cooldowns[name] = {
         options.target
+        options.
       }


limit infoHashTable

limit infoHashTable

--- src/dht-crawler.coffee
+++ src/dht-crawler.coffee
@@ -21,8 +21,7 @@
 	findInterval: 2 * 60 * 1000
 	infoHashTimeout: 30 * 60 * 1000
 	maxRouteTableLen: 1000
-	maxQueryNodesLen: 100
-	maxAnnounceNodesLen: 1000
+	maxInfoHashTableLen: 15000

 module.exports =
 	class DHTCrawler extends events.EventEmitter
@@ -205,15 +204,16 @@
 						else
 							@addInfoHash infoHash

-					for i in [0..peersBin.length-1] by 6
-						ipStr = nodesBin[i] + "." + nodesBin[i + 1] + "." + nodesBin[i + 2] + "." + nodesBin[i + 3]
-						port = nodesBin.readUInt16BE(i + 4)
-						addressStr = ipStr + ':' + port
-
-						torrent.announceNodes.insert addressStr
-						@addPeer infoHash, torrent, addressStr
-
-					torrent.lastActive = Date.now()
+					if torrent != undefined
+						for i in [0..peersBin.length-1] by 6
+							ipStr = nodesBin[i] + "." + nodesBin[i + 1] + "." + nodesBin[i + 2] + "." + nodesBin[i + 3]
+							port = nodesBin.readUInt16BE(i + 4)
+							addressStr = ipStr + ':' + port
+
+							torrent.announceNodes.insert addressStr
+							@addPeer infoHash, torrent, addressStr
+
+						torrent.lastActive = Date.now()
 				else if resp.nodes != undefined
 					@addNodes resp.nodes

@@ -325,8 +325,9 @@
 				else
 					@addInfoHash infoHashStr

-			torrent.queryNodes.insert args.id.toString 'hex'
-			torrent.lastActive = Date.now()
+			if torrent != undefined
+				torrent.queryNodes.insert args.id.toString 'hex'
+				torrent.lastActive = Date.now()

 			nodesBin = @getClosestNodesBin args.info_hash

@@ -353,16 +354,17 @@
 				else
 					@addInfoHash infoHashStr

-			ipStr = address.ip
-			port = if args.implied_port != undefined and args.implied_port == 1
-				address.port
-			else
-				args.port
-			addressStr = ipStr + ':' + port
-
-			torrent.announceNodes.insert addressStr
-			torrent.lastActive = Date.now()
-			@addPeer infoHashStr, torrent, addressStr
+			if torrent != undefined
+				ipStr = address.ip
+				port = if args.implied_port != undefined and args.implied_port == 1
+					address.port
+				else
+					args.port
+				addressStr = ipStr + ':' + port
+
+				torrent.announceNodes.insert addressStr
+				torrent.lastActive = Date.now()
+				@addPeer infoHashStr, torrent, addressStr

 			resp =
 				t: message.t
@@ -402,7 +404,6 @@
 			allNodes = @routeTable.toArray()

 			if allNodes.length > @options.maxRouteTableLen
-				console.log 'route table is overload'
 				return

 			existNode = @routeTable.get node.id
@@ -426,6 +427,9 @@

 		addInfoHash: (infoHash) ->
+			if @infoHashTable.length > @options.maxInfoHashTableLen
+				return undefined
+
 			torrent =
 				queryNodes: hll()
 				announceNodes: hll()


update

update

--- udo_framer/MODHome201805v2.framer/app.coffee
+++ udo_framer/MODHome201805v2.framer/app.coffee
@@ -243,7 +243,8 @@
 				ArrowBarBG.visible=false
 				scrollMainPage.scrollToPoint(x:0,y:500)
 		item.on "focus",->
-			showBarkerImage(MainBtnsPageMappingDict[this.name]['value'])
+			if this.name.indexOf("First")>=0
+				showBarkerImage(MainBtnsPageMappingDict[this.name]['value'])
 			#print this.name
 # 			if this.name.indexOf("First")>=0
 # 				this.visible=true


drop cache on reset

drop cache on reset

--- chrome/content/zotero-better-bibtex/debug-bridge.coffee
+++ chrome/content/zotero-better-bibtex/debug-bridge.coffee
@@ -40,6 +40,7 @@
   Zotero.Collections.erase((coll.id for coll in Zotero.getCollections()))
   Zotero.BetterBibTeX.keymanager.reset(true)
   Zotero.Items.emptyTrash()
+  Zotero.DB.query('delete from betterbibtex.cache')

   err = JSON.stringify((item.toArray() for item in Zotero.BetterBibTeX.safeGetAll()))
   throw "reset failed -- Library not empty -- #{err}" unless Zotero.DB.valueQuery('select count(*) from items') == 0


failtimer duration increased for couldnt connect to backend modal

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -16,7 +16,7 @@

   constructor:(options = {}, data)->

-    options.failWait  = 5000            # duration in miliseconds to show a connection failed modal
+    options.failWait  = 10000            # duration in miliseconds to show a connection failed modal

     super options, data


client: landing: clarified event name

client: landing: clarified event name

--- client/landing/site.landing/coffee/teams/AppView.coffee
+++ client/landing/site.landing/coffee/teams/AppView.coffee
@@ -33,7 +33,7 @@
       cssClass : 'login-form'
       callback : (formData) ->

-        track 'submitted signup form', { category: 'TeamSignUp' }
+        track 'started team signup', { category: 'TeamSignUp' }

         finalize = (email) ->
           utils.storeNewTeamData 'signup', formData


Enemies go flying upside down.

Enemies go flying upside down.

--- src/entities.coffee
+++ src/entities.coffee
@@ -203,8 +203,7 @@
                 return true
             when me.collision.types.ENEMY_OBJECT
                 if response.overlapV.y > 0 and !@body.jumping
-                    other.body.setCollisionMask(me.collision.types.NO_OBJECT)
-                    me.game.world.removeChild(other)
+                    other.die()
                     # bounce (force jump)
                     @body.falling = false
                     @body.vel.y = -@body.maxVel.y
@@ -259,22 +258,57 @@
         @body.collisionType = me.collision.types.WORLD_SHAPE
         @body.addShape(new me.Rect(0, 0, settings.width, settings.height))
         @body.setMaxVelocity(0,0)
-        @timeTilSpawn = 10
+        @timeTilSpawn = 50
         @renderable.flipX(@facing)
     update: (dt) ->
         if @timeTilSpawn-- < 0
             [x, y] = [@getRx(), @getRy()]
             dx = (if @facing then -32 else 32)
-            me.game.world.addChild(new game.PotFrog(x + dx, y))
-            @timeTilSpawn = 10
+            vx = dx / 32 * 4
+            if Math.random() > .5
+                me.game.world.addChild(new game.PotFrog(x + dx, y, vx))
+            else
+                me.game.world.addChild(new game.Chicken(x + dx, y, vx))
+            @timeTilSpawn = 50
+}
+game.DeadMonster = me.Entity.extend {
+    init: (x, y, settings) ->
+        @_super(me.Entity, 'init', [x, y, settings])
+        @renderable.flipY(true)
+        @body.vel.y = 9
+        @body.setCollisionMask(me.collision.types.NO_OBJECT)
+        @renderable.addAnimation('stand', [settings.frame or 0])
+        @renderable.setCurrentAnimation('stand')
+        @alwaysUpdate = true
+    update: (dt) ->
+        @body.update(dt)
+        if @pos.y > 10000
+            me.game.world.removeChild(@)
 }

 game.Monster = me.Entity.extend {
+    getRx: () ->
+        {pos, width} = @getBounds()
+        return Math.round(0.5 + pos.x + @anchorPoint.x * (width - @renderable.width))
+
+    getRy: () ->
+        {pos, height} = @getBounds()
+        return Math.round(0.5 + pos.y + @anchorPoint.y * (height - @renderable.height))
+
+    die: () ->
+        [x, y] = [@getRx(), @getRy()]
+        mon = new game.DeadMonster(x, y, @settings)
+        me.game.world.addChild(mon)
+        @body.setCollisionMask(me.collision.types.NO_OBJECT)
+        me.game.world.removeChild(@)
+        return mon
+
     init: (x, y, settings) ->
         # call the parent constructor
         @_super(me.Entity, 'init', [x, y, settings])
         @walkLeft = false
         # walking & jumping speed
+        @settings = settings
         @body.setVelocity 4, 6
         @body.addShape(new me.Rect(0, 0, settings.width, settings.height))
         @body.collisionType = me.collision.types.ENEMY_OBJECT
@@ -306,13 +340,29 @@
 }

 game.PotFrog = game.Monster.extend {
-    init: (x, y) ->
+    init: (x, y, vx) ->
         settings = {
             image: 'potfrog'
             width: 32, height: 32
             spritewidth: 32, spriteheight: 48
-        }
+        }
         @_super(game.Monster, 'init', [x, y, settings])
+        @renderable.translate(0, -8)
+        @body.vel.x = vx
+}
+
+game.Chicken = game.Monster.extend {
+    init: (x, y, vx) ->
+        settings = {
+            image: 'monsters'
+            width: 32, height: 32
+            frame: 1 # For dead monster
+            spritewidth: 32, spriteheight: 32
+        }
+        @_super(game.Monster, 'init', [x, y, settings])
+        @renderable.addAnimation('stand', [1])
+        @renderable.setCurrentAnimation('stand')
+        @body.vel.x = vx
 }

 ###*


check address is set

check address is set

--- src/core/javascripts/controllers/map.js.coffee
+++ src/core/javascripts/controllers/map.js.coffee
@@ -61,7 +61,7 @@

     $scope.mapBounds = new google.maps.LatLngBounds()
     for comp in $scope.companies
-      if comp.address.lat && comp.address.long
+      if comp.address and comp.address.lat and comp.address.long
         latlong = new google.maps.LatLng(comp.address.lat,comp.address.long)
         $scope.mapBounds.extend(latlong)

@@ -79,7 +79,7 @@
   # load map and create the map markers. the map is hidden at this point
   $scope.map_init.then ->
     for comp in $scope.companies
-      if comp.address.lat && comp.address.long
+      if comp.address and comp.address.lat and comp.address.long
         latlong = new google.maps.LatLng(comp.address.lat,comp.address.long)
         marker = new google.maps.Marker({
           map: $scope.myMap,


Revert "Prevent tour wizard button from disappearing"

This reverts commit 685e422eb1c9897cf1f9a4143a8845a1d186c3ae.

--- src/views/feature-tour-start.coffee
+++ src/views/feature-tour-start.coffee
@@ -14,7 +14,7 @@
             'click .close-button' : 'hideTour'
             'click .prompt-button' : 'showTour'
         hideTour: (ev) ->
-            p13n.set 'hide_tour', false
+            p13n.set 'hide_tour', true
             @trigger 'close'
             ev.stopPropagation()
         showTour: (ev) ->


fix learn substitution response

fix learn substitution response

--- src/factoids.coffee
+++ src/factoids.coffee
@@ -96,9 +96,12 @@
     key = msg.match[1]
     re = new RegExp(msg.match[2], msg.match[4])
     fact = factoids.get key
-    value = fact.value.replace re, msg.match[3]
+    value = fact?.value.replace re, msg.match[3]

-    msg.reply factoids.set key, value, msg.message.user.name
+    if value?
+      msg.reply factoids.set key, value, msg.message.user.name
+    else
+      msg.reply 'Not a factoid'

   robot.respond /forget (.{3,})/i, (msg) ->
     msg.reply factoids.forget msg.match[1]


console.log with error message on unsuccessful login.

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -104,14 +104,20 @@
       method: 'POST'
       headers:
         'Content-Type': 'application/x-www-form-urlencoded',
-        'Content-Length': post_data.length
+        'Content-Length': post_data.length,
+        'Accept': 'text/javascript'

     req = https.request options, (res) =>
       @cookies = res.headers["set-cookie"].map((cookie) ->
         cookie.split(";")[0]
       )
       res.on "end", () =>
-        @emit "login"
+        switch res.statusCode
+          when 200, 302
+            @emit "login"
+          else
+            console.log "ERROR: Unsuccessful login. Exiting."
+            process.exit(0)

     req.write(post_data)
     req.end()


Checking only valid items are elapsed.

Checking only valid items are elapsed.

--- src/environment.coffee
+++ src/environment.coffee
@@ -14,8 +14,8 @@
         defaults =
             density : 10
             gravitationalConstant : 3
-            width : 0
-            height : 0
+            width : 800
+            height : 600

         ((@[k] = v) for k, v of timber.defaults(opts, defaults))

@@ -36,7 +36,7 @@
     # @param element {Array} the element to update
     # @param milliseconds {Number} the number of ms that have elapsed
     elapse: (elements, milliseconds) ->
-        (@applyForces(e, milliseconds) for e in elements)
+        (@applyForces(e, milliseconds) for e in elements when e)
         return this

     # Apply the environment's forces to the element for the given number of


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -18,7 +18,7 @@
   self.bind 'update', ->
     I.flickerDuration = I.flickerDuration.approach(0, 1)

-    if I.flickerDuration > 0 && (I.age / I.flickerFrequency).floor() % 2
+    if I.flickerDuration && (I.age / I.flickerFrequency).floor() % 2
       I.alpha = I.flickerAlpha
     else
       I.alpha = originalAlpha


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -4,7 +4,7 @@
     player = GameObject
       alpha: 0.9

-    player
+    player.include 'Flickerab'

 @name Flickerable
 @module


Track which events and callbacks are being listened to

--- layout.coffee
+++ layout.coffee
@@ -51,7 +51,7 @@
     listenTo: (obj, ev, callback) ->
       obj.bind(ev, callback)
       @listeningTo or= []
-      @listeningTo.push obj
+      @listeningTo.push {obj: obj, ev: ev, callback: callback}
       this

     listenToOnce: (obj, ev, callback) ->
@@ -71,8 +71,8 @@
           idx = listeningTo.indexOf(obj)
           listeningTo.splice(idx, 1) unless idx is -1
       else
-        for obj in @listeningTo
-          obj.unbind()
+        for {obj, ev, callback} in @listeningTo
+          obj.unbind(ev, callback)
         @listeningTo = undefined

     unbind: (ev, callback) ->


Extract Default Modules

Extract Default Modules

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -165,7 +165,8 @@
   "Drawable"
   "Expirable"
   "Follow"
-  Metered", Movable, Rotatable, TimedEvents, Tween]
+  "Metered"
+  "Movable",Rotatable, TimedEvents, Tween]

 ###*
 Construct an object instance from the given entity data.


Use the native matrix css string methods

--- src/primitives/matrix.coffee
+++ src/primitives/matrix.coffee
@@ -3,24 +3,20 @@

 WebKitCSSMatrix::cssValues = ->

-	# Math.round is a lot faster then toFixed
-
-	# Rounding is very important, because javascript sometimes
-	# noted numbers with e and css really does not like that.
-
-	# r = (v) -> v.toFixed 5
-	r = (v) -> utils.round v, 5
-
-	values = "matrix3d(
-#{r @m11}, #{r @m12}, #{r @m13}, #{r @m14},
-#{r @m21}, #{r @m22}, #{r @m23}, #{r @m24},
-#{r @m31}, #{r @m32}, #{r @m33}, #{r @m34},
-#{r @m41}, #{r @m42}, #{r @m43}, #{r @m44})"
+	@toString()

+	# r = (v) -> utils.round v, 5
+	#
+	# values = "matrix3d(
+	# 	#{r @m11}, #{r @m12}, #{r @m13}, #{r @m14},
+	# 	#{r @m21}, #{r @m22}, #{r @m23}, #{r @m24},
+	# 	#{r @m31}, #{r @m32}, #{r @m33}, #{r @m34},
+	# 	#{r @m41}, #{r @m42}, #{r @m43}, #{r @m44})"

 class Matrix

 	constructor: (matrix) ->
+
 		if matrix instanceof WebKitCSSMatrix
 			@from matrix

@@ -60,16 +56,18 @@

 	@define "rotateY",
 		get: -> @_rotateY or 0
-		set: (value) -> @_rotateY = value
+		set: (value) ->
+			@_rotateY = value

 	@define "rotateZ",
 		get: -> @_rotateZ or 0
-		set: (value) -> @_rotateZ = value
+		set: (value) ->
+			@_rotateZ = value

 	@define "rotate",
 		get: -> @rotateZ
-		set: (value) -> @rotateZ = value
-
+		set: (value) ->
+			@rotateZ = value

 	decompose: (m) ->

@@ -93,6 +91,21 @@
 			z: -Math.atan2(m.m21/result.scale.y, m.m11/result.scale.x)

 		return result
+
+		# Requires: https://raw.github.com/joelambert/morf/master/
+		#	js/src/WebkitCSSMatrix.ext.js
+		#
+		# d = m.decompose()
+		#
+		# result = {}
+		#
+		# result =
+		# 	translation: d.translate
+		# 	scale: d.scale
+		# 	rotation: d.rotate
+		#
+		# return result
+

 	from: (matrix) ->

@@ -113,6 +126,7 @@
 	matrix: ->
 		m = new WebKitCSSMatrix()
 		m = m.translate @_x, @_y, @_z
+		# m = m.rotate @_rotateX, @_rotateY, @_rotateZ
 		m = m.rotate @_rotateX, 0, 0
 		m = m.rotate 0, @_rotateY, 0
 		m = m.rotate 0, 0, @_rotateZ
@@ -124,4 +138,4 @@
 		view._matrix = @

-exports.Matrix = Matrix
+exports.Matrix = Matrix


Adding response for 'cute me'

Adding response for 'cute me'

--- src/hello-world.coffee
+++ src/hello-world.coffee
@@ -1,9 +1,6 @@
 module.exports = (robot) ->
-  robot.respond /hello/, (msg) ->
-    msg.reply "hello!"
-
-  robot.hear /orly/, ->
-    msg.send "yarly"
+  robot.respond /cute me/, (msg) ->
+    cuteMe(msg)

   cuteMe = (msg) ->
     msg.http("http://api.dailycute.com.net/v1/posts/all")


Don't set defaults here.

Don't set defaults here.

--- src/honeybadger.coffee
+++ src/honeybadger.coffee
@@ -91,8 +91,8 @@
       name: opts['name'],
       fingerprint: opts['fingerprint'],
       context: opts['context'],
-      component: opts['component'] || @configuration.component,
-      action: opts['action'] || @configuration.action
+      component: opts['component'],
+      action: opts['action']
     }, @configuration)

     (if handler(notice) == false then return false) for handler in @beforeNotifyHandlers


change default font to inconsolata-g

change default font to inconsolata-g

--- config.cson
+++ config.cson
@@ -5,6 +5,7 @@
     showOnStartup: false
   editor:
     invisibles: {}
+    fontFamily: " Inconsolata-g, Menlo, Consolas, 'DejaVu Sans Mono', monospace"
   tabs:
     usePreviewTabs: true
   core: {}


Convert indentation to spaces

Convert indentation to spaces

--- src/kissmetrics.coffee
+++ src/kissmetrics.coffee
@@ -24,7 +24,7 @@
 # requests made in Node and in a browser.

 httpRequest = (url) ->
-	if NODEJS is on then https.get url else (new Image()).src = url
+  if NODEJS is on then https.get url else (new Image()).src = url

 # ## Kissmetrics Client
@@ -47,125 +47,125 @@
 # ```

 class KissmetricsClient
-	constructor: (@key, @person) ->
-		@host        = 'trk.kissmetrics.com'
-		@port        = 80
-		@query_types =
-			record : 'e'
-			set    : 's'
-			alias  : 'a'
+  constructor: (@key, @person) ->
+    @host        = 'trk.kissmetrics.com'
+    @port        = 80
+    @query_types =
+      record : 'e'
+      set    : 's'
+      alias  : 'a'

-	# ### Record
-	# ----------
+  # ### Record
+  # ----------

-	# Record an "event" in Kissmetrics.
-	# http://support.kissmetrics.com/apis/common-methods#record
-	#
-	# ##### Arguments
-	#
-	# `action` (String): Name of the event you're recording. This is
-	#   usually something a person did or something that affects them.
-	#
-	# `properties` (Object) *Optional*: Properties to associate with
-	#   the person's event. Keys will be used as property names and values
-	#   as property values.
-	#
-	# ```
-	# km.record('Signed up', {page: 'home'})
-	# ```
+  # Record an "event" in Kissmetrics.
+  # http://support.kissmetrics.com/apis/common-methods#record
+  #
+  # ##### Arguments
+  #
+  # `action` (String): Name of the event you're recording. This is
+  #   usually something a person did or something that affects them.
+  #
+  # `properties` (Object) *Optional*: Properties to associate with
+  #   the person's event. Keys will be used as property names and values
+  #   as property values.
+  #
+  # ```
+  # km.record('Signed up', {page: 'home'})
+  # ```

-	record: (action, properties = {}) ->
-		properties._n = action
-		@_generateQuery 'record', properties
+  record: (action, properties = {}) ->
+    properties._n = action
+    @_generateQuery 'record', properties

-	# ### Set
-	# -------
+  # ### Set
+  # -------

-	# Set a "property" in Kissmetrics.
-	# http://support.kissmetrics.com/apis/common-methods#set
-	#
-	# ##### Arguments
-	#
-	# `properties` (Object): Properties to associate with
-	#   the person's event. Keys will be used as property names and values
-	#   as property values.
-	#
-	# This behaves exactly like the `properties` argument in `record` except
-	# that it is required and that each property has to be sent in its own query.
-	#
-	# ```
-	# km.set({location: 'San Francisco', gender: 'male'})
-	# ```
+  # Set a "property" in Kissmetrics.
+  # http://support.kissmetrics.com/apis/common-methods#set
+  #
+  # ##### Arguments
+  #
+  # `properties` (Object): Properties to associate with
+  #   the person's event. Keys will be used as property names and values
+  #   as property values.
+  #
+  # This behaves exactly like the `properties` argument in `record` except
+  # that it is required and that each property has to be sent in its own query.
+  #
+  # ```
+  # km.set({location: 'San Francisco', gender: 'male'})
+  # ```

-	set: (properties) ->
-		for name, value of properties
-			data       = {}
-			data[name] = value
-			@_generateQuery 'set', data
+  set: (properties) ->
+    for name, value of properties
+      data       = {}
+      data[name] = value
+      @_generateQuery 'set', data

-	# ### Alias
-	# ---------
+  # ### Alias
+  # ---------

-	# Alias a person to another "identity" in Kissmetrics.
-	# http://support.kissmetrics.com/apis/common-methods#alias
-	#
-	# ##### Arguments
-	#
-	# `to` (String): A new identifier to map to the `@person` set on
-	# the current instance.
-	#
-	# ```
-	# km.alias('evan+newemail@example.com')
-	# ```
+  # Alias a person to another "identity" in Kissmetrics.
+  # http://support.kissmetrics.com/apis/common-methods#alias
+  #
+  # ##### Arguments
+  #
+  # `to` (String): A new identifier to map to the `@person` set on
+  # the current instance.
+  #
+  # ```
+  # km.alias('evan+newemail@example.com')
+  # ```

-	alias: (to) ->
-		@_generateQuery 'alias', _n: to
+  alias: (to) ->
+    @_generateQuery 'alias', _n: to

-	# ### Generate Query
-	# #### (Private)
-	# ------------------
+  # ### Generate Query
+  # #### (Private)
+  # ------------------

-	# Prepare data to be sent to Kissmetrics by turning it into a URL path
-	# and query string. Once the query is formed, call `record()` to send
-	# it to Kissmetrics.
-	#
-	# ##### Arguments
-	#
-	# * `type` (String): Type of data being sent (`record`, `set` or `alias`).
-	#
-	# * `data` (Object): Specific data being recorded about this person.
+  # Prepare data to be sent to Kissmetrics by turning it into a URL path
+  # and query string. Once the query is formed, call `record()` to send
+  # it to Kissmetrics.
+  #
+  # ##### Arguments
+  #
+  # * `type` (String): Type of data being sent (`record`, `set` or `alias`).
+  #
+  # * `data` (Object): Specific data being recorded about this person.

-	_generateQuery: (type, data) ->
-		data._k = @key
-		data._p = @person
+  _generateQuery: (type, data) ->
+    data._k = @key
+    data._p = @person

-		queryParts = for key, val of data
-			key = encodeURIComponent key
-			val = encodeURIComponent val
-			"#{key}=#{val}"
+    queryParts = for key, val of data
+      key = encodeURIComponent key
+      val = encodeURIComponent val
+      "#{key}=#{val}"

-		queryString = queryParts.join '&'
+    queryString = queryParts.join '&'

-		@_request "#{@query_types[type]}?#{queryString}"
+    @_request "#{@query_types[type]}?#{queryString}"

-	# ### Request
-	# #### (Private)
-	# --------------
+  # ### Request
+  # #### (Private)
+  # --------------

-	# Query the Kissmetrics API
-	#
-	# ##### Arguments
-	#
-	# `endpoint` (String): URL path (without a leading slash) that will be used
-	#   as a Kissmetrics API endpoint.
+  # Query the Kissmetrics API
+  #
+  # ##### Arguments
+  #
+  # `endpoint` (String): URL path (without a leading slash) that will be used
+  #   as a Kissmetrics API endpoint.

-	_request: (endpoint) ->
-		httpRequest "https://#{@host}:#{@port}/#{endpoint}"
+  _request: (endpoint) ->
+    httpRequest "https://#{@host}:#{@port}/#{endpoint}"

 # ### Exports


I don't even know Coffeescript anymore

I don't even know Coffeescript anymore

--- src/legal-eagle.coffee
+++ src/legal-eagle.coffee
@@ -152,7 +152,9 @@
     else if licenseText.toLocaleLowerCase().indexOf('public domain')  > -1
       'Public Domain'

-  {license ? 'UNKNOWN', source: licenseFileName, sourceText: licenseText}
+  license ?= 'UNKNOWN'
+
+  {license, source: licenseFileName, sourceText: licenseText}

 readIfExists = (path) ->
   readFileSync(path, 'utf8') if existsSync(path)


change comment in fixture

change comment in fixture

--- src/lib/fixture.coffee
+++ src/lib/fixture.coffee
@@ -67,7 +67,7 @@

     ###*
-    inserts data in LoopBack
+    inserts data to datasource

     @param {Array} names list of fixture models to insert data
     @method insert
@@ -184,7 +184,7 @@

     ###*
-    inserts data in LoopBack
+    inserts data to datasource

     @method insert
     @return {Promise}
@@ -246,12 +246,12 @@
             obj = {}
             dataName = data.shift()

-            break if not dataName # dataNameが空文字列となっている行よりも下の行はみない
+            break if not dataName # omit reading all lines below the line whose dataName is empty

             for name, i in names
-                break if not name # titleが空文字列となっているカラムより右のカラムは見ない
+                break if not name # omit reading all columns at right side of the column whose title is empty
                 value = data[i]
-                value = Number(value) if value.match(/^[0-9]+$/) # 数字だけのカラムは数値として扱う
+                value = Number(value) if value.match(/^[0-9]+$/) # regard number-like values as a number
                 obj[name] = value

             objs[dataName] = obj


Added a functionality to remove a superNode when needed

--- src/public/models/node.coffee
+++ src/public/models/node.coffee
@@ -32,7 +32,7 @@

 		broadcastTimeout = 4000 # Wait for return messages after a node broadcasts that it has a token
 		tokenThreshhold = 1
-		superNodeSwitchThreshhold = 0.8 # Scaler
+		superNodeSwitchThreshhold = 0.5 # Scaler

 		# Constructs a new app.
 		#
@@ -71,6 +71,7 @@

 			setInterval(@_updateCoordinates, 2500)
 			setInterval(@_lookForBetterSupernode, 10000)
+			@staySuperNodeTimeout = null

 			@runBenchmark()

@@ -127,6 +128,7 @@
 			peer.die()
 			@_peers.remove(peer)
 			@trigger('peer.removed', peer)
+			@_triggerStaySuperNodeTimeout()

 		# Returns a peer specified by an id
 		#
@@ -183,7 +185,6 @@
 		#
 		setParent: ( peer, callback ) ->
 			peer.query('peer.requestParent', @id, ( accepted ) =>
-				console.log accepted
 				if accepted
 					@_parent?.emit('peer.abandonParent')
 					@_parent?.role = Peer.Role.None
@@ -207,7 +208,7 @@
 		addChild: ( peer ) ->
 			if peer is @_parent
 				@_parent = null
-
+			console.log peer.id
 			peer.role = Peer.Role.Child
 			if @getChildren().length > 4
 				_(@generateToken).defer()
@@ -219,6 +220,7 @@
 		#
 		removeChild: ( peer ) ->
 			peer.role = Peer.Role.None
+			@_triggerStaySuperNodeTimeout()

 		# Returns a child specified by an id
 		#
@@ -279,6 +281,26 @@
 			@trigger('setSuperNode', superNode) # App is listening
 			@broadcast('peer.setSuperNode', @id, superNode)
 			@isSuperNode = superNode
+			if superNode
+				@_triggerStaySuperNodeTimeout()
+			else
+				for sibling in @getSiblings()
+					@removeSibling(sibling)
+				@_pickParent()
+
+		_triggerStaySuperNodeTimeout: () =>
+			if @getChildren().length is 0
+				clearTimeout(@staySuperNodeTimeout)
+				@staySuperNodeTimeout = setTimeout( ( ) =>
+					@_superNodeTimeout()
+				, 20000)
+
+		_superNodeTimeout: () =>
+			console.log "Aantal kinderen: ", @getChildren().length
+			if @getChildren().length is 0
+				@setSuperNode(false)
+				clearTimeout(@staySuperNodeTimeout)
+

 		# Is called when a peer becomes a supernode
 		#
@@ -292,13 +314,16 @@
 				peer.isSuperNode = isSuperNode

 				if @isSuperNode
-					@addSibling(peer)
+					if isSuperNode
+						@addSibling(peer)
+					else
+						@removeSibling(peer)
 			else if @isSuperNode
 				peer = @connect(peerId)
 				peer.once('channel.opened', =>
 					@addSibling(peer)
 				)
-			else if _(@getPeers()).filter( ( peer ) -> peer.isSuperNode).length < 5
+			else if isSuperNode and _(@getPeers()).filter( ( peer ) -> peer.isSuperNode).length < 5
 				@connect(peerId)

 		# Attempts to emit to a peer by id. Unreliable.
@@ -371,11 +396,12 @@
 				when 'peers'
 					return _(@getChildren().concat(@getSiblings(), @getParent())).map( ( peer ) -> peer?.id )
 				when 'peer.requestParent'
-					child = @getPeer(args[0])
-					console.log "hier is mijn nieuwe kind", child
-					if child?
-						@addChild(child)
-						return true
+					if @isSuperNode
+						child = @getPeer(args[0])
+						console.log "hier is mijn nieuwe kind", child
+						if child?
+							@addChild(child)
+							return true
 					return false

 		# Is called when a node enters a network. This will either
@@ -416,20 +442,20 @@
 								peer.ping( ( latency ) =>
 									pingCount++
 									if pingCount is candidates.length
-										@_pickParent(peers)
+										@_pickParent()
 								)
 							)
 						) ( peer )
 			)

-		# Recursive function that attempts to pick a parent from an
-		# array of available peers. If a parent request is refused,
+		# Recursive function that attempts to pick a parent from all
+		# connected supernodes. If a parent request is refused,
 		# this function calls itself with all candidates that have
 		# not yet refused a parent request.
 		#
-		# @param candidates [Array<Peer>] an array of available peers
-		#
-		_pickParent: ( candidates ) =>
+		_pickParent: ( candidates = null ) =>
+			unless candidates?
+				candidates = _(@getPeers()).filter( ( p ) => p.isSuperNode )
 			if candidates.length > 0
 				candidates = _(candidates).sortBy( 'latency' )
 				candidate = candidates.shift()
@@ -578,19 +604,18 @@
 		#
 		_pickNewTokenOwner: ( ) ->
 			bestCandidateDistance = null
-			for candidate in @token.candidates
+			for candidate in @token.candidates ? []
 				if !bestCandidateDistance? or candidate.distance < bestCandidateDistance
 					bestCandidateDistance = candidate.distance
 					bestCandidate = candidate

 			console.log "Best Candidate is " + bestCandidate.nodeId + " with distance " + bestCandidateDistance
-
-			if bestCandidate.nodeId is @id
-				@setSuperNode(true)
-			else
-				@emitTo(bestCandidate.nodeId,'token.add', @token.serialize())
-				@token = null
-			return bestCandidate
+			if bestCandidate
+				if bestCandidate.nodeId is @id
+					@setSuperNode(true)
+				else
+					@emitTo(bestCandidate.nodeId,'token.add', @token.serialize())
+					@token = null

 		# Applies Vivaldi alghoritm. Calculates the coordinates of a node
 		#
@@ -611,7 +636,8 @@
 		_lookForBetterSupernode: () =>
 			siblings = @getSiblings()
 			children = @getChildren()
-			if @isSuperNode and siblings.length > 0 and children.length > 0
+			childrenLength = children.length
+			if @isSuperNode and siblings.length > 0 and childrenLength > 0
 				for child in children
 					bestCandidateDistance = null
 					for parent in siblings


Improved App.Utils.signatureIdentify(), take messages with more then 300 chars.

--- app/assets/javascripts/app/lib/app_post/utils.coffee
+++ app/assets/javascripts/app/lib/app_post/utils.coffee
@@ -250,9 +250,10 @@
   @signatureIdentify: (message, test = false) ->
     textToSearch = @html2text(message)

-    # count lines, if we do have lower the 10, ignore this
+    # count lines, if we do have lower the 8 or more then 300 chars ignore this
     textToSearchInLines = textToSearch.split("\n")
     if !test
+      return message if textToSearch.length < 300
       return message if textToSearchInLines.length < 8

     quote = (str) ->


Fix className bug introduced in previous commits

--- app/assets/javascripts/components/timeline/week.cjsx
+++ app/assets/javascripts/components/timeline/week.cjsx
@@ -52,6 +52,7 @@
         </span>
       )
     else
+      afterTimelineEnd = ''
       week_dates = (
         <span className='week__week-dates pull-right'>
           {dateCalc.start()} - {dateCalc.end()} {@props.meetings if @props.meetings}


Fix where clause typing

Fix where clause typing

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -75,7 +75,7 @@
     ['label', isLabel atom]
     ['string', /^"/.test symbol]
     ['char', /^\\/.test symbol]
-    ['regex', /^\/[^ \/]/.test symbol]
+    ['regex', /^\/[^\s\/]/.test symbol]
     ['const', /^[A-Z][^\s\.]*$/.test symbol] # TODO: instead label based on context
     ['paren', symbol in ['(', ')']]
     ['bracket', symbol in ['[', ']']]
@@ -964,9 +964,10 @@
           ctx.allBoundTypeVariables(),
           (findFree currentType),
           (substituteList ctx.substitution, matched.tea.constraints)
+        # log "assign type", name, printType currentType
         ctx.assignType name,
           if ctx.isAtDeferrableDefinition()
-            quantifyAll (addConstraints currentType, retainedConstraints)
+            quantifyUnbound ctx, (addConstraints currentType, retainedConstraints)
           else
             toForAll currentType
   # here I will create type schemes for all definitions
@@ -1094,6 +1095,7 @@
       paramTypeVars = map (-> ctx.freshTypeVariable star), params
       paramTypes = map (__ toForAll, toConstrained), paramTypeVars
       ctx.newLateScope()
+      ctx.bindTypeVariables (map (({name}) -> name), paramTypeVars)
       # log "adding types", (map _symbol, params), paramTypes
       ctx.declareTypes paramNames, paramTypes

@@ -1812,6 +1814,7 @@
     else
       atom.label = 'name'
       type = toConstrained ctx.freshTypeVariable star
+      ctx.bindTypeVariables [type.type.name]
       ctx.addToDefinedNames {name: symbol, type: type}
       type: type
       pattern:
@@ -2173,7 +2176,7 @@

 isName = (expression) ->
   throw new Error "Nothing passed to isName" unless expression
-  (isAtom expression) and (expression.symbol is '~' or /[^~"'\/].*/.test expression.symbol)
+  (isAtom expression) and (expression.symbol in ['~', '/', '//'] or /[^~"'\/].*/.test expression.symbol)

 isAtom = (expression) ->
   not (Array.isArray expression)


Fixed instance typing, instance constraints must be added to the declared method types

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1375,7 +1375,7 @@
       # Context reduction
       isDeclaredConstraint = (c) ->
         entailed = entail ctx, unifiedType.constraints, c
-      notDeclared = filter (__ _not, isDeclaredConstraint), checked = constraints #(substituteList ctx.substitution, constraints)
+      notDeclared = filter (__ _not, isDeclaredConstraint), constraints #(substituteList ctx.substitution, constraints)
       {success} = deferConstraints ctx, notDeclared, currentType, scopeIndex
       if success
         [deferredConstraints, retainedConstraints] = success
@@ -2182,7 +2182,8 @@
   for name, {arity, type} of values classDeclaration.declarations
     freshType = freshInstance ctx, type
     instanceSpecificType = substitute freshingSub, freshType
-    quantifiedType = quantifyUnbound ctx, instanceSpecificType
+    quantifiedType = quantifyUnbound ctx,
+      (addConstraints instanceSpecificType, freshInstanceType.constraints)
     # Prefix so the instance method doesn't shadow the class method
     prefixedName = instancePrefix instanceName, name
     # TODO: Check against redefing a method


Calling zero arity function

Calling zero arity function

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -4688,6 +4688,15 @@
       return fn;
     };""").join('\n\n') +
 """
+var _0 = function (fn) {
+  if (fn._ === 0 || !fn._ && fn.length === 0) {
+    return fn();
+  } else if (fn._ > 0 || !fn._ && fn.length > 0) {
+    return function (a) {
+      return _1(fn, a);
+    };
+  }
+}
 ;
 """


Autocomple types

Autocomple types

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1156,6 +1156,8 @@
         # throw new Error "type name #{atom.symbol} was not defined" unless kind
         malformed ctx, atom, "This type name has not been defined" if not isFake atom
         kindOfType = star
+      if isFake atom
+        atom.inferredType = yes
       withOrigin (if isFake atom
         ctx.freshTypeVariable kindOfType
       else
@@ -5739,6 +5741,31 @@
   else
     compiled.requires

+findAvailableTypes = (moduleName, inferredType) ->
+  {declared: {typeNames}} = lookupCompiledModule moduleName
+  {ctx} = contextWithDependencies reverseModuleDependencies moduleName
+  printed = (kind, name) ->
+    kindArity = kindFnNumArgs kind
+    type:
+      if kindArity is 0
+        name
+      else
+        "(#{name}#{Array(kindArity + 1).join ' '})"
+    docs: null # TODO: add docs
+  available = concatMaps typeNames, ctx._scope().typeNames
+  if inferredType and inferredType.name
+    available = filterMap ((name) -> name isnt inferredType.name), available
+    inferred = newMapWith 'inferred', type: printType inferredType
+  else
+    inferred = newMap()
+  values concatMaps inferred, mapMap printed, available
+
+kindFnNumArgs = (kind) ->
+  if kind is star
+    0
+  else
+    1 + kindFnNumArgs kind.to
+
 findMatchingDefinitions = (moduleName, reference) ->
   {declared: {savedScopes}} = lookupCompiledModule moduleName
   {ctx} = contextWithDependencies reverseModuleDependencies moduleName
@@ -7358,6 +7385,7 @@

 exports.compileTopLevel = compileTopLevel
 exports.compileExpression = compileExpression
+exports.findAvailableTypes = findAvailableTypes
 exports.findMatchingDefinitions = findMatchingDefinitions
 exports.findDocsFor = findDocsFor
 exports.astizeList = astizeList


don't crash when missing chemfile

don't crash when missing chemfile

--- src/lib/chem.coffee
+++ src/lib/chem.coffee
@@ -33,6 +33,7 @@
   # figure out the path to the user's chemfile
   if not (chemfile_compiler = compilerFromPath(chemfile_path))?
     console.error "Missing chemfile or unrecognized chemfile extension."
+    process.exit(-1)
     return null

   if chemfile_compiler.require?