Prevent HTML messages from flashing

Prevent HTML messages from flashing

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -62,8 +62,10 @@
         html = message.get 'html'

         # @TODO Do we want to convert text only messages to HTML ?
-        #if text and not html and @state.messageDisplayHTML
-        #    html = markdown.toHTML text
+        # /!\ if messageDisplayHTML is set, this method should always return
+        # a value fo html, otherwise the content of the email flashes
+        if text and not html and @state.messageDisplayHTML
+            html = markdown.toHTML text

         if html and not text and not @state.messageDisplayHTML
             text = toMarkdown html
@@ -418,44 +420,44 @@
         #   and resize the frame
         if @refs.content
             frame = @refs.content.getDOMNode()
-            loadContent = =>
-                doc = frame.contentDocument or frame.contentWindow.document
+            loadContent = (e) =>
+                doc = frame.contentDocument or frame.contentWindow?.document
                 if doc?
-                    if not doc.getElementById 'cozystyle'
-                        s = document.createElement 'style'
-                        s.id = "cozystyle"
-                        doc.head.appendChild(s)
-                        font = "./fonts/sourcesanspro/SourceSansPro-Regular"
-                        rules = [
-                            """
-                            @font-face{
-                              font-family: 'Source Sans Pro';
-                              font-weight: 400;
-                              font-style: normal;
-                              font-stretch: normal;
-                              src: url('#{font}.eot') format('embedded-opentype'),
-                                   url('#{font}.otf.woff') format('woff'),
-                                   url('#{font}.otf') format('opentype'),
-                                   url('#{font}.ttf') format('truetype');
-                            }
-                            """,
-                            "body {
-                                font-family: 'Source Sans Pro';
-                            }",
-                            "blockquote {
-                                margin-left: .5em;
-                                padding-left: .5em;
-                                border-left: 2px solid blue;
-                                color: blue;
-                            }",
-                            "blockquote blockquote { border-color: red !important; color: red; }",
-                            "blockquote blockquote blockquote { border-color: green !important; color: green; }",
-                            "blockquote blockquote blockquote blockquote { border-color: magenta !important; color: magenta; }",
-                            "blockquote blockquote blockquote blockquote blockquote { border-color: blue !important; color: blue; }",
-                        ]
-                        rules.forEach (rule, idx) ->
-                            s.sheet.insertRule rule, idx
-                        doc.body.innerHTML = @_htmlContent
+                    frame.dataset.messageID = @props.message.get('id')
+                    s = document.createElement 'style'
+                    s.id = "cozystyle"
+                    doc.head.appendChild(s)
+                    font = "./fonts/sourcesanspro/SourceSansPro-Regular"
+                    rules = [
+                        """
+                        @font-face{
+                          font-family: 'Source Sans Pro';
+                          font-weight: 400;
+                          font-style: normal;
+                          font-stretch: normal;
+                          src: url('#{font}.eot') format('embedded-opentype'),
+                               url('#{font}.otf.woff') format('woff'),
+                               url('#{font}.otf') format('opentype'),
+                               url('#{font}.ttf') format('truetype');
+                        }
+                        """,
+                        "body {
+                            font-family: 'Source Sans Pro';
+                        }",
+                        "blockquote {
+                            margin-left: .5em;
+                            padding-left: .5em;
+                            border-left: 2px solid blue;
+                            color: blue;
+                        }",
+                        "blockquote blockquote { border-color: red !important; color: red; }",
+                        "blockquote blockquote blockquote { border-color: green !important; color: green; }",
+                        "blockquote blockquote blockquote blockquote { border-color: magenta !important; color: magenta; }",
+                        "blockquote blockquote blockquote blockquote blockquote { border-color: blue !important; color: blue; }",
+                    ]
+                    rules.forEach (rule, idx) ->
+                        s.sheet.insertRule rule, idx
+                    doc.body.innerHTML = @_htmlContent
                     rect = doc.body.getBoundingClientRect()
                     frame.style.height = "#{rect.height + 60}px"
                 else


fsfile: update fetchPermission to return file existence

--- client/app/lib/util/fs/fsfile.coffee
+++ client/app/lib/util/fs/fsfile.coffee
@@ -229,6 +229,5 @@
     .nodeify (err, result) ->

       return callback err  if err
-
-      { readable, writable } = result
-      callback null, { readable, writable }
+      { readable, writable, exists } = result
+      callback null, { readable, writable, exists }


My class warfare is over. Use CoffeeScript classes.

--- lib/upnp.coffee
+++ lib/upnp.coffee
@@ -3,45 +3,31 @@
 "use strict"

 async = require 'async'
-eventEmitter = require('events').EventEmitter.prototype
-log = new (require 'log')
-require './utils'
+{EventEmitter} = require 'events'

 httpServer = require './httpServer'
 helpers = require './helpers'
 ssdp = require './ssdp'
-utils = require './utils'
-
-services =
-    ContentDirectory: require './services/ContentDirectory'
-    ConnectionManager: require './services/ConnectionManager'

 exports.createDevice = (reqType, name, address) ->
     type = reqType.toLowerCase()
-
-    device = newDevice(name or type, type)
-    unless device.type?
+    unless type of devices
+        device = new EventEmitter
         device.emit 'error', new Error "UPnP device of type #{type} is not yet implemented."
         return device
+    new devices[type](name, address)

-    device.address = address if address?
+class Device extends EventEmitter

-    device
-        .addServices()
-        .init (err, res) ->
-            return device.emit 'error', err if err?
-            Object.defineProperty device, 'uuid', value: "uuid:#{res.uuid}"
-            device.address = res.address
-            device.httpPort = res.port
-            device.emit 'ready'
+    constructor: (@name, @address) ->
+        @schemaPrefix = 'urn:schemas-upnp-org'
+        @schemaVersion = '1.0'
+        @upnpVersion = '1.0'
+        @init()

-    device
-
-newDevice = (name, type) ->
-
-    init = (callback) ->
-        # Asynchronous operations to get some device properties.
-        async.parallel(
+    # Asynchronous operations to get some device properties.
+    init: (callback) ->
+        async.parallel
             uuid: (callback) => helpers.getUuid @type, @name, callback
             address: (callback) =>
                 if @address?
@@ -49,38 +35,30 @@
                 else
                     helpers.getNetworkIP callback
             port: (callback) => httpServer.start.call @, callback
-            callback)
+            (err, res) =>
+                return device.emit 'error', err if err?
+                @uuid = "uuid:#{res.uuid}"
+                @address = res.address
+                @httpPort = res.port
+                @emit 'ready'
         @

-    announce = (callback) ->
+    announce: (callback) ->
         ssdp.start.call @
         do callback if callback?
         @

-    addServices = ->
+services =
+    ConnectionManager: require './services/ConnectionManager'
+    ContentDirectory: require './services/ContentDirectory'
+
+class MediaServer extends Device
+
+    constructor: ->
+        super
+        @type = 'MediaServer'
+        @version = 1
         @services = {}
-        for st in @serviceTypes
-            @services[st] = Object.create services[st], device: { value: @ }
-        @
+        new services[type](@) for type in ['ConnectionManager','ContentDirectory']

-    baseProps =
-        name: { value: name, enumerable: yes }
-        announce: { value: announce, enumerable: yes }
-        upnpVersion: { value: '1.0' }
-        addServices: { value: addServices }
-        init: { value: init }
-
-    typeProps =
-        mediaserver:
-            type: { value: 'MediaServer', enumerable: yes }
-            version: { value: 1, enumerable: yes }
-            schemaPrefix: { value: 'urn:schemas-upnp-org' }
-            schemaVersion: { value: '1.0' }
-            serviceTypes: { value: [ 'ConnectionManager', 'ContentDirectory' ] }
-
-    Object.create(eventEmitter
-        if typeProps[type]?
-            utils.extend baseProps, typeProps[type]
-        else
-            baseProps
-    )
+devices = mediaserver: MediaServer


removing this bit

removing this bit

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -19,7 +19,6 @@
 # DEALINGS IN THE SOFTWARE.

 HyCodeMirror = CodeMirror.fromTextArea($('#hython-target')[0], {
-    value: "(print \"hello, world\")",
     mode:  "clojure",
     theme: "twilight",
     autofocus: true,


replaced _.has with _.each for adding source conditions

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -115,9 +115,9 @@
         'change': 'changeStateSource'
         'click #gmap_button': 'openGMap'
         'mouseover #can': 'onCanvas'
-
+
       onCanvas: ->
-          reinitializeCanvas(@model.getCid())
+          reinitializeCanvas(@model.getCid())

       initialize: ->
         @current_state = 'show'
@@ -133,8 +133,8 @@
             $.inArray(@field_type,
             Formbuilder.options.FIELDSTYPES_CUSTOM_VALIDATION) == -1
           return true if !@field.add_remove_require
-          @field.add_remove_require(@model.getCid(), required)
-
+          @field.add_remove_require(@model.getCid(), required)
+
       show_hide_fields: (check_result, set_field) ->
         do( set_field = set_field) =>
             if(check_result is true )
@@ -189,7 +189,7 @@
                     source_model, condition, set_field.value)
                 check_match_condtions.push(check_result)
                 @clearFields()
-                @changeStateSource()
+                @changeStateSource()

                 if and_flag is true
                   if check_match_condtions.indexOf(false) == -1
@@ -200,7 +200,7 @@
                   if check_match_condtions.indexOf(true) != -1
                     @show_hide_fields(true, set_field)
                   else
-                    @show_hide_fields('false', set_field)
+                    @show_hide_fields('false', set_field)

         return @

@@ -281,7 +281,7 @@
           base_templ_suff = if @model.is_input() then '' else '_non_input',
           set_field_class = false
         ) =>
-
+
           if @model.attributes.conditions
             for set_field in @model.get('conditions')
               if set_field
@@ -661,7 +661,7 @@

       triggerEvent:->
-        do (field_view = null,
+        do (field_view = null,
             fieldViews = @fieldViews
             model = ""
         ) =>
@@ -691,12 +691,12 @@
                     val = model.get('field_values')[value]
                   else if model.get('field_values')
                     val = model.get('field_values')[name]
-                  if val
+                  if val
                     val_set = true
                     @setFieldVal($(x), val)
                   index
-              if val_set
-                field_view.trigger('change_state')
+              if val_set
+                field_view.trigger('change_state')

       setFieldVal: (elem, val) ->
         do(setters = null, type = $(elem).attr('type')) =>
@@ -818,15 +818,16 @@
       addConditions: (model) ->
         unless _.isEmpty(model.attributes.conditions)
           _.each(model.attributes.conditions, (condition) ->
-            do(source = {}, source_condition = {}, target_condition = {}) =>
+            do(source = {}, source_conditions = {}, target_condition = {}) =>
               unless _.isEmpty(condition.source)
                 source = model.collection.where({cid: condition.source})
                 target_condition = _.clone(condition)
                 target_condition.isSource = false
-                unless _.has(source[0].attributes.conditions, target_condition)
-                  _.extend( source_condition, target_condition)
-                  source[0].attributes.conditions.push(source_condition)
-                  source[0].save()
+                _.each(source[0].attributes.conditions, (source_condition) ->
+                  unless _.isEqual(source_condition,target_condition)
+                    _.extend( source_conditions, target_condition)
+                    source[0].attributes.conditions.push(source_conditions)
+                    source[0].save()
           )

       formData: ->


Set the keymap default target to atom.workspaceView's DOM element

--- src/atom.coffee
+++ src/atom.coffee
@@ -236,6 +236,7 @@
     WorkspaceView = require './workspace-view'
     @workspace = Workspace.deserialize(@state.workspace) ? new Workspace
     @workspaceView = new WorkspaceView(@workspace)
+    @keymap.defaultTarget = @workspaceView[0]
     $(@workspaceViewParentSelector).append(@workspaceView)

   deserializePackageStates: ->


stack-editor: accept templateId as an argument and fetch stack template

--- client/stack-editor/lib/index.coffee
+++ client/stack-editor/lib/index.coffee
@@ -136,10 +136,13 @@
     editor?.destroy()

-  reloadEditor: (template) ->
+  reloadEditor: (templateId) ->

-    @removeEditor template._id
-    @showView template
+    { computeController } = kd.singletons
+
+    computeController.fetchStackTemplate templateId, (err, template) =>
+      @removeEditor template._id
+      @showView template

   createEditor: (stackTemplate) ->


beaglebone-green.coffee: Set yocto version to rocko

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@balena.io>

--- beaglebone-green.coffee
+++ beaglebone-green.coffee
@@ -38,7 +38,7 @@
 		machine: 'beaglebone-green'
 		image: 'resin-image-flasher'
 		fstype: 'resinos-img'
-		version: 'yocto-pyro'
+		version: 'yocto-rocko'
 		deployArtifact: 'resin-image-flasher-beaglebone-green.resinos-img'
 		compressed: true


Quick fix for Blight quotation marks.

Quick fix for Blight quotation marks.

--- lib/completion.coffee
+++ lib/completion.coffee
@@ -768,7 +768,7 @@
     { snippet: '"Arctic Breath"' },
     { snippet: '"Assassin\'s Mark"' },
     { snippet: '"Ball Lightning"' },
-    { snippet: '"Blight"' },
+    { snippet: 'Blight' },
     { snippet: '"Bone Offering"' },
     { snippet: 'Clarity' },
     { snippet: '"Cold Snap"' },


fix: show syntax error marker on current file

--- lib/controller.coffee
+++ lib/controller.coffee
@@ -1,6 +1,6 @@
 url = require('url')
 Repl = require('./repl')
-{$} = require 'atom'
+{$, Range} = require 'atom'

 module.exports =
@@ -98,6 +98,7 @@
     @activeRepl?.clearPostWindow()

   recompile: ->
+    @destroyMarkers()
     if @activeRepl
       @activeRepl.recompile()
     else
@@ -209,11 +210,12 @@

     atom.workspace.open(uri, options)
       .then (editor) =>
-
         setMark = (point) =>
           editor.setCursorBufferPosition(point)
           expression = editor.lineForBufferRow(point[0])
           range = [point, [point[0], expression.length - 1]]
+          @destroyMarkers()
+
           marker = editor.markBufferRange(range, invalidate: 'touch')
           decoration = editor.decorateMarker(marker,
             type: 'line',


Remove bootstrap topics.

Remove bootstrap topics.

--- server/bootstrap.coffee
+++ server/bootstrap.coffee
@@ -1,54 +0,0 @@
-# if the database is empty on server start, create some sample data.
-Meteor.startup ->
-  if Lists.find().count() is 0
-    data = [
-      {
-        name: "Meteor Principles"
-        items: [
-          "Data on the Wire"
-          "One Language"
-          "Database Everywhere"
-          "Latency Compensation"
-          "Full Stack Reactivity"
-          "Embrace the Ecosystem"
-          "Simplicity Equals Productivity"
-        ]
-      }
-      {
-        name: "Languages"
-        items: [
-          "Lisp"
-          "C"
-          "C++"
-          "Python"
-          "Ruby"
-          "JavaScript"
-          "Scala"
-          "Erlang"
-          "6502 Assembly"
-        ]
-      }
-      {
-        name: "Favorite Scientists"
-        items: [
-          "Ada Lovelace"
-          "Grace Hopper"
-          "Marie Curie"
-          "Carl Friedrich Gauss"
-          "Nikola Tesla"
-          "Claude Shannon"
-        ]
-      }
-    ]
-    timestamp = (new Date()).getTime()
-    _.each data, (list) ->
-      list_id = Lists.insert(name: list.name)
-      _.each list.items, (text) ->
-        Todos.insert
-          listId: list_id
-          text: text
-          finalized: true
-          createdAt: new Date(timestamp)
-
-        timestamp += 1 # ensure unique timestamp.
-


Add comment about event handlers

Add comment about event handlers

--- www/js/views/checkins.coffee
+++ www/js/views/checkins.coffee
@@ -88,6 +88,8 @@
       unless $('.modal .area-container').length
         $('#modal-content').before HeaderTemplate(@_templateData())

+      # These event handlers are added here as they are outside the usually
+      # re-rendered part of the view's template.
       $('.modal .area-container select').on 'change', @changeSectionSearch
       $('.modal .area-container .venue-search').on 'reset', @resetSearch
       $('.modal .area-container #venue-search').on 'blur', @blurSearch


Just quick fixes.

Just quick fixes.

--- components/modals/KDModalView.coffee
+++ components/modals/KDModalView.coffee
@@ -1,7 +1,6 @@
 class KDModalView extends KDView

   constructor:(options = {}, data)->
-
     options.overlay       ?= no            # a Boolean
     options.overlayClick  ?= yes           # a Boolean
     options.height       or= "auto"        # a Number for pixel value or a String e.g. "100px" or "20%" or "auto"
@@ -27,7 +26,7 @@
     @setClass "fx"                                if options.fx
     @setTitle options.title                       if options.title
     @setContent options.content                   if options.content
-    @addSubView options.view, ".kdmodal-content"  if options.view
+    @addSubView options.view                      if options.view

     @on 'ModalCancelled', options.cancel          if options.cancel

@@ -60,7 +59,6 @@
     @listenWindowResize()

   setDomElement:(cssClass)->
-
     {helpContent, helpTitle} = @getOptions()

     if helpContent
@@ -82,12 +80,10 @@
     """

   addSubView:(view, selector = ".kdmodal-content")->
-
     selector = null if @$(selector).length is 0
     super view, selector

   setButtons:(buttonDataSet)->
-
     @buttons or= {}
     @setClass "with-buttons"
     defaultFocusTitle = null
@@ -180,7 +176,6 @@
     @getDomElement().find(".kdmodal-content").html content

   display:->
-
     if @getOptions().fx
       @utils.defer =>
         @setClass "active"


Fix bug in mode_passkeys.

Fix bug in mode_passkeys.

Caught this while working on the dom_tests infrastructure.  Testing
works!

--- content_scripts/mode_passkeys.coffee
+++ content_scripts/mode_passkeys.coffee
@@ -6,7 +6,7 @@
       trackState: true # Maintain @enabled, @passKeys and @keyQueue.
       keydown: (event) => @handleKeyChar KeyboardUtils.getKeyChar event
       keypress: (event) => @handleKeyChar String.fromCharCode event.charCode
-      keyup: (event) => @handleKeyChar String.fromCharCode event.charCode
+      keyup: (event) => @handleKeyChar KeyboardUtils.getKeyChar event

   # Keystrokes are *never* considered passKeys if the keyQueue is not empty.  So, for example, if 't' is a
   # passKey, then 'gt' and '99t' will neverthless be handled by Vimium.


change input builder to input[type=checkbox]:visible: CheckboxBuilder

--- vendor/assets/javascripts/rails_admin_glyph_theme/src/glyph-theme.coffee
+++ vendor/assets/javascripts/rails_admin_glyph_theme/src/glyph-theme.coffee
@@ -83,7 +83,7 @@
   fieldTypes:
     'input[type="file"]': FileUploadBuilder
     'input[type="number"]': SpinnerBuilder
-    'input[type="checkbox"]': CheckboxBuilder
+    'input[type="checkbox"]:visible': CheckboxBuilder
     'select': SelectBuilder

   constructor: (@selector, options = {}) ->


Refactor <body> creation

Refactor <body> creation

--- src/trix/models/html_sanitizer.coffee
+++ src/trix/models/html_sanitizer.coffee
@@ -10,9 +10,7 @@

   constructor: (html, {@allowedAttributes} = {}) ->
     @allowedAttributes ?= DEFAULT_ALLOWED_ATTRIBUTES
-    {@body, head} = createHTMLDocument(html)
-    for element in head.querySelectorAll("style")
-      @body.appendChild(element)
+    @body = createBodyElementForHTML(html)

   sanitize: ->
     @sanitizeElements()
@@ -55,9 +53,11 @@
     return if nodeIsAttachmentElement(element)
     tagName(element) is "script" or element.getAttribute("data-trix-serialize") is "false"

-  createHTMLDocument = (html = "") ->
+  createBodyElementForHTML = (html = "") ->
     # Remove everything after </html>
     html = html.replace(/<\/html[^>]*>[^]*$/i, "</html>")
     doc = document.implementation.createHTMLDocument("")
     doc.documentElement.innerHTML = html
-    doc
+    for element in doc.head.querySelectorAll("style")
+      doc.body.appendChild(element)
+    doc.body


Added DataUnit class

Added DataUnit class

--- src/fits.coffee
+++ src/fits.coffee
@@ -7,6 +7,14 @@
   @stringPattern = /'(.*)'\s*\/*(.*)/
   @principalMandatoryKeywords = ['BITPIX', 'NAXIS', 'END']
   @extensionKeywords = ['BITPIX', 'NAXIS', 'PCOUNT', 'GCOUNT']
+  @arrayKeywords = ['BSCALE', 'BZERO', 'BUNIT', 'BLANK', 'CTYPEn', 'CRPIXn', 'CRVALn', 'CDELTn', 'CROTAn', 'DATAMAX', 'DATAMIN']
+  @otherReservedKeywords = [
+    'DATE', 'ORIGIN', 'BLOCKED',
+    'DATE-OBS', 'TELESCOP', 'INSTRUME', 'OBSERVER', 'OBJECT', 'EQUINOX', 'EPOCH',
+    'AUTHOR', 'REFERENC',
+    'COMMENT', 'HISTORY',
+    'EXTNAME', 'EXTVER', 'EXTLEVEL'
+  ]

   constructor: ->
     # e.g. [index, value, comment]
@@ -124,6 +132,17 @@
   isPrimary: -> return @primary
   isExtension: -> return @extension

+class DataUnit
+
+  constructor: ->
+    @dataunit = {}
+
+class HDU
+
+  constructor: (header, dataunit)->
+    @header   = header
+    @dataunit = dataunit
+
 class File
   @LINEWIDTH   = 80
   @BLOCKLENGTH = 2880
@@ -134,12 +153,16 @@
     @view       = new jDataView buffer, undefined, undefined, false
     @headers    = []
     @dataunits  = []
+    @hdus       = []

     @headerNext = true
     @eof        = false

     @readHeader() while @headerNext
-    # @readData()
+    # loop
+    #   @readHeader() while @headerNext
+    #   @readDataUnit()
+    #   break if @eof

   # ##Class Methods

@@ -163,11 +186,15 @@
     # Determine if a data unit follows
     @headerNext = not header.hasDataUnit()

-    @headers.push header
+    @headers.push(header)
+
     # Seek to the next relavant character in file
     excess = File.excessChars(linesRead)
     @view.seek(File.LINEWIDTH * linesRead + excess)
     @checkEOF()
+
+  readDataUnit: ->
+

   # # Read a data unit
   # readData: ->
@@ -216,22 +243,10 @@

   checkEOF: -> @eof = true if @view.tell() is @length

-  # readBinTable: (header) ->
-  #   # Check for required keywords
-  #   for keyword in File.requiredBinTableKeywords
-  #     if keyword is "TFORM"
-  #       tfields = parseInt(header["TFIELDS"])
-  #       i = 1
-  #       while i <= tfields
-  #         throw "FITS binary table does not contain the required keyword TFORM#{i}" unless header.hasOwnProperty("TFORM#{i}")
-  #         i += 1
-  #     else
-  #       throw "FITS binary table does not contain the required keyword #{keyword}" unless header.hasOwnProperty(keyword)
-
-
 FITS = @FITS    = {}
 module?.exports = FITS

 FITS.version    = '0.0.1'
 FITS.File       = File
-FITS.Header     = Header+FITS.Header     = Header
+FITS.DataUnit   = DataUnit


removing concludePendingEvents tests

removing concludePendingEvents tests

--- test/coffeescripts/AggregatorSpec.coffee
+++ test/coffeescripts/AggregatorSpec.coffee
@@ -109,22 +109,6 @@

       expect(aggregator.sender.flush).toHaveBeenCalledOnce()

-    it "should conclude pending events", ->
-      aggregator = @createAggregatorAndRecordAction()
-
-      @sentEvents = []
-
-      @beginLoad aggregator
-      @beginLoad aggregator
-
-      expect(aggregator.sender.send).not.toHaveBeenCalled()
-
-      @startSession aggregator
-
-      expect(@sentEvents.length).toBe 2
-      for event in @sentEvents
-        expect(event.status).toBe "Navigation"
-
     it "should append defaultParams to events", ->
       aggregator = @createAggregator()


Use SecureValue field for password input

--- WsseDynamicValue.coffee
+++ WsseDynamicValue.coffee
@@ -35,7 +35,7 @@

 WsseDynamicValue.inputs = [
   DynamicValueInput("username", "Username", "String"),
-  DynamicValueInput("password", "Password", "String")
+  DynamicValueInput("password", "Password", "SecureValue")
 ]

 registerDynamicValueClass WsseDynamicValue


Tests: Environments: Add another method.

--- tests/src/environments/environments.coffee
+++ tests/src/environments/environments.coffee
@@ -26,16 +26,21 @@
       .pause  200

+clickAddVMButton = (browser) ->
+
+  sidebarTitle = '[testpath=main-sidebar] .activity-sidebar .vms .sidebar-title'
+
+  browser
+    .waitForElementVisible   '[testpath=main-sidebar]', 20000
+    .waitForElementVisible   sidebarTitle, 20000
+    .moveToElement           sidebarTitle + ' a.buy-vm', 10, 10
+    .click                   sidebarTitle + ' a.buy-vm'
+
+
 seeUpgradeModal = (browser) ->

-    sidebarTitle = '[testpath=main-sidebar] .activity-sidebar .vms .sidebar-title'
-
-    browser
-      .waitForElementVisible   '[testpath=main-sidebar]', 20000
-      .waitForElementVisible   sidebarTitle, 20000
-      .moveToElement           sidebarTitle + ' a.buy-vm', 10, 10
-      .click                   sidebarTitle + ' a.buy-vm'
-      .waitForElementVisible   '.computeplan-modal.free-plan .kdmodal-inner', 20000 # Assertion
+    clickAddVMButton(browser)
+    browser.waitForElementVisible '.computeplan-modal.free-plan .kdmodal-inner', 20000 # Assertion

   addDomain = (browser) ->


Remove unnecessary empty lines.

Remove unnecessary empty lines.

--- test/integration/connection2-test.coffee
+++ test/integration/connection2-test.coffee
@@ -132,11 +132,8 @@

   request.on('row', (columns) ->
     test.strictEqual(columns.length, 1)
-
     test.strictEqual(columns[0].value, 8)
-
     test.strictEqual(columns[0].isNull, false)
-
     test.strictEqual(columns.byName().C1.value, 8)
   )


Now with a missing fat arrow => =>

Now with a missing fat arrow => =>

--- lib/main.coffee
+++ lib/main.coffee
@@ -79,7 +79,7 @@
   initializeIfNotInitialized: ->
     return if @isInitialized

-    @subscriptions.add atom.views.addViewProvider Notification, (model) ->
+    @subscriptions.add atom.views.addViewProvider Notification, (model) =>
       new NotificationElement(model, @visibilityDuration)

     @notificationsElement = document.createElement('atom-notifications')


Update to match rewrite

Update to match rewrite

--- lib/tree.coffee
+++ lib/tree.coffee
@@ -7,7 +7,7 @@
     @createView() if @state.attached
     rootView.command 'tree-view:toggle', => @createView().toggle()
     rootView.command 'tree-view:reveal-active-file', => @createView().revealActiveFile()
-    atom.contextMenuMap.add '.tree-view', 'Add file', 'tree-view:add'
+    atom.contextMenu.add '.tree-view', { label: 'Add file', command: 'tree-view:add' }

   deactivate: ->
     @treeView?.deactivate()


Changed version from v.1.5.0 to 1.5.1

Changed version from v.1.5.0 to 1.5.1

--- src/init.coffee
+++ src/init.coffee
@@ -11,7 +11,7 @@
 # @type String
 # @static
 ###
-d3plus.version = "1.5.0 - Aqua"
+d3plus.version = "1.5.1 - Aqua"

 ###*
 # The URL for the repo, used internally for certain error messages.


fixed arrow placement if arrow is going up

--- drawer.coffee
+++ drawer.coffee
@@ -126,8 +126,14 @@
 		parentRectangle = new Rectangle parent.top, parent.width, parent.height
 		childRectangle = new Rectangle child.top, child.width, child.height

-		parentConnectorHook = parentRectangle.getConnectorForDirection direction
-		childConnectorHook = childRectangle.getConnectorForDirection "up"
+		# if the parent is physically above the child, then parent.top.y > child.top.y
+		# if this isn't the case, then we need to connect the parent top to the child bottom
+		if (parent.top.y < child.top.y)
+			parentConnectorHook = parentRectangle.getConnectorForDirection direction
+			childConnectorHook = childRectangle.getConnectorForDirection "up"
+		else
+			parentConnectorHook = parentRectangle.getConnectorForDirection "up"
+			childConnectorHook = childRectangle.getConnectorForDirection "down"

 		@drawLine parentConnectorHook, childConnectorHook, arrow


:lipstick: Nicest formatting for subscriptions

--- lib/table-view.coffee
+++ lib/table-view.coffee
@@ -26,67 +26,66 @@
     @bodyComponent = React.renderComponent(TableComponent(props), @body[0])
     @headComponent = React.renderComponent(TableHeaderComponent(props), @head[0])

-    subs = @subscriptions = new CompositeDisposable
-    sub = (o,e,c) => subs.add @asDisposable o.on e, c
-
-    subs.add @table.onDidChangeRows @requestUpdate
-    subs.add @table.onDidAddColumn @onColumnAdded
-    subs.add @table.onDidRemoveColumn @onColumnRemoved
-
-    sub @hiddenInput, 'textInput', (e) =>
-      unless @isEditing()
-        @startEdit()
-        @editView.setText(e.originalEvent.data)
-
-    sub @, 'core:confirm', => @startEdit()
-    sub @, 'core:undo', => @table.undo()
-    sub @, 'core:redo', => @table.redo()
-    sub @, 'core:move-left', => @moveLeft()
-    sub @, 'core:move-right', => @moveRight()
-    sub @, 'core:move-up', => @moveUp()
-    sub @, 'core:move-down', => @moveDown()
-    sub @, 'core:move-to-top', => @moveToTop()
-    sub @, 'core:move-to-bottom', => @moveToBottom()
-    sub @, 'core:page-up', => @pageUp()
-    sub @, 'core:page-down', => @pageDown()
-    sub @, 'core:select-right', => @expandSelectionRight()
-    sub @, 'core:select-left', => @expandSelectionLeft()
-    sub @, 'core:select-up', => @expandSelectionUp()
-    sub @, 'core:select-down', => @expandSelectionDown()
-    sub @, 'table-edit:select-to-end-of-line', => @expandSelectionToEndOfLine()
-    sub @, 'table-edit:select-to-beginning-of-line', => @expandSelectionToBeginningOfLine()
-    sub @, 'table-edit:select-to-end-of-table', => @expandSelectionToEndOfTable()
-    sub @, 'table-edit:select-to-beginning-of-table', => @expandSelectionToBeginningOfTable()
-    sub @, 'mousedown', (e) => e.preventDefault(); @focus()
-
-    sub @body, 'scroll', @requestUpdate
-    sub @body, 'dblclick', (e) => @startEdit()
-    sub @body, 'mousedown', (e) =>
-      e.preventDefault()
-
-      @stopEdit() if @isEditing()
-
-      if position = @cellPositionAtScreenPosition(e.pageX, e.pageY)
-        @activateCellAtPosition position
-
-      @focus()
+    @subscriptions = new CompositeDisposable
+
+    @subscriptions.add @table.onDidChangeRows @requestUpdate
+    @subscriptions.add @table.onDidAddColumn @onColumnAdded
+    @subscriptions.add @table.onDidRemoveColumn @onColumnRemoved
+
+    @subscribeTo @hiddenInput,
+      'textInput': (e) =>
+        unless @isEditing()
+          @startEdit()
+          @editView.setText(e.originalEvent.data)
+
+    @subscribeTo this,
+      'core:confirm': => @startEdit()
+      'core:undo': => @table.undo()
+      'core:redo': => @table.redo()
+      'core:move-left': => @moveLeft()
+      'core:move-right': => @moveRight()
+      'core:move-up': => @moveUp()
+      'core:move-down': => @moveDown()
+      'core:move-to-top': => @moveToTop()
+      'core:move-to-bottom': => @moveToBottom()
+      'core:page-up': => @pageUp()
+      'core:page-down': => @pageDown()
+      'core:select-right': => @expandSelectionRight()
+      'core:select-left': => @expandSelectionLeft()
+      'core:select-up': => @expandSelectionUp()
+      'core:select-down': => @expandSelectionDown()
+      'table-edit:select-to-end-of-line': => @expandSelectionToEndOfLine()
+      'table-edit:select-to-beginning-of-line': => @expandSelectionToBeginningOfLine()
+      'table-edit:select-to-end-of-table': => @expandSelectionToEndOfTable()
+      'table-edit:select-to-beginning-of-table': => @expandSelectionToBeginningOfTable()
+      'mousedown': (e) => e.preventDefault(); @focus()
+
+    @subscribeTo @body,
+      'scroll': => @requestUpdate()
+      'dblclick': (e) => @startEdit()
+      'mousedown': (e) =>
+        e.preventDefault()
+
+        @stopEdit() if @isEditing()
+
+        if position = @cellPositionAtScreenPosition(e.pageX, e.pageY)
+          @activateCellAtPosition position
+
+        @focus()

     @configUndefinedDisplay = atom.config.get('table-edit.undefinedDisplay')
-    subs.add @asDisposable atom.config.observe 'table-edit.undefinedDisplay', (@configUndefinedDisplay) =>
-      @requestUpdate()
-
     @configPageMovesAmount = atom.config.get('table-edit.pageMovesAmount')
-    subs.add @asDisposable atom.config.observe 'table-edit.pageMovesAmount', (@configPageMovesAmount) =>
-      @requestUpdate()
-
     @configRowHeight = atom.config.get('table-edit.rowHeight')
-    subs.add @asDisposable atom.config.observe 'table-edit.rowHeight', (@configRowHeight) =>
-      @computeRowOffsets()
-      @requestUpdate()
-
     @configRowOverdraw = atom.config.get('table-edit.rowOverdraw')
-    subs.add @asDisposable atom.config.observe 'table-edit.rowOverdraw', (@configRowOverdraw) =>
-      @requestUpdate()
+
+    @observeConfig
+      'table-edit.undefinedDisplay': (@configUndefinedDisplay) =>
+        @requestUpdate()
+      'table-edit.pageMovesAmount': (@configPageMovesAmount) => @requestUpdate()
+      'table-edit.rowHeight': (@configRowHeight) =>
+        @computeRowOffsets()
+        @requestUpdate()
+      'table-edit.rowOverdraw': (@configRowOverdraw) => @requestUpdate()

     @setSelectionFromActiveCell()
     @subscribeToColumn(column) for column in @table.getColumns()
@@ -115,6 +114,14 @@
     @rows ?= @body.find('.table-edit-rows')

   getUndefinedDisplay: -> @undefinedDisplay ? @configUndefinedDisplay
+
+  subscribeTo: (object, events) ->
+    for event, callback of events
+      @subscriptions.add @asDisposable object.on event, callback
+
+  observeConfig: (configs) ->
+    for config, callback of configs
+      @subscriptions.add @asDisposable atom.config.observe config, callback

   #    ########   #######  ##      ##  ######
   #    ##     ## ##     ## ##  ##  ## ##    ##


refactor

refactor

--- lib/timedevent.coffee
+++ lib/timedevent.coffee
@@ -6,7 +6,7 @@
     @id = args.id
     @event = args.event

-@TimedEvents = class TimedEvents
+class @TimedEvents
   constructor: ->
     @list = []
   event2date: (e) ->


Apply filter options to bundled packages

--- src/list.coffee
+++ src/list.coffee
@@ -99,6 +99,14 @@
       _atomPackages ?= {}
       packages = (metadata for packageName, {metadata} of _atomPackages)

+      packages = packages.filter (metadata) ->
+        if options.argv.themes
+          metadata.theme
+        else if options.argv.packages
+          not metadata.theme
+        else
+          true
+
       unless options.argv.bare or options.argv.json
         if options.argv.themes
           console.log "#{'Built-in Atom themes'.cyan} (#{packages.length})"


Add back coffee-script require but don't assign it

--- src/main.coffee
+++ src/main.coffee
@@ -2,6 +2,7 @@
 path = require 'path'
 delegate = require 'atom_delegate'
 optimist = require 'optimist'
+require 'coffee-script'

 atomApplication = null


Cache selector specificity

Cache selector specificity

--- src/main.coffee
+++ src/main.coffee
@@ -1,6 +1,14 @@
 specificity = require 'specificity'
+
+cache = {}
+
 module.exports =
   specificity: (selector) ->
+    cachedSpecificity = cache[selector]
+    return cachedSpecificity if cachedSpecificity?
+
     resultString = specificity.calculate(selector)[0].specificity
     [i, a, b, c] = resultString.split(',').map (value) -> parseInt(value)
-    (a * 100) + (b * 10) + (c * 1)
+    selectorSpecificity = (a * 100) + (b * 10) + (c * 1)
+    cache[selector] = selectorSpecificity
+    selectorSpecificity


Tolerate parsing error

Tolerate parsing error

--- src/main.coffee
+++ src/main.coffee
@@ -46,7 +46,11 @@
     mod.nameIndexes = {}
     mod.rawFilePath = filePath
     filePathIndexesInMods[filePath] = mods.length - 1
-    parsed = esprima.parse(code)
+    parsed = esprima.parse("""
+        (function(exports, module, require) {
+        #{code}
+        })();
+    """, {tolerant: true})
     checkTreeNode = (node) ->
         if not (typeof node == "object" and node != null)
             return


templatePrefix can now be passed in the POST body

--- src/main.coffee
+++ src/main.coffee
@@ -30,13 +30,13 @@
 require './string_ext'

 # create a new job using another job as a template
-copy_job_from_template = (newJobPrefix, copyFrom, jenkinsUrl) ->
+copy_job_from_template = (templatePrefix, newJobPrefix, copyFrom, jenkinsUrl) ->
   logger.info 'main#copy_job_from_template',
     newJobPrefix: newJobPrefix
     copyFrom: copyFrom
     jenkinsUrl: jenkinsUrl

-  newJobName = newJobPrefix + copyFrom.slice(config.jobPrefix.length)
+  newJobName = newJobPrefix + copyFrom.slice(templatePrefix.length)
   urlParams = '/createItem?name=' + newJobName + '&mode=copy&from=' + copyFrom
   createUrl = url.resolve(jenkinsUrl, urlParams)

@@ -52,8 +52,10 @@
       statusCode: response.statusCode

 # get all jobs that start with config.expectedJobPrefix
+# param: jenkinsUrl => the base url to the jenkins server
+# param: templatePrefix => the prefix for the templates to search for on the server
 # param: callback(jobsFound)
-get_template_jobs = (jenkinsUrl, callback) ->
+get_template_jobs = (jenkinsUrl, templatePrefix, callback) ->
   urlParams = '/api/json'
   requestUrl = url.resolve(jenkinsUrl, urlParams)
   logger.info 'main#get_template_jobs',
@@ -71,11 +73,11 @@
         logger.info 'job on Jenkins',
           index: index
           job: element
-        if element.name.startsWith(config.jobPrefix)
+        if element.name.startsWith(templatePrefix)
           jobsFound.push new JenkinsJob(element.name, element.url)
     callback(jobsFound)

-# callback for API '/api/create-jobs/:id'
+# callback for API '/api/create-jobs'
 exports.create_jobs = (req, res) ->
   logger.info 'main#create_jobs',
     body: req.body
@@ -84,11 +86,15 @@
   if _.isNull(jenkinsUrl) || _.isUndefined(jenkinsUrl)
     jenkinsUrl = config.defaultJenkinsUrl

-  get_template_jobs jenkinsUrl, (jobsFound) ->
+  templatePrefix = req.body.jenkinsUrl
+  if _.isNull(templatePrefix) || _.isUndefined(templatePrefix)
+    templatePrefix = config.jobPrefix
+
+  get_template_jobs jenkinsUrl, templatePrefix, (jobsFound) ->
     logger.info 'jobs found =>',
       jobs: jobsFound

     _.each jobsFound, (element, index, list) ->
-      copy_job_from_template(req.body.jobPrefix, element.name, jenkinsUrl)
+      copy_job_from_template(templatePrefix, req.body.jobPrefix, element.name, jenkinsUrl)

   res.send 200


Always use Pane::addItem when adding items.

--- src/pane.coffee
+++ src/pane.coffee
@@ -31,15 +31,18 @@

   # Private:
   initialize: (args...) ->
+    @items = []
     if args[0] instanceof telepath.Document
       @state = args[0]
-      @items = _.compact @state.get('items').map (item) ->
-        atom.deserializers.deserialize(item)
-    else
-      @items = args
+      _.compact @state.get('items').each (item) =>
+        @addItem(item, @items.length) if item = atom.deserializers.deserialize(item)
+
+    else
+      items = args
       @state = atom.site.createDocument
         deserializer: 'Pane'
-        items: @items.map (item) -> item.getState?() ? item.serialize()
+        items: items.map (item) -> item.getState?() ? item.serialize()
+      items.forEach (item) => @addItem(item, @items.length)

     @subscribe @state.get('items'), 'changed', ({index, removedValues, insertedValues, siteId}) =>
       return if siteId is @state.siteId


Improve changelog generation task

Improve changelog generation task

--- tasks/changelog.coffee
+++ tasks/changelog.coffee
@@ -9,16 +9,25 @@
   fs.outputFileAsync './build/changelogs/deb.txt',
     changelogJson
       .map (release) ->
-        log = release.changes
-          .map (line) -> ' * ' + line
-          .join '\n'
+        if Array.isArray release.changes
+          log = release.changes
+            .map (line) -> '  - ' + line
+            .join '\n'
+        else
+          log = []
+          for key in Object.keys(release.changes)
+            logs = release.changes[key]
+              .map (line) -> '    - ' + line
+              .join '\n'
+            log.push '  * ' + key
+            log.push logs
+          log = log.join '\n'

         parsedDate = new Date(release.releasedAt)
         date = moment(parsedDate).format('ddd, DD MMM YYYY HH:mm:ss ZZ')

-        return manifest.name + ' (' + release.version.split('-')[0] + ') ' +
-          (release.version + '-stable').split('-')[1] + '; urgency=' + release.urgency +
-          '\n\n' + log + '\n\n' +
+        return manifest.name + ' (' + release.version + ') ' + release.channel +
+          '; urgency=' + release.urgency + '\n\n' + log + '\n\n' +
           '-- ' + manifest.author + '  ' + date
       .join '\n\n'

@@ -26,22 +35,45 @@
   fs.outputFileAsync './build/changelogs/rpm.txt',
     changelogJson
       .map (release) ->
-        log = release.changes
-          .map (line) -> '- ' + line
-          .join '\n'
+        if Array.isArray release.changes
+          log = release.changes
+            .map (line) -> '- ' + line
+            .join '\n'
+        else
+          log = []
+          for key in Object.keys(release.changes)
+            logs = release.changes[key]
+              .map (line) -> '- ' + key + ': ' + line
+              .join '\n'
+            log.push logs
+          log = log.join '\n'

         parsedDate = new Date(release.releasedAt)
         date = moment(parsedDate).format('ddd MMM DD YYYY')

-        return '* ' + date + ' ' + manifest.author + ' ' + release.version + '\n' + log
+        channelSuffix = ''
+        if release.channel != 'stable'
+          channelSuffix = '-' + release.channel
+
+        return '* ' + date + ' ' + manifest.author + ' ' + release.version + channelSuffix + '\n' + log
       .join '\n\n'

 gulp.task 'changelog:md', () ->
   changelog = changelogJson
     .map (release, index) ->
-      log = release.changes
-        .map (line) -> '- ' + line
-        .join '\n'
+      if Array.isArray release.changes
+        log = release.changes
+          .map (line) -> '- ' + line
+          .join '\n'
+      else
+        log = []
+        for key in Object.keys(release.changes)
+          logs = release.changes[key]
+            .map (line) -> '- ' + line
+            .join '\n'
+          log.push '\n**' + key + '**\n'
+          log.push logs
+        log = log.join '\n'

       parsedDate = new Date(release.releasedAt)
       date = moment(parsedDate).format('YYYY-DD-MM')
@@ -49,12 +81,16 @@
       fullChangelog = ''
       if index < changelogJson.length - 1
         fullChangelog = '[Full Changelog](https://github.com/Aluxian/Whatsie/compare/v' +
-          changelogJson[index+1].version.split('-')[0] + '...v' + release.version.split('-')[0] + ') • '
+          changelogJson[index+1].version + '...v' + release.version + ') • '

-      download = '[Download](https://github.com/Aluxian/Whatsie/releases/tag/v' + release.version.split('-')[0] + ')'
+      download = '[Download](https://github.com/Aluxian/Whatsie/releases/tag/v' + release.version + ')'

-      return '## [' + release.version + '](https://github.com/Aluxian/Whatsie/tree/v' +
-          release.version.split('-')[0] + ') (' + date + ')\n\n' + fullChangelog + download + '\n\n' + log
+      channelSuffix = ''
+      if release.channel != 'stable'
+        channelSuffix = '-' + release.channel
+
+      return '## [' + release.version + channelSuffix + '](https://github.com/Aluxian/Whatsie/tree/v' +
+          release.version + ') (' + date + ')\n\n' + fullChangelog + download + '\n' + log
     .join '\n\n'
   changelog += '\n'
   fs.outputFileAsync './CHANGELOG.md', changelog


added Repo#remote_remove(name, callback)

--- src/repo.coffee
+++ src/repo.coffee
@@ -103,6 +103,16 @@
     , (err, stdout, stderr) ->
       callback err

+  # Public: Remove a remote.
+  #
+  # name     - String name of the remote.
+  # callback - Receives `(err)`
+  #
+  remote_remove: (name, callback) ->
+    @git "remote", {}, ["rm", name]
+    , (err, stdout, stderr) ->
+      callback err
+
   # Public: `git fetch <name>`.
   #
   # name     - String name of the remote


Fix socket not closing when changing league

--- elements/stream-channel.coffee
+++ elements/stream-channel.coffee
@@ -17,6 +17,7 @@
       onMessageCallbacks[data.channel](data.message)

   socket.emit('presence', leagueId)
+  socket

 Polymer
   is: 'stream-channel'
@@ -46,7 +47,7 @@
   _onLeagueChange: ->
     @whenReady.then =>
       if @connection
-        @connection.leave()
+        @connection.close()
         clearInterval(@intervalId)
         @connection = null
       if @leagueId


pass render test

pass render test

--- src/test.coffee
+++ src/test.coffee
@@ -14,15 +14,15 @@
 {test_god} = require './test/test_god'
 {test_world} = require './test/test_world'
 {test_game} = require './test/test_game'
+{test_layout} = require './layout/test_layout'
 {test_render} = require './test/test_render'
-{test_layout} = require './layout/test_layout'

 run_tests = (rx) ->
 #  test_god(test, rx)
 #  test_world(test, rx)
   test_game(test, rx)
+  test_layout(test, rx)
   test_render(test, rx)
-  test_layout(test, rx)

 exports.test = (rx) ->
   console.log "Running tests..."


Minor updates + fixes in Time.

Minor updates + fixes in Time.

--- src/time.coffee
+++ src/time.coffee
@@ -1,5 +1,5 @@
 util = require './util'
-
+math = require './math/math'

 ###
   Timer: keeps track of time, measures intervals etc.
@@ -155,6 +155,11 @@
   equals: (time) -> @value == if typeof(time) == 'number' then time else time.value
   toString: -> "Time(#{@value}ms)"

+  # Returns its position relative to the given Timespan.
+  normalizedTo: (span) -> math.fit(@value, span.from.value, span.to.value, 0, 1)
+
+  toFrame: (fps=60) -> Math.round(@value / (1000 / fps))
+
   # factory methods
   @s: (seconds) -> new Time seconds * 1000
   @ms: (milliseconds) -> new Time milliseconds
@@ -174,11 +179,11 @@

       when 1
         @from = new Time(0)
-        @to = new Time(argument[0])
+        @to = new Time(arguments[0])

       when 2
-        @from = new Time( arguments[0] )
-        @to = new Time( arguments[1] )
+        @from = new Time(arguments[0])
+        @to = new Time(arguments[1])

   # returns a list of Time events by stepping through this timespan along a given interval
   # interval can be another time object or millisecond value


refactor after testing

refactor after testing

fixed intended changes for #11 and allow for falsey form field values

--- templates:forms.coffee
+++ templates:forms.coffee
@@ -97,15 +97,20 @@
     # Store validated element values as local data (can't submit invalid data anyway)

     validatedValues = {} # (non-reactive)
-
+
     # Track which fields have changed when initial data is present (Issue #11)
     changedValues = {}

     self.setValidatedValue = (field, value) ->

       # First, opt into setting `changed` if this is a unique update.
-      if validatedValues[field] && !_.isEqual(value, validatedValues[field])
+      if _.has(validatedValues, field) && !_.isEqual(value, validatedValues[field])
         validatedValues[field] = value
+
+        # If the value was present in initial data, set it as `changed`.
+        if self.data.data && _.has(self.data.data, field)
+          changedValues[field] = value
+
         setChanged()

       # If the field doesn't exist in validatedValues yet, add it.
@@ -115,11 +120,8 @@
         # If initial data was provided--
         # Initial validation passing back that value shouldn't trigger `changed`.
         if self.data.data
-          unless self.data.data[field] && _.isEqual(self.data.data[field], value)
+          unless _.has(self.data.data, field) && _.isEqual(self.data.data[field], value)
             setChanged()
-
-            # Update field in `changedValues`.
-            changedValues[field] = value

         # If no initial data was provided, trigger `changed` because it's a new value.
         else
@@ -224,6 +226,7 @@

       self = this
       parentData = Template.parentData(1)
+      initValue = null

       self.valid = new Blaze.ReactiveVar(true)
       self.changed = new Blaze.ReactiveVar(false)
@@ -241,13 +244,17 @@
         self.success = parentData.success || null
         self.schema = parentData.schema || null
         self.schemaContext = parentData.schemaContext || null
-        initValue = parentData.data && parentData.data[self.field] || null
+
+        if parentData.data && _.has(parentData.data, self.field)
+          initValue = parentData.data[self.field]

       # But if not, run standalone
       else
         self.schema = self.data.schema || null
         self.schemaContext = self.data.schema && self.data.schema.newContext() || null
-        initValue = self.data.data && self.data.data[self.field] || null
+
+        if self.data.data && _.has(self.data.data, self.field)
+          initValue = self.data.data[self.field]

       # Value
       # -----


Make it work on IE9 by using JSONP

Make it work on IE9 by using JSONP

--- webapp/app/scripts/models/DistrictIdFinder.coffee
+++ webapp/app/scripts/models/DistrictIdFinder.coffee
@@ -46,6 +46,7 @@
           data:
             key: googleApiKey
             sql: "SELECT id FROM #{fusionTableId} WHERE ST_INTERSECTS(geometry,RECTANGLE(LATLNG(#{latitude},#{longitude}),LATLNG(#{latitude},#{longitude})))"
+          dataType: 'jsonp'
           success: (json) ->
             id = json?.rows?[0]?[0]
             realId = if id


Updated module tests

Updated module tests

--- test/all/module.coffee
+++ test/all/module.coffee
@@ -22,6 +22,9 @@
     expect(resProp).to.have.property('a')
     expect(resProp).to.have.property('b')
     expect(resProp).to.have.property('c')
+    expect(resProp.a).to.equal(5)
+    expect(resProp.b).to.equal(4)
+    expect(resProp.c).to.equal(3)

   it 'udefine without dependencies (function factory)', ->
     udefine 'def', -> 42
@@ -30,3 +33,57 @@

     expect(resProp).to.be.a('number')
     expect(resProp).to.equal(42)
+
+  it 'udefine module (object) definition with injection', ->
+    injectObj = {}
+
+    udefine.inject.add 'ghi',
+      root: injectObj
+      name: 'ghi'
+
+    udefine 'ghi',
+      g: 7
+      h: 8
+      i: 9
+
+    expect(injectObj).to.be.a('object')
+    expect(injectObj.ghi).to.be.a('object')
+    expect(injectObj.ghi).to.have.property('g')
+    expect(injectObj.ghi).to.have.property('h')
+    expect(injectObj.ghi).to.have.property('i')
+    expect(injectObj.ghi.g).to.equal(7)
+    expect(injectObj.ghi.h).to.equal(8)
+    expect(injectObj.ghi.i).to.equal(9)
+
+  it 'udefine module (function) definition with injection', ->
+    injectObj = {}
+
+    udefine.inject.add 'jkl',
+      root: injectObj
+      name: 'jkl'
+
+    udefine 'jkl',
+      j: true
+      k: 'string'
+      l: ->
+
+    expect(injectObj).to.be.a('object')
+    expect(injectObj.jkl).to.be.a('object')
+    expect(injectObj.jkl).to.have.property('j')
+    expect(injectObj.jkl).to.have.property('k')
+    expect(injectObj.jkl).to.have.property('l')
+    expect(injectObj.jkl.j).to.equal(true)
+    expect(injectObj.jkl.k).to.equal('string')
+
+  it 'udefine module definition with injection (module !== injection)', ->
+    injectObj = {}
+
+    udefine.inject.add 'mno',
+      root: injectObj
+      name: 'Universe'
+
+    udefine 'mno', -> -> 42
+
+    expect(injectObj).to.be.a('object')
+    expect(injectObj.Universe).to.be.a('function')
+    expect(injectObj.Universe()).to.equal(42)


add test.

add test.

--- test/basic-CRUD.coffee
+++ test/basic-CRUD.coffee
@@ -13,30 +13,34 @@
       done()

   describe "GET:", ->
-    it "should be successful to get all of the resources", (done) ->
+    it "should get all of the resources", (done) ->
       request(app)
         .get("/odata/books")
         .expect(200)
         .expect('Content-Type', /json/)
         .end (err, res) ->
-          done(err)  if(err)
+          if(err)
+            done(err)
+            return
           res.body.should.be.have.property('value')
           res.body.value.length.should.be.equal(books.length)
           res.body.value[0].title.should.be.equal(books[0].title)
           done()
-    it "should be successful to get all of the resources", (done) ->
+    it "should get one of the resources", (done) ->
       request(app)
         .get("/odata/books/#{books[1]._id}")
         .expect(200)
         .expect('Content-Type', /json/)
         .end (err, res) ->
-          done(err)  if(err)
+          if(err)
+            done(err)
+            return
           res.body.should.be.have.property('title')
           res.body.title.should.be.equal(books[1].title)
           done()

   describe "POST:", ->
-    it "should be successful to create a new resource", (done) ->
+    it "should create new resource", (done) ->
       request(app)
         .post("/odata/books")
         .send
@@ -50,30 +54,52 @@
         .expect(201)
         .expect('Content-Type', /json/)
         .end (err, res) ->
-          done(err)  if(err)
+          if(err)
+            done(err)
+            return
           res.body.should.be.have.property('_id')
           res.body.should.be.have.property('title')
           res.body.title.should.be.equal("Steve Jobs")
           done()
-    it "should be failed to create a new resource without data", (done) ->
+    it "should not create new resource if post nothing", (done) ->
       request(app)
         .post("/odata/books")
         .expect(422, done)

   describe "PUT:", ->
-    it "should be successful to modify an exist resource", (done) ->
-      done()
-    it "should be failed to modify an not exist resource", (done) ->
-      done()
-    it "should be failed to modify an resource without id", (done) ->
+    it "should modify resource if it exist", (done) ->
+      books[2].title = "I'm a new title"
+      request(app)
+        .put("/odata/books/#{books[2]._id}")
+        .send(books[2])
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end (err, res) ->
+          if(err)
+            done(err)
+            return
+          res.body.should.be.have.property('title')
+          res.body.title.should.be.equal(books[2].title)
+          done()
+    it "should not modify resource if it not exist", (done) ->
+      request(app)
+        .put("/odata/books/000000000000000000000000")
+        .send(books[3])
+        .expect(404, done)
+    it "should not modify resource if id is a wrong format", (done) ->
+      request(app)
+        .put("/odata/books/wrong-id")
+        .send(books[3])
+        .expect(500, done)
+    it "should not modify resource if not use id", (done) ->
       done()

   describe "DELETE:", ->
-    it "should be successful to delete a exist resource", (done) ->
+    it "should delete resource if it exist", (done) ->
       done()
-    it "should be failed to delete an not exist resource", (done) ->
+    it "should not delete resource if it not exist", (done) ->
       done()
-    it "should be failed to delete an resource without id", (done) ->
+    it "should not delete resource if not use id", (done) ->
       done()
-    it "should be failed to delete a resource twice", (done) ->
+    it "should not delete a resource twice", (done) ->
       done()


add a browser-based test too

add a browser-based test too

--- test/browser/main.iced
+++ test/browser/main.iced
@@ -1,5 +1,6 @@

 mods =
+  sigs : require "../files/verify_sigs.iced"
   multiples : require "../files/multiples.iced"
   unbox_cant_verify : require '../files/unbox_cant_verify.iced'
   zip : require '../files/zip.iced'


fixed #1

fixed #1

--- src/view.coffee
+++ src/view.coffee
@@ -2,8 +2,22 @@

 .directive 'poiView', ['$injector', ($injector) ->
     $router = $injector.get '$router'
+    $rootScope = $injector.get '$rootScope'
+    $location = $injector.get '$location'
     $compile = $injector.get '$compile'
     $controller = $injector.get '$controller'
+
+    onClickLink = (event) ->
+        if event.ctrlKey or event.metaKey or event.shiftKey or event.which is 2 or event.button is 2
+            return
+        target = event.target.target
+        href = event.target.href
+        return if target or not href
+        if $location.absUrl() is href
+            # reload
+            event.preventDefault()
+            $rootScope.$apply ->
+                $router.reload yes

     restrict: 'A'
     link: (scope, $element) ->
@@ -18,6 +32,8 @@
                 @param destroy {bool} if it is true, call .$destroy()
                 ###
                 if destroy
+                    if @rule.parents.length is 1
+                        $element.off 'click', 'a', onClickLink
                     @scope?.$destroy()
                 else if @rule
                     if $router.oldState.name.indexOf("#{$router.state.name}.") is 0
@@ -33,8 +49,12 @@
                     $controller rule.controller, resolve
                 $element.html rule.template
                 $compile($element.contents()) @scope
+                if rule.parents.length is 1
+                    $element.on 'click', 'a', onClickLink
             destroy: ->
                 return if not @rule
+                if @rule.parents.length is 1
+                    $element.off 'click', 'a', onClickLink
                 @scope.$destroy()
                 @scope = null
                 @rule = null


リファクタリング

リファクタリング

--- bin/lamjet.coffee
+++ bin/lamjet.coffee
@@ -41,18 +41,6 @@
         return Promise.resolve(result)
       .then (result)-> writeArtifact("package.json", result.body)

-  makeLambdaConfigJs = ->
-    return readTemplate("lambda-config.js")
-      .then (result)-> writeArtifact("lambda-config.js", result.body)
-
-  makeGitIgnore = ->
-    return readTemplate("gitignore")
-      .then (result)-> writeArtifact(".gitignore", result.body)
-
-  makeGulpfileCoffee = ->
-    return readTemplate("gulpfile.coffee")
-      .then (result)-> writeArtifact("gulpfile.coffee", result.body)
-
   copyTemplate = (source, destination)->
     return readTemplate(source)
       .then (result)-> writeArtifact((destination ? source), result.body)
@@ -64,13 +52,14 @@

   Promise.resolve()
     .then (result)-> makePackageJson(functionName: defaultFunctionName)
-    .then (result)-> makeLambdaConfigJs()
-    .then (result)-> makeGitIgnore()
-    .then (result)-> makeGulpfileCoffee()
+    .then (result)-> copyTemplate("lambda-config.js")
+    .then (result)-> copyTemplate("gitignore", ".gitignore")
+    .then (result)-> copyTemplate("gulpfile.coffee")
     .then (result)-> copyTemplate("index.coffee", path.join("src", "index.coffee"))
     .then (result)-> copyTemplate("index_spec.coffee", path.join("src", "index_spec.coffee"))
     .then (result)->
-      console.log(JSON.stringify({then: result}, null, 2))
+      # console.log(JSON.stringify({then: result}, null, 2))
+      console.log("ok")

 else
   console.log "Usage: lamjet init"


bom: test false value

bom: test false value

--- test/option.bom.coffee
+++ test/option.bom.coffee
@@ -5,6 +5,17 @@

   it 'preserve bom if not defined', (next) ->
     parser = parse (err, data) ->
+      data.should.eql [
+        ['\ufeffa', 'b', 'c']
+        ['d', 'e', 'f']
+      ]
+      next()
+    parser.write Buffer.from "\ufeffa,b,c\n"
+    parser.write Buffer.from 'd,e,f'
+    parser.end()
+
+  it 'preserve BOM if false', (next) ->
+    parser = parse bom: false, (err, data) ->
       data.should.eql [
         ['\ufeffa', 'b', 'c']
         ['d', 'e', 'f']


Oscillator

Oscillator


Add functional tests for listener middleware

--- test/robot_test.coffee
+++ test/robot_test.coffee
@@ -228,3 +228,75 @@
         done()

       @robot.receive testMessage
+
+    describe 'Listener Middleware', ->
+      it 'allows listener callback execution', (testDone) ->
+        listenerCallback = sinon.spy()
+        @robot.hear /^message123$/, listenerCallback
+        @robot.listenerMiddleware (robot, listener, response, next, done) ->
+          # Allow Listener callback execution
+          next done
+
+        testMessage = new TextMessage @user, 'message123'
+        @robot.receive testMessage, () ->
+          expect(listenerCallback).to.have.been.called
+          testDone()
+
+      it 'can block listener callback execution', (testDone) ->
+        listenerCallback = sinon.spy()
+        @robot.hear /^message123$/, listenerCallback
+        @robot.listenerMiddleware (robot, listener, response, next, done) ->
+          # Block Listener callback execution
+          done()
+
+        testMessage = new TextMessage @user, 'message123'
+        @robot.receive testMessage, () ->
+          expect(listenerCallback).to.not.have.been.called
+          testDone()
+
+      it 'receives the correct arguments', (testDone) ->
+        @robot.hear /^message123$/, () ->
+        testListener = @robot.listeners[0]
+        testMessage = new TextMessage @user, 'message123'
+
+        @robot.listenerMiddleware (robot, listener, response, next, done) =>
+          expect(robot).to.equal(@robot)
+          expect(listener).to.equal(testListener)
+          expect(response.message).to.equal(testMessage)
+          expect(next).to.be.a('function')
+          expect(done).to.be.a('function')
+          testDone()
+
+        @robot.receive testMessage
+
+      it 'executes middleware in order of definition', (testDone) ->
+        execution = []
+
+        testMiddlewareA = (robot, listener, response, next, done) ->
+          execution.push 'middlewareA'
+          next () ->
+            execution.push 'doneA'
+            done()
+
+        testMiddlewareB = (robot, listener, response, next, done) ->
+          execution.push 'middlewareB'
+          next () ->
+            execution.push 'doneB'
+            done()
+
+        @robot.listenerMiddleware testMiddlewareA
+        @robot.listenerMiddleware testMiddlewareB
+
+        @robot.hear /^message123$/, () ->
+          execution.push 'listener'
+
+        testMessage = new TextMessage @user, 'message123'
+        @robot.receive testMessage, () ->
+          expect(execution).to.deep.equal([
+            'middlewareA'
+            'middlewareB'
+            'listener'
+            'doneB'
+            'doneA'
+          ])
+          testDone()


JWorkspace: minor code styling

JWorkspace: minor code styling

--- workers/social/lib/social/models/workspace.coffee
+++ workers/social/lib/social/models/workspace.coffee
@@ -60,10 +60,10 @@

       { slug, name } = res

-      data.name     = name
-      data.slug     = slug
-      data.rootPath = "/home/#{nickname}/Workspaces/#{slug}"  unless data.rootPath
-      workspace     = new JWorkspace data
+      data.name      = name
+      data.slug      = slug
+      data.rootPath  = "/home/#{nickname}/Workspaces/#{slug}"  unless data.rootPath
+      workspace      = new JWorkspace data

       workspace.save (err) ->
         return callback err  if err
@@ -108,8 +108,8 @@

     JWorkspace.one selector, (err, ws)->
       return callback err  if err?
-      unless ws?
-        callback new KodingError "Workspace not found."
+      unless ws
+        callback new KodingError 'Workspace not found.'
       else
         ws.remove (err)-> callback err


WorkspaceModel: Use sort and limit for workspace query.

--- workers/social/lib/social/models/workspace.coffee
+++ workers/social/lib/social/models/workspace.coffee
@@ -26,7 +26,7 @@

   @create = secure (client, data, callback) ->
     data.owner = client.connection.delegate._id
-    data.slug  = Inflector.slugify data.name.toLowerCase()
+    data.slug  = Inflector.slugify data.name?.toLowerCase()

     {name, slug, machineUId, rootPath, owner, layout} = data

@@ -38,18 +38,22 @@
           owner : client.connection.delegate._id
           slug  : new RegExp slug

-        JWorkspace.some query, {}, (err, workspaces) ->
+        options =
+          sort  : slug: -1
+          limit : 1
+
+        JWorkspace.some query, options, (err, workspaces) ->
           return callback err, null  if err

-          seed = 1
+          if name is 'My Workspace' and workspaces.length is 0
+            workspaces = [ { name: 'My Workspace', slug: 'my-workspace' } ]

-          for workspace in workspaces
-            parts = workspace.slug.split '-'
-            last  = parts[parts.length - 1]
-            seed  = ++last  unless isNaN last
-
-          name = "#{name} #{seed}"
-          slug = "#{slug}-#{seed}"
+          workspace = workspaces[0]
+          parts     = workspace.slug.split '-'
+          last      = parts[parts.length - 1]
+          seed      = if isNaN last then 1 else ++last
+          name      = "#{name} #{seed}"
+          slug      = "#{slug}-#{seed}"

           create_ { name, slug, machineUId, rootPath, owner, layout }, callback
       else


options: easier handling of an error message

--- cs/options.coffee
+++ cs/options.coffee
@@ -359,14 +359,13 @@
   busStopId = Stops.nameAndDirectionToId stopName, direction

   Bus.getLines busStopId, (json) ->
-    # Did the json even reach us? Is the result an error message?
-    # HOTFIX console.log json ############################################################
-    errorMessage = null
-    if typeof json is 'undefined' then errorMessage = 'Oops, frakoblet'
-    if typeof json is 'string' then errorMessage = json
-    if typeof json[0] isnt 'undefined' then errorMessage = 'Feil: ' + json[0]

-    if errorMessage isnt null
+    # HOTFIX
+    console.log json ############################################################
+
+    # Error message?
+    if typeof json is 'string'
+      errorMessage = json
       # Show error message
       $(cssSelector + ' .lines').html ''+errorMessage+''
       # Show retry-button


Fix Drop Table Sorting

Fix Drop Table Sorting

--- server/app/assets/javascript/drop.coffee
+++ server/app/assets/javascript/drop.coffee
@@ -42,6 +42,7 @@
         @drops = xs.reverse().map (it) ->
           it.getShipName ?= 'ドロップ無し'
           it
+        @drops.sort (x, y) -> y.count - x.count
     draw: ->
       countSum = @countUpDrops(@drops)
       typed = _.groupBy @drops, (drop) -> drop.getShipType


[Email Confirmation] Add status field for referrals which are waiting for user's email confirmation

--- client/app/Applications/Account.kdapplication/views/referralsystem.coffee
+++ client/app/Applications/Account.kdapplication/views/referralsystem.coffee
@@ -159,7 +159,14 @@
     super options, data

   viewAppended: ->
-    super
+    @getData().isEmailVerified (err, status)=>
+      unless (err or status)
+        @addSubView editLink = new KDCustomHTMLView
+           tagName      : "a"
+           partial      : "Mail Verification Waiting"
+           cssClass     : "action-link"
+
+      super

   partial: (data)->
     """


Purposefully fail test - TravisCI integration test

--- test/test_thing.coffee
+++ test/test_thing.coffee
@@ -2,4 +2,4 @@

 describe 'MyThing', ->
   it 'should do stuff', ->
-    'this thing'.should.equal('this thing')
+    'this thing'.should.not.equal('this thing')


Added test for keyIn.

Added test for keyIn.

--- test/utils-test.coffee
+++ test/utils-test.coffee
@@ -96,6 +96,19 @@
 			input = 'foo'
 			expect(utils.identity input).toBe input

+	describe 'keyIn', ->
+		it 'returns a filter function that can filter an Immutable.Map by a set of keys', ->
+			input = Immutable.Map
+				foo: 'bar'
+				bar: 'baz'
+				baz: 'quux'
+			test = Immutable.Map
+				foo: 'bar'
+				bar: 'baz'
+			expect(
+				Immutable.is input.filter(utils.keyIn 'foo', 'bar'), test
+			).toBe true
+
 	describe 'mapObjectRecursively', ->
 		it 'takes mapper functions and goes over the object recursively, applying to objects with provided props', ->
 			input =


Attempt at SVG creation, not yet working

--- svgtiler.coffee
+++ svgtiler.coffee
@@ -1,10 +1,22 @@
 path = require 'path'
 fs = require 'fs'
 csvParse = require 'csv-parse'
+xmldom = require 'xmldom'
+DOMParser = xmldom.DOMParser
+domImplementation = new xmldom.DOMImplementation()
+XMLSerializer = xmldom.XMLSerializer
+
+SVGNS = 'http://www.w3.org/2000/svg'

 splitIntoLines = (data) ->
   data.replace('\r\n', '\n').replace('\r', '\n').split('\n')
 whitespace = /[\s\uFEFF\xA0]+/  ## based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
+
+class Symbol
+  constructor: (@key, @svg) ->
+    @xml = new DOMParser().parseFromString @svg
+  @parse: (key, text) ->
+    new @ key, text

 class Input
   @encoding: 'utf8'
@@ -32,7 +44,7 @@
         key = line[0]  ## Whitespace at beginning means defining whitespace
       else
         key = line[...separator.index]
-      value = line[separator.index + separator[0].length..]
+      value = Symbol.parse key, line[separator.index + separator[0].length..]
       map[key] = value
     map

@@ -52,9 +64,21 @@
     console.log '->', filename
     fs.writeFileSync filename, @renderSVG mappings
   renderSVG: (mappings) ->
+    doc = domImplementation.createDocument SVGNS, 'svg'
+    doc.appendChild defs = doc.createElementNS SVGNS, 'defs'
+    symbols = {}
     for row, i in @data
       for cell, j in row
-        console.log i, j, cell, mappings.lookup cell
+        symbol = mappings.lookup cell
+        continue unless symbol?
+        symbols[symbol.key] = symbol
+        #console.log i, j, cell, symbol
+    for key, symbol of symbols
+      defs.appendChild node = doc.createElementNS SVGNS, 'symbol'
+      node.setAttribute 'id', key
+      node.appendChild symbol.xml
+    console.log doc.firstChild
+    new XMLSerializer().serializeToString doc

 class Mappings
   constructor: (@maps = []) ->


Tests check if popup window is created ok

--- spec/router_test.coffee
+++ spec/router_test.coffee
@@ -20,6 +20,8 @@
 window.Zhain.prototype.window_open = (url, checker) ->
   return @do (done) ->
     w = @w = window.open(url, "test_window")
+    if !w
+      throw new Error("Could not open pop-up window. Please enable popups.")
     window.Zhain.prototype.trie(done, 100, -> checker(w))

 window.Zhain.prototype.click = (selector, checker) ->


Make jasmine timeout quicker (the right way)

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -15,8 +15,6 @@
 require 'window'

 requireStylesheet "jasmine.css"
-
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 200

 beforeEach ->
   window.fixturesProject = new Project(require.resolve('fixtures'))
@@ -65,6 +63,8 @@
 jasmine.unspy = (object, methodName) ->
   throw new Error("Not a spy") unless object[methodName].originalValue?
   object[methodName] = object[methodName].originalValue
+
+jasmine.getEnv().defaultTimeoutInterval = 200

 window.keyIdentifierForKey = (key) ->
   if key.length > 1 # named key


Text now is rendered on visibility change

--- style.coffee
+++ style.coffee
@@ -346,13 +346,16 @@
 		if @waiting
 			return

-		if @node instanceof Text and not @isTextSet
+		if @node instanceof Text and not @isTextSet and @isRendered
 			visible = false
 		else
 			visible = @getVisibility()

 		if visible and not @isRendered and @file.isRendered
 			@renderRec()
+
+			if @node instanceof Text and not @isTextSet
+				visible = false

 		if @item
 			@setItemVisible visible


Reintroduce "Serial Monitor" menu item // Resolve #17

--- menus/main.cson
+++ menus/main.cson
@@ -35,6 +35,10 @@
       {
         'label': 'Serial Ports'
         'command': 'platformio-ide:maintenance.serial-ports'
+      }
+      {
+        'label': 'Serial Monitor'
+        'command': 'platformio-ide:maintenance.serial-monitor'
       }
       {'type': 'separator'}
       {


title cased menu option

title cased menu option

--- menus/tabs.cson
+++ menus/tabs.cson
@@ -15,7 +15,7 @@

     {type: 'separator'}

-    {label: 'Open this tab in new window', command: 'tabs:open-tab-in-new-window'}
+    {label: 'Open This Tab In New Window', command: 'tabs:open-tab-in-new-window'}
   ]

   '.tab-bar': [


Added method to check if document has owner field. Changed allowOwner to allow if the doc doesn't have an owner field.

--- src/AccountsUtil.coffee
+++ src/AccountsUtil.coffee
@@ -45,6 +45,8 @@
     user = @resolveUser(user)
     doc[@AUTHOR_FIELD] == user?.username

+  hasOwner: (doc) -> doc[@AUTHOR_FIELD]?
+
   isOwnerOrAdmin: (doc, user) -> @isOwner(doc, user) || @isAdmin(user)

   isAuthorized: (doc, user, predicate) ->
@@ -75,7 +77,7 @@
       catch e
     user

-  allowOwner: (userId, doc) -> @isOwnerOrAdmin(doc, userId)
+  allowOwner: (userId, doc) -> (userId? && !@hasOwner(doc)) || @isOwnerOrAdmin(doc, userId)

   setUpCollectionAllow: (collection) ->
     allowOwner = @allowOwner.bind(@)


Nginx: remove unnecessary configs

Nginx: remove unnecessary configs

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -316,10 +316,6 @@
           set $prerender 0;
         }

-        if ($arg__escaped_fragment_ !~* 'Pricing|About|Legal|Features') {
-          set $prerender 0;
-        }
-
         #resolve using Google's DNS server to force DNS resolution and prevent caching of IPs
         resolver 8.8.8.8;

@@ -381,12 +377,6 @@
        return 301 $scheme://koding.com$request_uri ;
     }

-    # redirect kodingen.com to koding.com
-    server {
-       server_name "~^kodingen.com" ;
-       return 301 $scheme://koding.com$request_uri ;
-    }
-
   # close http
   }
   """


dev tool: allow to give columns to display

New options in display_data script:

* *--columns col1,col2* allows to give the columns to display
* *--width 10,10* gives columns widths

Date and vendors are always displayed.

--- dev/display_data.coffee
+++ dev/display_data.coffee
@@ -1,16 +1,10 @@
 minimist = require 'minimist'
 moment = require 'moment'
 Table = require 'cli-table'
-
-table = new Table
-    head: ['date', 'amount', 'type', 'vendor']
-    colWidths: [13, 6, 20, 20]
-
 log = require('printit')
-    suffix: 'Konnector dev tool'
+    prefix: 'Konnector dev tool'

 argv = minimist process.argv.slice(2)
-
 model =  argv._[0]

@@ -21,14 +15,42 @@
     process.exit 1

-Model.all (err, models) ->
-    for model in models
-        table.push [
-            moment(model.date).format('YYYY-MM-DD')
-            model.amount or ''
-            model.type or ''
-            model.vendor or ''
-        ]
+if argv.delete
+    Model.requestDestroy 'byDate', (err) ->
+        log.info 'All models were destroyed.'

-    console.log table.toString()
-    console.log "#{models.length} rows"
+else
+    head = ['date', 'vendor']
+
+    cols = []
+    if argv.columns?
+        cols = argv.columns.toString().split ','
+        head = head.concat cols
+
+    if argv.widths?
+        widths = argv.widths.toString().split ','
+        widths = widths.map (width) ->
+            parseInt width
+        colWidths = [13, 20].concat widths
+    else
+        colWidths = [13, 20]
+
+    table = new Table
+        head: head
+        colWidths: colWidths
+
+    Model.all (err, models) ->
+        for model in models
+            row = [
+                moment(model.date).format('YYYY-MM-DD')
+                model.vendor or ''
+            ]
+            values = cols.map (col) ->
+                return model[col] or ''
+
+            row = row.concat values
+            table.push row
+
+        console.log table.toString()
+        console.log "#{models.length} rows"
+


Major refactor

Major refactor

--- src/Overloadable.coffee
+++ src/Overloadable.coffee
@@ -1,61 +1,176 @@
-Overloadable = (defaultFunction) ->
-    unless typeof defaultFunction is "function" or typeof defaultFunction is "undefined"
-    	throw new TypeError "If passed, argument defaultFunction must be a function"
+ERRORS =
+    INVALID_OVERLOAD_CALL: ".overload() should be called with at least 1 element defining signature and a function to call"
+    INVALID_OVERLOAD_FUNCTION: "You should pass a function that will be assigned to array of arguments"
+    NO_MATCHING_OVERLOADS: "No overloads matches given signature"
+    INVALID_DEFAULT_FUNCTION: "If passed, argument defaultFunction must be a function"
+    FUNCTION_NOT_EXTENSIBLE: "You cannot overload non-extensible function"
+    NO_SUCH_MATCHER: "That type of matcher doesn't exist"
+
+class Overloadable
+    @_inheritFromOverloadable: do ->
+        if typeof Object.setPrototypeOf is "function"
+            Object.setPrototypeOf Overloadable.prototype, Function.prototype
+
+            return (overloadableFunction) ->
+                Object.setPrototypeOf overloadableFunction, Overloadable.prototype
+        else return (overloadableFunction) ->
+            for own property of Overloadable.prototype
+                Object.defineProperty overloadableFunction, property,
+                    value: Overloadable.prototype[property]
+
+
+    constructor: (defaultFunction) ->
+        if defaultFunction? and typeof defaultFunction isnt "function"
+    	    throw new TypeError ERRORS.INVALID_DEFAULT_FUNCTION
+
+        overloadableFunction = ->
+    	    overloadableFunction._invoke this, arguments...
+
+        overloadableFunction._overloads = []
+        overloadableFunction._defaultFunction = defaultFunction;
+
+        Overloadable._inheritFromOverloadable overloadableFunction
+
+        return overloadableFunction
+
+    _invoke: (thisArg, passedArguments...) ->
+        matchedFunction = @match passedArguments
+        defaultFunction = @getDefault()
+
+        return matchedFunction.apply(thisArg, passedArguments) if matchedFunction?
+        return defaultFunction.apply(thisArg, passedArguments) if defaultFunction?
+
+        throw new Error ERRORS.NO_MATCHING_OVERLOADS
+
+    overload: (args...) ->
+        ###NO TESTS!
+        unless Object.isExtensible(this)
+            throw new TypeError ERRORS.FUNCTION_NOT_EXTENSIBLE
+        !!!###
+
+        argumentsLength = args.length;
+        if argumentsLength < 2 then throw new Error ERRORS.INVALID_OVERLOAD_CALL
+
+        passedSignature = args[...argumentsLength - 1]
+        functionToCall = args[argumentsLength - 1]
+
+        if typeof functionToCall isnt "function"
+            throw new Error ERRORS.INVALID_OVERLOAD_FUNCTION
+
+        overload = new Overload passedSignature, functionToCall;
+        @_overloads.push overload
+
+    getDefault: -> @_defaultFunction

-    signaturesContainer = []
-    overloadableFunction = ->
-        invoke.call this, signaturesContainer, arguments, defaultFunction
+    match: (passedArguments) ->
+        for overload in @._overloads
+            if overload.isSignatureMatchingArguments passedArguments
+                return overload.getAssignedFunction()
+        null
+
+###NO TESTS!
+for own property of Overloadable.prototype
+    Object.defineProperty Overloadable.prototype, property,
+        enumerable: false
+        writable: false
+        configurable: false
+!!!###

-    overloadableFunction.overload = overload.bind signaturesContainer
-    overloadableFunction.getDefault = ->
-        defaultFunction
+class Overload
+    constructor: (signature, @_assignedFunction) ->
+        compiledSignature = for signatureElement in signature
+            matcher = AbstractMatcher.getMatcher signatureElement
+            matcher.compile signatureElement
+
+        @_signature = compiledSignature
+
+    getSignature: -> @_signature[..]

-    overloadableFunction
+    getAssignedFunction: -> @_assignedFunction

-Overloadable.getClassOf = (what) ->
-    Object::toString
-        .call(what)
-        .match(/\[object (\w+)\]/)[1]
-        .toLowerCase();
+    isSignatureMatchingArguments: (passedArguments) ->
+        signature = @getSignature()
+        return false unless passedArguments.length is signature.length
+
+        for compiledMatcher, index in signature
+            argument = passedArguments[index]
+            return false unless compiledMatcher.match argument

-#overload is always bound to a signature container of an overloadable function
-overload = (argumentsList, functionToCall) ->
-    if not Array.isArray(argumentsList) or argumentsList.length is 0
-        throw new Error "You should pass a nonempty array of arguments expected in a signature"
+        true
+

-    unless typeof functionToCall is "function"
-        throw new Error "You should pass a function that will be assigned to array of arguments"
+class MatcherFactory
+    constructor: () ->
+        @_matchers = Object.create null
+
+    registerMatcher: (argumentClass, matcher) ->
+        if @_matchers[argumentClass]? then throw new Error
+
+        @_matchers[argumentClass] = matcher
+
+    getMatcher: (argumentClass) ->
+        unless @_matchers[argumentClass]? then throw new Error
+
+        new @_matchers[argumentClass]()
+
+matcherFactory = new MatcherFactory()
+
+class AbstractMatcher
+    @_getClassOf: (what) ->
+        whatAsString = Object::toString.call(what)
+        /\[object (\w+)\]/.exec(whatAsString)[1].toLowerCase()
+
+    @getMatcher: (argument) ->
+        argumentClass = @_getClassOf argument
+        matcherFactory.getMatcher argumentClass

-    @push { argumentsList, functionToCall }
+    compile: (value) ->
+        new CompiledMatcher this, value
+
+class TypeMatcher extends AbstractMatcher
+    match: (argument, overloadSignatureElement) ->
+        AbstractMatcher._getClassOf(argument) is overloadSignatureElement
+
+    matcherFactory.registerMatcher "string", TypeMatcher
+
+class AlternativeMatcher extends AbstractMatcher
+    match: (argument, overloadSignatureElement) ->
+        for element in overloadSignatureElement
+            matcher = AbstractMatcher.getMatcher element
+            if matcher.match(argument, element) then return true
+
+        return false
+
+    compile: (matcherValue) ->
+        flattenedArray = @flattenArray(matcherValue)
+        super(flattenedArray)
+
+    flattenArray: (array) ->
+        arrayCount = -1;
+        flattenedArray = array
+
+        while arrayCount isnt 0
+            previousStepResult = flattenedArray
+            arrayCount = 0
+            flattenedArray = []
+            for element in previousStepResult
+                if AbstractMatcher._getClassOf(element) is "array"
+                    flattenedArray.push element...
+                    arrayCount += 1
+                else
+                    flattenedArray.push element
+
+        flattenedArray
+
+    matcherFactory.registerMatcher "array", AlternativeMatcher
+
+class CompiledMatcher
+    constructor: (@_matcher, @_value) ->
+
+    getMatcher: () -> @_matcher
+
+    getValue: () -> @_value
+
+    match: (argument) -> @getMatcher().match argument, @getValue()

-invoke = (signaturesContainer, invocationArguments, defaultFunction) ->
-    argumentsList = Array::slice.call invocationArguments, 0
-
-    matchedFunction = getFunctionMatchingArgumentsList signaturesContainer, argumentsList
-    return matchedFunction.apply(this, argumentsList) if matchedFunction?
-
-    return defaultFunction.apply(this, argumentsList) if defaultFunction?
-
-    throw new Error "No overloaded signatures matches this one: #{argumentsList}"
-
-getFunctionMatchingArgumentsList = (signaturesContainer, argumentsList) ->
-    for signature in signaturesContainer
-        matchingResult = isSignatureMatchingArgumentsList signature.argumentsList, argumentsList
-        return signature.functionToCall if matchingResult is true
-
-     null
-
-isSignatureMatchingArgumentsList = (signatureArgumentsList, argumentsList) ->
-    return false if signatureArgumentsList.length isnt argumentsList.length
-    for signatureArgument, i in signatureArgumentsList
-        signatureArgumentType = Overloadable.getClassOf signatureArgument
-        if signatureArgumentType is "string"
-            return false if signatureArgument isnt Overloadable.getClassOf argumentsList[i]
-        else if signatureArgumentType is "array"
-            for argument in signatureArgument
-                return true if argument is Overloadable.getClassOf argumentsList[i]
-            return false
-
-    true
-
-@Overloadable = Overloadable
+@Overloadable = Overloadable


Changing and renaming current commit ammend test

--- spec/models/git-commit-amend-spec.coffee
+++ spec/models/git-commit-amend-spec.coffee
@@ -13,7 +13,7 @@

 commitFilePath = Path.join(repo.getPath(), 'COMMIT_EDITMSG')

-describe "GitCommitAmend", ->
+describe "GitCommitAmendShort", ->
   beforeEach ->
     spyOn(atom.workspace, 'getActivePane').andReturn currentPane
     spyOn(atom.workspace, 'open').andReturn Promise.resolve textEditor
@@ -39,7 +39,7 @@
         else Promise.resolve ''

   it "gets the previous commit message and changed files", ->
-    expectedGitArgs = ['whatchanged', '-1', '--name-status', '--format=%B']
+    expectedGitArgs = ['whatchanged', '-1', '--format=%B']
     GitCommitAmend repo
     expect(git.cmd).toHaveBeenCalledWith expectedGitArgs, cwd: repo.getWorkingDirectory()


Remove unecessary logs

Remove unecessary logs

--- packages/rocketchat-file/file.server.coffee
+++ packages/rocketchat-file/file.server.coffee
@@ -21,26 +21,19 @@

 detectGM = ->
-	console.log 'GM: Getting GraphicsMagick version'
 	exec 'gm version', Meteor.bindEnvironment (error, stdout, stderr) ->
-		console.log 'GM: GraphicsMagick', arguments
 		if not error? and stdout.indexOf('GraphicsMagick') > -1
-			console.log 'GM: GraphicsMagick installed'
 			RocketChatFile.enable()

 			RocketChat.Info.GraphicsMagick =
 				enabled: true
 				version: stdout
 		else
-			console.log 'GM: GraphicsMagick not installed'
 			RocketChat.Info.GraphicsMagick =
 				enabled: false

-		console.log 'GM: Getting ImageMagick version'
 		exec 'convert -version', Meteor.bindEnvironment (error, stdout, stderr) ->
-			console.log 'GM: ImageMagick', arguments
 			if not error? and stdout.indexOf('ImageMagick') > -1
-				console.log 'GM: ImageMagick installed'
 				if RocketChatFile.enabled isnt true
 					# Enable GM to work with ImageMagick if no GraphicsMagick
 					RocketChatFile.gm = RocketChatFile.gm.subClass({imageMagick: true})
@@ -50,7 +43,6 @@
 					enabled: true
 					version: stdout
 			else
-				console.log 'GM: ImageMagick not installed'
 				if RocketChatFile.enabled isnt true
 					RocketChatFile.disable()


cleanup

cleanup

--- js/src/service/SharingClient.coffee
+++ js/src/service/SharingClient.coffee
@@ -108,8 +108,6 @@
         realm       = UserUtils.principalToComponents(principal).realm
         userKeys    = usernamesToKeys(usernames, realm)

-        log.error('userkeys', userKeys)
-
         revocationRequest = new RevocationRequest { id, userKeys }
       .then ->
         @sharingApi.revokeObject(revocationRequest)


Fix upload issue Atom 1.7 (#772)

Fix upload issue Atom 1.7 (#772)

Atom 1.7 supported pane mode and change class hierarchy.
http://blog.atom.io/2017/04/12/atom-1-16.html

--- menus/remote-ftp.cson
+++ menus/remote-ftp.cson
@@ -1,16 +1,16 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'context-menu':
-  '.tree-view.full-menu': [
+  '.tree-view.full-menu, .tree-view .full-menu': [
       { label: 'Upload', command: 'remote-ftp:upload-selected' },
       { label: 'Download', command: 'remote-ftp:download-selected-local' },
       { label: 'Sync local -> remote', command: 'remote-ftp:sync-with-local' }
     ]
-  '.tree-view.multi-select': [
+  '.tree-view.multi-select, .tree-view .multi-select': [
       { label: 'Upload', command: 'remote-ftp:upload-selected' },
       { label: 'Download', command: 'remote-ftp:download-selected-local' },
       { label: 'Sync local -> remote', command: 'remote-ftp:sync-with-local' }
     ]
-  '.remote-ftp-view .list-tree.full-menu': [
+  '.remote-ftp-view .list-tree.full-menu, .remote-ftp-view .list-tree .full-menu': [
       { label: 'Add File', command: 'remote-ftp:add-file' },
       { label: 'Add Folder', command: 'remote-ftp:add-folder' },
       { label: 'Refresh', command: 'remote-ftp:refresh-selected' },
@@ -21,7 +21,7 @@
       { label: 'Sync local <- remote', command: 'remote-ftp:sync-with-remote' },
       { label: 'Hide', command: 'remote-ftp:toggle' }
     ]
-  '.remote-ftp-view .list-tree.multi-select': [
+  '.remote-ftp-view .list-tree.multi-select, .remote-ftp-view .list-tree .multi-select': [
       { label: 'Download', command: 'remote-ftp:download-selected' },
       { label: 'Sync local <- remote', command: 'remote-ftp:sync-with-remote' }
     ]


removed unnecessary comments

removed unnecessary comments

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -222,22 +222,6 @@

                 o.windowWidth = o.browserWindow.width()

-            # if optimalCrumbHeight isnt o.el.height()
-            #     o.compress()
-            # else
-            #     console.log 'must be decompressed'
-
-            # if typeof optimalWidth is "undefined"
-            #     if optimalCrumbHeight isnt o.el.height()
-            #         o.el.data "#{ pluginName.toLowerCase() }-optimalwidth", (current.width() + o.opts.allowance)
-
-            #         o.compress()
-            # else
-            #     if current.width() >= optimalWidth
-            #         o.decompress() if _this.isCompressed()
-            #     else
-            #         o.compress() unless _this.isCompressed()
-
             return null

         o.compress = ->


Fix indent

Fix indent

--- lib/atom-ternjs-view-manager.coffee
+++ lib/atom-ternjs-view-manager.coffee
@@ -4,33 +4,33 @@
 module.exports =
 class ViewManager

-    renamePanel: null
-    renameModel: null
-    manager: null
+  renamePanel: null
+  renameModel: null
+  manager: null

-    constructor: (manager) ->
-        @manager = manager
-        @registerViewProvider()
+  constructor: (manager) ->
+    @manager = manager
+    @registerViewProvider()

-    registerViewProvider: ->
-        atom.views.addViewProvider
-            modelConstructor: RenameModel
-            viewConstructor: RenameView
+  registerViewProvider: ->
+    atom.views.addViewProvider
+      modelConstructor: RenameModel
+      viewConstructor: RenameView

-    showRename: ->
-        if @renamePanel
-            @renamePanel.show()
-            return
-        @renameModel = new RenameModel(@manager)
-        @renamePanel = atom.workspace.addBottomPanel item: @renameModel
+  showRename: ->
+    if @renamePanel
+      @renamePanel.show()
+      return
+    @renameModel = new RenameModel(@manager)
+    @renamePanel = atom.workspace.addBottomPanel item: @renameModel

-    hideRename: ->
-        return unless @renamePanel?.isVisible()
-        @renamePanel.hide()
-        @focusEditor()
+  hideRename: ->
+    return unless @renamePanel?.isVisible()
+    @renamePanel.hide()
+    @focusEditor()

-    focusEditor: ->
-        editor = atom.workspace.getActiveTextEditor()
-        return unless editor
-        view = atom.views.getView(editor)
-        view?.focus?()
+  focusEditor: ->
+    editor = atom.workspace.getActiveTextEditor()
+    return unless editor
+    view = atom.views.getView(editor)
+    view?.focus?()


Remove `atom` argument from activate

Remove `atom` argument from activate

--- src/atom-package.coffee
+++ src/atom-package.coffee
@@ -79,7 +79,7 @@
       @activateConfig()
       @activateStylesheets()
       if @requireMainModule()
-        @mainModule.activate(atom.packages.getPackageState(@name) ? {}, atom)
+        @mainModule.activate(atom.packages.getPackageState(@name) ? {})
         @mainActivated = true
     catch e
       console.warn "Failed to activate package named '#{@name}'", e.stack


added drop, addAttribute and removeAttribute to adapter

--- lib/sails-migrations/adapter.coffee
+++ lib/sails-migrations/adapter.coffee
@@ -8,6 +8,24 @@
     @registerCollection(tableName, (err)=>
       return cb(err) if err
       @adapter.define(tableName, definition, cb)
+    )
+
+  drop: (tableName, cb)->
+    @registerCollection(tableName, (err)=>
+      return cb(err) if err
+      @adapter.drop(tableName, cb)
+    )
+
+  addAttribute: (tableName, attrName, attrDef, cb)->
+    @registerCollection(tableName, (err)=>
+      return cb(err) if err
+      @adapter.addAttribute(tableName, attrName, attrDef, cb)
+    )
+
+  removeAttribute: (tableName, attrName, cb)->
+    @registerCollection(tableName, (err)=>
+      return cb(err) if err
+      @adapter.removeAttribute(tableName, attrName, cb)
     )

   query: (query, data, cb)->


Update docs

Update docs

--- src/binary-trunk.coffee
+++ src/binary-trunk.coffee
@@ -9,13 +9,13 @@
 # [search]: #BinarySearchTree
 # [layout]: #BinaryTreeTidier

-# [binary-trunk][] makes working with **binary trees** in *CoffeeScript* and
-# *Javascript* easy by providing a well-equipped **[tree node][node]**, example
-# **[search tree][search]** implementation, and an excellent **[layout algorithm][layout]**
+# [binary-trunk][] tries to make working with **binary trees** in *CoffeeScript* and
+# *Javascript* easy, by providing a well-equipped *[tree node][node]*, example
+# *[search tree][search]* implementation, and a *[layout algorithm][layout]*
 # implementation for visualizing the contents of your trees.
 #
 # The [source code][github] is available under the [MIT license][license], there is
-# a [thorough test-suite][tests] with accompanying [annotated test-suite code][testcode]
+# a [thorough test-suite][tests], with accompanying [annotated test-suite code][testcode]
 #
 root = this
 DJC = root.DJC = root.DJC or {}


use new images json file in hubot script

--- src/chickendance.coffee
+++ src/chickendance.coffee
@@ -16,21 +16,9 @@
 # Author:
 #   Morgan Wigmanich <okize123@gmail.com> (https://github.com/okize)

+images = require './data/images.json'
+
 module.exports = (robot) ->

   robot.respond /chicken dance/i, (msg) ->
     msg.send msg.random images
-
-images = [
-  'http://gifti.me/i/ByNdVc3oQ.gif',
-  'http://gifti.me/i/9mPDixUjeN.gif',
-  'http://gifti.me/i/H7gV.gif',
-  'http://gifti.me/i/KcyqQCi.gif',
-  'http://gifti.me/i/26zUV.gif',
-  'http://gifti.me/i/ZHs9k.gif',
-  'http://gifti.me/i/KugIhe3SJm.gif',
-  'http://gifti.me/i/K8kYRmZ3.gif',
-  'http://gifti.me/i/fjrE1V.gif',
-  'http://gifti.me/i/6besOA.gif',
-  'http://gifti.me/i/7QArMx1J.gif'
-]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -7,6 +7,7 @@

   App ||= {}

+
   self.bind "update", ->
     I.x = I.x.clamp(I.width / 2, App.width - I.width / 2)
     I.y = I.y.clamp(I.height / 2, App.height - I.height / 2)


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -4,6 +4,7 @@
 the viewport.

+
   player = GameObject
     includedModules: ["ClampBounds"]
     width: 5


just removing $log outputs

just removing $log outputs

--- src/common/dates.coffee
+++ src/common/dates.coffee
@@ -1,4 +1,3 @@
-'use strict'
 angular.module('hoay.dates', [
   'ngCookies'
   'pascalprecht.translate'
@@ -25,7 +24,6 @@
       @updateDifferences()

     @updateDifferences = ->
-      $log.info "updateDifferences"
       if @start isnt undefined and @end isnt undefined
         sum = getSumByDates @start, @end
         @days = sum.days
@@ -34,9 +32,6 @@
         @totalDays = getTotalDaysByDates @start, @end
         @totalMonths = getTotalMonthsByDates @start, @end
         @totalYears = getTotalYearsByDates @start, @end
-
-      $log.info "updateDifferences @totalYears: #{@totalYears}"
-

     getTotalDaysByDates =(startDate, endDate) ->
       startMoment = moment startDate
@@ -122,6 +117,7 @@

       recount = attrs.counterRestart or false
       max = scope.$eval(attrs.counterMax)
+      timeoutPromise = null

       scope.$watch max, (value)->
         if max isnt undefined
@@ -132,7 +128,7 @@
           restart()

       restart = ->
-        $timeout animate, 200
+        timeoutPromise = $timeout animate, 200

       tweenable = new Tweenable()
       animate = ->
@@ -152,4 +148,11 @@
       updateElement = (value)->
         $(element)[0].text parseInt(value)

+      element.bind '$destroy', ->
+        # cancel timeout
+        $timeout.cancel timeoutPromise
+        # cancel tween
+        tweenable.stop()
+
+
 ]