make bubblesort a little less painful

make bubblesort a little less painful

--- graphs.coffee
+++ graphs.coffee
@@ -4,9 +4,11 @@
   bubble: """
 bubbleSort = ->
   VA.locals.swapped = true
+  VA.locals.y = VA.length
   while VA.locals.swapped
+    VA.locals.y--
     VA.locals.swapped = false
-    for x in [0...VA.length - 1]
+    for x in [0...VA.locals.y]
       VA.locals.x = x
       if VA.gt(x, x + 1)
         VA.swap(x, x + 1)


Add explicit BranchNodeModel.save to recurse over childrem

The base ContextNodeMode.save now performs a shallow clone of
`attributes`. This is prevent deep-copying instances. As a result
the BranchNodeModel extends this behavior to recurse over children
and _gather_ their publicAttributes.

--- coffee/cilantro/models/context.coffee
+++ coffee/cilantro/models/context.coffee
@@ -30,7 +30,8 @@
         # into the savedAttributes. This will must be called to make
         # the attributes visible (via toJSON) for upstream callers
         save: ->
-            @publicAttributes = c.dom.extend true, {}, @attributes
+            @publicAttributes = c._.clone @attributes
+            return

         validate: (attrs, options) ->
             try
@@ -80,6 +81,18 @@
                     if not (child instanceof ContextNodeModel)
                         child = children[i] = getContextNodeModel(child)
                     return child
+            return
+
+        save: ->
+            super
+            attrs = @publicAttributes
+            children = []
+            # Recurse on children to ensure no node instances are present
+            for child, i in attrs.children
+                if child instanceof ContextNodeModel
+                    child = child.publicAttributes
+                children.push(child)
+            attrs.children = children
             return

         toJSON: ->


use project_id for filter.

use project_id for filter.

--- coffee/services/queryEditState.coffee
+++ coffee/services/queryEditState.coffee
@@ -19,12 +19,19 @@

     ###
-     filter issues by searchField.text.
+     filter issues by searchField.text and item.project_id.
     ###
     listFilter: (item) =>
-      if @$scope.searchField.text.isBlank() then return true
-      return (item.id + "").contains(@$scope.searchField.text) or
-             item.name.toLowerCase().contains(@$scope.searchField.text.toLowerCase())
+      if @$scope.searchField.text.isBlank()
+        matchText = true
+      else
+        matchText = (item.id + "").contains(@$scope.searchField.text) or
+          item.name.toLowerCase().contains(@$scope.searchField.text.toLowerCase())
+      if item.project_id?
+        matchProject = item.project_id is @$scope.selected[0].id
+      else
+        matchProject = true
+      return matchText and matchProject

     ###


added newline to eof

added newline to eof

--- src/scripts/tvshow.coffee
+++ src/scripts/tvshow.coffee
@@ -76,4 +76,4 @@
             else
               msg.reply "I couldn't find TV show " + msg.match[1]
         else
-            msg.reply "Sorry, there was an error looking up your show"+            msg.reply "Sorry, there was an error looking up your show"


Properly prevent and check for common malicious errors

--- src/mathematical.coffee
+++ src/mathematical.coffee
@@ -1,5 +1,7 @@
 {Processor} = require('../build/Release/processor.node')
 _ = require 'underscore'
+_s = require 'underscore.string'
+Corrections = require './corrections'

 FORMAT_TYPES = ["svg", "png", "mathml"]

@@ -14,21 +16,40 @@
   constructor: (opts = {}) ->
     @config = _.extend(DEFAULT_OPTS, opts)

-    @processor = new Processor(@config)
+    throw new TypeError "maxsize must be an integer!" unless _.isNumber(@config["maxsize"])
+    throw new TypeError "maxsize cannot be less than 0!" if @config["maxsize"] < 0
+    throw new TypeError "format must be a string!" unless _.isString(@config["format"])
+    throw new TypeError "format type must be one of the following formats: #{FORMAT_TYPES.join(', ')}" unless _.contains(FORMAT_TYPES, @config["format"])
+
+    config = _.clone(@config)
+    config["format"] = _.indexOf(FORMAT_TYPES, @config["format"])
+
+    @processor = new Processor config

   render: (maths) ->
-    data_hash = @processor.process(maths)
-    switch @config['format']
-      when 'svg'
-        data_hash["svg"] = data_hash["svg"][@xml_header.length..-1] # remove starting <?xml...> tag
-        data_hash["svg"] = @svg_to_base64(data_hash["svg"]) if @config['base64']
-        data_hash
+    throw new TypeError "text must be a string!" unless _.isString(maths)
+    maths = _s.strip(maths)
+    throw new SyntaxError "text must be in itex format (`$...$` or `$$...$$`)!" unless _.isNull(maths.match(/\A\${1,2}/))

-    data_hash
+    maths = @apply_corrections(maths)
+
+    try
+      throw Error unless data_hash = @processor.process(maths)
+      switch @config['format']
+        when 'svg'
+          data_hash["svg"] = data_hash["svg"][@xml_header.length..-1] # remove starting <?xml...> tag
+          data_hash["svg"] = @svg_to_base64(data_hash["svg"]) if @config['base64']
+          return data_hash
+        when "png", "mathml"
+          return data_hash
+    catch e # an error in the C code, probably a bad TeX parse
+      console.error "#{e.message}: #{maths}"
+
+  apply_corrections: (maths) ->
+    Corrections.apply(maths)

   xml_header: ->
     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"

   svg_to_base64: (contents) ->
-    #"data:image/svg+xml;base64,#{Base64.strict_encode64(contents)}"
-    return contents
+    "data:image/svg+xml;base64,#{new Buffer(contents).toString('base64')}"


No localStorage on Chrome Apps, fixes #221

--- components/MigrateLocalStorage.coffee
+++ components/MigrateLocalStorage.coffee
@@ -30,6 +30,9 @@
     return graph

   migrateGraphs: (store) ->
+    # Don't use localStorage in Chrome App
+    return if typeof chrome isnt 'undefined' and chrome.storage
+
     try
       localStorage
     catch e


config: enable /WFGH

config: enable /WFGH

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -224,13 +224,6 @@
         access_log off;
       }

-      #{if environment isnt "dev" then "
-        location = /WFGH {
-          return 301 /;
-        }
-      " else ""
-      }
-
       # no need to send static file serving requests to webserver
       # serve static content from nginx
       location /a/ {


nginx: include existing mime.types file

nginx: include existing mime.types file

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -149,6 +149,11 @@
     when 'linux'  then 'use epoll;'
     else ''

+  for path in ['/etc/nginx/mime.types', '/usr/local/etc/nginx/mime.types']
+    continue  unless fs.existsSync path
+    mime_types = "include #{path};"
+    break
+
   config = """
   worker_processes #{if environment is "dev" then 1 else 16};
   master_process #{if environment is "dev" then "off" else "on"};
@@ -183,7 +188,7 @@
     sendfile on;

     # for proper content type setting, include mime.types
-    include #{if environment is 'dev' then '/usr/local/etc/nginx/mime.types;' else '/etc/nginx/mime.types;'}
+    #{mime_types}

     #{createUpstreams(workers)}


Small refactoring.

Small refactoring.

--- app/client/js/react/app.coffee
+++ app/client/js/react/app.coffee
@@ -43,34 +43,34 @@
           footer.component()

       getActivePage: (props) ->
-        switch routes.active
+        page = switch routes.active
           when routes.home then mySongs
           when routes.newSong then editSongPage
           when routes.songs then songsPage
           when routes.recentlyUpdatedSongs then recentlyUpdatedSongsPage
-          when routes.song
-            # TODO: Show all song versions, not just first.
-            # /beatles/let-it-be -> All songs published under this url.
-            # /beatles/let-it-be/yz525kwxli9s -> One song.
-            props.song = songsStore.songsByUrl[0]
-            songPage
-          when routes.mySong, routes.editSong
-            @getLocalSongPage props
+          when routes.song then @getPublishedSongPage props
+          when routes.mySong, routes.editSong then @getLocalSongPage props
           when routes.trash then trashPage
           when routes.about then aboutPage
-          when routes.me
-            return notFoundPage if !userStore.isLogged()
-            mePage
-          else notFoundPage
+          when routes.me then userStore.isLogged() && mePage
+        # 404 is handled in two places for a good reason.
+        # app.Storage can return 404, which is used by router, but app data can
+        # be changed anytime later, so that's why render has to check it again.
+        page || notFoundPage
+
+      getPublishedSongPage: (props) ->
+        # TODO: Show all song versions, not just first.
+        # /beatles/let-it-be -> All songs published under this url.
+        # /beatles/let-it-be/yz525kwxli9s -> One song.
+        props.song = songsStore.songsByUrl[0]
+        songPage

       getLocalSongPage: (props) ->
         song = userStore.songByRoute routes.active
         # Local song can be removed anytime, for example from different tab.
-        return notFoundPage if !song
+        return if !song
         props.song = song
-        switch routes.active
-          when routes.mySong then songPage
-          when routes.editSong then editSongPage
+        if routes.active == routes.mySong then songPage else editSongPage

       getPageClassName: (page) ->
         switch page


pass default sort options on through to query

--- client/lanes/models/Query.coffee
+++ client/lanes/models/Query.coffee
@@ -271,15 +271,16 @@
     reset: ->
         unless @defaultSort is false
             sort = @defaultSort or @fields.findWhere(visible: true).id
-            @setSortField( @fields.findWhere(id: sort), sortAscending: true, silent: true )
+            @setSortField( @fields.findWhere(id: sort), sortAscending: @sortAscending, silent: true )

         @clauses.reset([
             {query: this, available_fields: @fields, field: @initialField}
         ])

     setSortField: (field, options = {silent: false}) ->
+        options.sortAscending ?= (if @sortField is field then !@sortAscending else true)
         @set({
-            sortAscending: options.sortAscending || (if @sortField is field then !@sortAscending else true)
+            sortAscending: options.sortAscending
             sortField: field
         }, options)


Add more functional descriptors to GOST style symbol generator

--- src/symbol/gost/ic.coffee
+++ src/symbol/gost/ic.coffee
@@ -3,16 +3,52 @@
   DD: ['digital']

 purposes =
+  '1': ['invertor', 'repeater']
   '>': ['opamp']
   'ADC': ['adc']
-  'CMP': ['comparator', 'supervisor', 'reset']
+  'BUF': ['buffer']
+  'BUS': ['bus']
+  '*C': ['capacitor']
+  'CD': ['coder']
+  'COMP': ['comparator', 'supervisor', 'reset']
+  'CPU': ['cpu']
+  'CTR': ['counter']
+  '*D': ['diode']
   'DAC': ['dac']
-  'MCU': ['mcu', 'microcontroller']
-  'MPU': ['cpu', 'mpu', 'microprocessor']
+  'DC': ['decoder']
+  'DEV': ['device']
+  'DIV': ['divider']
+  'DM': ['demodulator']
+  'DPY': ['display']
+  'DRAM': ['ram', 'dram', 'dynamic']
+  'DX': ['demultiplexor']
+  'FIFO': ['fifo']
+  'G': ['generator']
+  '*L': ['inductor']
+  'M': ['memory']
+  'MD': ['modulator']
+  'MOD': ['modificator']
+  'MPU': ['mcu', 'microcontroller', 'microprocessor', 'mpu']
   'MUX': ['multiplexer']
+  'NVRAM': ['ram', 'nvram', 'mram', 'fram', 'non-volatile']
   'PLM': ['cpld', 'fpga']
+  'PPI': ['interface', 'programmable']
+  'PROM': ['rom', 'prom', 'programmable']
+  '*R': ['resistor']
+  'RAM': ['ram']
+  'RG': ['register']
+  'ROM': ['rom']
+  'RPROM': ['rom', 'rpprom', 'reprogrammable']
   'RTX': ['isolator', 'interface', 'transceiver']
+  'SM': ['adder']
+  'SRAM': ['ram', 'sram', 'static']
+  '*ST': ['regulator', 'stabilizer']
   '*STU': ['ac-dc', 'dc-dc', 'ldo', 'regulator']
+  'SUB': ['subtractor']
+  'SW': ['switch']
+  '*T': ['transistor']
+  'TH': ['hysteresis', 'threshold']
+  'X/Y': ['converter', 'sensor']

 updateElement = (element) ->
   unless element.keywords? then return


got a version of generate_key working...

almost ready to hand it off to chris.

--- src/pgp.iced
+++ src/pgp.iced
@@ -32,8 +32,7 @@

 #=================================================================

-generate_key = ({nbits, progress_hook, userid, passphrase}, cb) ->
-  console.log packet
+generate_key = ({nbits, progress_hook, userid}, cb) ->
   userIdString = (new packet.UserID()).write_packet(userid);
   await generate_raw_key { nbits, progress_hook }, defer key
   privKeyString = key.privateKey.string
@@ -52,8 +51,6 @@

     publicKeyString = privKey.privateKeyPacket.publicKey.data
     userid_buffer = new Buffer userid, 'utf8'
-    console.log publicKeyString
-    console.log publicKeyString.length

     bufs = [
       new Buffer [ 0x99 ],
@@ -67,10 +64,16 @@
     signature = (new packet.Signature()).write_message_signature(C.subpacket_types.issuer, hashData, privKey)
     payload = (which) -> which + userIdString + signature.openpgp

+    # We're not using this feature for now....
+    # privateKeyArmored = encoding.armor C.message_types.private_key, payload(privKeyString)
+
     ret =
-      privateKey : privKey
-      privateKeyArmored : encoding.armor C.message_types.private_key, payload(privKeyString)
       publicKeyArmored : encoding.armor C.message_types.public_key, payload(key.publicKey.string)
+      privateKeyObject :
+        signature : new Buffer signature.openpgp, 'binary'
+        userid : userid
+        privateKey : new Buffer privKeyString, 'binary'
+
   cb err, ret

 #=================================================================


[TEST] Added a test for empty variables

[TEST] Added a test for empty variables

--- test/variables.coffee
+++ test/variables.coffee
@@ -64,6 +64,9 @@
   it "renders", ->
     renderTest 'worked', '{{ test }}', test:'worked'

+  it "renders when empty", ->
+    renderTest '', '{{ }}'
+
   context "with filter", ->
     it "renders", ->
       MoneyFilter =


Remove NullGrammar class now in first-mate

--- src/null-grammar.coffee
+++ src/null-grammar.coffee
@@ -1,23 +0,0 @@
-Token = require './token'
-{Emitter} = require 'emissary'
-
-### Internal ###
-module.exports =
-class NullGrammar
-  Emitter.includeInto(this)
-
-  name: 'Null Grammar'
-  scopeName: 'text.plain.null-grammar'
-
-  getScore: -> 0
-
-  tokenizeLine: (line) ->
-    { tokens: [new Token(value: line, scopes: ['null-grammar.text.plain'])] }
-
-  tokenizeLines: (text) ->
-    lines = text.split('\n')
-    for line, i in lines
-      {tokens} = @tokenizeLine(line)
-      tokens
-
-  grammarUpdated: -> # noop


Time simulation on the server even when no events pending.

--- packages/landsofillusions/simulation/server.coffee
+++ packages/landsofillusions/simulation/server.coffee
@@ -17,9 +17,6 @@
   @simulateGameState: (gameStateDocument) ->
     console.log "Simulating game state", gameStateDocument?._id if LOI.debug

-    # Nothing to do if no events are pending.
-    return unless gameStateDocument.events.length
-
     # Calculate how much time has passed since game state was updated.
     elapsedRealTime = Date.now() - gameStateDocument.stateLastUpdatedAt.getTime()

@@ -28,7 +25,12 @@

     # What would be the game time now if we simulated for this duration?
     simulationEndGameTime = gameStateDocument.state.gameTime + elapsedGameTime
-
+
+    # Just update game time if no events are pending.
+    unless gameStateDocument.events.length
+      @_updateGameTime gameStateDocument, simulationEndGameTime
+      return
+
     # Keep processing events that happen before the end time.
     eventsProcessedCount = 0

@@ -63,8 +65,10 @@
         # Event was not processed so we stop here.
         break

-    # No need to update game state if no events were processed.
-    return unless eventsProcessedCount
+    # Just update game time if no events were processed.
+    unless eventsProcessedCount
+      @_updateGameTime gameStateDocument, simulationEndGameTime
+      return

     # Move game time to end time, unless the blocking event was earlier.
     gameStateDocument.state.gameTime = simulationEndGameTime unless earliestEvent.gameTime < simulationEndGameTime
@@ -83,6 +87,15 @@
         readOnlyState: gameStateDocument.readOnlyState
         events: gameStateDocument.events

+  @_updateGameTime: (gameStateDocument, gameTime) ->
+    console.log "Pushing game time forward.", gameTime, gameStateDocument?._id if LOI.debug
+
+    LOI.GameState.documents.update gameStateDocument._id,
+      $set:
+        'state.gameTime': gameTime
+        'readOnlyState.simulatedGametime': gameTime
+        stateLastUpdatedAt: new Date()
+
 # Initialize on startup.
 Document.startup ->
   # Simulate game state every 10 minutes.


Fixing #723; switch cases not iterated by eachChild.

--- helpers.coffee
+++ helpers.coffee
@@ -50,10 +50,16 @@
     object[key] = val
   object

-# Return a flattened version of an array (shallow and nonrecursive).
+# Return a flattened version of an array.
 # Handy for getting a list of `children` from the nodes.
-exports.flatten = (array) ->
-  array.concat.apply [], array
+exports.flatten = flatten = (array) ->
+  flattened = []
+  for element in array
+    if Array.isArray(element)
+      flattened = flattened.concat flatten(element)
+    else
+      flattened.push element
+  flattened

 # Delete a key from an object, returning the value. Useful when a node is
 # looking for a particular method in an options hash.


Removed unused method

Removed unused method

--- app/js/initializers/xdm.coffee
+++ app/js/initializers/xdm.coffee
@@ -10,5 +10,4 @@
   remote:
     showFactlink: {}
     prepareNewFactlink: {}
-    position: {}
 }


Revert "Have Surface handle when WebGL crashes"

This reverts commit 2af74d672ff63955db45f977832fe34b666506bb.
We've seen this alert pop up when WebGL does *not* crash, so
removing this.

--- app/lib/surface/Surface.coffee
+++ app/lib/surface/Surface.coffee
@@ -89,9 +89,6 @@

   constructor: (@world, @normalCanvas, @webGLCanvas, givenOptions) ->
     super()
-    @webGLCanvas[0].addEventListener "webglcontextlost", (event) =>
-      @webGLCrashed = true
-      alert('WebGL crashed. Please reload or try another browser.')
     $(window).on('keydown', @onKeyEvent)
     $(window).on('keyup', @onKeyEvent)
     @normalLayers = []
@@ -195,7 +192,6 @@
   #- Update loop

   tick: (e) =>
-    return if @webGLCrashed
     # seems to be a bug where only one object can register with the Ticker...
     oldFrame = @currentFrame
     oldWorldFrame = Math.floor oldFrame
@@ -265,7 +261,6 @@
     @dimmer?.setSprites @lankBoss.lanks

   drawCurrentFrame: (e) ->
-    return if @webGLCrashed
     ++@totalFramesDrawn
     @normalStage.update e
     @webGLStage.update e
@@ -369,7 +364,6 @@
   #- Changes and events that only need to happen when the frame has changed

   onFrameChanged: (force) ->
-    return if @webGLCrashed
     @currentFrame = Math.min(@currentFrame, @world.frames.length - 1)
     @debugDisplay?.updateFrame @currentFrame
     return if @currentFrame is @lastFrame and not force


remove mailto in login page

remove mailto in login page

--- app/assets/javascripts/components/yale-login.cjsx
+++ app/assets/javascripts/components/yale-login.cjsx
@@ -9,7 +9,6 @@

Welcome

To begin marking and transcribing programs, please log in to your prefered account

         Yale Login
-        Can't access your account?


Minor style fixes for LineHighlighter

Minor style fixes for LineHighlighter

--- app/assets/javascripts/line_highlighter.js.coffee
+++ app/assets/javascripts/line_highlighter.js.coffee
@@ -73,7 +73,7 @@
     lineNumber = $(event.target).data('line-number')
     current = @hashToRange(@_hash)

-    unless current[0] and event.shiftKey
+    unless current[0] && event.shiftKey
       # If there's no current selection, or there is but Shift wasn't held,
       # treat this like a single-line selection.
       @setHash(lineNumber)
@@ -105,9 +105,9 @@
   hashToRange: (hash) ->
     matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/)

-    if matches and matches.length
+    if matches && matches.length
       first = parseInt(matches[1])
-      last  = matches[2] and parseInt(matches[2]) or null
+      last  = if matches[2] then parseInt(matches[2]) else null

       [first, last]
     else


fixed some broken stuff about the uploader since we removed part of the json2 plugin.

--- app/assets/javascripts/mercury/uploader.js.coffee
+++ app/assets/javascripts/mercury/uploader.js.coffee
@@ -91,8 +91,8 @@
       xhr.upload[eventName] = (event) => @uploaderEvents[eventName].call(@, event)
     xhr.onload = (event) =>
       try
-        response = jQuery.evalJSON(event.target.responseText)
-        Mercury.trigger('action', {action: 'insertImage', value: {src: response.url}})
+        response = jQuery.parseJSON(event.target.responseText)
+        Mercury.trigger('action', {action: 'insertImage', value: {src: response.image.url}})
       catch error
         @updateStatus('Unable to process response')


fix readable file size

fix readable file size

--- app/assets/javascripts/uploadcare/utils.js.coffee
+++ app/assets/javascripts/uploadcare/utils.js.coffee
@@ -166,14 +166,24 @@
         top: e.pageY - top - 10

+  ns.fileSizeLabels = 'B KB MB GB TB PB EB ZB YB'.split ' '
+
   ns.readableFileSize = (value, onNaN='', prefix='', postfix='') ->
     value = parseInt(value, 10)
-    return onNaN if isNaN(value)
-    labels = 'B KB MB GB TB PB EB ZB YB'.split ' '
-    for label, i in labels
-      if value < 512 or i is labels.length - 1
-        return "#{prefix}#{value} #{label}#{postfix}"
-      value = Math.round(value / 1024)
+    if isNaN(value)
+      return onNaN
+
+    digits = 2
+    i = 0
+    while value > 999 and i < ns.fileSizeLabels.length - 1
+      i++
+      value /= 1024
+
+    # number of digits after point: total number minus digits before point
+    fixedTo = Math.max(0, digits - value.toFixed(0).length)
+    # fixed → number → string, to trim trailing zeroes
+    value = Number(value.toFixed(fixedTo))
+    return "#{prefix}#{value} #{ns.fileSizeLabels[i]}#{postfix}"

   ns.imagePath = (name) ->
     uploadcare.settings.build().scriptBase + 'images/' + name


better

better

--- vue/src/shared/record_store/base_model.coffee
+++ vue/src/shared/record_store/base_model.coffee
@@ -141,38 +141,38 @@
       return @recordStore[args.from].nullModel()
     @[name+'Is'] = (obj) => @recordStore[args.from].find(@[args.by]) == obj

-    belongsToPolymorphic: (name) ->
-      # belongsToPolymorphic('model', with: 'record')
-      # name is record in record_type
-      # handle is the method name we give it to access it. defaults to name
-
-      # opts['with'] = name unless opts['with']?
-      typeMap = {
-        Group: 'groups'
-        Discussion: 'discussions'
-        Poll: 'polls'
-        Outcome: 'outcomes'
-        Stance: 'stances'
-        Comment: 'comments'
-        CommentVote: 'comments'
-        Membership: 'memberships'
-        MembershipRequest: 'membershipRequests'
-      }
-
-      assoc = name
-      @[name] = =>
-        recordType = @[assoc+'Type']
-        recordId = @[assoc+'Id']
-        collection = typeMap[recordType]
-        return unless recordType
-        # console.log 'called.. @, name, assoc, recordType, recordId, collection', @, @id, name, assoc, recordType, recordId, collection
-
-        return @recordStore[collection].find(recordId)
-
-        # if recordId
-        #   @recordStore[collection].find(recordId)
-        # else
-        #   @recordStore[collection].nullModel()
+  belongsToPolymorphic: (name) ->
+    # belongsToPolymorphic('model', with: 'record')
+    # name is record in record_type
+    # handle is the method name we give it to access it. defaults to name
+
+    # opts['with'] = name unless opts['with']?
+    typeMap = {
+      Group: 'groups'
+      Discussion: 'discussions'
+      Poll: 'polls'
+      Outcome: 'outcomes'
+      Stance: 'stances'
+      Comment: 'comments'
+      CommentVote: 'comments'
+      Membership: 'memberships'
+      MembershipRequest: 'membershipRequests'
+    }
+
+    assoc = name
+    @[name] = =>
+      recordType = @[assoc+'Type']
+      recordId = @[assoc+'Id']
+      collection = typeMap[recordType]
+      return unless recordType
+      # console.log 'called.. @, name, assoc, recordType, recordId, collection', @, @id, name, assoc, recordType, recordId, collection
+
+      return @recordStore[collection].find(recordId)
+
+      # if recordId
+      #   @recordStore[collection].find(recordId)
+      # else
+      #   @recordStore[collection].nullModel()

   translationOptions: ->


Pawn implementation nearly done.

Pawn implementation nearly done.

--- app/scripts/chess/board.coffee
+++ app/scripts/chess/board.coffee
@@ -28,25 +28,32 @@
 	whiteSymbol: "\u2659"

 	getMoves: (x,y,game) ->
+		# TODO: Implement en passant and pawn conversion.
 		moves = []
-		# White.
-		if @color == 'white'
-			# Default move.
-			if y+1 < 8 and game.get( x, y+1 ) instanceof BlankPiece
-				moves.push( [x,y+1] )
-				# Double step.
-				if y == 1 and y+2 < 8 and game.get( x, y+2 ) instanceof BlankPiece
-					moves.push( [x,y+2] )
-			# TODO: Attack available.
-		# Black.
-		else
-			# Default move.
-			if y-1 >= 0 && game.get( x, y-1 ) instanceof BlankPiece
-				moves.push( [x,y-1] )
-				# Double step.
-				if y == 6 && y-2 >= 0 && game.get( x, y-2 ) instanceof BlankPiece
-					moves.push( [x,y-2] )
-			# TODO: Attack available.
+		# Constant that represents which direction pawns move in.
+		movement = 1
+		if @color == 'black'
+			movement = -1
+
+		# Default move.
+		if y+movement < 8 and game.get( x, y+movement ) instanceof BlankPiece
+			moves.push( [x,y+movement] )
+			# Double step.
+			if y == 1 and y+(2*movement) < 8 and game.get( x, y+(2*movement) ) instanceof BlankPiece
+				moves.push( [x,y+(2*movement)] )
+		# Check if the pawn can capture a piece.
+		if game.inBounds( x-1, y+movement )
+			enemy = game.get( x-1, y+movement )
+			if not (enemy instanceof BlankPiece)
+				if @color != enemy.color
+					moves.push( [x-1, y+movement] )
+		if game.inBounds( x+1, y+movement )
+			enemy = game.get( x+1, y+movement )
+			if not (enemy instanceof BlankPiece)
+				if @color != enemy.color
+					moves.push( [x+1, y+movement] )
+
+		# DEBUG
 		i = 0
 		while i < moves.length
 			console.log( moves[i] )
@@ -238,6 +245,7 @@

 #myGame.importFEN( "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" )
 myGame.setup("classic")
+myGame.set(4,2,"p")
 #myGame.test()
 myGame.log()
 myGame.getMoves(3,1)


Update pt-PT.coffee

Update pt-PT.coffee

Some new additions and minor fixes to old ones

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -113,7 +113,7 @@
     title: "Definições do Wizard"
     customize_avatar: "Altera o teu Avatar"
     clothes: "Roupas"
-#    trim: "Trim"
+    trim: "Faixa"
     cloud: "Nuvem"
     spell: "Feitiço"
     boots: "Botas"
@@ -191,7 +191,7 @@
     victory_ranking_game: "A submeter..."
 #    victory_return_to_ladder: "Return to Ladder"
     victory_play_next_level: "Jogar próximo nível"
-    victory_go_home: "Ir para a Home"
+    victory_go_home: "Ir para o Inicio"
     victory_review: "Conta-nos mais!"
     victory_hour_of_code_done: "É tudo?"
     victory_hour_of_code_done_yes: "Sim, a minha Hora de Código chegou ao fim!"
@@ -199,19 +199,19 @@
     multiplayer_link_description: "Dá este link a alguém para se juntar a ti."
     multiplayer_hint_label: "Dica:"
     multiplayer_hint: " Carrega no link para seleccionar tudp, depois pressiona ⌘-C ou Ctrl-C para copiar o link."
-    multiplayer_coming_soon: "Mais funcionalidades de multiplayer hão de vir!"
+    multiplayer_coming_soon: "Mais funcionalidades de multiplayer brevemente!"
     guide_title: "Guia"
     tome_minion_spells: "Feitiços dos teus Minions"
-    tome_read_only_spells: "Feitiços Read-Only"
+    tome_read_only_spells: "Feitiços apenas de leitura"
     tome_other_units: "Outras Unidades"
 #    tome_cast_button_castable: "Cast Spell"
-#    tome_cast_button_casting: "Casting"
+    tome_cast_button_casting: "A lançar"
     tome_cast_button_cast: "Lançar Feitiço"
 #    tome_autocast_delay: "Autocast Delay"
     tome_select_spell: "Escolhe um Feitiço"
     tome_select_a_thang: "Escolhe Alguém para "
     tome_available_spells: "Feitiços disponíveis"
-    hud_continue: "Continuar (pressiona shift-space)"
+    hud_continue: "Continuar (shift-espaço)"
     spell_saved: "Feitiço Guardado"
     skip_tutorial: "Saltar (esc)"
 #    editor_config: "Editor Config"
@@ -275,12 +275,12 @@
     create_system_title: "Criar novo Sistema"
     new_component_title: "Criar novo Componente"
     new_component_field_system: "Sistema"
-#    new_article_title: "Create a New Article"
-#    new_thang_title: "Create a New Thang Type"
-#    new_level_title: "Create a New Level"
-#    article_search_title: "Search Articles Here"
-#    thang_search_title: "Search Thang Types Here"
-#    level_search_title: "Search Levels Here"
+    new_article_title: "Criar um Novo Artigo"
+    new_thang_title: "Criar um Novo tipo the Thang"
+    new_level_title: "Criar um Novo Nível"
+    article_search_title: "Procura Artigos Aqui"
+    thang_search_title: "Procura Tipos de Thang Aqui"
+    level_search_title: "Procura Níveis aqui"

   article:
     edit_btn_preview: "Visualizar"


Update pt-PT.coffee

Update pt-PT.coffee

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -383,7 +383,7 @@
     guide: "Guia"
     restart: "Reiniciar"
     goals: "Objetivos"
-#    goal: "Goal"
+    goal: "Objetivo"
     success: "Successo!"
     incomplete: "Incompletos"
     timed_out: "Ficou sem tempo"
@@ -411,10 +411,10 @@
     tome_cast_button_castable: "Lançar Feitiço" # Temporary, if tome_cast_button_run isn't translated.
     tome_cast_button_casting: "A Lançar" # Temporary, if tome_cast_button_running isn't translated.
     tome_cast_button_cast: "Feitiço Lançado" # Temporary, if tome_cast_button_ran isn't translated.
-#    tome_cast_button_run: "Run"
-#    tome_cast_button_running: "Running"
-#    tome_cast_button_ran: "Ran"
-#    tome_submit_button: "Submit"
+    tome_cast_button_run: "Correr"
+    tome_cast_button_running: "A Correr"
+    tome_cast_button_ran: "Corrido"
+    tome_submit_button: "Submeter"
     tome_select_spell: "Selecione um Feitiço"
     tome_select_a_thang: "Selecione Alguém para "
     tome_available_spells: "Feitiços Disponíveis"
@@ -423,7 +423,7 @@
     skip_tutorial: "Saltar (esc)"
     keyboard_shortcuts: "Atalhos do Teclado"
     loading_ready: "Pronto!"
-#    loading_start: "Start Level"
+    loading_start: "Iniciar Nível"
     tip_insert_positions: "Pressione Shift e Clique num ponto do mapa para inseri-lo no editor de feitiços."
     tip_toggle_play: "Alterne entre Jogar e Pausar com Ctrl+P."
     tip_scrub_shortcut: "Ctrl+[ rebobina e Ctrl+] avança."


Reorganized views/ui/index.coffee

Reorganized views/ui/index.coffee

--- src/views/ui/index.coffee
+++ src/views/ui/index.coffee
@@ -1,6 +1,6 @@
 module.exports =

+  Button: require './button'
   ContentBlock: require './content_block'
-  Button: require './button'
   Tabs: require './tabs'
   Window: require './window'


Work around Atom not displaying keybindings

--- keymaps/ide-haskell-repl.cson
+++ keymaps/ide-haskell-repl.cson
@@ -1,4 +1,10 @@
-'.platform-linux atom-text-editor.ide-haskell-repl, .platform-win32 atom-text-editor.ide-haskell-repl':
+'.platform-linux atom-text-editor.ide-haskell-repl':
+  'shift-enter': 'ide-haskell-repl:exec-command'
+  'shift-up': 'ide-haskell-repl:history-back'
+  'shift-down': 'ide-haskell-repl:history-forward'
+  'ctrl-shift-r': 'ide-haskell-repl:ghci-reload'
+
+'.platform-win32 atom-text-editor.ide-haskell-repl':
   'shift-enter': 'ide-haskell-repl:exec-command'
   'shift-up': 'ide-haskell-repl:history-back'
   'shift-down': 'ide-haskell-repl:history-forward'


(new doc) Document, in detail, the `Alien` nukespace API

--- Paws/Source/data.coffee
+++ Paws/Source/data.coffee
@@ -178,6 +178,33 @@
       to.locals   = @locals

 Paws.Alien = Alien = class Alien extends Execution
+
+   # An `Alien` is an `Execution` that's implemented with JavaScript code, instead of as a series of
+   # Paws combinations. These are the primitive building-blocks with which Paws programs are built.
+   #
+   # Most `Alien`s are exposed to Paws code through 'bags' of `Alien`s stored under useful names on
+   # the `locals` of the first (root-level) `Execution` in a Paws program; notably, the primitives
+   # described by the Paws specification are exposed in such a bag, named "infrastructure."
+   #
+   # `Alien`s in this implementation consist of a series of 'bits', each of which is a JavaScript
+   # `Function`. Each 'bit' of the `Alien` implements a (clearly synchronous, as they are written in
+   # JavaScript) set of operations to be preformed upon the next resumption of the `Execution`
+   # represented by this `Alien`. (It can be easier to conceptualize an `Alien` as an explicit
+   # coroutine, with the `return` statement of one bit and the arguments of the following bit acting
+   # somewhat like a traditional `yield` statement.)
+   #
+   # The first `Function`-bit will be invoked upon resumption of the `Alien`, and then discarded
+   # (thus causing the following `Function` to receive the resumption thereafter.) Upon resumption,
+   # the `Function` will be invoked with the following arguments and environment:
+   #
+   #  1. `result`, whatever resumption-value was queued to cause this resumption
+   #  2. `unit` (optional), the `Unit` in which this resumption is relevant (thus providing access
+   #     to the current `Unit`'s staging-`queue` and responsibility-`table`.)
+   #
+   # In addition, the `this` at invocation will be the `Alien` itself, giving the bit-body
+   # convenient access to a place to store incrementally-constructed results. (Note that the actual
+   # `Alien` object referred to during the invocation of subsequent bits may *not* be the same, as
+   # the `Alien` may have been branched!)
    constructor: constructify(return:@) (@bits...)->

    complete: -> !this.bits.length


Add debug log

Add debug log

--- rtmp.coffee
+++ rtmp.coffee
@@ -2362,6 +2362,8 @@
         callback null  # ignore

   createAck: ->
+    if DEBUG_OUTGOING_RTMP_PACKETS
+      logger.info "createAck"
     return createRTMPMessage
       chunkStreamID: 2
       timestamp: 0  # TODO: Is zero OK?


Fix a bug where video-only stream does not work

#19

--- rtsp.coffee
+++ rtsp.coffee
@@ -1746,8 +1746,10 @@
     streamId = RTSPServer.getStreamIdFromUri req.uri
     logger.info "[rtsp] client #{client.id} started uploading stream #{streamId}"
     stream = avstreams.getOrCreate streamId
-    @emit 'video_start', stream
-    @emit 'audio_start', stream
+    if client.announceSDPInfo.video?  # has video
+      @emit 'video_start', stream
+    if client.announceSDPInfo.audio?  # has audio
+      @emit 'audio_start', stream
     res = """
     RTSP/1.0 200 OK
     Session: #{client.sessionID};timeout=60


log where mongo is

log where mongo is

--- models/mongo.coffee
+++ models/mongo.coffee
@@ -17,6 +17,7 @@
     else @db.open -> # no callback

   parseUrl: (urlString)->
+    sys.log "Connecting to MongoDB at #{urlString}"
     uri = url.parse urlString
     @host = uri.hostname
     @port = parseInt(uri.port)


[backport] prevent closing twice create task

--- app/coffee/modules/taskboard/lightboxes.coffee
+++ app/coffee/modules/taskboard/lightboxes.coffee
@@ -171,9 +171,6 @@
                 deleteAttachments(data)
                     .then () => createAttachments(data)
                     .then () =>
-                        currentLoading.finish()
-                        lightboxService.close($el)
-
                         $rs.tasks.getByRef(data.project, data.ref, params).then (task) ->
                             $rootscope.$broadcast(broadcastEvent, task)


Referral: move referral modal to utils

Referral: move referral modal to utils

--- client/Account/views/referralsystem.coffee
+++ client/Account/views/referralsystem.coffee
@@ -41,77 +41,7 @@
         """

-  showRedeemReferralPointModal: ()->
-    vmController = KD.getSingleton("vmController")
-    vmController.fetchVMs yes, (err, vms)=>
-      return KD.showError err if err
-      return @notify_ "You don't have any VMs. Please create one VM" if not vms or vms.length < 1
-
-      KD.remote.api.JReferral.fetchRedeemableReferrals { type: "disk" }, (err, referals)=>
-        return KD.showError err if err
-        return @notify_ "You dont have any referrals" if not referals or referals.length < 1
-
-        @modal = modal = new KDModalViewWithForms
-          title                   : "Redeem Your Referral Points"
-          cssClass                : "redeem-modal"
-          content                 : ""
-          overlay                 : yes
-          width                   : 500
-          height                  : "auto"
-          tabs                    :
-            forms                 :
-              Redeem               :
-                callback          : =>
-                  @modal.modalTabs.forms.Redeem.buttons.redeemButton.showLoader()
-                  @emit "RedeemReferralPointSubmitted", @
-                buttons           :
-                  redeemButton    :
-                    title         : "Redeem"
-                    style         : "modal-clean-gray"
-                    type          : "submit"
-                    loader        :
-                      color       : "#444444"
-                      diameter    : 12
-                    callback      : -> @hideLoader()
-                  cancel          :
-                    title         : "Cancel"
-                    style         : "modal-cancel"
-                    callback      : (event)-> modal.destroy()
-                fields            :
-                  vmToResize    :
-                    label         : "Select a WM to resize"
-                    cssClass      : "clearfix"
-                    itemClass     : KDSelectBox
-                    type          : "select"
-                    name          : "vmToResize"
-                    validate      :
-                      rules       :
-                        required  : yes
-                      messages    :
-                        required  : "You must select a VM!"
-                    selectOptions : (cb)->
-                      options = for vm in vms
-                        ( title : vm, value : vm)
-                      cb options
-                  sizes           :
-                    label         : "Select Size"
-                    cssClass      : "clearfix"
-                    itemClass     : KDSelectBox
-                    type          : "select"
-                    name          : "size"
-                    validate      :
-                      rules       :
-                        required  : yes
-                      messages    :
-                        required  : "You must select a size!"
-                    selectOptions : (cb)=>
-                      options = []
-                      previousTotal = 0
-                      referals.forEach (referal, i)->
-                        previousTotal += referal.amount
-                        options.push ( title : "#{previousTotal} #{referal.unit}" , value : previousTotal)
-                      cb options
-
+  showRedeemReferralPointModal = KD.utils.showRedeemReferralPointModal

   loadView: ->
     super


googleapis: added state as a paramater for first oauth

--- workers/social/lib/social/models/oauth.coffee
+++ workers/social/lib/social/models/oauth.coffee
@@ -4,6 +4,7 @@
 oauth       = require 'oauth'
 parser      = require 'url'
 KodingError = require '../error'
+JSession    = require './session'

 module.exports = class OAuth extends bongo.Base
   @share()
@@ -33,16 +34,19 @@
         callback null, url
       when 'google'
         { client_id } = KONFIG.google
-        url  = 'https://accounts.google.com/o/oauth2/auth?'
-        url += 'scope=https://www.google.com/m8/feeds '
-        url += 'https://www.googleapis.com/auth/userinfo.profile '
-        url += 'https://www.googleapis.com/auth/userinfo.email&'
-        url += "redirect_uri=#{redirectUri}&"
-        url += 'response_type=code&'
-        url += "client_id=#{client_id}&"
-        url += 'access_type=offline'
+        JSession.one { clientId: client.sessionToken }, (err, session) ->
+          state = session._id
+          url  = 'https://accounts.google.com/o/oauth2/auth?'
+          url += 'scope=https://www.google.com/m8/feeds '
+          url += 'https://www.googleapis.com/auth/userinfo.profile '
+          url += 'https://www.googleapis.com/auth/userinfo.email&'
+          url += "redirect_uri=#{redirectUri}&"
+          url += 'response_type=code&'
+          url += "client_id=#{client_id}&"
+          url += "state=#{state}&"
+          url += 'access_type=offline'

-        callback null, url
+          callback null, url
       when 'linkedin'
         { client_id } = KONFIG.linkedin
         state = crypto.createHash('md5').update((new Date).toString()).digest('hex')
@@ -101,7 +105,6 @@
       }

   @saveTokens = (client, provider, credentials, callback) ->
-    JSession = require './session'
     JSession.one { clientId: client.sessionToken }, (err, session) ->
       return callback err  if err
       return callback new KodingError 'Session not found'  unless session


modified example

modified example

--- cli.coffee
+++ cli.coffee
@@ -91,13 +91,23 @@
 	-----
 	Example:(file/inference rules2)
 	-----
-	$.F ['%.c','%.o']
+	$.F ['a','%.o','%.c']

-	$.R '%.o',['%.c'],(g,t)->
-		$.X "gcc -c \#{t.deps[0]} -o \#{t.target}"
-		.then $.saved t.target  #indicates target has already saved
+	$.R '%.o','%.c',(g,t)->
+		$.S "gcc -c -o \#{t.target} \#{t.dep}"
+		.then $.saved t.target  #$.saved indicats target has already been saved

-	#>recipe test.o
+	$.R 'a',['b.o','a.o'],(g,t)->
+		$.S "gcc -o \#{t.target} \#{t.deps.join ' '}"
+		.then $.saved 'a'
+
+	$.R 'clean',$.PX 'rm -f *.o'
+	$.R 'cleanall','clean',$.PX 'rm -f a'
+
+	$.R 'default','a'
+	$.set 'TRACE',true
+	#>recipe
+	#>recipe cleanall
 	"""
 	process.exit 0


More straightforward naming of helper functions.

--- scripts/generate-samples-ast.coffee
+++ scripts/generate-samples-ast.coffee
@@ -72,8 +72,9 @@

   , (err, successFlags) ->
     if not err and false in successFlags
-      err = new Error 'Generation of some samples was unsuccessful.'
-    cb err
+      cb new Error 'Generation of some samples was unsuccessful.'
+    else
+      cb err

 main = ->


Fix repetition

Fix repetition

--- scripts/stackoverflow-poller.coffee
+++ scripts/stackoverflow-poller.coffee
@@ -7,7 +7,7 @@

 Poller = require '../support/poller'

-POLL_INTERVAL    = 1000 * 60 * 15 # 15 minute interval to start with
+POLL_INTERVAL    = 1000 * 60 * 10 # 10 minute interval to start with
 ANNOUNCE_ROOMS   = []

 do =>
@@ -23,11 +23,12 @@
   constructor: (@robot, @poster) ->
     @poster  ||= new ThreadPoster(@robot)
     @poller    = new Poller(@robot.http(HOST_URL+API_URI).query(
-      site:   "stackoverflow"
-      order:  "desc"
-      sort:   "activity"
-      tagged: "octobercms"
-      filter: "default"
+      pagesize: 3
+      site:     "stackoverflow"
+      order:    "desc"
+      sort:     "activity"
+      tagged:   "octobercms"
+      filter:   "default"
     ), POLL_INTERVAL)
     @recentIds = []

@@ -44,7 +45,7 @@
   newThreadsCallback: (threads) ->
     newThreads = threads.filter (thread) => @threadIsNew(thread)
     if newThreads.length > 0
-      @poster.postNewThreads(newThreads.slice(0, 3))
+      @poster.postNewThreads(newThreads)
     else
       console.log "No new posts from fetch at ", new Date()


fix bug when stacking more than two modules

--- app/static/scripts/room.coffee
+++ app/static/scripts/room.coffee
@@ -229,6 +229,7 @@
             }

     sortableMod         : () =>
+        lastModId = undefined
         $('#modArea').sortable({
             containment: '#container',
             tolerance: 'pointer',
@@ -238,6 +239,8 @@
                     visible = (pos.t > 0 && pos.l > 0 && pos.t + pos.h < pos.docH && pos.l + pos.w < pos.docW)
                     if !visible
                         $(ui.sender).sortable 'cancel'
+            start: (event, ui) ->
+                lastModId = $('#modArea .module:last').attr 'id'
             sort: (event, ui) ->
                 $('.module').css 'clear', ''
             stop: (event, ui) ->
@@ -245,10 +248,16 @@
                 if ui.position.left < ui.originalPosition.left
                     prevHeight = do (ui.item.prevAll '.module:first').height
                     if prevHeight && ui.position.top >= prevHeight
+                        if lastModId
+                            console.log lastModId
+                            ui.item.remove().insertAfter $('#' + lastModId)
                         $(draggedItemId).css 'clear', 'left'
                 else if ui.position.left > ui.originalPosition.left
                     nextHeight = do (ui.item.nextAll '.module:first').height
                     if nextHeight && ui.position.top >= nextHeight
+                        if lastModId
+                            console.log lastModId
+                            ui.item.remove().insertAfter $('#' + lastModId)
                         $(draggedItemId).css 'clear', 'left'
                 else
                     $(draggedItemId).css 'clear', ''


Globals: Put userEnvironmentData into globals.

--- client/app/lib/globals.coffee
+++ client/app/lib/globals.coffee
@@ -16,6 +16,7 @@
   socialApiData: null
   isTesting: false
   userId: null
+  userEnvironmentData: {}
   REMOTE_API: {}
   COMPUTECONTROLLER_TIMEOUT: 30000
   WORKSPACE_WELCOME_TXT: """


Fixing Autoplay so now it only loads manually

--- client/components/player.cjsx
+++ client/components/player.cjsx
@@ -41,7 +41,9 @@
       urls: [audioUrl]
       volume: 1.0
       buffer: true
-      autoplay: true
+      autoplay: false
+      onload: =>
+        @sound.play()

   render: ->
     <div>


added reset method for all anchors

added reset method for all anchors

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -335,6 +335,49 @@
         panel.style[transformProp] = 'translate3d(0, #{@panelHeight}px, 0)'

+  reset: (callback) ->
+    for panel, i in @panels[@lastAnchor]
+      switch @lastAnchor
+        when 'left'
+          y = 0
+          if i is 0
+            x = 0
+          else
+            x = @panelWidth - 1
+        when 'right'
+          y = 0
+          if i is 0
+            x = @_getRightAnchorCoord()
+          else
+            x = -@panelWidth + 1
+        when 'top'
+          x = 0
+          if i is 0
+            y = 0
+          else
+            y = @panelHeight - 1
+        when 'bottom'
+          x = 0
+          if i is 0
+            y = @_getBottomAnchorCoord()
+          else
+            y = -@panelHeight + 1
+
+
+      panel.style[transformProp] = @_transform [x, y]
+
+      if @shading
+        if @lastAnchor is 'left' or @lastAnchor is 'right'
+          @shaders[@lastAnchor].right[i].style.opacity = 0
+          @shaders[@lastAnchor].left[i].style.opacity = 0
+        else
+          @shaders[@lastAnchor].top[i].style.opacity = 0
+          @shaders[@lastAnchor].bottom[i].style.opacity = 0
+
+    @_callback callback: callback
+
+
+
   accordion: (angle, options) ->
     options = extendObj options, @_accordionDefaults
     {anchor} = options


removed reset check

removed reset check

--- oridomi.coffee
+++ oridomi.coffee
@@ -524,10 +524,7 @@
     else
       @panels[@lastOp.anchor][0].addEventListener css.transitionEnd, @_onTransitionEnd, false

-    if operation.method is 'reset'
-      @lastOp = @_defaultOp
-    else
-      @lastOp = operation
+    @lastOp = operation

   # Handler called when a CSS transition ends.


Fix weird CSS

Fix weird CSS

--- client/app/CommonViews/ShowMoreDataModalView.coffee
+++ client/app/CommonViews/ShowMoreDataModalView.coffee
@@ -21,15 +21,18 @@

     if participants[0] instanceof KD.remote.api.JAccount
       @type = "account"
+      css   = "modal-topic-wrapper"
     else if participants[0] instanceof KD.remote.api.JTag
       @type = "tag"
+      css   = "modal-topic-wrapper"
     else
       @type = "app"
+      css   = "modal-applications-wrapper"

     options.title    or= titleMap()[@type]
     options.height   = "auto"
     options.overlay  = yes
-    options.cssClass = "modal-applications-wrapper"
+    options.cssClass = css
     options.buttons  =
       Close :
         style : "modal-clean-gray"


app: enable profile text and linkcontainer tests

--- client/app/lib/components/profile/test/index.coffee
+++ client/app/lib/components/profile/test/index.coffee
@@ -1,3 +1,5 @@
 describe 'Profile', ->
+  require './profiletext.test'
   require './profilelink.test'
+  require './profilelinkcontainer.test'


Fixing embedded urls on updated messages.

--- server/methods/updateMessage.coffee
+++ server/methods/updateMessage.coffee
@@ -4,7 +4,7 @@
 			throw new Meteor.Error('invalid-user', "[methods] updateMessage -> Invalid user")

 		originalMessage = RocketChat.models.Messages.findOneById message._id
-
+
 		if not originalMessage?._id?
 			return

@@ -31,7 +31,7 @@
 		message.ets = new Date()

 		if urls = message.msg.match /([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\w]+)?\??([-\+=&!:;%@\/\.\,\w]+)?#?([\w]+)?)?/g
-			message.urls = urls
+			message.urls = urls.map (url) -> url: url

 		message = RocketChat.callbacks.run 'beforeSaveMessage', message

@@ -43,5 +43,5 @@
 		,
 			$set: message

-		# Meteor.defer ->
-		# 	RocketChat.callbacks.run 'afterSaveMessage', RocketChat.models.Messages.findOneById(message.id)
+		Meteor.defer ->
+			RocketChat.callbacks.run 'afterSaveMessage', RocketChat.models.Messages.findOneById(tempid)


Fix obsolete title/description in Component schema config new return type docs

--- app/schemas/models/level_component.coco.coffee
+++ app/schemas/models/level_component.coco.coffee
@@ -122,8 +122,6 @@
         type: 'object'
         title: 'Variable Type'
         description: 'Variable return types by code language. Can usually leave blank. Fill in if it is a primitive type and not auto in C++.'
-        title: 'Language Descriptions',
-        description: 'Example return values by code language.',
         additionalProperties: {type: 'string', description: 'Description of the return value.', maxLength: 1000}
         format: 'code-languages-object'
         default: {cpp: 'auto'}


find only the visible button

find only the visible button

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -32,7 +32,7 @@
 			BOX_PREFIX: 'selection_act_'

 		@GM_SEL:
-			ARCHIVE_BUTTON: 	'.T-I.J-J5-Ji.lR.T-I-ax7.T-I-Js-IF.ar7'
+			ARCHIVE_BUTTON: 	'.T-I.J-J5-Ji.lR.T-I-ax7.T-I-Js-IF.ar7:visible'
 			THREAD_BUTTON_BAR: 	'.iH > div'

 		# production URL


Fixed missing paren in c40b8b1e.

Fixed missing paren in c40b8b1e.

--- server/patches/patch_handler.coffee
+++ server/patches/patch_handler.coffee
@@ -91,7 +91,7 @@
     @sendPatchCreatedHipChatMessage creator: req.user, patch: doc, target: doc.targetLoaded, docLink: docLink
     watchers = doc.targetLoaded.get('watchers') or []
     # Don't send these emails to the person who submitted the patch, or to Nick, George, or Scott.
-    watchers = (w for w in watchers when not w.equals(req.user.get('_id')) and not (w + '' in ['512ef4805a67a8c507000001', '5162fab9c92b4c751e000274', '51538fdb812dd9af02000001'])
+    watchers = (w for w in watchers when not w.equals(req.user.get('_id')) and not (w + '' in ['512ef4805a67a8c507000001', '5162fab9c92b4c751e000274', '51538fdb812dd9af02000001']))
     return unless watchers?.length
     User.find({_id: {$in: watchers}}).select({email: 1, name: 1}).exec (err, watchers) =>
       for watcher in watchers


adding validation messages should only happen if there is a validation error

--- ui/coffee/edit.coffee
+++ ui/coffee/edit.coffee
@@ -262,17 +262,21 @@
         extentVal = $(this).val()
         switch $(this).attr 'attr'
           when 'NorthBound'
-            extentLi.addClass 'invalid' if extentVal > 90 or extentVal < -90
-            messages.push 'NorthBound must be between -90 and 90'
+            if extentVal > 90 or extentVal < -90
+              extentLi.addClass 'invalid'
+              messages.push 'NorthBound must be between -90 and 90'
           when 'SouthBound'
-            extentLi.addClass 'invalid' if extentVal > 90 or extentVal < -90
-            messages.push 'SouthBound must be between -90 and 90'
+            if extentVal > 90 or extentVal < -90
+              extentLi.addClass 'invalid'
+              messages.push 'SouthBound must be between -90 and 90'
           when 'EastBound'
-            extentLi.addClass 'invalid' if extentVal > 180 or extentVal < -180
-            messages.push 'EastBound must be between -90 and 90'
+            if extentVal > 180 or extentVal < -180
+              extentLi.addClass 'invalid'
+              messages.push 'EastBound must be between -90 and 90'
           when 'WestBound'
-            extentLi.addClass 'invalid' if extentVal > 180 or extentVal < -180
-            messages.push 'WestBound must be between -90 and 90'
+            if extentVal > 180 or extentVal < -180
+              extentLi.addClass 'invalid'
+              messages.push 'WestBound must be between -90 and 90'

       # List validation messages
       msgContainer = $('#validation-message-container')


no results config

no results config

--- scripts/app.coffee
+++ scripts/app.coffee
@@ -20,5 +20,4 @@
   resultsContainer: resultsElem
   json: '/search.json'
   searchResultTemplate: '{title}'
-
-
+  noResultsText: 'No results.'


Because now she's `dusyeah` :sleeping:

Because now she's `dusyeah` :sleeping:

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -62,7 +62,7 @@

 # Make any changes required to the name
 filterName = (name) ->
-  if /dusya/i.test(name)
+  if /dusy[ae]/i.test(name)
     # She likes to change her name A LOT. We can assume if it
     # looks like her, it's her.
     name = 'dusya'


Stop flushing the DB

Stop flushing the DB

--- app/storage.coffee
+++ app/storage.coffee
@@ -19,7 +19,6 @@
   # Using local Redis server
   redis = nodeRedis.createClient()

-redis.flushdb()
 redis.setnx "users:index", "-1"


Don't explode the whole home page if rail query fails

--- apps/home/routes.coffee
+++ apps/home/routes.coffee
@@ -24,6 +24,14 @@
   method = if req.cookies?['hide-welcome-hero']? then 'push' else 'unshift'
   res.cookie 'hide-welcome-hero', '1', expires: new Date(Date.now() + 31536000000)
   method
+
+maybeFetchHomepageRails = (req)->
+  deferred = Q.defer()
+  metaphysics(query: query, req: req)
+    .then (data) -> deferred.resolve data
+    .catch -> deferred.resolve home_page: { artwork_modules: [] }
+
+  deferred.promise

 @index = (req, res, next) ->
   return if metaphysics.debug req, res, { method: 'post', query: query }
@@ -52,7 +60,7 @@
   Q
     .all [
       heroUnits.fetch cache: true, cacheTime: timeToCacheInSeconds
-      metaphysics query: query, req: req
+      maybeFetchHomepageRails req
       featuredLinks.fetch cache: true
       featuredArticles.fetch
         cache: true


#918 - Manual ID is incorrectly incremented

--- jsapp/xlform_model_view/view.choices.coffee
+++ jsapp/xlform_model_view/view.choices.coffee
@@ -100,12 +100,13 @@
         @options.cl.options.add(@model)
         @p.html("Option #{1+@options.i}").addClass("preliminary")

-      @p.on 'shown', (e, obj) -> obj.input.$input.on 'paste', (e) -> e.stopPropagation()
       $viewUtils.makeEditable @, @model, @p, edit_callback: _.bind @saveValue, @
       @n = $('span', @c)
-      @n.on 'shown', (e, obj) -> obj.input.$input.on 'paste', (e) -> e.stopPropagation()
       $viewUtils.makeEditable @, @model, @n, edit_callback: (val) =>
         other_names = @options.cl.getNames()
+
+        if @model.get('name')? && val.toLowerCase() == @model.get('name').toLowerCase()
+          other_names.splice _.indexOf(other_names, @model.get('name')), 1
         if val is ''
           @model.unset('name')
           @model.set('setManually', false)


Fixing acceleration bugs

Fixing acceleration bugs

--- ship.coffee
+++ ship.coffee
@@ -9,7 +9,7 @@
       direction: Math.random()*Math.PI*2
       health: 100
       position: [Math.random(), Math.random()]
-      velocity: [Math.random(), Math.random()]
+      velocity: [0, 0] #[Math.random(), Math.random()]
       score: 0
       size: 0.025
       accelerating: false
@@ -55,14 +55,15 @@
     if (@accelerating)
       accelVector = Helper.multVector([Math.cos(@direction), Math.sin(@direction)], @acceleration)
       @velocity = Helper.addVectors(@velocity, Helper.multVector(accelVector, timeDiff))
-    @position = [@position[0] + @velocity[0]*timeDiff, @position[1] + @velocity[1]*timeDiff]
+
+    @position = Helper.addVectors(@position, Helper.multVector(@velocity, timeDiff))
     @position[0] = @position[0] % 1
     @position[0] = 1 - @position[0] if @position[0] < 0
     @position[1] = @position[1] % 1
     @position[1] = 1 - @position[1] if @position[1] < 0

-    @velocity = [@velocity[0]*0.9999, @velocity[1]*0.9999]
-    @velocity[0] = Math.max(0, Math.min(@velocity[0], 1));
-    @velocity[1] = Math.max(0, Math.min(@velocity[1], 1));
+    @velocity = [@velocity[0]*0.99, @velocity[1]*0.99]
+    @velocity[0] = Math.max(-1, Math.min(@velocity[0], 1));
+    @velocity[1] = Math.max(-1, Math.min(@velocity[1], 1));

 module.exports = Ship


fixes layered search test

fixes layered search test

--- apps/artwork/test/client/layered_search.coffee
+++ apps/artwork/test/client/layered_search.coffee
@@ -4,7 +4,7 @@
 sinon = require 'sinon'
 { resolve } = require 'path'
 { fabricate } = require 'antigravity'
-{ Layer, Layers, LayeredSearchView } = benv.requireWithJadeify resolve(__dirname, '../../client/layered_search'), ['template']
+{ Layer, Layers, LayeredSearchView } = benv.requireWithJadeify resolve(__dirname, '../../client/layered_search'), ['template', 'newTemplate']
 Artwork = require '../../../../models/artwork'
 Fair = require '../../../../models/fair'

@@ -177,6 +177,7 @@
       @view.layers.fair = @fair
       @layers.push id: _.uniqueId('layer'), type: 'fair'
       @view.layers.reset @layers
+      @view.relatedV2 = false
       @view.render()

       done()


Fix typo

Fix typo

--- server/materials/data/topics/bit_operations.coffee
+++ server/materials/data/topics/bit_operations.coffee
@@ -8,7 +8,7 @@
 export default bit_operations = () ->
     return {
         topic: topic("Битовые операции", "Задачи на битовые операции", [
-            label("""
Терия: основная, дополнительно:
+            label("""
Теория: основная, дополнительно:
             раз, два.
             Тут везде разные языки программирования, но принцип один и тот же, максимум операции могут по-разному записываться.
"""),
             problem(123),
@@ -21,4 +21,4 @@
             problem(122),
             problem({testSystem: "codeforces", contest: "1303", problem: "D"}),
         ]
-    }+    }


wait longer

wait longer

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -135,4 +135,4 @@
     called = true
     callback()
   if mixpanel.__loaded then cb() else mixpanel.set_config(loaded: cb)
-  setTimeout cb, 2000 # Ensure we callback whether mixpanel is working or not+  setTimeout cb, 5000 # Ensure we callback whether mixpanel is working or not


Rob is gone now but he'll be back on Monday

--- src/scripts/main.coffee
+++ src/scripts/main.coffee
@@ -6,7 +6,7 @@

 class RobCountdown

-  date_rob_arrives: Date.now
+  date_rob_arrives: (new Date(2015, 11, 14, 9, 0, 0, 0))

   start: ->
     @isHeBack()


Ping script

Ping script

--- src/scripts/ping.coffee
+++ src/scripts/ping.coffee
@@ -1,15 +1,50 @@
-# Utility commands surrounding Hubot uptime.
+# Hubot is very attentive (ping hubot)
+
+phrases = [
+  "Yes, master?"
+  "At your service"
+  "Unleash my strength"
+  "I'm here. As always"
+  "By your command"
+  "Ready to work!"
+  "Yes, milord?"
+  "More work?"
+  "Ready for action"
+  "Orders?"
+  "What do you need?"
+  "Say the word"
+  "Aye, my lord"
+  "Locked and loaded"
+  "Aye, sir?"
+  "I await your command"
+  "Your honor?"
+  "Command me!"
+  "At once"
+  "What ails you?"
+  "Yes, my firend?"
+  "Is my aid required?"
+  "Do you require my aid?"
+  "My powers are ready"
+  "It's hammer time!"
+  "I'm your robot"
+  "I'm on the job"
+  "You're interrupting my calculations!"
+  "What is your wish?"
+  "How may I serve?"
+  "At your call"
+  "You require my assistance?"
+  "What is it now?"
+  "Hmm?"
+  "I'm coming through!"
+  "I'm here, mortal"
+  "I'm ready and waiting"
+  "Ah, at last"
+  "I'm here"
+  "Something need doing?"
+]
+
 module.exports = (robot) ->
-  robot.respond /PING$/i, (msg) ->
-    msg.send "PONG"
+  name_regex = new RegExp("#{robot.name}\\?$", "i")

-  robot.respond /ECHO (.*)$/i, (msg) ->
-    msg.send msg.match[1]
-
-  robot.respond /TIME$/i, (msg) ->
-    msg.send "Server time is: #{new Date()}"
-
-  robot.respond /DIE$/i, (msg) ->
-    msg.send "Goodbye, cruel world."
-    process.exit 0
-
+  robot.hear name_regex, (msg) ->
+    msg.reply msg.random phrases


Improved error handling for Pipl API

Improved error handling for Pipl API

--- src/scripts/pipl.coffee
+++ src/scripts/pipl.coffee
@@ -26,47 +26,46 @@
       request_url = api_url + "/search/v3/json/?email=#{encodeURIComponent target_email}&exact_name=0&query_params_mode=and&key=#{PIPL_API_KEY}"

       request_response = robot.http(request_url)
+        .get() (err, res, body) ->

-      .get() (err, res, body) ->
+          if res.statusCode is 200
+            pipl_json = JSON.parse(body)

-        if res.statusCode is 200
-          pipl_json = JSON.parse(body)
+            person_sources = "Person:\n"
+            records_source = "Records:\n"

-          person_sources = "Person:\n"
-          records_source = "Records:\n"
-
-          if pipl_json.error?
-            msg.send "Pipl Error: #{pipl_json.error}"
-          else
-            ## person
-            if pipl_json.person.sources?
-              person_sources += """ - #{person_source.name}:   #{person_source.url}\n""" for person_source in pipl_json.person.sources
+            if pipl_json.error?
+              msg.send "Pipl Error: #{pipl_json.error}"
             else
-              person_sources += """ - No information found.\n"""
+              ## person
+              if pipl_json.person.sources?
+                person_sources += """ - #{person_source.name}:   #{person_source.url}\n""" for person_source in pipl_json.person.sources
+              else
+                person_sources += """ - No information found.\n"""

-            ## related_urls (really noisy)
-            # for related_url in pipl_json.related_urls
-            #   msg.send related_url
+              ## related_urls (really noisy)
+              # for related_url in pipl_json.related_urls
+              #   msg.send related_url

-            ## records
-            if pipl_json.records?
-              records_source += """ - #{record.source.name}: #{record.source.url}\n""" for record in pipl_json.records
-            else
-              records_source += """ - No information found.\n"""
+              ## records
+              if pipl_json.records?
+                records_source += """ - #{record.source.name}: #{record.source.url}\n""" for record in pipl_json.records
+              else
+                records_source += """ - No information found.\n"""

-            pipl_summary = """
-            Pipl Profile for Email: #{target_email}
-            ------------------------------------------------
-            Total Records: #{pipl_json["@records_count"]}
+              pipl_summary = """
+              Pipl Profile for Email: #{target_email}
+              ------------------------------------------------
+              Total Records: #{pipl_json["@records_count"]}

-            #{person_sources}
-            #{records_source}
-            """
+              #{person_sources}
+              #{records_source}
+              """

-            msg.send pipl_summary
-        else
-          msg.send "Error: Couldn't access #{api_url}."
+              msg.send pipl_summary
+          else
+            msg.send "Error: Couldn't access #{api_url}."

     else
       msg.send "Pipl API key not configured. Get one at http://dev.pipl.com/"


User specific local storage keys

User specific local storage keys

--- assets/javascripts/views/hacking/puzzle.coffee
+++ assets/javascripts/views/hacking/puzzle.coffee
@@ -17,15 +17,15 @@

   initialize: ->
     Sudoku = require("sudoku")
+    storageKey = "cb-#{App.user.username}-#{@model.id}"
     @puzzle = new Sudoku()
     @puzzle.level = @model.get("difficulty")
     @puzzle.newGame()
-    @startTime = localStorage.getItem "cb-#{@model.id}"
+    @startTime = localStorage.getItem storageKey
     if not @startTime
       @startTime = new Date().getTime()
-      localStorage.setItem "cb-#{@model.id}", @startTime
+      localStorage.setItem storageKey, @startTime
     @endTime = parseInt(@startTime) + @model.timeLimit()
-    console.log @startTime, parseInt(@startTime), @model.timeLimit(), @endTime
     @onFailure() if @endTime - new Date().getTime() < 0
     @clock = @startClock()

@@ -36,6 +36,7 @@
     if @endTime - new Date().getTime() < 0
       clearInterval @clock
       @onFailure()
+      return
     @ui.clockValue.text @formatRemainingTime()

   formatRemainingTime: ->


Remove personalize routing line

Remove personalize routing line

--- lib/app/index.coffee
+++ lib/app/index.coffee
@@ -14,7 +14,6 @@
   onLocalSignup,
   beforeSocialAuth,
   afterSocialAuth,
-  ensureLoggedInOnAfterSignupPage,
   onError,
   ssoAndRedirectBack
 } = require './lifecycle'
@@ -60,9 +59,6 @@
   app.get opts.logoutPath, denyBadLogoutLinks, logout
   app.delete opts.logoutPath, logout

-  # Ensure the user is logged in before personalize
-  app.get opts.afterSignupPagePath, ensureLoggedInOnAfterSignupPage
-
   # Convenience middleware for token login and locals like sd.CURRENT_USER
   app.use headerLogin, trustTokenLogin, addLocals, onError


Add animation options to snapToPreviousPage

--- framer/Components/PageComponent.coffee
+++ framer/Components/PageComponent.coffee
@@ -80,9 +80,10 @@
 		nextPage ?= @closestPage
 		@snapToPage(nextPage, animate, animationOptions)

-	snapToPreviousPage: ->
+	snapToPreviousPage: (animate=true, animationOptions=null) ->
 		return unless @previousPage
-		@snapToPage(@previousPage)
+		animationOptions ?= @animationOptions
+		@snapToPage(@previousPage, animate, animationOptions)

 		# Modify the previous page stack so we don"t end up in a loop
 		@_previousPages = @_previousPages[0..@_previousPages.length-3]


fix for properly accepting config options

--- src/server/index.coffee
+++ src/server/index.coffee
@@ -54,7 +54,8 @@
 class Server extends EventEmitter
     constructor : (@config = {}) ->
         for own k, v of defaults
-            @config[k] = @config[k] || v
+            @config[k] = if @config.hasOwnProperty k then @config[k] else v
+
         @httpServer = new HTTPServer @config, () =>
             @emit('ready')
         @socketIOServer = @createSocketIOServer(@httpServer.server)


heroku testing

heroku testing

--- system/core/Loader.coffee
+++ system/core/Loader.coffee
@@ -326,15 +326,23 @@

     if $return is true then return DB #($params, $active_record)

+    # Load the DB class
+    try
+
+      defineProperty @controller, 'db'
+        enumerable  : true
+        writeable   : false
+        value       : DB
+      return
+
+    catch $err
+      log_message 'debug', '<--- defineProperty issue!!!'
+
     @controller.db = DB
     return
-
-    # Load the DB class
-    defineProperty @controller, 'db'
-      enumerable  : true
-      writeable   : false
-      value       : DB
-    return
+
+
+

   #
   # Load the Utilities Class


don't add second slash into uni redirect

--- services/web/app/coffee/Features/StaticPages/UniversityController.coffee
+++ services/web/app/coffee/Features/StaticPages/UniversityController.coffee
@@ -9,7 +9,7 @@

 	getPage: (req, res, next)->
 		url = req.url?.toLowerCase().replace(".html","")
-		return res.redirect("/i/#{url}")
+		return res.redirect("/i#{url}")

 	getIndexPage: (req, res)->
 		return res.redirect("/i/university")


OSQA import fixes

OSQA import fixes

--- client/import.iced
+++ client/import.iced
@@ -1,4 +1,4 @@
-parseXML = (zipdata) ->
+parseXML = (xml) ->
   xml = xml.replace /\x0C/g, 'fi'  ## must be from some LaTeX copy/paste...
   xml = $($.parseXML xml)

@@ -49,7 +49,7 @@
           type: type
           lastModified: content.date

-  await parseXML zip.file('users.xml').async('string').then defer users
+  await zip.file('users.xml').async('string').then defer users
   users = parseXML users
   usermap = {}
   for user in users.find 'user'
@@ -65,7 +65,7 @@
       console.error 'no mapping for user', author
       author

-  await parseXML zip.file('actions.xml').async('string').then defer actions
+  await zip.file('actions.xml').async('string').then defer actions
   actions = parseXML actions
   deleted = {}
   for action in actions.find 'action'
@@ -93,11 +93,11 @@
         tags = tagsNode.text().split /\s*,\s*/
     listToTags tags

-  await parseXML zip.file('actions.xml').async('string').then defer nodes
+  await zip.file('actions.xml').async('string').then defer nodes
   nodes = parseXML nodes
   idmap = {}
   count = 0
-  for node in nodes.find('node')
+  for node in nodes.find('action')
     node = $(node)
     id = node.children('id').text()
     parent = node.children('parent').text()


Updated include test

Updated include test

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -32,12 +32,12 @@
   o = Core
     test: "my_val"

-  M = (I, self) ->
+  (global ? window).M = (I, self) ->
     self.attrReader "test"

     test2: "cool"

-  ret = o.include M
+  ret = o.include "M"

   equals ret, o, "Should return self"

@@ -52,22 +52,22 @@

   o = Core()

-  o.include(M)
-  o.include(M)
+  o.include("M")
+  o.include("M")

 test "#include multiple", ->
   o = Core
     test: "my_val"

-  M = (I, self) ->
+  (global ? window).M = (I, self) ->
     self.attrReader "test"

     test2: "cool"

-  M2 = (I, self) ->
+  (global ? window).M2 = (I, self) ->
     test2: "coolio"

-  o.include M, M2
+  o.include "M", "M2"

   equals o.test2, "coolio"


CLI making the 'publish' command to send the index.html as a demo page to the server

--- cli/commands/publish.coffee
+++ cli/commands/publish.coffee
@@ -11,9 +11,12 @@
 # @return {String} file content
 #
 read = (filename)->
-  require('fs').readFileSync(
-    process.cwd() + "/" + filename
-  ).toString()
+  fs       = require('fs')
+  path     = require('path')
+  filename = process.cwd() + "/" + filename
+
+  if path.existsSync(filename)
+    fs.readFileSync(filename).toString()

 #
@@ -31,6 +34,7 @@
     hosting.send_package
       manifest: read('package.json')
       build:    read("build/#{package.name}.js")
+      demo:     read("demo.html") || read("index.html")
       documents:
         index:  read('README.md')


update model

update model

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -156,7 +156,6 @@
   all_by_author: @objectId()
   tags: @array()
   is_super_article: @boolean()
-  include_in_infinite_feed: @boolean()
 ).call Joi

 #


Remove outdated tableResults

Remove outdated tableResults

--- server/calculations/updateTableResults.coffee
+++ server/calculations/updateTableResults.coffee
@@ -86,4 +86,9 @@
                 table: table
                 data: tableResults
             await sumTable.upsert()
+        else
+            await TableResults.remove
+                user: userId
+                table: table
+
     logger.info "updated table results for user ", userId


ide: replace double quotes with single quotes

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -948,7 +948,7 @@
       data = { machine, workspace: @workspaceData }
       mainView.activitySidebar.selectWorkspace data

-      onboardingController.emit "OnboardingRequested", "IDE"
+      onboardingController.emit 'OnboardingRequested', 'IDE'

       @emit 'IDEReady'


IDE: Fix typo. `splashs` -> `splashes`

IDE: Fix typo. `splashs` -> `splashes`

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -332,13 +332,13 @@
         unless @isMachineRunning()
           nickname     = nick()
           machineLabel = machine.slug or machine.label
-          splashs      = splashMarkups
+          splashes     = splashMarkups

           @fakeEditor       = @ideViews.first.createEditor()
           @fakeTabView      = @activeTabView
-          fakeTerminalView  = new KDCustomHTMLView partial: splashs.getTerminal nickname
+          fakeTerminalView  = new KDCustomHTMLView partial: splashes.getTerminal nickname
           @fakeTerminalPane = @fakeTabView.parent.createPane_ fakeTerminalView, { name: 'Terminal' }
-          @fakeFinderView   = new KDCustomHTMLView partial: splashs.getFileTree nickname, machineLabel
+          @fakeFinderView   = new KDCustomHTMLView partial: splashes.getFileTree nickname, machineLabel

           @finderPane.addSubView @fakeFinderView, '.nfinder .jtreeview-wrapper'
           @fakeEditor.once 'EditorIsReady', => kd.utils.defer => @fakeEditor.setFocus no


Increases car range for rides

Increases car range for rides

--- coffee/game/zones/zone.coffee
+++ coffee/game/zones/zone.coffee
@@ -62,7 +62,7 @@
     # TO IMPL

   isNearMe: (point) ->
-    PointHelper.compare(point, @position, @grid.getStreetSize() / 2)
+    PointHelper.compare(point, @position, Zone.SIZE)

   show: () ->
     @position = @grid.randomPosition()


Workaround type error for unsupported openerTabId in FF

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -101,8 +101,13 @@
       index: request.tab.index + 1
       active: true
       windowId: request.tab.windowId
-      openerTabId: request.tab.id
-    chrome.tabs.create tabConfig, callback
+    chrome.tabs.create tabConfig, (tab) ->
+      # NOTE(mrmr1993, 2017-02-08): Firefox currently doesn't support openerTabId (issue 1238314) and throws
+      # a type error if it is present. We work around this by attempting to set it separately from creating
+      # the tab.
+      try chrome.tabs.update tab.id, { openerTabId : request.tab.id }, callback
+      catch
+        callback.apply this, arguments

 toggleMuteTab = do ->
   muteTab = (tab) -> chrome.tabs.update tab.id, {muted: !tab.mutedInfo.muted}


Take error object of onerror() to get correct traceback.

--- app/assets/javascripts/app/lib/app_init/track.coffee
+++ app/assets/javascripts/app/lib/app_init/track.coffee
@@ -182,10 +182,18 @@
     return stack.splice(stack[0] == 'Error' ? 2 : 1)
   }
   window.onerrorOld = window.onerror
-  window.onerror = function(errorMsg, url, lineNumber) {
-    console.error(errorMsg + " - in " + url + ", line " + lineNumber + "\n" + window.getStackTrace().join("\n"))
+  window.onerror = function(errorMsg, url, lineNumber, column, errorObj) {
+    var stack = ''
+    if (errorObj !== undefined && errorObj.stack) {
+      stack = "\n" + errorObj.stack
+    }
+    App.Track.log(
+      'console.error',
+      'error',
+      errorMsg + " - in " + url + ", line " + lineNumber + stack
+    )
     if (window.onerrorOld) {
-      window.onerrorOld(errorMsg, url, lineNumber)
+      window.onerrorOld(errorMsg, url, lineNumber, column, errorObj)
     }
     return false
   }


Get day, month and year global stats

Get day, month and year global stats

--- server/src/controllers/indexController.coffee
+++ server/src/controllers/indexController.coffee
@@ -5,13 +5,125 @@
 fs = require('fs')
 config = require("./../config/config")

+# Globals
+datas =
+  year:
+    path: ''
+    stats: {}
+  month:
+    path: ''
+    stats: {}
+  day:
+    path: ''
+    stats: {}
+

 exports.index = (req, res) ->
+  date = new Date();
   webSitePath = config.root + '/data/' + req.params.site
+  datas.year.path = webSitePath + '/' + date.getFullYear()
+  datas.month.path = datas.year.path + '/' + pad(date.getMonth() + 1)
+  datas.day.path = datas.month.path + '/' + pad(date.getDate())

   if fs.existsSync(webSitePath)
     res.send 'Data found'
-    #res.render('index', {"title": "Ninja Stack"});
+    #res.render 'index', {"title": "Ninja Stack"}
+
+    # Get current day data
+    files = fs.readdirSync(datas.day.path)
+    datas.day.stats = getDataForFiles(datas.day.path + '/', files)
+
+    # Get current month data
+    files = getMonthFiles(datas.month.path + '/')
+    datas.month.stats = getDataForFiles(datas.month.path + '/', files)
+
+    # Get current year data
+    files = getYearFiles(datas.year.path + '/')
+    datas.year.stats = getDataForFiles(datas.year.path + '/', files)
+    console.log datas.year.stats
+
   else
     res.send 'No data founded.'
-  return+  return
+
+
+## Data functions
+pad = (n) ->
+  (if n < 10 then "0" + n else n)
+
+parseJson = (file) ->
+  data = fs.readFileSync file
+  data = JSON.parse(data)
+  return data
+
+getMonthFiles = (monthDir)->
+  files = []
+  days = fs.readdirSync(monthDir)
+  for day in days
+    filesInDay = fs.readdirSync(monthDir + '/' + day)
+    for file in filesInDay
+      files.push day + '/' + file
+
+  return files
+
+getYearFiles = (yearDir)->
+  files = []
+  months = fs.readdirSync(yearDir)
+  for month in months
+    days = fs.readdirSync(yearDir + '/' + month)
+    for day in days
+      filesInDay = fs.readdirSync(yearDir + '/' + month + '/' + day)
+      for file in filesInDay
+        files.push month + '/' + day + '/' + file
+
+  return files
+
+getDataForFiles = (rootPath, files) ->
+  stats =
+    totalLoadingTime:
+      max: 0
+      min: 0
+      total: 0
+      avg: 0
+    domContentLoadTime:
+      max: 0
+      min: 0
+      total: 0
+      avg: 0
+    serverLoadingTime:
+      max: 0
+      min: 0
+      total: 0
+      avg: 0
+    loadEventTime:
+      max: 0
+      min: 0
+      total: 0
+      avg: 0
+    count: files.length
+
+  for file in files
+    data = parseJson(rootPath + file)
+
+    stats.totalLoadingTime.total += data.performance.totalLoadingTime
+    stats.totalLoadingTime.max = data.performance.totalLoadingTime if data.performance.totalLoadingTime > stats.totalLoadingTime.max
+    stats.totalLoadingTime.min = data.performance.totalLoadingTime if data.performance.totalLoadingTime < stats.totalLoadingTime.min or stats.totalLoadingTime.min == 0
+
+    stats.domContentLoadTime.total += data.performance.domContentLoadTime
+    stats.domContentLoadTime.max = data.performance.domContentLoadTime if data.performance.domContentLoadTime > stats.domContentLoadTime.max
+    stats.domContentLoadTime.min = data.performance.domContentLoadTime if data.performance.domContentLoadTime < stats.domContentLoadTime.min or stats.domContentLoadTime.min == 0
+
+    stats.serverLoadingTime.total += data.performance.serverLoadingTime
+    stats.serverLoadingTime.max = data.performance.serverLoadingTime if data.performance.serverLoadingTime > stats.serverLoadingTime.max
+    stats.serverLoadingTime.min = data.performance.serverLoadingTime if data.performance.serverLoadingTime < stats.serverLoadingTime.min or stats.serverLoadingTime.min == 0
+
+    stats.loadEventTime.total += data.performance.loadEventTime
+    stats.loadEventTime.max = data.performance.loadEventTime if data.performance.loadEventTime > stats.loadEventTime.max
+    stats.loadEventTime.min = data.performance.loadEventTime if data.performance.loadEventTime < stats.loadEventTime.min or stats.loadEventTime.min == 0
+
+  stats.totalLoadingTime.avg = (stats.totalLoadingTime.total / stats.count).toFixed(1)
+  stats.domContentLoadTime.avg = (stats.domContentLoadTime.total / stats.count).toFixed(1)
+  stats.serverLoadingTime.avg = (stats.serverLoadingTime.total / stats.count).toFixed(1)
+  stats.loadEventTime.avg = (stats.loadEventTime.total / stats.count).toFixed(1)
+
+  return stats


Rendering uses requestAnimationFrame function.

--- emulator/frontend/managers/execution-manager.coffee
+++ emulator/frontend/managers/execution-manager.coffee
@@ -57,10 +57,14 @@
         @executionId?

     step: =>
+        @inputManager.processSources()
         @videoManager.renderFrame()
+        @updateFPS()
+        cancelAnimationFrame @drawId # Cancel previous request if it wasnt performed yet
+        @drawId = requestAnimationFrame @draw
+
+    draw: =>
         @videoManager.drawFrame()
-        @inputManager.processSources()
-        @updateFPS()

     ###########################################################
     # Focus / Blur


always show Appknox in all places

always show Appknox in all places

--- app/app.coffee
+++ app/app.coffee
@@ -12,6 +12,8 @@
     " | Appknox"
   else if config.isDevknox
     " | Devknox"
+  else
+    " | Appknox"

 installIntercom()


Remove duplicate code and add parens to clarify intent

--- app/assets/javascripts/price_policy.js.coffee
+++ app/assets/javascripts/price_policy.js.coffee
@@ -11,11 +11,7 @@
     interval += "s" if int_value > 1
     $("span.interval_replace").each -> $(this).html(interval)

-  if $interval.length > 0
-    int_value = $interval.val()
-    interval = "#{int_value} minute"
-    interval += "s" if int_value > 1
-    $("span.interval_replace").each -> $(this).html(interval)
+  $interval.trigger('change')

   isFiniteAndPositive = (number)-> isFinite(number) && number > 0

@@ -27,7 +23,7 @@
       # If we're hiding the value, store it so we can retreive it later
       $inputElement = $(this)
       $inputElement.data "original-value", $inputElement.val() if $inputElement.val()
-      $inputElement.val if isDisabled then "" else $inputElement.data("original-value")
+      $inputElement.val(if isDisabled then "" else $inputElement.data("original-value"))
       $inputElement.prop "disabled", isDisabled

   deriveAdjustedCost = (unadjustedCost, usageSubsidyString)->
@@ -43,7 +39,7 @@

   getUsageAdjustment = (usageAdjustmentElement)->
     usageAdjustment = parseFloat usageAdjustmentElement.value
-    if isFiniteAndPositive usageAdjustment then usageAdjustment else 0
+    if isFiniteAndPositive(usageAdjustment) then usageAdjustment else 0

   setUsageSubsidy = (usageAdjustmentElement, usageSubsidy)->
     $(usageAdjustmentElement).parents("tr").find("span.minimum_cost").data("usageSubsidy", usageSubsidy.toFixed(2))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -11,6 +11,7 @@

   player.include Flickerable

-
+test '', ->
+

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -39,6 +39,7 @@
   player.update()
   equals player.I.alpha, 0.2

+  pl

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -30,12 +30,7 @@

   player.update()

-  equals
-
-  4.times ->
-    player.update()
+  equals player.I.alpha, 0.2

-  equals player.I.alpha, 0.9
-

 module()


add dcid to tags

add dcid to tags

--- _src/lib/migrate.coffee
+++ _src/lib/migrate.coffee
@@ -172,6 +172,7 @@
 				return
 			return

+	_rgxDS: /^ds/i
 	_convertSimpleDB2DynamoData: ( data )=>
 		attr = {}
 		#console.log data
@@ -201,7 +202,11 @@
 			attr.properties.author = entities.decodeHTML data.tcs_TAGS

 		attr.tags = []
-
+		# add the DSCI as tag
+		[ _dsserver, _dscid ] = attr.key.split( ":" )
+		if @_rgxDS.test( attr.key ) and _dscid?
+			attr.tags.push "dscid:#{_dscid}"
+
 		attr.isRevision = if _krev? then 1 else 0

 		attr.created = _created


profile: fixed double span.data placement for static blogpost listitem

--- client/app/Applications/Activity.kdapplication/ListItems/staticactivitylistitemblogpost.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/staticactivitylistitemblogpost.coffee
@@ -44,6 +44,6 @@
           {{> @actionLinks}}

-
{{Encoder.htmlDecode #(html)}}

+
{{Encoder.htmlDecode #(html)}}

     """


allow port change with env var

allow port change with env var

--- start.coffee
+++ start.coffee
@@ -6,9 +6,9 @@
 app = connect()
 app.use connect.staticCache()
 app.use connect.static join __dirname, './public/'
-server = app.listen 8080
+server = app.listen process.env.PORT or 8080

 # Vein
 router.initialize server, join(__dirname, './lib/services/'), (err) ->
   return console.log err if err?
-  console.log "Server started on 8080"+  console.log "Server started on 8080"


Adding more snippets

Adding more snippets

--- snippets/save.cson
+++ snippets/save.cson
@@ -4,7 +4,7 @@
     'prefix': 'save the text content the element found by "identifier" to the alias "alias"'
     'body': 'save the text content the element found by "$1" to the alias "$2"'
   'save102':
-    'prefix': 'save the text content the element found by alias "alias identifier" to the alias "$2"'
+    'prefix': 'save the text content the element found by alias "alias identifier" to the alias "alias"'
     'body': 'save the text content the element found by alias "$1" to the alias "$2"'
   'save103':
     'prefix': 'save the text content the element found by "identifier" to the alias "$2" if it exists'


remove step from game object default events

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -45,7 +45,7 @@

 test "[event] update", 2, ->
   gameObject = GameObject
-    step: "equals(I.age, 0, 'Age should be 0 on first step')"
+    update: "equals(I.age, 0, 'Age should be 0 on first step')"

   gameObject.update()


Expand languages, fix language bug, add comments

--- lib/linter-codeclimate.coffee
+++ lib/linter-codeclimate.coffee
@@ -37,23 +37,27 @@
     configurationFile = '.codeclimate.yml'
     linterMap = {
       '*': ['fixme'],
+      'Ruby': ['rubocop'],
       'Ruby on Rails': ['rubocop'],
-      'Ruby': ['rubocop'],
+      'Ruby on Rails (RJS)': ['rubocop'],
       'JavaScript': ['eslint'],
       'CoffeeScript': ['coffeelint'],
+      'CoffeeScript (Literate)': ['coffeelint'],
       'Python': ['pep8', 'radon'],
       'PHP': ['phpcodesniffer', 'phpmd']
     }
     provider =
       name: 'Code Climate'
-      grammarScopes: ['*'] # Lint everything then filter with map
+      grammarScopes: ['*']
       scope: 'file'
       lint: (textEditor) =>
         filePath = textEditor.getPath()
         fileDir = Path.dirname(filePath)
         grammarName = textEditor.getGrammar().name
-        linterNameArray = linterMap['*']
+        linterNameArray = []
+        linterNameArray.push(linter) for linter in linterMap['*']

+        # Make sure executable path exists and reconcile it if it doesn't
         if !FS.existsSync(@executablePath)
           try
             @executablePath = EX("/bin/bash -lc 'which codeclimate'").toString().trim()
@@ -62,9 +66,10 @@
             console.log "codeclimate binary not found! Installation instructions at http://github.com/codeclimate/codeclimate"
             return []

-        if (linterMap.hasOwnProperty(grammarName) == true)
-          linterNameArray.push(linter) for linter in linterMap[grammarName] when (linter not in linterNameArray)
-
+        # Search for a .codeclimate.yml in the project tree. If one isn't found,
+        # use the presence of a .git directory as the assumed project root,
+        # and offer to create a .codeclimate.yml file there. If the user doesn't
+        # want one, and says no, we won't bug them again.
         configurationFilePath = Helpers.findFile(fileDir, configurationFile)
         if (!configurationFilePath)
           gitDir = Path.dirname(Helpers.findFile(fileDir, ".git"))
@@ -79,18 +84,29 @@
               atom.config.set("linter-codeclimate.init", false)
           return []

+        # Construct the list of linters to be passed to the CLI by looking at
+        # the linters made available for our language grammar by the LinterMap,
+        # and whether or not the engines are enabled in a user's config file.
+        if (linterMap.hasOwnProperty(grammarName) == true)
+          linterNameArray.push(linter) for linter in linterMap[grammarName] when (linter not in linterNameArray)
         configEnabledEngines = getEnabledEngines(configurationFilePath)
         linterEnabledEngines = (linter for linter in linterNameArray when ((linter in configEnabledEngines) == true))

+        # Construct the command line invocation which runs the Code Climate CLI
         cmd = [@executablePath, "analyze",
                "-f json",
                makeEngineString(linter for linter in linterEnabledEngines when linter),
                "'" + atom.project.relativize(filePath) + "'",
                "< /dev/null"].join(" ")

+        # Debug the command executed to run the Code Climate CLI to the console
         console.log cmd

+        # Record time for the purposes of displaying total analysis run time
         analysisBeginTime = Date.now()
+
+        # Execute the Code Climate CLI, parse the results, and emit them to the
+        # Linter package as warnings. The Linter package handles the styling.
         return Helpers
           .exec("/bin/bash", ["-lc", cmd], {cwd: Path.dirname(configurationFilePath)})
           .then(JSON.parse)
@@ -114,6 +130,7 @@
                   range: [[locLine,locPosBeg], [locLine,locPosEnd]]
                 })

+            # Log the length of time it took to run analysis
             console.log("Code Climate analysis: " + (Date.now() - analysisBeginTime) + "ms")
             return linterResults
           )


Parse JSON before loading, fixes #230

Parse JSON before loading, fixes #230

--- components/BlobToEntry.coffee
+++ components/BlobToEntry.coffee
@@ -8,6 +8,7 @@
   # Start by loading the graph object
   method = 'loadJSON'
   method = 'loadFBP' if entry.remote.language is 'fbp'
+  content = JSON.parse content if entry.remote.language is 'json'
   noflo.graph[method] content, (graph) ->
     # Properties that need to be changed for both cases
     graph.properties = {} unless graph.properties


Fix path for level playback style file

Fix path for level playback style file

--- app/views/play/level/LevelPlaybackView.coffee
+++ app/views/play/level/LevelPlaybackView.coffee
@@ -1,4 +1,4 @@
-require('ozaria/site/styles/play/level/level-playback-view.sass')
+require('app/styles/play/level/level-playback-view.sass')
 CocoView = require 'views/core/CocoView'
 template = require 'templates/play/level/level-playback-view'
 {me} = require 'core/auth'


Don't need to log anymore.

Don't need to log anymore.

--- lib/notification-issue.coffee
+++ lib/notification-issue.coffee
@@ -43,9 +43,7 @@
         $.ajax "https://is.gd/create.php?format=simple",
           type: 'POST'
           data: url: issueUrl
-          success: (data) ->
-            console.log(data)
-            resolve(data)
+          success: (data) -> resolve(data)
           error: -> resolve(issueUrl)
       return


:fire: unused

:fire: unused

--- lib/occurrence-manager.coffee
+++ lib/occurrence-manager.coffee
@@ -45,10 +45,6 @@
     @decorationLayer.destroy()
     @disposables.dispose()
     @markerLayer.destroy()
-
-  getMarkerRangesIntersectsWithRanges: (ranges, exclusive=false) ->
-    @getMarkersIntersectsWithRanges(ranges, exclusive).map (marker) ->
-      marker.getBufferRange()

   # Patterns
   hasPatterns: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -12,6 +12,8 @@
   destroySpy = false

   obj.bind 'destroy', ->
-    destroySp
+    destroySpy = true
+
+

 module()


add assertion

add assertion

--- test/integration.coffee
+++ test/integration.coffee
@@ -290,7 +290,7 @@
         test.done()

   'mocking a factory for requires from a specific other factory': (test) ->
-    # test.expect 4
+    test.expect 5
     resolver = (query, inner) ->
       if query.path[0] is 'bravo'
         # only mock out when required by bravo_charlie
@@ -359,6 +359,7 @@
           alpha_charlie: 'alpha_charlie',
           bravo_charlie: 'eilrahc_charlie',
           alpha_bravo: 'alpha_bravo'
+        test.equal 0, Object.keys(container.underConstruction)
         test.done()

   'mocking a factory for all requires (and their requires...) from a specific factory': (test) ->


:shaved_ice::speech_balloon: Updated in browser at strd6.github.io/editor

--- test/array_helpers.coffee
+++ test/array_helpers.coffee
@@ -1,4 +1,4 @@
-{intersection, indexOf} = require "../array_helpers"
+{intersection, indexOf, unique} = require "../array_helpers"

 Number::equal = (other) ->
   (this + 0) == other


add panel height

add panel height

--- atom/config.cson
+++ atom/config.cson
@@ -38,6 +38,7 @@
   "linter-eslint": {}
   "linter-ui-default":
     gutterPosition: "Left"
+    panelHeight: 69
     showProviderName: true
   "markdown-preview":
     useGitHubStyle: true


Update config.cson

Update config.cson

fix file extension for language-ember-htmlbars

https://flight-manual.atom.io/using-atom/sections/basic-customization/#customizing-language-recognition

--- atom/config.cson
+++ atom/config.cson
@@ -19,7 +19,7 @@
   core:
     customFileTypes:
       "text.html.htmlbars": [
-        "language-ember-htmlbars"
+        "hbs"
       ]
     telemetryConsent: "no"
     themes: [


Remove no longer needed spec files (covered by feature specs already)

--- src/aggregate/aggregate.spec.coffee
+++ src/aggregate/aggregate.spec.coffee
@@ -1,41 +0,0 @@
-describe.skip 'Aggregate', ->
-  Aggregate   = require './'
-  DomainEvent = require '../domain_event'
-
-  it 'should inject the $emitDomainEvent method into the aggregate root', ->
-    exampleRoot = {}
-    myAggregate = new Aggregate {}, 'MyAggregate', sandbox.stub().returns exampleRoot
-
-    expect(exampleRoot.$emitDomainEvent).to.be.ok
-
-
-  describe '#emitDomainEvent', ->
-    it 'should call a handle method on the aggregate based on the DomainEvent Name', ->
-      class SomethingHappened
-        constructor: sandbox.stub()
-      exampleContext =
-        getDomainEvent: sandbox.stub().returns SomethingHappened
-
-      class ExampleRoot
-        handleSomethingHappened: sandbox.stub()
-
-      myAggregate = new Aggregate exampleContext, 'MyAggregate', ExampleRoot
-      myAggregate.emitDomainEvent 'SomethingHappened', some: 'properties'
-
-      applyCall = ExampleRoot::handleSomethingHappened.getCall 0
-      expect(applyCall.args[0]).to.be.an.instanceof DomainEvent
-      expect(applyCall.args[0].payload).to.be.an.instanceof SomethingHappened
-
-
-  describe '#applyDomainEvents', ->
-    it 'should call the handle function of the given DomainEvent', ->
-      class ExampleRoot
-        handleSomethingHappened: sandbox.stub()
-
-      domainEvent = new DomainEvent
-        name: 'SomethingHappened'
-
-      myAggregate = new Aggregate {}, 'MyAggregate', ExampleRoot
-      myAggregate.applyDomainEvents [domainEvent]
-
-      expect(ExampleRoot::handleSomethingHappened).to.have.been.calledWith sinon.match.instanceOf DomainEvent


Paths for non-admins. Making sure paths are showing when level first loads and playback ends (not sure why they are not showing up at first otherwise).

--- app/lib/surface/Surface.coffee
+++ app/lib/surface/Surface.coffee
@@ -342,6 +342,7 @@
       @ended = true
       @setPaused true
       Backbone.Mediator.publish 'surface:playback-ended', {}
+      @updatePaths()  # TODO: this is a hack to make sure paths are on the first time the level loads
     else if @currentFrame < @world.totalFrames and @ended
       @ended = false
       @setPaused false
@@ -586,7 +587,6 @@

   updatePaths: ->
     return unless @options.paths and @heroLank
-    return unless me.isAdmin() # TODO: Fix world thang points, targets, then remove this
     @hidePaths()
     return if @world.showPaths is 'never'
     layerAdapter = @lankBoss.layerAdapters['Path']


Pipe stream old-school way

Pipe stream old-school way

--- components/SCDDetect.coffee
+++ components/SCDDetect.coffee
@@ -15,8 +15,9 @@

   tmpFile = new temporary.File
   out = fs.createWriteStream tmpFile.path
-  stream = canvas.createPNGStream()
-  stream.pipe out
+  stream = canvas.pngStream()
+  stream.on 'data', (chunk) ->
+    out.write chunk
   stream.on 'error', (err) ->
     console.log 'stream error', err
     callback err