CoffeeScript is still not Python. :)

CoffeeScript is still not Python. :)

--- web/coffee/dependencies.coffee
+++ web/coffee/dependencies.coffee
@@ -1,6 +1,6 @@
 @apiCall = (type, url, data) ->

-  if type == "POST":
+  if type == "POST"
     data.token = $.cookie("token")

   $.ajax {url: url, type: type, data: data, cache: false}


don't send OK response anymore

don't send OK response anymore

--- app/controllers/account_controller.coffee
+++ app/controllers/account_controller.coffee
@@ -94,7 +94,7 @@
 #POST /accounts/password/
 action 'initializeKeys', =>
     user.getUser (err, user) ->
-
+
         if err
             console.log "[initializeKeys] err: #{err}"
             send 500
@@ -104,7 +104,7 @@
                 app.crypto.masterKey =
                     cryptoTools.genHashWithSalt body.password, user.salt
                 app.crypto.slaveKey = user.slaveKey
-                send 200
+                send success: true
                 if app.crypto.masterKey.length isnt 32
                     console.log "[initializeKeys] err: password to initialize
                         keys is different than user password"
@@ -122,7 +122,7 @@
                         console.log "[initializeKeys] err: #{err}"
                         send 500
                     else
-                        send 200
+                        send success: true

 #PUT /accounts/password/
@@ -153,7 +153,7 @@
                                 console.log "[updateKeys] err: #{err}"
                                 send 500
                             else
-                                send 200
+                                send success: true
                 else
                     console.log "[updateKeys] err: masterKey and slaveKey don't\
                         exist"
@@ -247,7 +247,7 @@
                         console.log "[updateAccount] err: #{err}"
                         send 500
                     else
-                        send 200
+                        send success: true
             else
                 send 401

@@ -265,7 +265,7 @@
                     console.log "[Merge] err: #{err}"
                     send 500
                 else
-                    send 200
+                    send success: true

 #PUT /account/upsert/:id
@@ -279,7 +279,7 @@
                         console.log "[Upsert] err: #{err}"
                         send 500
                     else if doc
-                        send 200
+                        send success: true
                     else
                         send {"_id": res.id}, 201
         else


Make game modes other than Legendary ignore Uber megas.

--- js/app.coffee
+++ js/app.coffee
@@ -52,11 +52,11 @@
         return false if pokemon.mega and @tiers[pokemon.mega.tier] >= @tiers[tier]
       @tiers[pokemon.tier] >= @tiers[tier] and not pokemon.legendary

-  @checkMega = (pokemon) ->
+  @checkMega = (pokemon, tier = 'OU') ->
     if @megas
       true
     else
-      not pokemon.mega
+      not pokemon.mega or @tiers[pokemon.mega.tier] < @tiers[tier]

   @formats = [
     name: 'Regular'
@@ -95,7 +95,7 @@
   ,
     name: 'Legendary'
     callback: (pokemon) =>
-      pokemon.legendary and @checkMega pokemon
+      pokemon.legendary and @checkMega pokemon, 'Uber'
   ]

   @format = @formats[0]


feat(form): use 'Kontakt' legend to structure the form

--- src/components/EntryForm.coffee
+++ src/components/EntryForm.coffee
@@ -86,15 +86,17 @@
               title.error and title.touched and div className: "err",
                 title.error

-        div className: "pure-g",
-          div className: "pure-u-1",
-            label null,
-              span className:"text","Beschreibung"
-            textarea fieldProps.description
-            description.error and description.touched and div className:"err",
-             description.error
+          div className: "pure-g",
+            div className: "pure-u-1",
+              label null,
+                span className:"text","Beschreibung"
+              textarea fieldProps.description
+              description.error and description.touched and div className:"err",
+               description.error

         fieldset null,
+          legend null,
+            span className:"text","Kontakt"
           div className: "pure-g",
             div className: "pure-u-1",
               label null, "Homepage"
@@ -102,10 +104,10 @@
               www.error and www.touched and div className: "err",
                 www.error

-        div className: "pure-g",
-          div className: "pure-u-1",
-            label null, "Telefon"
-            input fieldProps.phone
+          div className: "pure-g",
+            div className: "pure-u-1",
+              label null, "Telefon"
+              input fieldProps.phone

         fieldset null,
           legend null,


add assignment is less primary when no periods

--- src/components/course-calendar/header.cjsx
+++ src/components/course-calendar/header.cjsx
@@ -61,6 +61,9 @@
     {date} = @state
     {format, duration, hasPeriods} = @props
     {courseId} = @context.router.getCurrentParams()
+
+    addAssignmentBSStyle = if hasPeriods then 'primary' else 'default'
+
     <div className='calendar-header'>
       <BS.Row className='calendar-header-actions'>
         {<NoPeriods
@@ -86,7 +89,7 @@
             id='add-assignment'
             className='add-assignment'
             title='Add Assignment'
-            bsStyle='primary'>
+            bsStyle={addAssignmentBSStyle}>
             {@renderAddActions()}
             </BS.DropdownButton>
         </BS.Col>


remove duplicate methods

remove duplicate methods

--- src/components/learning-guide-chart.coffee
+++ src/components/learning-guide-chart.coffee
@@ -35,7 +35,7 @@
     topMargin = 5

     space_between = (WIDTH-leftMargin)/fields.length
-
+
     points = _.map(fields, (f,i) ->
         {
           x: Math.max(leftMargin + space_between * i)
@@ -72,38 +72,6 @@
       .attr('y', 5)
       .attr('class', 'main-title')
       .text("Your Flight Path | #{guide.title} | All Topics")
-
-
-  drawStaticImages: (container, points) ->
-    @addImage(CLOUDS_PATH, width:10, x: 35, y: 10)
-    @addImage(CLOUDS_PATH, width:16, x: 77, y: 20)
-
-
-  drawCircles: (container, fields, points) ->
-    wrap = container.append('g')
-      .attr('class', 'circles')
-       .selectAll('g')
-       .data(fields)
-
-    circles = wrap.enter()
-      .append('g')
-      .attr('transform', (f,i) ->
-        "translate(#{points[i].x},#{points[i].y})"
-      )
-      .on('click', (field) =>
-        @navigateToPractice(field)
-      )
-    circles.append('circle')
-      .attr('r', (f) ->
-        # awaiting a better alogorithm for the circle radius
-        Math.max(f.questions_answered_count / 20, 2)
-      )
-    circles.append('text')
-      .text( (f) ->
-        f.questions_answered_count
-      )
-      .attr('text-anchor', 'middle')
-      .attr('dy', '0.5')

   drawXAxis: (container, fields, points) ->
@@ -131,7 +99,7 @@
       .attr('text-anchor', 'middle').attr('dy', '1.8').attr('text-anchor', 'middle')
       .text( (f,i) -> i+1 )

-
+
   drawYLabel: (container,ypos,text) ->
     wrap = container.append('g')
       .append("svg:text")


Remove argparse code (no longer used)

Remove argparse code (no longer used)

--- svgtiler.coffee
+++ svgtiler.coffee
@@ -7,7 +7,6 @@
 domImplementation = new xmldom.DOMImplementation()
 XMLSerializer = xmldom.XMLSerializer
 prettyXML = require('prettify-xml')
-argparse = require('argparse')

 SVGNS = 'http://www.w3.org/2000/svg'
 XLINKNS = 'http://www.w3.org/1999/xlink'
@@ -289,16 +288,6 @@
   '.csv': CSVDrawing
   '.tsv': TSVDrawing

-argparser = argparse.ArgumentParser()
-argparser.addArgument ['-tw', '--tile-width'],
-  help: 'Force all symbol tiles to have specified width'
-  nargs: 1
-argparser.addArgument ['-th', '--tile-height'],
-  help: 'Force all symbol tiles to have specified height'
-  nargs: 1
-argparser.addArgument ['filenames'],
-  nargs: argparse.Const.REMAINDER
-
 help = ->
   console.log """
 Usage: #{process.argv[0]} #{process.argv[1]} (...options and filenames...)


Make log actually useful

Make log actually useful

--- src/ivy.coffee
+++ src/ivy.coffee
@@ -145,7 +145,7 @@
     name = @taskObjectRegistry[func]
     queueName = @taskRegistry[@taskObjectRegistry[func]].queue

-    logger.silly "Sending delayedCall for function #{name} into queue #{queue} to backend #{queue.BACKEND_NAME} with args", args
+    logger.silly "Sending delayedCall for function #{name} into queue #{queueName} to backend #{queue.currentQueue} with args", args

     queue.sendTask
       name:    name


Run apm commands with --no-color flag

Run apm commands with --no-color flag

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -23,7 +23,7 @@

 getAvailable = (callback) ->
   command = apmCommand
-  args = ['available', '--json']
+  args = ['available', '--json', '--no-color']
   outputLines = []
   stdout = (lines) -> outputLines.push(lines)
   errorLines = []
@@ -55,7 +55,7 @@
   atom.packages.unloadPackage(name) if atom.packages.isPackageLoaded(name)

   command = apmCommand
-  args = ['install', "#{name}@#{version}"]
+  args = ['install', "#{name}@#{version}", '--no-color']
   outputLines = []
   stdout = (lines) -> outputLines.push(lines)
   errorLines = []
@@ -77,7 +77,7 @@
   atom.packages.deactivatePackage(name) if atom.packages.isPackageActive(name)

   command = apmCommand
-  args = ['uninstall', name]
+  args = ['uninstall', name, '--no-color']
   outputLines = []
   stdout = (lines) -> outputLines.push(lines)
   errorLines = []


Made paragraph icon a real button

Made paragraph icon a real button

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -70,6 +70,13 @@
       left: left + "px"
       top: top + "px"

+  _updatePositionRegularly: ->
+    $(window).on 'resize', @_updatePosition
+    @_interval = setInterval @_updatePosition, 1000
+    @_updatePosition()
+
+  _updatePosition: =>
+
   hide: =>
     @frame.fadeOut()
     @_visible = false
@@ -77,6 +84,9 @@
   destroy: =>
     @frame.destroy()
     @_robustHover.destroy()
+    $(window).off 'resize', @_updatePosition
+    clearInterval @_interval
+    @$boundingBox?.remove()

 class FactlinkJailRoot.ShowButton extends Button
   content: '

'
@@ -88,10 +98,7 @@

     @$nearEl = $(options.el)
     @_fadeIn()
-    @_updatePosition()
-
-    $(window).on 'resize', => @_updatePosition()
-    setInterval (=> @_updatePosition()), 1000
+    @_updatePositionRegularly()

     # remove when removing client_buttons feature toggle
     unless FactlinkJailRoot.can_haz.client_buttons
@@ -102,7 +109,7 @@
       return el if window.getComputedStyle(el).display == 'block'
     console.error 'FactlinkJailRoot: No text container found for ', el

-  _updatePosition: ->
+  _updatePosition: =>
     top = @$nearEl.offset().top + @$nearEl.outerHeight()/2 - @frame.$el.outerHeight()/2

     textContainer = @_textContainer(@$nearEl[0])
@@ -147,8 +154,6 @@
     current_user_opinion = $(event.target).data('opinion')
     FactlinkJailRoot.createFactFromSelection(current_user_opinion)

-  _updatePosition: ->
-
   placeNearSelection: (mouseX=null) ->
     return if @_visible

@@ -170,19 +175,29 @@
     @_showAtCoordinates top, left

-class ParagraphButton
-  constructor: (@el) ->
-    @$el = $(@el)
-
-    # TODO: actually show icon
+class ParagraphButton extends Button
+  content: '

'
+
+  constructor: (options) ->
+    super
+
+    @_bindCallbacks
+      mouseenter: => @startHovering()
+      mouseleave: => @stopHovering()
+
+    @nearEl = options.el
+
+    @_fadeIn()
+    @_updatePositionRegularly()
+
+  _updatePosition: ->
+    contentBox = FactlinkJailRoot.contentBox(@nearEl)
+
+    @_showAtCoordinates contentBox.top, contentBox.left + contentBox.width

     if FactlinkJailRoot.can_haz.debug_bounding_boxes
-      contentBox = FactlinkJailRoot.contentBox(el)
-
+      @$boundingBox?.remove()
       @$boundingBox = FactlinkJailRoot.drawBoundingBox contentBox, 'green'
-
-  remove: ->
-    @$boundingBox?.remove()

 class FactlinkJailRoot.ParagraphButtons
@@ -206,13 +221,13 @@
     return unless @_paragraphHasContent(el)
     return if @_containsFactlink(el)

-    @_paragraphButtons.push new ParagraphButton el
+    @_paragraphButtons.push new ParagraphButton el: el

   addParagraphButtons: ->
     return unless FactlinkJailRoot.can_haz.paragraph_icons

     for paragraphButton in @_paragraphButtons
-      paragraphButton.remove()
+      paragraphButton.destroy()

     for el in $('p, h2, h3, h4, h5, h6')
       @_addParagraphButton el


Fix race condition on page load.

Fix race condition on page load.

--- app/node_modules/xo-services/index.coffee
+++ app/node_modules/xo-services/index.coffee
@@ -194,7 +194,7 @@
     }

     # TODO: should probably be merged in the main collection in xo-lib.
-    currentAcls = null
+    currentAcls = Object.create(null)
     updateCurrentAcls = () ->
       xoApi.call('acl.getCurrent').then((acls) ->
         currentAcls = Object.create(null)


Improve message list scrolling, refs #427

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -241,7 +241,7 @@
         return should

     render: ->
-        ul className: 'list-unstyled',
+        ul className: 'list-unstyled', ref: 'messageList',
             @props.messages.map((message, key) =>
                 id = message.get('id')
                 cid = message.get('conversationID')
@@ -276,9 +276,15 @@
         # If selected message has changed, scroll the list to put
         # current message into view
         if @state.messageID isnt @props.messageID
+            scrollable = @refs.messageList?.getDOMNode()?.parentNode
             active = document.querySelector("[data-message-id='#{@props.messageID}']")
             if active? and not DomUtils.isVisible(active)
+                scroll = scrollable?.scrollTop
                 active.scrollIntoView(false)
+                # display half of next message
+                if scroll isnt @refs.scrollable?.getDOMNode()?.scrollTop
+                    scrollable?.scrollTop += active.getBoundingClientRect().height / 2
+
             @setState messageID: @props.messageID


don't try to process if no results

don't try to process if no results

--- src/hubot-influxdb.coffee
+++ src/hubot-influxdb.coffee
@@ -205,6 +205,8 @@

 check_alert = (robot, msg, query_name, query, database) ->
   influx_clients[database].query(query, (e, results) ->
+    if !results
+      return
     for data in results
       columns = data.columns
       for result in data.points


fixes bug in rendering of grid view

fixes bug in rendering of grid view

--- src/components/grid_view.coffee
+++ src/components/grid_view.coffee
@@ -32,8 +32,7 @@

     $(@container).append $(@el)

-  render: ()->
-    console.log "Blah"
+  afterRender: ()->
     @collection.each (model,index)=>
       @render_row.apply(@, [model,index])


Was returning the window instead of re object

--- responsive-elements.coffee
+++ responsive-elements.coffee
@@ -4,11 +4,11 @@
  * Inspired by https://github.com/kumailht/responsive-elements
  *
  * @copyright Corey Worrell 2014
- * @version   1.1.0
+ * @version   1.1.1
 ###

 do ->
-	window.re = do ->
+	window.re = new ->
 		b = this

 		# Configuration
@@ -53,17 +53,17 @@

 		# Bind listeners
 		bind = ->
-			dom.window.addEventListener 'load', respondAll
-			dom.window.addEventListener 'resize', debounceRespondAll
+			b.dom.window.addEventListener 'load', respondAll
+			b.dom.window.addEventListener 'resize', debounceRespondAll

 		# Unbind listeners
 		unbind = ->
-			dom.window.removeEventListener 'load', respondAll, false
-			dom.window.removeEventListener 'resize', debounceRespondAll, false
+			b.dom.window.removeEventListener 'load', respondAll, false
+			b.dom.window.removeEventListener 'resize', debounceRespondAll, false

 		# Respond all elements
 		respondAll = (e) ->
-			for element in dom.elements
+			for element in b.dom.elements
 				respond element

 		# Respond with a debounce
@@ -124,10 +124,7 @@
 			b

 		# Enable
-		b.enable = (config) ->
-			if config
-				b.setConfig config
-
+		b.enable = ->
 			bind()
 			b


Client:Login: removed precondition from app registiration

--- client/landing/Login/AppController.coffee
+++ client/landing/Login/AppController.coffee
@@ -1,9 +1,13 @@
 class LoginAppsController extends AppController

-  handler = (callback)->-> KD.singleton('appManager').open 'Login', callback
+  handler = (callback)->->
+    unless KD.isLoggedIn()
+      KD.singleton('appManager').open 'Login', callback
+    else
+      KD.getSingleton('router').handleRoute "/Activity"

   handleResetRoute = ({params:{token}})->
-    do handler (app)=>
+    KD.singleton('appManager').open 'Login', (app)=>
       if KD.isLoggedIn()
         KD.getSingleton('router').handleRoute "/Account/Profile?focus=password&token=#{token}"
       else
@@ -17,16 +21,13 @@
       '/:name?/Redeem'          : handler (app)-> app.getView().animateToForm 'redeem'
       '/:name?/Register'        : handler (app)-> app.getView().animateToForm 'register'
       '/:name?/Recover'         : handler (app)-> app.getView().animateToForm 'recover'
+      '/:name?/Reset'           : handler (app)-> app.getView().animateToForm 'reset'
       '/:name?/Reset/:token'    : handleResetRoute
       '/:name?/ResendToken'     : handler (app)-> app.getView().animateToForm 'resendEmail'
     hiddenHandle                : yes
     behavior                    : 'application'
-    preCondition                :
-      condition             : (options, cb)->
-        # return cb yes if KD.singleton('router').getCurrentPath() is '/Reset'
-        cb not KD.isLoggedIn()
-      failure               : -> KD.getSingleton('router').handleRoute "/Activity"
-
+    # removed preCondition because Reset can be called while
+    # current user logged-in status
   constructor:(options = {}, data)->

     options.view    = new LoginView


invitations: fix merge conflict

invitations: fix merge conflict

--- client/landing/Login/AppController.coffee
+++ client/landing/Login/AppController.coffee
@@ -1,11 +1,10 @@
 class LoginAppsController extends AppController

-  handler = (callback)->
-    ({params})->
-      unless KD.isLoggedIn()
-        KD.singleton('appManager').open 'Login', (app)-> callback app, params
-      else
-        KD.getSingleton('router').handleRoute "/Activity"
+  handler = (callback)->->
+    unless KD.isLoggedIn()
+      KD.singleton('appManager').open 'Login', (app)-> callback app
+    else
+      KD.getSingleton('router').handleRoute "/Activity"

   handleResetRoute = ({params:{token}})->
     KD.singleton('appManager').open 'Login', (app)=>


修改创建模块信息选择，区分独立模块和基于应用的模块

修改创建模块信息选择，区分独立模块和基于应用的模块

--- lib/module/create-module-info-view.coffee
+++ lib/module/create-module-info-view.coffee
@@ -4,6 +4,7 @@
 {Directory} = require 'atom'
 {$, TextEditorView, View} = require 'atom-space-pen-views'
 CreateModuleTypeView = require './create-module-type-view'
+disabled = 'disabled'

 module.exports =
 class CreateModuleInfoView extends View
@@ -13,14 +14,18 @@
       @h2 desc.createModuleTitle, class: 'box-subtitle'
       @div class: 'box-form', =>
         @div class: 'form-row clearfix', =>
-          @label desc.moduleInApp, class: 'row-title pull-left'
+          @div class: 'row-title pull-left selectPath', =>
+            @input type: 'radio', name: 'modulePaths', outlet: 'selectAppPath', id: 'selectAppPath'
+            @label desc.moduleInApp, for: 'selectAppPath'
           @div class: 'row-content pull-left', =>
             @select class: 'form-control', outlet: 'selectProject'
         @div class: 'form-row clearfix', =>
-          @label desc.modulePath, class: 'row-title pull-left'
+          @div class: 'row-title pull-left selectPath', =>
+            @input type: 'radio', name: 'modulePaths', outlet: 'selectModulePath', id: 'selectModulePath'
+            @label desc.modulePath,for: 'selectModulePath'
           @div class: 'row-content pull-left', =>
             @div class: 'textEditStyle', outlet: 'modulePath'
-            @span class: 'inline-block status-added icon icon-file-directory openFolder', click: 'openFolder'
+            @span class: 'inline-block status-added icon icon-file-directory openFolder', click: 'openFolder', outlet:'openBtn'
         @div class: 'form-row clearfix', =>
           @label desc.moduleId, class: 'row-title pull-left'
           @div class: 'row-content pull-left', =>
@@ -42,6 +47,8 @@
     @moduleName.getModel().onDidChange => @checkInput()
     # @mainEntry.getModel().onDidChange => @checkInput()
     @selectProject.on 'change',(e) => @onSelectChange(e)
+    @selectAppPath.on 'change',(e) => @toggleRow(e)
+    @selectModulePath.on 'change',(e) => @toggleRow(e)
     @moduleName.setText ''
     @moduleId.setText ''
     # @mainEntry.setText desc.mainEntryFileName
@@ -51,31 +58,65 @@
     @parentView.disableNext()
     @parentView.hidePrevBtn()

-    projects = @findProject()
-    projectNum = projects.length
+    @projects = @findProject()
+    projectNum = @projects.length
+
     if projectNum isnt 0
-      @selectProject.empty()
-      @setSelectItem path for path in projects
-      @modulePath.parents('.form-row').addClass 'hide'
-      @selectProject.parents('.form-row').removeClass 'hide'
-      @modulePath.html pathM.join @selectProject.val(),'modules'
-    else
-      @selectProject.parents('.form-row').addClass 'hide'
-      @modulePath.parents('.form-row').removeClass 'hide'
-    # console.log @
+      @selectProject.find('.sopt').remove()
+      @setSelectItem path for path in @projects
+      @selectAppPath[0].checked = true
+    else
+      @setSelectItem '','sopt'
+      @selectModulePath[0].checked = true
+    @toggleRow()
+    @setSelectItem 'other','other',"#{desc.other}--#{desc.openFromFolder}"
     @checkPath()

   # destroy: ->
   #   @element.remove()
-  setSelectItem:(path) ->
-    filePath = pathM.join path,desc.ProjectConfigFileName
-    obj = Util.readJsonSync filePath
-    projectName = if obj? then obj.name else pathM.basename path
-    optionStr = "<option value='#{path}'>#{projectName}  -  #{path}</option>"
-    @selectProject.append optionStr
+
+  toggleRow: ->
+    console.log 'toggleRow'
+    selectRow = @selectProject.parents('.form-row')
+    inputRow = @modulePath.parents('.form-row')
+    if @selectModulePath[0].checked
+      selectRow.addClass disabled
+      inputRow.removeClass disabled
+      @openBtn.removeClass disabled
+      @selectProject.attr(disabled,disabled)
+    else
+      inputRow.addClass disabled
+      @openBtn.addClass disabled
+      selectRow.removeClass disabled
+      @selectProject.removeAttr(disabled)
+    @checkPath()
+
+  setSelectItem:(path,className,text,insertBefore,isSelected) ->
+    if className?
+      text?=""
+      optionStr = "<option value='#{path}' class='#{className}'>#{text}</option>"
+    else
+      filePath = pathM.join path,desc.ProjectConfigFileName
+      obj = Util.readJsonSync filePath
+      projectName = if obj? then obj.name else pathM.basename path
+      if isSelected? and isSelected is true
+        isSelected = ' selected'
+      else
+        isSelected = ''
+      optionStr = "<option value='#{path}' class='sopt'#{isSelected}>#{projectName}  -  #{path}</option>"
+    if insertBefore is true
+      @selectProject.find('.other').before optionStr
+    else
+      @selectProject.append optionStr

   getElement: ->
     @element
+
+  getPath: ->
+    if @selectAppPath[0].checked
+      pathM.join @selectProject.val().trim(),'modules'
+    else
+      @modulePath.html().trim()

   serialize: ->

@@ -90,7 +131,7 @@
     return projects

   getModuleInfo: ->
-    modulePath = @modulePath.html()
+    modulePath = @getPath()
     if @modulePath.isProject
       modulePath = pathM.join modulePath,'modules'
       isProject = true
@@ -107,16 +148,47 @@

   openFolder: (e) ->
     console.log 'openFolder'
+    el = e.currentTarget
+    if el.classList.contains(disabled) is true
+      return
     atom.pickFolder (paths) =>
       if paths?
-        console.log "select path:#{paths[0]}"
-        @modulePath.html paths[0]
+        path = paths[0]
+        selectEl = @selectProject[0]
+        console.log "select path:#{path}"
+        if el is selectEl
+          if @checkProjectPath(path)
+            for opt in selectEl.options
+              console.log opt,opt.value,path,opt.value is path
+              if opt.value is path
+                opt.selected = true
+                console.log '...'
+                return
+            @selectProject.find('option[value=""]').remove()
+            @setSelectItem path,null,null,true,true
+            console.log '!!!'
+            # optionExists = false
+            # @projects.forEach (projectPath,i) =>
+            #   if projectPath is
+            # if optionExists is no
+            # @selectProject.find("option[value='#{path}]'").prop 'selected',true
+          else
+            alert desc.selectProjectPath
+        else
+          @modulePath.html path

   onSelectChange: (e) ->
     el = e.currentTarget
-    # console.log el.value
-    @modulePath.html el.value
+    console.log el.value
+    if el.value is 'other'
+      @openFolder(e)
+    # @modulePath.html el.value
     @checkPath()
+
+  checkProjectPath: (path) ->
+    result = null
+    configPath = pathM.join path,desc.ProjectConfigFileName
+    return Util.isFileExist configPath,'sync'

   checkPath: ->
     path = @moduleId.getText().trim()
@@ -126,10 +198,8 @@
         @errorMsg2.addClass('hide')
       else
         @errorMsg2.removeClass('hide')
-      projectPath = @modulePath.html().trim()
-
-      configPath = pathM.join projectPath,desc.ProjectConfigFileName
-      isProject = @modulePath.isProject = Util.isFileExist configPath,'sync'
+      projectPath = @getPath().trim()
+      isProject = @modulePath.isProject = @checkProjectPath projectPath
       projectPath = pathM.join projectPath,'modules' if isProject

@@ -150,7 +220,7 @@
     flag1 = @moduleId.getText().trim() isnt ""
     flag2 = @moduleName.getText().trim() isnt ""
     # flag3 = @mainEntry.getText().trim() isnt ""
-    flag4 = @modulePath.html().trim() isnt ""
+    flag4 = @getPath().trim() isnt ""
     flag5 = @errorMsg.hasClass 'hide'
     flag6 = @errorMsg2.hasClass 'hide'


javascript doesn't allow sorting with boolean results

--- vendor/assets/javascripts/fustrate/generic_table.coffee
+++ vendor/assets/javascripts/fustrate/generic_table.coffee
@@ -11,7 +11,8 @@

   sortRows: (rows, sortFunction = ->) ->
     sorted = ([sortFunction(row), row] for row in rows)
-    sorted.sort (x, y) -> if x[0] == y[0] then 0 else x[0] > y[0]
+    sorted.sort (x, y) ->
+      if x[0] == y[0] then 0 else if x[0] > y[0] then 1 else -1
     sorted.map (row) -> row[1]

   createRow: (item) =>


Edited isNaN check in fullpie.coffee

Edited isNaN check in fullpie.coffee

--- widgets/fullpie/fullpie.coffee
+++ widgets/fullpie/fullpie.coffee
@@ -48,9 +48,22 @@
     for val in data
       sum += val.value

+#    arcs.append("svg:text").attr("transform", (d, i) ->
+#      procent_val = Math.round(data[i].value/sum * 100)
+#      d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
+#      d.outerRadius = radius
+#      "translate(" + arc.centroid(d) + ")")
+#      .attr('fill', "#fff")
+#      .attr("text-anchor", "middle").text((d, i) -> data[i].label).attr('font-size', '28px')
+#      .append('svg:tspan')
+#      .attr('x', 0)
+#      .attr('dy', 25)
+#      .attr('font-size', '90%')
+#      .text((d,i) -> data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)')
+
     arcs.append("svg:text").attr("transform", (d, i) ->
-      procent_val = Math.round(data[i].value/sum * 100)
-      d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
+      percent_val = getpercentval(data[i])
+      d.innerRadius = (radius * (100-percent_val)/100) - 45  #45=max text size/2
       d.outerRadius = radius
       "translate(" + arc.centroid(d) + ")")
       .attr('fill', "#fff")
@@ -59,4 +72,10 @@
       .attr('x', 0)
       .attr('dy', 25)
       .attr('font-size', '90%')
-      .text((d,i) -> data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)')
+      .text((d,i) -> data[i].value + ' (' + percent_val + '%)')
+
+    getpercentval: (x) ->
+      if _.isNan(Math.round(x.value/sum * 100))
+        0
+      else
+        Math.round(x.value/sum * 100)


Changed Fullpie widget to handle NaN results

--- widgets/fullpie/fullpie.coffee
+++ widgets/fullpie/fullpie.coffee
@@ -50,15 +50,36 @@
     for val in data
       sum += val.value

-    arcs.append("svg:text").attr("transform", (d, i) ->
-      procent_val = Math.round(data[i].value/sum * 100)
-      d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
-      d.outerRadius = radius
-      "translate(" + arc.centroid(d) + ")")
-      .attr('fill', "#fff")
-      .attr("text-anchor", "middle").text((d, i) -> data[i].label).attr('font-size', '28px')
-      .append('svg:tspan')
-      .attr('x', 0)
-      .attr('dy', 25)
-      .attr('font-size', '90%')
-      .text((d,i) -> data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)')
+# Core
+#    arcs.append("svg:text").attr("transform", (d, i) ->
+#      procent_val = Math.round(data[i].value/sum * 100)
+#      d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
+#      d.outerRadius = radius
+#      "translate(" + arc.centroid(d) + ")")
+#      .attr('fill', "#fff")
+#      .attr("text-anchor", "middle").text((d, i) -> data[i].label).attr('font-size', '28px')
+#      .append('svg:tspan')
+#      .attr('x', 0)
+#      .attr('dy', 25)
+#      .attr('font-size', '90%')
+#      .text((d,i) -> data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)')
+    sum=0
+    for val in data
+      sum += val.value
+    if !sum
+      arcs.append("svg:text")
+        .attr('fill', "#fff")
+        .attr("text-anchor", "middle").text("No Results").attr('font-size', '28px')
+    else
+      arcs.append("svg:text").attr("transform", (d, i) ->
+        procent_val = Math.round(data[i].value/sum * 100)
+        d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
+        d.outerRadius = radius
+        "translate(" + arc.centroid(d) + ")")
+        .attr('fill', "#fff")
+        .attr("text-anchor", "middle").text((d, i) -> data[i].label).attr('font-size', '28px')
+        .append('svg:tspan')
+        .attr('x', 0)
+        .attr('dy', 25)
+        .attr('font-size', '90%')
+        .text((d,i) -> data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)')


Testpie formula testing

Testpie formula testing

--- widgets/testpie/testpie.coffee
+++ widgets/testpie/testpie.coffee
@@ -53,7 +53,7 @@
       sum += val.value

     arcs.append("svg:text").attr("transform", (d, i) ->
-      procent_val = Math.round(data[i].value/sum * 100)
+      procent_val = isNan(Math.round(data[i].value/sum * 100)) ? 0 : Math.round(data[i].value/sum * 100)
       d.innerRadius = (radius * (100-procent_val)/100) - 45  #45=max text size/2
       d.outerRadius = radius
       "translate(" + arc.centroid(d) + ")")


Sim: Fix relative paths to the public directory.

--- server/index.coffee
+++ server/index.coffee
@@ -1,5 +1,6 @@
 http = require 'http'
 express = require 'express'
+path = require 'path'
 require 'sugar'

 config = require './config'
@@ -25,7 +26,7 @@
   app.use(auth.middleware())
   app.use(express.methodOverride())
   app.use(app.router)
-  app.use(express.static(__dirname + "/public"))
+  app.use(express.static(path.join(__dirname, "../public")))

   # Routing
   renderHomepage = (req, res) ->


mocked out getNextTile

mocked out getNextTile

--- ng-modules/travelScreen/travelScreen.coffee
+++ ng-modules/travelScreen/travelScreen.coffee
@@ -125,6 +125,11 @@
         }
     };

+    vm.getNextTile = function(xpos){
+        // if distance travelled to destination big enough, append destination tile, else use filler
+        return new Tile(xpos, document.getElementById("test-bg"));
+    }
+
     vm.travel = function(){
         data.travel();

@@ -141,7 +146,7 @@
         // append new bg tiles if needed
         var overhang = vm.tiles[vm.tiles.length - 1].getOverhang();
         while (overhang < 100){
-            vm.tiles.push(new Tile(window.innerWidth + overhang, document.getElementById("test-bg")));
+            vm.tiles.push(vm.getNextTile(window.innerWidth + overhang));
             overhang = vm.tiles[vm.tiles.length -1].getOverhang();
             console.log('tile added');
         }


Copy over the labels for subtask items, mixing in with any other labels specified for the subtask

--- src/jira/create.coffee
+++ src/jira/create.coffee
@@ -42,11 +42,11 @@
         fields:
           project: key: project
           summary: summary
-          labels: labels
           description: ""
           issuetype: name: type

       _(issue.fields).extend fields if fields
+      issue.fields.labels = _(issue.fields.labels).union labels
       issue.fields.description += """
         #{(if description then description + "\n\n" else "")}
         Reported by #{msg.message.user.name} in ##{msg.message.room} on #{msg.robot.adapterName}
@@ -91,6 +91,7 @@
     .then (parent) ->
       Create.with parent.fields.project.key, "Sub-task", summary, msg,
         parent: key: parent.key
+        labels: parent.fields.labels or []
         description: "Sub-task of #{key}\n\n"

 module.exports = Create


clean up, don't delete and fetch in parallel

--- src/job-manager.coffee
+++ src/job-manager.coffee
@@ -84,21 +84,15 @@
       @client.hgetall key, (error, result) =>
         delete error.code if error?
         return callback error if error?
+        return callback() unless result?
+        return callback() unless result['request:metadata']?

-        _.each result, (value, key) =>
-          newKey = _.last _.split key, /:/
-          result[newKey] = value
-          delete result[key]
+        request =
+          createdAt: result['request:createdAt']
+          metadata:  JSON.parse result['request:metadata']
+          rawData:   result['request:data']

-        return callback() unless result?
-        if result.ignoreResponse?
-          @client.del key, ->
-        return callback() unless result.metadata?
-
-        callback null,
-          createdAt: result.createdAt
-          metadata: JSON.parse result.metadata
-          rawData: result.data
+        callback null, request

   getResponse: (responseQueue, responseId, callback) =>
     @client.brpop "#{responseQueue}:#{responseId}", @timeoutSeconds, (error, result) =>
@@ -108,17 +102,22 @@

       [channel,key] = result

-      async.parallel
-        metadata: async.apply @client.hget, key, 'response:metadata'
-        data: async.apply @client.hget, key, 'response:data'
-        del: async.apply @client.del, key, "#{responseQueue}:#{responseId}" # clean up
-      , (error, result) =>
+      @client.hmget key, ['response:metadata', 'response:data'], (error, data) =>
         delete error.code if error?
         return callback error if error?
-        return callback new Error('Response timeout exceeded'), null unless result.metadata?

-        callback null,
-          metadata: JSON.parse result.metadata
-          rawData: result.data
+        @client.del key, "#{responseQueue}:#{responseId}", (error) =>
+          delete error.code if error?
+          return callback error if error?
+
+          [metadata, rawData] = data
+
+          return callback new Error('Response timeout exceeded'), null unless metadata?
+
+          response =
+            metadata: JSON.parse metadata
+            rawData: rawData
+
+          callback null, response

 module.exports = JobManager


TDD: should has author property

TDD: should has author property

--- test/spec/services/shareddata.coffee
+++ test/spec/services/shareddata.coffee
@@ -19,3 +19,8 @@
     expect sharedData
       .to.has.property 'gists'
       .a 'array'
+
+  it 'should have author property', ->
+    expect sharedData
+      .to.has.property 'author'
+      .a 'object'


Concat secure keys cache

Concat secure keys cache

--- app/src/utils/storage/secure_store.coffee
+++ app/src/utils/storage/secure_store.coffee
@@ -15,7 +15,7 @@
       _.defer => cb?(@_keysCache)
     else
       super (encrypted_keys) =>
-        @_keysCache = _.uniq(_.compact(Try(=> @_aes.decrypt(encrypted_key)).orNull() for encrypted_key in encrypted_keys))
+        @_keysCache = _.uniq(@_keysCache.concat(_.compact(Try(=> @_aes.decrypt(encrypted_key)).orNull() for encrypted_key in encrypted_keys)))
         @_hasCalledKeys = yes
         cb?(@_keysCache)


Add test for backwards compatibility for Animation object

Signed-off-by: Niels van Hoorn <9639b9eee3fe09ce53e8dfede54f918fb4bd39be@nvh.io>

--- test/tests/LayerAnimationTest.coffee
+++ test/tests/LayerAnimationTest.coffee
@@ -579,3 +579,18 @@

 				@layer.x.should.equal 100
 				calledEvents.should.eql(["start", "end", "stop"])
+		describe "Backwards compatibility", ->
+			it "Should still support deprecated API", (done) ->
+				layer = new Layer()
+
+				animation = new Animation
+					layer: layer
+					properties:
+						x:50
+					curve: "linear"
+					time: 0.1
+
+				animation.start()
+				Utils.delay animation.options.time, ->
+					layer.x.should.equal 50
+					done()


Moderation: checkTagExistend method returns boolean result

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -288,16 +288,17 @@
     {title, category} = tag
     @one {title, category}, (err, tag) ->
       return callback err if err
-      return callback new KodingError "Tag already exists!" if tag
-      callback null
+      found = yes if tag
+      callback null, found

   @create$ = permit 'create tags',
     success: (client, data, callback)->
       {connection:{delegate}} = client
       data.category = "user-tag"
       data.group = client.context.group
-      checkTagExistence data, (err) ->
+      checkTagExistence data, (err, found) ->
         return callback err if err
+        return callback new KodingError "Tag already exists!" if found
         create data, delegate, callback

   createSynonym : permit ['create tags', 'read tags'],


Moderation: Recreation of deleted tags is prevented

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -347,7 +347,6 @@
         _id     :
           $nin  : blacklist
         category: "user-tag"
-        status  : $ne : 'deleted'
       },{
         skip
         limit
@@ -434,30 +433,33 @@

   @byRelevance$ = permit 'read tags',
     success: (client, seed, options, callback)->
-      kallback = (err, tags) ->
+      filterSynonyms = (err, tags) ->
         return callback err if err
         resultMap = {}
+        deletedTags = []
         synonyms = []
         for tag in tags
           tag.children = []
-          unless tag.status is 'synonym' then resultMap[tag.getId()] = tag
-          else
-            childTag = tag
-            synonyms.push ->
-              childTag.fetchSynonym (err, synonym) ->
-                if not err and synonym?
-                  resultMap[synonym.getId()] ?= synonym
-                  parentTag = resultMap[synonym.getId()]
-                  parentTag.children ?= []
-                  parentTag.children.push childTag.title
-                synonyms.fin()
+          switch tag.status
+            when 'deleted' then deletedTags.push tag.title
+            when 'synonym'
+              childTag = tag
+              synonyms.push ->
+                childTag.fetchSynonym (err, synonym) ->
+                  if not err and synonym?
+                    resultMap[synonym.getId()] ?= synonym
+                    parentTag = resultMap[synonym.getId()]
+                    parentTag.children ?= []
+                    parentTag.children.push childTag.title
+                  synonyms.fin()
+            else resultMap[tag.getId()] = tag

         dash synonyms, ->
           result = []
           result.push val for key, val of resultMap
-          callback null, result
-
-      @byRelevance client, seed, options, kallback
+          callback null, result, deletedTags
+
+      @byRelevance client, seed, options, filterSynonyms

   @fetchSystemTags    = permit 'fetch system tag',
    success: (client, selector, options, callback)->


add username attribute to findOrCreate criteria

--- api/policies/isAuth.coffee
+++ api/policies/isAuth.coffee
@@ -8,7 +8,7 @@
     .verify sails.config.oauth2.verifyUrl, sails.config.oauth2.scope, token
     .then (info) ->
       sails.models.user
-        .findOrCreate _.pick(info.user, 'email')
+        .findOrCreate _.pick(info.user, 'username', 'email')
         .populateAll()
     .then (user) ->
       done null, user
@@ -16,6 +16,9 @@
       done null, false, message: err

 module.exports = (req, res, next) ->
+  if req.isSocket
+    req = _.extend req, _.pick(require('http').IncomingMessage.prototype, 'login', 'logIn', 'logout', 'logOut', 'isAuthenticated', 'isUnauthenticated')
+
   middleware = passport.authenticate('bearer', { session: false } )
   middleware req, res, ->
     next()


Members: fetch members from mongo not neo4j

--- client/Social/Members/AppController.coffee
+++ client/Social/Members/AppController.coffee
@@ -50,7 +50,7 @@
               JAccount.byRelevance @_searchValue, options, callback
             else
               group = KD.getSingleton('groupsController').getCurrentGroup()
-              group.fetchMembersFromGraph options, (err, res)=>
+              group.fetchMembers selector, options, (err, res)=>
                 callback err, res

               group.countMembers (err, count) =>


app/kodingapps: fix typo

app/kodingapps: fix typo

--- client/app/lib/kodingappscontroller.coffee
+++ client/app/lib/kodingappscontroller.coffee
@@ -80,7 +80,7 @@
     if app.style
       cb = if app.script then kd.noop else callback
       @appendHeadElement 'style',  \
-        { app: app, url:app.style, identifier:app.identifier, force: yes }, callback
+        { app: app, url:app.style, identifier:app.identifier, force: yes }, cb

     if app.script
       @appendHeadElement 'script', \


Added report to url check

Added report to url check

--- lib/server/incoming.coffee
+++ lib/server/incoming.coffee
@@ -84,7 +84,7 @@
     imp_name = null

     url = req.url
-    if url and url.replace('/incoming', '').length > 1
+    if url and (url.replace('/incoming', '').length > 1 or url.replace('/report', '').length > 1)
       # Determine from url
       _.each @implementations, (config, name) =>
         if url.match(new RegExp(name, "gi"))


Remove checkt as part of the core

Remove checkt as part of the core


Fix the display of the repositories (a repo doesnt have any 'id' attribute, using the 'name' for now) [closes #13]

--- app/assets/javascripts/models/repository.js.coffee
+++ app/assets/javascripts/models/repository.js.coffee
@@ -1,5 +1,5 @@
 Lyonrb.Repository = SC.Record.extend
-  primaryKey: 'id'
+  primaryKey: 'name'

 Lyonrb.Repository.mixin
   all: (id) -> Lyonrb.store.find(this)


Don't fetch users….

Don't fetch users….

--- app/assets/javascripts/views/clouds/show.js.coffee
+++ app/assets/javascripts/views/clouds/show.js.coffee
@@ -73,7 +73,7 @@
       @chat_view = new Cloudsdale.Views.CloudsChat(model: @model)
       @.$('.float-container > .container-inner > .chat-wrapper').replaceWith(@chat_view.el)

-    @model.users().cachedFetch()
+    # @model.users().cachedFetch()
     @model.users().listen()

     # if @.$('.cloud-sidebar-bottom').children().length == 0


mongo fetch dbs fix

mongo fetch dbs fix

--- kites/databases/mongodbApi.coffee
+++ kites/databases/mongodbApi.coffee
@@ -1,7 +1,7 @@
 mongo   = require 'mongodb'
 log4js  = require 'log4js'
 config  = require('./config').mongo
-
+{daisy} = require "sinkrow"
 logFile = '/var/log/node/MongoDBApi.log'
 #log     = log4js.addAppender log4js.fileAppender(logFile), "[MongoDBApi]"
 log     = log4js.getLogger('[MongoDBApi]')
@@ -35,7 +35,7 @@
     id.substr 0, length

   getDbUsers : (dbName,callback)->
-
+    log.debug 'does call'

     db = new mongo.Db dbName, @server, native_parser: false
     db.open (err,db)=>
@@ -43,15 +43,16 @@
         log.error error = "[ERROR] can't open database #{dbName}: #{err}"
       else
         db.admin().authenticate @mongoUser, @mongoPass,(err,result)=>
+          log.error err if err
           db.collection "system.users",(err,collection)=>
+            log.error err if err
             collection.find({},{fields:user:1}).toArray (err,items)->
+              log.error err if err
               usernames = []
-              l = items.length
               for user in items
                 usernames.push user.user
-                l--
-                if l is 0
-                  callback usernames
+              console.log "miki",usernames
+              callback usernames

   fetchDatabaseList :(options, callback)->
     #
@@ -80,19 +81,20 @@
                   callback? error
                 else
                   users_dbs = []
+                  dbInfo = []
                   for db in result.documents[0].databases
                      users_dbs.push db.name if db.name.match username
-
-                  dbInfo = []
-                  l = users_dbs.length
-                  for users_db in users_dbs
-                    l--
-                    @getDbUsers users_db,(usersArray)=>
-                      dbInfo.push { dbName: users_db, dbUser: usersArray }
-                      if l is 0
-                        callback dbInfo
-
-
+                  queue = []
+                  users_dbs.forEach (users_db, index)=>
+                    queue.push =>
+                      @getDbUsers users_db,(usersArray)=>
+                        log.debug "x",users_db,index,usersArray
+                        dbInfo.push { dbName: users_db, dbUser: usersArray }
+                        queue.next()
+                  queue.push ->
+                    log.debug 'info FINALLY', dbInfo
+                    callback null, dbInfo
+                  daisy queue

   createDatabase : (options,callback)->


no_unnecessary_fat_arrows: super is no longer allowed in normal functions in cs2

--- test/test_no_unnecessary_fat_arrows.coffee
+++ test/test_no_unnecessary_fat_arrows.coffee
@@ -32,7 +32,6 @@
     'function with this.a': shouldPass '=> this.a'
     'function with @': shouldPass '=> @'
     'function with @a': shouldPass '=> @a'
-    'function with super': shouldPass '=> super'

     'nested simple functions':
         'with inner fat arrow': shouldError '-> => 1'


testing webhook, just log request body...

--- routes/index.coffee
+++ routes/index.coffee
@@ -18,7 +18,8 @@

 ### New Card ###
 router.post '/card', (req, res) ->
-  log req.body
+  log req
+  res.send()

 #  if req.body?.card?.id
 #    parse.updateCard req.body


GetModel: fix exceptions

GetModel: fix exceptions

--- components/GetModel.coffee
+++ components/GetModel.coffee
@@ -1,4 +1,3 @@
-schemagen = require 'mongoose-schemagen'
 noflo = require 'noflo'

 class CreateModel extends noflo.Component
@@ -11,12 +10,12 @@

     @inPorts.connection.on 'data', (connection) =>
       @connection = connection
-      @createmodel()
+      @getModel()
     @inPorts.name.on 'data', (name) =>
       @name = name
-      @createmodel()
+      @getModel()

-  createModel: () ->
+  getModel: () ->
     return unless @connection and @name
     model = @connection.model(@name)
     delete @name


Fuss with FrameView styles a little.

Fuss with FrameView styles a little.

--- lib/stacktrace-view.coffee
+++ lib/stacktrace-view.coffee
@@ -31,7 +31,7 @@
     @div class: 'frame inset-panel', =>
       @div class: 'panel-heading', =>
         @span class: 'function-name text-highlight inline-block', frame.functionName
-        @span class: 'source-location text-subtle inline-block', =>
+        @span class: 'source-location text-info inline-block pull-right', =>
           @text "#{frame.path} @ #{frame.lineNumber}"
       @div class: 'panel-body padded', =>
         @pre output: 'source', 'Source goes here'


Remove assumption and reference of global data

QueryWorkflow now takes the data as passed options on construction. These
include:

- `concepts`: a collection concepts that are deemed queryable
- `context`: a context model which is commonly the currently "active"
one for the session.

--- src/coffee/cilantro/ui/workflows/query.coffee
+++ src/coffee/cilantro/ui/workflows/query.coffee
@@ -10,7 +10,18 @@

     templates = _.object ['query'], templates

+    ###
+    The QueryWorkflow provides an interface for navigating and viewing
+    concepts that are deemed 'queryable'. This includes browsing the
+    available concepts in the index, viewing details about the
+    concept in the workspace as well as adding or modifying filters,
+    and viewing the list of filters in the context panel.
+    TODO: break out context panel as standalone view

+    This view requires the following options:
+    - concepts: a collection of concepts that are deemed queryable
+    - context: the session/active context model
+    ###
     class QueryWorkflow extends Marionette.Layout
         className: 'query-workflow'

@@ -21,29 +32,26 @@
             workspace: '.concept-workspace-region'
             context: '.context-panel-region'

+        initialize: (options) ->
+            if not (@context = options.context)
+                throw new Error 'context model required'
+            if not (@concepts = options.concepts)
+                throw new Error 'concept collection required'
+
         onRender: ->
             @workspace.show new concept.ConceptWorkspace

-            # Deferred loading of views..
-            @concepts.show new base.LoadView
-                message: 'Loading query concepts...'
+            @concepts.show new concept.ConceptPanel
+                collection: @concepts

-            @context.show new base.LoadView
-                message: 'Loading session context...'
+            @concepts.currentView.$el.stacked
+                fluid: '.index-region'

-            c.promiser.when 'concepts', =>
-                @concepts.show new concept.ConceptPanel
-                    collection: c.data.concepts.queryable
+            @context.show new context.ContextPanel
+                model: @context

-                @concepts.currentView.$el.stacked
-                    fluid: '.index-region'
-
-            c.promiser.when 'contexts', =>
-                @context.show new context.ContextPanel
-                    model: c.data.contexts.getSession()
-
-                @context.currentView.$el.stacked
-                    fluid: '.tree-region'
+            @context.currentView.$el.stacked
+                fluid: '.tree-region'

     { QueryWorkflow }


rm -C

rm -C

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -2390,9 +2390,11 @@
             cb    : undefined   # cb(true or false)
             mv_args : undefined
             alert : true        # show alerts
-        args = [opts.src, opts.dest]
         if opts.mv_args?
-            args = args.concat(opts.mv_args)
+            args = opts.mv_args
+        else
+            args = []
+        args = args.concat(['--', opts.src, opts.dest])
         salvus_client.exec
             project_id : @project.project_id
             command    : 'mv'


client sync: fix a potentially *major* annoyance with save causing multiple syncs.

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -251,7 +251,7 @@
     _save: (cb) =>
         if not @dsync_client?
             cb("must be connected before saving"); return
-        @_sync (err) =>
+        @sync (err) =>
             if err
                 cb(err); return
             @call


Fix auth function

Fix auth function

User id's are not numbers anymore

Closes #25

Closes #16

--- routes/users/index.coffee
+++ routes/users/index.coffee
@@ -12,7 +12,7 @@
     auth_id = req.user.id
     req_id = req.params.id
     if auth_id and req_id
-      if Number(auth_id) is Number(req_id)
+      if auth_id is req_id
         next()
       else
         res.send 403


Prevent hitting hidden cannons from the deck

--- src/components/entities/BattleShip.coffee
+++ src/components/entities/BattleShip.coffee
@@ -52,21 +52,21 @@
     @hatch = Crafty.e('CarrierHatch, ShipHatch1').attr(
       x: @x + 10 * 32
       y: @y + 29
-      floorOffset: 65
+      floorOffset: 75
     )
     @attach(@hatch)

     @hatch2 = Crafty.e('CarrierHatch, ShipHatch2').attr(
       x: @x + 16 * 32
       y: @y + 29
-      floorOffset: 65
+      floorOffset: 75
     )
     @attach(@hatch2)

     @hatch3 = Crafty.e('CarrierHatch, ShipHatch3').attr(
       x: @x + 22 * 32
       y: @y + 29
-      floorOffset: 65
+      floorOffset: 75
     )
     @attach(@hatch3)


Refactor TransformerBase's extends

Refactor TransformerBase's extends

--- lib/transforms/base.coffee
+++ lib/transforms/base.coffee
@@ -2,8 +2,6 @@
   buildError
   clone
 } = require('../helpers')
-
-extend = require('util')._extend

 ###**
 # TransformerBase:
@@ -58,18 +56,23 @@

 module.exports =
 class TransformerBase
+
+  ###
+  # Run multiple transformations
+  ###
+
   @run: (ast, options, classes, ctx) ->
+    # Combine `classes` into one mega-class
     Xformer = class extends TransformerBase
-
-    classes.forEach (klass) ->
-      extend(Xformer.prototype, klass.prototype)
-
+    safeExtend Xformer, classes
+
+    # Run that
     xform = new Xformer(ast, options)
     result = xform.run()

+    # Collect warnings into `ctx`, then return
     ctx.warnings ?= []
     ctx.warnings = ctx.warnings.concat(xform.warnings)
-
     result

   constructor: (@ast, @options) ->
@@ -218,3 +221,19 @@
   FunctionExpressionExit: (node) ->
     @popStack()
     node
+
+###
+# Extends a class `dest`'s prototype with those from other classes in `classes`.
+# Throws an error if there's a clash.
+###
+
+safeExtend = (dest, classes) ->
+  added = {}
+  classes.forEach (klass) ->
+    for key, fn of klass::
+      if klass::hasOwnProperty(key)
+        if added[key] and key isnt 'constructor'
+          throw new Error("Conflict with member '#{key}'")
+        dest::[key] = fn
+        added[key] = true
+  dest


Fix rotors spinning on parked heli

Fix rotors spinning on parked heli

--- src/components/entities/Helicopter.coffee
+++ src/components/entities/Helicopter.coffee
@@ -54,7 +54,12 @@
       @pauseAnimation()
       @sprite(8, 6)
     else
-      @animate('fly', -1) unless @isPlaying('fly')
+      if @rotors is on
+        @animate('fly', -1) unless @isPlaying('fly')
+      else
+        @pauseAnimation()
+        @sprite(12, 8)
+

   updateMovementVisuals: (rotation, dx, dy, dt) ->
     @vx = dx * (1000 / dt)


add comment showing the real job format

add comment showing the real job format

--- src/controllers/meshblu-rpc-controller.coffee
+++ src/controllers/meshblu-rpc-controller.coffee
@@ -5,6 +5,13 @@
   constructor: ({@service}) ->

   message: (request, response) =>
+    # job =
+    #     metadata:
+    #       auth: @auth
+    #       toUuid: @auth.uuid
+    #       jobType: 'SearchDevices'
+    #     data:
+    #       type: 'bug'
     {metadata, data} = request.body
     meshbluConfig = request.meshbluAuth
     toUuid = request.meshbluAuth.uuid


Deprecate KeymapManager::remove

Deprecate KeymapManager::remove

The disposable return from ::add should be used instead.

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -248,11 +248,11 @@
         index = @keyBindings.indexOf(keyBinding)
         @keyBindings.splice(index, 1) unless index is -1

-  # Public: Remove the key bindings added with {::add} or {::loadKeymap}.
-  #
-  # * `source` A {String} representing the `source` in a previous call to
-  #   {::add} or the path in {::loadKeymap}.
   remove: (source) ->
+    Grim.deprecate("Call .dispose() on the Disposable returned from KeymapManager::add instead")
+    @removeBindingsFromSource(source)
+
+  removeBindingsFromSource: (source) ->
     @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source
     undefined

@@ -350,12 +350,12 @@
   reloadKeymap: (filePath) ->
     if fs.isFileSync(filePath)
       if bindings = @readKeymap(filePath, true)
-        @remove(filePath)
+        @removeBindingsFromSource(filePath)
         @addKeymap(filePath, bindings)
         @emit 'reloaded-key-bindings', filePath
         @emitter.emit 'did-reload-keymap', {path: filePath}
     else
-      @remove(filePath)
+      @removeBindingsFromSource(filePath)
       @emit 'unloaded-key-bindings', filePath
       @emitter.emit 'did-unload-keymap', {path: filePath}


added global pprint to wisp language extension

--- src/languages/wisp.coffee
+++ src/languages/wisp.coffee
@@ -30,6 +30,7 @@
               try
                   result = compiler.compile code
                   global.exports = module.exports
+                  global.pprint = (x) -> console.dir x, colors: true
                   output = vm.runInThisContext(result.code, {filename})
               catch e
                 err = e


lexical_linter: typo

lexical_linter: typo

--- src/lexical_linter.coffee
+++ src/lexical_linter.coffee
@@ -58,7 +58,7 @@
         @tokenApi.lineNumber = @lineNumber

         # Multiple rules might run against the same token to build context.
-        # Every every rule should run even if something has already produced an
+        # Every rule should run even if something has already produced an
         # error for the same token.
         errors = []
         for rule in @rules when token[0] in rule.tokens


remove BaseModel#assertSubModelProp()

remove BaseModel#assertSubModelProp()

--- src/lib/base-model.coffee
+++ src/lib/base-model.coffee
@@ -210,8 +210,6 @@
     @return {BaseModel} this
     ###
     setRelatedModel: (prop, submodel) ->
-
-        @assertSubModelProp(prop, 'setRelatedModel(s)')

         typeInfo = @constructor.properties[prop]
         modelName = typeInfo.model
@@ -254,7 +252,6 @@
     @method setRelatedModels
     ###
     unsetRelatedModel: (prop) ->
-        @assertSubModelProp(prop, 'unsetRelatedModel(s)')

         typeInfo = @constructor.properties[prop]
         modelName = typeInfo.model
@@ -286,7 +283,6 @@
     @method addRelatedModels
     ###
     addRelatedModels: (prop, submodels...) ->
-        @assertSubModelProp(prop, 'addRelatedModels')

         typeInfo = @constructor.properties[prop]
         modelName = typeInfo.model
@@ -452,23 +448,6 @@
             return @

-
-    ###*
-    assert given prop is model prop
-
-    @method assertSubModelProp
-    @private
-    ###
-    assertSubModelProp: (prop, method) ->
-
-        typeInfo = @constructor.properties[prop]
-
-        if not typeInfo? or not typeInfo.model
-            throw @getFacade().error """
-                #{@constructor.name}.#{method}(#{prop})
-                #{prop} is not a prop for model.
-            """
-
     @getModelName: ->
         @name.replace(/([A-Z])/g, (st)-> '-' + st.charAt(0).toLowerCase()).slice(1)


Add some documentation on Network initialization

--- src/lib/Network.coffee
+++ src/lib/Network.coffee
@@ -22,13 +22,25 @@
 # components, attach sockets between them, and handle the sending
 # of Initial Information Packets.
 class Network extends EventEmitter
+  # Processes contains all the instantiated components for this network
   processes: {}
+  # Connections contains all the socket connections in the network
   connections: []
+  # Initials contains all Initial Information Packets (IIPs)
   initials: []
+  # The Graph this network is instantiated with
   graph: null
+  # Start-up timestamp for the network, used for calculating uptime
   startupDate: null
   portBuffer: {}

+  # All NoFlo networks are instantiated with a graph. Upon instantiation
+  # they will load all the needed components, instantiate them, and
+  # set up the defined connections and IIPs.
+  #
+  # The network will also listen to graph changes and modify itself
+  # accordingly, including removing connections, adding new nodes,
+  # and sending new IIPs.
   constructor: (graph) ->
     @processes = {}
     @connections = []
@@ -36,8 +48,12 @@
     @graph = graph

     if typeof process is 'object' and process.title is 'node'
+      # On Node.js we default the baseDir for component loading to
+      # the current working directory
       @baseDir = graph.baseDir or process.cwd()
     else
+      # On browser we default the baseDir to the Component loading
+      # root
       @baseDir = graph.baseDir or '/'

     # As most NoFlo networks are long-running processes, the


add errors & loader to controller

add errors & loader to controller

--- app/scripts/controllers/fragments/get-infos.coffee
+++ app/scripts/controllers/fragments/get-infos.coffee
@@ -27,6 +27,28 @@

         ###*
+         # Current seeked filename
+         # @attribute filename
+        ###
+        $scope.filename = ""
+
+
+        ###*
+         # Currently loading
+         # @attribute loading
+        ###
+        $scope.loading =
+            list: false
+
+
+        ###*
+         # Current errors
+         # @attribute errors
+        ###
+        $scope.errors = []
+
+
+        ###*
          # Close seeked movie
          # @method close
         ###
@@ -35,22 +57,37 @@

         ###*
-         # Seek a movie list from a filename search
-         # @method seek
+         # Init controller
+         # @method init
         ###
-        $scope.seek = () ->
+        $scope.init = () ->

             # Get filename from movie previous added file
             full_path = $scope.movie.path.split "/"
             full_filename = full_path.pop().split "."
             ext = full_filename.pop()
-            filename = $scope.movie.filename = filename = full_filename.join " "
+            $scope.movie.filename = $scope.filename = full_filename.join " "
+
+            $scope.seek()
+
+
+        ###*
+         # Seek a movie list from a filename search
+         # @method seek
+        ###
+        $scope.seek = () ->
+            $scope.errors = []
+            $scope.loading.list = true

             # Call provider for infos
             $trakt
-                .search filename
+                .search $scope.filename
                 .then (data) ->
                     $scope.movies = data
+                .catch (e) ->
+                    $scope.errors.push e
+                .finally () ->
+                    $scope.loading.list = false

         ###*
@@ -73,4 +110,4 @@

         # GOGO GADGETO SEEKING
-        $scope.seek()
+        $scope.init()


Second take on combinability bug

Second take on combinability bug

--- src/dockerfile-functions.coffee
+++ src/dockerfile-functions.coffee
@@ -23,24 +23,25 @@
     when Run, MultiRun then return MultiRun
     when Volume, MultiVolume then return MultiVolume
     when ContextCopy, MultiContextCopy then return MultiContextCopy
-    else return type
+    else return undefined

 module.exports.combinable = (a, b) ->
-  combinesTo(a.constructor) is combinesTo(b.constructor)
+  combinesA = combinesTo(a.constructor)
+  combinesA? and combinesA is combinesTo(b.constructor)

 module.exports.aggregateRegion = (list, index) ->
   next = index + 1
   # Index should point at the beginning of a suitable region
   type = list[index].constructor
   ctor = combinesTo(type)
-  return next if ctor is type
+  return next unless ctor?
   # Every combinable command becomes a multi-command (with a potential to collect
   # more than one command)
   aggregated = new ctor(list[index])
   list[index] = aggregated
   # Add every following command until it does not combine
   while list.length > next
-    return next unless combinesTo(list[next].constructor) is ctor
+    return next if combinesTo(list[next].constructor) != ctor
     aggregate = new ctor(list[index], list[next])
     list[index] = aggregate
     list[(next)..(next)] = []


Add filters to dashboard

Add filters to dashboard

--- lib/handler/dashboard.coffee
+++ lib/handler/dashboard.coffee
@@ -11,6 +11,7 @@
 class Dashboard extends ScrollView
   @content: ->
      @div class: "test", overflow: "scroll", tabindex: -1, style: "overflow: scroll;", =>
+       @div class:'filters',    style: 'padding: 20px 20px 5px 20px;'
        @div class:'container1', style: 'padding: 20px 20px 20px 20px;'
        @div class:'container2', style: 'padding: 20px 20px 20px 20px;'
        @div class:'container3', style: 'padding: 20px 20px 20px 20px;'
@@ -18,18 +19,40 @@
    initialize: (state) ->
      super
      console.log("State path: " + state.path)
-     files_ = @getFilesinDirectory(state.path)
-     for somefile in files_
-       console.log(somefile)
-     @getAnnotationCounts(files_ , state.path)
-     @getUserContributions(state.path)
-     @getTagDistributions(state.path)
-     @getAnnotationsCountbyTime(state.path)
-     @addGraph()
-
-  addGraph: ->
+     @updateState(state)
+
+  updateState: (newstate) =>
+    @destroyScene()
+    console.log("Is user undefined? " + @user)
+    console.log("Is tag undefined? " + @tag)
+    console.log("State path: " + newstate.path)
+    files_ = @getFilesinDirectory(newstate.path)
+    if(typeof(newstate.user) != 'undefined')
+      console.log("Setting user")
+      @user = newstate.user
+    if(typeof(newstate.tag) != 'undefined')
+      console.log("Setting tag")
+      @tag = newstate.tag
+    for somefile in files_
+      console.log(somefile)
+    @getAnnotationCounts(files_ , newstate.path)
+    @getUserContributions(newstate.path)
+    @getTagDistributions(newstate.path)
+    @getAnnotationsCountbyTime(newstate.path)
+    @addFilter(newstate.path, @updateState)
+    @addGraph(newstate.path, @updateState)
+
+  destroyScene:  ->
+    @user = undefined;
+    @tag = undefined;
+    @find('div.filters').empty()
+    @find('div.container1').empty()
+    @find('div.container2').empty()
+    @find('div.container3').empty()
+
+  addGraph: (directory ,callback) ->
     newDiv = document.createElement('div')
-    newDiv.style = "width: 80%; height: 40%;"
+    newDiv.style = "width: 100%; height: 40%;"
     @find('div.container1').append(newDiv)
     myChart = new (Highcharts.Chart)(
       chart:
@@ -38,6 +61,11 @@
       title: text: 'Files by number of Annotations'
       xAxis: categories: @set1
       yAxis: title: text: 'Number of Annotations'
+      plotOptions: series:
+        cursor: 'pointer'
+        point: events: click: ->
+          console.log('Category: ' + @category + ', value: ' + @y)
+          return
       series: [
         {
           name: 'User Generated Annotation'
@@ -50,7 +78,7 @@
       ])

     newSpan1 = document.createElement('div')
-    newSpan1.style = "width: 45%; height: 45%; display: inline-block;"
+    newSpan1.style = "width: 50%; height: 45%; display: inline-block;"
     @find('div.container2').append(newSpan1)
     piechart1 = new (Highcharts.Chart)(
       chart:
@@ -60,6 +88,12 @@
       plotOptions: pie:
         allowPointSelect: true
         cursor: 'pointer'
+        point: events: click: ->
+          console.log('value: ' + @name)
+          callback(
+            uri: 'test'
+            path: directory
+            user: @name)
         dataLabels: enabled: false
         showInLegend: true
       series: [ {
@@ -69,7 +103,7 @@
       } ])

     newSpan2 = document.createElement('div')
-    newSpan2.style = "width: 45%; height: 45%; display: inline-block;"
+    newSpan2.style = "width: 50%; height: 45%; display: inline-block;"
     @find('div.container2').append(newSpan2)
     piechart1 = new (Highcharts.Chart)(
       chart:
@@ -79,6 +113,12 @@
       plotOptions: pie:
         allowPointSelect: true
         cursor: 'pointer'
+        point: events: click: ->
+          console.log('value: ' + @name)
+          callback(
+            uri: 'test'
+            path: directory
+            tag: @name)
         dataLabels: enabled: false
         showInLegend: true
       series: [ {
@@ -88,7 +128,7 @@
       } ])

     newDiv2 = document.createElement('div')
-    newDiv2.style = "width: 80%; height: 40%;"
+    newDiv2.style = "width: 100%; height: 40%;"
     @find('div.container3').append(newDiv2)
     myChart2 = new (Highcharts.Chart)(
       chart:
@@ -133,19 +173,39 @@
       json = fs.readFileSync(fileName)
       data = JSON.parse(json)
       annotatedFiles = data.annotated_files
-      for annot in annotatedFiles
-        macgenAnnots[annot.meta.name] = annot.annotations.length
-        customAnnots[annot.meta.name] = annot.custom_annotations.length
+      if(typeof(@user) != 'undefined')
+        for annot in annotatedFiles
+          macgenAnnots[annot.meta.name] = annot.annotations.length
+          for cust in annot.custom_annotations
+            if(cust.user.name == @user)
+              if(typeof(customAnnots[annot.meta.name]) == 'undefined' )
+                customAnnots[annot.meta.name] = 0
+              else
+                customAnnots[annot.meta.name] += 1
+      else if(typeof(@tag) != 'undefined')
+        for annot in annotatedFiles
+          macgenAnnots[annot.meta.name] = annot.annotations.length
+          for cust in annot.custom_annotations
+            if(cust.tags.indexOf(@tag) != -1)
+              if(typeof(customAnnots[annot.meta.name]) == 'undefined' )
+                customAnnots[annot.meta.name] = 0
+              else
+                customAnnots[annot.meta.name] += 1
+      else
+        for annot in annotatedFiles
+          macgenAnnots[annot.meta.name] = annot.annotations.length
+          customAnnots[annot.meta.name] = annot.custom_annotations.length
       for file in files
-        @set1.push(file)
-        if(typeof macgenAnnots[file] == 'undefined')
-          @set2.push(0)
-        else
-          @set2.push(macgenAnnots[file] )
-        if(typeof customAnnots[file] == 'undefined')
-          @set3.push(0)
-        else
-          @set3.push(customAnnots[file] )
+        if(file != '.annotator')
+          @set1.push(file)
+          if(typeof macgenAnnots[file] == 'undefined')
+            @set2.push(0)
+          else
+            @set2.push(macgenAnnots[file] )
+          if(typeof customAnnots[file] == 'undefined')
+            @set3.push(0)
+          else
+            @set3.push(customAnnots[file] )
       for elem in @set1
         console.log('Element: ' + elem)
       for elem in @set2
@@ -164,15 +224,38 @@
       json = fs.readFileSync(fileName)
       data = JSON.parse(json)
       annotatedFiles = data.annotated_files
-      for annot in annotatedFiles
-        for cust in annot.custom_annotations
-          if(list1.indexOf(cust.user.name) == -1)
-            list1.push(cust.user.name)
-          if(typeof userCntMap[cust.user.name] == 'undefined')
-            userCntMap[cust.user.name] = 1
-          else
-            console.log('In else...')
-            userCntMap[cust.user.name] += 1
+      if(typeof(@user) != 'undefined')
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            if(cust.user.name == @user)
+              if(list1.indexOf(cust.user.name) == -1)
+                list1.push(cust.user.name)
+              if(typeof userCntMap[cust.user.name] == 'undefined')
+                userCntMap[cust.user.name] = 1
+              else
+                console.log('In else...')
+                userCntMap[cust.user.name] += 1
+      else if(typeof(@tag) != 'undefined')
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            if(cust.tags.indexOf(@tag) != -1)
+              if(list1.indexOf(cust.user.name) == -1)
+                list1.push(cust.user.name)
+              if(typeof userCntMap[cust.user.name] == 'undefined')
+                userCntMap[cust.user.name] = 1
+              else
+                console.log('In else...')
+                userCntMap[cust.user.name] += 1
+      else
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            if(list1.indexOf(cust.user.name) == -1)
+              list1.push(cust.user.name)
+            if(typeof userCntMap[cust.user.name] == 'undefined')
+              userCntMap[cust.user.name] = 1
+            else
+              console.log('In else...')
+              userCntMap[cust.user.name] += 1
       for usr in list1
         @set4.push({
             name: usr
@@ -183,6 +266,62 @@
     catch error
       console.log('Error: ' + error)

+  addFilter: (directory, callback) ->
+    if(typeof(@user) != 'undefined')
+      filterlabel = @createFilterLabel()
+      filterelem =  @createFilterElement(@user)
+      filterremove = @createFiletrRemoveButton()
+      filterremove.addEventListener 'click', ->
+        callback(
+          uri: 'test'
+          path: directory
+        )
+      @find('div.filters').empty()
+      @find('div.filters').append(filterlabel)
+      @find('div.filters').append(filterelem)
+      @find('div.filters').append(filterremove)
+    else if(typeof(@tag) != 'undefined')
+      filterlabel = @createFilterLabel()
+      filterelem =  @createFilterElement(@tag)
+      filterremove = @createFiletrRemoveButton()
+      filterremove.addEventListener 'click', ->
+        callback(
+          uri: 'test'
+          path: directory
+        )
+      @find('div.filters').empty()
+      @find('div.filters').append(filterlabel)
+      @find('div.filters').append(filterelem)
+      @find('div.filters').append(filterremove)
+
+  createFilterLabel : ->
+      filterlabel = document.createElement('span')
+      filterlabel.style = "padding: 5px 20px 5px 20px;"
+      #filterlabel.classList.add('badge')
+      #filterlabel.classList.add('badge-success')
+      filterlabel.innerHTML = 'Filtered By'
+      return filterlabel
+
+  createFilterElement: (filtertag) ->
+      filterelem = document.createElement('span')
+      filterelem.style = "padding: 5px 20px 5px 20px;"
+      filterelem.classList.add('badge')
+      filterelem.classList.add('badge-success')
+      filterelem.classList.add('icon')
+      filterelem.classList.add('icon-tag')
+      filterelem.innerHTML = filtertag
+      return filterelem
+
+  createFiletrRemoveButton: ->
+    filterremove = document.createElement('span')
+    filterremove.style = "margin-left: 30px; padding: 5px 20px 5px 20px;  cursor: pointer; cursor: hand;"
+    filterremove.classList.add('badge')
+    filterremove.classList.add('badge-error')
+    filterremove.classList.add('icon')
+    filterremove.classList.add('icon-x')
+    filterremove.innerHTML = 'Remove Filter'
+    return filterremove
+
   getTagDistributions: (dir) ->
     list1 = []
     userCntMap = {}
@@ -192,16 +331,41 @@
       json = fs.readFileSync(fileName)
       data = JSON.parse(json)
       annotatedFiles = data.annotated_files
-      for annot in annotatedFiles
-        for cust in annot.custom_annotations
-          for tag in cust.tags
-            if(list1.indexOf(tag) == -1)
-              list1.push(tag)
-            if(typeof userCntMap[tag] == 'undefined')
-              userCntMap[tag] = 1
-            else
-              console.log('In else...')
-              userCntMap[tag] += 1
+      if(typeof(@user) != 'undefined')
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            if(cust.user.name == @user)
+              for tag in cust.tags
+                if(list1.indexOf(tag) == -1)
+                  list1.push(tag)
+                if(typeof userCntMap[tag] == 'undefined')
+                  userCntMap[tag] = 1
+                else
+                  console.log('In else...')
+                  userCntMap[tag] += 1
+      else if(typeof(@tag) != 'undefined')
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            for tag in cust.tags
+              if(tag == @tag)
+                if(list1.indexOf(tag) == -1)
+                  list1.push(tag)
+                if(typeof userCntMap[tag] == 'undefined')
+                  userCntMap[tag] = 1
+                else
+                  console.log('In else...')
+                  userCntMap[tag] += 1
+      else
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            for tag in cust.tags
+              if(list1.indexOf(tag) == -1)
+                list1.push(tag)
+              if(typeof userCntMap[tag] == 'undefined')
+                userCntMap[tag] = 1
+              else
+                console.log('In else...')
+                userCntMap[tag] += 1
       for tag in list1
         @set5.push({
             name: tag
@@ -230,17 +394,46 @@
       json = fs.readFileSync(fileName)
       data = JSON.parse(json)
       annotatedFiles = data.annotated_files
-      for annot in annotatedFiles
-        for cust in annot.custom_annotations
-          createdDate = @parseDate(cust.createdAt)
-          console.log("Created Date " + createdDate)
-          for timing in @set6
-            if(timing.isAfter(createdDate))
-              if(typeof customAnnots[timing] == 'undefined')
-                customAnnots[timing] = 1
-              else
-                customAnnots[timing] += 1
-            else
+      if(typeof(@user) != 'undefined')
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            if(cust.user.name == @user)
+              createdDate = @parseDate(cust.createdAt)
+              console.log("Created Date " + createdDate)
+              for timing in @set6
+                if(timing.isAfter(createdDate))
+                  if(typeof customAnnots[timing] == 'undefined')
+                    customAnnots[timing] = 1
+                  else
+                    customAnnots[timing] += 1
+                else
+                  customAnnots[timing] = 0
+      else if(typeof(@tag) != 'undefined')
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            if(cust.tags.indexOf(@tag) != -1)
+              createdDate = @parseDate(cust.createdAt)
+              console.log("Created Date " + createdDate)
+              for timing in @set6
+                if(timing.isAfter(createdDate))
+                  if(typeof customAnnots[timing] == 'undefined')
+                    customAnnots[timing] = 1
+                  else
+                    customAnnots[timing] += 1
+                else
+                  customAnnots[timing] = 0
+      else
+        for annot in annotatedFiles
+          for cust in annot.custom_annotations
+            createdDate = @parseDate(cust.createdAt)
+            console.log("Created Date " + createdDate)
+            for timing in @set6
+              if(timing.isAfter(createdDate))
+                if(typeof customAnnots[timing] == 'undefined')
+                  customAnnots[timing] = 1
+                else
+                  customAnnots[timing] += 1
+              else
                 customAnnots[timing] = 0
       for timing in @set6
         @set7.push(customAnnots[timing])


Don't create add friend button if user is guest

--- resources/assets/coffee/react/_components/friend-button.coffee
+++ resources/assets/coffee/react/_components/friend-button.coffee
@@ -90,6 +90,8 @@

   render: =>
+    # hide button if currentUser is empty
+    return span() if !currentUser.id?
     # hide button if component's user_id is missing or the button would be for ourself
     return span() if !@props.user_id || @props.user_id == currentUser.id
     # hide the add button if we have hit the max friends limit


MultiInput: when user_selected activated and all inputs empty, enable the first one. See #55692

--- src/elements/MultiInput/MultiInput.coffee
+++ src/elements/MultiInput/MultiInput.coffee
@@ -113,6 +113,9 @@
 				v = @getValue()[input.getName()]
 			# All keys are selected when user_selectable option is false, otherwise the only ones with values are selected.
 			@__userSelectedData[input.getName()] = not @__user_selectable or (@__user_selectable and not CUI.util.isEmpty(v))
+
+		if not Object.values(@__userSelectedData).some((enabled) -> enabled)
+			@__userSelectedData[@__inputs[0].getName()] = true

 	setInputVisibility: ->
 		# the "append" re-orders the input, if needed


add default value as true for sparkline 'redraw on resize'

--- modules/plot/main/coffee/Sparkline.coffee
+++ modules/plot/main/coffee/Sparkline.coffee
@@ -7,6 +7,7 @@
       data: [],
       type: 'line',
       labelRenderer: (element, obj) -> hx.select(element).text(obj.y + ' (' + obj.x + ')')
+      redrawOnResize: true
     }, options)

     innerLabelRenderer = (element, meta) ->


Remove a console.log

Remove a console.log

--- app/assets/components/toggler/toggler.coffee
+++ app/assets/components/toggler/toggler.coffee
@@ -48,7 +48,6 @@
     @target.toggleClass(@toggle_classes)
     @el.toggleClass(@toggle_classes) if @dual_toggle
     @is_active = !@is_active
-    console.log @target

   activate: ->
     @target.addClass(@toggle_classes)


Update application.js.coffee

Update application.js.coffee

The require jquery.turbolinks should be before the require turbolinks.

https://github.com/rails/turbolinks => "Add the gem to your project, then add the following line to your JavaScript manifest file, after jquery.js but before turbolinks.js"

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -15,8 +15,8 @@
 #= require jquery.atwho
 #= require jquery.scrollTo
 #= require jquery.blockUI
+#= require jquery.turbolinks
 #= require turbolinks
-#= require jquery.turbolinks
 #= require bootstrap
 #= require select2
 #= require raphael


Fix document ready with TurboLinks.

Fix document ready with TurboLinks.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -4,7 +4,7 @@
 #= require bootstrap
 #= require_tree .

-$(->
+ready = ->
   $('#job_date_type').change(->
     switch $(@).val()
       when 'agreement', ''
@@ -17,4 +17,6 @@
         $('.job_start_date').slideDown()
         $('.job_end_date').slideDown()
   ).change()
-)
+
+$(document).ready(ready)
+$(document).on('page:load', ready)


fade up original content if no search string

--- app/assets/javascripts/droom/forms.js.coffee
+++ app/assets/javascripts/droom/forms.js.coffee
@@ -55,6 +55,7 @@
       if (k >= 32 and k <= 165) or k == 8
         if @_prompt.val() == ""
           @display @_original_content
+          @_original_content.fadeTo "fast", 1
         else
           @_form.submit()


Handle multiple coordinate sets in curves

--- src/path-parser.coffee
+++ src/path-parser.coffee
@@ -47,12 +47,30 @@

     node

+  # When a command has more than one set of coords specified, we iterate over
+  # each set of coords.
+  #
+  # Relative coordinates are relative to the last set of coordinates, not the
+  # last command. (The SVG docs http://www.w3.org/TR/SVG/paths.html are not
+  # super clear on this.)
+  iterateOverParameterSets = (command, setSize, isRelative, callback) ->
+    sets = command.parameters.length / setSize
+    for setIndex in [0...sets]
+      minindex = setIndex * setSize + 0
+      maxIndex = setIndex * setSize + setSize
+      paramSet = command.parameters.slice(minindex, maxIndex)
+      paramSet = makeAbsolute(paramSet) if isRelative
+      callback(paramSet, setIndex)
+    return
+
   for i in [0...groupedCommands.length]
     command = groupedCommands[i]
     switch command.type
-      when 'M'
+      when 'M', 'm'
         # Move to
-        currentPoint = command.parameters
+        params = command.parameters
+        params = makeAbsolute(params) if command.type == 'm'
+        currentPoint = params
         addNewSubpath(currentPoint)

       when 'L', 'l'
@@ -75,67 +93,80 @@
           params = params.slice(1)
         createNode([currentPoint[0], params[0]], i)

-      when 'C', 'c', 'Q', 'q'
+      when 'C', 'c'
         # Bezier
-        params = command.parameters
-        params = makeAbsolute(params) if command.type in ['c', 'q']
-
-        if command.type in ['C', 'c']
-          currentPoint = slicePoint(params, 4)
-          handleIn = slicePoint(params, 2)
-          handleOut = slicePoint(params, 0)
-        else
-          currentPoint = slicePoint(params, 2)
-          handleIn = handleOut = slicePoint(params, 0)
-
-        lastNode = currentSubpath.nodes[currentSubpath.nodes.length - 1]
-        lastNode.setAbsoluteHandleOut(handleOut)
-
-        if node = createNode(currentPoint, i)
-          node.setAbsoluteHandleIn(handleIn)
-        else
-          firstNode = currentSubpath.nodes[0]
-          firstNode.setAbsoluteHandleIn(handleIn)
+        setSize = 6
+        isRelative = command.type == 'c'
+        iterateOverParameterSets command, setSize, isRelative, (paramSet) ->
+          currentPoint = slicePoint(paramSet, 4)
+          handleIn = slicePoint(paramSet, 2)
+          handleOut = slicePoint(paramSet, 0)
+
+          lastNode = currentSubpath.nodes[currentSubpath.nodes.length - 1]
+          lastNode.setAbsoluteHandleOut(handleOut)
+
+          if node = createNode(currentPoint, i)
+            node.setAbsoluteHandleIn(handleIn)
+          else
+            firstNode = currentSubpath.nodes[0]
+            firstNode.setAbsoluteHandleIn(handleIn)
+
+      when 'Q', 'q'
+        # Bezier
+        setSize = 4
+        isRelative = command.type == 'q'
+        iterateOverParameterSets command, setSize, isRelative, (paramSet) ->
+          currentPoint = slicePoint(paramSet, 2)
+          handleIn = handleOut = slicePoint(paramSet, 0)
+
+          lastNode = currentSubpath.nodes[currentSubpath.nodes.length - 1]
+          lastNode.setAbsoluteHandleOut(handleOut)
+
+          if node = createNode(currentPoint, i)
+            node.setAbsoluteHandleIn(handleIn)
+          else
+            firstNode = currentSubpath.nodes[0]
+            firstNode.setAbsoluteHandleIn(handleIn)

       when 'S', 's'
         # Shorthand cubic bezier.
         # Infer last node's handleOut to be a mirror of its handleIn.
-        params = command.parameters
-        params = makeAbsolute(params) if command.type == 's'
-
-        currentPoint = slicePoint(params, 2)
-        handleIn = slicePoint(params, 0)
-
-        lastNode = currentSubpath.nodes[currentSubpath.nodes.length - 1]
-        lastNode.join('handleIn')
-
-        if node = createNode(currentPoint, i)
-          node.setAbsoluteHandleIn(handleIn)
-        else
-          firstNode = currentSubpath.nodes[0]
-          firstNode.setAbsoluteHandleIn(handleIn)
+        setSize = 4
+        isRelative = command.type == 's'
+        iterateOverParameterSets command, setSize, isRelative, (paramSet) ->
+          currentPoint = slicePoint(paramSet, 2)
+          handleIn = slicePoint(paramSet, 0)
+
+          lastNode = currentSubpath.nodes[currentSubpath.nodes.length - 1]
+          lastNode.join('handleIn')
+
+          if node = createNode(currentPoint, i)
+            node.setAbsoluteHandleIn(handleIn)
+          else
+            firstNode = currentSubpath.nodes[0]
+            firstNode.setAbsoluteHandleIn(handleIn)

       when 'T', 't'
         # Shorthand quadradic bezier.
         # Infer node's handles based on previous node's handles
-        params = command.parameters
-        params = makeAbsolute(params) if command.type == 't'
-
-        currentPoint = slicePoint(params, 0)
-
-        lastNode = currentSubpath.nodes[currentSubpath.nodes.length - 1]
-        lastNode.join('handleIn')
-
-        # Use the handle out from the previous node.
-        # TODO: Should check if the last node was a Q command...
-        # https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Bezier_Curves
-        handleIn = lastNode.getAbsoluteHandleOut()
-
-        if node = createNode(currentPoint, i)
-          node.setAbsoluteHandleIn(handleIn)
-        else
-          firstNode = currentSubpath.nodes[0]
-          firstNode.setAbsoluteHandleIn(handleIn)
+        setSize = 2
+        isRelative = command.type == 'q'
+        iterateOverParameterSets command, setSize, isRelative, (paramSet) ->
+          currentPoint = slicePoint(paramSet, 0)
+
+          lastNode = currentSubpath.nodes[currentSubpath.nodes.length - 1]
+          lastNode.join('handleIn')
+
+          # Use the handle out from the previous node.
+          # TODO: Should check if the last node was a Q command...
+          # https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Bezier_Curves
+          handleIn = lastNode.getAbsoluteHandleOut()
+
+          if node = createNode(currentPoint, i)
+            node.setAbsoluteHandleIn(handleIn)
+          else
+            firstNode = currentSubpath.nodes[0]
+            firstNode.setAbsoluteHandleIn(handleIn)

       when 'Z', 'z'
         currentSubpath.closed = true
@@ -162,6 +193,7 @@
       type: token.string
       parameters: []

+
     while nextToken = pathTokens[i+1]
       if nextToken.type == NUMBER
         command.parameters.push(parseFloat(nextToken.string))


simplified code

simplified code

--- app/assets/javascripts/pages/websites.coffee
+++ app/assets/javascripts/pages/websites.coffee
@@ -12,33 +12,25 @@

 $('#platform').on 'click', (platform) ->
   manipulateWizard(3)
+  reset_platforms()
   switch platform.target.parentElement.name
     when 'javascript'
-      reset_platforms()
       $('#javascript').show()
     when 'nod_js'
-      reset_platforms()
       $('#nod_js').show()
     when 'rails'
-      reset_platforms()
       $('#rails').show()
     when 'ruby'
-      reset_platforms()
       $('#ruby').show()
     when 'python'
-      reset_platforms()
       $('#python').show()
     when 'ios'
-      reset_platforms()
       $('#ios').show()
     when 'php'
-      reset_platforms()
       $('#php').show()
     when 'java'
-      reset_platforms()
       $('#java').show()
     when 'django'
-      reset_platforms()
       $('#django').show()

 reset_platforms = () ->


Wrong indentation.

Wrong indentation.

--- src/scripts/views/article-container.coffee
+++ src/scripts/views/article-container.coffee
@@ -23,8 +23,8 @@
 		@props.dispatch actions.fetch(params)
 	save: (data) ->
 		@props.dispatch actions.save data
- 	componentWillMount: ->
- 		@fetch @props.params unless @props.lastUpdate
+	componentWillMount: ->
+		@fetch @props.params unless @props.lastUpdate
 	componentWillReceiveProps: (nextProps) ->
 		nextParams = Immutable.Map nextProps.params
 		params = Immutable.Map @props.params


Execute frame-split procedure statements sequentially (to match new semantics in rapids).

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -1015,7 +1015,6 @@

       sum += part.ratio

-    console.log splits
     splits

   requestBindFrames = (key, sourceKeys, go) ->
@@ -1031,38 +1030,33 @@

       randomVecKey = createTempKey()

-      _.requestExec "(tmp= #{randomVecKey} (h2o.runif #{frameKey} -1))", (error, result) ->
+      statements = []
+
+      push statements, "(tmp= #{randomVecKey} (h2o.runif #{frameKey} -1))"
+
+      for part, i in splits
+        g = if i isnt 0 then "(>= #{randomVecKey} #{part.min})" else null
+
+        l = if i isnt splits.length - 1 then "(< #{randomVecKey} #{part.max})" else null
+
+        sliceExpr = if g and l
+          "(& #{g} #{l})"
+        else if l
+          l
+        else
+          g
+
+        push statements, "(tmp= #{part.key} (rows #{frameKey} #{sliceExpr}))"
+
+      push statements, "(rm #{randomVecKey})"
+
+      _.requestExec "(, #{statements.join ' '})", (error, result) ->
         if error
           go error
         else
-          splitFrames = for part, i in splits
-            g = if i isnt 0 then "(>= #{randomVecKey} #{part.min})" else null
-
-            l = if i isnt splits.length - 1 then "(< #{randomVecKey} #{part.max})" else null
-
-            sliceExpr = if g and l
-              "(& #{g} #{l})"
-            else if l
-              l
-            else
-              g
-
-            "(tmp= #{part.key} (rows #{frameKey} #{sliceExpr}))"
-
-          futures = map splitFrames, (expr) ->
-            _fork _.requestExec, expr
-
-          Flow.Async.join futures, (error, results) ->
-            if error
-              go error
-            else
-              _.requestDeleteFrame randomVecKey, (error, result) ->
-                if error
-                  go error
-                else
-                  go null, extendSplitFrameResult
-                    keys: splitKeys
-                    ratios: splitRatios
+          go null, extendSplitFrameResult
+            keys: splitKeys
+            ratios: splitRatios

     else
       go new Flow.Error 'The number of split ratios should be one less than the number of split keys'


refactor media comment show action in coffeescript

refs CNVS-19909

While investigating this ticket I refactored this coffeescript
for readability.

Change-Id: Ifea5ec3cd230229c7b2fab3fc587d416dfae89bc
Reviewed-on: https://gerrit.instructure.com/53393
Tested-by: Jenkins
Reviewed-by: Josh Simpson <bfb4987b134a00f73d881fff5113b1d5fa1876bc@instructure.com>
Product-Review: Strand McCutchen <3926e7f2370f1e7814576250688185c598b36b91@instructure.com>
QA-Review: Strand McCutchen <3926e7f2370f1e7814576250688185c598b36b91@instructure.com>

--- app/coffeescripts/jquery/mediaComment.coffee
+++ app/coffeescripts/jquery/mediaComment.coffee
@@ -76,7 +76,6 @@

   # enable the playback speed selector
   mejs.MepDefaults.features.splice(positionAfterSubtitleSelector, 0, 'speed')
-

   getSourcesAndTracks = (id) ->
     dfd = new $.Deferred
@@ -166,16 +165,23 @@

     show: (id, mediaType) ->
       $this = $(this)
+
       if dialog = $this.data('media_comment_dialog')
         dialog.dialog('open')
       else
         # Create a dialog box
-        spaceNeededForControls = 35
         mediaType = mediaType || 'video'
-        height = if mediaType is'video' then 426 else 180
-        width = if mediaType is 'video' then VIDEO_WIDTH else 400
+
+        if mediaType is 'video'
+          height = 426
+          width  = VIDEO_WIDTH
+        else
+          height = 180
+          width  = 400
+
         $dialog = $('<div style="overflow: hidden; padding: 0;" />')
         $dialog.css('padding-top', '120px') if mediaType is 'audio'
+
         $dialog.dialog
           title: I18n.t('titles.play_comment', "Play Media Comment")
           width: width
@@ -192,6 +198,7 @@
               mediaCommentId: id
               googleAnalyticsTitle: id

+            spaceNeededForControls = 35
             $mediaTag = createMediaTag({sourcesAndTracks, mediaPlayerOptions, mediaType, height: height-spaceNeededForControls, width})
             $mediaTag.appendTo($dialog.html(''))


Use `@command` in TreeView

Use `@command` in TreeView

--- src/extensions/tree-view/tree-view.coffee
+++ src/extensions/tree-view/tree-view.coffee
@@ -48,22 +48,22 @@
   initialize: (@rootView) ->
     super
     @on 'click', '.entry', (e) => @entryClicked(e)
-    @on 'core:move-up', => @moveUp()
-    @on 'core:move-down', => @moveDown()
-    @on 'tree-view:expand-directory', => @expandDirectory()
-    @on 'tree-view:collapse-directory', => @collapseDirectory()
-    @on 'tree-view:open-selected-entry', => @openSelectedEntry(true)
-    @on 'tree-view:move', => @moveSelectedEntry()
-    @on 'tree-view:add', => @add()
-    @on 'tree-view:remove', => @removeSelectedEntry()
-    @on 'tree-view:directory-modified', =>
+    @command 'core:move-up', => @moveUp()
+    @command 'core:move-down', => @moveDown()
+    @command 'tree-view:expand-directory', => @expandDirectory()
+    @command 'tree-view:collapse-directory', => @collapseDirectory()
+    @command 'tree-view:open-selected-entry', => @openSelectedEntry(true)
+    @command 'tree-view:move', => @moveSelectedEntry()
+    @command 'tree-view:add', => @add()
+    @command 'tree-view:remove', => @removeSelectedEntry()
+    @command 'tree-view:directory-modified', =>
       if @hasFocus()
         @selectEntryForPath(@selectedPath) if @selectedPath
       else
         @selectActiveFile()
     @on 'tree-view:unfocus', => @rootView.focus()
-    @rootView.on 'tree-view:toggle', => @toggle()
-    @rootView.on 'tree-view:reveal-active-file', => @revealActiveFile()
+    @rootView.command 'tree-view:toggle', => @toggle()
+    @rootView.command 'tree-view:reveal-active-file', => @revealActiveFile()
     @rootView.on 'active-editor-path-change', => @selectActiveFile()
     @rootView.project.on 'path-change', => @updateRoot()


Basic, crude word filter.

Basic, crude word filter.

--- scripts/moderation.coffee
+++ scripts/moderation.coffee
@@ -10,3 +10,9 @@
             msg.send "/timeout #{msg.envelope.user.name} 1"
             msg.send "Watch it, #{msg.envelope.user.name}! You need permission to post links."
             return
+
+    robot.hear /(nigger*\w*|nigga*\w*|cunt*\w*|fag*\w*|queer*\w*|twat*\w*)/i, (msg) ->
+        unless robot.auth.hasRole(msg.envelope.user, ['admin'])
+            msg.send "/timeout #{msg.envelope.user.name} 1"
+            msg.send "#{msg.envelope.user.name}! That kind of language is not acceptable."
+            return


Refactor writeFile and readFile to use isBinary better

--- scripts/gh-book/app.coffee
+++ scripts/gh-book/app.coffee
@@ -69,11 +69,12 @@

     # Github read/write and repo configuration

-    writeFile = (path, text, commitText) ->
-      session.getBranch().write path, text, commitText
+    writeFile = (path, text, commitText, isBinary) ->
+      if isBinary
+        text = btoa(text)
+      session.getBranch().write path, text, commitText, isBinary

-    readFile =       (path) -> session.getBranch().read       path
-    readBinaryFile = (path) -> session.getBranch().read       path, true # isBinary == true
+    readFile = (path, isBinary) -> session.getBranch().read path, isBinary
     readDir =        (path) -> session.getBranch().contents   path

@@ -84,17 +85,13 @@
       console.log method, path
       ret = null
       switch method
-        when 'read'
-          if model.isBinary
-            ret = readBinaryFile(path)
-          else
-            ret = readFile(path)
-        when 'update' then ret = writeFile(path, model.serialize(), 'Editor Save')
+        when 'read' then ret = readFile(path, model.isBinary)
+        when 'update' then ret = writeFile(path, model.serialize(), 'Editor Save', model.isBinary)
         when 'create'
           # Create an id if this model has not been saved yet
           id = _uuid()
           model.set 'id', id
-          ret = writeFile(path, model.serialize())
+          ret = writeFile(path, model.serialize(), model.isBinary)
         else throw "Model sync method not supported: #{method}"

       ret.done (value) => options?.success?(value)