Add license list

Add license list

--- src/about.coffee
+++ src/about.coffee
@@ -13,7 +13,7 @@

About this application:

@@ -30,19 +30,21 @@
     for key in ["platform", "application"]
       msg += """

-

Rubic #{key} is supported by:

+

Rubic #{key} is supported by:

       """
       index = 0
-      for item in @_list[key]
+      for item in @_supporters[key]
+        name = escapeHtml(item.name)
+        desc = escapeHtml(item.desc)
         msg += "
\n" if (index % 3) == 0
         msg += """

-
+
         """
         if item.image?
           msg += "\n"
         else
-          msg += "#{item.name}\n"
+          msg += "#{name}\n"
         msg += """

@@ -54,10 +56,19 @@

         """
+    for key, lic of @_licenses
+      msg += """
+

+

License (#{escapeHtml(key)}):

+

+

+          #{escapeHtml(lic).replace(/\n/g, "
")}
+

+

+      """
     msg += "
"
     bootbox.dialog({
       message: msg
-      closeButton: false
       buttons: {success: {label: "OK"}}
     })

@@ -72,25 +83,29 @@
   @private
   List of supporters
   ###
-  @_list:
+  @_supporters:
     platform: [
       {
-        image: "mruby_logo_red.svg"
+        image: "mruby_logo_red_cropped.png"
         name: "mruby"
         desc: "The lightweight implementation of the Ruby language"
         url: "http://www.mruby.org/"
+        license: ["MIT License"]      # checked on 2015/03/28
       }
       {
         image: "peridotcraft_logo_1.png"
         name: "PERIDOT"
         desc: "Simple & Compact FPGA"
         url: "https://peridotcraft.com/"
-      }
-      {
-        image: "Emscripten_logo_full.png"
+        license: ["Apache License, Version 2.0"]  # checked on 2015/03/28
+      }
+      {
+        image: "Emscripten_logo_full_cropped.png"
         name: "emscripten"
         desc: "An LLVM-based project that compiles C and C++ into highly-optimizable JavaScript"
         url: "http://kripken.github.io/emscripten-site/"
+        license: ["MIT License",      # checked on 2015/03/28
+                  "University of Illinois/NCSA Open Source License"]
       }
     ]
     application: [
@@ -98,36 +113,134 @@
         name: "jQuery"
         desc: "A fast, small, and feature-rich JavaScript library"
         url: "https://jquery.com/"
+        license: ["MIT License"]      # checked on 2015/03/29
       }
       {
         name: "Bootstrap"
         desc: "A sleek, intuitive, and powerful movile first front-end framework for faster and easier web development"
         url: "http://getbootstrap.com/"
+        license: ["MIT License"]      # checked on 2015/03/29
       }
       {
         name: "Ace"
         desc: "The high performance code editor for the web"
         url: "http://ace.c9.io/"
+        license: ["BSD Simplified"]   # checked on 2015/03/29
       }
       {
         name: "Bootstrap Notify"
-        desc: "The plugin helps to turn standard bootstrap alerts into “glowl” like notifications"
+        desc: "The plugin helps to turn standard bootstrap alerts into \"glowl\" like notifications"
         url: "http://bootstrap-growl.remabledesigns.com/"
+        license: ["MIT License"]      # checked on 2015/03/29
       }
       {
         name: "Bootbox.js"
         desc: "Alert, confirm and flexible modal dialogs for the Bootstrap framework"
         url: "http://bootboxjs.com/"
+        license: ["MIT License"]      # checked on 2015/03/29
       }
       {
         name: "js-yaml"
         desc: "YAML parser for JavaScript"
         url: "http://nodeca.github.io/js-yaml/"
+        license: ["MIT License"]      # checked on 2015/03/29
       }
       {
         name: "spin.js"
         desc: "A spinning activity indicator"
         url: "http://fgnass.github.io/spin.js/"
+        license: ["MIT License"]      # checked on 2015/03/29
       }
     ]

+  ###*
+  @private
+  License declarations
+  ###
+  @_licenses: {
+    "Rubic": """
+    Copyright (c) 2015 Shuta Kimura (@kimu_shu)
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "mruby": """
+    Copyright (c) 2014 mruby developers
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "canarium.js for PERIDOT": """
+    Copyright (C) 2014, J-7SYSTEM Works. All rights Reserved.
+    (Apache License, Version 2.0)
+
+    * This module is a free sourcecode and there is NO WARRANTY.
+    * No restriction on use. You can use, modify and redistribute it for personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY.
+    * Redistributions of source code must retain the above copyright notice.
+
+    PERIDOT Project - https://github.com/osafune/peridot
+    """
+    #----------------------------------------------------------------
+    "emscripten": """
+    Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "Node.js included in emscripten": """
+    Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "jQuery": """
+    Copyright jQuery Foundation and other contributors, https://jquery.org/
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "Bootstrap": """
+    Copyright (c) 2011-2015 Twitter, Inc
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "Ace": """
+    Copyright (c) 2010, Ajax.org B.V. All rights reserved.
+    (3-Clause BSD License)
+
+    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+        * Neither the name of Ajax.org B.V. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    """
+    #----------------------------------------------------------------
+    "bootstrap-notify": """
+    Copyright (c) 2014 Robert McIntosh
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "Bootbox.js": """
+    Copyright (C) 2011-2014 by Nick Payne nick@kurai.co.uk
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "js-yaml": """
+    Copyright (C) 2011-2015 by Vitaly Puzrin
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "spin.js": """
+    Copyright (c) 2011-2014 Felix Gnass [fgnass at neteye dot de]
+    (The MIT License -- The permission notice is included at the end of this dialog)
+    """
+    #----------------------------------------------------------------
+    "The MIT License": """
+    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+    """
+    #----------------------------------------------------------------
+  }
+


Really fixed cross-platform undo/redo

Really fixed cross-platform undo/redo

--- src/code/mixins/app-view.coffee
+++ src/code/mixins/app-view.coffee
@@ -65,13 +65,23 @@
     else
       @props.linkManager.loadDataFromUrl @props.url

-    ($ window).on 'keyup', (e) =>
-      y = e.keyCode is 89
-      z = e.keyCode is 90
-      if (e.ctrlKey or e.metaKey) and (y or z)
+    # cross platform undo/redo
+    ($ window).on 'keydown', (e) =>
+      y = (e.keyCode is 89) or (e.keyCode is 121)
+      z = (e.keyCode is 90) or (e.keyCode is 122)
+      return if not (y or z)
+      if e.metaKey
+        undo = z and not e.shiftKey
+        redo = z and e.shiftKey
+      else if e.ctrlKey
+        undo = z
+        redo = y
+      else
+        undo = redo = false
+      if undo or redo
         e.preventDefault()
-        @props.linkManager.redo() if y
-        @props.linkManager.undo() if z
+        @props.linkManager.redo() if redo
+        @props.linkManager.undo() if undo

   componentDidUnmount: ->
     @addDeleteKeyHandler false


:art: start at the first step when reviewing

--- src/components/performance.cjsx
+++ src/components/performance.cjsx
@@ -120,15 +120,15 @@
       when 'not_started' then 'Not started'

     {courseId} = @props
-    linkParams = {courseId, id: cell.id}
-
-    <Router.Link to='viewTask' params={linkParams}>{status}</Router.Link>
+    linkParams = {courseId, id: cell.id, stepIndex: 1}
+
+    <Router.Link to='viewTaskStep' params={linkParams}>{status}</Router.Link>

   renderHomeworkCell: (cell) ->
     {courseId} = @props
-    linkParams = {courseId, id: cell.id}
-
-    <Router.Link to='viewTask' params={linkParams}>
+    linkParams = {courseId, id: cell.id, stepIndex: 1}
+
+    <Router.Link to='viewTaskStep' params={linkParams}>
       {cell.correct_exercise_count}/{cell.exercise_count}
     </Router.Link>


Fixing small typo. (#2298)

Fixing small typo. (#2298)

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -3,7 +3,7 @@
 os = require 'os'

 # This is the main Gruntfile that manages building N1 distributions.
-# The reason it's inisde of the build/ folder is so everything can be
+# The reason it's inside of the build/ folder is so everything can be
 # compiled against Node's v8 headers instead of Chrome's v8 headers. All
 # packages in the root-level node_modules are compiled against Chrome's v8
 # headers.


Closes #77 - Adding support for the Angular `$log` provider, adding logic to disable the `$log` calls if `Env.env == "production"`

--- app/assets/javascripts/sprangular/module.coffee
+++ app/assets/javascripts/sprangular/module.coffee
@@ -24,7 +24,7 @@
       newInstance

 # Default Headers
-Sprangular.config ["$httpProvider", "$locationProvider", ($httpProvider, $locationProvider) ->
+Sprangular.config ["$httpProvider", "$locationProvider", ($httpProvider, $locationProvider, $logProvider) ->
   $httpProvider.defaults.headers.common['Accept'] = 'application/json'
   $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
   $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded'
@@ -32,6 +32,9 @@
   $locationProvider
     .html5Mode false
     .hashPrefix '!'
+
+  $logProvider
+    .debugEnabled (Env.env isnt "production")
 ]

 Sprangular.run ($rootScope, $location, Status, Account, Cart, Flash) ->


Protect against null config in pivotal

Protect against null config in pivotal

--- plugins/pivotal-tracker/index.coffee
+++ plugins/pivotal-tracker/index.coffee
@@ -11,7 +11,7 @@
     params =
       "story[name]": "#{event.error.exceptionClass} in #{event.error.context}"
       "story[story_type]": "bug"
-      "story[labels]": (config.labels || "bugsnag").trim()
+      "story[labels]": (config?.labels || "bugsnag").trim()
       "story[description]":
         """
         *#{event.error.exceptionClass}* in *#{event.error.context}*


Thang Editor now centers unit better, lists action names in a more useful order.

--- app/views/editor/thang/ThangTypeEditView.coffee
+++ app/views/editor/thang/ThangTypeEditView.coffee
@@ -21,7 +21,7 @@
 template = require 'templates/editor/thang/thang-type-edit-view'
 storage = require 'core/storage'

-CENTER = {x: 200, y: 300}
+CENTER = {x: 200, y: 400}

 commonTasks = [
   'Upload the art.'
@@ -189,7 +189,9 @@
     context.fileSizeString = @fileSizeString
     context

-  getAnimationNames: -> _.keys(@thangType.get('actions') or {})
+  getAnimationNames: ->
+    _.sortBy _.keys(@thangType.get('actions') or {}), (a) ->
+      {move: 1, cast: 2, attack: 3, idle: 4, portrait: 6}[a] or 5

   afterRender: ->
     super()


Adding auto-loading of schedules to Schedules service via constructor

--- app/assets/javascripts/admin/order_cycles/services/schedules.js.coffee
+++ app/assets/javascripts/admin/order_cycles/services/schedules.js.coffee
@@ -1,7 +1,16 @@
-angular.module("admin.orderCycles").factory "Schedules", ($q, RequestMonitor, ScheduleResource, OrderCycles, Dereferencer) ->
+angular.module("admin.orderCycles").factory "Schedules", ($q, $injector, RequestMonitor, ScheduleResource, OrderCycles, Dereferencer) ->
   new class Schedules
+    # all: []
     byID: {}
-    # all: []
+
+    constructor: ->
+      if $injector.has('schedules')
+        @load($injector.get('schedules'))
+
+    load: (schedules) ->
+      for schedule in schedules
+        # @all.push schedule
+        @byID[schedule.id] = schedule

     add: (params) =>
       ScheduleResource.create params, (schedule) =>
@@ -35,9 +44,6 @@
           InfoDialog.open 'error', "Could not delete schedule: #{schedule.name}"

     index: ->
-      request = ScheduleResource.index (data) =>
-        @byID[schedule.id] = schedule for schedule in data
-        data
-        # @all = data
+      request = ScheduleResource.index (data) => @load(data)
       RequestMonitor.load(request.$promise)
       request


fix updating Marionette v2

fix updating Marionette v2

--- app/assets/javascripts/admin/views/tasktype/task_type_form_view.coffee
+++ app/assets/javascripts/admin/views/tasktype/task_type_form_view.coffee
@@ -131,14 +131,6 @@

   initialize : ->
-
-    @teamSelectionView = new SelectionView(
-      collection : new TeamCollection()
-      childViewOptions :
-        modelValue: -> return "#{@model.get("name")}"
-      data : "amIAnAdmin=true"
-      name: "team"
-    )

     if @options.isEditForm
       @model.fetch().done(=> @prefillForm())
@@ -201,5 +193,12 @@

   onRender : ->

-    @team.show(@teamSelectionView)
-
+    teamSelectionView = new SelectionView(
+      collection : new TeamCollection()
+      childViewOptions :
+        modelValue: -> return "#{@model.get("name")}"
+      data : "amIAnAdmin=true"
+      name: "team"
+    )
+    @team.show(teamSelectionView)
+


Made logic wait document load end

Made logic wait document load end

--- app/assets/javascripts/backend/purchase_process/purchase_orders.coffee
+++ app/assets/javascripts/backend/purchase_process/purchase_orders.coffee
@@ -2,30 +2,31 @@
   'use strict'

   createNewParcelReception = () ->
-    $(document).ready ->
-      $newParcelBtn = $('#generate-parcel-btn')
-      newParcelUrl = $newParcelBtn.prop('href')
+    $newParcelBtn = $('#generate-parcel-btn')
+    newParcelUrl = $newParcelBtn.prop('href')

-      $(document).on 'change', 'input[data-list-selector]', ->
-        selectedOrders = $('input[data-list-selector]:checked').filter ->
-                           /\d/.test($(this).data('list-selector'))
-        selectedOrdersIds = selectedOrders.map ->
-                              $(this).data('list-selector')
-                            .toArray()
-        reconciledOrders = selectedOrders.filter ->
-                             $(this).closest('tr').data('reconciliation-state') == 'reconcile'
+    $(document).on 'change', 'input[data-list-selector]', ->
+      selectedOrders = $('input[data-list-selector]:checked').filter ->
+                         /\d/.test($(this).data('list-selector'))
+      selectedOrdersIds = selectedOrders.map ->
+                            $(this).data('list-selector')
+                          .toArray()
+      reconciledOrders = selectedOrders.filter ->
+                           $(this).closest('tr').data('reconciliation-state') == 'reconcile'

-        if selectedOrdersIds.length > 0
-          $newParcelBtn.prop('href', "#{newParcelUrl}?mode=prefilled&purchase_order_ids=#{selectedOrdersIds}")
-        else
-          $newParcelBtn.prop('href', newParcelUrl)
-
-        disabled = !selectedOrders.length || reconciledOrders.length
-        # !! so we're sure disabled is a Boolean and not just truthy/falsy — for jQuery
-        $newParcelBtn.toggleClass('disabled', !!disabled)
+      if selectedOrdersIds.length > 0
+        $newParcelBtn.prop('href', "#{newParcelUrl}?mode=prefilled&purchase_order_ids=#{selectedOrdersIds}")
+      else
+        $newParcelBtn.prop('href', newParcelUrl)
+
+      disabled = !selectedOrders.length || reconciledOrders.length
+      # !! so we're sure disabled is a Boolean and not just truthy/falsy — for jQuery
+      $newParcelBtn.toggleClass('disabled', !!disabled)

-  $('#purchase_orders-list').each createNewParcelReception()
-  $('#unreceived_purchase_orders-list').each createNewParcelReception()
+  $(document).ready ->
+    createNewParcelReception() if $('#purchase_orders-list, #unreceived_purchase_orders-list').length > 0
+
+

 ) ekylibre, jQuery


Added translation for co-firing coal CHP

--- app/assets/javascripts/lib/views/plant_profitability_table_view.coffee
+++ app/assets/javascripts/lib/views/plant_profitability_table_view.coffee
@@ -61,14 +61,14 @@
     energy_power_nuclear_gen2_uranium_oxide: 'nuclear_ii'
     energy_power_nuclear_gen3_uranium_oxide: 'nuclear_iii'
     energy_power_supercritical_coal: 'coal_conv'
-    energy_power_engine_network_gas: 'gas_engine'
     energy_power_turbine_network_gas: 'gas_turbine'
     energy_power_ultra_supercritical_ccs_coal: 'coal_pwd_ccs'
     energy_power_ultra_supercritical_coal: 'coal_pwd'
     energy_power_ultra_supercritical_cofiring_coal: 'coal_pwd_cofiring'
     energy_power_ultra_supercritical_crude_oil: 'oil_plant'
     energy_power_ultra_supercritical_lignite: 'lignite'
-    energy_power_ultra_supercritical_network_gas: 'gas_conv'
+    energy_power_ultra_supercritical_network_gas: 'gas_conv',
+    energy_chp_ultra_supercritical_cofiring_coal: 'coal_chp_cofiring'

   # Sort by two fields
   #


sorted, adding this mod will maybe resuilt in unwanted behaviour


watch for updated vendor files.

watch for updated vendor files.

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -61,6 +61,7 @@

 gulp.task "watch", ["build"], ->
   gulp.watch path.wrapper + "*.js", ["build"]
+  gulp.watch path.vendor + "*.js", ["build"]
   gulp.watch path.public + "**/*.coffee", ["build"]


feat(web): add missing methods to angular service;

--- apps/actor-web/app/services/actorService.coffee
+++ apps/actor-web/app/services/actorService.coffee
@@ -1,112 +1,125 @@
 class ActorService
+  isLoggedIn: false
   messenger: null
   currentPeer: null
-  isLoggedIn: false

-  constructor: (@$rootScope, @$sessionStorage) ->
+  constructor: (@$rootScope) ->
     console.log '[AW]ActorService constructor'
-    window.jsAppLoaded = =>
-      @initActor()
-      console.log '[AW]ActorService $broadcast: actorReady'
-      @$rootScope.$broadcast 'actorReady'
+    window.jsAppLoaded = => @initActor()

   initActor: ->
     console.log '[AW]ActorService initActor'
-    window.messenger = @messenger = new actor.ActorApp
-    @isLoggedIn = @messenger.isLoggedIn()
+    @messenger = new actor.ActorApp
+    window.messenger = @messenger
+    @setLoggedIn() if @messenger.isLoggedIn()
     console.log '[AW]ActorService initActor: @isLoggedIn:', @isLoggedIn
-    if @isLoggedIn then @setLoggedIn() else @setLoggedOut()
+    console.log '[AW]ActorService initActor: $broadcast: actorReady'
+    @$rootScope.$broadcast 'actorReady'

-  # isLoggedIn: ->
-  #   @messenger.isLoggedIn()
-
-  # checkAccess: (event, toState, toParams, fromState, fromParams) ->
-  #   console.log '[AW]ActorService checkAccess'
-  #   if toState.data != undefined
-  #     if toState.data.noLogin != undefined && toState.data.noLogin
-  #       console.log '[AW]ActorService checkAccess: before login'
-  #       return
-  #   else
-  #     if @isLoggedIn
-  #       console.log '[AW]ActorService checkAccess: authenticated'
-  #       @$rootScope.isLoggedIn = @$sessionStorage.isLoggedIn
-  #     else
-  #       console.log '[AW]ActorService checkAccess: redirect to login'
-  #       event.preventDefault()
-  #       @$rootScope.$state.go 'login'

   setLoggedIn: () =>
     console.log '[AW]ActorService setLoggedIn'
+    @isLoggedIn = true
     @$rootScope.$state.go 'home'
     @$rootScope.$broadcast 'actorLoggedIn'
-
   setLoggedOut: () =>
     console.log '[AW]ActorService setLoggedOut'
     localStorage.clear()
     @isLoggedIn = false
     @$rootScope.$state.go 'login'
     @$rootScope.$broadcast 'actorLoggedOut'
+  setCurrentPeer: (peer) ->
+    console.log '[AW]ActorService setCurrentPeer'
+    console.log '[AW]ActorService setCurrentPeer: peer:', peer
+    @currentPeer = peer
+
+  bindChat: (peer, callback) ->
+    console.log '[AW]ActorService bindChat'
+    console.log '[AW]ActorService bindChat: peer:', peer
+    @messenger.bindChat peer, callback
+  bindDialogs: (callback) ->
+    console.log '[AW]ActorService bindDialogs'
+    @messenger.bindDialogs (dialogs) ->
+      console.log '[AW]ActorService bindDialogs: dialogs:', dialogs
+      callback dialogs
+  bindGroup: (peer) ->
+    console.log '[AW]ActorService bindGroup'
+    console.log '[AW]ActorService bindGroup: peer:', peer
+  bindTyping: ->
+    console.log '[AW]ActorService bindTyping'
+  bindUser: (peer) ->
+    console.log '[AW]ActorService bindUser'
+    console.log '[AW]ActorService bindUser: peer:', peer
+
+  unbindChat: (peer, callback) ->
+    console.log '[AW]ActorService unbindChat'
+    console.log '[AW]ActorService unbindChat: peer:', peer
+    @messenger.unbindChat peer, callback
+  unbindDialogs: ->
+    console.log '[AW]ActorService unbindDialogs'
+  unbindGroup: ->
+    console.log '[AW]ActorService unbindGroup'
+  unbindTyping: ->
+    console.log '[AW]ActorService unbindTyping'
+  unbindUser: ->
+    console.log '[AW]ActorService unbindUser'
+
+  getAuthPhone: ->
+    console.log '[AW]ActorService getAuthPhone'
+  getAuthState: ->
+    console.log '[AW]ActorService getAuthState'
+  getGroup: ->
+    console.log '[AW]ActorService getGroup'
+  getTyping: ->
+    console.log '[AW]ActorService getTyping'
+  getUid: ->
+    console.log '[AW]ActorService getUid'
+    @messenger.getUid()
+  getUser: (uid) ->
+    console.log '[AW]ActorService getUser'
+    @messenger.getUser uid
+
+  clearChat: ->
+    console.log '[AW]ActorService clearChat'
+  deleteChat: ->
+    console.log '[AW]ActorService deleteChat'
+
+  # Draft
+  loadDraft: (peer) ->
+    console.log '[AW]ActorService loadDraft'
+    @messenger.loadDraft peer
+  saveDraft: (peer, draft) ->
+    console.log '[AW]ActorService saveDraft'
+    console.log '[AW]ActorService saveDraft: draft:', draft
+    if draft != null
+      @messenger.saveDraft peer, draft
+

   requestSms: (phone) ->
     console.log '[AW]ActorService requestSms'
     console.log '[AW]ActorService requestSms: phone:', phone
     @messenger.requestSms phone.toString(), (state) =>
       console.log '[AW]ActorService requestSms: state:', state
-      if state == 'code'
-        console.log '[AW]ActorService requestSms: state: code'
-        @$rootScope.$broadcast 'actorAuthCode'
+      switch state
+        when 'code'
+          console.log '[AW]ActorService requestSms: $broadcast actorAuthCode'
+          @$rootScope.$broadcast 'actorAuthCode'
     , (tag, message, canTryAgain, state) ->
-      console.log '[AW]ActorService requestSms: error'
+      console.log '[AW]ActorService requestSms: error: state:', state

   sendCode: (code) ->
     console.log '[AW]ActorService sendCode'
     @messenger.sendCode code, (state) =>
       console.log '[AW]ActorService sendCode: state:', state
-      if state == 'logged_in'
-        @setLoggedIn()
-      if state == 'signup'
-        @$rootScope.$broadcast 'actorSignUp'
-    , (tag, message, canTryAgain, state) =>
-      console.log '[AW]ActorService sendCode: error'
-      console.log '[AW]ActorService sendCode: tag, message, canTryAgain, state:', tag, message, canTryAgain, state
+      switch state
+        when 'logged_in'
+          @setLoggedIn()
+        when 'logged_in'
+          @$rootScope.$broadcast 'actorSignUp'
+    , (tag, message, canTryAgain, state) ->
+      console.log '[AW]ActorService sendCode: error: state:', state

-  bindDialogs: (callback) ->
-    console.log '[AW]ActorService bindDialogs'
-    @messenger.bindDialogs (items) ->
-      console.log '[AW]ActorService bindDialogs: items', items
-      callback items

-  onConversationClosed: (peer) ->
-    console.log '[AW]ActorService closeConversation'
-    console.log '[AW]ActorService closeConversation: peer:', peer
-    @messenger.onConversationClosed peer
-    @$rootScope.$broadcast 'onConversationClosed', peer
-
-  onConversationOpen: (peer) ->
-    console.log '[AW]ActorService openConversation'
-    console.log '[AW]ActorService openConversation: peer:', peer
-    @setCurrentPeer peer
-    @messenger.onConversationOpen peer
-    @$rootScope.$broadcast 'onConversationOpen', peer
-
-  bindChat: (peer, callback) ->
-    console.log '[AW]ActorService bindChat'
-    console.log '[AW]ActorService bindChat: peer:', peer
-    @messenger.bindChat peer, callback
-
-  unbindChat: (peer) ->
-    console.log '[AW]ActorService unbindChat'
-    console.log '[AW]ActorService unbindChat: peer:', peer
-    @messenger.unbindChat peer, ->
-
-  setCurrentPeer: (peer) ->
-    console.log '[AW]ActorService setCurrentPeer'
-    console.log '[AW]ActorService setCurrentPeer: peer:', peer
-    @currentPeer = peer
-
-  onTyping: ->
-    console.log '[AW]ActorService onTyping'
-    @messenger.onTyping @currentPeer

   sendMessage: (peer, message) ->
     console.log '[AW]ActorService sendMessage'
@@ -114,33 +127,35 @@
     # console.log '[AW]ActorService sendMessage: peer:', peer
     @messenger.sendMessage peer, message

-#  getAuthPhone: () ->
-#    console.log '[AW]ActorService getAuthPhone'
-#    @messenger.getAuthPhone()

-  loadDraft: (peer) ->
-    console.log '[AW]ActorService loadDraft'
-    @messenger.loadDraft peer
+  # Events
+  onAppHidden: ->
+    console.log '[AW]ActorService onAppHidden'
+  onAppVisible: ->
+    console.log '[AW]ActorService onAppVisible'
+  onConversationClosed: (peer) ->
+    console.log '[AW]ActorService onConversationClosed'
+    console.log '[AW]ActorService onConversationClosed: peer:', peer
+    @messenger.onConversationClosed peer
+    @$rootScope.$broadcast 'onConversationClosed', peer
+  onConversationOpen: (peer) ->
+    console.log '[AW]ActorService onConversationOpen'
+    console.log '[AW]ActorService onConversationOpen: peer:', peer
+    @messenger.onConversationOpen peer
+    @$rootScope.$broadcast 'onConversationOpen', peer
+  onDialogsClosed: ->
+    console.log '[AW]ActorService onDialogsClosed'
+  onDialogsOpen: ->
+    console.log '[AW]ActorService onDialogsOpen'
+  onProfileClosed: ->
+    console.log '[AW]ActorService onProfileClosed'
+  onProfileOpen: ->
+    console.log '[AW]ActorService onProfileOpen'
+  onTyping: ->
+    console.log '[AW]ActorService onTyping'
+    @messenger.onTyping @currentPeer

-  saveDraft: (peer, draft) ->
-    console.log '[AW]ActorService saveDraft'
-    console.log '[AW]ActorService saveDraft: draft:', draft
-    if draft != null
-      @messenger.saveDraft peer, draft
-
-  # onGroupOnline: () ->
-  #   console.log '[AW]ActorService onGroupOnline'
-  #   @$rootScope.$broadcast 'onGroupOnline'
-
-  getUid: ->
-    @messenger.getUid()
-
-  getUser: (uid) ->
-    console.log '[AW]ActorService getUser'
-    @messenger.getUser uid
-
-
-ActorService.$inject = ['$rootScope', '$sessionStorage']
+ActorService.$inject = ['$rootScope']

 angular
   .module 'actorWeb'


Fix date formatting

Fix date formatting

--- client/app/components/torrentList/torrentElement/torrentElement.coffee
+++ client/app/components/torrentList/torrentElement/torrentElement.coffee
@@ -24,7 +24,7 @@
             else if field is 'seeds'
               processedField = if torrent.seeds? then torrent.seeds + " seeds" else 'N/A'
             else if field is 'date'
-              processedField = moment(torrent.date, 'YYYY-MM-DD').format('YYYY-MM-DD')
+              processedField = moment(torrent.date).format('YYYY-MM-DD')
             else if field is 'title'
               processedField = torrent.title
             else


cleaning up array constructions

cleaning up array constructions

--- src/color.coffee
+++ src/color.coffee
@@ -81,12 +81,9 @@
       arr = args.first()

       alpha = if arr[3]? then arr[3] else 1
-      parsedColor = [
-        parseInt(arr[0]),
-        parseInt(arr[1]),
-        parseInt(arr[2]),
-        parseFloat(alpha)
-      ]
+
+      parsedColor = arr.map (channel) ->
+        parseFloat(channel)
     else if arguments.length == 2
       c = arguments[0]
       a = arguments[1]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -20,12 +20,10 @@
       parsedColor = color

     channels =
-      color = parseColor(color)
-
-      r: parseInt(color[1])
-      g: parseInt(color[2])
-      b: parseInt(color[3])
-      a: parseFloat(color[4]) || 1
+      r: parseInt(parsedColor[1])
+      g: parseInt(parsedColor[2])
+      b: parseInt(parsedColor[3])
+      a: parseFloat(parsedColor[4]) || 1

     self =
       channels: channels


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -204,13 +204,19 @@
     ###*
     A copy of the calling color that is its complementary color on the color wheel.

-

+

     red = Color(255, 0, 0)

     cyan = red.complement()

-    cyan.toString()
-    # => 'rgba(0, 255, 255, 1)'
+    for color, index in ['red', 'cyan']
+      canvas.drawRect
+        color: eval(color)
+        x: 20 + (60 * index)
+        y: 20 + (60 * index)
+        width: 60
+        height: 60
+

     @name complement


Files now support copy by content.

Files now support copy by content.

--- lib/files.coffee
+++ lib/files.coffee
@@ -1,8 +1,32 @@
+logsmith = require 'logsmith'
+fs_extra = require 'fs-extra'
+path_extra = require 'path-extra'
+
+# ---
+
 exports.roost = (opt, manifest, target) ->
 	return if not manifest.files?

 	for file_name, file_def of manifest.files
 		switch
-			when file_def.source? then target.copy file_def.source, file_name
-			when file_def.content then throw new Error 'not implemented'
-			+			when file_def.source?
+				target.copy file_def.source, file_name
+			when file_def.content?
+				temp_source = path_extra.join path_extra.tempdir(), new Date().getTime().toString()
+
+				target.next (callback) ->
+					fs_extra.outputFile temp_source, file_def.content, (err) ->
+						logsmith.exception err if err
+
+						return callback new Error "cannot create temp file #{temp_source}" if err
+						return callback null if callback
+
+				target.recover (callback) ->
+					fs_extra.remove temp_source, (err) ->
+						logsmith.exception err if err
+
+						return callback new Error "cannot delete temp file #{temp_source}" if err
+						return callback null if callback
+
+				target.copy temp_source, file_name
+


Changed dependency to lodash

Changed dependency to lodash

--- lib/forms.coffee
+++ lib/forms.coffee
@@ -1,4 +1,4 @@
-_ = require "underscore"
+_ = require "lodash"
 valids = require "valids"

 ###*


Update for compatibility with upnp-device 0.0.1

--- lib/index.coffee
+++ lib/index.coffee
@@ -13,6 +13,7 @@
 mmd = require 'musicmetadata'
 path = require 'path'
 upnp = require 'upnp-device'
+mime.define 'audio/flac': ['flac']

 getContainerType = (fileTypes) ->
     maxVal = 0
@@ -39,20 +40,18 @@
         (err) -> callback err, sortedFiles

 mimeContainerMap =
-    audio: 'album.musicAlbum'
+    audio: 'object.container.album.musicAlbum'

 makeContainer = (dir, sortedFiles, callback) ->
     contentType = getContainerType sortedFiles
     media =
-        type: 'container'
-        instance: mimeContainerMap[contentType]
+        class: mimeContainerMap[contentType] or 'object.container'
         location: dir

     if contentType is 'audio'
-        stream = fs.createReadStream sortedFiles[contentType][0]
-        parser = new mmd stream
+        parser = new mmd stream = fs.createReadStream sortedFiles[contentType][0]
         parser.on 'metadata', (data) ->
-            media.creator = data.albumartist[0]
+            media.creator = data.albumartist?[0] or data.artist?[0] or 'Unknown'
             media.title = data.album
             callback null, media
         parser.on 'done', (err) ->
@@ -64,20 +63,19 @@
         callback null, media

 mimeItemMap =
-    audio: 'audioItem.musicTrack'
-    image: 'imageItem'
+    audio: 'object.item.audioItem.musicTrack'
+    image: 'object.item.imageItem'
+    text: 'object.item.textItem'

 makeItem = (type, file, callback) ->
     media =
-        type: 'item'
-        instance: mimeItemMap[type]
+        class: mimeItemMap[type] or 'object.item'
         location: file
      switch type
         when 'audio'
-            stream = fs.createReadStream file
-            parser = new mmd stream
+            parser = new mmd stream = fs.createReadStream file
             parser.on 'metadata', (data) ->
-                media.creator = data.artist or 'Unknown'
+                media.creator = data.artist?[0] or 'Unknown'
                 media.title = data.title or 'Untitled'
                 media.album = data.album or 'Untitled'
                 callback null, media


add spec for Bacon.fromArray

add spec for Bacon.fromArray

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1007,6 +1007,12 @@
       -> Bacon.once(new Bacon.Error("oop"))
       [error()])

+describe.only "Bacon.fromArray", ->
+  describe "Turns an array into an EventStream", ->
+    expectStreamEvents(
+      -> Bacon.fromArray([1, 2, 3])
+      [1, 2, 3])
+
 describe "EventStream.concat", ->
   describe "provides values from streams in given order and ends when both are exhausted", ->
     expectStreamEvents(


hrumph

hrumph

--- spec/ipso_spec.coffee
+++ spec/ipso_spec.coffee
@@ -288,9 +288,11 @@
                 error.expected.should.equal 'something else'

-
-
-
-
-
-
+        it 'throws expected functions on timeout even if timeout is reset', ipso (facto, Something) ->
+
+            @timeout 10
+            Something.does something: -> 'hrumph'
+
+
+
+


GTL extraction test

GTL extraction test

--- spec/preparser.coffee
+++ spec/preparser.coffee
@@ -51,3 +51,22 @@
       chai.expect(statements[0]).to.be.an 'array'
       chai.expect(statements[0].length).to.equal 2
       chai.expect(statements[0][0]).to.equal 'vfl'
+
+  describe 'with a simple GTL rule', ->
+    source = """
+    @-gss-layout "frontpageLayout" {
+      grid: "aaab"
+            "aaab"
+            "cccc";
+      place-a: "#box1" "#box1";
+    }
+    """
+    statements = null
+    it 'should produce a statement array', ->
+      statements = parser.parse source
+      chai.expect(statements).to.be.an 'array'
+    it 'should include a single GTL part into the array', ->
+      chai.expect(statements.length).to.equal 1
+      chai.expect(statements[0]).to.be.an 'array'
+      chai.expect(statements[0].length).to.equal 2
+      chai.expect(statements[0][0]).to.equal 'gtl'


Remove old commented out debug code

Remove old commented out debug code

--- src/ExifReader.coffee
+++ src/ExifReader.coffee
@@ -233,7 +233,6 @@
     tagValue

   _readIptcTags: () ->
-    #throw new Error('here')
     try
       naaBlock = @_getIptcNaaResourceBlock()
     catch error


Trigger Autocomplete upon Backspace

Trigger Autocomplete upon Backspace

https://github.com/saschagehlich/autocomplete-plus/issues/137

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -206,8 +206,7 @@
   # e - The change {Event}
   onChanged: (e) =>
     return unless @editorHasFocus()
-    typedText = e.newText.trim()
-    if typedText.length is 1 and atom.config.get "autocomplete-plus.enableAutoActivation"
+    if atom.config.get("autocomplete-plus.enableAutoActivation") and ( e.newText.trim().length is 1 or e.oldText.trim().length is 1 )
       @contentsModified()
     else
       # Don't refocus since we probably still have focus


Fix up some bugs in creating PatchElement

--- lib/changes/diff-view.coffee
+++ lib/changes/diff-view.coffee
@@ -1,5 +1,7 @@
 $         = require 'jquery'
 PatchView = require '../patch/patch-view'
+PatchElement = require './patch-element'
+Patch = require './patch'

 {CompositeDisposable} = require 'atom'

@@ -86,14 +88,14 @@
     @constructor.patchCache["#{status}#{path}"] or=
       @createPatchView(patch, status)

-  createPatchView: (patch, status) ->
-    patchView  = new PatchView
-    patchView.setPatch
-      patch: patch
-      status: status
-    patchView
-
-  renderPatch: (e, entry, patch) ->
+  createPatchView: (_patch, status) ->
+    patchElement  = new PatchElement
+    patch = new Patch(patch: _patch, status: status)
+    patchElement.initialize({changesView: @changesView, patch: patch})
+    patchElement
+
+  renderPatch: (e) ->
+    {patch, entry} = e.detail
     if patch
       currentPatch = @querySelector('git-patch-view')
       patchView = @getPatchView(patch, entry.status)


handle config in tests

handle config in tests

--- lib/connection/client.coffee
+++ lib/connection/client.coffee
@@ -156,7 +156,8 @@
       @clientCall 'kill', 'kill'

   clargs: ->
-    {precompiled, optimisationLevel} = atom.config.get 'julia-client.juliaOptions'
+    {precompiled, optimisationLevel} =
+      atom.config.get('julia-client.juliaOptions') ? {precompiled: true, optimisationLevel: 2}
     as = []
     as.push "--precompiled=#{if precompiled then 'yes' else 'no'}"
     as.push "-O#{optimisationLevel}" unless optimisationLevel is 2


We can just use the old timeout, no need to reset it

--- src/event.coffee
+++ src/event.coffee
@@ -12,8 +12,7 @@
   trigger: (args...) ->
     @queue.push(args)
     if @options.async
-      clearTimeout(@queue.timeout)
-      @queue.timeout = setTimeout((=> @resolve()), 0)
+      @queue.timeout or= setTimeout((=> @resolve()), 0)
     else
       @resolve()


Add four-directional motion

Add four-directional motion

--- src/hello.coffee
+++ src/hello.coffee
@@ -58,7 +58,13 @@
       [0, 0, 0, 0, 0, 0]]

   # dat mutation?
-  Keys.down.onValue ->
-    GameState.mutate "camera motion", (state) ->
-      state.camera[0] += 1
+  bindMotion = (key, x, y) ->
+    key.onValue ->
+      GameState.mutate "camera motion", (state) ->
+        state.camera[0] += x
+        state.camera[1] += y
+  bindMotion Keys.down, 0, 1
+  bindMotion Keys.up, 0, -1
+  bindMotion Keys.left, -1, 0
+  bindMotion Keys.right, 1, 0


Remove double arrow

Remove double arrow

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -83,7 +83,7 @@

     self = this
     @findEditor.on 'focus', -> self.setLastFocusedElement(this)
-    @findEditor.on 'blur', -> -> self.setLastFocusedElement(this)
+    @findEditor.on 'blur', -> self.setLastFocusedElement(this)
     @replaceEditor.on 'focus', -> self.setLastFocusedElement(this)
     @replaceEditor.on 'blur', -> self.setLastFocusedElement(this)
     @pathsEditor.on 'focus', -> self.setLastFocusedElement(this)


do not save empty comments

do not save empty comments

--- client/comments.coffee
+++ client/comments.coffee
@@ -35,6 +35,9 @@
     comments_raw = ta.value
     ta.value = ""
     Session.set "comment_preview_html", null
+    if comments_raw == ""
+      # nothing to do. don't bother saving.
+      return
     collection = window.get_collection_from_name(@show_name)
     cdict = comment: comments_raw # FIXME: add auth stuff and timestamp on server.
     collection.update @_id,


add log

add log

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -1,4 +1,5 @@
 apns = require 'apn'
+logger = require 'winston'

 class PushServiceAPNS
     tokenFormat: /^[0-9a-f]{64}$/i


sitewide: cleanup includes file

sitewide: cleanup includes file

--- client/includes.coffee
+++ client/includes.coffee
@@ -1,17 +1,10 @@
 module.exports = [
   # --- Libraries ---
-  # "libs/pusher.min.js",
   "libs/encode.js",
   "libs/docwritenoop.js",
   "libs/sha1.encapsulated.coffee",
   "libs/jquery-1.9.1.js",
   "libs/underscore-min.1.3.js"
-  # "libs/highlight.pack.js",
-  # "libs/jquery-1.7.1.js",
-  # "libs/jquery-1.7.1.min.js",
-  # "libs/jquery-ui-1.8.16.custom.min.js",
-  # "libs/three.js" ==> added for testplay, please dont delete,
-  # "libs/jquery-emit.js",

   # --- Framework ---
   "libs/sockjs-0.3-patched.js",
@@ -179,7 +172,6 @@
   "app/CommonViews/CommonListHeader.coffee",
   "app/CommonViews/CommonInnerNavigation.coffee",
   "app/CommonViews/headers.coffee",
-  # "app/CommonViews/logo.coffee",
   "app/CommonViews/HelpBox.coffee",
   "app/CommonViews/KeyboardHelper.coffee",
   "app/CommonViews/VerifyPINModal.coffee",
@@ -227,7 +219,6 @@
   "app/CommonViews/fatih/fatihprefpane.coffee",
   "app/CommonViews/fatih/fatih.coffee",

-  # "app/MainApp/foreign_auth.coffee",
   "app/MainApp/kodingappcontroller.coffee",
   "app/MainApp/sidebar/sidebarcontroller.coffee",
   "app/MainApp/sidebar/sidebarview.coffee",
@@ -348,43 +339,18 @@
   # minifying jquery breaks the code.

-#  "libs/jquery-hashchange.js",
   "libs/jquery-timeago.js",
   "libs/date.format.js",
   "libs/jquery.cookie.js",
   "libs/jquery.getcss.js",
-  # "libs/keypress.js",
   "libs/mousetrap.js",
-  # "libs/jquery.wmd.js",
-  # "libs/jquery.fieldselection.js",
-  # "libs/jquery.multiselect.min.js",
-  # "libs/log4js.js",
-  # "libs/jsonh.js",
   "libs/md5-min.js",
-  # "libs/frogaloop.min.js",
-
-  # "libs/bootstrap-twipsy.js",
-  # "libs/jquery.tipsy.js",
   "libs/async.js",
   "libs/jquery.mousewheel.js",
-  # "libs/mwheelIntent.js",
-  # "libs/underscore.js",
   "libs/inflector.js",
   "libs/canvas-loader.js",
-
   "libs/marked.js",
-  # "libs/google-code-prettify/prettify.js",
-  # "libs/less.min.js",
-
-  # "libs/xml2json.js",
-  # "libs/ZeroClipboard.js",
-
   "app/Helpers/jspath.coffee",
-
-  # "app/Helpers/Command.coffee",
-  # "app/Helpers/FileEmitter.coffee",
-  # "app/Helpers/polyfills.coffee",
-  # "app/Helpers/CommandParser.coffee",

   # --- Applications ---
   "app/MainApp/channels/kitechannel.coffee",
@@ -692,7 +658,6 @@
   # --- Styles ---
   "css/style.css",
   "css/highlight-styles/sunburst.css",
-  # "css/tipsy.css",

   "Framework/themes/default/kdfn.styl",
   "stylus/appfn.styl",
@@ -707,7 +672,6 @@
   "Framework/themes/default/kd.modal.styl",
   "Framework/themes/default/kd.form.styl",
   "Framework/themes/default/kd.tooltip.styl",
-  # "stylus/kd.tiptip.styl" => discarded,

   "stylus/app.styl",
   "stylus/app.landing.styl",


Deletion works with queuing events

Deletion works with queuing events

--- lib/pusht.coffee
+++ lib/pusht.coffee
@@ -38,6 +38,7 @@
     @colours = require('./helpers/colour-list')
     @friendColours = []
     @timeouts = []
+    @events = []

   deactivate: ->
     @subscriptions.dispose()
@@ -206,14 +207,16 @@
       buffer.append(chunk)
       @triggerPush = true

-    @pairingChannel.bind 'client-change', (data) => @changeBuffer(data)
+    @pairingChannel.bind 'client-change', (events) =>
+      console.log(events);
+      _.each(events, (event) => @changeBuffer(event) )

     # @pairingChannel.bind 'client-disconnected', (data) =>
     #   @clearMarkers(data.colour)
     #   disconnectView = new AlertView "Your pair buddy has left the session."
     #   atom.workspace.addModalPanel(item: disconnectView, visible: true)

-
+    @triggerEventQueue()
     @listenToBufferChanges()
     @syncSelectionRange()

@@ -221,13 +224,21 @@
     @buffer.onDidChange (event) =>
       return unless @triggerPush
       deletion = !(event.newText is "\n") and (event.newText.length is 0)
-      @pairingChannel.trigger 'client-change', {deletion: deletion, event: event, colour: @markerColour}
+      console.log(deletion)
+      @events.push({deletion: deletion, event: event, colour: @markerColour})
+
+  triggerEventQueue: ->
+    setInterval(=>
+      if @events.length > 0
+        @pairingChannel.trigger 'client-change', @events
+        @events = []
+    , 200)

   changeBuffer: (data) ->
     newRange = Range.fromObject(data.event.newRange)
     oldRange = Range.fromObject(data.event.oldRange)
     newText = data.event.newText
-
+    console.log(data.deletion)
     @triggerPush = false

     @clearMarkers(data.colour)


Rename `awsBucketName` to `bucketName`

Rename `awsBucketName` to `bucketName`

--- src/index.coffee
+++ src/index.coffee
@@ -21,7 +21,7 @@
   @opts.aws.httpOptions.timeout ?= 10000
   @opts.aws.maxRetries          ?= 3
   @opts.aws.params              ?= {}
-  @opts.aws.params.Bucket       = awsBucketName
+  @opts.aws.params.Bucket       = bucketName
   @opts.aws.path                ?= ''
   @opts.aws.region              ?= 'us-east-1'
   #@opts.aws.secretAccessKey
@@ -37,9 +37,9 @@
   @opts.versions                ?= []

   if not @opts.url and @opts.aws.region is 'us-east-1'
-    @opts.url ?= "https://s3.amazonaws.com/#{@opts.aws.params.Bucket}/"
+    @opts.url ?= "https://s3.amazonaws.com/#{bucketName}/"
   else if not @opts.url
-    @opts.url ?= "https://s3-#{@opts.aws.region}.amazonaws.com/#{@opts.aws.params.Bucket}/"
+    @opts.url ?= "https://s3-#{@opts.aws.region}.amazonaws.com/#{bucketName}/"

   @s3 = new S3 @opts.aws


export both as "adaptors" and "adaptor"

export both as "adaptors" and "adaptor"

--- src/index.coffee
+++ src/index.coffee
@@ -1,8 +1,13 @@
-module.exports =
+adaptors =
+  Ipfs: require './adaptor/ipfs'
+  Json: require './adaptor/json'
+  GoogleSpreadsheet: require './adaptor/googleSpreadsheet'
+
+modules =
   Node: require './core/node'
   Edge: require './core/edge'
   Sphere: require './core/sphere'
-  adaptor:
-    Ipfs: require './adaptor/ipfs'
-    Json: require './adaptor/json'
-    GoogleSpreadsheet: require './adaptor/googleSpreadsheet'
+  adaptors: adaptors
+  adaptor: adaptors   # don't break if using old syntax
+
+module.exports = modules


export nonretryable

export nonretryable

--- src/index.coffee
+++ src/index.coffee
@@ -11,5 +11,7 @@
     RequestAsyncProcessor: require "./processors/request.async.processor"
     JobProcessor: require "./processors/job"
     MaxRetriesProcessor: require "./processors/maxRetries.processor"
+  Exceptions:
+    NonRetryable: require "./exceptions/non.retryable"
   Sources: require "./sources"
   Senders: require "./senders"


Input: Make CTRL-Z work with content_size: true.

--- src/elements/Input/Input.coffee
+++ src/elements/Input/Input.coffee
@@ -254,7 +254,9 @@
 			call: (ev) =>
 				# console.debug "keydown on input", ev.hasModifierKey(), ev.keyCode(), @_incNumbers

-				if ev.ctrlKey() or ev.metaKey()
+				# dont return here if CTRL-Z is pressed
+				if (ev.ctrlKey() and not ev.keyCode() == 90) or ev.metaKey()
+					# console.debug "leaving keydown"
 					return

 				@lastKeyDownEvent = ev
@@ -303,6 +305,7 @@
 						@showCursor(ev)

 					return
+
 				@initCursor(ev)
 				@showCursor(ev)

@@ -841,7 +844,6 @@

 		if @_content_size
 			@__initContentSize()
-

 		CUI.Events.listen
 			type: "input"


Testrunner respects custom build path now.

Closes #139.

--- src/testrunner.coffee
+++ src/testrunner.coffee
@@ -45,7 +45,7 @@
     jsdom.env
       html: path.join brunchdir, "index.html"
       scripts: [
-        path.join brunchdir, "build/web/js/app.js"
+        path.resolve options.buildPath, "web/js/app.js"
         path.resolve __dirname, "../vendor/jasmine.js"
         "/tmp/brunchtest/specs.js"
       ]


PUBDEV-844 Format model and prediction outputs to .4f

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -53,7 +53,7 @@
     when 'byte', 'short', 'int', 'long'
       createVector column.name, TNumber, parseNumbers data
     when 'float', 'double'
-      createVector column.name, TNumber, parseNumbers data
+      createVector column.name, TNumber, (parseNumbers data), format4f
     when 'string'
       createFactor column.name, TString, data
     when 'matrix'
@@ -80,6 +80,12 @@
   else
     number

+format4fi = (number) ->
+  if number
+    number.toFixed(4).replace(/\.0+$/, '')
+  else
+    number
+
 combineTables = (tables) ->
   leader = head tables

@@ -116,6 +122,18 @@
   target = new Array source.length
   for element, i in source
     target[i] = if element? then element else undefined
+  target
+
+parseAndFormat = (source) ->
+  target = new Array source.length
+  for element, i in source
+    target[i] = if element?
+      if isNumber element
+        format4fi element
+      else
+        element
+    else
+      undefined
   target

 repeatValues = (count, value) ->
@@ -411,13 +429,13 @@
       origin: origin

   inspectRawArray_ = (name, origin, description, array) -> ->
-    createDataframe name, [createList name, parseNulls array], (sequence array.length), null,
+    createDataframe name, [createList name, parseAndFormat array], (sequence array.length), null,
       description: description
       origin: origin

   inspectRawObject_ = (name, origin, description, obj) -> ->
     vectors = for k, v of obj
-      createList k, [ if v is null then undefined else v ]
+      createList k, [ if v is null then undefined else if isNumber v then format4fi v else v ]

     createDataframe name, vectors, (sequence 1), null,
       description: description
@@ -543,7 +561,7 @@
             else
               console.log "WARNING: dropping [#{k}] from inspection:", v
           else
-            record[k] = v
+            record[k] = if isNumber v then format4fi v else v

     return


Prevent plot() without args from failing

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -251,8 +251,10 @@
   plot = (f) ->
     if _isFuture f
       _fork proceed, H2O.PlotInput, f
-    else
+    else if isFunction f
       _fork createPlot, f
+    else
+      assist plot

   grid = (f) ->
     plot (g) -> g(


use deploy run in all environments

use deploy run in all environments

--- lib/token.coffee
+++ lib/token.coffee
@@ -24,7 +24,7 @@

   @deployContract: ->
     output = run "node_modules/.bin/truffle migrate --network #{nodeEnv}
-      --reset --verbose-rpc", relaxed: true, quiet: nodeEnv is 'test'
+      --reset --verbose-rpc", relaxed: true, quiet: nodeEnv

     pattern = ///#{CONTRACT_NAME}:\s(0x[0-9a-f]{40})///
     contractAddress = pattern.exec(output)?[1]


Fix problem with unit list accessibility comparator

Didn't work in embeds if user has profile set.


:lipstick:

:lipstick:

Nathan's code pee

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -41,7 +41,6 @@
   attachRootView: (pathToOpen) ->
     rootViewState = atom.rootViewStates[$windowNumber]
     @rootView = if rootViewState
-      console.log "RootView deserialize", rootViewState
       RootView.deserialize(rootViewState)
     else
       new RootView {pathToOpen}


Trim full names

Trim full names

--- lib/users.coffee
+++ lib/users.coffee
@@ -10,7 +10,7 @@

 @displayUser = (username) ->
   user = findUsername username
-  user?.profile?.fullname or username
+  user?.profile?.fullname?.trim?() or username

 @linkToAuthor = (group, user) ->
   link = pathFor 'author',


Fix bad reference to API response

Fix bad reference to API response

Fixes a bug where everything _other_ that `hubot fitbit leaders` would return an error because the `json` variable isn't defined. Bad code, needs tests.

--- src/hubot-fitbit-leaders.coffee
+++ src/hubot-fitbit-leaders.coffee
@@ -70,7 +70,7 @@
   robot.respond /fitbit register/i, (msg) ->
     Fitbit.get('/profile.json', accessToken)
     .then (res) ->
-      robot.logger.debug json
+      robot.logger.debug res
       user = getResponseBody(res).user
       unless user.fullName
         user.fullName = 'the bot'
@@ -82,7 +82,7 @@
   robot.respond /fitbit approve/i, (msg) ->
     Fitbit.get('/friends/invitations.json', accessToken)
     .then (res) ->
-      robot.logger.debug json
+      robot.logger.debug res
       if getResponseBody(res).friends.length is 0
         msg.send "No pending requests."
         return


Typo.

Typo.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -59,7 +59,7 @@
       return callback log
     callback null, stdout

-exports.replaceSlashes = do ->
+exports.replaceSlashes = replaceSlashes = do ->
   if os.platform() is 'win32'
     (_) -> _.replace(/\//g, '\\')
   else


Add --token argument to apm login command

--- src/login.coffee
+++ src/login.coffee
@@ -28,11 +28,12 @@
       be used to identify you when publishing packages to atom.io.
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')
+    options.alias('t', 'token').describe('token', 'atom.io API token')

   run: (options) ->
     {callback} = options
     options = @parseOptions(options.commandArgs)
-    Q(user: options.argv.user)
+    Q(token: options.argv.token)
       .then(@welcomeMessage)
       .then(@openURL)
       .then(@getToken)
@@ -48,6 +49,8 @@
     "AtomPackageManager/#{require('../package.json').version}"

   welcomeMessage: (state) =>
+    return Q(state) if state.token
+
     welcome = """
       Welcome to Atom!

@@ -61,7 +64,9 @@

     @prompt({prompt: "Press [Enter] to open your account page on Atom.io."})

-  openURL: ->
+  openURL: (state) =>
+    return Q(state) if state.token
+
     open('https://atom.io/account')

   getToken: (state) =>


Add findOrCreate and a method for course updates

--- src/user/model.coffee
+++ src/user/model.coffee
@@ -10,6 +10,7 @@
   is_customer_service: false
   name: null
   profile_url: null
+  courses: []

 User =

@@ -17,7 +18,7 @@

   update: (data) ->
     _.extend(this, data.user)
-    @courses = _.map data.courses, (course) -> new Course(course)
+    @courses = _.map data.courses, (course) => new Course(@, course)
     @channel.emit('change')

   logout: ->
@@ -25,12 +26,22 @@
     @channel.emit('change')

   getCourse: (collectionUUID) ->
-    _.findWhere @courses, ecosystem_book_uuid: collectionUUID
+    _.findWhere( @courses, ecosystem_book_uuid: collectionUUID )
+
+  findOrCreateCourse: (collectionUUID) ->
+    @getCourse(collectionUUID) or (
+      course = new Course(@, ecosystem_book_uuid: collectionUUID)
+      @courses.push(course)
+      course
+    )

   ensureStatusLoaded: ->
     api.channel.emit("user.send.statusUpdate") unless @isLoggedIn()

   isLoggedIn: -> !!@profile_url
+
+  onCourseUpdate: (course) ->
+    @channel.emit('change')

 api.channel.on 'user.receive.*', ({data}) ->
   User.update(loaded: true)


Only use CC courses that the user is a student off

The teacher role cannot request exercises.

--- src/user/model.coffee
+++ src/user/model.coffee
@@ -18,7 +18,9 @@

   update: (data) ->
     _.extend(this, data.user)
-    @courses = _.compact _.map data.courses, (course) -> new Course(course) if course.is_concept_coach
+    @courses = _.compact _.map data.courses, (course) ->
+      if course.is_concept_coach and _.detect(course.roles, (role) -> role.type is 'student')
+        new Course(course)
     @channel.emit('change')

   get: ->


Removed reference to util

Removed reference to util

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -1,4 +1,3 @@
-util = require 'util'
 URL = require '../vendor/url'
 DatabaseURL = require './database_url'
 Backbone = require 'backbone'


auth: send 503 errors clients [regression]

--- workers/auth/lib/auth/authworker.coffee
+++ workers/auth/lib/auth/authworker.coffee
@@ -188,12 +188,24 @@
     @fetchNotificationExchange (exchange)->
       exchange.publish routingKey, { event, contents }

+  respondServiceUnavailable:->
+    @bongo.respondToClient routingKey, {
+      method    : 'error'
+      arguments : [message: 'Service unavailable!', code:503]
+      callbacks : {}
+    }
+
   join: do ->

     joinHelper = (messageData, routingKey, socketId) ->
       @authenticate messageData, routingKey, (session) =>
+
         serviceInfo = @getNextServiceInfo messageData.name
-        return console.error "No service info! #{messageData.name}"  unless serviceInfo?
+
+        unless serviceInfo?
+          @respondServiceUnavailable()
+          return console.error "No service info! #{messageData.name}"
+
         { serviceUniqueName, serviceGenericName, loadBalancing } = serviceInfo

         params = {
@@ -209,12 +221,7 @@
         then @sendAuthWho params
         else if serviceUniqueName?
         then @sendAuthJoin params
-        else
-          @bongo.respondToClient routingKey, {
-            method    : 'error'
-            arguments : [message: 'Service unavailable!', code:503]
-            callbacks : {}
-          }
+        else @respondServiceUnavailable()

     ensureGroupPermission = (group, account, roles, callback) ->
       {JPermissionSet, JGroup} = @bongo.models


Finished validation tests

Finished validation tests

--- tests/jasmine/server/unit/patientsMethods.coffee
+++ tests/jasmine/server/unit/patientsMethods.coffee
@@ -53,7 +53,7 @@
                 expect(Meteor.user).toHaveBeenCalled()
                 expect(Roles.userIsInRole).toHaveBeenCalled()

-        xdescribe "returns a 422 if missing required field", ->
+        describe "returns a 422 if missing required field", ->

             beforeEach ->
                 spyOn Meteor, "user"
@@ -61,8 +61,18 @@
                 spyOn Roles, "userIsInRole"
                     .and.returnValue true

-            it "patient.firstName", ->
-                patient = lastName: "McTestington", mrn: "1234512345"
+            testValidation = (field) ->
+                temp = patient[field]
+                patient[field] = null
                 Meteor.call "patient", patient, (err, resp) ->
                     expect(err.error).toBe 422
+                    patient[field] = temp

+            it "patient.firstName", ->
+                testValidation "firstName"
+
+            it "patient.lastName", ->
+                testValidation "lastName"
+
+            it "patient.mrn", ->
+                testValidation "mrn"


removed useless console.log

removed useless console.log

Former-commit-id: 74007fd227041cef2a97118988c14fc26a615b9c [formerly a02b71eb7efbcafd24ffbb9b2e35edd721138e12]
Former-commit-id: 5e8f382f7fd32d82f50ad5e5a6501b6b248d5b84

--- src/utils/tree.coffee
+++ src/utils/tree.coffee
@@ -45,7 +45,7 @@
         model: nodes,
         sel: sel,
         msa: @msa
-      console.log m
+
       # remove top collection
       _.each(nodes.models, (e) ->
         delete e.collection


jezus

jezus

--- apps/auction/client/bid_form.coffee
+++ apps/auction/client/bid_form.coffee
@@ -34,13 +34,13 @@
   getBidAmount: =>
     val = @$('input.max-bid').val()
     if val
-      saleArtwork.cleanBidAmount val
+      @saleArtwork.cleanBidAmount val

   placeBid: =>
     @timesPolledForBidPlacement = 0
     @$submit.addClass('is-loading')
     @clearErrors()
-    if @getBidAmount(@$('input.max-bid').val()) >= @saleArtwork.get('minimum_next_bid_cents')
+    if @getBidAmount() >= @saleArtwork.get('minimum_next_bid_cents')
       bidderPosition = new BidderPosition
         sale_id              : @model.get('id')
         artwork_id           : @saleArtwork.get('artwork').id


fix phone position issue

fix phone position issue

--- apps/home/client/iphone_view.coffee
+++ apps/home/client/iphone_view.coffee
@@ -20,12 +20,12 @@
     @positionPhone()
     @$window.on 'resize.feed', _.throttle((=> @positionPhone()), 70)

-  positionPhone: ->
+  positionPhone: (maxHeight = @maxHeight) ->
     windowHeight = @parent.getHeight()
     windowWidth = @parent.getWidth()

     @height = windowHeight - (@minTop * 2)
-    @height = if @height > @maxHeight then @maxHeight else @height
+    @height = if @height > maxHeight then maxHeight else @height

     @$el.height @height
     @width = Math.floor(@height * @phoneHeightToWidthRatio)
@@ -45,8 +45,7 @@
     @trigger 'repositioned'

     if @left < @textWidth and windowWidth > @textWidth * 2
-      @maxHeight = @maxHeight * 0.7
-      @positionPhone()
+      @positionPhone(maxHeight * 0.7)

   sizeIphoneContentAreas: ->
     @contentAreaTop = @height * @phoneAreaAboveContentAreaToHeightRatio


Always load the new url, don't rely on Backbone history behaviour, which will not reload urls that are currently loaded

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -42,7 +42,8 @@
   backbone = showFrame.contentWindow.Backbone
   history = backbone?.history
   if history && backbone.History.started
-    history.navigate url, true
+    history.loadUrl url # Force loading the url, even if already showing that url
+    history.navigate url
   else
     showFrame.onload = -> window.onModalReady()
     showFrame.src = showUrl


opt: windows fall back for signs

opt: windows fall back for signs

--- libs/util.coffee
+++ libs/util.coffee
@@ -2,15 +2,16 @@
 npm = require './npm'

 isWin = process.platform is 'win32'
-warnSign = if isWin then ' * Warning: ' else '⚠  '
-errorSign = if isWin then ' ERROR: ' else '✖  '
-okSign = if isWin then ' ^_^Y  ' else '✔  '
+warnSign = if isWin then '‼ ' else '⚠  '
+errorSign = if isWin then '× ' else '✖  '
+okSign = if isWin then '√  ' else '✔  '
+infoSign = if isWin then 'i ' else 'ℹ '

 cwdFilePath = (names...) ->
     path.join.apply path, [process.cwd()].concat names

 logInfo = (str) ->
-    console.log '\n>>  '.yellow + str.green
+    console.log '\n>> '.yellow + str.green
 logSucc = (str) ->
     console.log "\n#{okSign}#{str}".green


Append to document if dropped low

Append to document if dropped low

--- src/controller.coffee
+++ src/controller.coffee
@@ -624,11 +624,19 @@

       mainPoint = @trackerPointToMain(position)

+      # If we are below the document, append to the document.
+      if mainPoint.y > @tree.view.getBounds().bottom() and mainPoint.x > 0
+        head = @tree.end
+        until head.type is 'blockEnd' or head is @tree.start then head = head.prev
+
+        if head is @tree.start then highlight = @tree
+        else highlight = head.block
+
       # If we are dragging a block,
       # we can drop on any Block not in a socket,
       # any Indent, or any Socket that does
       # not contain a block.
-      if @draggingBlock.type is 'block'
+      else if @draggingBlock.type is 'block'
         highlight = @tree.find (block) ->
           (not (block.inSocket?() ? false)) and
             block.view.dropArea? and


add failed_screenshots to clean task

add failed_screenshots to clean task

--- tasks/clean.coffee
+++ tasks/clean.coffee
@@ -6,5 +6,6 @@
   run: ->
     gulp.src('digest.json', {read: false}).pipe(clean())
     gulp.src('test/tmp', {read: false}).pipe(clean())
+    gulp.src('test/failed_screenshots', {read: false}).pipe(clean())
     gulp.src('public', {read: false}).pipe(clean())


Change animation to two steps

Change animation to two steps

--- src/controller.coffee
+++ src/controller.coffee
@@ -2895,12 +2895,11 @@
         @highlightCanvas.style.opacity =
         @cursorCanvas.style.opacity = 0

-      setTimeout (=>
-        @iceElement.style.transition =
-          @paletteWrapper.style.transition = "left #{translateTime}ms"
-        @iceElement.style.left = '0px'
-        @paletteWrapper.style.left = "#{-@paletteWrapper.offsetWidth}px"
-      ), fadeTime
+      @iceElement.style.transition =
+        @paletteWrapper.style.transition = "left #{fadeTime}ms"
+
+      @iceElement.style.left = '0px'
+      @paletteWrapper.style.left = "#{-@paletteWrapper.offsetWidth}px"

       setTimeout (=>
         # Translate the ICE editor div out of frame.
@@ -3055,7 +3054,7 @@
         ), translateTime

         @iceElement.style.transition =
-          @paletteWrapper.style.transition = "left #{translateTime}ms"
+          @paletteWrapper.style.transition = "left #{fadeTime}ms"

         @iceElement.style.left = "#{@paletteWrapper.offsetWidth}px"
         @paletteWrapper.style.left = '0px'
@@ -3085,7 +3084,7 @@

   Editor::toggleBlocks = (cb) ->
     if @currentlyUsingBlocks
-      return @performMeltAnimation 500, 1000, cb
+      return @performMeltAnimation 700, 500, cb
     else
       return @performFreezeAnimation 500, 500, cb

@@ -3776,7 +3775,10 @@
             @copyPasteInput.value = @lassoSegment.stringify()
           @copyPasteInput.setSelectionRange 0, @copyPasteInput.value.length
       on_keyup: =>
-        @iceElement.focus()
+        if @textFocus?
+          @hiddenInput.focus()
+        else
+          @iceElement.focus()

     pressedVKey = false
     pressedXKey = false


Never use cache when checking for CORS update

--- tasks/crawl.coffee
+++ tasks/crawl.coffee
@@ -69,7 +69,7 @@

     grunt.utils.async.series
       correctAsAt: (callback) ->
-        getCached CORS_BASE + 'ModuleInfoListing.jsp?fac_c=10', (data) ->
+        get CORS_BASE + 'ModuleInfoListing.jsp?fac_c=10', (data) ->
           re = /Correct as at ([^<]+)</
           if match = re.exec data
             if !options.refresh? && fs.existsSync(options.dest) &&


Fix line number animation

Fix line number animation

--- src/controller.coffee
+++ src/controller.coffee
@@ -3497,10 +3497,9 @@

       @lineNumberTags[line] = lineDiv

-    lineDiv.style.top =  treeView.bounds[line].y - @scrollOffsets.main.y + 'px'
+    lineDiv.style.top = "#{treeView.bounds[line].y + treeView.distanceToBase[line].above - @view.opts.textHeight - @fontAscent}px"
     lineDiv.style.height =  treeView.bounds[line].height + 'px'
     lineDiv.style.fontSize = @fontSize + 'px'
-    lineDiv.style.paddingTop = (treeView.distanceToBase[line].above - @view.opts.textHeight - @fontAscent) + 'px'

     @lineNumberWrapper.appendChild lineDiv


Make sessions bind to ace editor sessions

--- src/controller.coffee
+++ src/controller.coffee
@@ -51,6 +51,25 @@
 command_modifiers = if isOSX then META_KEYS else CONTROL_KEYS
 command_pressed = (e) -> if isOSX then e.metaKey else e.ctrlKey

+class PairDict
+  constructor: (@pairs) ->
+
+  get: (index) ->
+    for el, i in @pairs
+      if el[0] is index
+        return el[1]
+
+  contains: (index) ->
+    @pairs.some (x) -> x[0] is index
+
+  set: (index, value) ->
+    for el, i in @pairs
+      if el[0] is index
+        el[1] = index
+        return true
+    @pairs.push [index, value]
+    return false
+
 # FOUNDATION
 # ================================

@@ -104,7 +123,7 @@
     if @options.mode of modes
       @mode = new modes[@options.mode] @options.modeOptions
     else
-      @mode = new coffee @options.modeOptions
+      @mode = null

     # Instantiate an Droplet editor view
     @view = new view.View standardViewSettings
@@ -236,7 +255,43 @@
       ctx: @mainCtx
       draw: @draw

+    @aceElement = document.createElement 'div'
+    @aceElement.className = 'droplet-ace'
+
+    @wrapperElement.appendChild @aceElement
+
+    @aceEditor = ace.edit @aceElement
+
+    @aceEditor.setTheme 'ace/theme/chrome'
+    @aceEditor.setFontSize 15
+    acemode = @options.mode
+    if acemode is 'coffeescript' then acemode = 'coffee'
+    @aceEditor.getSession().setMode 'ace/mode/' + acemode
+    @aceEditor.getSession().setTabSize 2
+
+    @currentlyAnimating = false
+
+    @transitionContainer = document.createElement 'div'
+    @transitionContainer.className = 'droplet-transition-container'
+
+    @dropletElement.appendChild @transitionContainer
+
     @session = new Session @, @options, @standardViewSettings
+    @sessions = new PairDict([
+      [@aceEditor.getSession(), @session]
+    ])
+
+    @blankSession = new Session @, {mode: '', palette: []}, @standardViewSettings
+
+    # Sessions are bound to other ace sessions;
+    # on ace session change Droplet will also change sessions.
+    @aceEditor.on 'changeSession', (e) =>
+      if @sessions.contains(e.session)
+        @session = @sessions.get(e.session)
+        @updateNewSession()
+      else
+        @setEditorState false
+        @session = @blankSession

     # Set up event bindings before creating a view
     @bindings = {}
@@ -407,6 +462,24 @@
     else
       @resizeTextMode()

+  updateNewSession: ->
+    @setEditorState @session.currentlyUsingBlocks
+    @redrawMain()
+    @rebuildPalette()
+    @redrawPalette()
+
+  bindNewSession: (opts) ->
+    if @sessions.contains(@aceEditor.getSession())
+      throw new ArgumentError 'Cannot bind a new session where one already exists.'
+    else
+      session = new Session @, opts, @standardViewSettings
+      @sessions.set(@aceEditor.getSession(), session)
+      @session = session
+      @session.currentlyUsingBlocks = false
+      @setValue @getAceValue()
+      @setPalette @session.paletteGroups
+      return session
+

 # RENDERING CAPABILITIES
 # ================================
@@ -670,7 +743,7 @@
     @paletteCanvas.height
   )

-  for entry in @currentPaletteBlocks
+  for entry in @session.currentPaletteBlocks
     # Layout this block
     paletteBlockView = @session.paletteView.getViewNodeFor entry.block
     paletteBlockView.layout PALETTE_LEFT_MARGIN, lastBottomEdge
@@ -1835,9 +1908,10 @@
 Editor::setPalette = (paletteGroups) ->
   @paletteHeader.innerHTML = ''
   @session.paletteGroups = paletteGroups
-
-  @currentPaletteBlocks = []
-  @currentPaletteMetadata = []
+  console.log 'setting palette'
+
+  @session.currentPaletteBlocks = []
+  @session.currentPaletteMetadata = []

   paletteHeaderRow = null

@@ -1908,21 +1982,21 @@
     return

   # Record that we are the selected group now
-  @currentPaletteGroup = paletteGroup.name
-  @currentPaletteBlocks = paletteGroup.parsedBlocks
-  @currentPaletteMetadata = paletteGroup.parsedBlocks
+  @session.currentPaletteGroup = paletteGroup.name
+  @session.currentPaletteBlocks = paletteGroup.parsedBlocks
+  @session.currentPaletteMetadata = paletteGroup.parsedBlocks

   # Unapply the "selected" style to the current palette group header
-  @currentPaletteGroupHeader?.className =
-      @currentPaletteGroupHeader.className.replace(
+  @session.currentPaletteGroupHeader?.className =
+      @session.currentPaletteGroupHeader.className.replace(
           /\s[-\w]*-selected\b/, '')

   # Now we are the current palette group header
-  @currentPaletteGroupHeader = paletteGroup.header
+  @session.currentPaletteGroupHeader = paletteGroup.header
   @currentPaletteIndex = i

   # Apply the "selected" style to us
-  @currentPaletteGroupHeader.className +=
+  @session.currentPaletteGroupHeader.className +=
       ' droplet-palette-group-header-selected'

   # Redraw the palette.
@@ -1946,7 +2020,7 @@
       state.consumedHitTest = true
       return

-    for entry in @currentPaletteBlocks
+    for entry in @session.currentPaletteBlocks
       hitTestResult = @hitTest palettePoint, entry.block, @session.paletteView

       if hitTestResult?
@@ -1988,7 +2062,7 @@
   @currentHighlightedPaletteBlock = null

   # Add new blocks
-  for data in @currentPaletteMetadata
+  for data in @session.currentPaletteMetadata
     block = data.block

     hoverDiv = document.createElement 'div'
@@ -2012,7 +2086,7 @@
       hoverDiv.addEventListener 'mousemove', (event) =>
         palettePoint = @trackerPointToPalette new @draw.Point(
             event.clientX, event.clientY)
-        if @session.viewOrChildrenContains block, palettePoint, @session.paletteView
+        if @viewOrChildrenContains block, palettePoint, @session.paletteView
             @clearPaletteHighlightCanvas()
             @paletteHighlightPath = @getHighlightPath block, {color: '#FF0'}, @session.paletteView
             @paletteHighlightPath.draw @paletteHighlightCtx
@@ -2470,7 +2544,7 @@
   return null

 Editor::handleTextInputClickInPalette = (palettePoint) ->
-  for entry in @currentPaletteBlocks
+  for entry in @session.currentPaletteBlocks
     hitTestResult = @hitTestTextInputInPalette palettePoint, entry.block

     # If they have clicked a socket, check to see if it is a dropdown
@@ -3139,28 +3213,6 @@
   @resizeBlockMode()
   return @setValue_raw @getAceValue()

-hook 'populate', 1, ->
-  @aceElement = document.createElement 'div'
-  @aceElement.className = 'droplet-ace'
-
-  @wrapperElement.appendChild @aceElement
-
-  @aceEditor = ace.edit @aceElement
-
-  @aceEditor.setTheme 'ace/theme/chrome'
-  @aceEditor.setFontSize 15
-  acemode = @options.mode
-  if acemode is 'coffeescript' then acemode = 'coffee'
-  @aceEditor.getSession().setMode 'ace/mode/' + acemode
-  @aceEditor.getSession().setTabSize 2
-
-  @currentlyAnimating = false
-
-  @transitionContainer = document.createElement 'div'
-  @transitionContainer.className = 'droplet-transition-container'
-
-  @dropletElement.appendChild @transitionContainer
-
 # For animation and ace editor,
 # we will need a couple convenience functions
 # for getting the "absolute"-esque position
@@ -3728,7 +3780,7 @@

 hook 'redraw_palette', 0, ->
   bounds = new @draw.NoRectangle()
-  for entry in @currentPaletteBlocks
+  for entry in @session.currentPaletteBlocks
     bounds.unite @session.paletteView.getViewNodeFor(entry.block).getBounds()

   # For now, we will comment out this line
@@ -4280,7 +4332,7 @@
     return true

   for childObj in modelView.children
-    if @session.viewOrChildrenContains childObj.child, point, view
+    if @viewOrChildrenContains childObj.child, point, view
       return true

   return false
@@ -4552,10 +4604,10 @@
 # ================================

 Editor::overflowsX = ->
-  @documentDimensions().width > @session.viewportDimensions().width
+  @documentDimensions().width > @viewportDimensions().width

 Editor::overflowsY = ->
-  @documentDimensions().height > @session.viewportDimensions().height
+  @documentDimensions().height > @viewportDimensions().height

 Editor::documentDimensions = ->
   bounds = @session.view.getViewNodeFor(@session.tree).totalBounds


Adapt event dispatching to the view syntax

--- lib/mixins/decoration-management.coffee
+++ lib/mixins/decoration-management.coffee
@@ -32,13 +32,13 @@
       # in the change handler. Bookmarks does this.
       if decorations?
         for decoration in decorations
-          @emit 'decoration-changed', marker, decoration, event
+          @trigger 'minimap:decoration-changed', marker, decoration, event

     decoration = new Decoration(marker, this, decorationParams)
     @decorationsByMarkerId[marker.id] ?= []
     @decorationsByMarkerId[marker.id].push(decoration)
     @decorationsById[decoration.id] = decoration
-    @emit 'decoration-added', marker, decoration
+    @trigger 'minimap:decoration-added', marker, decoration
     decoration

   removeDecoration: (decoration) ->
@@ -49,13 +49,13 @@
     if index > -1
       decorations.splice(index, 1)
       delete @decorationsById[decoration.id]
-      @emit 'decoration-removed', marker, decoration
+      @trigger 'minimap:decoration-removed', marker, decoration
       @removedAllMarkerDecorations(marker) if decorations.length is 0

   removeAllDecorationsForMarker: (marker) ->
     decorations = @decorationsByMarkerId[marker.id].slice()
     for decoration in decorations
-      @emit 'decoration-removed', marker, decoration
+      @trigger 'minimap:decoration-removed', marker, decoration
     @removedAllMarkerDecorations(marker)

   removedAllMarkerDecorations: (marker) ->
@@ -67,4 +67,4 @@
     delete @decorationMarkerDestroyedSubscriptions[marker.id]

   decorationUpdated: (decoration) ->
-    @emit 'decoration-updated', decoration
+    @trigger 'minimap:decoration-updated', decoration


Update templates (until new system in place)

--- assets/js/directives/toolbar.coffee
+++ assets/js/directives/toolbar.coffee
@@ -52,10 +52,8 @@

             AngularJS

-
1.0.1

-

-
1.0.2

-
1.0.2 + Jasmine

+
1.0.5

+
1.0.5 + Jasmine

1.1.0 (unstable)

@@ -66,14 +64,6 @@

Backbone.js

YUI

-

-

-            Import gist...
-

-

-

-
Launch builder...

-


add pen to @el

add pen to @el

--- assets/js/views/content_show.coffee
+++ assets/js/views/content_show.coffee
@@ -1,10 +1,11 @@
 define ['marionette', 'marked', 'pen', 'pen_markdown'], (Marionette, marked, Pen) ->
   class ContentShow extends Marionette.ItemView
     template: '#content-show'
+    id: 'content_show'

     templateHelpers: ->
       markdownContent: ->
         marked(@content)

     onRender: ->
-      new Pen("#content")
+      new Pen(@el)


Fix crash when deleting album, refs #163

--- server/controllers/album.coffee
+++ server/controllers/album.coffee
@@ -174,6 +174,6 @@
         return next err if err

         Photo.fromAlbum req.album, (err, photos) ->
-            photo.destroy() for photo in photos
+            photo.destroy(() -> ) for photo in photos

         res.send success: "Deletion succeded."


Fix Express deprecation warning on res.sendfile

--- server/controllers/photo.coffee
+++ server/controllers/photo.coffee
@@ -170,7 +170,7 @@
                     res.on 'close', -> request.abort()
                     stream.pipe res
                 else
-                    return res.sendfile './server/img/error.gif'
+                    return res.sendFile './server/img/error.gif'

         else if req.photo.binary?[which]
             binaryPath = "/data/#{req.photo.id}/binaries/#{which}"
@@ -179,10 +179,10 @@
                     res.on 'close', -> request.abort()
                     stream.pipe res
                 else
-                    return res.sendfile './server/img/error.gif'
+                    return res.sendFile './server/img/error.gif'

         else
-            return res.sendfile './server/img/error.gif'
+            return res.sendFile './server/img/error.gif'

         # This is a temporary hack to allow caching
         # ideally, we would do as follow :


Map cmd-alt keys to conflict resolution commands.

--- keymaps/merge-conflicts.cson
+++ keymaps/merge-conflicts.cson
@@ -7,5 +7,13 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
-'.workspace':
-  'ctrl-alt-o': 'merge-conflicts:toggle'
+
+'.platform-darwin .editor.conflicted':
+  'cmd-alt-down': 'merge-conflicts:next-unresolved'
+  'cmd-alt-up': 'merge-conflicts:previous-unresolved'
+  'cmd-alt-enter': 'merge-conflicts:resolve-current'
+  'cmd-alt-1': 'merge-conflicts:accept-ours'
+  'cmd-alt-2': 'merge-conflicts:accept-theirs'
+
+'.platform-darwin .workspace':
+  'cmd-alt-m': 'merge-conflicts:detect'


no global variables. everything on the stack.

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -63,9 +63,10 @@
   leftType.linkTo rightType

 class Variable
-  constructor: (@label, @type, @namespace) ->
-class Func
-  constructor: (@label, @codeNode) ->
+  constructor: (@localOffset, @type, @namespace) ->
+  access: ->
+    positive = (@localOffset + 0x10000) % 0x10000
+    "[z+0x#{positive.toString 16}]"

 class Namespace
   constructor: (@parent)->
@@ -79,25 +80,24 @@
       @root = @parent.root
       @extraAsm = @root.extraAsm
       @nextLabel = @root.nextLabel
+    @localVariableCount = 1 # return address is at index 0
+    @nextLocalOffset = -> -@localVariableCount++
   find: (name) ->
     @names[name] or @parent?.find name
-  createVariable: (name, type) ->
-    variable = new Variable "_"+name, type, this
+  createVariable: (name, type, localOffset) ->
+    variable = new Variable localOffset or @nextLocalOffset(), type, this
     @names[name] = variable
-    @extraAsm.push variable
     variable
   createBuiltinFunc: (name, type, asm) ->
     label = @nextLabel()
     @extraAsm.push {asm: ":#{label}\n#{asm}"}
     variable = @createVariable name, type
-    variable.asm = ":#{variable.label} dat #{label}"
+    "set #{variable.access()}, #{label}"
   createLiteralString: (value) ->
     label = @nextLabel()
     asm = ":#{label} dat #{value.length}, #{JSON.stringify value}"
     @extraAsm.push {asm}
     label
-  createFunc: (codeNode) ->
-    new Func @nextLabel(), codeNode
   createSubNamespace: ->
     new Namespace this

@@ -246,10 +246,15 @@
   # A **Block** is the only node that can serve as the root.
   compileRoot: ->
     o = {namespace: new Namespace}
-    o.namespace.createBuiltinFunc "printc", new Type("func", [intType], voidType), stdlib.printc
-    o.namespace.createBuiltinFunc "prints", new Type("func", [stringType], voidType), stdlib.prints
-    code = @compileStatement o
-    codes = [code, "set [0x8ffe], 0\n"]
+    codes = []
+    codes.push "; stdlib"
+    codes.push o.namespace.createBuiltinFunc "printc", new Type("func", [intType], voidType), stdlib.printc
+    codes.push o.namespace.createBuiltinFunc "prints", new Type("func", [stringType], voidType), stdlib.prints
+    codes.push "; main"
+    codes.push @compileStatement o
+    codes.push "; exit"
+    codes.push "set [0x8ffe], 0"
+    codes.push ""

     for variable in o.namespace.extraAsm
       codes.push variable.asm
@@ -283,7 +288,7 @@
     unless @type?
       variable = o.namespace.find @value
       throw SyntaxError "undefined variable \"#{@value}\"" unless variable?
-      return {type: variable.type, access: "[#{variable.label}]"}
+      return {type: variable.type, access: variable.access()}
     switch @type
       when "int" then {type: intType, access: @value}
       when "string" then {type: stringType, access: o.namespace.createLiteralString JSON.parse @value}
@@ -409,8 +414,15 @@
     # good news, guys: you're parameter types are now known!
     for codeNode in callType.findRoot().codeNodes
       codeNode.compileFunc o
-    codes = ("set push, #{arg.access}" for arg in args)
-    codes.push "jsr [#{funcVariable.label}]"
+    codes = []
+    # position the stack just after our local variables
+    codes.push "sub sp, #{o.namespace.localVariableCount}"
+    for arg in args
+      codes.push "set push, #{arg.access}"
+    codes.push "jsr #{funcVariable.access()}"
+    # restore the stack and z
+    codes.push "add sp, #{o.namespace.localVariableCount}"
+    codes.push "set z, sp"
     return codes.join("\n")

   # Tag this invocation as creating a new instance.
@@ -582,7 +594,7 @@
       unless variable?
         throw SyntaxError "variable \"#{name}\" is not declared"
       linkTypes value.type, variable.type
-    "set [#{variable.label}], #{value.access}"
+    "set #{variable.access()}, #{value.access}"

   isStatement: (o) ->
     o?.level is LEVEL_TOP and @context? and "?" in @context
@@ -603,18 +615,22 @@
     @context = '_this' if @bound

   compileExpression: (o) ->
-    @func = o.namespace.createFunc this
-    @func.type = new Type "func", (new Type for _ in @params), new Type
-    @func.type.codeNodes.push this
-    {type: @func.type, access: @func.label}
+    @label = o.namespace.nextLabel()
+    @type = new Type "func", (new Type for _ in @params), new Type
+    @type.codeNodes.push this
+    {type: @type, access: @label}

   compileFunc: (o) ->
     o = {namespace: o.namespace.createSubNamespace()}
-    code = @body.compileStatement o
-    # TODO figure returning stuff
-    linkTypes @func.type.findRoot().returnType, voidType
-    returnCode = "set pc, pop"
-    o.namespace.extraAsm.push {asm: ":#{@func.label}\n#{code}\n#{returnCode}"}
+    codes = []
+    codes.push ":#{@label}"
+    # we need z because sp can't be offset inline
+    codes.push "set z, sp"
+    codes.push @body.compileStatement o
+    # TODO figure out how to return stuff
+    linkTypes @type.findRoot().returnType, voidType
+    codes.push "set pc, pop"
+    o.namespace.extraAsm.push asm: codes.join "\n"

   children: ['params', 'body']


Server: minor cleanup

Server: minor cleanup

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -252,7 +252,7 @@
   { fields } = req.body

   unless fields?
-    res.status(400).send "Bad request"
+    res.status(400).send 'Bad request'
     return

   validations = Object.keys fields
@@ -275,6 +275,7 @@
   return res.status(400).send 'Bad request'  unless username?

   JUser.usernameAvailable username, (err, response) =>
+
     return res.status(400).send 'Bad request'  if err

     {kodingUser, forbidden} = response
@@ -520,9 +521,6 @@
   return true  if firstLetter.toUpperCase() is firstLetter
   return false

-# Handles all internal pages
-# /USER || /SECTION || /GROUP[/SECTION] || /APP
-#
 app.get '/WFGH', (req, res, next)->

   {JGroup} = koding.models


server: recover minimal error check

server: recover minimal error check

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -343,6 +343,8 @@
   { JPasswordRecovery } = koding.models
   { email } = req.body

+  return res.status(400).send 'Invalid email!'  if not email
+
   JPasswordRecovery.recoverPasswordByEmail { email }, (err) ->
     return res.status(403).send err.message  if err?


server: fix error with session destructing

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -106,12 +106,16 @@
 app.use (req, res, next) ->
   {JSession} = koding.models
   {clientId} = req.cookies
+
   # fetchClient will validate the clientId.
   # if it is in our db it will return the session it
   # it it is not in db, creates a new one and returns it
-  JSession.fetchSession clientId, (err, { session })->
-    return next() if err or not session
-    updateCookie req, res, session
+  JSession.fetchSession clientId, (err, result)->
+
+    return next()  if err
+    return next()  unless result?.session
+
+    updateCookie req, res, result.session

     next()


fix bugs.

fix bugs.

--- src/order.coffee
+++ src/order.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Make an order for easy record.
+#   Make an order for easyily record and calculate total money needed.
 #
 # Dependencies:
 #
@@ -9,7 +9,7 @@
 #
 # Commands:
 #   hubot order <food> <note> $<money> - make an order
-#   hubot order <food> <note> $<money> for @somone - make an order for someone
+#   hubot order <food> <note> $<money> for @someone - make an order for someone
 #   hubot order my - show your order
 #   hubot order all - show all orders and calculate total money needed.
 #	hubot order reset - reset all orders
@@ -26,12 +26,12 @@
 			{@name, @userId, @date, @food, @note, @money} = options

 		toString: () ->
-			"#{@name} - #{@food} #{@note} $#{@money} @#{@date.toLocaleString()}"
+			"#{@name} - #{@food} #{@note} $#{@money}}"

 	robot.brain.data['orders'] = []

 	robot.respond /order\s+(\S*)\s+((\S*\s+)+)?\$(\S*)(\s+for\s+@?(\S*))?/i, (msg) ->
-		robot.logger.debug Util.inspect(msg)
+		#robot.logger.debug Util.inspect(msg)
 		date_current = new Date()
 		food = msg.match[1]
 		note = msg.match[2]
@@ -47,22 +47,20 @@

 		order = new Order name:user, userId:userId, date:date_current, food:food, note:note, money:money

-		isOrderExist = false
 		for _order in robot.brain.data.orders
-			if(_order.name is order.name)
-				_order = order
-				isOrderExist = true
+			if(_order.name is user)
+				index = robot.brain.data.orders.indexOf(_order)
+				robot.brain.data.orders.splice(index, 1)
 				break

-		if(isOrderExist isnt true)
-			robot.brain.data.orders.push order
+		robot.brain.data.orders.push order

-		msg.reply "You order #{food} #{note} $#{money} for <@#{userId}|#{user}> at #{date_current}"
+		msg.reply "You order #{food} #{note} $#{money} for <@#{userId}|#{user}>}"

 	robot.respond /order my/i, (msg) ->
 		date_current = new Date()
 		user = msg.message.user.name
-		order = o for o in robot.brain.data.orders when o.name is user and o.date_current.getDate() is date_current.getDate()
+		order = o for o in robot.brain.data.orders when o.name is user #and o.date.getDate() is date_current.getDate()
 		if (order?)
 			msg.reply order
 		else
@@ -71,23 +69,19 @@
 	robot.respond /order all/i, (msg) ->
 		date_current = new Date()
 		orders = robot.brain.data.orders
-		robot.logger.debug Util.inspect(orders)
 		totalMoney = 0
 		if (orders isnt null)
-			robot.logger.debug Util.inspect(order) for order in orders
 			totalMoney += parseInt( order.money, 10 ) for order in orders
-			msg.reply order for order in orders when order.date.getDate() is date_current.getDate()
+			msg.reply order for order in orders #when order.date.getDate is date_current.getDate
 			msg.reply "Total money : #{totalMoney}"
 		else
 			msg.reply "There has no orders."

-	rebot.respond /order reset/i, (msg) ->
+	robot.respond /order reset/i, (msg) ->
 		robot.brain.data['orders'] = []
 		msg.reply "Orders reset !"

 	parseTarget = (text) ->
-		robot.logger.debug text
 		regexPattern = /order .* for \<@?(\S*)\>/i
 		matches = text.match regexPattern
-		robot.logger.debug Util.inspect(matches)
 		matches[1]


improve memory usage

improve memory usage

--- server/methods/automation/backup.coffee
+++ server/methods/automation/backup.coffee
@@ -10,17 +10,17 @@
 	folder = 'backups/' + moment().format() + '/'

 	data =
-		projects: Projects.find().fetch()
-		shifts: Shifts.find().fetch()
-		weeks: Weeks.find().fetch()
-		users: Meteor.users.find().fetch()
-		messages: Messages.find().fetch()
+		projects: -> Projects.find().fetch()
+		shifts: -> Shifts.find().fetch()
+		weeks: -> Weeks.find().fetch()
+		users: -> Meteor.users.find().fetch()
+		messages: -> Messages.find().fetch()

 	for it in Object.keys(data)
 		s3.putObject
 			Bucket: bucket
 			Key: folder + it + '.json'
-			Body: JSON.stringify data[it]
+			Body: JSON.stringify data[it]()
 		, (e, d) -> if e then console.error e

 	'Backup successfully completed!'


Age module

Age module

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -39,7 +39,7 @@

   self.bind "update", (dt) ->
     if I.fadeOut
-      I.alpha = startingAlpha * (1 - ((I.age + 1) / I.duration))
+      I.alpha = startingAlpha * (1 - (I.age / I.duration))

     if I.duration != -1 && I.age >= I.duration
       I.active = false


Removing unused LocalStorageStore

Removing unused LocalStorageStore

--- shared/store/local_storage_store.coffee
+++ shared/store/local_storage_store.coffee
@@ -1,34 +0,0 @@
-MemoryStore = require('./memory_store')
-
-module.exports = class LocalStorageStore extends MemoryStore
-
-  constructor: ->
-    @_keys = {}
-
-  _get: (key) ->
-    json = window.localStorage.getItem(@_formatKey(key))
-    data = try
-      JSON.parse(json)
-    catch e
-      null
-    data
-
-  _set: (key, data) ->
-    json = JSON.stringify(data)
-    window.localStorage.setItem(@_formatKey(key), json)
-    @_keys[key] = true
-
-  _clear: (key) ->
-    window.localStorage.removeItem(@_formatKey(key))
-    delete @_keys[key]
-
-  _clearAll: ->
-    for key, value of @_keys
-      window.localStorage.removeItem(@_formatKey(key))
-    @_keys = {}
-
-  @canHaz = ->
-    try
-      window?.localStorage?
-    catch e
-      return false


al-html, bind to comment without container

--- src/html/base.coffee
+++ src/html/base.coffee
@@ -45,7 +45,7 @@
                 return
             prepare: ->
                 if element.nodeType is 8
-                    self.baseElement = document.createElement 'div'
+                    self.baseElement = null
                     self.topElement = element
                 else
                     self.baseElement = element
@@ -58,16 +58,34 @@
                     self.childCD.destroy()
                     self.childCD = null
                 if self.activeElement
-                    self.removeDom self.activeElement
+                    if Array.isArray self.activeElement
+                        for el in self.activeElement
+                            self.removeDom el
+                    else
+                        self.removeDom self.activeElement
                     self.activeElement = null
                 return
             insertBlock: (html) ->
-                self.activeElement = self.baseElement.cloneNode false
-                self.activeElement.innerHTML = html
-                self.insertDom self.topElement, self.activeElement
-                self.childCD = env.changeDetector.new()
-                alight.bind self.childCD, self.activeElement,
-                    skip_attr: env.skippedAttr()
+                if self.baseElement
+                    self.activeElement = self.baseElement.cloneNode false
+                    self.activeElement.innerHTML = html
+                    self.insertDom self.topElement, self.activeElement
+                    self.childCD = env.changeDetector.new()
+                    alight.bind self.childCD, self.activeElement,
+                        skip_attr: env.skippedAttr()
+                else
+                    t = document.createElement 'body'
+                    t.innerHTML = html
+                    current = self.topElement
+                    self.activeElement = []
+                    self.childCD = env.changeDetector.new()
+                    while el=t.firstChild
+                        self.insertDom current, el
+                        current = el
+                        self.activeElement.push el
+
+                        alight.bind self.childCD, current,
+                            skip_attr: env.skippedAttr()
                 return
             updateDom: (html) ->
                 self.removeBlock()


Add --userconfig flag to npm install commands

--- src/installer.coffee
+++ src/installer.coffee
@@ -43,17 +43,17 @@
   installModule: (modulePath, callback) ->
     console.log '\nInstalling module...'

-    installModuleArgs = ['install']
-    installModuleArgs.push(modulePath)
-    installModuleArgs.push("--target=#{config.getNodeVersion()}")
-    installModuleArgs.push('--arch=ia32')
-    installModuleArgs.push('--silent')
+    installArgs = ['--userconfig', config.getUserConfigPath(), 'install']
+    installArgs.push(modulePath)
+    installArgs.push("--target=#{config.getNodeVersion()}")
+    installArgs.push('--arch=ia32')
+    installArgs.push('--silent')
     env = _.extend({}, process.env, HOME: @atomNodeDirectory)

     installDirectory = temp.mkdirSync('apm-install-dir-')
     nodeModulesDirectory = path.join(installDirectory, 'node_modules')
     mkdir(nodeModulesDirectory)
-    @spawn @atomNpmPath, installModuleArgs, {env, cwd: installDirectory}, (code) =>
+    @spawn @atomNpmPath, installArgs, {env, cwd: installDirectory}, (code) =>
       if code is 0
         for child in fs.readdirSync(nodeModulesDirectory)
           cp(path.join(nodeModulesDirectory, child), path.join(@atomPackagesDirectory, child), forceDelete: true)
@@ -66,13 +66,13 @@
   installModules: (callback) =>
     console.log '\nInstalling modules...'

-    installModulesArgs = ['install']
-    installModulesArgs.push("--target=#{config.getNodeVersion()}")
-    installModulesArgs.push('--arch=ia32')
-    installModulesArgs.push('--silent')
+    installArgs = ['--userconfig', config.getUserConfigPath(), 'install']
+    installArgs.push("--target=#{config.getNodeVersion()}")
+    installArgs.push('--arch=ia32')
+    installArgs.push('--silent')
     env = _.extend({}, process.env, HOME: @atomNodeDirectory)

-    @spawn @atomNpmPath, installModulesArgs, {env}, (code) ->
+    @spawn @atomNpmPath, installArgs, {env}, (code) ->
       if code is 0
         callback()
       else


Replace old method call

Replace old method call

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -55,7 +55,7 @@
       range = document.body.createTextRange()
       range.moveToPoint(pageX, pageY)
       range.select()
-      return @getSelectedRange()?[0]
+      return @updateCurrentLocationRange()

     if domRange
       @createLocationRangeFromDOMRange(domRange)


API for externally adding and removing items

--- vendor/assets/javascripts/token-text-area.coffee
+++ vendor/assets/javascripts/token-text-area.coffee
@@ -151,6 +151,37 @@
     else
       $(items[currentIndex]).addClass("selected")

+  # This is meant to be an external function used to add items to the DOM
+  # item is an object of the form:
+  # { id: 'xyz', name: 'the display name' }
+  pushItem: (item) ->
+    id = item.id
+    name = item.name
+    return unless id and name
+
+    # token = '' + name + ''
+    node = document.createElement('span')
+    node.className = 'token'
+    node.contentEditable = false
+    node.setAttribute('data-id', id)
+    node.innerHTML = name
+    @input.append(node)
+    @saveEquation()
+
+  removeItem: (id) ->
+    @input.find("[data-id='#{id}']").remove()
+    @saveEquation()
+
+  fillFromEquation: (equation, items) ->
+    html = equation.replace(/\#(\w+)\#/g, (dirtyId) ->
+      id = dirtyId.replace(/\#/g, '')
+      name = items.filter( (item) -> item.id == id )[0].name
+      return '' + name + ''
+    )
+    @input.html( html )
+    @saveEquation()
+
+
   addItem: (result) ->
     id = result.data('id')
     return unless id