Split large test into several smaller

Split large test into several smaller

--- src/oneToMany.coffee
+++ src/oneToMany.coffee
@@ -23,100 +23,183 @@

-    it "should provide has-one-relations", (done) ->
-      api = manikin.create()
-      model =
-
-        accounts:
-          defaultSort: 'name'
-          fields:
-            email: 'string'
-
-        questions:
-          owners: account: 'accounts'
-          defaultSort: 'order'
-          fields:
-            text: 'string'
-
-        devices:
-          owners: account: 'accounts'
-          fields:
-            name: 'string'
-
-        answers:
-          owners: question: 'questions'
-          fields:
-            option: 'number'
-            device:
-              type: 'hasOne'
-              model: 'devices'
-
-      saved = {}
-      promise(api).connect(connectionData, model, noErr())
-      .post('accounts', { email: 'some@email.com' }, noErr (account) ->
-        saved.account = account
-      ).then('post', -> @ 'questions', { text: 'q1', account: saved.account.id }, noErr (question) ->
-        saved.q1 = question
-      ).then('post', -> @ 'questions', { text: 'q2', account: saved.account.id }, noErr (question) ->
-        saved.q2 = question
-      ).then('post', -> @ 'devices', { name: 'd1', account: saved.account.id }, noErr (device) ->
-        saved.d1 = device
-      ).then('post', -> @ 'devices', { name: 'd1', account: saved.account.id }, noErr (device) ->
-        saved.d2 = device
-
-      # Can set it to a deviceID
-      ).then('post', -> @ 'answers', { option: 1, question: saved.q1.id, device: saved.d1.id }, noErr (answer) ->
-        answer.device.should.eql saved.d1.id
-        saved.a1 = answer
-
-      #Can set it to null
-      ).then('post', -> @ 'answers', { option: 1, question: saved.q1.id, device: null }, noErr (answer) ->
-        should.not.exist answer.device
-        saved.a2 = answer
-
-      # Can update it to null
-      ).then('putOne', -> @ 'answers', { device: null }, { id: saved.a1.id }, noErr (answer) ->
-        should.not.exist answer.device
-
-      # Can update it to another device
-      ).then('putOne', -> @ 'answers', { device: saved.d2.id }, { id: saved.a1.id }, noErr (answer) ->
-        answer.device.should.eql saved.d2.id
-
-      # If the devices is deleted, the answers device is set to null
-      ).then('delOne', -> @ 'devices', { id: saved.d2.id }, noErr () ->
-        (1).should.eql 1
-      ).then('getOne', -> @ 'answers', { filter: { id: saved.a1.id } }, noErr (answer) ->
-        should.not.exist answer.device
-
-      # Can't update it to something that is not a key of the correct type
-      ).then('putOne', -> @ 'answers', { device: saved.a2.id }, { id: saved.a1.id }, (err, answer) ->
-        err.should.eql new Error()
-        err.toString().should.eql "Error: Invalid hasOne-key for 'device'"
-        should.not.exist answer
-
-      ).then(done)
-
-
-    it "should be possible to add objects even when their hasOnes-collections are empty", (done) ->
-      api = manikin.create()
-      model =
-        bananas:
-          owners: {}
-          fields:
-            color: 'string'
-
-        monkeys:
-          owners: {}
-          fields:
-            name: 'string'
-            banana:
-              type: 'hasOne'
-              model: 'bananas'
-
-      saved = {}
-      promise(api).connect(connectionData, model, noErr())
-      .then('post', -> @ 'monkeys', { name: 'george' }, noErr (monkey) ->
-        saved.monkey = monkey
-      ).then('list', -> @ 'monkeys', {}, noErr (monkeys) ->
-        monkeys.length.should.eql 1
-      ).then(done)
+
+
+
+    it "should be possible to save hasOne-values", (done) ->
+      api = manikin.create()
+      model =
+
+        devices:
+          fields:
+            name: 'string'
+
+        answers:
+          fields:
+            option: 'number'
+            device:
+              type: 'hasOne'
+              model: 'devices'
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'devices', { name: 'd1' }, noErr (device) ->
+          api.post 'answers', { option: 1, device: device.id }, noErr (answer) ->
+            answer.device.should.eql device.id
+            api.close(done)
+
+
+
+    it "can initialize a hasOne to null", (done) ->
+      api = manikin.create()
+      model =
+
+        devices:
+          fields:
+            name: 'string'
+
+        answers:
+          fields:
+            option: 'number'
+            device:
+              type: 'hasOne'
+              model: 'devices'
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'devices', { name: 'd1' }, noErr (device) ->
+          api.post 'answers', { option: 1, device: null }, noErr (answer) ->
+            should.not.exist answer.device
+            api.close(done)
+
+
+
+    it "can overwrite a hasOne with null", (done) ->
+      api = manikin.create()
+      model =
+
+        devices:
+          fields:
+            name: 'string'
+
+        answers:
+          fields:
+            option: 'number'
+            device:
+              type: 'hasOne'
+              model: 'devices'
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'devices', { name: 'd1' }, noErr (device) ->
+          api.post 'answers', { option: 1, device: device.id }, noErr (answer) ->
+            api.putOne 'answers', { device: null }, answer, noErr (answer2) ->
+              should.not.exist answer2.device
+              api.close(done)
+
+
+
+    it "can update it to another key", (done) ->
+      api = manikin.create()
+      model =
+
+        devices:
+          fields:
+            name: 'string'
+
+        answers:
+          fields:
+            option: 'number'
+            device:
+              type: 'hasOne'
+              model: 'devices'
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'devices', { name: 'd1' }, noErr (device) ->
+          api.post 'devices', { name: 'd2' }, noErr (device2) ->
+            api.post 'answers', { option: 1, device: device.id }, noErr (answer) ->
+              api.putOne 'answers', { device: device2.id }, answer, noErr (answer2) ->
+                answer2.device.should.eql device2.id
+                api.close(done)
+
+
+
+    it "resets the relation to null if the entry pointed to is deleted", (done) ->
+      api = manikin.create()
+      model =
+
+        devices:
+          fields:
+            name: 'string'
+
+        answers:
+          fields:
+            option: 'number'
+            device:
+              type: 'hasOne'
+              model: 'devices'
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'devices', { name: 'd1' }, noErr (device) ->
+          api.post 'answers', { option: 1, device: device.id }, noErr (answer) ->
+            api.delOne 'devices', device, noErr ->
+              api.getOne 'answers', { id: answer.id }, noErr (answer) ->
+                should.not.exist answer.device
+                api.close(done)
+
+
+
+    it "cant update it to something that is not a key", (done) ->
+      api = manikin.create()
+      model =
+
+        devices:
+          fields:
+            name: 'string'
+
+        answers:
+          fields:
+            option: 'number'
+            device:
+              type: 'hasOne'
+              model: 'devices'
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'devices', { name: 'd1' }, noErr (device) ->
+          api.post 'answers', { option: 1, device: device.id }, noErr (answer) ->
+            api.putOne 'answers', { device: 'hellow-there' }, answer, (err, answer2) ->
+              err.should.eql new Error()
+              err.toString().should.eql "Error: Invalid hasOne-key for 'device'"
+              should.not.exist answer2
+              api.close(done)
+
+
+
+    it "cant update it to an id of an entry belonging to another collection", (done) ->
+      api = manikin.create()
+      model =
+
+        devices:
+          fields:
+            name: 'string'
+
+        answers:
+          fields:
+            option: 'number'
+            device:
+              type: 'hasOne'
+              model: 'devices'
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'devices', { name: 'd1' }, noErr (device) ->
+          api.post 'answers', { option: 1, device: device.id }, noErr (answer) ->
+            api.post 'answers', { option: 1, device: device.id }, noErr (answer2) ->
+              api.putOne 'answers', { device: answer2.id }, answer, (err, answer3) ->
+                err.should.eql new Error()
+                err.toString().should.eql "Error: Invalid hasOne-key for 'device'"
+                should.not.exist answer3
+                api.close(done)


typos and misc mini stuff

typos and misc mini stuff

--- src/organizer.coffee
+++ src/organizer.coffee
@@ -1,4 +1,3 @@
-#coffee      = require 'coffee-script'
 fs          = require 'fs'
 path        = require 'path'
 detective   = require 'detective'
@@ -13,6 +12,7 @@
     return false

 # criteria for whether a require string is relative, rather than absolute
+# absolute require strings will scan on the defined require paths (@domainPaths)
 isRelative = (reqStr) -> reqStr[0...2] is './'

 # convert relative requires to absolute ones
@@ -66,14 +66,12 @@

   circularCheck = (treePos, dep) -> # makes sure no circular references exists for dep going up from current point in tree (tree starts at top)
     requiree = treePos.name
+    chain = [dep]
     loop
-      #console.log "circularCheck for", dep, treePos.name, !!treePos.parent #, dep, treePos.parent
       return if treePos.parent is undefined # got all the way to @basePoint without finding self => good
+      chain.push treePos.name
       treePos = treePos.parent # follow the chain up
-
-      if treePos.name is dep
-        uncircularize(tree) # so that node is able to console.log it (cant log a circular structure)
-        throw new Error("circular dependency detected : #{treePos.name} <- .. <- #{requiree} <- #{treePos.name}")
+      throw new Error("circular dependency detected: #{chain.join(' <- ')} <- #{dep}") if treePos.name is dep
     return

   ((treePos) =>
@@ -140,11 +138,11 @@
       arguments.callee(dep)
     return
   )(@tree) # creates an object of arrays of form [level, domain], so key,val of obj => val = [level, domain]
-  ([key,val] for key,val of obj).sort((a,b) -> b[1][1] - a[1][1]).map((e) -> [e[0], e[1][1]]) # returns arrray of pairs where pair = [name, domain]
+  ([key,val] for key,val of obj).sort((a,b) -> b[1][1] - a[1][1]).map((e) -> [e[0], e[1][1]]) # returns array of pairs where pair = [name, domain]
 Organizer::writeCodeTree = (target) ->

-# require gives a function which returns a closured object with access to only the public methods
+# requiring this gives a function which returns a closured object with access to only the public methods of a bound instance
 # TODO: include some strings to ignore [e.g. stuff from internal that require will handle outside default behaviour]
 module.exports = (basePoint, domains, useLocalTests=false) ->
   throw new Error("brownie organizer: basePoint required") if !basePoint
@@ -156,7 +154,7 @@

-# test everything up to this point
+# tests
 if module is require.main
   clientPath = '/home/clux/repos/deathmatchjs/app/client/'
   sharedPath = '/home/clux/repos/deathmatchjs/app/shared/'


Use new Model in PaneAxis

Use new Model in PaneAxis

--- src/pane-axis.coffee
+++ src/pane-axis.coffee
@@ -1,7 +1,7 @@
-{Model} = require 'theorist'
 {Emitter, CompositeDisposable} = require 'event-kit'
 {flatten} = require 'underscore-plus'
 Serializable = require 'serializable'
+Model = require './model'

 module.exports =
 class PaneAxis extends Model


recaptcha.coffe is modified.

recaptcha.coffe is modified.

--- src/recaptcha.coffee
+++ src/recaptcha.coffee
@@ -14,7 +14,10 @@
         self = @

         # a method name called when recaptcha api script will be loaded
-        onloadMethod    = "onRecaptchaApiLoaded"
+        _onloadMethod    = "onRecaptchaApiLoaded"
+
+        # delete global grecapthca or not
+        _deleteGrecaptha = off

         @setParameters = (params)->
             _parameters = params
@@ -29,7 +32,7 @@
             scriptTag.type = 'text/javascript';
             scriptTag.async = true;
             scriptTag.defer = true;
-            scriptTag.src = "//www.google.com/recaptcha/api.js?onload=#{onloadMethod}&render=explicit" + if _languageCode then "&h1#{_languageCode}" else ""
+            scriptTag.src = "//www.google.com/recaptcha/api.js?onload=#{_onloadMethod}&render=explicit" + if _languageCode then "&h1#{_languageCode}" else ""

             s = $document[0].querySelector('body')
             s.appendChild(scriptTag);
@@ -38,18 +41,13 @@
         this.$get = ($document, $q, $window, $rootScope)->
             'ngInject'
             {
-                init: ->
-                    getAndFree = ->
-                        _grecaptcha = $window.grecaptcha
-                        $window.grecaptcha = undefined
-                        return
-
-                    getAndFree() if $window.grecaptcha
+                init : ->
+                    _grecaptcha = $window.grecaptcha if $window.grecaptcha
                     return $q.resolve _grecaptcha if _grecaptcha

                     promise = $q (resolve, reject)->
-                        $window[onloadMethod] = ->
-                            getAndFree()
+                        $window[_onloadMethod] = ->
+                            _grecaptcha = $window.grecaptcha
                             $rootScope.$apply ->
                                 resolve _grecaptcha
                             return
@@ -59,7 +57,7 @@

                     promise

-                render: (element, params, onSuccess, onExpire)->
+                render : (element, params, onSuccess, onExpire)->
                     _params = angular.extend {}, _parameters, params

                     if not _params.sitekey
@@ -73,21 +71,24 @@

                     _grecaptcha.render element, _params

-                getParameters: ->
+                getParameters : ->
                     _parameters

-                getGrecaptcha: ->
+                getGrecaptcha : ->
                     _grecaptcha

-                getLanguageCode: ->
+                getLanguageCode : ->
                     _languageCode
-
-                setLanguageCode: (languageCode)->
+
+                setLanguageCode : (languageCode)->
                     if _languageCode isnt languageCode
                         _grecaptcha = undefined
                     return
+
+                setParameters : (param)->
+                    angular.extend _parameters, param
+                    return
             }
-
         return

     app = angular.module('grecaptcha', [])


coffeescript isn't rendering this as you intend for it to. If you include the parentheses the result will be:

var _ref;
if ((_ref = this.get('method')) === 'PUT' || _ref === 'POST') {
  options.contentType = this.get('contentType');
}

instead of:

if (this.get('method' === 'PUT' || 'method' === 'POST')) {
  options.contentType = this.get('contentType');
}

--- src/batman.jquery.coffee
+++ src/batman.jquery.coffee
@@ -36,7 +36,7 @@
       @loading no
       @loaded yes

-  if @get 'method' in ['PUT', 'POST']
+  if @get('method') in ['PUT', 'POST']
     options.contentType = @get 'contentType'

   jQuery.ajax options


clean up

clean up

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -109,7 +109,7 @@
       return

     @getLastVersion: (resp) ->
-      chrome.storage.sync.get M.STORE.LAST_VERSION, (items) =>
+      chrome.storage.sync.get M.STORE.LAST_VERSION, (items) ->
         resp items[M.STORE.LAST_VERSION]
         return
       return


fix concat (get it from fn)

fix concat (get it from fn)

--- src/buffer-stream.coffee
+++ src/buffer-stream.coffee
@@ -96,7 +96,7 @@
         if @buffer.length is 0
             @buffer = buffer
         else
-            @buffer = concat(@buffer, buffer)
+            @buffer = fn.concat(@buffer, buffer)

         if @size is 'flexible'
             if @enabled


undo busted encoding (update still not working)

--- lib/pegg-parse.coffee
+++ lib/pegg-parse.coffee
@@ -127,12 +127,12 @@
         card.deck = JSON.parse(categories)?[0]
       if card.content?.length > 0
         console.log "TODO: implement update"
-        @updateCard card
+#        @updateCard card
       else
         @createCard card
           .then (result) =>
             log "card created: ", @pretty result
-            @updatePost postId, JSON.stringify(entities.encode(result))
+            @updatePost postId, JSON.stringify result
             @incrementDeck card.deck

   fetchPostData: (postId) =>
@@ -145,17 +145,12 @@
       post.choices = []
       post.post = postId
       post.question = entities.decode result.title.rendered
-      content = result.content.raw or result.content.rendered.replace(/<(?:.|\n)*?>/gm, '')
-      unless _.isEmpty(content)
-        parsedContent = JSON.parse(entities.decode(content))
-        post.id = parsedContent.cardId
+      post.content = result.content.raw or result.content.rendered.replace(/<(?:.|\n)*?>/gm, '')
       for i in [1..4]
         choice = {}
         choice.gifUrl = result["gif#{i}"]
         choice.text = result["answer#{i}"]
         choice.num = i
-        unless _.isEmpty(content)
-          choice.id = _.find parsedContent.choices, num: i
         post.choices.push choice
       console.log JSON.stringify post
       return post


MainTab: substitute `document.body.scrollTop` with `window.scrollY` [#80403198]

--- client/Core/maintabpaneview.coffee
+++ client/Core/maintabpaneview.coffee
@@ -11,18 +11,13 @@

     super

-    KD.utils.defer =>
-      {body, documentElement}   = document
-      documentElement.scrollTop = @lastScrollTops.window
-      body.scrollTop            = @lastScrollTops.body
+    KD.utils.defer => window.scrollTo 0, @lastScrollTops.window

   hide: ->

     return  unless @active

-    {body, documentElement} = document
-    @lastScrollTops.window  = documentElement.scrollTop
-    @lastScrollTops.body    = body.scrollTop
+    @lastScrollTops.window = window.scrollY

-    super+    super


Webterm: when oskite dies, reconnect to it

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -81,6 +81,55 @@

     @setKeyView()

+  generateOptions:->
+    delegateOptions = @getDelegate().getOptions()
+    myOptions       = @getOptions()
+
+    params =
+      remote      : @terminal.clientInterface
+      sizeX       : @terminal.sizeX
+      sizeY       : @terminal.sizeY
+      joinUser    : myOptions.joinUser  ? delegateOptions.joinUser
+      session     : myOptions.session   ? delegateOptions.session ? @sessionId
+      mode        : myOptions.mode      ? 'create'
+
+  getVMName:->
+    delegateOptions = @getDelegate().getOptions()
+    myOptions       = @getOptions()
+    return myOptions.vmName or delegateOptions.vmName
+
+  webtermConnect:(mode)->
+    return console.info "reconnection is in progrees" if @reconnectionInPrgress
+    @reconnectionInPrgress = yes
+    options = @generateOptions()
+    options.mode = mode   if mode
+    KD.getSingleton("vmController").run
+      method        : "webterm.connect",
+      vmName        : @getVMName()
+      withArgs      : options
+    , (err, remote) =>
+      if err
+        warn err
+        if err.code is "ErrInvalidSession"
+          @reconnectionInPrgress = false
+          @emit 'TerminalCanceled',
+            vmName: @getVMName()
+            sessionId: @getOptions().session
+            error: err
+          return
+        else
+          @reconnectionInPrgress = false
+          throw err
+
+      @setOption "session", remote.session
+      @terminal.eventHandler = (data)=> @emit "WebTermEvent", data
+      @terminal.server       = remote
+      @sessionId = remote.session
+
+      @emit "WebTermConnected", remote
+      console.error "just connected"  if mode is "resume"
+      @reconnectionInPrgress = false
+
   connectToTerminal: ->
     @appStorage = KD.getSingleton('appStorageController').storage 'Terminal', '1.0.1'
     @appStorage.fetchStorage =>
@@ -91,42 +140,21 @@
       @appStorage.setValue 'scrollback', 1000 if not @appStorage.getValue('scrollback')?
       @updateSettings()

-      delegateOptions = @getDelegate().getOptions()
-      myOptions       = @getOptions()
-
-      params =
-        remote      : @terminal.clientInterface
-        sizeX       : @terminal.sizeX
-        sizeY       : @terminal.sizeY
-        joinUser    : myOptions.joinUser  ? delegateOptions.joinUser
-        session     : myOptions.session   ? delegateOptions.session
-        mode        : myOptions.mode      ? 'create'
-
-      KD.getSingleton("vmController").run
-        method        : "webterm.connect",
-        vmName        : myOptions.vmName or delegateOptions.vmName
-        withArgs      : params
-      , (err, remote) =>
-        if err
-          warn err
-
-          if err.code is "ErrInvalidSession"
-            @emit 'TerminalCanceled',
-              vmName: myOptions.vmName
-              sessionId: myOptions.session
-              error: err
-            return
-          else throw err
-
-        @terminal.eventHandler = (data)=> @emit "WebTermEvent", data
-        @terminal.server       = remote
-        @sessionId = remote.session
-        @emit "WebTermConnected", remote
-
-    KD.getSingleton("status").on "reconnected", =>
-      @handleReconnect yes
+      @webtermConnect()
+
+      KD.getSingleton("vmController").getKite @getVMName(), (kite)=>
+        kite.on 'destroy', =>
+          console.error "Couldn't connect to your VM. Trying to reconnect...(err:oskite)"
+          @webtermConnect("resume")
+
+      KD.kite.mq.on "broker.error", (err)=>
+        console.error "Couldn't connect to your VM. Trying to reconnect...(err:broker)"
+        if err.code is 404
+          KD.getSingleton("vmController").getKite @getVMName(), (kite)=>
+            @webtermConnect("resume")

     KD.getSingleton("kiteController").on "KiteError", (err) =>
+      console.log "kite errr", err
       @reconnected = no
       {code, serviceGenericName} = err


update brain

update brain

--- core/brain.coffee
+++ core/brain.coffee
@@ -41,7 +41,7 @@

     receive : (msgObject)->
         if msgObject.message instanceof Mew.Message.TextMessage
-            @mew.logger.debug "Received Text Message #{JSON.stringify(msgObject)} "
+            #@mew.logger.debug "Received Text Message #{JSON.stringify(msgObject)} "
             matchPart = @ruleManager.getTextMatchPart msgObject.message.text
             if matchPart
                 for ruleKey of @ruleManager.mewTextListenerPool


Implement D3Line::render

Implement D3Line::render

--- src/chart-d3-line.coffee
+++ src/chart-d3-line.coffee
@@ -29,4 +29,24 @@
     data = @generateData(labels, datasets)
     return this if _.isEmpty(data)

+    options = @options
+    strokeWidth = @options.barStrokeWidth
+
+    chartWidth = @width
+    chartHeight = @height
+
+    x0Scale = D3Line.xScale(labels, chartWidth)
+    x1Scale = D3Line.xScale(d3.range(datasets.length), x0Scale.rangeBand())
+    yScale = D3Line.yScale(data, chartHeight)
+
+    @renderXGrid(x0Scale, chartHeight)
+    @renderYGrid(yScale, chartWidth)
+    @renderGrid()
+    @renderXAxis(x0Scale, chartHeight)
+    @renderYAxis(yScale)
+
+    @updateGridTickStyle(options)
+    @updateScaleStrokeStyle(options)
+    @updateScaleTextStyle(options)
+
     this


упростил apiэ

упростил apiэ


Add optional "help" parameter for Handlebars form helper "wrap" fn

--- client/source/helpers/forms.coffee
+++ client/source/helpers/forms.coffee
@@ -7,8 +7,12 @@
     className: 'control-label'
   , text

-wrap = (content, label) ->
-  content = createLabel(label) + content if label
+wrap = (content, options={}) ->
+  _.defaults options,
+    label: null
+    help: null
+  content = createLabel(options.label) + content if options.label
+  content += tag 'p', {className: 'help-block'}, options.help if options.help
   tag 'div', class: 'form-group', content

 tag = (el, attrs={}, content='', options={}) ->
@@ -41,7 +45,9 @@
     _.extend params,
       'data-sluggify': options.hash.sluggify

-  wrap tag('input', params, false, selfClosing: true), settings.label
+  wrap tag('input', params, false, selfClosing: true),
+    label: settings.label
+    help: settings.help

 Handlebars.registerHelper 'textarea', (name, value, options) ->
   settings = options.hash
@@ -56,7 +62,7 @@
   , value

   if settings.label
-    wrap textarea, settings.label
+    wrap textarea, label: settings.label
   else
     textarea

@@ -90,7 +96,7 @@
   cb = tag 'input', params
   cb = tag 'label', {}, cb + " #{label}" if label

-  wrap cb
+  wrap cb, options.hash.help

 Handlebars.registerHelper 'select', (name, value, selectOptions, options) ->

@@ -117,4 +123,6 @@
     className: settings.className
     tabindex: settings.tabindex
     name: name
-  , optionEls.join ''), settings.label
+  , optionEls.join ''),
+    label: settings.label
+    help: settings.help


Updated cothml-parser (constructor)

Updated cothml-parser (constructor)

--- src/cohtml-parser.coffee
+++ src/cohtml-parser.coffee
@@ -5,12 +5,20 @@

 class CohtmlParser extends GenericParser

-  constructor: ->
-    super
-
-
-
-
-
+  constructor: (inputString, optionMap = {})->
+    super inputString
+    { indentCharacter } = optionMap
+    @indentCharacter = indentCharacter or '  '
+    @charset =
+      tag: CohtmlParser.CommonTokens.urlSafeBase64
+      id: CohtmlParser.CommonTokens.urlSafeBase64
+      class: CohtmlParser.CommonTokens.urlSafeBase64
+      attribute: CohtmlParser.CommonTokens.urlSafeBase64
+      ws: CohtmlParser.CommonTokens.whitespace
+      equal: '='
+      dquote: '"'
+      pipe: '|'
+      backtick: '`'
+      newline: CohtmlParser.CommonTokens.newline

 @CohtmlParser = CohtmlParser


Provide onDetails callback for preview

Provide onDetails callback for preview

--- src/components/demo.cjsx
+++ src/components/demo.cjsx
@@ -80,6 +80,7 @@
     displayFeedback: false
     displayFormats:  false
     displayTags: false
+    isSelected: false

   toggleFeedbackDisplay: (ev) ->
     @setState(displayFeedback: not @state.displayFeedback)
@@ -89,6 +90,12 @@

   toggleTagsDisplay: (ev) ->
     @setState(displayTags: not @state.TagsFormats)
+
+  onSelection: ->
+    @setState(isSelected: not @state.isSelected)
+
+  onDetailsClick: (ev, exercise) ->
+    console.warn "Exercise details was clicked"

   render: ->
     {displayFeedback, displayFormats, displayTags} = @state
@@ -106,8 +113,11 @@
       'fa-square-o':  not displayTags

     <ExercisePreview exercise={exercisePreviewStub}
+      onSelection={@onSelection}
+      onDetailsClick={@onDetailsClick}
       displayFormats={displayFormats}
       displayAllTags={displayTags}
+      isSelected={@state.isSelected}
       displayFeedback={displayFeedback}
     >
       <button className="toggle" onClick={@toggleFeedbackDisplay}>
@@ -222,9 +232,9 @@
   displayName: 'Demo'
   render: ->
     demos =
+      exercisePreview: <ExercisePreviewDemo/>
       notices: <NoticesDemo />
       exercise: <ExerciseDemo/>
-      exercisePreview: <ExercisePreviewDemo/>
       breadcrumbs: <BreadcrumbDemo/>
       html: <HTMLDemo/>


:lipstick: Reorder rows methods

:lipstick: Reorder rows methods

--- lib/table-view.coffee
+++ lib/table-view.coffee
@@ -213,6 +213,27 @@

     @findRowAtPosition(@body.scrollTop() + scrollViewHeight) ? @table.getRowsCount() - 1

+  getScreenRows: -> @screenRows
+
+  getScreenRow: (row) -> @table.getRow(@screenRowToModelRow(row))
+
+  getScreenRowHeightAt: (row) -> @getRowHeightAt(@screenRowToModelRow(row))
+
+  setScreenRowHeightAt: (row, height) ->
+    @setRowHeightAt(@screenRowToModelRow(row), height)
+
+  getScreenRowOffsetAt: (row) -> @rowOffsets[row]
+
+  getRowsContainer: ->
+    @rowsContainer ?= @body.find('.table-edit-rows')
+
+  getRowResizeRuler: ->
+    @rowResizeRuler ?= @body.find('.row-resize-ruler')
+
+  screenRowToModelRow: (row) -> @screenToModelRowsMap[row]
+
+  modelRowToScreenRow: (row) -> @modelToScreenRowsMap[row]
+
   makeRowVisible: (row) ->
     rowHeight = @getScreenRowHeightAt(row)
     scrollViewHeight = @body.height()
@@ -230,27 +251,6 @@
       @body.scrollTop(scrollTopAsLastVisibleRow)
     else
       @body.scrollTop(scrollTopAsFirstVisibleRow)
-
-  getScreenRows: -> @screenRows
-
-  getScreenRow: (row) -> @table.getRow(@screenRowToModelRow(row))
-
-  getScreenRowHeightAt: (row) -> @getRowHeightAt(@screenRowToModelRow(row))
-
-  setScreenRowHeightAt: (row, height) ->
-    @setRowHeightAt(@screenRowToModelRow(row), height)
-
-  getScreenRowOffsetAt: (row) -> @rowOffsets[row]
-
-  screenRowToModelRow: (row) -> @screenToModelRowsMap[row]
-
-  modelRowToScreenRow: (row) -> @modelToScreenRowsMap[row]
-
-  getRowsContainer: ->
-    @rowsContainer ?= @body.find('.table-edit-rows')
-
-  getRowResizeRuler: ->
-    @rowResizeRuler ?= @body.find('.row-resize-ruler')

   computeRowOffsets: ->
     offsets = []


Reuse patterns

Reuse patterns

--- lib/tag-finder.coffee
+++ lib/tag-finder.coffee
@@ -8,6 +8,8 @@
   constructor: (@editorView) ->
     {@editor} = @editorView

+    @tagPattern = /(<(\/?))([^\s>]+)([\s>]|$)/
+    @wordRegex = /[^>]*/
     @tagSelector = new ScopeSelector('meta.tag | punctuation.definition.tag')
     @commentSelector = new ScopeSelector('comment.*')

@@ -44,6 +46,7 @@
     unpairedCount = 0
     @editor.scanInBufferRange @getTagPattern(tagName), scanRange, ({match, range, stop}) =>
       return if @isRangeCommented(range)
+
       if match[1]
         unpairedCount++
       else
@@ -58,8 +61,8 @@
     return unless @isCursorOnTag()

     ranges = null
-    endPosition = @editor.getCursor().getCurrentWordBufferRange(wordRegex: /[^>]*/).end
-    @editor.backwardsScanInBufferRange /(<(\/?))([^\s>]+)([\s>]|$)/, [[0, 0], endPosition], ({match, range, stop}) =>
+    endPosition = @editor.getCursor().getCurrentWordBufferRange({@wordRegex}).end
+    @editor.backwardsScanInBufferRange @tagPattern, [[0, 0], endPosition], ({match, range, stop}) =>
       stop()

       [entireMatch, prefix, isClosingTag, tagName, suffix] = match


Blow up when trying to use tooltips

Blow up when trying to use tooltips

--- src/space-pen.coffee
+++ src/space-pen.coffee
@@ -426,6 +426,19 @@
 $.Event.prototype.currentTargetView = -> $(@currentTarget).containingView()
 $.Event.prototype.targetView = -> $(@target).containingView()

+$.fn.setTooltip = ->
+  throw new Error """
+    setTooltip is no longer available. Please use `atom.tooltips.add` instead.
+    See the docs at https://atom.io/docs/api/latest/TooltipManager#instance-add
+  """
+
+$.fn.destroyTooltip = $.fn.hideTooltip = ->
+  throw new Error """
+    destroyTooltip is no longer available. Please dispose the object returned
+    from  `atom.tooltips.add` instead.
+    See the docs at https://atom.io/docs/api/latest/TooltipManager#instance-add
+  """
+
 # Exports

 exports = exports ? this


rtmp: Add comments

rtmp: Add comments

--- rtmp.coffee
+++ rtmp.coffee
@@ -8,29 +8,39 @@
 SESSION_STATE_HANDSHAKE_ONGOING = 2
 SESSION_STATE_HANDSHAKE_DONE = 3

+# name of the stream
 STREAM_APP_NAME = 'live'

 # media information
-AUDIO_DATA_RATE = 32  # kbps
+AUDIO_SAMPLE_RATE = 22050  # Hz
+AUDIO_DATA_RATE = 32   # kbps
+VIDEO_DATA_RATE = 500  # kbps
 VIDEO_FRAME_RATE = 30
 GOP_SIZE = 60
-AUDIO_SAMPLE_RATE = 22050
-WIDTH = 480
-HEIGHT = 360
-VIDEO_DATA_RATE = 500  # kbps
+WIDTH = 480   # px
+HEIGHT = 360  # px

 VIDEO_CODEC_ID = 7   # H.264
 AUDIO_CODEC_ID = 10  # AAC

 PLAY_CHUNK_SIZE = 4096

-# How many RTMP messages are queued before sending
+# number ot RTMP messages to be queued before being sent
 QUEUE_RTMP_MESSAGES = 5

+# maximum timestamp value + 1
 TIMESTAMP_ROUNDOFF = 4294967296  # 32 bits

+# when this period of time has passed after the last ping,
+# the client is regarded as gone
 PING_TIMEOUT = 5000  # ms
+
+# when this period of time has passed after the last RTMP message,
+# the session will be abandoned
 SESSION_TIMEOUT = 60000  # ms
+
+# when this period of time has passed after the last RTMPT message,
+# the session will be abandoned
 RTMPT_SESSION_TIMEOUT = 60000  # ms

 sessionsCount = 0


Now having fun with redis config

Now having fun with redis config

--- config/initializers/pub_sub.coffee
+++ config/initializers/pub_sub.coffee
@@ -2,16 +2,16 @@
 redback = require 'redback'
 betturl = require 'betturl'

-Caboose.app.config.redis = betturl.parse(process.env.REDIS_URL || {})
+config = betturl.parse(process.env.REDIS_URL || {})

 Caboose.app.channels = {
-  command: redback.createClient(Caboose.app.config.redis.host, Caboose.app.redis.config.post).createChannel('commands')
+  command: redback.createClient(config.host, config.post).createChannel('commands')
 }

 return unless Caboose.command is 'server'

 channels = {
-  command: redback.createClient(Caboose.app.config.redis.host, Caboose.app.redis.config.post).createChannel('commands').subscribe()
+  command: redback.createClient(config.host, config.post).createChannel('commands').subscribe()
 }

 _(channels).each (channel, type) ->


formatting

formatting

--- source/infrastructure/repository.coffee
+++ source/infrastructure/repository.coffee
@@ -29,6 +29,7 @@
       events: aggregate.getEvents?() ? []
       commands: aggregate.getCommands?() ? []
     expectedVersion ?= aggregate.getVersion()
+
     @commitStore.add changes, aggregate.getId(), expectedVersion

   useSnapshotter: (@_snapshotter) ->


Only select entry nodes

Only select entry nodes

This ensure the resizer or any other non-entry
nodes are never selected when moving up or down.

--- src/tree-view.coffee
+++ src/tree-view.coffee
@@ -184,7 +184,7 @@
     if selectedEntry
       if selectedEntry.is('.expanded.directory')
         return if @selectEntry(selectedEntry.find('.entry:first'))
-      until @selectEntry(selectedEntry.next())
+      until @selectEntry(selectedEntry.next('.entry'))
         selectedEntry = selectedEntry.parents('.entry:first')
         break unless selectedEntry.length
     else
@@ -195,7 +195,7 @@
   moveUp: ->
     selectedEntry = @selectedEntry()
     if selectedEntry
-      if previousEntry = @selectEntry(selectedEntry.prev())
+      if previousEntry = @selectEntry(selectedEntry.prev('.entry'))
         if previousEntry.is('.expanded.directory')
           @selectEntry(previousEntry.find('.entry:last'))
       else


Add pane and pane item observer methods to workspace. Also document.

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -89,6 +89,43 @@

   editorAdded: (editor) ->
     @emit 'editor-created', editor
+
+  # Public: Invoke the given callback when a pane is added to the workspace.
+  #
+  # * `callback` {Function} to be called panes are added.
+  #   * `event` {Object} with the following keys:
+  #     * `pane` The added pane.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+  onDidAddPane: (callback) -> @paneContainer.onDidAddPane(callback)
+
+  # Public: Invoke the given callback with all current and future panes in the
+  # workspace.
+  #
+  # * `callback` {Function} to be called with current and future panes.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+  observePanes: (callback) -> @paneContainer.observePanes(callback)
+
+  # Public: Invoke the given callback when a pane item is added to the
+  # workspace.
+  #
+  # * `callback` {Function} to be called panes are added.
+  #   * `event` {Object} with the following keys:
+  #     * `item` The added pane item.
+  #     * `pane` {Pane} containing the added item.
+  #     * `index` {Number} indicating the index of the added item in its pane.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+  onDidAddPaneItem: (callback) -> @paneContainer.onDidAddPaneItem(callback)
+
+  # Public: Invoke the given callback with all current and future panes items in
+  # the workspace.
+  #
+  # * `callback` {Function} to be called with current and future pane items.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+  observePaneItems: (callback) -> @paneContainer.observePaneItems(callback)

   # Public: Register a function to be called for every current and future
   # {Editor} in the workspace.


minor turning DEBUG_STRINGIFICATION_CHECKS off

--- fizzylogo_globals.coffee
+++ fizzylogo_globals.coffee
@@ -20,7 +20,7 @@

 outerMostContext = null

-DEBUG_STRINGIFICATION_CHECKS = true
+DEBUG_STRINGIFICATION_CHECKS = false

 if DEBUG_STRINGIFICATION_CHECKS
   stringsTable_TO_CHECK_CONVERTIONS = {}


Set default location in case geolocation fails

--- routes.coffee
+++ routes.coffee
@@ -10,6 +10,8 @@
     @response.json @session.location

   @get '/journeys': ->
+    if not @session.location
+      @session.location = { latitude: 51.518190, longitude: -0.178099 }
     lat = @query.lat or @session.location.latitude
     lon = @query.lon or @session.location.longitude
     models.journey.find({ location : { $near : [lat,lon] } })


Timeout is 10 seconds.

Timeout is 10 seconds.

--- functions/helpers.coffee
+++ functions/helpers.coffee
@@ -12,7 +12,7 @@

   ajaxRequest: (url, cb) ->
-    $.ajax({url: url, timeout: 20000})
+    $.ajax({url: url, timeout: 10000})
       .fail (err) ->
         console.log err
       .done (body) ->


Build app now runs in parallel with downloading electron and flash.

--- build_detail.coffee
+++ build_detail.coffee
@@ -38,6 +38,7 @@

 # If !use_taobao_mirror, download Electron from GitHub.
 use_taobao_mirror = config.get 'buildscript.useTaobaoMirror', true
+log "Download electron from #{if use_taobao_mirror then 'taobao mirror' else 'github'}"
 npm_exec_path = path.join __dirname, 'node_modules', 'npm', 'bin', 'npm-cli.js'
 bower_exec_path = path.join __dirname, 'node_modules', 'bower', 'bin', 'bower'

@@ -306,7 +307,7 @@
   release_path

 packageStage3Async = async (platform, arch, poi_version, electron_version,
-            download_dir, app_path, building_root, release_dir) ->
+            download_dir, building_root, release_dir) ->
   platform_arch = "#{platform}-#{arch}"
   poi_fullname = "poi-v#{poi_version}-#{platform_arch}"
   stage3_electron = path.join building_root, poi_fullname
@@ -317,14 +318,13 @@
     yield fs.removeAsync stage3_electron
   catch e

-  copy_app = fs.copyAsync app_path, stage3_app
   install_flash = installFlashAsync platform, arch, download_dir, flash_dir

   electron_url = get_electron_url platform, arch, electron_version
   install_electron = downloadExtractZipAsync electron_url, download_dir, '',
       stage3_electron, 'electron'

-  yield Promise.join copy_app, install_flash, install_electron
+  yield Promise.join install_flash, install_electron

   if platform == 'win32'
     raw_poi_exe = path.join(building_root, "#{platform_arch}.raw.poi.exe")
@@ -336,6 +336,7 @@
       path.join(stage3_electron, 'poi.exe'),
       clobber: true
     Promise.resolve
+      app_path: stage3_app
       log: " To complete packaging #{platform}-#{arch}, you need to:\n
             (1) Modify #{raw_poi_exe} and save as #{platform_arch}.poi.exe by\n
             ...(a) changing its icon into poi\n
@@ -348,13 +349,13 @@
         log "#{platform}-#{arch} successfully packaged to #{release_path}."

   else if platform == 'linux'
-
     yield fs.moveAsync path.join(stage3_electron, 'electron'),
       path.join(stage3_electron, 'poi'),
       clobber: true
     package_release = packageReleaseAsync poi_fullname, stage3_electron,
       release_dir
     Promise.resolve
+      app_path: stage3_app
       log: null
       todo: async ->
         release_path = yield package_release
@@ -445,14 +446,21 @@

   return if !checkNpmVersion()

-  app_path = yield packageAppAsync poi_version, building_root, release_dir
-  return if !electron_version?
+  app_path_promise = packageAppAsync poi_version, building_root, release_dir
+  if !electron_version?
+    yield app_path_promise
+    return

   # Stage3: Package each platform
   stage3_info = yield Promise.all (
     for [platform, arch] in platform_arch_list
       packageStage3Async(platform, arch, poi_version, electron_version,
-        download_dir, app_path, building_root, release_dir))
+        download_dir, building_root, release_dir))
+
+  # Copy app
+  for info in stage3_info
+    if info.app_path?
+      fs.copySync (yield app_path_promise), info.app_path

   # Finishing work of stage 3
   stage3_logs = (for [[platform, arch], info] in _.zip(


trigger import events

trigger import events

--- src/menu/views/ImportMenu.coffee
+++ src/menu/views/ImportMenu.coffee
@@ -14,6 +14,7 @@
     @addNode "FASTA",(e) =>
       url = prompt "URL", "/test/dummy/samples/p53.clustalo.fasta"
       url = corsURL url, @g
+      @g.trigger "import:fasta:url", url
       FastaReader.read url, (seqs) =>
         # mass update on zoomer
         zoomer = @g.zoomer.toJSON()
@@ -30,6 +31,7 @@
     @addNode "CLUSTAL", =>
       url = prompt "URL", "/test/dummy/samples/p53.clustalo.clustal"
       url = corsURL url, @g
+      @g.trigger "import:clustal:url", url
       Clustal.read url, (seqs) =>
         zoomer = @g.zoomer.toJSON()
         #zoomer.textVisible = false


improved dead games removal

improved dead games removal

--- server.coffee
+++ server.coffee
@@ -44,7 +44,7 @@
     socket.gameid = false
     lobby.emit('netrunner', {type: "games", games: games})
   for k, v of games
-    delete games[k] if v.players.length is 1 and not v.started and (new Date() - v.date) > 3600000
+    delete games[k] if (v.players.length + v.spectators.length) is 1 and not v.started and (new Date() - v.date) > 1800000

 joinGame = (socket, gameid) ->
   game = games[gameid]


change default port

change default port

--- server.coffee
+++ server.coffee
@@ -3,6 +3,6 @@
 app = module.exports = require('railway').createServer()

 if not module.parent
-    port = process.env.PORT or 8002
+    port = process.env.PORT or 9202
     app.listen port, "127.0.0.1"
     console.log "Railway server listening on port %d within %s environment", port, app.settings.env


Don't use the favicon so it runs.

Don't use the favicon so it runs.

--- server.coffee
+++ server.coffee
@@ -126,7 +126,6 @@

 app.set('port', portNumber)
-app.use(favicon(__dirname + '/client/favicon.png'))
 app.use(require('morgan')('combined'))
 app.use(bodyParser.json())
 app.use(require('method-override')())
@@ -140,7 +139,6 @@
 }))
 app.use(passport.initialize())
 app.use(passport.session())
-app.use(express.static(__dirname + '/client'))

 #
 # Update how these are set to use app.set('development', stuff);


fix(grunt): disable packaging under travis builds

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -326,10 +326,10 @@
   ciTasks.push('mkdeb') if process.platform is 'linux'
   ciTasks.push('test') if process.platform is 'darwin'
   ciTasks.push('codesign') unless process.env.TRAVIS
-  ciTasks.push('mkdmg') if process.platform is 'darwin'
-  ciTasks.push('create-windows-installer') if process.platform is 'win32'
-  # ciTasks.push('publish-docs') if process.platform is 'darwin'
-  ciTasks.push('publish-nylas-build') if process.platform is 'darwin'
+  ciTasks.push('mkdmg') if process.platform is 'darwin' and not process.env.TRAVIS
+  ciTasks.push('create-windows-installer') if process.platform is 'win32' and not process.env.TRAVIS
+  # ciTasks.push('publish-docs') if process.platform is 'darwin' and not process.env.TRAVIS
+  ciTasks.push('publish-nylas-build') if process.platform is 'darwin' and not process.env.TRAVIS
   grunt.registerTask('ci', ciTasks)

   defaultTasks = ['download-electron', 'build', 'set-version', 'generate-asar']


Run spec on darwin only

Run spec on darwin only

...as we're not ready yet to use Linux.

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -226,7 +226,7 @@
   ciTasks.push('set-version', 'check-licenses', 'lint')
   ciTasks.push('mkdeb') if process.platform is 'linux'
   ciTasks.push('create-windows-installer') if process.platform is 'win32'
-  ciTasks.push('test') if process.platform in ['darwin', 'linux']
+  ciTasks.push('test') if process.platform is 'darwin'
   ciTasks.push('codesign') unless process.env.TRAVIS
   ciTasks.push('publish-build') unless process.env.TRAVIS
   grunt.registerTask('ci', ciTasks)


[Cartilage.View] Fixed issues with automatic CSS class name detection.

--- app/assets/javascripts/cartilage/views/view.js.coffee
+++ app/assets/javascripts/cartilage/views/view.js.coffee
@@ -8,21 +8,23 @@
   subviews: []
   observers: []

-  className: ->
-    [ "view", _.dasherize(@constructor.name) ].join(" ")
-
   template: (options) ->
     JST[_.underscore(@constructor.name)](options)

   render: ->
-    templateOptions = {}

+    # Determine CSS class names from the view class hierarchy and any other
+    # custom classes defined by a subclass.
+
+    # Automatically assign collections and models to the template, as their
+    # respective class names.
+    templateVariables = {}
     if @collection
-      templateOptions[_.camelize(@collection.constructor.name)] = @collection
+      templateVariables[_.camelize(@collection.constructor.name)] = @collection
     if @model
-      templateOptions[_.camelize(@model.constructor.name)] = @model
+      templateVariables[_.camelize(@model.constructor.name)] = @model
+    ($ @el).html @template(templateVariables)

-    ($ @el).html @template(templateOptions)
     @

   cleanup: ->
@@ -115,3 +117,32 @@

   removeFromSuperviewAnimated: ->
     @removeFromSuperview(true)
+
+  determineClassName: ->
+    classNames = [ _.dasherize(@constructor.name) ]
+
+    # Get class names for each view in the class heirarchy
+    superklass = @constructor.__super__
+    while superklass
+      classNames.push _.dasherize(superklass.constructor.name)
+      superklass = superklass.__super__
+
+    # Reverse the order of the class names
+    classNames = classNames.reverse()
+
+    # Merge in custom class name(s)
+    if _.isFunction(@className)
+      console.log "Merging Classes:", @className
+      classNames.push @className().split(" ")
+    else if @className
+      console.log "Merging Classes:", @className
+      classNames.push @className.split(" ")
+
+    # Flatten the Array
+    classNames = _.flatten(classNames)
+
+    # Remove any duplicate class names
+    classNames = _.unique(classNames)
+
+    # Convert the class names array and override the @className property
+    @className = classNames.join(" ")


OX-322 split VolumeCell.getVoxelArray()

OX-322 split VolumeCell.getVoxelArray()

--- app/assets/javascripts/oxalis/model/volumecell.coffee
+++ app/assets/javascripts/oxalis/model/volumecell.coffee
@@ -15,9 +15,8 @@

   getVoxelArray : ->

-    res = []
     if @layers.length == 0
-      return res
+      return []

     # Get cuboid of possible voxels
     minCoord = @layers[0].minCoord.slice()
@@ -27,6 +26,11 @@
         minCoord[i] = Math.min(minCoord[i], @layers[0].minCoord[i])
         maxCoord[i] = Math.max(maxCoord[i], @layers[0].maxCoord[i])

+    return @getVoxelArrayForCuboid(minCoord, maxCoord)
+
+  getVoxelArrayForCuboid : (minCoord, maxCoord) ->
+
+    res = []
     # Check every voxel in this cuboid
     for x in [minCoord[0]..maxCoord[0]]
       for y in [minCoord[1]..maxCoord[1]]


fix wrong logging function

fix wrong logging function

--- app/assets/javascripts/uploadcare/files/object.coffee
+++ app/assets/javascripts/uploadcare/files/object.coffee
@@ -224,7 +224,7 @@
               attempts += 1
               if attempts > @settings.multipartMaxAttempts
                 if @settings.debugUploads
-                  utils.info("Part ##{partNo} and file upload is failed.", uuid)
+                  utils.log("Part ##{partNo} and file upload is failed.", uuid)
                 df.reject()
               else
                 if @settings.debugUploads


Fix URL parsing for mediaCommentThumbnail

Fixes CNVS-25238

Test Plan:
* Upload a media file to the Files tab
* Unpublish or lock the media file
* Add the file to a page or other RCE-editable text area where
  previews are available
* View the page, etc. as a student
* The media previews should not be visible or playable in any browser,
  including IE 11 and Edge

Change-Id: I6487317273927bd66cd456027bc4c48048c86dad
Reviewed-on: https://gerrit.instructure.com/68737
Tested-by: Jenkins
Reviewed-by: Sterling Cobb <7a57d889ea7f266299434459898a90c7f6a1ca4d@instructure.com>
QA-Review: Ryan Allen <b65b638b3333142073650bd017e5f36609bed2c6@instructure.com>
Product-Review: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>

--- app/coffeescripts/jquery/mediaCommentThumbnail.coffee
+++ app/coffeescripts/jquery/mediaCommentThumbnail.coffee
@@ -15,7 +15,9 @@

     $link = $(elem)
     try
-      url = new URL($link.attr('href'))
+      a = document.createElement('a');
+      a.href = $link.attr('href')
+      url = a
     return if url && deparam(url.search).no_preview

     dimensions = MEDIA_COMMENT_THUMBNAIL_SIZES[size] ? MEDIA_COMMENT_THUMBNAIL_SIZES.normal


Fixing the youtube plugin, wasn't reading videos with _ or - in the id.

--- src/plugins/youtube_watch.coffee
+++ src/plugins/youtube_watch.coffee
@@ -1,8 +1,5 @@
 TwitterApi = require "ntwitter"
 dateFormat = require('dateformat');
-
-long_video_url = /\/?watch.*?/
-short_video_url =

 class Plugin
   constructor: (@bot, @config) ->
@@ -29,7 +26,7 @@
   videoDetailsLong: (channel, who, message, url) =>
     {path, query} = url
     # https://www.youtube.com/watch?v=HU2ftCitvyQ
-    if path.match(/^\/watch/) && (match = query.match /v=([A-Za-z0-9]+)/)
+    if path.match(/^\/watch/) && (match = query.match /v=([A-Za-z0-9_-]+)/)
       [_, video_id] = match
       console.log video_id
       @videoDetails channel, who, message, video_id
@@ -37,7 +34,7 @@
   videoDetailsShort: (channel, who, message, url) =>
     {path} = url
     # http://youtu.be/HU2ftCitvyQ
-    if match = path.match /\/?([A-Za-z0-9]+)/
+    if match = path.match /\/?([A-Za-z0-9_-]+)/
       [_, video_id] = match
       @videoDetails channel, who, message, video_id


fix(common): prevent the ghost click issue for iOS.

iOS sucks just like Internet Explorer did back in my age.

--- brasilcomvc/common/static/coffee/common-header.coffee
+++ brasilcomvc/common/static/coffee/common-header.coffee
@@ -1,7 +1,18 @@
 body = $(document.body)

-$(document).on 'touchend', (e) ->
+_prevent_ghost_click = (e) ->
+	e.preventDefault()
+	setTimeout(
+		-> body.off 'touchend', _prevent_ghost_click,
+		370)
+
+$(document).on 'touchstart', (e) ->
 	in_nav = !!$(e.target).closest('#main-header .main-nav').length
+
 	if not in_nav and body.hasClass('header-focus')
 		e.preventDefault()
+
+	if in_nav and not body.hasClass('header-focus')
+		body.on 'touchend', _prevent_ghost_click
+
 	body.toggleClass 'header-focus', in_nav


Drop batchedUpdates on input since we're batching in the model anyway

--- src/editor-scroll-view-component.coffee
+++ src/editor-scroll-view-component.coffee
@@ -1,5 +1,4 @@
 React = require 'react'
-ReactUpdates = require 'react/lib/ReactUpdates'
 {div} = require 'reactionary'

 InputComponent = require './input-component'
@@ -40,8 +39,11 @@
   onInput: (char, replaceLastCharacter) ->
     {editor} = @props

-    ReactUpdates.batchedUpdates ->
-      editor.selectLeft() if replaceLastCharacter
+    if replaceLastCharacter
+      editor.transact ->
+        editor.selectLeft()
+        editor.insertText(char)
+    else
       editor.insertText(char)

   onMouseDown: (event) ->


Delete eventric.spec.coffee

Delete eventric.spec.coffee

--- src/eventric.spec.coffee
+++ src/eventric.spec.coffee
@@ -1,47 +0,0 @@
-describe.skip 'Index', ->
-
-  describe '#context', ->
-    contextInstance = null
-    contextStub = null
-
-    beforeEach ->
-      contextInstance =
-        subscribeToAllDomainEvents: sandbox.stub()
-      contextStub = sandbox.stub().returns contextInstance
-
-      mockery.registerMock './context', contextStub
-
-
-    it 'should throw an error if no name given for the context', ->
-      expect(-> new eventric.context).to.throw Error
-
-
-    it 'should create a context instance', ->
-      someContext = eventric.context 'someContext'
-      expect(contextStub).to.have.been.calledWithNew
-
-
-    it 'should register global domain event handlers on the context', ->
-      someContext = eventric.context 'someContext'
-      expect(contextInstance.subscribeToAllDomainEvents).to.have.been.called
-
-
-  describe '#set/#get', ->
-
-    it 'should save given key/value pairs', ->
-      key = Math.random()
-      value = Math.random()
-      eventric.set key, value
-      expect(eventric.get key).to.equal value
-
-
-    it 'should return undefined for a not set key', ->
-      key = Math.random()
-      expect(eventric.get key).to.not.exist
-
-
-    it 'should overwrite already defined values', ->
-      key = Math.random()
-      eventric.set key, '1'
-      eventric.set key, '2'
-      expect(eventric.get key).to.equal '2'


insights: Timeperiod downloader

insights: Timeperiod downloader

--- src/insights-download-timeperiod.coffee
+++ src/insights-download-timeperiod.coffee
@@ -2,7 +2,17 @@
 '2017-03-01'

 fs = require 'fs'
-{ spawn } = require 'child_process'
+async = require 'async'
+{ exec } = require 'child_process'
+
+# inclusive. [start, end]
+daysInPeriod = (start, end) ->
+  days = [ ]
+  current = new Date start
+  while current < end
+    days.push current
+    current = new Date(current.setDate(current.getDate() + 1))
+  return days

 haveData = (file, callback) ->
   fs.exists file, (exists) ->
@@ -12,8 +22,68 @@
       hasData = stat.size > 1
       return callback null, hasData

+dateOnlyString = (date) ->
+  return date.toISOString().split('T')[0]
+
+# never returns Error, instead puts them as .error in returned data
+executeDay = (day, args, callback) ->
+  date = dateOnlyString day
+  file = "#{args.prefix}#{date}#{args.suffix}"
+  haveData file, (err, alreadyDone) ->
+    return callback null, { file: file, error: err, status: 'failed when checking file'  } if err
+    if alreadyDone
+      console.log 'SKIP', file
+      return callback null, { file: file, status: 'skipped, already done' }
+    else
+      cmd = args.command.replace '#DATE', date
+      cmd += " > #{file}"
+      options =
+        shell: true
+      console.log '$', cmd
+      exec cmd, options, (err, stdout, stderr) ->
+        status = if err then "FAILED, #{stderr}" else 'SUCCESS'
+        console.log '!', status
+        return callback null, { file: file, error: err, stdout: stdout, stderr: stderr, status: status }
+
+# returns array of an object describing. If has .error, then was a failure
+executeOverDays = (args, callback) ->
+  days = daysInPeriod args.start, args.end
+
+  async.mapSeries days, (d, cb) ->
+    executeDay d, args, cb
+  , callback
+
+parse = (args) ->
+  program = require 'commander'
+  program
+    .option('--prefix <PREFIX>', 'Prefix for all files', String, '')
+    .option('--suffix <SUFFIX>', 'Suffic for all files', String, '')
+    .option('--command <COMMAND>', 'Command to execute for each day.', String, '')
+    .option('--start <DATE>', 'Start time of period to run', String, '')
+    .option('--end <DATE>', 'End time of period to run', String, 'now')
+    .parse(args)
+
+normalize = (args) ->
+  throw new Error "--command must be specified" if not args.command
+  throw new Error "--start must be specified" if not args.start
+  throw new Error "--end must be specified" if not args.end
+
+  args.start = new Date args.start
+  args.end = new Date args.end
+
+  return args
+
 main = () ->
-  haveData 'the-grid-api-march18.json', (err, data) ->
-    console.log 'err', err, data
+  #haveData 'the-grid-api-march18.json', (err, data) ->
+  #  console.log 'err', err, data
+  args = parse process.argv
+  args = normalize args
+
+  executeOverDays args, (err, results) ->
+    failures = results.filter (r) -> r.error
+    exitCode = failures.length
+    if exitCode
+      console.log failures
+    process.exit exitCode

 main() if not module.parent


app-settings-menu: Added appsettingsmenubutton to includes.

--- client/includes.coffee
+++ client/includes.coffee
@@ -324,6 +324,7 @@
   # "app/MainApp/bottompanels/chat/chatuseritem.coffee",
   # "app/MainApp/bottompanels/terminal/terminalpanel.coffee",

+  "app/MainApp/maincontroller/mainviews/appsettingsmenubutton.coffee",
   "app/MainApp/maincontroller/mainviews/mainview.coffee",
   "app/MainApp/maincontroller/mainviews/contentpanel.coffee",
   "app/MainApp/maincontroller/mainviewcontroller.coffee",


Don't lose inputs when focusing to send button

--- client/inputbox.coffee
+++ client/inputbox.coffee
@@ -11,6 +11,10 @@
     Session.set('inputFocused', true)
   'blur #message': (evt, template) ->
     Session.set('inputFocused', false)
+  'focus #btnSend': (evt, template) ->
+    Session.set('inputFocused', true)
+  'blur #btnSend': (evt, template) ->
+    Session.set('inputFocused', false)
   'click #btnSend': sendMessage

 Template.inputbox.helpers


increase the postgres query cache

increase the postgres query cache

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -42,10 +42,10 @@
             connect      : true
             password     : undefined
             pool         : undefined   # IGNORED for now.
-            cache_expiry : 2000  # expire cached queries after this many milliseconds
+            cache_expiry : 30000  # expire cached queries after this many milliseconds
                                  # keep this very short; it's just meant to reduce impact of a bunch of
                                  # identical permission checks in a single user query.
-            cache_size   : 100   # cache this many queries; use @_query(cache:true, ...) to cache result
+            cache_size   : 500   # cache this many queries; use @_query(cache:true, ...) to cache result
             concurrent_warn : 500
         @setMaxListeners(10000)  # because of a potentially large number of changefeeds
         @_state = 'init'


start raw server first; maybe get directory listing slightly more quickly (probably not)

--- src/smc-project/local_hub.coffee
+++ src/smc-project/local_hub.coffee
@@ -292,20 +292,6 @@
         (cb) ->
             init_info_json(cb)
         (cb) ->
-            # This is also written by forever; however, by writing it directly it's also possible
-            # to run the local_hub server in a console, which is useful for debugging and development.
-            fs.writeFile(misc_node.abspath("#{DATA}/local_hub.pid"), "#{process.pid}", cb)
-        (cb) ->
-            secret_token.init_secret_token (err, token) ->
-                if err
-                    cb(err)
-                else
-                    the_secret_token = token
-                    console_sessions.set_secret_token(token)
-                    cb()
-        (cb) ->
-            start_tcp_server(the_secret_token, tcp_port, cb)
-        (cb) ->
             raw_server.start_raw_server
                 project_id : INFO.project_id
                 base_url   : INFO.base_url
@@ -315,6 +301,20 @@
                 port       : raw_port
                 logger     : winston
                 cb         : cb
+        (cb) ->
+            # This is also written by forever; however, by writing it directly it's also possible
+            # to run the local_hub server in a console, which is useful for debugging and development.
+            fs.writeFile(misc_node.abspath("#{DATA}/local_hub.pid"), "#{process.pid}", cb)
+        (cb) ->
+            secret_token.init_secret_token (err, token) ->
+                if err
+                    cb(err)
+                else
+                    the_secret_token = token
+                    console_sessions.set_secret_token(token)
+                    cb()
+        (cb) ->
+            start_tcp_server(the_secret_token, tcp_port, cb)
     ], (err) ->
         if err
             winston.debug("ERROR starting server -- #{err}")


Fix component rerendering on any project change

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -137,6 +137,8 @@

     reduxProps :
         projects :
+            # get_total_project_quotas relys on this data
+            # Will be removed by #1084
             project_map : rtypes.immutable.Map
             get_total_project_quotas : rtypes.func
         "#{name}" :
@@ -145,6 +147,11 @@

     propTypes :
         project_id : rtypes.string
+
+    shouldComponentUpdate : (nextProps) ->
+        return @props.free_warning_extra_shown != nextProps.free_warning_extra_shown or
+            @props.free_warning_closed != nextProps.free_warning_closed or
+            @props.project_map?.get(@props.project_id).get('users') != nextProps.project_map?.get(@props.project_id).get('users')

     extra : (host, internet) ->
         {PolicyPricingPageUrl} = require('./customize')


Update example.

Update example.

--- examples/table.coffee
+++ examples/table.coffee
@@ -15,20 +15,44 @@

 Table = View
   collection: Users
-  template: -> '

'
+  template: -> '''
+

+

+

+

+  '''
   render: ->
-    for model in @collection
-      @el.appendChild (Row model).el
+    # Get column headers from properties of first model in collection
+    columns = (k for k,v of own @collection.first())
+    # Create a header using column
+    header = Header columns
+    # Bind to table
+    @bind 'table', header
+    # Bind rows to after header
+    rows = (Row model for model in @collection)
+    @bind('table').after header, rows
+
+Header = View
+  template: -> '

'
+  initialize: (@columns) ->
+  render: ->
+    @el.appendChild (Heading v) for v in @columns
+
+Heading = View
+  template: -> '	'
+  initialize: (@value) ->
+  render: ->
+    @el.innerHTML = @value

 Row = View
   model: User
   template: -> '

'
   render: ->
     for k,v of own @model
-      @el.appendChild (Cell @model, k)
+      @el.appendChild (Cell v)

 Cell = View
-  model: User
   template: -> '	'
+  initialize: (@value) ->
   render: ->
-    @el.innerHTML = @model[@column]
+    @el.innerHTML = @value


[#5815290] Fix error if last expression is the result of a square root

For some reason roots don't have `.last()` on their expression. There might be a
better fix.

--- src/math/expression_manipulation.coffee
+++ src/math/expression_manipulation.coffee
@@ -165,7 +165,7 @@
 class AppendDecimal extends ExpressionManipulation
   # @destructive
   doAppendD: (expression, expression_position)->
-    last = expression.last()
+    last = if expression.last? then expression.last()

     if typeof last == 'undefined'
       new_last = @comps.build_number(value: 0)


Update _menubar.coffee

Update _menubar.coffee

--- coffee/_menubar.coffee
+++ coffee/_menubar.coffee
@@ -16,7 +16,7 @@
 [
     { label: 'English', lang: 'en' },
     { label: '繁體中文', lang: 'zh-TW' },
-    { label: 'מְדִינַת יִשְׂרָאֵל', lang: 'he-IL' },
+    { label: 'עברית', lang: 'he-IL' },
     { label: 'Esperanto', lang: 'eo' },
     { label: 'Italiano', lang: 'it' },
     { label: 'slovenščina', lang: 'sl-SI' },


util: RegExp => RegEx

util: RegExp => RegEx

--- coffee/src/util.coffee
+++ coffee/src/util.coffee
@@ -6,11 +6,11 @@
   if not condition
     throw new Error(description)

-acorn.util.urlRegExp = (url) ->
+acorn.util.urlRegEx = (url) ->
   ///^(https?:\/\/)?#{url}$///

 acorn.util.isUrl = (url) ->
-  acorn.util.urlRegExp.test url
+  acorn.util.urlRegEx.test url

 # helpers to construct acorn urls TODO: delete these?
 acorn.util.url = ->


Show locale datetime string

Show locale datetime string


Default overdrawMargin to 15 even when passed as null from outside

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -107,7 +107,6 @@
   getDefaultProps: ->
     cursorBlinkPeriod: 800
     cursorBlinkResumeDelay: 100
-    lineOverdrawMargin: 15

   componentWillMount: ->
     @props.editor.manageScrollPosition = true
@@ -115,6 +114,7 @@
     @setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))

     {editor, lineOverdrawMargin, cursorBlinkPeriod, cursorBlinkResumeDelay}  = @props
+    lineOverdrawMargin ?= 15

     @presenter = new TextEditorPresenter
       model: editor


Simplify updating of custom gutter decoration state

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -30,7 +30,7 @@
     @characterWidthsByScope = {}
     @lineDecorationsByScreenRow = {}
     @lineNumberDecorationsByScreenRow = {}
-    @customGutterDecorationsByGutterNameAndScreenRow = {}
+    @customGutterDecorationsByGutterName = {}
     @screenRowsToMeasure = []
     @transferMeasurementsToModel()
     @transferMeasurementsFromModel()
@@ -625,16 +625,12 @@
         @customGutterDecorations[gutterName] = {}

       continue unless @gutterIsVisible(gutter)
-
-      if customGutterDecorationsByScreenRow = @customGutterDecorationsByGutterNameAndScreenRow[gutterName]
-        for screenRow in [@startRow..@endRow - 1]
-          if decorationsById = customGutterDecorationsByScreenRow[screenRow]
-            for decorationId, {properties, screenRange} of decorationsById
-              @customGutterDecorations[gutterName][decorationId] ?=
-                top: @lineHeight * screenRange.start.row
-                height: @lineHeight * screenRange.getRowCount()
-                item: properties.item
-                class: properties.class
+      for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName]
+        @customGutterDecorations[gutterName][decorationId] =
+          top: @lineHeight * screenRange.start.row
+          height: @lineHeight * screenRange.getRowCount()
+          item: properties.item
+          class: properties.class

   clearAllCustomGutterDecorations: ->
     allGutterNames = Object.keys(@customGutterDecorations)
@@ -1177,11 +1173,16 @@
   updateLineDecorations: ->
     @lineDecorationsByScreenRow = {}
     @lineNumberDecorationsByScreenRow = {}
-    @customGutterDecorationsByGutterNameAndScreenRow = {}
-
-    for decorationId, {properties, screenRange, rangeIsReversed} of @decorations
-      if Decoration.isType(properties, 'line') or Decoration.isType(properties, 'gutter')
+    @customGutterDecorationsByGutterName = {}
+
+    for decorationId, decorationState of @decorations
+      {properties, screenRange, rangeIsReversed} = decorationState
+      if Decoration.isType(properties, 'line') or Decoration.isType(properties, 'line-number')
         @addToLineDecorationCaches(decorationId, properties, screenRange, rangeIsReversed)
+
+      else if Decoration.isType(properties, 'gutter') and properties.gutterName?
+        @customGutterDecorationsByGutterName[properties.gutterName] ?= {}
+        @customGutterDecorationsByGutterName[properties.gutterName][decorationId] = decorationState

     return

@@ -1221,12 +1222,6 @@
       if Decoration.isType(properties, 'line-number')
         @lineNumberDecorationsByScreenRow[row] ?= {}
         @lineNumberDecorationsByScreenRow[row][decorationId] = properties
-
-      else if Decoration.isType(properties, 'gutter')
-        gutterName = properties.gutterName
-        @customGutterDecorationsByGutterNameAndScreenRow[gutterName] ?= {}
-        @customGutterDecorationsByGutterNameAndScreenRow[gutterName][row] ?= {}
-        @customGutterDecorationsByGutterNameAndScreenRow[gutterName][row][decorationId] = {properties, screenRange}

     return


body, ref-list title improvements

body, ref-list title improvements

--- src/textorum/tinymce/tree.coffee
+++ src/textorum/tinymce/tree.coffee
@@ -219,7 +219,13 @@
           .map((idx, ele) -> $(ele).text()).get().join(", ")
       when "title", "article-id" then $(node).text()
       when "journal-id", "issn", "publisher" then $(node).text()
-      when "sec", "ack", "ref-list" then $(node).children('[data-xmlel="title"]').text()
+      when "body"
+        nodecount = $(node).children('[data-xmlel="sec"]').length
+        "#{nodecount} section" + (if nodecount == 1 then "" else "s")
+      when "sec", "ack" then $(node).children('[data-xmlel="title"]').text()
+      when "ref-list"
+        nodecount = $(node).children('[data-xmlel="ref"]').length
+        $(node).children('[data-xmlel="title"]').text() + " - #{nodecount} reference" + (if nodecount == 1 then "" else "s")
       when "p" then $(node).text().substr(0, 20) + "..."
       when "table-wrap", "fig"
         jqnode = $(node)
@@ -262,6 +268,7 @@
         select_limit: 1
       core:
         animation: 0
+        html_titles: true
       contextmenu:
         select_node: true
         show_at_node: true


Removing unused event emitter

Removing unused event emitter

--- src/processors/job/job.processor.coffee
+++ src/processors/job/job.processor.coffee
@@ -2,9 +2,7 @@

 NotificationsApi = require("./notification.api")
 request = require("request-promise")
-Promise = require("bluebird")
 _ = require("lodash")
-EventEmitter = require("events").EventEmitter
 notificationsApi = null

 _getHeaders = (headers) ->
@@ -21,8 +19,6 @@
     headers: _getHeaders HeadersForRequest
   }, _.isUndefined

-emitter = new EventEmitter
-
 _notifySuccess = ({ message: { JobId }, statusCode }) -> notificationsApi.success JobId, statusCode
 _notifyFail = ({ message: { JobId }, statusCode, error }) -> notificationsApi.fail JobId, statusCode, error


rename --playerdetect to --detectplayer, --mungedetect -> --detectmunge, added --detect for controlling both player detection and munge detection

--- src/app.coffee
+++ src/app.coffee
@@ -39,20 +39,24 @@
 #######################
 # bootstrap

+if argv.detect?
+    argv.detectmunge = argv.detect
+    argv.detectplayer = argv.detect
+
 bootstrap = ->

     async.series [

         (callback) ->

-            if argv.mungedetect isnt 'false'
+            if argv.detectmunge isnt 'false'
                 MungeDetector.detect callback
             else
                 MungeDetector.initFromDatabase callback

         , (callback) ->

-            if argv.playerdetect isnt 'false'
+            if argv.detectplayer isnt 'false'
                 AccountInfo.fetch callback
             else
                 callback()


Connman is a function

Connman is a function

--- src/app.coffee
+++ src/app.coffee
@@ -1,4 +1,4 @@
-connman = require 'connman-simplified'
+connman = require('connman-simplified')()
 app = require('express')()
 bodyParser = require('body-parser')


stuff

stuff

--- src/leaf.iced
+++ src/leaf.iced
@@ -0,0 +1,69 @@
+
+
+#===========================================================
+
+exports.Triple = class Triple
+  constructor : ({@seqno, @payload_hash, @sig_id}) ->
+
+#--------------------------
+
+class LeafParser
+
+  constructor : (@val) ->
+
+  parse : () ->
+
+    if not Array.isArray(@val) or @val.length < 2
+      throw new Error "Expected an array of length 2 or more"
+    else if typeof(@val[0]) isnt 'number'
+      throw new Error "Need a number for first slot"
+    else if typeof(@val[1]) is 'string'
+      # We messed up and didn't version the initial leafs of the tree
+      version = 1
+    else
+      version = @val[0]
+
+    switch version
+      when 1 then @parse_v1()
+      when 2 then @parse_v2()
+      else throw new Error "unknown leaf version: #{version}"
+
+  parse_v1 : () ->
+    pub = @parse_triple @val
+    new Leaf { pub }
+
+  parse_v2 : () ->
+    if @val.length < 2 then throw new Error "No public chain"
+    pub = @parse_triple @val[1]
+    semipriv = if (@val.length > 2) and @val[2]?.length then @parse_triple(@val[2]) else null
+    return new Leaf { pub, semipriv }
+
+  match_hex : (s) ->
+    (typeof(s) is 'string') and !!(s.match(/^([a-fA-F0-9]*)$/)) and (s.length % 2 is 0)
+
+  parse_triple : (val) ->
+    msg = if (val.length < 2) then "Bad triple with < 2 values"
+    else if val.length > 3 then "Bad triple with > 3 values"
+    else if typeof(val[0]) isnt 'number' then "Bad sequence #"
+    else if not @match_hex(val[1]) then "bad value[1]"
+    else if val.length > 2 and val[2].length and not @match_hex(val[2]) then "bad value[2]"
+    else null
+    throw new Error msg if msg?
+    new Triple { seqno : val[0], payload_hash : val[1], sig_id : val[2] }
+
+#--------------------------
+
+exports.Leaf = class Leaf
+
+  constructor : ({@pub, @semipriv}) ->
+
+  get_public : () -> @pub
+
+  @parse: (version, val) ->
+    parser = new LeafParser version, val
+    err = leaf = null
+    try leaf = parser.parse()
+    catch e then err = e
+    [err, leaf]
+
+#===========================================================


delete unproductive comment

delete unproductive comment

--- client/entry.coffee
+++ client/entry.coffee
@@ -30,7 +30,6 @@
 class @T9NHelper

   @translate: (code) ->
-#    console.log "translate: #{code}"
     T9n.get code, "error.accounts"

   @accountsError: (err) ->


add base hero slogan to waypoint detector

--- client/index.coffee
+++ client/index.coffee
@@ -78,7 +78,8 @@

   $main_header = $('#main-header')

-  $('.card__heading,
+  $('.base-hero-content__slogan,
+     .card__heading,
      .character-selector__heading,
      .profile__avatar,
      .billboard__heading').waypoint


router fixed

router fixed

--- js/routers/router.coffee
+++ js/routers/router.coffee
@@ -6,11 +6,21 @@
       views.header.render()

   routes:
-    "": "home"
-    home: "home"
+    "": "destination",
+    "destination": "destination",
+    "route": "route",
+    "timeline": "timeline"

-  home: ->
-    require ["cs!views/map"], ->
-      views.map.render()
+  destination: ->
+    require ["cs!views/destination-map"], ->
+      views.destinationMap.render()
+
+  route: ->
+    require ["cs!views/route"], ->
+      views.route.render()
+
+  timeline: ->
+    require ["cs!views/timeline-map"], ->
+      views.timelineMap.render()

 return new Router


Make store mixins more flexible with functions

--- app/data/store.coffee
+++ app/data/store.coffee
@@ -26,20 +26,24 @@
       changeCallbackName = "updateStateWithStore_#{Math.random().toString().split('.')[1]}"

       getCurrentState = ->
-        state = {}
-
-        if typeof stateProperties is 'string'
-          state[stateProperties] = store
+        if typeof stateProperties is 'function'
+          state = stateProperties.call this

         else
-          for storeProperty, stateProperty of stateProperties
-            state[stateProperty] = store[storeProperty]
+          state = {}
+
+          if typeof stateProperties is 'string'
+            state[stateProperties] = store
+
+          else
+            for storeProperty, stateProperty of stateProperties
+              state[stateProperty] = store[storeProperty]

         state

       Mixin =
         getInitialState: ->
-          getCurrentState()
+          getCurrentState.call this

         componentDidMount: ->
           store.listen @[changeCallbackName]
@@ -48,7 +52,7 @@
           store.stopListening @[changeCallbackName]

       Mixin[changeCallbackName] = ->
-        @setState getCurrentState()
+        @setState getCurrentState.call this

       Mixin


Refactored showNotFound into notFoundView

--- app/lib/Router.coffee
+++ app/lib/Router.coffee
@@ -44,7 +44,7 @@
       return @openRoute(args.join('/'))
     view = new ViewClass({}, slugOrId)
     view.render()
-    if view then @openView(view) else @showNotFound()
+    @openView if view then view else @notFoundView()

   cache: {}
   openRoute: (route) ->
@@ -99,7 +99,7 @@
       return document.location.reload()
     path = "views/#{path}"
     ViewClass = @tryToLoadModule path
-    return @showNotFound() if not ViewClass
+    return @openView @notFoundView() if not ViewClass
     view = new ViewClass({}, args...)  # options, then any path fragment args
     view.render()
     @openView(view)
@@ -117,7 +117,7 @@
       break if ViewClass
       split -= 1

-    return @showNotFound() if not ViewClass
+    return @notFoundView() if not ViewClass
     args = pieces[split+1..]
     view = new ViewClass({}, args...)  # options, then any path fragment args
     view.render()
@@ -129,7 +129,7 @@
       if error.toString().search('Cannot find module "' + path + '" from') is -1
         throw error

-  showNotFound: ->
+  notFoundView: ->
     NotFoundView = require('views/not_found')
     view = new NotFoundView()
     view.render()


fixed doubling, also readded get doc

fixed doubling, also readded get doc

--- src/leaflet.pouch.coffee
+++ src/leaflet.pouch.coffee
@@ -48,18 +48,21 @@
 									cb(null, true) unless noOpt
 									cb("No Option") if noOpt
 							@sync()
-	addDoc: (doc, cb) ->
+	addDoc: (doc, cb = ()-> true) ->
 		if "type" of doc and doc.type == "Feature"
-			@localDB.post doc, cb or ()-> true unless "_id" of doc
-			@localDB.put doc, cb or ()-> true if "_id" of doc and doc._id.slice(0,8) != "_design/"
+			unless "_id" of doc
+				@localDB.post doc, cb
+			else if "_id" of doc and doc._id.slice(0,8) != "_design/"
+				@localDB.put doc, cb
 		else if "type" of doc and doc.type == "FeatureCollection"
-			@localDB.bulkDocs doc.features, cb or ()-> true
+			@localDB.bulkDocs doc.features, cb
 		else if doc.length
-			@localDB.bulkDocs doc, cb or ()-> true
-
+			@localDB.bulkDocs doc, cb
+	getDoc: (doc, cb = ()-> true) ->
+		@localDB.get id, cb

-	deleteDoc: (id, cb) ->
-		@localDB.get id, (err, doc) =>
+	deleteDoc: (id, cb = ()-> true) ->
+		@getDoc id, (err, doc) =>
 			@localDB.remove doc, cb unless err
 			cb("err") if err


changed default closing keyword from :End to :EndX

--- client/prefs.coffee
+++ client/prefs.coffee
@@ -10,7 +10,7 @@
 [ # name             default (type is determined from default value; setter enforces type and handles encoding)
   ['autoCloseBrackets', 1] # whether to insert {}[]() in pairs
   ['autoCloseBlocks',   1] # whether to insert :end after :if,:for,etc when Enter is pressed
-  ['autoCloseBlocksEnd',1] # 0: close blocks with ":EndIf",":EndFor",etc;  1: close blocks only with ":End"
+  ['autoCloseBlocksEnd',0] # 0: close blocks with ":EndIf",":EndFor",etc;  1: close blocks only with ":End"
   ['colourScheme',      'Default'] # name of the active colour scheme
   ['colourSchemes',     []] # objects describing user-defined colour schemes
   ['favs',              [host: '127.0.0.1', port: 4502]]


fix level 2 metrics

fix level 2 metrics

--- games/tutorial.coffee
+++ games/tutorial.coffee
@@ -35,17 +35,18 @@
       name: 'Click "Right" Program Brick to Remove It From "Run".'
       comment: '
       Introduce kids to the concept of an editable program buffer.
-      This is a gentle introduction to the art of debugging.
+      This is an example of debugging an existing program.
       '
-      goal: { clicks: 1, ticks: 6, bricks: 0 }
-      bricks: 1
+      goal: { clicks: 1, ticks: 6, bricks: 1 }
+      bricks: 2
       sprites: [
         { kind: 'gate', actions: {interrupt: ['_victory']}, position: [5,0] }
         { kind: 'turtle', actions: {run: ['forward', 'right']}, editable: false }
       ]
     }
+    # TODO: add a level explaining how/why to rearrange bricks
     {
-      name: 'Add "Forward" Brick to Run Program'
+      name: 'Click "Forward" Brick to Create A Run Program'
       comment: '
       Start using program bricks to create their own programs.
       Limit actions to 2 to avoid brute force non-run-loop solutions.


don't allow opinion_filter customization do be a function

--- @client/filter.coffee
+++ @client/filter.coffee
@@ -560,8 +560,6 @@
     return DIV null if !users.users
     filters_only_for_hosts = customization('opinion_filters_admin_only')
     custom_filters = customization 'opinion_filters'
-    if typeof(custom_filters) == 'function'
-      custom_filters = custom_filters()

     is_admin = fetch('/current_user').is_admin


fix address

fix address

--- @client/footer.coffee
+++ @client/footer.coffee
@@ -173,7 +173,7 @@
                     args:
                       style:
                         textDecoration: 'underline'
-                      href: 'mailto:hello@consider.it'
+                      href: 'mailto:help@consider.it'

                   "Report bugs to <link>help@consider.it</link>"


Better collision detection for diagonal movements.

--- coffee/scene.coffee
+++ coffee/scene.coffee
@@ -181,53 +181,54 @@
     @frameID = requestAnimationFrame(@render)
     @renderer.render(@scene, @camera)

+  translateCamera: (distance, isX) ->
+    # Try the movement and revert if it would move us into a block.
+    x = @camera.position.x
+    y = @camera.position.y
+    z = @camera.position.z
+
+    if isX
+      @camera.translateX(distance)
+    else
+      @camera.translateZ(distance)
+
+    # Revert if hitting a block.
+    if @isBlocked(x: @camera.position.x, y: @camera.position.y, z: @camera.position.z)
+      @camera.position.set(x, y, z)
+    else
+      # Don't let translations move up/down or go off the map
+      @camera.position.x = Math.min(@camera.position.x, @worldSize)
+      @camera.position.x = Math.max(@camera.position.x, -@worldSize)
+      @camera.position.y = y
+      @camera.position.z = Math.min(@camera.position.z, @worldSize)
+      @camera.position.z = Math.max(@camera.position.z, -@worldSize)
+
   move: =>
     step = 0.25
     diagStep = step / Math.sqrt(2)
     jumpStep = step / 2

-    posY = @camera.position.y
-
     switch @direction
       when Dir.S
-        unless @isBlocked(x: @camera.position.x, y: @camera.position.y, z: @camera.position.z - step)
-          @camera.translateZ(step)
+        @translateCamera(step, false)
       when Dir.SE
-        unless @isBlocked(x: @camera.position.x - diagStep, y: @camera.position.y, z: @camera.position.z)
-          @camera.translateX(diagStep)
-        unless @isBlocked(x: @camera.position.x, y: @camera.position.y, z: @camera.position.z - diagStep)
-          @camera.translateZ(diagStep)
+        @translateCamera(diagStep, true)
+        @translateCamera(diagStep, false)
       when Dir.W
-        unless @isBlocked(x: @camera.position.x + step, y: @camera.position.y, z: @camera.position.z)
-          @camera.translateX(-step)
+        @translateCamera(-step, true)
       when Dir.NW
-        unless @isBlocked(x: @camera.position.x + diagStep, y: @camera.position.y, z: @camera.position.z)
-          @camera.translateX(-diagStep)
-        unless @isBlocked(x: @camera.position.x, y: @camera.position.y, z: @camera.position.z + diagStep)
-          @camera.translateZ(-diagStep)
+        @translateCamera(-diagStep, true)
+        @translateCamera(-diagStep, false)
       when Dir.N
-        unless @isBlocked(x: @camera.position.x, y: @camera.position.y, z: @camera.position.z + step)
-          @camera.translateZ(-step)
+        @translateCamera(-step, false)
       when Dir.NE
-        unless @isBlocked(x: @camera.position.x - diagStep, y: @camera.position.y, z: @camera.position.z)
-          @camera.translateX(diagStep)
-        unless @isBlocked(x: @camera.position.x, y: @camera.position.y, z: @camera.position.z + diagStep)
-          @camera.translateZ(-diagStep)
+        @translateCamera(diagStep, true)
+        @translateCamera(-diagStep, false)
       when Dir.E
-        unless @isBlocked(x: @camera.position.x - step, y: @camera.position.y, z: @camera.position.z)
-          @camera.translateX(step)
+        @translateCamera(step, true)
       when Dir.SW
-        unless @isBlocked(x: @camera.position.x + diagStep, y: @camera.position.y, z: @camera.position.z)
-          @camera.translateX(-diagStep)
-        unless @isBlocked(x: @camera.position.x, y: @camera.position.y, z: @camera.position.z - diagStep)
-          @camera.translateZ(diagStep)
-
-    # Don't let translations move up/down or go off the map
-    @camera.position.x = Math.min(@camera.position.x, @worldSize)
-    @camera.position.x = Math.max(@camera.position.x, -@worldSize)
-    @camera.position.y = posY
-    @camera.position.z = Math.min(@camera.position.z, @worldSize)
-    @camera.position.z = Math.max(@camera.position.z, -@worldSize)
+        @translateCamera(-diagStep, true)
+        @translateCamera(diagStep, false)

     # If jumping, move up a little and decrement frames.
     if @jumping


making --run the default option for consistency. If you want to save the file, use -c or --compile.

--- command_line.coffee
+++ command_line.coffee
@@ -11,8 +11,8 @@
         '''

 SWITCHES: [
+  ['-c', '--compile',       'compile to JavaScript and save as .js files']
   ['-i', '--interactive',   'run an interactive CoffeeScript REPL']
-  ['-r', '--run',           'compile and run a CoffeeScript']
   ['-o', '--output [DIR]',  'set the directory for compiled JavaScript']
   ['-w', '--watch',         'watch scripts for changes, and recompile']
   ['-p', '--print',         'print the compiled JavaScript to stdout']
@@ -77,10 +77,10 @@
     else if o.nodes             then puts CoffeeScript.nodes(code).toString()
     else
       js: CoffeeScript.compile code, compile_options()
-      if      o.run             then eval js
+      if      o.compile         then write_js source, js
       else if o.lint            then lint js
       else if o.print or o.eval then print js
-      else                           write_js source, js
+      else                           eval js
   catch err
     if o.watch                  then puts err.message else throw err


Update deprecated selectors in keymap

Update deprecated selectors in keymap

--- keymaps/spell-check.cson
+++ keymaps/spell-check.cson
@@ -1,21 +1,21 @@
-'.platform-darwin .editor':
+'.platform-darwin atom-text-editor':
   'cmd-:': 'spell-check:correct-misspelling'

-'.platform-darwin .corrections .editor':
+'.platform-darwin .corrections atom-text-editor':
   'cmd-:': 'core:cancel'

-'.platform-win32 .editor':
+'.platform-win32 atom-text-editor':
   'ctrl-:': 'spell-check:correct-misspelling'

-'.platform-win32 .corrections .editor':
+'.platform-win32 .corrections atom-text-editor':
   'ctrl-:': 'core:cancel'
-
-'.platform-linux .editor':
+
+'.platform-linux atom-text-editor':
   'ctrl-:': 'spell-check:correct-misspelling'

-'.platform-linux .corrections .editor':
+'.platform-linux .corrections atom-text-editor':
   'ctrl-:': 'core:cancel'

-'.corrections .mini.editor input':
+'.corrections atom-text-editor[mini]':
   'enter': 'core:confirm'
   'tab': 'core:confirm'


Updated the autolaunch function for VM

Updated the autolaunch function for VM

--- src/lync-launcher.coffee
+++ src/lync-launcher.coffee
@@ -1,10 +1,11 @@
 ps = require 'ps-node'
 _  = require 'lodash'
-{spawn} = require('child_process')
+exec = require('child_process').exec
+LyncManager = require './lync-manager.coffee'

 intervalId = null

-autoCheck = (intervalTime=10000) =>
+autoCheck = (intervalTime=20000) =>
   if !intervalId
     intervalId = setInterval _checkLync, intervalTime

@@ -13,15 +14,9 @@
     intervalId = clearInterval intervalId

 _checkLync = () =>
-  ps.lookup {command: 'lync'}, (error, result) =>
-    return unless _.isEmpty result
-    options =
-      stdio: ['pipe', 'pipe', 'pipe', 'ipc']
-      shell: true
-      detached: true
-
-    child = spawn 'cd C:\\ && start lync.exe', options
-    child.unref()
+  LyncManager.getState null, (error, state) =>
+    if !state.hasClient
+      exec 'cd C:\\ && start lync.exe'

 module.exports = {
   autoCheck,


fixed file loading bug where it would crash if no file was supplied

--- src/cli.coffee
+++ src/cli.coffee
@@ -3,7 +3,7 @@
 module.exports.CLI = class CLI
    constructor : ->
       argv = process.argv
-      @file = '[]'
+      @file = []
       @ports =
          stub : 80
          admin : 81


Do not add stream multiple times

Do not add stream multiple times

--- assets/js/test.coffee
+++ assets/js/test.coffee
@@ -26,7 +26,7 @@
     invite_echo(room)

   session.on 'peer_stream_ready', (peer) ->
-    if !peer.isLocal()
+    if !peer.isLocal() and $('#' + peer.id).length == 0
       element = $("
<video width='800px' autoplay></video>
")
       $('#video').append(element)


Describe plugin options.

Describe plugin options.

--- js/jquery.table-summarizer.js.coffee
+++ js/jquery.table-summarizer.js.coffee
@@ -116,11 +116,11 @@
   $.tableSummarizer::defaults =
       debug: false
       summaryCssClass: 'summary'      # CSS class to be applied to the summary row
-      showValue: true
-      cssClass: ['duration']          # sum values contained somewhere inside elements with this CSS class
-      summarizableAttr: 'data-minute' # sum values contained somewhere inside elements with this CSS class
-      position: 'after'               # summary row position: 'before' or 'after'
-      roundTo: 2
+      showValue: true                 # Do you want to show the sub-total inside the summary
+      cssClass: ['duration']          # Calculate total of values contained somewhere inside elements with this CSS class
+      summarizableAttr: 'data-minute' # The data attribute that contains the value to sum (ex: )
+      position: 'after'               # Show the summary row, 'before' or 'after' each tbody
+      roundTo: 2                      # Round your sub-totals results

       onReady: ->                     # Function(), called when tableSummarizer is ready
       onError: ->                     # Function(), called when tableSummarizer has not found an element to work on


Give email a default value so error not thrown from md5 in case of missing email.

--- src/index.cjsx
+++ src/index.cjsx
@@ -19,6 +19,7 @@
     rating: 'g'
     https: false
     default: "retro"
+    email: ''
     className: ""

   render: ->


Ignore /invites when the HUBOT_IRC_IGNOREINVITE environment variable is set

--- src/irc.coffee
+++ src/irc.coffee
@@ -247,7 +247,7 @@
     bot.addListener 'invite', (channel, from) ->
       console.log('%s invite you to join %s', from, channel)

-      if not process.env.HUBOT_IRC_PRIVATE
+      if not process.env.HUBOT_IRC_PRIVATE or process.env.HUBOT_IRC_IGNOREINVITE
         bot.join channel

     @bot = bot


clarify user messages; fix help command

clarify user messages; fix help command

--- src/tf.coffee
+++ src/tf.coffee
@@ -21,13 +21,14 @@

 module.exports = (robot) ->

-  robot.respond /auth help$/, (msg) ->
+  robot.respond /tf help$/, (msg) ->
     cmds = []
     arr = [
       "tf create key - create rsa key, for git operations"
       "tf destroy key - erase key"
       "tf show key - display public key"
       "tf clone <repourl> <projectname>"
+      "tf (plan|apply|destroy) <projectname> - tf operations"
     ]

     for str in arr
@@ -49,7 +50,7 @@
     fp = basepath + "/hubot-tf"

     if fs.existsSync("#{fp}.pub")
-      return msg.send {room: msg.message.user.name}, "Key exists!  Destroy it, then try this again?"
+      return msg.send {room: msg.message.user.name}, "Key exists!  Destroy it first."

     exec "ssh-keygen -f #{fp} -b 1024 -C hubot-tf -N ''", (error, stdout, stderr) ->
       #msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
@@ -65,7 +66,7 @@
     fp = basepath + "/hubot-tf"

     if ! fs.existsSync("#{fp}.pub")
-      return msg.send {room: msg.message.user.name}, "No key on file!  Create one first, eh?"
+      return msg.send {room: msg.message.user.name}, "No key on file!  Create one first."

     pubkey = fs.readFileSync("#{fp}.pub", 'utf-8').toString()
     return msg.send {room: msg.message.user.name}, "```\n#{pubkey}\n```"
@@ -79,7 +80,7 @@
     fp = basepath + "/hubot-tf"

     if ! fs.existsSync("#{fp}.pub")
-      return msg.send {room: msg.message.user.name}, "No key on file!  Create one first, eh?"
+      return msg.send {room: msg.message.user.name}, "No key on file!  Create one first."

     fs.unlinkSync("#{fp}")
     fs.unlinkSync("#{fp}.pub")


real time update of new member bucket threw an exception

--- client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -118,7 +118,8 @@
       for item in @itemsOrdered
         if item.getData() instanceof NewMemberBucketData
           data = item.getData()
-          data.anchors.pop()
+          if data.count > 3
+            data.anchors.pop()
           data.anchors.unshift bucket.anchor
           data.count++
           item.slideOut =>


Fix ampersands; kinda

Fix ampersands; kinda

--- components/feed/models/feed_item.coffee
+++ components/feed/models/feed_item.coffee
@@ -24,6 +24,12 @@

   parse: (response) ->
     response.title = DOMPurify.sanitize(response.title)
+      # DOMPurify replaces normal ampersands with HTML entities.
+      # *I think* Jade should render HTML entities without having to completely unescape but...
+      # https://github.com/visionmedia/jade/issues/966
+      # We can't completely unescape because that template is sometimes rendered on the server
+      # and we don't have a server-side sanitization solution, yet.
+      ?.replace(' & ', ' & ')
     response.body = DOMPurify.sanitize(response.body)
     response


Fix timezone offset problem in JS

Fix timezone offset problem in JS

Stupid JS and it’s braindead insistence that everything be formatted as a local time.

--- evesrp/static/js/handlebars-helpers.coffee
+++ evesrp/static/js/handlebars-helpers.coffee
@@ -20,7 +20,14 @@
 datefmt = (date, options) ->
     style = options.hash.style ? 'medium'
     if typeof date == "string"
-        date = new Date date
+        # Javascript's Date handling is stupid
+        local_date = new Date date
+        date = new Date(local_date.getUTCFullYear(),
+            local_date.getUTCMonth(),
+            local_date.getUTCDate(),
+            local_date.getUTCHours(),
+            local_date.getUTCMinutes(),
+            local_date.getUTCSeconds())
     if style == 'medium'
         ui.dateFormatMedium date
     else if style == 'short'


Publish location update over the mediator

--- components/location_search/index.coffee
+++ components/location_search/index.coffee
@@ -2,6 +2,7 @@
 Backbone = require 'backbone'
 geo = require '../geo/index.coffee'
 { isTouchDevice } = require '../util/device.coffee'
+mediator = require '../../lib/mediator.coffee'

 template = -> require('./template.jade') arguments...

@@ -14,6 +15,7 @@

   announce: (location) ->
     @trigger 'location:update', location
+    mediator.trigger 'location:update', location

   # Should always fire before Google's place_changed event
   preAnnounce: ->


Default to loading fonts over https.

Default to loading fonts over https.

--- base/GoogleFonts.cjsx
+++ base/GoogleFonts.cjsx
@@ -37,7 +37,7 @@
     getDefaultProps: -> {
         defer: false
         text: ''
-        protocol: 'http'
+        protocol: 'https'
     }

     render: ->


Coffeelint lists plugin

Coffeelint lists plugin

--- src/plugins/lists.coffee
+++ src/plugins/lists.coffee
@@ -2,34 +2,33 @@
 #     (c) 2011 Henri Bergius, IKS Consortium
 #     Hallo may be freely distributed under the MIT license
 ((jQuery) ->
-    jQuery.widget "IKS.hallolists",
-        options:
-            editable: null
-            toolbar: null
-            uuid: ''
-            lists:
-                ordered: true
-                unordered: true
-            buttonCssClass: null
+  jQuery.widget "IKS.hallolists",
+    options:
+      editable: null
+      toolbar: null
+      uuid: ''
+      lists:
+        ordered: true
+        unordered: true
+      buttonCssClass: null

-        populateToolbar: (toolbar) ->
-            buttonset = jQuery ""
-            buttonize = (type, label) =>
+    populateToolbar: (toolbar) ->
+      buttonset = jQuery ""
+      buttonize = (type, label) =>
+        buttonElement = jQuery ''
+        buttonElement.hallobutton
+          uuid: @options.uuid
+          editable: @options.editable
+          label: label
+          command: "insert#{type}List"
+          icon: "icon-list-#{label.toLowerCase()}"
+          cssClass: @options.buttonCssClass
+        buttonset.append buttonElement

-                buttonElement = jQuery ''
-                buttonElement.hallobutton
-                  uuid: @options.uuid
-                  editable: @options.editable
-                  label: label
-                  command: "insert#{type}List"
-                  icon: "icon-list-#{label.toLowerCase()}"
-                  cssClass: @options.buttonCssClass
-                buttonset.append buttonElement
+      buttonize "Ordered", "OL" if @options.lists.ordered
+      buttonize "Unordered", "UL" if @options.lists.unordered

-            buttonize "Ordered", "OL" if @options.lists.ordered
-            buttonize "Unordered", "UL" if @options.lists.unordered
-
-            buttonset.hallobuttonset()
-            toolbar.append buttonset
+      buttonset.hallobuttonset()
+      toolbar.append buttonset

 )(jQuery)


Fixed unhandled exception if trying to browse for a removed media / invalid id

--- lib/services/ContentDirectory.coffee
+++ lib/services/ContentDirectory.coffee
@@ -194,6 +194,7 @@
   fetchObject: (id, cb) ->
     @redis.hgetall id, (err, object) ->
       return cb new SoapError 501 if err?
+      return cb new SoapError 701 unless object
       return cb new SoapError 701 unless Object.keys(object).length > 0
       cb null, object


Handle ManageFeeds Socket Requests

Handle ManageFeeds Socket Requests

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -30,6 +30,27 @@
     @_initializeEventric()

+    db = [{name:'jiha'}]
+
+    websocket.on 'connection', (socket) ->
+      console.log "USER CONNECTED"
+
+      socket.on 'FeedContextGetFeeds', (func) ->
+        func(db)
+
+      socket.on 'FeedContextCreateFeed', () ->
+        db.push {name:''}
+        socket.broadcast.emit 'FeedContextFeedCreated'
+
+      socket.on 'FeedContextRemoveAllFeeds', () ->
+        db = [{}]
+        socket.broadcast.emit 'FeedContextFeedsRemoved'
+
+      socket.on 'FeedContextChangeFeedTitle', (payload) ->
+        db[payload.feedId].name = payload.feedTitle
+        socket.broadcast.emit 'FeedContextFeedTitleChanged', payload
+
+

   _readFeeds: ->
     @_reader  = require('./reader')( webserver: webserver, websocket: websocket )