Clean up code

Clean up code

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -1013,7 +1013,7 @@

     render: ->
         if not @props.customer
-            return <AddPaymentMethod redux={redux} on_close={() => redux.getActions('billing').setState({continue_first_purchase: true})} />
+            return <AddPaymentMethod redux={redux} />
         for card_data in @props.customer.sources.data
             if card_data.id == @props.customer.default_source
                 default_card = card_data
@@ -2046,6 +2046,11 @@
             selected_plan   = {@props.selected_plan}
             redux           = {@props.redux} />

+    finish_first_subscription: ->
+        set_selected_plan('')
+        @actions('billing').remove_all_coupons();
+        @actions('billing').setState({continue_first_purchase: false})
+
     render_page: ->
         cards    = @props.customer?.sources?.total_count ? 0
         subs     = @props.customer?.subscriptions?.total_count ? 0
@@ -2055,7 +2060,7 @@
         else if not @props.customer? or @props.continue_first_purchase
             <div>
                 <AddSubscription
-                    on_close        = {() => set_selected_plan(''); @actions('billing').remove_all_coupons(); redux.getActions('billing').setState({continue_first_purchase: false})}
+                    on_close        = {@finish_first_subscription}
                     selected_plan   = {@props.selected_plan}
                     actions         = {@props.redux.getActions('billing')}
                     applied_coupons = {@props.applied_coupons}


Better error handling

Better error handling

--- plugins/webhook/index.coffee
+++ plugins/webhook/index.coffee
@@ -1,7 +1,7 @@
 NotificationPlugin = require "../../notification-plugin"

 class Webhook extends NotificationPlugin
-  @receiveEvent: (config, event) ->
+  @receiveEvent: (config, event, callback) ->
     payload = JSON.stringify(event).replace(/\．/g,".")

     # Send the request to the url
@@ -9,6 +9,10 @@
       .post(config.url)
       .type('json')
       .send(payload)
-      .end();
+      .on "error", (err) ->
+        callback(err)
+      .end (res) ->
+        return callback(res.error) if res.error
+        callback()

 module.exports = Webhook


roll back meteor login if parse login fails

--- app/packages/openam/login.coffee
+++ app/packages/openam/login.coffee
@@ -7,10 +7,14 @@
     passw = form.password.value.trim()

     Meteor.call 'loginUser', email, passw, (err, token) ->
-      Meteor.loginWithToken(token)
-      Parse.User.logIn(email, passw)
-        .then ()->
-          console.log(Parse.User.current())
-        .fail (error)->
-          toastr.error('Could not log into Parse.')
-          console.log(error)
+      Meteor.loginWithToken token, (err)->
+        if err
+          toastr.error('Could not log into Meteor')
+          return
+        Parse.User.logIn(email, passw)
+          .then ()->
+            console.log(Parse.User.current())
+          .fail (error)->
+            toastr.error('Could not log into Parse.')
+            console.log(error)
+            Meteor.logout()


add in recordName

add in recordName

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -165,6 +165,7 @@
             recordData.push
                 colData: colData
                 _id: record._id
+                recordName: record[@_colToUseForName()
                 recordDisplayName: @_recordName() + ' ' + record[@_colToUseForName()]
                 editOk: @collection().editOk?(record)
                 deleteOk: @collection().deleteOk?(record)


Removing extra compile fn

Removing extra compile fn

--- scripts/editor.coffee
+++ scripts/editor.coffee
@@ -22,23 +22,8 @@

 handle_error = (message) -> $error.text(message).show()

-run = (e) ->
-  e.preventDefault()
-  try eval(resultsCode)
-  catch error then handle_error(error.message)
-
 compile = ->
-  source = source_el.value
-  localStorage.setItem('source', source)
-
-  try
-    results = CoffeeScript.compile(source, bare: on)
-    localStorage.setItem('results', results)
-    results_el.value = results
-    $error.hide()
-  catch error then handle_error(error.message)
-
-compile = ->
+  console.log 'compile'
   sourceCode = source.getValue()
   localStorage.setItem('source', sourceCode)

@@ -50,6 +35,7 @@
   catch error then handle_error(error.message)

 decompile = ->
+  console.log 'decompile'
   resultsCode = results.getValue()
   localStorage.setItem('results', resultsCode)

@@ -63,4 +49,9 @@
 $source.bind('keyup', compile)
 $results.bind('keyup', decompile)

+run = (e) ->
+  e.preventDefault()
+  try eval(resultsCode)
+  catch error then handle_error(error.message)
+
 $try.bind('click', run)


add misawa neta

add misawa neta

--- scripts/misawa.coffee
+++ scripts/misawa.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Jojo Neta
+#   Misawa Neta
 #
 # Dependencies:
 #   None
@@ -8,11 +8,41 @@
 #   None
 #
 # Commands:
-#   hubot daga - get daga kotowaru
-#   hubot tururu- get tururu
+#   hubot 俺の分 - get masa meigen
+#   hubot ゲザー - get masa meigen
+#   hubot 申し訳 - get masa meigen
+#   hubot 休む - get masa meigen
+#   hubot 嫌な予感 - get masa meigen
+#   hubot 俺は土下座 - get masa meigen
+#   hubot 本気 - get masa meigen
+#   hubot かます - get masa meigen
+#   hubot 有給 - get masa meigen

 module.exports = (robot) ->
   robot.respond /俺の分$/, (msg) ->
     msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/6/9/692ab57e.gif"
     msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/6/d/6dc7eec8.gif"

+  robot.respond /ゲザー$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/3/d/3da1e0e7.gif"
+
+  robot.respond /申し訳$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/6/9/692ab57e.gif"
+
+  robot.respond /休む$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/3/7/37cd9209.gif"
+
+  robot.respond /嫌な予感$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/9/1/91216b28.gif"
+
+  robot.respond /俺は土下座$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/6/2/628b0725.gif"
+
+  robot.respond /本気$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/0/d/0db6d427.gif"
+
+  robot.respond /かます$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/6/4/64af58d5.gif"
+
+  robot.respond /有給$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/e/a/ea14c261.gif"


Blur subscribe inputs when modal closes. Fixes #207.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -1248,6 +1248,9 @@
       $('#add_form_modal').on 'shown.bs.modal', () ->
         $('#add_form_modal [data-behavior~=feeds_search_field]').focus()

+      $('#add_form_modal').on 'hide.bs.modal', () ->
+        $('#add_form_modal input').blur()
+
       subscription = feedbin.queryString('subscribe')
       if subscription?
         $('[data-behavior~=show_subscribe]').click()


update list after delete

update list after delete

--- app/assets/javascripts/hosts.js.coffee
+++ app/assets/javascripts/hosts.js.coffee
@@ -4,4 +4,7 @@
 $('a.edit').on 'click', (e)->
 	e.preventDefault()
 	e.stopPropagation()
-	$('span.delete').toggleClass('show')+	$('span.delete').toggleClass('show')
+
+$('.item').on 'ajax:success', (e)->
+	$(@).parent().remove()


let setState run before onClick callback

--- resources/assets/coffee/react/_components/block-button.coffee
+++ resources/assets/coffee/react/_components/block-button.coffee
@@ -37,13 +37,14 @@

   updateBlocks: (data) =>
-    @props.onClick()
     @setState block: _.find(data, target_id: @props.userId), ->
       currentUser.blocks = _.filter data, relation_type: 'block'
       currentUser.friends = _.filter data, relation_type: 'friend'
       $.publish 'user:update', currentUser
       $.publish 'blockButton:refresh'
       $.publish 'friendButton:refresh'
+
+    @props.onClick()

   refresh: (e) =>


@cache isn't used between renders anymore...I think

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -61,7 +61,6 @@
     $.subscribe 'beatmapDiscussionPost:markRead.beatmapDiscussions', @markPostRead
     $.subscribe 'beatmapsetDiscussions:userFilterChanged.beatmapDiscussions', (_e, { selectedUserId }) =>
       @setState () ->
-        @cache = {}
         { selectedUserId }

     $(document).on 'ajax:success.beatmapDiscussions', '.js-beatmapset-discussion-update', @ujsDiscussionUpdate


Rollback to usernames for non-responsive bot.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -135,7 +135,7 @@
             scores = [username + " has " + points[username]] for username of recall

             topscore = scores.sort((a, b) ->
-                b.points - a.points
+                b.points[username] - a.points[username]
             ).slice(0, pointcount)

             # msg.send "The top #{pointcount} users with the most points are: #{topscore}"


Only resize once most of the time.

Only resize once most of the time.

--- bokehjs/src/coffee/document.coffee
+++ bokehjs/src/coffee/document.coffee
@@ -102,7 +102,10 @@
     # Ideally the solver would  settle in one pass (can that be done?),
     # but it currently needs two passes to get it right.
     @_resize()
-    @_resize()
+    if not @_first_resize_complete?
+      @_resize()
+      @_first_resize_complete = true
+

   _resize: () ->


show a message more long time.

show a message more long time.

--- coffee/controllers/mainCtrl.coffee
+++ coffee/controllers/mainCtrl.coffee
@@ -84,7 +84,7 @@
   _errorLoadProject = (data, status) =>
     if status is STATUS_CANCEL then return
     Log.debug("_errorLoadProjects() start")
-    Message.toast Resource.string("msgLoadProjectFail").format(data.account.name), 2000
+    Message.toast Resource.string("msgLoadProjectFail").format(data.account.name), 3000

   ###


With the use of ninvoke

With the use of ninvoke

--- backend/backend.coffee
+++ backend/backend.coffee
@@ -44,65 +44,27 @@
       true

   mongoQuery = (collection, query) ->
-    dfd = Q.defer()
-    db.collection collection, (err, collection) ->
-      dfd.reject err if err
-      collection.find query, (err, cursor) ->
-        dfd.reject err if err
-        cursor.toArray (err, docs) ->
-          dfd.reject err if err
-
-          dfd.resolve docs
-
-    dfd.promise
-
+    Q.ninvoke db, "collection", collection
+      .then (col) -> Q.ninvoke col, "find", query
+      .then (cursor) -> Q.ninvoke cursor, "toArray"

   mongoRemove = (collection, query) ->
-    dfd = Q.defer()
-    db.collection collection, (err, collection) ->
-      dfd.reject err if err
-      collection.remove query, { single: true }, (err) ->
-        dfd.reject err if err
-
-        dfd.resolve {}
-
-    dfd.promise
+    Q.ninvoke db, "collection", collection
+      .then (col) -> Q.ninvoke col, "remove", query, { single: true }

   mongoUpdate = (collection, query, data) ->
     delete data._id;
-
-    dfd = Q.defer()
-    db.collection collection, (err, collection) ->
-      dfd.reject err if err
-      collection.update query, data, (err) ->
-        dfd.reject err if err
-
-        dfd.resolve {}
-
-    dfd.promise
+    Q.ninvoke db, "collection", collection
+      .then (col) -> Q.ninvoke col, "update", query, data

   mongoInsert = (collection, data) ->
-    dfd = Q.defer()
-    db.collection collection, (err, collection) ->
-      dfd.reject err if err
-      collection.insert data, (err, docs) ->
-        dfd.reject err if err
-
-        dfd.resolve docs[0]
-
-    dfd.promise
-
+    Q.ninvoke db, "collection", collection
+      .then (col) -> Q.ninvoke col, "insert", data
+      .then (docs) -> docs[0]

   mongoDrop = (collection) ->
-    dfd = Q.defer()
-    db.collection collection, (err, collection) ->
-      dfd.reject err if err
-      collection.drop (err) ->
-        dfd.reject err if err
-
-        dfd.resolve {}
-
-    dfd.promise
+    Q.ninvoke db, "collection", collection
+      .then (col) -> Q.ninvoke col, "drop"

   app.options '*', (req, res) ->
     setCORS res


Remote old single file

Remote old single file

--- app/assets/javascripts/votelist.coffee
+++ app/assets/javascripts/votelist.coffee
@@ -1,93 +0,0 @@
-# Ember.js Application
-VoteList = Ember.Application.create()
-
-
-# Model object indicating one potential vote recipient
-VoteList.Candidate = Ember.Resource.extend
-  url: 'candidate'
-  name: 'candidate'
-  properties: ['displayName', 'score']
-
-  displayName: 'somebody'
-  score: 0
-
-  # Increment the score by one
-  upvote: ->
-    this.set 'score', this.get('score') + 1
-    this.save()
-
-  # Decrement the score by one
-  downvote: ->
-    this.set 'score', this.get('score') - 1
-    this.save()
-
-
-# Controller for candidate models
-VoteList.CandidateController = Ember.ResourceController.create
-  type: VoteList.Candidate
-  url: 'candidate'
-
-  # Computed property to keep content sorted
-  sortedContent:(() ->
-    content = this.get('content')
-
-    if content
-      result = Ember.copy content
-
-      result.sort ( a, b ) ->
-        b.get('score') - a.get('score')
-    else
-      []
-  ).property('@each.score').cacheable()
-
-  # Creation method for adding a new canddiate
-  createCandidate: (displayName, shouldSave = true, score = 0, id) ->
-    cand = VoteList.Candidate.create displayName: displayName, score: score, id: id
-
-    this.pushObject(cand)
-
-    if (shouldSave)
-      cand.save()
-
-
-# View for creating a new candidate
-VoteList.CreateCandidateView = Ember.TextField.extend
-  # Automate having new items inserted into the
-  # list of candidates when enter is pressed
-  insertNewline: ->
-    val = this.get('value');
-
-    if val
-      VoteList.CandidateController.createCandidate val
-      this.set 'value', ''
-
-
-# View for displaying an individual candidate
-VoteList.CandidateView = Ember.View.extend
-  # Candidate model this view is representing
-  person: null
-
-  # View helper to pass calls down to the model
-  upvote: ->
-    this.person.upvote()
-
-  # View helper to pass calls down to the model
-  downvote: ->
-    this.person.downvote()
-
-
-# Basic setup on first page load
-jQuery ->
-  # Setup common AJAX settings for most requests
-  $.ajaxSetup
-    type: 'POST'
-    headers:
-      'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
-    accept: 'application/json'
-
-  VoteList.CandidateController.findAll()
-
-
-# Send VoteList out to the rest of the system
-root = exports ? this
-root.VoteList = VoteList


Include a realistic use case

Include a realistic use case

--- components/mixins/test/form.coffee
+++ components/mixins/test/form.coffee
@@ -7,6 +7,9 @@

 class FormView extends Backbone.View
   _.extend @prototype, Form
+
+  events:
+    'submit form' : 'submit'

   template: ->
     """
@@ -18,6 +21,11 @@
       </form>
     """

+  submitStub: -> # Stubbed in beforeEach hook
+  submit: ->
+    return if @formIsSubmitting()
+    @submitStub()
+
   render: ->
     @$el.html @template()
     this
@@ -28,9 +36,11 @@
       benv.expose $: benv.require 'jquery'
       Backbone.$ = $
       @view = new FormView().render()
+      sinon.stub FormView::, 'submitStub'
       done()

   afterEach ->
+    @view.submitStub.restore()
     benv.teardown()

   describe '#formIsSubmitting', ->
@@ -43,6 +53,14 @@
     it 'disables the button', ->
       @view.formIsSubmitting()
       @view.$('button').prop('disabled').should.be.true
+
+    it 'should work with an actual form submission', ->
+      @view.submitStub.called.should.be.false
+      @view.$('form').submit()
+      @view.submitStub.called.should.be.true
+      @view.$('form').submit()
+      @view.$('form').submit()
+      @view.submitStub.callCount.should.equal 1

   describe '#reenableForm', ->
     it 'reenables the form', ->
@@ -69,6 +87,13 @@
       @view.$('form').find('textarea[name=comment]').val values['comment']
       @view.serializeForm().should.include values

+    it 'should work with an actual form submission', ->
+      @view.$('form').submit()
+      @view.reenableForm()
+      @view.$('form').submit()
+      @view.$('form').submit()
+      @view.submitStub.callCount.should.equal 2
+
   describe '#validateForm', ->
     # Note: https://github.com/tmpvar/jsdom/issues/544
     it 'should set a class on the form and call the #checkValidity on the form', ->


feat(swimlanes): Add swimlane selector in backlog (#2045)

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -78,6 +78,7 @@
         @.translationData = {q: @.filterQ}
         @scope.userstories = []
         @.totalUserStories = 0
+        @scope.swimlanesList = Immutable.List()

         return if @.applyStoredFilters(@params.pslug, "backlog-filters")

@@ -93,6 +94,7 @@

         # On Success
         promise.then =>
+            @.loadSwimlanes()
             @.firstLoadComplete = true

             title = @translate.instant("BACKLOG.PAGE_TITLE", {projectName: @scope.project.name})
@@ -241,6 +243,32 @@
             @scope.closedSprintsById = groupBy(sprints, (x) -> x.id)
             @rootscope.$broadcast("closed-sprints:reloaded", sprints)
             return sprints
+
+    loadSwimlanes: ->
+
+
+        userstoriesNoSwimlane = @scope.userstories.filter (us) =>
+            return us.swimlane == null
+
+        emptySwimlaneExists = @scope.swimlanesList.filter (swimlane) =>
+            return swimlane.id == -1
+
+         if userstoriesNoSwimlane.length && !emptySwimlaneExists.size
+            @scope.project.swimlanes.forEach (swimlane) =>
+                if (!@scope.swimlanesList.includes(swimlane))
+                    @scope.swimlanesList = @scope.swimlanesList.push(swimlane)
+
+            emptySwimlane = {
+                id: -1,
+                kanban_order: 1,
+                name: @translate.instant("KANBAN.UNCLASSIFIED_USER_STORIES")
+            }
+            @scope.swimlanesList = @scope.swimlanesList.insert(0, emptySwimlane)
+
+        else
+            @scope.project.swimlanes.forEach (swimlane) =>
+                if (!@scope.swimlanesList.includes(swimlane))
+                    @scope.swimlanesList = @scope.swimlanesList.push(swimlane)

     loadSprints: ->
         params = {closed: false}


survey: adding one more dot to the agency size question

--- app/_src/components/blog/estimation-survey/collision.coffee
+++ app/_src/components/blog/estimation-survey/collision.coffee
@@ -4,6 +4,7 @@

 	NODES = [
 		# 18 of these bad boys (blue)
+		{ r: 10, fill: "#006457" },
 		{ r: 10, fill: "#006457" },
 		{ r: 10, fill: "#006457" },
 		{ r: 10, fill: "#006457" },


Fix the jQuery plugin

Fix the jQuery plugin

--- app/assets/javascripts/browse_everything/behavior.js.coffee
+++ app/assets/javascripts/browse_everything/behavior.js.coffee
@@ -4,22 +4,27 @@

   initialize = (options) ->
     opts = $.extend(true, {}, options)
-    unless opts.datatype?
-      opts.datatype = if opts.target then 'html' else 'json'
+
+  toHiddenFields = (data) ->
+    fields = $.param(data)
+      .split('&')
+      .map (t) -> t.split('=',2)
+    elements = $(fields).map () ->
+      "<input type='hidden' name='#{decodeURIComponent(this[0])}' value='#{decodeURIComponent(this[1])}'/>"
+    $(elements.toArray().join("\n"))

   $.fn.browseEverything = (options) ->
     initialize(options)

-    dialog = $('div#browse-everything')
-    if dialog.length == 0
-      dialog = $('

').appendTo('body')
-    dialog.load opts.route, () ->
-      dialog.modal()
+    $(this).click () ->
+      dialog = $('div#browse-everything')
+      if dialog.length == 0
+        dialog = $('

').appendTo('body')
+      dialog.load opts.route, () -> dialog.modal('show')
     promise = $.Deferred()
-    return promise

-  $('*[data-toggle=browse-everything]').click () ->
-      $(this).browseEverything($(this).data())
+  triggers = $('*[data-toggle=browse-everything]')
+  triggers.browseEverything($(this).data()) if triggers.length > 0

   $(document).on 'click', 'button.ev-cancel', (event) ->
     event.preventDefault()
@@ -34,11 +39,12 @@
     resolver_url = main_form.data('resolver')
     $.ajax resolver_url,
       type: 'POST'
-      dataType: opts.datatype
+      dataType: 'json'
       data: main_form.serialize()
     .done (data) ->
       if opts.target?
-        $(opts.target).append($(data))
+        fields = toHiddenFields({selected_files: data})
+        $(opts.target).append($(fields))
       promise.resolve(data)
     .fail (xhr,status,error) ->
       promise.reject(status, error, xhr.responseText)


Always return an empty array in TokenizedBuffer.getInvalidatedRanges

..because TokenizedBuffer never synchronously invalidates beyond the
extent of the spatial change.

Signed-off-by: Nathan Sobo <2e8aa918660411855c6d44d5bb2da677aa033255@github.com>

--- src/tokenized-buffer.coffee
+++ src/tokenized-buffer.coffee
@@ -49,10 +49,7 @@
     new TokenizedBufferIterator(this)

   getInvalidatedRanges: ->
-    if @invalidatedRange?
-      [@invalidatedRange]
-    else
-      []
+    []

   onDidInvalidateRange: (fn) ->
     @emitter.on 'did-invalidate-range', fn
@@ -206,8 +203,6 @@
     newEndStack = @stackForRow(end + delta)
     if newEndStack and not _.isEqual(newEndStack, previousEndStack)
       @invalidateRow(end + delta + 1)
-
-    @invalidatedRange = Range(start, end)

   isFoldableAtRow: (row) ->
     if @largeFileMode


query is already part of filters

query is already part of filters

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -203,10 +203,6 @@

       state[key] = value

-      if key == 'query'
-        # ;_;
-        @initialInput = value
-
     filters: state
     isExpanded: expand


row -> bufferRow for clarity

row -> bufferRow for clarity

--- src/tokenized-buffer.coffee
+++ src/tokenized-buffer.coffee
@@ -224,16 +224,15 @@
   tokenizedLineForRow: (bufferRow) ->
     @tokenizedLines[bufferRow]

-
-  stackForRow: (row) ->
-    @tokenizedLines[row]?.ruleStack
-
-  indentLevelForRow: (row) ->
-    line = @buffer.lineForRow(row)
+  stackForRow: (bufferRow) ->
+    @tokenizedLines[bufferRow]?.ruleStack
+
+  indentLevelForRow: (bufferRow) ->
+    line = @buffer.lineForRow(bufferRow)
     indentLevel = 0

     if line is ''
-      nextRow = row + 1
+      nextRow = bufferRow + 1
       lineCount = @getLineCount()
       while nextRow < lineCount
         nextLine = @buffer.lineForRow(nextRow)
@@ -242,7 +241,7 @@
           break
         nextRow++

-      previousRow = row - 1
+      previousRow = bufferRow - 1
       while previousRow >= 0
         previousLine = @buffer.lineForRow(previousRow)
         unless previousLine is ''


display alpha as hex

display alpha as hex

--- app/assets/javascripts/jax/dev/views/color_picker.js.coffee
+++ app/assets/javascripts/jax/dev/views/color_picker.js.coffee
@@ -30,6 +30,9 @@
       @$(".rgbhex").css 'color', '#000'
     else
       @$(".rgbhex").css 'color', '#fff'
+    alphaHex = parseInt(@color.alpha * 255).toString(16)
+    alphaHex = "0#{alphaHex}" if alphaHex.length is 1
+    @$(".alpha").val alphaHex

   initialize: ->
     @options.alpha = true if @options.alpha is undefined
@@ -61,7 +64,5 @@
         step: 0.001
         min: 0
         max: 1
-        slide: (event, ui) =>
-          @$(".alpha").val ui.value
-          @color.alpha = ui.value
+        slide: (event, ui) => @color.alpha = ui.value
     @jaxColorChanged()


fixed tinymce observer

fixed tinymce observer

--- app/assets/javascripts/lolita/file-upload/gallery.js.coffee
+++ app/assets/javascripts/lolita/file-upload/gallery.js.coffee
@@ -27,8 +27,12 @@
     @._initialized = true

   _observeImageDropping: ->
-    tinyMCE.on "AddEditor", (e) =>
-      @_observeDropOnTinyMCEEditor(e.editor)
+    if typeof tinyMCE.onAddEditor == 'object' && typeof tinyMCE.onAddEditor.add == 'function'
+      tinyMCE.onAddEditor.add (e) =>
+        @_observeDropOnTinyMCEEditor(e.editor)
+    else
+      tinyMCE.on "AddEditor", (e) =>
+        @_observeDropOnTinyMCEEditor(e.editor)

   toggle: ->
@@ -123,8 +127,8 @@
     $('<img>').attr({src: imagePath, alt: imagePath}).attr('data-original-url', originalImagePath)

   _fileContainer: (filePath, originalFilePath) ->
-    $('<li>').html(
-      $('<a>').attr('href', '#').addClass('file').html(
+    $('<li>').html(
+      $('<a>').attr('href', '#').addClass('file').html(
         $('<img>').attr({src: @_getFileIcon(originalFilePath), title: @_getFileBasename(originalFilePath)})
         .attr('data-original-url', originalFilePath)
       )
@@ -142,7 +146,7 @@
     self = this
     newEditor.on "LoadContent", (e) ->
       $(e.target.getContainer()).droppable({
-        drop: (event, ui) =>
+        drop: (event, ui) ->
           e.target.selection.setContent(self._insertableTag(ui.draggable))
       })


Update url_manager.coffee

Update url_manager.coffee

--- app/assets/javascripts/oxalis/controller/url_manager.coffee
+++ app/assets/javascripts/oxalis/controller/url_manager.coffee
@@ -10,7 +10,7 @@
   constructor : (@controller, @model) ->

     @baseUrl      = document.location.pathname
-    @initialState = @parseUrl @baseUrl
+    @initialState = @parseUrl()

     @update = _.throttle(
       => location.replace(@buildUrl())
@@ -18,9 +18,9 @@
     )

-  parseUrl : (url)->
+  parseUrl : ->

-    stateString = url.match(/^.*#([\d.-]*(?:,[\d.-]*)*)$/)?[1]
+    stateString = location.hash
     state       = {}

     if stateString?


Move test inside package

Move test inside package

--- test/test.skin.coffee
+++ test/test.skin.coffee
@@ -1,21 +0,0 @@
-'use strict'
-
-Acho = require '..'
-util = require './util'
-skinCli = require 'acho-skin-cli'
-
-inlineSkin =
-  types:
-    pokemon:
-      level : 4
-      color : 'blue'
-
-describe 'Acho :: skin', ->
-  it 'load an skin', ->
-    AchoCLI = Acho.skin(skinCli)
-
-    instance = AchoCLI
-      uppercase: true
-
-    levels = Object.keys(instance.types)
-    levels.should.be.eql ['debug', 'info', 'success', 'warn', 'error']


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -39,11 +39,30 @@
     width: 8
     height: 8
     collisionMargin: Point(0, 0)
-
+
+  ###*
+  The position of this game object. By default it is the top left point.
+  Redefining the center method will change the relative position.
+
+

+  player = Core
+    x: 50
+    y: 40
+
+  player.include(Bounded)
+
+  player.position()
+  # => {x: 50, y: 40}
+

+
+  @name position
+  @methodOf Bounded#
+  @returns {Point} The position of this object
+  ###
   distance: (otherObj) ->
     Point.distance(self.position(), otherObj.position())

-    ###*
+  ###*
   The position of this game object. By default it is the top left point.
   Redefining the center method will change the relative position.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -40,7 +40,11 @@
     height: 8
     collisionMargin: Point(0, 0)

-  closes
+  closest: ->
+    if Object.isString(groupA)
+        groupA = engine.find(groupA)
+      else
+        groupA = [].concat(groupA)

   ###*
   Distance between two objects. Proxies to Point.distance.


fixes #722

fixes #722

--- content/BetterBibTeX.coffee
+++ content/BetterBibTeX.coffee
@@ -31,12 +31,15 @@
     UNINSTALL
   ###

-  AddonManager.addAddonListener({
+  uninstaller = {
     onUninstalling: (addon, needsRestart) ->
       return unless addon.id == 'better-bibtex@iris-advies.com'
       debug('uninstall')

+      quickCopy = Zotero.Pref.get('export.quickCopy.setting')
       for label, metadata of Translators.byName
+        Zotero.Prefs.clear('export.quickCopy.setting') if quickCopy == "export=#{metadata.translatorID}"
+
         try
           Translators.uninstall(label, metadata.translatorID)

@@ -46,7 +49,7 @@

     onOperationCancelled: (addon, needsRestart) ->
       return unless addon.id == 'better-bibtex@iris-advies.com'
-      return if addon.pendingOperations & AddonManager.PENDING_UNINSTALL
+      return if addon.pendingOperations & (AddonManager.PENDING_UNINSTALL | AddonManager.PENDING_DISABLE)

       for id, header of Translators.byId
         try
@@ -55,7 +58,9 @@
       delete Zotero.BetterBibTeX.uninstalled

       return
-  })
+  }
+  uninstaller.onEnabling = uninstaller.onUninstalling
+  AddonManager.addAddonListener(uninstaller)

   ###
     MONKEY PATCHES


Handle properties without type specified

--- src/builder.coffee
+++ src/builder.coffee
@@ -492,42 +492,42 @@
 		isNullOr @argument, Expression

 Properties =
-	ArrayExpression: ['elts', 'loc']
-	ArrayPattern: ['elts', 'loc']
-	ArrowExpression: ['params', 'defaults', 'rest', 'body', 'isGenerator', 'isExpression', 'loc']
-	AssignmentExpression: ['op', 'left', 'right', 'loc']
-	BinaryExpression: ['op', 'left', 'right', 'loc']
+	ArrayExpression: ['elements', 'loc']
+	ArrayPattern: ['elements', 'loc']
+	ArrowExpression: ['params', 'defaults', 'rest', 'body', 'generator', 'expression', 'loc']
+	AssignmentExpression: ['operator', 'left', 'right', 'loc']
+	BinaryExpression: ['operator', 'left', 'right', 'loc']
 	BlockStatement: ['body', 'loc']
 	BreakStatement: ['label', 'loc']
-	CallExpression: ['callee', 'args', 'loc']
-	CatchClause: ['arg', 'guard', 'body', 'loc']
-	ComprehensionBlock: ['left', 'right', 'isForEach', 'loc']
+	CallExpression: ['callee', 'arguments', 'loc']
+	CatchClause: ['param', 'guard', 'body', 'loc']
+	ComprehensionBlock: ['left', 'right', 'each', 'loc']
 	ComprehensionExpression: ['body', 'blocks', 'filter', 'loc']
-	ConditionalExpression: ['test', 'cons', 'alt', 'loc']
+	ConditionalExpression: ['test', 'consequent', 'alternate', 'loc']
 	ContinueStatement: ['label', 'loc']
 	DebuggerStatement: ['loc']
 	DoWhileStatement: ['body', 'test', 'loc']
 	EmptyStatement: ['loc']
-	ExpressionStatement: ['expr', 'loc']
+	ExpressionStatement: ['expression', 'loc']
 	ForStatement: ['init', 'test', 'update', 'body', 'loc']
-	ForInStatement: ['left', 'right', 'body', 'isForEach', 'loc']
+	ForInStatement: ['left', 'right', 'body', 'each', 'loc']
 	ForOfStatement: ['left', 'right', 'body', 'loc']
-	FunctionDeclaration: ['id', 'params', 'defaults', 'rest', 'body', 'isGenerator', 'isExpression', 'loc']
-	FunctionExpression: ['id', 'params', 'defaults', 'rest', 'body', 'isGenerator', 'isExpression', 'loc']
+	FunctionDeclaration: ['id', 'params', 'defaults', 'rest', 'body', 'generator', 'expression', 'loc']
+	FunctionExpression: ['id', 'params', 'defaults', 'rest', 'body', 'generator', 'expression', 'loc']
 	GeneratorExpression: ['body', 'blocks', 'filter', 'loc']
 	Identifier: ['name', 'loc']
-	IfStatement: ['test', 'cons', 'alt', 'loc']
+	IfStatement: ['test', 'consequent', 'alternate', 'loc']
 	LabeledStatement: ['label', 'body', 'loc']
 	LetExpression: ['head', 'body', 'loc']
 	LetStatement: ['head', 'body', 'loc']
 	Literal: ['value', 'loc']
-	LogicalExpression: ['op', 'left', 'right', 'loc']
-	MemberExpression: ['obj', 'prop', 'isComputed', 'loc']
-	NewExpression: ['callee', 'args', 'loc']
-	ObjectExpression: ['props', 'loc']
-	ObjectPattern: ['props', 'loc']
+	LogicalExpression: ['operator', 'left', 'right', 'loc']
+	MemberExpression: ['object', 'property', 'computed', 'loc']
+	NewExpression: ['callee', 'arguments', 'loc']
+	ObjectExpression: ['properties', 'loc']
+	ObjectPattern: ['properties', 'loc']
 	Program: ['body', 'loc']
-	Property: ['key', 'val', 'kind', 'loc']
+	Property: ['key', 'value', 'kind', 'loc']
 	PropertyPattern: ['key', 'patt', 'loc']
 	ReturnStatement: ['arg', 'loc']
 	SequenceExpression: ['exprs', 'loc']
@@ -619,8 +619,19 @@
 			args.push null
 			continue
 		if Array.isArray tree[prop]
-			args.push tree[prop].map (el) -> validate el
-		else if typeof tree[prop] == 'object' and tree[prop].type?
+			# Property nodes don't always have a type property
+			if tree.type == 'ObjectExpression' && prop == 'properties'
+				args.push tree[prop].map (el) ->
+					validate {
+						type: 'Property'
+						key: tree[prop].key
+						value: tree[prop].value
+						kind: tree[prop].kind
+						loc: tree[prop].loc
+					}
+			else
+				args.push tree[prop].map (el) -> validate el
+		else if typeof tree[prop] == 'object' && tree[prop].type?
 			args.push validate tree[prop]
 		else
 			args.push tree[prop]


IE support to workaround non-functional postMessage

--- src/user/login-gateway.cjsx
+++ src/user/login-gateway.cjsx
@@ -3,6 +3,8 @@
 User  = require './model'
 api   = require '../api'

+SECOND = 1000
+IS_IE  = window.navigator.userAgent.indexOf("MSIE ")
 LoginGateway = React.createClass

   getInitialState: ->
@@ -21,6 +23,7 @@
       "left=" + (window.screen.width - nY) / 2].join()

     loginWindow = window.open(User.urlForLogin(), 'oxlogin', options)
+    @ieWindowClosedCheck() if IS_IE
     @setState({loginWindow})

   parseAndDispatchMessage: (msg) ->
@@ -34,6 +37,13 @@
     window.removeEventListener('message', @parseAndDispatchMessage)
   componentWillMount: ->
     window.addEventListener('message', @parseAndDispatchMessage)
+
+  ieWindowClosedCheck: ->
+    return unless @isMounted()
+    if @state.loginWindow and @state.loginWindow.closed
+      User.ensureStatusLoaded(true)
+    else
+      _.delay(@ieWindowClosedCheck, SECOND)

   renderWaiting: ->
     <p>


mixpanel noop kludge

mixpanel noop kludge

--- client/app/MainApp/mixpanel.coffee
+++ client/app/MainApp/mixpanel.coffee
@@ -37,7 +37,7 @@
     user = KD.whoami()
     username = user.profile.nickname
     mixpanel.identify username
-    mixpanel.people.set
+    mixpanel.people.set
       "$username"   : username
       "name"        : "#{user.profile.firstName} #{user.profile.lastName}"
       "$joinDate"   : user.meta.createdAt
@@ -60,15 +60,12 @@

   userRegistered:(account)=>
-    @track "UserRegistered",
+    @track "UserRegistered",
       "$username"   : account.profile.nickname
       "$loginDate"  : Date.now()

-KD.logToMixpanel = (args, percent=100)->
-    if KD.utils.runXpercent percent
-      mixpanel.track args
-      log "Log #{percent}% of time: #{args}"
+KD.logToMixpanel = ->

 if mixpanel? && KD.config.logToExternal then do ->
   KD.mixpanel = new KDMixpanel


client: welcome: inital landing flag for welcome route

--- client/app/lib/kodingrouter.coffee
+++ client/app/lib/kodingrouter.coffee
@@ -93,7 +93,9 @@

     if areStepsFinished
     then return '/IDE'
-    else return '/Welcome'
+    else
+      localStorage.landedOnWelcome = yes
+      return '/Welcome'

   setPageTitle: (title = 'Koding') -> kd.singletons.pageTitle.update title


Use blacklist instead of whitelist to copy public directory.

--- app/templates/website/Gruntfile.coffee
+++ app/templates/website/Gruntfile.coffee
@@ -20,16 +20,13 @@
     pkg: grunt.file.readJSON 'package.json'

     watch:
+      public:
+        files: ['assets/**/*', '!assets/css/**/*.styl', '!assets/css/**/*.less', '!assets/js/**/*.coffee']
+        tasks: ['newer:copy:public']
       coffee:
         cwd: 'assets/js'
         files: 'assets/js**/*.coffee'
         tasks: ['brerror:newer:coffee:dev']
-      js:
-        files: 'assets/js/**/*.js'
-        tasks: ['newer:copy:js']
-      css:
-        files: 'assets/css/**/*.css'
-        tasks: ['newer:copy:css']
       stylesheets:
         cwd: 'assets/css' <% if(options.css === 'stylus') { %>
         files: 'assets/css/**/*.styl'
@@ -42,12 +39,6 @@
         tasks: ['brerror:newer:jade:dev'] <% } else if (options.html === 'ejs') { %>
         files: 'views/**/*.ejs'
         tasks: ['brerror:newer:ejs:dev']  <% } %>
-      images:
-        files: [
-          'assets/img/**'
-          'assets/favicon.ico'
-        ]
-        tasks: ['newer:copy:images']
       options:
         livereload: livereloadPort

@@ -125,30 +116,11 @@
           livereload: true

     copy:
-      images:
-        files: [
-          expand: true
-          cwd: 'assets'
-          src: ['img/**']
-          dest: 'public'
-        ,
-          src: 'assets/favicon.ico'
-          dest: 'public/favicon.ico'
-        ]
-      js:
-        files: [
-          expand: true
-          cwd: 'assets'
-          src: 'js/**/*.js'
-          dest: 'public'
-        ]
-      css:
-        files: [
-          expand: true
-          cwd: 'assets'
-          src: 'css/**/*.css'
-          dest: 'public'
-        ]
+      public:
+        expand: true
+        cwd: 'assets'
+        src: ['**/*', '!css/**/*.styl', '!css/**/*.less', '!js/**/*.coffee']
+        dest: 'public'

     concurrent:
       start:
@@ -173,7 +145,7 @@
               callback filepath

   grunt.event.on 'watch', (action, filepath, task) ->
-    return if task == 'images' || task == 'css' || task == 'js'
+    return unless task in ['coffee', 'stylesheets', 'views']
     cwd = path.join __dirname, grunt.config("watch.#{task}.cwd")
     filename = path.basename filepath, path.extname(filepath)
     searchWord filename, cwd, (file) ->


adds strength value to Sankey tooltips

adds strength value to Sankey tooltips

--- src/viz/types/sankey.coffee
+++ src/viz/types/sankey.coffee
@@ -22,6 +22,7 @@
         id: "left_" + e[vars.edges.source][vars.id.value]
         dupe: "left"
         data: e[vars.edges.source]
+        value: e[vars.edges.strength.value]
       t = e[vars.edges.target]
     else
       s = e[vars.edges.source]
@@ -29,6 +30,7 @@
         id: "right_" + e[vars.edges.target][vars.id.value]
         dupe: "right"
         data: e[vars.edges.target]
+        value: e[vars.edges.strength.value]
     nodes.push s if placed.indexOf(s.id) < 0
     nodes.push t if placed.indexOf(t.id) < 0
     placed.push s.id
@@ -57,6 +59,7 @@
       width: n.dx
       height: n.dy
       suffix: n.dupe
+    d[vars.edges.strength.value] = n.value unless d.id is focus
     returnData.push d

   vars.edges.path = layout.link()


support scan operations on redis cluster

--- services/track-changes/app/coffee/RedisManager.coffee
+++ services/track-changes/app/coffee/RedisManager.coffee
@@ -2,6 +2,7 @@
 redis = require("redis-sharelatex")
 rclient = redis.createClient(Settings.redis.history)
 Keys = Settings.redis.history.key_schema
+async = require "async"

 module.exports = RedisManager =

@@ -33,12 +34,19 @@
 		rclient.smembers Keys.docsWithHistoryOps({project_id}), callback

 	# iterate over keys asynchronously using redis scan (non-blocking)
+	# handle all the cluster nodes or single redis server
 	_getKeys: (pattern, callback) ->
+		nodes = rclient.nodes?('master') || [ rclient ];
+		doKeyLookupForNode = (node, cb) ->
+			RedisManager._getKeysFromNode node, pattern, cb
+		async.concatSeries nodes, doKeyLookupForNode, callback
+
+	_getKeysFromNode: (node, pattern, callback) ->
 		cursor = 0  # redis iterator
 		keySet = {} # use hash to avoid duplicate results
 		# scan over all keys looking for pattern
 		doIteration = (cb) ->
-			rclient.scan cursor, "MATCH", pattern, "COUNT", 1000, (error, reply) ->
+			node.scan cursor, "MATCH", pattern, "COUNT", 1000, (error, reply) ->
 				return callback(error) if error?
 				[cursor, keys] = reply
 				for key in keys
@@ -50,21 +58,20 @@
 		doIteration()

 	# extract ids from keys like DocsWithHistoryOps:57fd0b1f53a8396d22b2c24b
+	# or DocsWithHistoryOps:{57fd0b1f53a8396d22b2c24b} (for redis cluster)
 	_extractIds: (keyList) ->
-		ids = (key.split(":")[1] for key in keyList)
+		ids = for key in keyList
+			m = key.match(/:\{?([0-9a-f]{24})\}?/) # extract object id
+			m[1]
 		return ids

-	# this will only work on single node redis, not redis cluster
 	getProjectIdsWithHistoryOps: (callback = (error, project_ids) ->) ->
-		return callback(new Error("not supported")) if rclient.nodes?
 		RedisManager._getKeys Keys.docsWithHistoryOps({project_id:"*"}), (error, project_keys) ->
 			return callback(error) if error?
 			project_ids = RedisManager._extractIds project_keys
 			callback(error, project_ids)

-	# this will only work on single node redis, not redis cluster
 	getAllDocIdsWithHistoryOps: (callback = (error, doc_ids) ->) ->
-		return callback(new Error("not supported")) if rclient.nodes?
 		# return all the docids, to find dangling history entries after
 		# everything is flushed.
 		RedisManager._getKeys Keys.uncompressedHistoryOps({doc_id:"*"}), (error, doc_keys) ->


Add in some null checks

Add in some null checks

--- services/track-changes/app/coffee/DiffGenerator.coffee
+++ services/track-changes/app/coffee/DiffGenerator.coffee
@@ -191,7 +191,7 @@
 		return part

 	_getLengthOfDiffPart: (part) ->
-		(part.u or part.d or part.i).length
+		(part.u or part.d or part.i or '').length

 	_getContentOfPart: (part) ->
-		part.u or part.d or part.i
+		part.u or part.d or part.i or ''


fix handling of existing previews

fix handling of existing previews

--- scripts/create_previews.coffee
+++ scripts/create_previews.coffee
@@ -6,6 +6,7 @@

 easyimg = require 'easyimage'
 resizeImage = Q.nbind easyimg.resize, easyimg
+getImageInfo = Q.nbind easyimg.info, easyimg

 mkdirp = require 'mkdirp'
 makeDirectories = Q.denodeify mkdirp
@@ -52,24 +53,29 @@

   fileExists(resizeOpts.dst).then (exists) ->
     if exists
-      process.stdout.write '-'
-      return Q.when {}
+      getImageInfo(resizeOpts.dst).then (existing) ->
+        process.stdout.write '-' unless quiet
+        existing
+    else
+      makeDirectories(path.dirname(resizeOpts.dst)).then ->
+        resizeImage(resizeOpts).spread (resized) ->
+          process.stdout.write '.' unless quiet
+          resized
+  .then (imageInfo) ->
+    medium =
+      width: parseInt imageInfo.width
+      height: parseInt imageInfo.height
+      src: dstPathOnServer

-    makeDirectories(path.dirname(resizeOpts.dst)).then ->
-      resizeImage(resizeOpts).spread (resized) ->
-        albumUpdateSemaphore.push ->
-          getAlbum(albumPath).then (album) ->
-            picture = _.find album.pictures, (pic) -> pic.path == picture.path
-            picture.media.push
-              width: parseInt resized.width
-              height: parseInt resized.height
-              src: dstPathOnServer
-            picture.media = _.sortBy picture.media, (medium) -> medium.width
-            saveAlbum(album)
-          .then ->
-            process.stdout.write '.' unless quiet
-      .fail ->
-        console.warn '\nFailed to create thumbnail:', resizeOpts.src
+    albumUpdateSemaphore.push ->
+      getAlbum(albumPath).then (album) ->
+        picture = _.find album.pictures, (pic) -> pic.path == picture.path
+        picture.media.push medium unless _.find(picture.media, (med) -> med.src == medium.src)
+        picture.media = _.sortBy picture.media, (med) -> medium.width
+        saveAlbum album
+
+  .fail ->
+    console.warn '\nFailed to create thumbnail:', resizeOpts.src

 createPreviews = (opts) ->
   {albums, sizes, concurrency, root, output, quiet} = opts


Turn github highlighted code blocks into normal markdown code blocks

--- docs/generate_website.coffee
+++ docs/generate_website.coffee
@@ -81,6 +81,11 @@
 pages = []
 for file in files
   content = fs.readFileSync file, 'utf8'
+
+  # turn github highlighted code blocks into normal markdown code blocks
+  content = content.replace /^```coffeescript\n((:?.|\n)*?)\n```/mg, (m, $1) ->
+    '    ' + $1.split('\n').join('\n    ')
+
   tree = markdown.parse(content)
   headers = extractHeaders(tree)
   generateImages(tree)


Small performance increase

Small performance increase

--- source/scripts/views/tasks.coffee
+++ source/scripts/views/tasks.coffee
@@ -191,7 +191,7 @@
       # if list.id is 'all' then task.listName = task.list().name

     # Render html
-    @ui.tasks.html html
+    @ui.tasks[0].innerHTML = html

     # Fade out animated stuff
     delay DURATION, =>


humanByteSize is now supported via Art.Foundation

--- source/Art/Engine/Core/Drawing/DrawCacheManager.coffee
+++ source/Art/Engine/Core/Drawing/DrawCacheManager.coffee
@@ -3,7 +3,7 @@
   inspect, Map, timeout, remove, log, defineModule
   getEnv
 } = require 'art-standard-lib'
-{isMobileBrowser} = (require 'art-foundation').Browser
+{humanByteSize, isMobileBrowser} = (require 'art-foundation').Browser
 {BaseClass} = require 'art-class-system'

 {
@@ -41,17 +41,6 @@
 call allocateCacheBitmap when a new cache bitmap is used
 call useCacheBitmap every time a the drawCache bitmap is used
 ###
-
-humanByteSize = (bytes) ->
-  if bytes < 2048
-    "#{bytes}bytes"
-  else
-    bytes /= 1024
-    if bytes < 2048
-      "#{bytes | 0}KB"
-    else
-      bytes /= 1024
-      "#{bytes | 0}MB"

 class CacheBitmap extends BaseClass


moved to private

moved to private

--- scripts/factoid-natural.coffee
+++ scripts/factoid-natural.coffee
@@ -1,44 +0,0 @@
-# Description
-#   Natural language queries for factoids
-#
-# Dependencies:
-#   "natural": "0.1.19"
-#
-# Configuration:
-#   None
-#
-# Commands:
-#   stemming <words> - how would natural factoid stem a set of words?
-#
-# Notes:
-#   None
-#
-# Author:
-#   ivey
-
-TextMessage = require('hubot').TextMessage
-FTNatural = require('natural')
-FTStemmer = FTNatural.PorterStemmer
-
-factoidkeys = [
-  ['merlinsupport', ['merlin', 'support']],
-  ['chefsupport', ['chef', 'support']],
-  ['runitcentos', ['chef', 'runit', 'cento']],
-  ['runitcentos', ['cookbook', 'runit', 'cento']]
-]
-
-compare = (tokens, factoidkey) ->
-  keywords = factoidkey[1]
-  contained = (word for word in keywords when word in tokens)
-  contained.length == keywords.length
-
-module.exports = (robot) ->
-  robot.respond /stemming (.*)/i, (msg) ->
-    msg.send FTStemmer.tokenizeAndStem(msg.match[1]).toString()
-
-  robot.hear /(.*)\?$/i, (msg) ->
-    stems = FTStemmer.tokenizeAndStem(msg.match[1])
-    stems.unshift msg.message.user.room.replace(/@.*/,'')
-    result = (k for k in factoidkeys when compare(stems,k))
-    if result[0]
-      robot.receive new TextMessage(msg.message.user, "~#{result[0][0]}")


Fix carousel initial rendering issue.

Fix carousel initial rendering issue.

--- apps/partner2/components/hero_artworks_carousel/view.coffee
+++ apps/partner2/components/hero_artworks_carousel/view.coffee
@@ -28,4 +28,5 @@
     return @remove() unless artworks.length >= 5

     @$el.html template artworks: artworks
-    initCarousel @$el, imagesLoaded: true
+    initCarousel @$el, wrapAround: true, imagesLoaded: true, (carousel) ->
+      carousel.cells.flickity.resize()


Ajout de la vue sur Waz

Ajout de la vue sur Waz

--- source/javascripts/SuiviSites/view/TabPanel.coffee
+++ source/javascripts/SuiviSites/view/TabPanel.coffee
@@ -15,4 +15,11 @@
       frameborder: 0
       tag: "iframe"
       src: "http://num-app1.univ-lorraine.fr/DemandeSite/admin"
+  ,
+    title: 'Web Admin Zone (WAZ)'
+    xtype : "component"
+    autoEl:
+      frameborder: 0
+      tag: "iframe"
+      src: "https://websites.univ-lorraine.fr/index.php?id=24"
   ]


newlines, bloody newlines

newlines, bloody newlines

--- spec/javascripts/util_spec.coffee
+++ spec/javascripts/util_spec.coffee
@@ -1,13 +1,13 @@
-describe 'Util', ->
-
-  it 'should not find duplicates', ->
-    loadFixtures('editable_fields.html')
-    util = new Util()
-    duplicates = util.find_duplicated_keys($('[data-cms]'))
-    expect(duplicates.length).toBe(0)
-
-  it 'should find duplicates', ->
-    loadFixtures('duplicate_fields.html')
-    util = new Util()
-    duplicates = util.find_duplicated_keys($('[data-cms]'))
-    expect(duplicates.length).toBe(1)
+describe 'Util', ->
+
+  it 'should not find duplicates', ->
+    loadFixtures('editable_fields.html')
+    util = new Util()
+    duplicates = util.find_duplicated_keys($('[data-cms]'))
+    expect(duplicates.length).toBe(0)
+
+  it 'should find duplicates', ->
+    loadFixtures('duplicate_fields.html')
+    util = new Util()
+    duplicates = util.find_duplicated_keys($('[data-cms]'))
+    expect(duplicates.length).toBe(1)


Fix age chart data labels color

Fix age chart data labels color

--- static/coffee/v2_age.coffee
+++ static/coffee/v2_age.coffee
@@ -58,7 +58,7 @@
           style: {
             fontSize: ".75rem"
             textOutline: false
-            color: 'white'
+            color: labelColor
           }
           formatter: ->
             this.y + "%"


Restablished master branch to point to guid tagging approach

--- spec/RendererSpec.coffee
+++ spec/RendererSpec.coffee
@@ -27,7 +27,7 @@

     describe '::createMarkup()', ->

-      it 'adds uid as data property', ->
+      xit 'adds uid as data property', ->
         markup = Renderer.createMarkup {header: 'AHeader', items: [], _uid: 'testguid'},
         {menu: "<menu header='{{header}}'>{{items}}</menu>",
         menuItem: "<menuitem text='{{text}}' value='{{value}}'></menuitem>"}


:art: Replace truthy check with greater than in helpers-spec

--- spec/helpers-spec.coffee
+++ spec/helpers-spec.coffee
@@ -9,7 +9,7 @@
   describe '::messageKey', ->
     it 'works', ->
       key = helpers.messageKey({type: 'error', text: ''})
-      expect(key.length).toBeTruthy()
+      expect(key.length).toBeGreaterThan(0)
     it 'generates a lowercase key', ->
       key = helpers.messageKey({type: 'error', text: ''})
       expect(key).toBe(key.toLowerCase())


Make specs nicer

Make specs nicer

Signed-off-by: Michelle Tilley <19f7a7eb6786def4c39d6d86c40af5dbc6589663@github.com>

--- spec/install-spec.coffee
+++ spec/install-spec.coffee
@@ -361,9 +361,9 @@
         it 'tries cloning the next URL until one works', ->
           install.cloneFirstValidGitUrl urls, {}, ->
           expect(install.cloneNormalizedUrl.calls.length).toBe 3
-          expect(install.cloneNormalizedUrl.calls[0].args[0]).toBe urls[0]
-          expect(install.cloneNormalizedUrl.calls[1].args[0]).toBe urls[1]
-          expect(install.cloneNormalizedUrl.calls[2].args[0]).toBe urls[2]
+          expect(install.cloneNormalizedUrl.argsForCall[0][0]).toBe urls[0]
+          expect(install.cloneNormalizedUrl.argsForCall[1][0]).toBe urls[1]
+          expect(install.cloneNormalizedUrl.argsForCall[2][0]).toBe urls[2]

     describe 'when installing a package from a git repository', ->
       [cloneUrl, pkgJsonPath] = []


resurrection.teamwork: destroy workspace itself

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -163,7 +163,7 @@
     @loader.hide()

   startNewSession: ->
-    @destroySubViews()
+    @destroy()
     options = @getOptions()
     delete options.sessionKey
     @addSubView new CollaborativeWorkspace options
@@ -184,9 +184,8 @@
     options                = @getOptions()
     options.sessionKey     = newOptions.sessionKey.trim()
     options.joinedASession = yes
-    @destroySubViews()
-
     @forceDisconnect()
+    @destroy()

     @addSubView new CollaborativeWorkspace options


Client: Teams: Added notifyPendingInvites method to notify user for pending invitations

--- client/admin/lib/views/invitations/invitesomeoneview.coffee
+++ client/admin/lib/views/invitations/invitesomeoneview.coffee
@@ -1,3 +1,4 @@
+_                   = require 'lodash'
 kd                  = require 'kd'
 remote              = require('app/remote').getInstance()
 KDView              = kd.View
@@ -7,7 +8,8 @@
 KDNotificationView  = kd.NotificationView
 InvitationInputView = require './invitationinputview'
 showError           = require 'app/util/showError'
-
+Promise             = require 'bluebird'
+async               = require 'async'

 module.exports = class InviteSomeoneView extends KDView

@@ -128,7 +130,45 @@
     modal.overlay.setClass 'second-overlay'

-  handleInvitationRequest: (invites) ->
+  notifyPendingInvites: (pendingInvites, newInvites) ->
+
+    partial = "<strong>#{pendingInvites[0].email}</strong> has already been invited. Are you sure you want to resend invitation?"
+
+    if pendingInvites.length > 1
+      emailsText = prepareEmailsText pendingInvites
+      partial = "#{emailsText} have already been invited. Are you sure you want to resend invitation?"
+
+    @resendInvitationConfirmModal = modal = new kd.ModalViewWithForms
+      title                   : 'Resend invitation'
+      overlay                 : yes
+      height                  : 'auto'
+      cssClass                : 'admin-invite-confirm-modal'
+      tabs                    :
+        forms                 :
+          confirm             :
+            buttons           :
+              "Resend Invitation" :
+                itemClass     : kd.ButtonView
+                cssClass      : 'confirm'
+                style         : 'solid green medium'
+                loader        : color: '#444444'
+                callback      : => @handleResendInvitations pendingInvites, newInvites
+              Cancel          :
+                itemClass     : kd.ButtonView
+                style         : 'solid medium'
+                callback      : => @sendInvitations newInvites
+            fields            :
+              planDetails     :
+                type          : 'hidden'
+                nextElement   :
+                  planDetails :
+                    cssClass  : 'content'
+                    itemClass : kd.View
+                    partial   : partial
+
+    modal.overlay.setClass 'second-overlay'
+
+

     remote.api.JInvitation.create invitations: invites, (err) =>
       if err


Panel: Fixes for getting panes correctly.

--- client/landing/app/CommonViews/workspace/panel/panel.coffee
+++ client/landing/app/CommonViews/workspace/panel/panel.coffee
@@ -6,8 +6,9 @@

     super options, data

-    @headerButtons = {}
-    @panes         = []
+    @headerButtons  = {}
+    @panesContainer = []
+    @panes          = []

     @createHeader()
     @createHeaderButtons()  if options.buttons?.length
@@ -41,10 +42,10 @@
         title   : @getOptions().hint

   createLayout: ->
-    @container = new KDView
-      cssClass : "panel-container"
+    @container  = new KDView
+      cssClass  : "panel-container"

-    panesLength            = @getOptions().panes.length
+    panesLength = @getOptions().panes.length
     return  unless panesLength
     panelTypeByPaneLength  =
       "1"                  : "SingleLayout"
@@ -56,7 +57,7 @@

   createPanes: ->
     for paneOptions, index in @getOptions().panes
-      @createPane paneOptions, @getPaneByIndex index
+      @createPane paneOptions, @getPaneContainerByIndex index

   createPane: (paneOptions, targetContainer) ->
     paneTypesToPaneClass =
@@ -64,17 +65,20 @@
       "editor"           : EditorPane
       "video"            : VideoPane
       "preview"          : PreviewPane
-    PaneClass            = paneTypesToPaneClass[paneOptions.type]
+    paneType             = paneOptions.type
+    PaneClass            = paneTypesToPaneClass[paneType]
+    pane                 = new PaneClass paneOptions

-    targetContainer.addSubView new PaneClass paneOptions
+    targetContainer.addSubView pane
+    @panes.push pane

   # GETTERS #
-  getPaneByIndex: (index) ->
-    return @panes[index]
+  getPaneContainerByIndex: (index) ->
+    return  @panesContainer[index]

   # LAYOUT CREATOR HELPERS #
   createSplitView: (type, views) ->
-    return new KDSplitView {
+    return  new KDSplitView {
       resizable : yes
       sizes     : ["50%", "50%"]
       type
@@ -86,7 +90,7 @@
       cssClass : "panel-container"

     @container.addSubView view
-    @panes.push view
+    @panesContainer.push view

   createDoubleLayout: ->
     pane1     = new KDView
@@ -94,7 +98,7 @@
     splitView = @createSplitView "vertical", [pane1, pane2]

     @container.addSubView splitView
-    @panes.push pane1, pane2
+    @panesContainer.push pane1, pane2

   createTripleLayout: ->
     pane1           = new KDView
@@ -104,8 +108,7 @@
     baseSplit       = @createSplitView "vertical", [pane1, rightInnerSplit]

     @container.addSubView baseSplit
-
-    @panes.push pane1, pane2, pane3
+    @panesContainer.push pane1, pane2, pane3

   createQuadrupleLayout: ->
     pane1           = new KDView
@@ -117,8 +120,7 @@
     baseSplit         = @createSplitView "vertical", [leftInnerSplit, rightInnerSplit]

     @container.addSubView baseSplit
-
-    @panes.push pane1, pane2, pane3, pane4
+    @panesContainer.push pane1, pane2, pane3, pane4

   pistachio: ->
     """


Fix typeo

Fix typeo

--- lib/assets/javascripts/config.js.coffee
+++ lib/assets/javascripts/config.js.coffee
@@ -16,7 +16,7 @@
   callback: (res, xhr) ->
     if xhr.status != 200
       return setImmediate =>
-        throw "failed to load json config via GET #{json_config_url}: #{xhr.status} #{JSON.stringify(res)}"
+        throw "failed to load json config via GET #{TentStatus.config.JSON_CONFIG_URL}: #{xhr.status} #{JSON.stringify(res)}"

     TentStatus.config ?= {}
     for key, val of JSON.parse(res)


setPristine on password fields after closing password form

--- app/components/profile-settings-page/profile-settings-page.coffee
+++ app/components/profile-settings-page/profile-settings-page.coffee
@@ -43,6 +43,8 @@
       $scope.passwordParams = {}
       $scope.passwordErrors = {}
       $scope.showPasswordFields = false
+      _.each ['current_password', 'password', 'confirm_password'], (fieldName) ->
+        $scope.accountDetailsForm[fieldName].$setPristine()

     $scope.save = ->
       $scope.formSubmitted = true


Babelify the app source too.

Babelify the app source too.

Fixes #10509.

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -146,6 +146,13 @@
       src: ['src/**/*.pegjs']
       dest: appDir
       ext: '.js'
+
+  for jsFile in glob.sync("src/**/*.js")
+    if usesBabel(jsFile)
+      babelConfig.dist.files.push({
+        src: [jsFile]
+        dest: path.join(appDir, jsFile)
+      })

   for child in fs.readdirSync('node_modules') when child isnt '.bin'
     directory = path.join('node_modules', child)


Update tooltip.coffee

Update tooltip.coffee

--- vendor/assets/javascripts/mice/tooltip.coffee
+++ vendor/assets/javascripts/mice/tooltip.coffee
@@ -110,8 +110,6 @@
         inDom =  $.contains(document.documentElement, @$element[0])
         return if e.isDefaultPrevented() or !inDom

-        that = @
-
         $tip = @tip()
         @setContent()

@@ -147,9 +145,9 @@

         @applyPlacement calculatedOffset, placement

-        complete = ->
-          that.$element.trigger('shown.mice.' + that.type)
-          that.hoverState = null
+        complete = =>
+          @$element.trigger('shown.mice.' + @type)
+          @hoverState = null

         if $.support.transition and @$tip.hasClass('fade') then $tip.one('miceTransitionEnd', complete).emulateTransitionEnd(150) else complete()

@@ -196,13 +194,12 @@
       @tip().find('.inner')[if @options.html then 'html' else 'text'](@getTitle()).removeClass('fade in top bottom left right')

     hide: ->
-      that = @
       $tip = @tip()
       e    = $.Event('hide.mice.' + @type)

-      complete = ->
-        $tip.detach() if that.hoverState != 'in'
-        that.$element.trigger('hidden.mice.' + that.type)
+      complete = =>
+        $tip.detach() if @hoverState != 'in'
+        @$element.trigger('hidden.mice.' + @type)

       @$element.trigger(e)


Add mask literate elements by default

Add mask literate elements by default

--- app/assets/javascripts/backend/accounts.js.coffee
+++ app/assets/javascripts/backend/accounts.js.coffee
@@ -2,6 +2,8 @@
   'use strict'

   $(document).ready ->
+    E.accounts.changeUnmarkVisibility()
+
     $('#lettersVisibility').on 'click', (e) ->
       E.accounts.changeUnmarkVisibility()


OX-129 bug fix

OX-129 bug fix

--- app/assets/javascripts/geometries/skeleton.coffee
+++ app/assets/javascripts/geometries/skeleton.coffee
@@ -171,7 +171,8 @@
         @activeNodeSphere.material.color.setHex(colorNormal)
     else
       sphere = @getSphereFromId(nodeID)
-      sphere.material.color.setHex(colorNormal)
+      if sphere?
+        sphere.material.color.setHex(colorNormal)
     @flycam.hasChanged = true

   setNodeRadius : (value) ->


Added mixpanel tracking for open and close actions

--- core/app/backbone/layouts/discussion_modal_container.coffee
+++ core/app/backbone/layouts/discussion_modal_container.coffee
@@ -15,10 +15,12 @@
     return unless @$el.is(event.target) || @ui.close.is(event.target)

     FactlinkApp.vent.trigger 'close_discussion_modal'
+    mp_track "Site Discussion Modal: Close"

   onRender: ->
     @$el.preventScrollPropagation()
     _.defer => @$el.addClass 'discussion-modal-container-visible'
+    mp_track "Site Discussion Modal: Open"

   fadeOut: (callback) ->
     @$el.removeClass 'discussion-modal-container-visible'


fix router following links

fix router following links

--- app/assets/javascripts/pinkman_base/router.coffee
+++ app/assets/javascripts/pinkman_base/router.coffee
@@ -291,7 +291,7 @@
       App.router = this
       window.$r = App.router
       @activate(window.location.pathname)
-      $('body').on 'click', 'a:not([data-pinkman="false"])', (ev) =>
+      $('body').on 'click', 'a:not([data-pinkman="false"],[target="blank"],[target="_blank"])', (ev) =>
         ev.preventDefault()
         path = ev.currentTarget.href
         @location(path) unless path? and @visit(path)


minor refactorings

minor refactorings

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -1,94 +1,94 @@
 #= require jquery-fileupload/basic
 #= require jquery-fileupload/vendor/tmpl

-(($) ->
-  $.fn.S3Uploader = (options) ->
+$ = jQuery

-    # support multiple elements
-    if @length > 1
-      @each ->
-        $(this).S3Uploader options
+$.fn.S3Uploader = (options) ->

-      return this
+  # support multiple elements
+  if @length > 1
+    @each ->
+      $(this).S3Uploader options

-    $uploadForm = this
+    return this

-    settings =
-      path: ''
-      additional_data: null
-      before_add: null
+  $uploadForm = this

-    settings = $.extend settings, options
+  settings =
+    path: ''
+    additional_data: null
+    before_add: null

-    current_files = []
+  $.extend settings, options

-    setUploadForm = ->
-      $uploadForm.fileupload
+  current_files = []

-        add: (e, data) ->
-          current_files.push data
-          file = data.files[0]
-          unless settings.before_add and not settings.before_add(file)
-            data.context = $(tmpl("template-upload", file))
-            $uploadForm.append(data.context)
-            data.submit()
+  setUploadForm = ->
+    $uploadForm.fileupload

-        progress: (e, data) ->
-          if data.context
-            progress = parseInt(data.loaded / data.total * 100, 10)
-            data.context.find('.bar').css('width', progress + '%')
+      add: (e, data) ->
+        current_files.push data
+        file = data.files[0]
+        unless settings.before_add and not settings.before_add(file)
+          data.context = $(tmpl("template-upload", file))
+          $uploadForm.append(data.context)
+          data.submit()

-        done: (e, data) ->
-          file = data.files[0]
-          domain = $uploadForm.attr('action')
-          path = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
-          to = $uploadForm.data('post')
-          content = {}
-          content[$uploadForm.data('as')] = domain + path + '/' + file.name
-          content.filename = file.name
-          content.filepath = path
-          if settings.additional_data
-            content = $.extend content, settings.additional_data
-          if 'size' of file
-            content.file_size = file.size
-          if 'type' of file
-            content.file_type = file.type
+      progress: (e, data) ->
+        if data.context
+          progress = parseInt(data.loaded / data.total * 100, 10)
+          data.context.find('.bar').css('width', progress + '%')

-          $.post(to, content)
-          data.context.remove() if data.context # remove progress bar
+      done: (e, data) ->
+        file = data.files[0]
+        domain = $uploadForm.attr('action')
+        path = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
+        to = $uploadForm.data('post')
+        content = {}
+        content[$uploadForm.data('as')] = domain + path + '/' + file.name
+        content.filename = file.name
+        content.filepath = path
+        if settings.additional_data
+          content = $.extend content, settings.additional_data
+        if 'size' of file
+          content.file_size = file.size
+        if 'type' of file
+          content.file_type = file.type

-          current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
-          if current_files.length == 0
-            $(document).trigger("s3_uploads_complete")
+        $.post(to, content)
+        data.context.remove() if data.context # remove progress bar

-        fail: (e, data) ->
-          alert("#{data.files[0].name} failed to upload.")
-          console.log("Upload failed:")
-          console.log(data)
+        current_files.splice($.inArray(data, current_files), 1) # remove that element from the array
+        if current_files.length == 0
+          $(document).trigger("s3_uploads_complete")

-        formData: (form) ->
-          data = form.serializeArray()
-          fileType = ""
-          if "type" of @files[0]
-            fileType = @files[0].type
-          data.push
-            name: "Content-Type"
-            value: fileType
+      fail: (e, data) ->
+        alert("#{data.files[0].name} failed to upload.")
+        console.log("Upload failed:")
+        console.log(data)

-          data[1].value = settings.path + data[1].value
+      formData: (form) ->
+        data = form.serializeArray()
+        fileType = ""
+        if "type" of @files[0]
+          fileType = @files[0].type
+        data.push
+          name: "Content-Type"
+          value: fileType

-          data
+        data[1].value = settings.path + data[1].value

-    #public methods
-    @initialize = ->
-      setUploadForm()
-      this
+        data

-    @path = (new_path) ->
-      settings.path = new_path
+  #public methods
+  @initialize = ->
+    setUploadForm()
+    this

-    @additional_data = (new_data) ->
-      settings.additional_data = additional_data
+  @path = (new_path) ->
+    settings.path = new_path

-    @initialize()
-) jQuery
+  @additional_data = (new_data) ->
+    settings.additional_data = additional_data
+
+  @initialize()


Adds new file only if before_add was successful

With this change before_add can be used to do javascript validation.
After an invalid file was not uploaded the next valid upload will
trigger 's3_uploads_complete' and not only 's3_upload_complete'

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -36,11 +36,11 @@
     $uploadForm.fileupload

       add: (e, data) ->
-        current_files.push data
         file = data.files[0]
         file.unique_id = Math.random().toString(36).substr(2,16)

         unless settings.before_add and not settings.before_add(file)
+          current_files.push data
           data.context = $(tmpl("template-upload", file).trim()) if $('#template-upload').length > 0
           $(data.context).appendTo(settings.progress_bar_target || $uploadForm)
           if settings.click_submit_target


Allow setting reporter from command line.

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -32,8 +32,8 @@

 gulp.task 'default', ->
 	console.log ''
-	console.log chalk.green "Specify a task, like #{chalk.blue 'build'} or #{chalk.blue 'watch'}."
-	console.log chalk.green "Run #{chalk.blue 'gulp --tasks'} for some hints."
+	console.log chalk.green " Specify a task, like #{chalk.blue 'build'} or #{chalk.blue 'watch'}."
+	console.log chalk.green " Run #{chalk.blue 'gulp --tasks'} for some hints."
 	console.log ''

@@ -89,6 +89,7 @@

 gulp.task 'nodeunit', ->
 	nodeunit = require 'gulp-nodeunit-runner'
+	reporter = 'minimal'
 	source = [
 		'tests/health-check.js'
 		'tests/health-check.coffee'
@@ -97,9 +98,11 @@
 	]
 	if args.single?
 		source = "tests/#{args.single}"
+	if args.reporter?
+		reporter = args.reporter
 	return gulp
 		.src source
-		.pipe nodeunit(reporter: 'minimal')
+		.pipe nodeunit(reporter: reporter)

 gulp.task 'force-exit', ->
 	process.exit 0


change limit to actual 1600

change limit to actual 1600

--- app/assets/javascripts/uploadcare/utils.js.coffee
+++ app/assets/javascripts/uploadcare/utils.js.coffee
@@ -105,7 +105,7 @@
     else
       text

-  ns.fitDimensionsWithCdnLimit = (dim, limit=1024) ->
+  ns.fitDimensionsWithCdnLimit = (dim, limit=1600) ->
     ns.fitDimensions(dim, limit, limit)

   ns.fitDimensions = (o, width, height) ->


remove console log

remove console log

--- vue/src/shared/models/discussion_model.coffee
+++ vue/src/shared/models/discussion_model.coffee
@@ -236,7 +236,6 @@
       params:
         discussion_id: @id
     .then (data) =>
-      console.log "have count", data
       @usersNotifiedCount = data.count

   forkedEvents: ->


add modules to context fns

add modules to context fns

--- src/context.coffee
+++ src/context.coffee
@@ -43,6 +43,7 @@

   collect_context_fns = (context) ->
     result = _.object _.filter _.map(context.modules, (module, name) -> [name, module.context_fns]), ([n, f]) -> f
+    result._modules = _.clone result
     _.extend result, _.map(context.imports, (i) -> context.modules[i].context_fns)...


cistrans: more explicit selector in the autocomplete call

--- cistrans/cistrans.coffee
+++ cistrans/cistrans.coffee
@@ -797,7 +797,7 @@
        .attr("class", "outerBox")

   # autocomplete
-  $('#genesymbol').autocomplete({
+  $('input#genesymbol').autocomplete({
     source: (request, response) ->
       matches = $.map(allgenes, (tag) ->
         tag if tag.toUpperCase().indexOf(request.term.toUpperCase()) is 0)


Fixing conditional to check if the current folder represents a Theoricus app or not.

--- cli/src/theoricus.coffee
+++ cli/src/theoricus.coffee
@@ -86,16 +86,16 @@
     current = path.resolve "."

     while true
-      app = path.normalize "#{current}/app/app_controller.coffee"
+      app = path.normalize "#{current}/src/app/app.coffee"

       if fs.existsSync app
         contents = fs.readFileSync app, "utf-8"
-        if contents.indexOf( "theoricus.mvc.Controller" ) > 0
+        if contents.indexOf( 'extends Theoricus' ) > 0
           return current
         else
           return null
       else
-        tmp = path.normalize path.resolve("#{current}/../")
+        tmp = path.normalize (path.join current, '..')
         if current == tmp
           return null
         else


Don't trim account setting fields

Don't trim account setting fields

--- resources/assets/coffee/_classes/account-edit.coffee
+++ resources/assets/coffee/_classes/account-edit.coffee
@@ -63,7 +63,7 @@
       if input.type == 'checkbox'
         value = input.checked
       else
-        value = input.value.trim()
+        value = input.value

     { value, prevValue }


Hide preview area on empty body

Hide preview area on empty body

--- resources/assets/coffee/_classes/post-preview.coffee
+++ resources/assets/coffee/_classes/post-preview.coffee
@@ -32,6 +32,10 @@
     $preview = $form.find('.js-post-preview--body')
     $previewBox = $form.find('.js-post-preview--box')

+    if body == ''
+      $previewBox.addClass 'hidden'
+      return
+
     return if $preview.attr('data-raw') == body

     $.post(url, text: body)


Utils: update the domainWithTLDPattern for existing domains.


Fix syntax

Fix syntax

--- src/Notifications.coffee
+++ src/Notifications.coffee
@@ -88,10 +88,11 @@
   # Run when notifications are added.
   collection.find()
   return unless readAllDate?
-  collection.update
-    {eventId: {$exists: true}, dateRead: {$exists: false}, dateCreated: {$lte: readAllDate}},
-    {$set: dateRead: readAllDate},
-    {multi: true}
+  selector =
+    eventId: {$exists: true}
+    dateRead: {$exists: false}
+    dateCreated: {$lte: readAllDate}
+  collection.update selector, {$set: dateRead: readAllDate}, {multi: true}

 currentId = new ReactiveVar()
 Tracker.autorun ->


Fix autoscroll behavior

Fix autoscroll behavior

* $('body').scrollTop() seems to always return 0 now;
  use $(window).scrollTop() instead.
* $('body').animate() replaced by $('html, body').animate().
* Router.onAfterAction with Meteor.setTimeout works when
  transitioning between messages (same route, different args),
  whereas onRendered did not trigger.
* Don't measure scrolling during a transition between
  onBeforeAction and onAfterAction.

--- client/autoscroll.coffee
+++ client/autoscroll.coffee
@@ -5,19 +5,26 @@

 lastURL = null
 pastTops = {}
+transitioning = false

 $(window).scroll ->
+  return if transitioning
   lastURL = document.URL
-  pastTops[lastURL] = $('body').scrollTop()
+  pastTops[lastURL] = $(window).scrollTop()
+  #console.log lastURL, $(window).scrollTop()

-## Find all templates that correspond to routes
-for route in Router.routes
-  template = route.options.template ? route.options.name
-  Template[template].onRendered ->
+Router.onBeforeAction ->
+  transitioning = true
+  @next()
+
+Router.onAfterAction ->
+  Meteor.setTimeout ->
+    transitioning = false
     url = document.URL
     #console.log url, lastURL, pastTops[url] or 0
     return if url == lastURL
     lastURL = url
-    $('body').animate
+    $('html, body').animate
       scrollTop: pastTops[url] or 0
     , 200
+  , 100


Added checking for the `DomReplayIgnore`-attribute in order to allow specified elements to be ignored when recording events. Closes #14

--- src/coffee/storage.coffee
+++ src/coffee/storage.coffee
@@ -12,6 +12,10 @@

     if not @record_input
       @util.debug "cancelling storage due to @record_input being false"
+      return
+
+    if element.hasAttribute "DomReplayIgnore"
+      @util.error 'Cannot track ignored element'
       return

     if not element or not element.id


Add component to display approvable labels


Hide postcode - not necessary as passed from billing address and wrecks mobile UX

--- app/assets/javascripts/darkswarm/directives/stripe_elements.js.coffee
+++ app/assets/javascripts/darkswarm/directives/stripe_elements.js.coffee
@@ -10,7 +10,7 @@
       stripe = $injector.get('stripeObject')

       card = stripe.elements().create 'card',
-        hidePostalCode: false
+        hidePostalCode: true
         style:
           base:
             fontFamily: "Roboto, Arial, sans-serif"


track x-forwarded-for header, not IP

track x-forwarded-for header, not IP

the connection IP is the proxy IP address, which is useless to us

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -90,7 +90,7 @@

   {JSession} = koding.models
   {clientId} = req.cookies
-  clientIPAddress = req.connection.remoteAddress
+  clientIPAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress
   res.cookie "clientIPAddress", clientIPAddress, { maxAge: 900000, httpOnly: false }
   JSession.updateClientIP clientId, clientIPAddress, (err)->
     if err then console.log err


Fix sass compiler.

Fix sass compiler.

--- src/compilers/sass.coffee
+++ src/compilers/sass.coffee
@@ -31,18 +31,27 @@
     return

-module.exports = (src, dst, cb) ->
+module.exports = (opts, cb) ->
   sass = requireTry 'node-sass'

+  includePaths = [
+    path.join process.cwd(), 'node_modules'
+    path.join path.dirname opts.filename
+  ]
+
+  try
+    bourbon = require 'node-bourbon'
+    includePaths.push bourbon.includePaths
+  catch err
+
   sass.render
-    file: src
-    # importer: resolveNpm
-    includePaths: [path.join process.cwd(), 'node_modules' ]
+    data:         opts.source
+    importer:     resolveNpm
+    includePaths: includePaths
     outputStyle: 'nested'
   , (err, res) ->
     throw err if err?

-    fs.writeFile dst, res.css, (err) ->
-      throw err if err?
-
-      cb null, true
+    cb null, """
+    module.exports = #{JSON.stringify res.css}
+    """


Remove unused method

Remove unused method

--- src/develop.coffee
+++ src/develop.coffee
@@ -36,17 +36,6 @@
     options.alias('h', 'help').describe('help', 'Print this usage message')

   showHelp: (argv) -> @parseOptions(argv).showHelp()
-
-  loadNpm: (callback) ->
-    npmOptions =
-      loglevel: 'silent'
-      userconfig: config.getUserConfigPath()
-
-    npm.load npmOptions, (error) ->
-      if error?
-        callback(error)
-      else
-        callback(null, npm)

   getRepositoryUrl: (packageName, callback) ->
     auth.getToken (error, token) ->


made "auto-close blocks" configurable

made "auto-close blocks" configurable

--- client/prefs-code.coffee
+++ client/prefs-code.coffee
@@ -3,11 +3,7 @@

 @name = 'Code'

-$acb = # checkbox for "Auto-close brackets"
-$ai  = # checkbox for "Auto-indent"
-$sw  = # input box for the amount of indent (in Vim they refer to this as the "shiftwidth" or "sw")
-$aim = $swm = # like $ai and $sw but for methods
-$fold = null
+$ai = $sw = $aim = $swm = $acbr = $acbl = $fold = null

 updateAutoIndentFields = ->
   $sw.add($aim).prop 'disabled', !$ai.is ':checked'
@@ -16,25 +12,36 @@

 @init = ($e) ->
   $e.html '''
-
<input id=code-acb  type=checkbox>Auto-close brackets: <tt>{}[]()</tt>

<input id=code-ai   type=checkbox>Auto-indent <input id=code-sw  size=1> spaces

<input id=code-aim  type=checkbox>in methods: <input id=code-swm size=1> spaces
+
<input id=code-acbr type=checkbox>Auto-close brackets: <tt>{}[]()</tt>
+
<input id=code-acbl type=checkbox>Auto-close blocks: <tt>:if :for ...</tt>

<input id=code-fold type=checkbox>Code folding
   '''
-  $acb = $ '#code-acb'; $ai = $ '#code-ai'; $sw = $ '#code-sw'; $aim = $ '#code-aim'; $swm = $ '#code-swm'
+  $ai   = $ '#code-ai'
+  $sw   = $ '#code-sw'
+  $aim  = $ '#code-aim'
+  $swm  = $ '#code-swm'
+  $acbr = $ '#code-acbr'
+  $acbl = $ '#code-acbl'
   $fold = $ '#code-fold'
   $ai.add($aim).change updateAutoIndentFields
   $sw.add($swm).click -> $(@).select(); return
   return

 @load = ->
-  $acb.prop 'checked', !!prefs.autoCloseBrackets(); $fold.prop 'checked', !!prefs.fold()
   sw  = prefs.indent();        $ai .prop 'checked', sw  >= 0; $sw .val sw  < 0 && 4 || sw
   swm = prefs.indentMethods(); $aim.prop 'checked', swm >= 0; $swm.val swm < 0 && 2 || swm
-  updateAutoIndentFields(); return
+  $acbr.prop 'checked', !!prefs.autoCloseBrackets()
+  $acbl.prop 'checked', !!prefs.autoCloseBlocks()
+  $fold.prop 'checked', !!prefs.fold()
+  updateAutoIndentFields()
+  return

 @save = ->
-  prefs.autoCloseBrackets $acb.is ':checked'; prefs.fold $fold.is ':checked'
+  prefs.autoCloseBrackets $acbr.is ':checked'
+  prefs.autoCloseBlocks   $acbl.is ':checked'
   prefs.indent        if $ai .is ':checked' then +$sw .val() || 0 else -1
   prefs.indentMethods if $aim.is ':checked' then +$swm.val() || 0 else -1
+  prefs.fold $fold.is ':checked'
   return


CoffeeLint-suggested cleanup

CoffeeLint-suggested cleanup

--- app/assets/javascripts/app/reports.coffee
+++ app/assets/javascripts/app/reports.coffee
@@ -21,33 +21,34 @@
     current_tab = @$tabs.find('[role=tab]')[index]

   init_tabs: ->
-    @$tabs.find('a').each ->
-      $(this).parent('li').data('base-href', $(this).attr('href'))
+    @$tabs.find('a').each (_, tab_link) ->
+      $tab_link = $(tab_link)
+      $tab_link.parent('li').data('base-href', $tab_link.attr('href'))

-    self = this
-    @$tabs.tabs({
+    @$tabs.tabs(
       active: window.activeTab
-      beforeActivate: (event, ui) ->
+      beforeActivate: (_, ui) =>
         # if there was an old error message, fade it out now
         $('#error-msg').fadeOut()
-
-        self.update_href ui.newTab
+        @update_href(ui.newTab)
         true

-      beforeLoad: (event, ui) ->
+      beforeLoad: (_, ui) ->
         # Show a loading message so the user sees immediate feedback
         # that their action is being applied
         ui.panel.html(' Loading...')
         ui.ajaxSettings.dataType = 'text/html'
         ui.jqXHR.error (xhr, status, error) ->
-          # don't show error message if it's because of user aborting ajax request
+          # don't show error message if the user aborted the ajax request
           if status != 'abort'
-            $('#error-msg').html('Sorry, but the tab could not load. Please try again soon.').show()
+            $('#error-msg').
+              html('Sorry, but the tab could not load. Please try again soon.').
+              show()

-      load: (event, ui) ->
-        self.fix_bad_dates(ui.panel)
-        self.update_export_urls()
-    })
+      load: (_, ui) =>
+        @fix_bad_dates(ui.panel)
+        @update_export_urls()
+    )

   build_query_string: ->
     "?" + @$element.serialize()
@@ -58,9 +59,7 @@
   init_form: ->
     $('#status_filter').chosen() if $('#status_filter').length
     $('.datepicker').datepicker()
-    self = this
-    @$element.find(':input').change ->
-      self.update_parameters()
+    @$element.find(':input').change => @update_parameters()

   update_href: (tab) ->
     tab.find('a').attr('href', @tab_url(tab))
@@ -71,11 +70,10 @@
     $('#date_end').val($(panel).find('.updated_values .date_end').text())

   init_pagination: ->
-    self = this
-    $(document).on 'click', '.pagination a', (evt) ->
+    $(document).on 'click', '.pagination a', (evt) =>
       evt.preventDefault()
-      $(self.current_tab()).find('a').attr('href', $(this).attr('href'))
-      self.refresh_tab()
+      $(@current_tab()).find('a').attr('href', $(evt.target).attr('href'))
+      @refresh_tab()

   update_export_urls: ->
     url = @tab_url(@current_tab())
@@ -89,13 +87,17 @@
       .click (event) => @export_all_email_confirm(event)

   export_all_email_confirm: (event) ->
-    new_to = prompt 'Have the report emailed to this address:', @$emailToAddressField.val()
+    new_to = prompt(
+      'Have the report emailed to this address:'
+      @$emailToAddressField.val()
+    )

     if new_to
       @$emailToAddressField.val(new_to)
       @update_export_urls()
       # Actual sending handled by remote: true
-      Flash.info("A report is being prepared and will be emailed to #{new_to} when complete")
+      Flash.info("A report is being prepared and will be emailed to #{new_to}
+        when complete")
     event.preventDefault()

 $ ->


downcase autocomplete for a Siteseeker bug

--- app/assets/javascripts/masthead.js.coffee
+++ app/assets/javascripts/masthead.js.coffee
@@ -114,7 +114,7 @@
         $.ajax
           url: $searchField.attr("data-autocomplete-url").replace("http:", location.protocol)
           data:
-            q: request.term
+            q: request.term.toLowerCase()
             ilang: 'sv'
           dataType: "jsonp"
           jsonpCallback: "results"


OX-55 Bug fix

OX-55 Bug fix

--- app/assets/javascripts/model/route.coffee
+++ app/assets/javascripts/model/route.coffee
@@ -438,6 +438,8 @@
     if @activeNode
       @activeNode.remove(id)
       @lastActiveNodeId = @activeNode.id
+    else
+      @deleteActiveTree()
     @push()

   deleteActiveTree : ->


Admin: Integrations: Destroy loader when error occured.

--- client/admin/lib/views/integrations/adminintegrationparentview.coffee
+++ client/admin/lib/views/integrations/adminintegrationparentview.coffee
@@ -35,7 +35,7 @@
       return @handleError err  if err

       @addSubView @mainView = new AdminIntegrationSetupView {}, data
-      @loader.destroy()
+      @loader?.destroy()

   handleConfigure: ->
@@ -49,7 +49,7 @@
       return @handleError err  if err

       @addSubView @mainView = new AdminIntegrationDetailsView {}, data
-      @loader.destroy()
+      @loader?.destroy()

   handleError: (err) ->
@@ -61,6 +61,7 @@
     if err.message is 'Not found'
       partial = 'There is no integration related with this identifier.'

+    @loader?.destroy()
     @addSubView @mainView = new kd.CustomHTMLView { partial, cssClass: 'error-view' }


Refactor create comment modal js action.

--- app/assets/javascripts/undersky.js.coffee
+++ app/assets/javascripts/undersky.js.coffee
@@ -263,17 +263,22 @@
       caption_data = panel.find('.caption').children()
       comments_data = panel.find('.comments-data').children()
       container = $('

')
-      form = $('<form action="' + self.attr('href') + '" method="post" data-remote="true"></form>')
-      header = $('
comment
')
-      body = $('
<textarea name="text" rows="4" cols="50" required="required"></textarea>
')
-      footer = $('

')
-      footer.append('
<input class="btn primary" name="commit" type="submit" value="comment" disabled="disabled" />
')
-      footer.append('
<input class="btn" name="cancel" type="reset" value="cancel" />
')
-      form.append(header, body, footer)
+      form = $([
+        '<form action="' + self.attr('href') + '" method="post" data-remote="true">',
+        '
comment
',
+        '
',
+        '    <textarea name="text" rows="4" cols="50" required="required"></textarea>',
+        '
',
+        '
',
+        '
<input class="btn primary" name="commit" type="submit" value="comment" disabled="disabled" />
',
+        '
<input class="btn" name="cancel" type="reset" value="cancel" />
',
+        '
',
+        '</form>',
+      ].join("\n"))
       if caption_data.size() > 0
         caption = $('

')
         caption.append(caption_data.clone())
-      form.append(caption)
+        form.append(caption)
       if comments_data.size() > 0
         comments = $('

')
         comments.append(comments_data.clone())


Fix growl top amount.

Fix growl top amount.

--- app/assets/javascripts/undersky.js.coffee
+++ app/assets/javascripts/undersky.js.coffee
@@ -12,7 +12,7 @@
         type: level
         offset:
           from: 'top'
-          amount: 60
+          amount: 20

   class Spinner
     constructor: (element) ->


working multi filters using computed property and isFiltering to determine

--- app/mixins/filter-params.coffee
+++ app/mixins/filter-params.coffee
@@ -26,40 +26,28 @@
       @set(filterName,filterValue)

     @get('_multiFilters').forEach (multiFilter) =>
-      @set("#{multiFilter}Selected",Ember.A([]))
-      @addObserver(multiFilter,@,'multiFilterParamObserver')
-      @addObserver("#{multiFilter}Selected.@each",@,'multiFilterSelectedObserver')
+      @set(multiFilter,null)
+      @setupMultiFilter(multiFilter)

     return

-  multiFilterParamObserver: (sender, key) ->
-    multiFilter = key
-    multiFilterSelected = Ember.A([])
-    filterParam = @get(multiFilter)
+  setupMultiFilter: (key) ->
+    Ember.run.next(@,->
+      multiFilter = key
+      multiFilterSelected = Ember.A([])
+      filterParam = @get(multiFilter)

-    if !!filterParam
+      filterParams = filterParam?.split(',') || []
       @_allFilters(multiFilter).then((multiFilters) =>
-        filterParams = if Ember.isArray(filterParam) then filterParam else filterParam.split(',')
-        filterParams.forEach (fp) =>
-          multiFilterSelected.pushObject(multiFilters.findBy('id',fp))
+        multiFilters.forEach (filter) =>
+          filter.set('isFiltering',filterParams?.contains(filter.get('id')))
       ).finally =>
+        @set("#{multiFilter}Selected",Ember.computed("#{@get('multiFilters')[multiFilter]}.@each.isFiltering",@multiFilterSelected))
+        @set("#{multiFilter}AllSelected",Ember.computed("#{@get('multiFilters')[multiFilter]}.@each.isFiltering",@multiFilterAllSelected))
+        @addObserver("#{multiFilter}Selected.@each.id",@,'multiFilterSelectedObserver')
         @removeObserver(multiFilter)
-        @set("all#{filterParam.capitalize()}Selected",@_allSelected(filterParam))
-        @set("#{multiFilter}Selected",multiFilterSelected)
-
-  multiFilterSelectedObserver: (sender,key) ->
-    filterParam = key.replace('Selected.@each','')
-    filterSelected = @get(key.replace('.@each',''))
-
-    @set("all#{filterParam.capitalize()}Selected",@_allSelected(filterParam))
-
-    if !!filterSelected
-      filterSelected.setEach('isFiltering',true)
-      filter = filterSelected.mapBy('id')
-      if filter.length is 0
-        @set(filterParam,undefined)
-      else
-        @set(filterParam,filter)
+        @addObserver(multiFilter,@,'multiFilterObserver')
+    )

   filterSelected: (key,value,prevValue) ->
     key = key.replace('Selected','')
@@ -76,8 +64,28 @@
       @get(key).then (filters) =>
         filters.findBy('id',filterId)

-  _allSelected: (filterParam) ->
-    @_allFilters(filterParam).isEvery('isFiltering')
+  multiFilterSelected: (key,value,prevValue) ->
+    key = key.replace('Selected','')
+    @_allFilters(key).filterBy('isFiltering')
+
+  multiFilterObserver: (sender, multiFilter) ->
+    filterParams = @get(multiFilter)?.split(',') || []
+    @_allFilters(multiFilter).then (multiFilters) =>
+      multiFilters.forEach (filter) =>
+        filter.set('isFiltering',filterParams?.contains(filter.get('id')))
+
+  multiFilterSelectedObserver: (sender, key) ->
+    key = key.replace('.@each.id','')
+    multiFilterSelected = @get(key)
+    key = key.replace('Selected','')
+    newValue = multiFilterSelected.mapBy('id').toString()
+    newValue = if newValue.length then newValue else null
+    @set(key,newValue)
+
+  multiFilterAllSelected: (key,value,prevValue) ->
+    key = key.replace('AllSelected','')
+    @_allFilters(key).isEvery('isFiltering')
+

   _allFilters: (filterName) ->
     @get(@get('multiFilters')[filterName])
@@ -85,28 +93,17 @@
   _selectMultiple: (filterName, filters) ->
     @_allFilters(filterName).setEach('isFiltering',false)
     filters.setEach('isFiltering',true)
-    @set("#{filterName}Selected",filters)
     return

   _select: (filterName, filter) ->
     filter.set('isFiltering',!filter.get('isFiltering'))
-    filters = @get("#{filterName}Selected")
-    if !!filters
-      if filter.get('isFiltering')
-        filters.pushObject(filter)
-      else
-        filters.removeObject(filter)
     return

   actions:
     selectAll: (filterName) ->
       allFilters = @_allFilters(filterName)
-      allSelected = @_allSelected(filterName)
+      allSelected = @get("#{filterName}AllSelected")
       allFilters.setEach('isFiltering',!allSelected)
-      if allSelected
-        @set("#{filterName}Selected",[])
-      else
-        @set("#{filterName}Selected",allFilters.get('content'))
     select: (filterName, filters) ->
       if Ember.isArray(filters)
         @_selectMultiple(filterName, filters)


enhance: filter by platform also

enhance: filter by platform also

--- app/components/project-preferences.coffee
+++ app/components/project-preferences.coffee
@@ -20,14 +20,17 @@
     .then (data) ->
       if platform is ENUMS.PLATFORM.ANDROID
         versions = that.set "versions", data
-        that.set "availableDevices", versions.devices.filterBy("is_tablet",true)
+        that.set "availableDevices", versions.devices.filterBy("platform", 0).filterBy("is_tablet",true)
+        debugger

       else if platform is ENUMS.PLATFORM.IOS
         versions = that.set "versions", data
-        that.set "availableDevices", versions.devices.filterBy("is_tablet",true)
+        that.set "availableDevices", versions.devices.filterBy("platform", 1).filterBy("is_tablet",true)
+        debugger
       else
         versions = that.set "versions", data
-        that.set "availableDevices", versions.devices.filterBy("is_tablet",true)
+        that.set "availableDevices", versions.devices.filterBy("platform", 1).filterBy("is_tablet",true)
+        debugger
     .catch (error) ->
       that.get("notify").error "failed"
       if Ember.isEmpty error?.errors


Revert "revert Logo to test if bug caused by SVG"

This reverts commit 238df23a61cbddfca3cfdd461445eef12e632e9e.


Button: Support CTRL+ALT+Click for group wise activate / deactivate.

--- src/elements/Button/Button.coffee
+++ src/elements/Button/Button.coffee
@@ -681,6 +681,27 @@
 		else
 			@deactivate(flags, event)

+	__callOnGroup: (call, flags, event) ->
+
+		group = @getGroup()
+		if not group or not event?.ctrlKey() or flags.ignore_ctrl
+			return
+
+		flags.ignore_ctrl = true
+		if not event.altKey()
+			started = true
+		else
+			started = false
+
+		for btn in @getGroupButtons()
+			if btn == @
+				started = true
+				continue
+			if not started
+				continue
+			btn[call](flags, event)
+		return
+
 	activate: (flags={}, event) ->
 		# console.error "activate", flags, @getUniqueId(), @__active, @_activate_initial

@@ -688,15 +709,7 @@
 			@addClass(@_active_css_class)
 			@setAria("pressed", true)
 			@__setState()
-			group = @getGroup()
-			if not group or not event?.ctrlKey() or flags.ignore_ctrl
-				return
-
-			flags.ignore_ctrl = true
-			for btn in @getGroupButtons()
-				if btn == @
-					continue
-				btn.activate(flags, event)
+			@__callOnGroup("activate", flags, event)
 			return

 		if @_activate_initial == false and flags.initial_activate
@@ -724,6 +737,7 @@
 		activate()
 		@

+
 	deactivate: (flags={}, event) ->
 		# console.error "deactivate", flags, @getUniqueId(), @__active, @_activate_initial, @_icon_inactive

@@ -731,15 +745,8 @@
 			@removeClass(@_active_css_class)
 			@setAria("pressed", false)
 			@__setState()
-			group = @getGroup()
-			if not group or not event?.ctrlKey() or flags.ignore_ctrl
-				return
-
-			flags.ignore_ctrl = true
-			for btn in @getGroupButtons()
-				if btn == @
-					continue
-				btn.deactivate(flags, event)
+			@__callOnGroup("deactivate", flags, event)
+			return

 		if @_activate_initial == false and flags.initial_activate
 			@__active = false
@@ -951,4 +958,3 @@
 CUI.Events.registerEvent
 	type: ["show", "hide", "cui-button-click"]
 	bubble: true
-


fixed main.cofee

fixed main.cofee

--- app/coffeescripts/main.coffee
+++ app/coffeescripts/main.coffee
@@ -1,7 +1,6 @@
-# Originating load file: main.html.  This set of functions and
-# collections is used to load all the javascript classes and libraries
-# into memory.  It also instantiates the AppView class and begin the
-# rendering process.
+# Originating load file: main.html.
+# This set of functions and collections is used to load all the javascript classes and libraries into memory.
+# It also instantiates the AppView class and begin the rendering process.
 load_sirius_classes = (after) ->
   head.js "js/Sirius.js", ->
     class_paths = _.map(window.sirius.sirius_classes_without_extensions, (cname) -> "js/#{cname}.js")
@@ -15,21 +14,21 @@
     head.js.apply(@, class_paths)

 window.load_sirius = ->
-  head.js "js/Sirius.js",
-          'js/menu-data.js',
-          'js/sirius-classes-load.js', ->
-            load_sirius_classes ->
-               # static instance level event aggegator that most
-               # classes use to register their own listeners on
-               new window.sirius.AppView()
+    head.js "js/Sirius.js",
+            'js/menu-data.js',
+            'js/sirius-classes-load.js', ->
+              load_sirius_classes ->
+                  # static instance level event aggegator that most classes use to register their
+                  # own listeners on
+                  new window.sirius.AppView()

 head.js 'https://www.google.com/jsapi',
-        '../libs/js/jquery-1.7.1.js',
-        '../libs/js/jquery-ui-1.8.21/js/jquery-ui-1.8.21.min.js',
-        '../libs/js/underscore.js',
-        '../libs/js/backbone.js',
-        '../libs/js/d3.v2.js',
-        '../libs/js/bootstrap/js/bootstrap.min.js', ->
-           google.load "maps", "3",
-             callback: "window.load_sirius()",
-             other_params: "libraries=geometry,drawing&sensor=false"
+        '../lib/js/jquery-1.7.1.js',
+        '../lib/js/jquery-ui-1.8.21/js/jquery-ui-1.8.21.min.js',
+        '../lib/js/underscore.js',
+        '../lib/js/backbone.js',
+        '../lib/js/bootstrap/js/bootstrap.min.js', ->
+               google.load "maps", "3",
+                  callback: "window.load_sirius()",
+                  other_params: "libraries=geometry,drawing&sensor=false"
+


Output: Support "markdown" in value, not just text and placeholder.

--- src/elements/Output/Output.coffee
+++ src/elements/Output/Output.coffee
@@ -14,7 +14,7 @@
 				check: String
 			text:
 				check: String
-			# set to yes for placeholder and text
+			# set to yes for placeholder and text and value
 			markdown:
 				mandatory: true
 				default: false
@@ -49,17 +49,14 @@
 	# enable: ->
 	# 	@removeClass("cui-data-field-disabled")

-	setText: (txt, markdown = null) ->
+	setText: (txt) ->
 		if CUI.util.isEmpty(txt)
 			@__textSpan.addClass("cui-output-empty")
 			txt = @_placeholder
 		else
 			@__textSpan.removeClass("cui-output-empty")

-		if markdown == null
-			@__textSpan.setText(txt, @_markdown)
-		else
-			@__textSpan.setText(txt, markdown)
+		@__textSpan.setText(txt, @_markdown or false)

 	checkValue: ->
 		# CUI.Output does not care about values
@@ -75,7 +72,7 @@
 			if CUI.util.isContent(ret)
 				@replace(ret)
 			else
-				@setText(ret, false) # don't use markdown for data driven output
+				@setText(ret)
 		@

 	getValue: ->