Supporting multiple template names to be specified in cell-templates

--- src/directives/table_view_components.coffee
+++ src/directives/table_view_components.coffee
@@ -121,6 +121,8 @@

   compile: (element, attr, linker) ->
     ($scope, $element, $attr, controllers) ->
-      templateName = $attr.for or $attr.cellTemplate or "?" # == default
-      controllers[1].cellTemplates[templateName] = [$element, linker, $attr]
+      templateNames =
+        if $attr.macCellTemplate then $attr.macCellTemplate.split() else ["?"]
+      for templateName in templateNames
+        controllers[1].cellTemplates[templateName] = [$element, linker, $attr]
 ]


new rules for field probability, new threshold for moves bonus

--- src/coffeescript/game.coffee
+++ src/coffeescript/game.coffee
@@ -114,7 +114,14 @@

     resetRandoms: () ->
         # choose random value and random direction
-        @value = randomChoice((k for k of @points))
+        # most common is gray field, most rare is red one.
+        points = [
+            'gray', 'gray', 'gray', 'gray',
+            'blue', 'blue', 'blue',
+            'green', 'green',
+            'red'
+        ]
+        @value = randomChoice(points)
         @direction = randomChoice(['left', 'right', 'up', 'down'])
         if @widget?
             @widget.reset()
@@ -626,10 +633,12 @@

         @level.score += @movePoints
         @level.scoreDiff = @movePoints
-        # console.log @moveLength
-        boardQuarter = Math.round((@board.size*@board.size)/4)
-        if @moveLength >= boardQuarter
-            @level.movesDiff = Math.round((@moveLength - boardQuarter)/2) + 1
+
+        # threshold depends on current score, so more points you have -> longer path
+        # you have to create to get moves bonus
+        threshold = Math.floor(@level.score / (@board.size*20)) + @board.size - 1
+        if @moveLength >= threshold
+            @level.movesDiff = @moveLength - threshold
             @level.moves += @level.movesDiff
         @topBarWidget.update()


code cleanup

code cleanup

--- client/landing/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Groups.kdapplication/AppController.coffee
@@ -665,9 +665,8 @@
       groupView.lazyBound 'assureTab', 'Members', GroupsMemberPermissionsView

     groupView.on 'MembershipPolicySelected',
-      groupView.lazyBound 'assureTab', 'Membership policy', GroupMembershipPolicyTabView
-      , (pane, view)=>
-        @prepareMembershipPolicyTab group, view
+      groupView.lazyBound 'assureTab', 'Membership policy', GroupMembershipPolicyTabView,
+        (pane, view)=> @prepareMembershipPolicyTab group, view

     contentDisplayController.emit "ContentDisplayWantsToBeShown", groupView
     callback groupView


Add missing KodingError, remove trailing whitespaces, remove debugger

--- client/landing/app/Applications/Topics.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Topics.kdapplication/AppController.coffee
@@ -166,7 +166,7 @@
     options.sort  or= "counts.followers": -1
     selector        = options.selector
     delete options.selector if options.selector
-
+
     if selector
       KD.remote.api.JTag.byRelevance selector, options, callback
     else


Add label and description to grid() output

--- src/core/modules/plot.coffee
+++ src/core/modules/plot.coffee
@@ -236,7 +236,7 @@
     go null, Flow.HTML.render 'div', el

   renderText = (config, go) ->
-    [ table, thead, tbody, tr, th, td, tdr ] = Flow.HTML.template 'table', '=thead', 'tbody', 'tr', '=th', '=td', '=td.rt'
+    [ grid, h4, p, table, thead, tbody, tr, th, td, tdr ] = Flow.HTML.template '.grid', '=h4', '=p', 'table', '=thead', 'tbody', 'tr', '=th', '=td', '=td.rt'

     ths = for column in config.data.columns
       th escape column.name
@@ -257,9 +257,13 @@
       tr tds

     go null, Flow.HTML.render 'div',
-      table [
-        thead tr ths
-        tbody trs
+      grid [
+        h4 config.data.label
+        p config.data.description
+        table [
+          thead tr ths
+          tbody trs
+        ]
       ]

   initialize = (config) ->


Move message onto new line for spacing

Move message onto new line for spacing

--- src/course/confirm-join.cjsx
+++ src/course/confirm-join.cjsx
@@ -27,7 +27,10 @@

   render: ->
     label = if @props.optionalStudentId
-      "Update school issued ID (leave blank to leave unchanged):"
+
+        Update school issued ID

+        (leave blank to leave unchanged):
+
     else
       "Enter your school issued ID:"


Prettify serialized notebooks before saving

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -312,7 +312,7 @@
   requestPutObject = (type, name, value, go) ->
     uri = "/3/NodePersistentStorage.json/#{encodeURIComponent type}"
     uri += "/#{encodeURIComponent name}" if name
-    doPost uri, { value: JSON.stringify value }, unwrap go, (result) -> result.name
+    doPost uri, { value: JSON.stringify value, null, 2 }, unwrap go, (result) -> result.name

   requestUploadObject = (type, name, formData, go) ->
     uri = "/3/NodePersistentStorage.json/#{encodeURIComponent type}"


NDPCommentView => CommentView

NDPCommentView => CommentView

--- core/app/backbone/views/evidence/ndp_fact_relation_or_comment_view.coffee
+++ core/app/backbone/views/evidence/ndp_fact_relation_or_comment_view.coffee
@@ -1,4 +1,4 @@
-class NDPCommentView extends Backbone.Marionette.ItemView
+class CommentView extends Backbone.Marionette.ItemView
   className: 'discussion-evidenceish-text'
   template: 'evidence/ndp_comment'

@@ -17,7 +17,7 @@
     @headingRegion.show new NDPEvidenceishHeadingView model: @model.creator()

     if @model instanceof Comment
-      @contentRegion.show new NDPCommentView model: @model
+      @contentRegion.show new CommentView model: @model
     else if @model instanceof FactRelation
       @contentRegion.show @_factBaseView()
     else


Fixes #8. Fixes warning for adding bot to private group.

--- src/hubot-slack-relay.coffee
+++ src/hubot-slack-relay.coffee
@@ -98,7 +98,7 @@
       relays.push @newRelay
       saveBrain()

-      if channel.is_member
+      if @newRelay.remote.client.self.id not in channel.members and not channel.is_im
         @newResponse.send 'Successfully connected to the remote relay and channel \'' + @newRelay.remoteRoom + '\'.'
       else
         @newResponse.send 'Successfully connected to the remote relay but ' + remoteBot + ' needs to be invited to \'' + @newRelay.remoteRoom + '\'.'


js fixes

js fixes

--- vendor/assets/javascripts/aced-api.js.coffee
+++ vendor/assets/javascripts/aced-api.js.coffee
@@ -3,6 +3,8 @@
   ($) ->
     methods = {
       init: (options) ->
+        options = {} unless options
+
         unless options['id']
           if @.attr('id').length > 0
             options['id'] = @.attr 'id'
@@ -15,7 +17,7 @@
         @.data 'aced_editor', editor

         delete options['id']
-        @.aced_conf options
+        @.aced 'conf', options
       ,
       conf: (options) ->
         unless @.data('aced_editor')
@@ -28,6 +30,7 @@
         if options['mode']
           editorMode = require("ace/mode/#{options['mode']}").Mode
           editor.getSession().setMode(new editorMode())
+        @
     }

     $.fn.aced = (method) ->
@@ -37,5 +40,5 @@
         return methods.init.apply @, arguments
       else
         $.error 'No method error'
-
+
 )(jQuery)


Fixed some stray path exists in the plugin loader

--- src/lib/plugin-loader.coffee
+++ src/lib/plugin-loader.coffee
@@ -1,6 +1,5 @@
 # Requires
-path = require('path')
-fs = require('fs')
+pathUtil = require('path')
 _ = require('underscore')
 semver = require('semver')
 balUtil = require('bal-util')
@@ -53,22 +52,22 @@
 	# Constructor
 	constructor: ({@docpad,@dirPath,@BasePlugin}) ->
 		# Apply
-		@pluginName = path.basename(@dirPath).replace(/^docpad-plugin-/,'')
+		@pluginName = pathUtil.basename(@dirPath).replace(/^docpad-plugin-/,'')
 		@pluginClass = {}
 		@pluginConfig = {}
 		@packageData = {}
-		@nodeModulesPath = path.resolve(@dirPath, 'node_modules')
+		@nodeModulesPath = pathUtil.resolve(@dirPath, 'node_modules')

 	# Exists
 	# Loads in the plugin either via a package.json file, or a guessing based on the name
 	# next(err,exists)
 	exists: (next) ->
 		# Package.json
-		packagePath = path.resolve(@dirPath, "package.json")
-		pluginPath = path.resolve(@dirPath, "#{@pluginName}.plugin.coffee")
-		path.exists packagePath, (exists) =>
+		packagePath = pathUtil.resolve(@dirPath, "package.json")
+		pluginPath = pathUtil.resolve(@dirPath, "#{@pluginName}.plugin.coffee")
+		balUtil.exists packagePath, (exists) =>
 			unless exists
-				path.exists pluginPath, (exists) =>
+				balUtil.exists pluginPath, (exists) =>
 					unless exists
 						return next?(null,false)
 					else
@@ -88,8 +87,8 @@

 						@pluginConfig = @packageData.docpad and @packageData.docpad.plugin or {}

-						pluginPath =  @packageData.main? and path.join(@dirPath, @pluginPath) or pluginPath
-						path.exists pluginPath, (exists) =>
+						pluginPath =  @packageData.main? and pathUtil.join(@dirPath, @pluginPath) or pluginPath
+						balUtil.exists pluginPath, (exists) =>
 							unless exists
 								return next?(null,false)
 							else


Block chart: Prevent blocks from appearing outside the chart.

--- app/assets/javascripts/lib/views/block_chart_view.coffee
+++ app/assets/javascripts/lib/views/block_chart_view.coffee
@@ -115,8 +115,12 @@
       if ($('#block_container_'+block[0]).hasClass('visible'))
         max_cost = block[1] if max_cost < block[1]
         max_invest = block[2] if max_invest < block[2]
-    max_cost = 200 if max_cost < 200
-    max_invest = 5 if max_invest < 5
+
+    # Allow for some extra room at the top and right of the chart, otherwise
+    # the labels for the most extreme values appear outside.
+    max_cost   = if max_cost < 200 then 200 else max_cost * 1.15
+    max_invest = if max_invest < 5 then 5 else max_invest * 1.15
+
     # update x axis
     ticks = $('#x-axis li')
     ticks.each (index,tick) ->


allow datatable to calculate sScrollXInner automatically

--- app/assets/javascripts/views/dataset_show_view.js.coffee
+++ app/assets/javascripts/views/dataset_show_view.js.coffee
@@ -33,16 +33,10 @@
     # bind to click event on individual input elements
     # to prevent default th click behaviour
     $dataTable.find("thead th input").click @clickHeadInput
-    columnsCount = $dataTable.find("thead tr:first th").length

     @dataTable = $dataTable.dataTable
       sDom: "<'row-fluid'<'span4'l><'span8'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
       sScrollX: "100%"
-      sScrollXInner:
-        if columnsCount > 6
-          "#{Math.round(columnsCount / 6 * 100)}%"
-        else
-          "100%"
       bProcessing: true
       bServerSide: true
       sAjaxSource: $dataTable.data "source"


Fix position of diagonal weapon

Fix position of diagonal weapon

--- app/scripts/components/weapons/RapidDiagonalLaser.coffee
+++ app/scripts/components/weapons/RapidDiagonalLaser.coffee
@@ -17,10 +17,10 @@
   install: (@ship) ->
     @level = @determineLevel @xp
     @attr
-      x: @ship.x + 10
-      y: @ship.y + 15
+      x: @ship.x + 20
+      y: @ship.y + 30
       z: @ship.z + 1
-      alpha: 1
+      alpha: 0
     @ship.attach this

     @shooting = no


Remove extraneous debugger statement

Remove extraneous debugger statement

--- src/languages/coffee.coffee
+++ src/languages/coffee.coffee
@@ -325,7 +325,6 @@
     # If there are no parameters, attempt to insert an empty socket so the user can add some
     else
       nodeBoundsStart = @getBounds(node).start
-      debugger
       match = @lines[nodeBoundsStart.line][nodeBoundsStart.column..].match(/^(\s*\()\s*\)\s*(-|=)>/)
       if match?
         position = {


Notification: Add click support to mention notification item

--- client/Main/notifications/notificationlistitem.coffee
+++ client/Main/notifications/notificationlistitem.coffee
@@ -147,7 +147,7 @@
           duration : 1000

     switch @getData().type
-      when "comment", "like"
+      when "comment", "like", "mention"
         KD.remote.api.SocialMessage.fetch id: @getData().targetId, showPost
       when "follow"
         KD.getSingleton('router').handleRoute "/#{@actors[0].profile.nickname}"


Activity: temp eventlogger added to act sidebar

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -1,11 +1,25 @@
 class ActivitySidebar extends KDCustomScrollView
+
+  addEventLogger = (source, eventName) -> source.on eventName, -> log eventName, arguments

   constructor: ->

     super

-    {activityController} = KD.singletons
+    {
+      activityController
+      notificationController
+      socialapi
+    } = KD.singletons
+
     activityController.on 'SidebarItemClicked', @bound 'itemClicked'
+
+    addEventLogger notificationController, 'AddedToChannel'
+    addEventLogger notificationController, 'RemovedFromChannel'
+
+
+    # addEventLogger socialapi, 'MessageAdded'
+    # addEventLogger socialapi, 'MessageRemoved'

   viewAppended:->


groupcontroller: add 'GroupLeft' to group notification list

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -98,6 +98,7 @@
           'InstanceChanged'
           'GroupDestroyed'
           'GroupJoined'
+          'GroupLeft'
           'StackAdminMessageCreated'
         ]


develop replaced gitProperyDTO -> getGitPropertyDTO in angularLogic due to replacement in pull-request refactoring

--- src/main/java/app/app.coffee
+++ src/main/java/app/app.coffee
@@ -12,7 +12,7 @@
 ).controller('NavigationCtrl', ($scope, $location, Restangular) ->
   gitProperties = Restangular.one('gitProperties')
   gitProperties.get().then (data) ->
-    $scope.gitPropertyDTO = data.gitPropertyDTO
+    $scope.gitPropertyDTO = data.getGitPropertyDTO
   $scope.isActive = (route) ->
     route is $location.path()
   $scope.triggerBuildInformation = ->


use Backbone's wait: true to abort an update if the server returns an error

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -65,8 +65,8 @@
     if attributes
       previousAttributes = @model.toJSON()

-      # !todo: might be a good idea to upgrade Backbone and use {wait: true} here
       @model.save attributes,
+        wait: true
         success: =>
           for attribute, newValue of attributes
             @debug "  . #{attribute} changed from ", previousAttributes[attribute], " to ", @model.get(attribute)
@@ -80,8 +80,8 @@
     if @confirmDelete(@resource)
       $(@el).removeClass('editable').addClass('deleted')

-      # !todo: upgrade backbone, use {wait: true}
       @model.destroy
+        wait: true
         success: =>
           @model.collection.remove(@model) if @model.collection
           @onDeleteSuccess


direct abbrev instead of via citeproc

direct abbrev instead of via citeproc

--- chrome/content/zotero-better-bibtex/keymanager.coffee
+++ chrome/content/zotero-better-bibtex/keymanager.coffee
@@ -2,6 +2,8 @@
   constructor: ->
     @log = Zotero.BetterBibTeX.log
     @journalAbbrevs = Object.create(null)
+    @journalAbbrevsStore = {}
+
     @keys = Zotero.BetterBibTeX.Cache.addCollection('keys', {disableChangesApi: false, indices: 'itemID libraryID citekey citekeyFormat'.split(/\s+/) })
     @keys.on('insert', (key) ->
       Zotero.BetterBibTeX.debug('keymanager.loki insert', key)
@@ -101,32 +103,12 @@
     item = arguments[1] if arguments[0]._sandboxManager # the sandbox inserts itself in call parameters

     return item.journalAbbreviation if item.journalAbbreviation
-    return unless item.publicationTitle
+    key = item.publicationTitle || item.reporter || item.code
+    return unless key
     return unless Zotero.BetterBibTeX.pref.get('autoAbbrev')

-    if typeof @journalAbbrevs[item.publicationTitle] is 'undefined'
-      styleID = Zotero.BetterBibTeX.pref.get('autoAbbrevStyle')
-      styleID = (style for style in Zotero.Styles.getVisible() when style.usesAbbreviation)[0].styleID if styleID is ''
-      style = Zotero.Styles.get(styleID) # how can this be null?
-
-      if style
-        cp = style.getCiteProc(null, true)
-
-        cp.setOutputFormat('html')
-        cp.updateItems([item.itemID])
-        cp.appendCitationCluster({ citationItems: [{id: item.itemID}], properties: {} } , true)
-        cp.makeBibliography()
-
-        abbrevs = cp
-        for p in ['transform', 'abbrevs', 'default', 'container-title']
-          abbrevs = abbrevs[p] if abbrevs
-
-        for own title,abbr of abbrevs or {}
-          @journalAbbrevs[title] = abbr
-
-      @journalAbbrevs[item.publicationTitle] ?= ''
-
-    return @journalAbbrevs[item.publicationTitle]
+    @journalAbbrevs[key] ?= Zotero.Cite.getAbbreviation(null, @journalAbbrevsStore, 'default', 'container-title', key)
+    return @journalAbbrevs[key]

   extract: ->
     [item, insitu] = (if arguments[0]._sandboxManager then Array.slice(arguments, 1) else arguments)


move user creation and update logic to processConvert function

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1465,6 +1465,63 @@
     daisy queue

+  processConvert = (options, callback) ->
+
+    { user, ip, country, region, clientId, account, client, referrer,
+    email, username, password, lastName, firstName, emailFrequency
+    invitation } = options
+
+    user     = null
+    error    = null
+    account  = null
+    newToken = null
+
+    queue = [
+
+      ->
+        userInfo = {
+          email, username, password, lastName, firstName, emailFrequency
+        }
+        createUser { userInfo }, (err, data) ->
+          return callback err  if err
+          { user, account } = data
+          queue.next()
+
+      ->
+        params = {
+          user, ip, country, region, username
+          password, clientId, account, client
+        }
+        updateUserInfo params, (err, newToken_) ->
+          return callback err  if err
+          newToken = newToken_
+          queue.next()
+
+      ->
+        updateAccountInfo { account, referrer, username }, (err) ->
+          return callback err  if err
+          queue.next()
+
+      ->
+        groupNames = [client.context.group, 'koding']
+
+        JUser.addToGroups account, groupNames, user.email, invitation, (err) ->
+          error = err
+          queue.next()
+
+      ->
+        createDefaultStackForKodingGroup { account }, (err) ->
+          return callback err  if err
+          queue.next()
+
+      -> callback null, { error, newToken, user, account }
+
+    ]
+
+    daisy queue
+
+
+
   @convert = secure (client, userFormData, callback) ->

     { slug, email, agree, username, lastName, referrer,
@@ -1523,39 +1580,14 @@
           queue.next()

       ->
-        userInfo = {
+        params = {
+          user, ip, country, region, clientId, account, client, referrer,
           email, username, password, lastName, firstName, emailFrequency
+          invitation
         }
-        createUser { userInfo }, (err, data) ->
-          return callback err  if err
-          { user, account } = data
-          queue.next()
-
-      ->
-        params = {
-          user, ip, country, region, username
-          password, clientId, account, client
-        }
-        updateUserInfo params, (err, newToken_) ->
-          return callback err  if err
-          newToken = newToken_
-          queue.next()
-
-      ->
-        updateAccountInfo { account, referrer, username }, (err) ->
-          return callback err  if err
-          queue.next()
-
-      =>
-        groupNames = [client.context.group, 'koding']
-
-        @addToGroups account, groupNames, user.email, invitation, (err) ->
-          error = err
-          queue.next()
-
-      ->
-        createDefaultStackForKodingGroup { account }, (err) ->
-          return callback err  if err
+        processConvert params, (err, data) ->
+          return callback err  if err
+          { error, newToken, user, account } = data
           queue.next()

       ->
@@ -2020,3 +2052,5 @@

     selector.username = @username
     JSession.remove selector, callback
+
+


Added more properties to accepted invite event

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -995,7 +995,11 @@
         group.approveMember account, roles, (err) ->
           return callback err  if err

-          Tracker.identifyAndTrack email, { subject: Tracker.types.TEAMS_ACCEPTED_INVITATION }
+          Tracker.identifyAndTrack email, {
+              groupName : slug
+              email     : email
+              subject   : Tracker.types.TEAMS_ACCEPTED_INVITATION
+          }

           # do not forget to redeem invitation
           redeemInvitation {


Pass "this" instance to IDEChatParticipantHeads

--- client/ide/lib/views/chat/idechatmessagepane.coffee
+++ client/ide/lib/views/chat/idechatmessagepane.coffee
@@ -165,7 +165,7 @@

   createParticipantHeads: ->

-    @participantHeads = new IDEChatParticipantHeads
+    @participantHeads = new IDEChatParticipantHeads delegate : this

     @forwardEvent @participantHeads, 'ParticipantSelected'

@@ -188,7 +188,7 @@
     if channel.lastMessage.payload?
       { systemType } = channel.lastMessage.payload
       systemType   or= channel.lastMessage.payload['system-message']
-
+
     isAlreadyUsed   = systemType not in [ 'initiate', 'start' ]
     hasParticipants = channel.participantCount > 1


Tests: Added extra waitFor activity element.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -182,6 +182,7 @@
       .click                  '[testpath="public-feed-link/Activity/Topic/public"]'
       .waitForElementVisible  '[testpath=ActivityInputView]', 10000
       .click                  '[testpath="ActivityTabHandle-/Activity/Public/Recent"]'
+      .waitForElementVisible  '.most-recent [testpath=activity-list]', 30000
       .click                  '[testpath=ActivityInputView]'
       .setValue               '[testpath=ActivityInputView]', post
       .click                  '.channel-title'


ComputeUtils: PR fixes add comments, print a different log statement

--- workers/social/lib/social/models/computeproviders/computeutils.coffee
+++ workers/social/lib/social/models/computeproviders/computeutils.coffee
@@ -204,6 +204,7 @@
       if group.slug is 'koding'
         {ObjectId} = require 'bongo'
         group.stackTemplates = [ObjectId "53fe557af052f8e9435a04fa"]
+        console.error "[critical] Koding group template is not set!"
       else
         return callback new KodingError "Template not set", "NotFound"

@@ -239,6 +240,11 @@

   JMachine   = require './machine'

+  # Following query will try to sort possible JMachines ordered by
+  # create date and will return the newest one with following cases
+  #  - provider must be equal to provided provider
+  #  - user and group must be same and user must be owner of the machine
+  #  - label needs to start with provider name and ends with "-vm-{0-9}" ~ GG
   selector   =
     provider : provider
     users    : $elemMatch: id: user.getId(), sudo: yes, owner: yes


Shorten syntax.

Shorten syntax.

--- core/app/backbone/factlink/cross_fade_region.coffee
+++ core/app/backbone/factlink/cross_fade_region.coffee
@@ -6,21 +6,18 @@

   crossFade: (newView) ->
     if @currentView
-      @$el.stop()
-      @$el.fadeOut @_fadeTime(), => @show newView
+      @$el.stop().fadeOut(@_fadeTime(), => @show newView)
     else
       @show(newView)

   open: (view) ->
-    @$el.stop()
-    @$el.hide()
-    @$el.html view.el
-    @$el.fadeIn @_fadeTime()
+    @$el.stop().hide()
+        .html(view.el)
+        .fadeIn(@_fadeTime())

   resetFade: ->
     return unless @$el #if there's no element, nothing's been shown.
-    @$el.stop()
-    @$el.fadeOut @_fadeTime(), => @reset()
+    @$el.stop().fadeOut(@_fadeTime(), => @reset())

   _fadeTime: -> @options?.fadeTime || defaultFadeTime


Abstracted out ReactActivity component

Abstracted out ReactActivity component

--- core/app/backbone/views/feed_activities_view.coffee
+++ core/app/backbone/views/feed_activities_view.coffee
@@ -21,16 +21,52 @@
       @model().map (model) =>
         switch model.get("action")
           when "created_comment", "created_sub_comment"
-            ReactCreatedComment(model: model)
+            ReactCreatedCommentActivity(model: model)
           when "followed_user"
-            ReactFollowedUser(model: model)
+            ReactFollowedUserActivity(model: model)

-ReactCreatedComment = React.createBackboneClass
-  displayName: 'ReactCreatedComment'
+ReactCreatedCommentActivity = React.createBackboneClass
+  displayName: 'ReactCreatedCommentActivity'

   render: ->
     user = new User @model().get('user')
     fact = new Fact @model().get("fact")
+
+    ReactActivity {
+        model: user
+        time: @model().get('created_at')
+        activity_header_action: [
+          _span ["feed-activity-description"],
+            "commented on"
+          ]
+      },
+     ReactFact model: fact
+     #still needs the actual comment here
+
+ReactFollowedUserActivity = React.createBackboneClass
+  displayName: 'ReactFollowedUserActivity'
+
+  render: ->
+    user = new User @model().get('user')
+    followed_user = new User @model().get('followed_user')
+
+    ReactActivity {
+        model: user,
+        time: @model().get('created_at')
+        activity_header_action: [
+            _span ["feed-activity-description"],
+              Factlink.Global.t.followed
+            " "
+            _a ["feed-activity-username", href: followed_user.link(), rel:"backbone"],
+              _img ["feed-activity-user-avatar image-32px", src: followed_user.avatar_url(32)]
+              " "
+              followed_user.get('name')
+          ]
+      }
+
+ReactActivity = React.createBackboneClass
+  render: ->
+    user = @model()

     _div ["feed-activity"],
       _div ["feed-activity-user"],
@@ -43,37 +79,10 @@
             _a ["feed-activity-username", href: user.link(), rel:"backbone"]
               user.get('name')
             ' '
-            _span ["feed-activity-description"],
-              "commented on"
+
+            this.props.activity_header_action
+
           _div ["feed-activity-time"],
-            TimeAgo(time: @model().get('created_at'))
-        _div [],
-          ReactFact model: fact
+            TimeAgo(time: this.props.time)
         _div ["feed-activity-content"],
-          '' #still needs the actual comment here
-
-
-ReactFollowedUser = React.createBackboneClass
-  displayName: 'ReactFollowedUser'
-
-  render: ->
-    user = new User @model().get('user')
-    followed_user = new User @model().get('followed_user')
-
-    _div ['feed-activity'],
-      _div ["feed-activity-user"],
-        _a [href: user.link(), rel:"backbone"],
-          _img ["feed-activity-user-avatar", src: user.avatar_url(48)]
-
-      _div ["feed-activity-container"],
-        _div ["feed-activity-heading"],
-          _div ["feed-activity-action"],
-            _a ["feed-activity-username", href: user.link(), rel:"backbone"],
-                user.name
-            _span ["feed-activity-description"],
-              Factlink.Global.t.followed
-            _a ["feed-activity-username", href: followed_user.link(), rel:"backbone"],
-              _img ["feed-activity-user-avatar image-32px", src: followed_user.avatar_url(32)]
-              followed_user.get('name')
-          _div ["feed-activity-time"],
-            TimeAgo(time: @model().get('created_at'))
+           this.props.children


added conflict handling to removeFileFromParentFolder and fixed fileTree mistaken changes on move/delete/etc.

--- src/FileManager/FileManager.coffee
+++ src/FileManager/FileManager.coffee
@@ -285,16 +285,17 @@
         link.dispatchEvent(e)

     removeFileFromParentFolder: (file) ->
-      _funcName = "removeFileFromFolder"
+      _funcName = "removeFileFromParentFolder"
       console.info _funcName
       assert.defined file._id, "file._id", _funcName
       assert.defined file.metadata.parentId, "file.metadata.parentId", _funcName
       File.getFile(file.metadata.parentId).then (folder) =>
         assert.array folder.content, "folder.content", _funcName
         if folder._id == @getCurrentDirId()
-          @fileTree.splice(
-            folder.content.indexOf(file._id)
-            1)
+          for i, f of @fileTree
+            if f._id == file._id
+              @fileTree.splice(i, 1)
+              break
         folder.content.splice(
           folder.content.indexOf(file._id)
           1)
@@ -302,6 +303,10 @@
           #TODO: change @ to a triggered update via changes watcher
           cache.expire(folder._id, "content")
           folder.listContent()
+        .catch (err) =>
+          if err.status == 409
+            cache.expire(folder._id, "content")
+            @removeFileFromParentFolder(file)

     deleteFile: (file) ->
       _funcName = "deleteFile"


sync_to_store Code refactor

sync_to_store Code refactor

--- app/assets/javascripts/action_cable_notifications/sync_to_store.coffee
+++ app/assets/javascripts/action_cable_notifications/sync_to_store.coffee
@@ -17,35 +17,35 @@
   # Private methods
   #####################################

-  processPacketHelper = (packet, collection) ->
-    local_collection = @collections[collection || packet.collection]
-    index = null
-    record = null
-
-    index = _.findIndex(local_collection, (record) -> record.id == packet.id)
-    if (index >= 0)
-      record = local_collection[index]
-
-    return {
-      collection: local_collection
-      index: index
-      record: record
-    }
-
   # Default callbacks for internal storage of received packets
   # Need to set include_initial: true in broadcasting options
-  default_callbacks =
+  class DefaultCallbacks
+    constructor: (@collections) ->
+
+    # Helper function
+    processPacketHelper = (packet, collection) ->
+      index = -1
+      record = null
+
+      local_collection = @collections[collection || packet.collection]
+      if !local_collection
+        console.warn("[update_many]: Collection #{collection_name} doesn't exist")
+      else
+        index = _.find(local_collection, (record) -> record.id == packet.id)
+        if (index >= 0)
+          record = local_collection[index]
+
+      return {
+        collection: local_collection
+        index: index
+        record: record
+      }
+
+    # Callbacks
+    ##################################################
+
     initialize: (collection) ->
       @collections[collection] = []
-
-    collection_add: (packet, collection) ->
-      local_collection = @collections[collection || packet.collection]
-      _.each packet.data, (fields) ->
-        record = _.findIndex(local_collection, (r) -> r.id == fields.id)
-        if record>=0
-          _.extend(local_collection[record], fields)
-        else
-          local_collection.push(fields)

     collection_remove: (packet, collection) ->
       console.warn 'Method not implemented: collection_remove '
@@ -65,13 +65,17 @@
         _.extend(data.record, packet.data)

     update_many: (packet, collection) ->
-      local_collection = @collections[collection || packet.collection]
-      _.each packet.data, (fields) ->
-        record = _.findIndex(local_collection, (r) -> r.id == fields.id)
-        if record>=0
-          _.extend(local_collection[record], fields)
-        else
-          local_collection.push(fields)
+      collection_name = collection || packet.collection
+      local_collection = @collections[collection_name]
+      if !local_collection
+        console.warn("[update_many]: Collection #{collection_name} doesn't exist")
+      else
+        _.each packet.data, (fields) ->
+          record = _.findIndex(local_collection, (r) -> r.id == fields.id)
+          if record>=0
+            _.extend(local_collection[record], fields)
+          else
+            local_collection.push(fields)

     destroy: (packet, collection) ->
       data = processPacketHelper(packet, collection)
@@ -85,28 +89,32 @@
   #####################################
   constructor: ->
     @collections = {}
+    @default_callbacks = new @DefaultCallbacks(@collections)

-    # Binds local methods and callbacks to this class
-    for name, callback of default_callbacks
-      default_callbacks[name] = callback.bind(this)
+    # Register internal store
+    @registerStore()

-    processPacketHelper = processPacketHelper.bind(this)
+  # Register a new store
+  registerStore: (store='default', callbacks) ->
+    if !registered_stores[store]
+      if callbacks
+        registered_stores[store] = callbacks
+      else
+        registered_stores[store] = default_callbacks
+    registered_stores[store]

-  # Registers a new store
-  registerStore: (collection, channel, callbacks) ->
-    if callbacks
-      registered_stores[collection] = callbacks
+  # Register a new collection into a store
+  registerCollection: (collection, channel, store='default') ->
+    if registered_stores[store]
+      # Initialize registered collection
+      new_collection = registered_stores[store].initialize?(collection)
+      # If ActionCable channel is specified, get initial values for subcription
+      if channel
+        channel.perform?("get_initial_values")
+      new_collection
     else
-      registered_stores[collection] = default_callbacks
-
-    # Initialize registered store
-    store = registered_stores[collection].initialize?(collection)
-
-    # If ActionCable channel is specified, get initial values for subcription
-    if channel
-      channel.perform?("get_initial_values")
-
-    store
+      console.warn("Store #{store} does not exist")
+      null

   # Dispatch received packet to registered stores
   storePacket: (packet, collection) ->
@@ -121,7 +129,6 @@
       @collections[collection].push record
     record

-
 # Export to global namespace
 #######################################
 @App = {} unless @App


fix: renamed caption #562

fix: renamed caption #562

--- app/assets/javascripts/dashboard/views/dashboard_task_list_view.coffee
+++ app/assets/javascripts/dashboard/views/dashboard_task_list_view.coffee
@@ -26,7 +26,7 @@
     <% } %>

-      Show finished tasks
+      Show finished tasks only

@@ -101,8 +101,8 @@
     @showFinishedTasks = not @showFinishedTasks
     @update()

-    verb = if @showFinishedTasks then "Hide" else "Show"
-    @ui.finishToggle.html("#{verb} finished tasks only")
+    verb = if @showFinishedTasks then "unfinished" else "finished"
+    @ui.finishToggle.html("Show #{verb} tasks only")

   transferTask : (evt) ->


[skip] Put focus on Add Ticket field when turning on Edit Mode for a Sprint (1m)

--- app/assets/javascripts/houston-scheduler/views/show_sprint_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/show_sprint_view.coffee
@@ -283,4 +283,5 @@
     else
       $button.addClass('btn-success')
       @$el.addClass('edit-mode')
-
+      $('#add_ticket').focus()
+


Fixed click location bug in tags input

Fixed click location bug in tags input

--- packages/emberella/lib/views/tags_input.js.coffee
+++ packages/emberella/lib/views/tags_input.js.coffee
@@ -905,8 +905,9 @@
     return if get(@, 'disabled') or e.target isnt get(@, 'element')
     posX = e.pageX
     posY = e.pageY
-    nearest = @.find((childView) ->
+    nearest = @find((childView) ->
       jQ = childView.$()
+      return false unless jQ?
       position = jQ.offset()
       height = jQ.outerHeight(true)
       space = jQ.outerWidth(true) - jQ.width()


payment: fix type

payment: fix type

BuyerReportRequested => BuyersReportRequested

--- client/landing/app/Applications/Dashboard.kdapplication/views/productsettingsview.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/views/productsettingsview.coffee
@@ -37,7 +37,7 @@
       @forwardEvents @["#{category}sView"], [
         'DeleteRequested'
         'EditRequested'
-        'BuyerReportRequested'
+        'BuyersReportRequested'
       ], category.capitalize()

   setProducts: (products) ->


added orchestrations

added orchestrations

--- src/middleware/stats.coffee
+++ src/middleware/stats.coffee
@@ -26,7 +26,7 @@

         if route.orchestrations?
           for orchestration in route.orchestrations
-            orchestrationStatus = orchestration.status
+            orchestrationStatus = orchestration.response.status
             sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name
             sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name + '.StatusCodes.' + orchestrationStatus
             sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.StatusCodes.' + route.response.status + '.orchestrations.' + orchestration.name
@@ -36,7 +36,7 @@
     if ctx.mediatorResponse?
       if ctx.mediatorResponse.orchestrations?
         for orchestration in ctx.mediatorResponse.orchestrations
-          orchestrationStatus = orchestration.status
+          orchestrationStatus = orchestration.response.status
           sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.orchestrations.' + orchestration.name
           sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.orchestrations.' + orchestration.name + '.StatusCodes.' + orchestrationStatus
           sdc.increment domain + '.Channels.' + ctx.authorisedChannel._id + '.Statuses.' + transactionStatus + '.orchestrations.' + orchestration.name
@@ -64,7 +64,7 @@
         if route.orchestrations?
           for orchestration in route.orchestrations
             orchestratrionDuration = orchestration.response.timestamp - orchestration.request.timestamp
-            orchestrationStatus = orchestration.status
+            orchestrationStatus = orchestration.response.status
             sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name, orchestratrionDuration
             sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name + '.StatusCodes.' + orchestrationStatus , orchestratrionDuration
             sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.StatusCodes' + route.response.status + '.orchestrations.' + orchestration.name, orchestratrionDuration
@@ -76,7 +76,7 @@
       if ctx.mediatorResponse.orchestrations?
         for orchestration in ctx.mediatorResponse.orchestrations
           orchestratrionDuration = orchestration.response.timestamp - orchestration.request.timestamp
-          orchestrationStatus = orchestration.Status
+          orchestrationStatus = orchestration.response.status
           sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.orchestrations.' + orchestration.name, orchestratrionDuration
           sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.orchestrations.' + orchestration.name + '.StatusCodes.' + orchestrationStatus , orchestratrionDuration


Don't need to test against 50 categories

--- src/spec/integration/categories.spec.coffee
+++ src/spec/integration/categories.spec.coffee
@@ -30,8 +30,8 @@
         levelFile: 'error'
     @logger = @client._logger

-    @logger.info 'Creating 50 categories'
-    Q.all _.map [1..50], => @client.categories.save(newCategory())
+    @logger.info 'Creating 10 categories'
+    Q.all _.map [1..10], => @client.categories.save(newCategory())
     .then (results) =>
       @logger.info "Created #{results.length} categories"
       done()


hook up preformat to hotkey

hook up preformat to hotkey

--- src/modules/keyboard.coffee
+++ src/modules/keyboard.coffee
@@ -27,10 +27,13 @@

   toggleFormat: (range, format) ->
     delta = @quill.getContents(range)
-    hasFormat = _.all(delta.ops, (op) ->
+    value = !_.all(delta.ops, (op) ->
       return op.attributes[format]
     )
-    @quill.formatText(range, format, !hasFormat, { source: 'user' })
+    if range.isCollapsed()
+      @quill.prepareFormat(format, value)
+    else
+      @quill.formatText(range, format, value, 'user')

   _initDeletes: ->
     _.each([DOM.KEYS.DELETE, DOM.KEYS.BACKSPACE], (key) =>


no shy

no shy

--- node_modules/hubot-plusplus/src/plusplus.coffee
+++ node_modules/hubot-plusplus/src/plusplus.coffee
@@ -70,9 +70,7 @@
                   else
                     "#{name} has #{score} points, #{reasonScore} of which are for #{reason}."
                 else
-                  if name.toLowerCase() =="shy"
-                    "#{name} has ∞ points"
-                  else if score == 1
+                  if score == 1
                     "#{name} has #{score} point"
                   else
                     "#{name} has #{score} points"
@@ -138,7 +136,6 @@
     tops = scoreKeeper[msg.match[1]](amount)

     if tops.length > 0
-      message.push("0. Shy : ∞")
       for i in [0..tops.length-1]
         message.push("#{i+1}. #{tops[i].name} : #{tops[i].score}")
     else


home: fix scrolling on refresh

home: fix scrolling on refresh

--- src/pages/home/index.coffee
+++ src/pages/home/index.coffee
@@ -31,13 +31,16 @@
     else
       section

-  onMount: =>
-    $ = _.bind document.querySelector, document
-
+  onMount: ($$el) =>
     @scrollTargetDisposable = @scrollTargetSubject.subscribe (target) ->
       if target
-        $$scrollTarget = $("a[name=#{target}]")
-        window.scrollTo 0, $$scrollTarget.offsetTop
+        $$scrollTarget = $$el.querySelector("a[name=#{target}]")
+        if document.readyState isnt 'complete'
+          window.addEventListener 'load', ->
+            setTimeout ->
+              window.scrollTo 0, $$scrollTarget.offsetTop
+        else
+          window.scrollTo 0, $$scrollTarget.offsetTop

   onBeforeUnmount: =>
     @scrollTargetDisposable?.dispose()


Update spec helper.

Update spec helper.


Implement invincible blob

Implement invincible blob

--- src/Blob.coffee
+++ src/Blob.coffee
@@ -1,7 +1,7 @@
 Action = require('ecstasy').Action

 class BlobComponent
-  constructor: ({@velX, @velY, @parent, @weight, @weightCap}) ->
+  constructor: ({@velX, @velY, @parent, @weight, @weightCap, @invincible}) ->
     @velX ?= 0
     @velY ?= 0
     @weight ?= 0.15
@@ -45,6 +45,10 @@
           entBlob.weight = entBlob.weightCap
           entBlob.weightCap = null
         else continue
+      if entBlob.invincible
+        entBlob.invincible -= delta
+        entBlob.invincible = null if entBlob.invincible < 0
+        continue
       # TODO: Implement better algorithm of this, such as QuadTree
       for other, j in @entities
         continue unless j > i
@@ -59,6 +63,7 @@
           pushOther entity, other
           continue
         continue if otherBlob.weight <= 0.1
+        continue if otherBlob.invincible
         if entPos.collides otherPos
           # Bigger one eats smaller one
           entityBig = entPos.radius > otherPos.radius


Fix user color in discussion filter (#4961)

Fix user color in discussion filter

--- resources/assets/coffee/react/beatmap-discussions/user-filter.coffee
+++ resources/assets/coffee/react/beatmap-discussions/user-filter.coffee
@@ -45,8 +45,7 @@

   mapUserProperties: (user) ->
-    default_group: user.default_group
-    groups: user.groups
+    group_badge: user.group_badge
     id: user.id
     text: user.username


ttest_power: Add density of noncentral t

--- ttest_power/distributions.coffee
+++ ttest_power/distributions.coffee
@@ -209,11 +209,16 @@

     quant

-# non-central t distribution
+# CDF of non-central t distribution
 pnct = (x, df, ncp, tol=1e-5) ->
     x = parseFloat(x)
     df = parseFloat(df)
     ncp = parseFloat(ncp)
+    if df <= 0
+        console.log("pnct: df must be positive")
+        return null
+
+    return pt(x, df) if ncp==0

     flip = (x < 0)
     ncp = -ncp if x < 0
@@ -230,9 +235,22 @@
         val = p*pbeta(y, j+0.5, df/2) + q*pbeta(y, j+1, df/2)
         prob += 0.5*val
         j += 1
-        console.log(j)

     return 1-prob if flip
     prob

-# CDF of non-central t distribution
+# density of non-central t distribution
+dnct = (x, df, ncp, tol=1e-5) ->
+    x = parseFloat(x)
+    df = parseFloat(df)
+    ncp = parseFloat(ncp)
+    if df <= 0
+        console.log("dnct: df must be positive")
+        return null
+
+    return dt(x, df) if ncp==0
+
+    if x == 0
+        return Math.exp( lgamma((df+1)/2) - ncp*ncp/2 - 0.5*Math.log(Math.PI * df) - lgamma(df/2) )
+
+    df/x * (pnct(x*Math.sqrt(1 + 2/df), df+2, ncp, tol) - pnct(x, df, ncp, tol) )


Detect mutations to text nodes that are immediate children of the editor element

--- src/trix/observers/mutation_observer.coffee
+++ src/trix/observers/mutation_observer.coffee
@@ -1,6 +1,8 @@
 #= require trix/utilities/helpers
+#= require trix/utilities/dom

 {defer} = Trix.Helpers
+{DOM} = Trix

 class Trix.MutationObserver
   options =
@@ -37,7 +39,7 @@
     node isnt @element and @nodeIsEditable(node)

   nodeIsEditable: (node) ->
-    node?.nodeType is Node.ELEMENT_NODE and node.isContentEditable
+    DOM.closestElementNode(node)?.isContentEditable

   nodesModifiedByMutation: (mutation) ->
     nodes = []
@@ -47,6 +49,7 @@
       when "characterData"
         # Changes to text nodes should consider the parent element
         nodes.push(mutation.target.parentNode)
+        nodes.push(mutation.target)
       when "childList"
         # Consider each added or removed node
         nodes.push(mutation.addedNodes...)


moved into 1 function

moved into 1 function

--- src/poll.coffee
+++ src/poll.coffee
@@ -14,36 +14,35 @@
 #   logikz

 module.exports = (robot) ->
-	generateId = ->
-		text = ""
-		possibleValues = "0123456789abcdef"
-		text += possibleValues.charAt(Math.floor(Math.random() * possibleValues.length)) for [1..5]
+	# generateId = ->
+	# 	text = ""
+	# 	possibleValues = "0123456789abcdef"
+	# 	text += possibleValues.charAt(Math.floor(Math.random() * possibleValues.length)) for [1..5]

-	parseOptions = (optionsString) ->
-		(optionsString.split "option: ")[1..]
+	# parseOptions = (optionsString) ->
+	# 	(optionsString.split "option: ")[1..]

-	createPoll = (topic, options) ->
-		@robot.logger.info "Creating poll with #{topic}"
-		id = generateId()
-		pollOptions = parseOptions options
-		@robot.logger.info "We have #{pollOptions.length()} options"
-		@robot.brain.data.poll[id].topic = topic
-		optionsString = ""
+	# createPoll = (topic, options) ->
+	# 	@robot.logger.info "Creating poll with #{topic}"
+	# 	id = generateId()
+	# 	pollOptions = parseOptions options
+	# 	@robot.logger.info "We have #{pollOptions.length()} options"
+	# 	@robot.brain.data.poll[id].topic = topic
+	# 	optionsString = ""

-		for pollOption in pollOptions
-			index = pollOptions.indexOf(pollOption)
-			robot.brain.data.poll[id].option[index].text
-			optionsString += "\t#{index}: #{pollOption}\n"
-		@robot.brain.save()
-		@robot.logger.info "Brain saved"
+	# 	for pollOption in pollOptions
+	# 		index = pollOptions.indexOf(pollOption)
+	# 		robot.brain.data.poll[id].option[index].text
+	# 		optionsString += "\t#{index}: #{pollOption}\n"
+	# 	@robot.brain.save()
+	# 	@robot.logger.info "Brain saved"

-		text =  """
-				POLL ID: #{id}
-				Topic: #{topic}
-				Options:
-				#{optionsString}
-				"""
-
+	# 	text =  """
+	# 			POLL ID: #{id}
+	# 			Topic: #{topic}
+	# 			Options:
+	# 			#{optionsString}
+	# 			"""
 	robot.respond /start poll (.*?)\s(option:\s?.*)+/i, (msg) ->
 		@robot.logger.info "Create poll called: #{msg}"
 		topic = msg.match[1]
@@ -53,8 +52,10 @@
 		# @robot.logger.info createPoll
 		# response = createPoll topic, options
 		@robot.logger.info "Creating poll with #{topic}"
-		id = generateId()
-		pollOptions = parseOptions options
+		id = ""
+		possibleValues = "0123456789abcdef"
+		id += possibleValues.charAt(Math.floor(Math.random() * possibleValues.length)) for [1..5]
+		pollOptions = (options.split "option: ")[1..]
 		@robot.logger.info "We have #{pollOptions.length()} options"
 		@robot.brain.data.poll[id].topic = topic
 		optionsString = ""


Log as error attempt to access unknown message key Provide function to locate available keys

--- tapestry-core/src/main/coffeescript/META-INF/modules/core/messages.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/core/messages.coffee
@@ -21,13 +21,23 @@
   # In the unexpected case that the data-locale attribute is missing, assume English
   locale = (document.documentElement.getAttribute "data-locale") or "en"

-  define ["core/messages/#{locale}"],
-    (messages) ->
+  define ["core/messages/#{locale}", "_", "core/console"],
+    (messages, _, console) ->

       # Returns the application message catalog message for the given key. Returns
       # a placeholder if the key is not found.
       get = (key) ->
-        return messages[key] || "[[Missing Key: '#{key}']]"
+        value = messages[key]
+
+        if value
+          return value
+        else
+          console.error "No value for message catalog key '#{key}' exists."
+          return "[[Missing Key: '#{key}']]"
+
+      # Returns all keys that are defined by the underlying catalog, in no specific order.
+      get.keys = -> _.keys messages
+

       # Export get as the main function; perhaps later we'll add a "format"
       # or something similar as a property of get.


Fix indentation

Fix indentation

--- tapestry-core/src/main/coffeescript/META-INF/modules/core/pageinit.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/core/pageinit.coffee
@@ -55,23 +55,23 @@
     head = document.head or document.getElementsByTagName("head")[0]

     _.chain(newStylesheets)
-    .map((ss) -> { href: rebuildURL(ss.href), media: ss.media })
-    .reject((ss) -> loaded.contains(ss.href).value())
-    .each((ss) ->
-      element = document.createElement "link"
-      element.setAttribute "type", "text/css"
-      element.setAttribute "rel", "stylesheet"
-      element.setAttribute "href", ss.href
-      if ss.media
-        element.setAttribute "media", ss.media
+      .map((ss) -> { href: rebuildURL(ss.href), media: ss.media })
+      .reject((ss) -> loaded.contains(ss.href).value())
+      .each((ss) ->
+        element = document.createElement "link"
+        element.setAttribute "type", "text/css"
+        element.setAttribute "rel", "stylesheet"
+        element.setAttribute "href", ss.href
+        if ss.media
+          element.setAttribute "media", ss.media

-      if insertionPoint
-        head.insertBefore element, insertionPoint.ownerNode
-      else
-        head.appendChild element
+        if insertionPoint
+          head.insertBefore element, insertionPoint.ownerNode
+        else
+          head.appendChild element

-      console.debug "Added stylesheet #{ss.href}"
-    )
+        console.debug "Added stylesheet #{ss.href}"
+      )

     return


Use the Glyphicon "remove" for the "Dismiss All" button

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.coffee
@@ -16,8 +16,8 @@
 #
 # Support for the core/Alerts components.
 #
-define ["./dom", "./console", "./messages", "./ajax", "underscore"],
-  (dom, console, messages, ajax, _) ->
+define ["./dom", "./console", "./messages", "./ajax", "underscore", "./bootstrap"],
+  (dom, console, messages, ajax, _, {glyph}) ->

     severityToClass =
       info: "alert-info"
@@ -68,7 +68,7 @@
         outer.append """

             <button class="btn btn-mini btn-default" data-action="dismiss-all">
-              ×
+              #{glyph "remove"}
               #{messages "core-dismiss-label"}
             </button>


Minor change.

Minor change.

--- src/tree.coffee
+++ src/tree.coffee
@@ -236,10 +236,10 @@
         _.each getNode(template, id).children, (childNode) ->
           # Check all children and trigger a change event so it's recursive.
           getCheckbox(template, childNode.id).prop('checked', isChecked).trigger('change')
-  onCreateNode = settings.onCreateNode
-  if onCreateNode
-    onCreateNode.apply(@, arguments)
-
+  callback = settings.onCreateNode
+  if callback
+    callback.apply(@, arguments)
+  # Add selection element which is shown when the row is selected.
   $selectRow = $('

')
   $('.jqtree-element', $em).append($selectRow)


Confirm leaving annotation editor without saving

--- app/assets/javascripts/annotationEditor.js.coffee
+++ app/assets/javascripts/annotationEditor.js.coffee
@@ -466,6 +466,10 @@
       new_zoom = 0.25 if new_zoom < 0.25
       new_zoom = 2.0 if new_zoom > 2.0
       @zoom(new_zoom)
+
+    unless @demo_mode
+      $(window).bind 'beforeunload', =>
+        return "You have unsaved changes. Leave anyway?" unless @saved

   createSubmissionPages: ->
     previousPage = undefined
@@ -531,6 +535,7 @@

   addCommand: (command) ->
     @commandBuffer.buffer.push(command)
+    @saved = false

   subscribeToAnnotation: (annotation) ->
@@ -576,7 +581,6 @@
   clickGrade: (phrase) =>
     if @gradingMode != 'sum' && !phrase.criterion.annotationsHaveGrades()
       this.addCommand(new SetSelectedPhraseCommand(phrase.criterion, phrase))
-      @saved = false

   clickPhrase: (phrase) =>
@@ -603,7 +607,6 @@
       this.addCommand(new SetSelectedPhraseCommand(phrase.criterion, undefined))

     page.createAnnotation(options)
-    @saved = false

   dropPhraseToAnnotation: (receiverAnnotation, draggedAnnotation, event, ui) =>
     #console.log draggedAnnotation


Ashok - Added removeFile for S3UploaderForm

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -34,6 +34,16 @@
       false

   setUploadForm = ->
+    obj = removeFile: (fileName) ->
+      removedFile = undefined
+      forms_for_submit = forms_for_submit.filter((form) ->
+        removedFile = form.files[0]  if form.files[0].name is fileName
+        form.files[0].name isnt fileName
+      )
+      current_files = forms_for_submit
+      removedFile
+
+    $.extend $uploadForm, obj
     $uploadForm.fileupload

       add: (e, data) ->


Add support for using a an upload template for each file

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -18,10 +18,24 @@
     additional_data: null
     before_add: null
     remove_completed_progress_bar: true
+    remove_completed_uploads: true
     remove_failed_progress_bar: false
+    remove_failed_upload_template: false
     progress_bar_target: null
+    filename_target: null
     click_submit_target: null
     allow_multiple_files: true
+    upload_template: """
+

+

+

+

+

+

+

+

+    """
+    upload_container: null

   $.extend settings, options

@@ -46,6 +60,14 @@
             $(data.context).appendTo(settings.progress_bar_target || $uploadForm)
           else if !settings.allow_multiple_files
             data.context = settings.progress_bar_target
+          else
+            $newUploadTemplate = $(settings.upload_template).clone()
+            $newUploadTemplate.attr("data-behaviour", "s3-upload")
+            $newUploadTemplate.attr("data-file-id", file.unique_id)
+            $newUploadTemplate.appendTo(settings.upload_container || $uploadForm)
+            if settings.filename_target
+              $newUploadTemplate.find(settings.filename_target).text(file.name)
+            data.context = $newUploadTemplate
           if settings.click_submit_target
             if settings.allow_multiple_files
               forms_for_submit.push data
@@ -90,7 +112,11 @@
               $uploadForm.trigger(event, [xhr, status, error])
               return event.result

-        data.context.remove() if data.context && settings.remove_completed_progress_bar # remove progress bar
+        if settings.allow_multiple_files
+          data.context.find(settings.progress_bar_target).remove() if data.context.find(settings.progress_bar_target) && settings.remove_completed_progress_bar # remove progress bar
+          data.context.remove() if data.context && settings.remove_completed_uploads # remove uploads
+        else
+          data.context.remove() if data.context && settings.remove_completed_progress_bar # remove progress bar
         $uploadForm.trigger("s3_upload_complete", [content])

         current_files.splice($.inArray(data, current_files), 1) # remove that element from the array


Uncheck

Uncheck

--- app/assets/javascripts/spina/galleryselect.coffee
+++ app/assets/javascripts/spina/galleryselect.coffee
@@ -12,8 +12,9 @@
     else
       $form.find('.gallery-select-counter').text("")
   else
+    checked = $(this).find('input').prop('checked')
     gallery.find('.item').removeClass('selected')
     gallery.find('.item input').prop('checked', false)
-    $(this).toggleClass('selected')
-    $(this).find('input').prop('checked', true)
-    $(this).closest('form').submit()+    $(this).addClass('selected') unless checked
+    $(this).find('input').prop('checked', !checked)
+    $(this).closest('form').submit()


Fix issues switching between all votes and personal vote

--- app/assets/javascripts/views/question_page.coffee
+++ app/assets/javascripts/views/question_page.coffee
@@ -204,6 +204,8 @@
   showPersonalVote: ->
     @column2HeaderText.text("Your Ranking")
     @addAnswerButton.show()
+    @personalVote.sortable('enable')
+    @personalVote.setRelation(Models.User.getCurrent().rankingsForQuestion(@question))

   updateAnswerRanking: (item) ->
     answerId = item.data('answer-id')


Write operations that are too large to S3

This is the best tradeoff for performance and speed. Otherwise we'll
lose an operation's data if we don't save it to S3.

--- src/server/db/amazon.coffee
+++ src/server/db/amazon.coffee
@@ -732,7 +732,7 @@
       compress_meta: (cb) ->
         compressor.encode(JSON.stringify(opData.meta), cb)

-      write_metadata: ['compress_op', 'compress_meta', (cb, results) ->
+      request: ['compress_op', 'compress_meta', (cb, results) ->
         request =
           TableName: operations_table,
           Item:
@@ -741,11 +741,35 @@
             op: { S: results.compress_op },
             meta: { S: results.compress_meta },

-        # Mark a snapshot as being compressed
+        # Mark an operation as being external
+        request_too_large = JSON.stringify(request).length > Math.pow(2, 16)
+        if request_too_large
+          delete request.Item.op
+          request.Item['e'] = { S: 't' }
+
+        # Mark an operation as being compressed
         request.Item['c'] = { S: 't' } if options['compress']

+        cb(null, request)
+      ]
+
+      write_metadata: ['request', (cb, results) ->
         operations_rw_queue.push((c) ->
-          db.PutItem(request, c)
+          db.PutItem(results.request, c)
+        , 'write Operation('+docName+'-'+opData.v+')', cb)
+      ]
+
+      write_data: ['request', (cb, results) ->
+        return cb(null, null) unless results.request.Item.e
+
+        params =
+          BucketName: snapshots_bucket
+          ObjectName: docName+'-'+opData.v+'.operation'
+          ContentLength: results.compress_op.length
+          Body: results.compress_op
+
+        s3_rw_queue.push((c) ->
+          s3.PutObject(params, c)
         , 'write Operation('+docName+'-'+opData.v+')', cb)
       ]
     (error, results) ->


correct some display bug

correct some display bug

--- src/coffee/directives/field.coffee
+++ src/coffee/directives/field.coffee
@@ -13,11 +13,11 @@
       scope.lbl = scope.field.label
       scope.$watch('field.valid', (v) ->
         switch v
-          when true then scope.lbl = scope.field.label
+          when true then scope.help = scope.field.help
           when false
             if !scope.field.error or scope.field.error == ""
-              scope.lbl = scope.field.label
-            else scope.lbl = scope.field.error
+              scope.help = scope.field.help
+            else scope.help = scope.field.error
       )

     return {


remove comment in test

remove comment in test

--- services/web/test/acceptance/coffee/ProjectDuplicateNameTests.coffee
+++ services/web/test/acceptance/coffee/ProjectDuplicateNameTests.coffee
@@ -40,7 +40,6 @@
 								name: "testfolder"
 								parent_folder_id: @rootFolderId
 					}, (err, res, body) =>
-							console.log "GOT FOLDER", body
 							@testFolderId = body._id
 							done()


fix review pattern

fix review pattern

--- src/commands/review-command.coffee
+++ src/commands/review-command.coffee
@@ -1,7 +1,8 @@
 module.exports = ({ pr, config, robot }) ->
   # slack to github
   users = JSON.parse config.githubUsers ? '{}'
-  pattern = /^\s*[@]?([^:,]+)[:,]?\s*review\s+(?:([^\/]+)\/)?([^#]+)#(\d+)\s*$/i
+  pattern =
+    /^\s*[@]?([^:,\s]+)[:,]?\s*review\s+(?:([^\/]+)\/)?([^#]+)#(\d+)\s*$/i
   robot.hear pattern, (res) ->
     reviewerInSlack = res.match[1]
     user = res.match[2] ? config.mergeDefaultUsername


label next/previous paging controls

label next/previous paging controls

And diable tabbing if control isn't active

--- tutor/src/components/paging-navigation.cjsx
+++ tutor/src/components/paging-navigation.cjsx
@@ -71,6 +71,8 @@
   renderPrev: ->
     cb = if @props.isBackwardEnabled then @props.onBackwardNavigation else null

     cb = if @props.isForwardEnabled then @props.onForwardNavigation else null
     <a href={@props.forwardHref}
+      tabIndex={if @props.isForwardEnabled then -1 else 0}
+      aria-label="Go Forward"
       disabled={not cb?}
       onClick={_.partial(@clickHandler, cb, @props.forwardHref)}
       className={classnames('paging-control', 'next', active: @state.activeNav is 'next')}


task list history viewer was completely broken by the new link processing code

--- src/smc-webapp/tasks.coffee
+++ src/smc-webapp/tasks.coffee
@@ -82,6 +82,7 @@

 class TaskList
     constructor: (@project_id, @filename, @element, @opts) ->
+        # NOTE: @filename need not be defined, e.g., when this object is being used by the history editor just for rendering
         @default_font_size = redux.getStore('account').get('font_size')
         @element.data('task_list', @)
         @element.find("a").tooltip(delay:{ show: 500, hide: 100 })
@@ -863,7 +864,8 @@
                 @display_desc(task)
                 return false

-        e.process_smc_links(project_id:@project_id, file_path:misc.path_split(@filename).head)
+        if @filename? # need not be defined, e.g., for history editor...
+            e.process_smc_links(project_id:@project_id, file_path:misc.path_split(@filename).head)
         e.find("table").addClass('table')  # makes bootstrap tables look MUCH nicer -- and gfm has nice tables
         task.element.find(".salvus-tasks-hash").click(@click_hashtag_in_desc)


Remove unnecessary debug

Remove unnecessary debug

--- src/components/GetObjectKey.coffee
+++ src/components/GetObjectKey.coffee
@@ -41,8 +41,6 @@
         throw "Key not defined" unless @key
         throw "Data is not an object" unless typeof data is "object"

-        console.log data[@key]
-
         @outPorts.out.send data[@key]

 exports.getComponent = ->


Don't wait forever when message refresh clicked call callback after 7 seconds

--- client/app/Applications/Inbox.kdapplication/Controllers/InboxMessageListController.coffee
+++ client/app/Applications/Inbox.kdapplication/Controllers/InboxMessageListController.coffee
@@ -1,15 +1,16 @@
 class InboxMessageListController extends KDListViewController
-
+
   constructor:()->
     super
     @selectedMessages = {}
-
+
   loadMessages:(callback)->
     @removeAllItems()
+    @utils.wait 7000, => callback?()
     KD.whoami().fetchMail
       limit       : 20
       sort        :
         timestamp : -1
     , (err, messages)=>
       @instantiateListItems messages
-      callback?()
+      callback?()


move padding var

move padding var

--- vue/src/shared/loaders/thread_loader.coffee
+++ vue/src/shared/loaders/thread_loader.coffee
@@ -5,7 +5,6 @@
 import EventBus         from '@/shared/services/event_bus'
 import Session from '@/shared/services/session'

-padding = 25

 export default class ThreadLoader
   constructor: (discussion) ->
@@ -20,12 +19,7 @@
     @visibleKeys = {}
     @collapsed = Vue.observable({})
     @loading = false
-    # @rules.push
-    #   name: "my stuff"
-    #   local:
-    #     find:
-    #       discussionId: @discussion.id
-    #       actorId: Session.user().id
+    @padding = 5

   setVisible: (isVisible, event) ->
     event.markAsRead() unless @visibleKeys.hasOwnProperty(event.positionKey)
@@ -81,7 +75,6 @@
           find:
             discussionId: @discussion.id
           simplesort: 'positionKey'
-          limit: padding
         remote:
           discussion_id: @discussion.id
           position_key_sw: event.positionKey
@@ -93,12 +86,12 @@
             discussionId: @discussion.id
             positionKey: {'$regex': "^#{event.positionKey}"}
           simplesort: 'positionKey'
-          limit: padding
+          limit: @padding
         remote:
           discussion_id: @discussion.id
           position_key_sw: event.positionKey
           order_by: 'position_key'
-          per: padding
+          per: @padding

   autoLoadAfter: (event) ->
     @loadAfter(event) if event.depth == 1
@@ -118,13 +111,13 @@
             $jgt: event.positionKey
             $regex: (positionKeyPrefix && "^#{positionKeyPrefix}") || undefined
         simplesort: 'positionKey'
-        limit: padding
+        limit: @padding
       remote:
         discussion_id: @discussion.id
         position_key_gt: event.positionKey
         position_key_sw: positionKeyPrefix || null
         order_by: 'position_key'
-        per: padding
+        per: @padding

   loadBefore: (event) ->
     @loading = event.id
@@ -140,14 +133,14 @@
             $regex: (positionKeyPrefix && "^#{positionKeyPrefix}") || undefined
         simplesort: 'positionKey'
         simplesortDesc: true
-        limit: padding
+        limit: @padding
       remote:
         discussion_id: @discussion.id
         position_key_lt: event.positionKey
         position_key_sw: positionKeyPrefix || null
         order_by: 'position_key'
         order_desc: 1
-        per: padding
+        per: @padding

   addLoadCommentRule: (commentId) ->
     @titleKey = 'strand_nav.from_comment'
@@ -157,7 +150,7 @@
         find:
           discussionId: @discussion.id
           commentId: {$gte: commentId}
-        limit: padding
+        limit: @padding
       remote:
         order: 'sequence_id'
         discussion_id: @discussion.id
@@ -186,7 +179,7 @@
           depth: 1
           position: {$gte: position}
         simplesort: 'positionKey'
-        limit: padding
+        limit: @padding
       remote:
         discussion_id: @discussion.id
         from_sequence_id_of_position: position
@@ -201,12 +194,12 @@
           discussionId: @discussion.id
           positionKey: {$jgte: positionKey}
         simplesort: 'positionKey'
-        limit: padding
+        limit: @padding
       remote:
         discussion_id: @discussion.id
         position_key_gte: positionKey
         order_by: 'position_key'
-        per: padding
+        per: @padding
     #
     # @rules.push
     #   name: "positionKey rollback"
@@ -234,7 +227,7 @@
           discussionId: @discussion.id
           sequenceId: {$gte: sequenceId}
         simplesort: 'sequenceId'
-        limit: padding
+        limit: @padding
       remote:
         from: sequenceId
         discussion_id: @discussion.id
@@ -248,10 +241,10 @@
           discussionId: @discussion.id
         simplesort: 'sequenceId'
         simplesortDesc: true
-        limit: padding
-      remote:
-        discussion_id: @discussion.id
-        per: padding
+        limit: @padding
+      remote:
+        discussion_id: @discussion.id
+        per: @padding
         order_by: 'sequence_id'
         order_desc: true

@@ -270,7 +263,7 @@
         find:
           discussionId: @discussion.id
         simplesort: 'sequenceId'
-        limit: padding
+        limit: @padding
       remote:
         discussion_id: @discussion.id
         order_by: 'sequence_id'
@@ -290,7 +283,7 @@
         find:
           discussionId: @discussion.id
           sequenceId: {$or: @discussion.unreadRanges().map((r) -> {$between: r} )}
-        limit: padding
+        limit: @padding
       remote:
         discussion_id: @discussion.id
         unread: true


Some additions for logger middleware.

Some additions for logger middleware.

--- src/core/middlewares/logger.coffee
+++ src/core/middlewares/logger.coffee
@@ -1,8 +1,22 @@
 {normal} = require '../logger'
+colors = require 'colors'
+
+arrowRight = "->".grey
+arrowLeft = "<-".grey
+
+statusColor = (status) ->
+  return "#{status}".green if 200 >= status < 300
+  return "#{status}".grey if 300 >= status < 400
+
+  # Maybe I don't need this code.
+  return "#{status}".yellow if 400 >= status < 500
+  return "#{status}".red if status >= 500

 logger = (next) ->
-  normal "-> #{@method} #{@url}"
+  normal "#{arrowRight} #{@method} #{@url}"
   yield next
-  normal "<- #{@status} #{@url}"
+  normal "
+    #{arrowLeft} #{statusColor @status} #{@url}
+  "

 module.exports = logger


client: remove Webpack no errors plugin

client: remove Webpack no errors plugin

--- web/react_chat/client/webpack.config.coffee
+++ web/react_chat/client/webpack.config.coffee
@@ -21,7 +21,6 @@
     new SplitByPathPlugin [
       { path: path.resolve(__dirname, 'node_modules/'), name: 'vendor' }
     ]
-    new webpack.NoErrorsPlugin()
     new webpack.DefinePlugin
       DEBUG: JSON.stringify(DEBUG)
       'process.env.NODE_ENV': JSON.stringify(process.env.ENVIRONMENT)


docs(macAutocomplete): Fixed typo

docs(macAutocomplete): Fixed typo

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -5,7 +5,7 @@
 @description
 A directive for providing suggestions while typing into the field

-Autocomplete allows for custom html templating in the dropdown and some properpities are exposed on the local scope on each template instance, including:
+Autocomplete allows for custom html templating in the dropdown and some properties are exposed on the local scope on each template instance, including:

 | Variable  | Type    | Details                                                                     |
 |-----------|---------|-----------------------------------------------------------------------------|


JApp: Default getters uses approved:yes selector, one is available for all cases and all methods are now using permit

--- workers/social/lib/social/models/app.coffee
+++ workers/social/lib/social/models/app.coffee
@@ -241,22 +241,49 @@
                 @addParticipant delegate, 'reviewer', (err)->
                   console.error "JApp:", err  if err

-
-  approve: permit 'approve apps',
-
-    success: (client, state = yes, callback)->
-
-      unless state
-        return @update approved: no, callback
-
-      identifier = @getAt 'identifier'
-
-      JApp.count {identifier, approved:yes}, (count)=>
-
-        # Check if any app used same identifier and already approved
-        if count > 1
-          return callback new KodingError \
-                 'Identifier already in use, please change it first'
+  getDefaultSelector = (client, selector)->
+    {delegate}   = client.connection
+    selector   or= {}
+    selector.$or = [
+      {approved  : yes}
+      {originId  : delegate.getId()}
+    ]
+    return selector
+
+  @some$ = @someWithRelationship
+  @someWithRelationship: permit 'list apps',
+
+    success: (client, selector, options, callback)->
+
+      selector  = getDefaultSelector client, selector
+      console.log "SOME CALLED", selector
+      options or= {}
+      options.limit = Math.min(options.limit or 0, 10)
+
+      @some selector, options, callback
+
+  @one$ = permit 'list apps',
+
+    success: (client, selector, options, callback)->
+
+      # selector = getDefaultSelector client, selector
+      [options, callback] = [callback, options] unless callback
+
+      @one selector, options, callback
+
+  @each$: permit 'list apps',
+
+    success: (client, selector, fields, options, callback)->
+
+      selector = getDefaultSelector client, selector
+      @each selector, fields, options, callback
+
+  delete: permit
+    advanced: [
+      { permission: 'delete own apps', validateWith: Validators.own }
+      { permission: 'delete apps' }
+    ]
+    success: (client, callback)-> @remove callback

   # approve: permit 'approve apps',


Socail: remove permissions from JTag

Socail: remove permissions from JTag

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -28,24 +28,6 @@
       """
       #{if @group is 'koding' then '' else "#{@group}/"}Topics/\#{slug}
       """
-    permissions     :
-      'read tags'             :
-        public                : ['guest', 'member', 'moderator']
-        private               : ['member', 'moderator']
-      'create tags'           : ['member', 'moderator']
-      'freetag content'       : ['member', 'moderator']
-      'browse content by tag' : ['member', 'moderator']
-      'edit tags'             : ['moderator']
-      'delete tags'           : ['moderator']
-      'edit own tags'         : ['moderator']
-      'delete own tags'       : ['moderator']
-      'assign system tag'     : ['moderator']
-      'fetch system tag'      : ['moderator']
-      'create system tag'     : ['moderator']
-      'remove system tag'     : ['moderator']
-      'create synonym tags'    : ['moderator']
-      # 'delete system tag'     : ['moderator']
-
     emitFollowingActivities : yes # create buckets for follower / followees
     indexes         :
       # slug          : 'unique'


Add percentage-based left draggable support.

--- src/traits/Draggable.coffee
+++ src/traits/Draggable.coffee
@@ -101,9 +101,16 @@

   __setDragStartPosition: ->
+    left = @root.style.left
+    top = @root.style.top
+
+    if (left.indexOf and left.indexOf('%') isnt -1)
+      parentWidth = @parentEl.getBoundingClientRect().width
+      left = (parseFloat(left) / 100) * parentWidth
+
     @__dragStartPosition = [
-      parseInt(@root.style.left or 0)
-      parseInt(@root.style.top or 0)
+      parseInt(left or 0)
+      parseInt(top or 0)
     ]


Have wrapper use the parent's styles.

Have wrapper use the parent's styles.

--- src/views/base/index.coffee
+++ src/views/base/index.coffee
@@ -233,9 +233,9 @@

     @view.remove @wrapper if @wrapper?

-    @wrapper = @make 'View',
-      height: @view.height or 'auto'
-      layout: @view.layout
+    @wrapper = @make 'View', @view.attributes
+      #height: @view.height or 'auto'
+      #layout: @view.layout

     callback @wrapper


Added 'add' method to base View to allow view to handle references to children if needed

--- src/views/base/index.coffee
+++ src/views/base/index.coffee
@@ -126,6 +126,9 @@
       attributes[key] = @parseTitaniumProperty attribute

     createTitaniumView viewName, attributes
+
+  add: (view, parent) ->
+    (parent ? @view).add view.render().view

   # Public: Empty by default. Override it with your own render logic.
   render: => @


statc/landing page: query stats only every 90 secs

--- src/webapp-lib/index.coffee
+++ src/webapp-lib/index.coffee
@@ -77,7 +77,7 @@
             console.log e
     r.send()
     # tail recursive callback
-    setTimeout(get_stats, 10 * 1000)
+    setTimeout(get_stats, 90 * 1000)

 init_video = ->
     for vplayer in document.getElementsByClassName("video-player")
@@ -85,7 +85,7 @@
         over = vplayer.getElementsByClassName("video-overlay")[0]
         do (vplayer, vid) ->
             vplayer.onclick = (el) ->
-                console.log vplayer, over, vid
+                #console.log vplayer, over, vid
                 vplayer.removeChild(over)
                 vid.setAttribute("controls", "true")
                 vid.setAttribute("loop", "true")


Render the chart when data comes in.

Render the chart when data comes in.

--- src/main/webapp/coffeescripts-hidden/weekly-performance-graph.coffee
+++ src/main/webapp/coffeescripts-hidden/weekly-performance-graph.coffee
@@ -25,3 +25,9 @@
         text: null

   requestEventSummary()
+
+  $(document).on 'event-summary-updated', (event) ->
+    if event.eventSummary.name == "tab-view"
+      chart.series[0].setData(event.eventSummary.data)
+    else if event.eventSummary.name == "tab-submit"
+      chart.series[1].setData(event.eventSummary.data)


Remove some logging

Remove some logging

--- src/scripts/geocodeme.coffee
+++ src/scripts/geocodeme.coffee
@@ -25,7 +25,6 @@
       msg.reply text

 geocodeMe = (msg, query, cb) ->
-    console.log(query)
     msg.http("https://maps.googleapis.com/maps/api/geocode/json")
       .header('User-Agent', 'Hubot Geocode Location Engine')
       .query({
@@ -34,6 +33,5 @@
       })
       .get() (err, res, body) ->
         response = JSON.parse(body)
-        console.log(response)
         location = response.results[0].geometry.location.lat + "," + response.results[0].geometry.location.lng
         cb "That's somewhere around " + location + " - https://maps.google.com/maps?q=" + location


player is a function

player is a function

--- resources/assets/coffee/_classes/osu-audio.coffee
+++ resources/assets/coffee/_classes/osu-audio.coffee
@@ -43,7 +43,7 @@
     promise = @player().play()
     # old api returns undefined
     promise?.catch (error) ->
-      return if error.name == 'AbortError' || error.name == 'NotSupportedError' && !osu.present(@player.src)
+      return if error.name == 'AbortError' || error.name == 'NotSupportedError' && !osu.present(@player().src)
       throw error


Tests: Google: Fix linter issue

Tests: Google: Fix linter issue

--- workers/social/lib/social/models/computeproviders/google.test.coffee
+++ workers/social/lib/social/models/computeproviders/google.test.coffee
@@ -35,7 +35,7 @@

       it 'should create default meta data', (done) ->

-        withConvertedUser ({client}) ->
+        withConvertedUser ({ client }) ->

           options =
             type          : 'google'


Updates JS to avoid URL Web API, which is not compatible with IE

--- app/assets/javascripts/syndication/widget.js.coffee
+++ app/assets/javascripts/syndication/widget.js.coffee
@@ -22,8 +22,9 @@
       'www.staging.dev.mas.local': 'https://staging-partner-tools.dev.mas.local/',
       'www.uat.moneyadviceservice.org.uk': 'https://uat-partner-tools.moneyadviceservice.org.uk/'
     }
-    toolLink = document.getElementsByClassName(masConfig.targetSelector)[0].getAttribute('href')
-    hostname = new URL(toolLink).hostname
+
+    hostname = document.getElementsByClassName(masConfig.targetSelector)[0].hostname;
+
     masConfig.toolsConfig = {
       syndication_url: 'https://partner-tools.moneyadviceservice.org.uk',
       syndication: {


add inaccessibility message for record video tinymce button

test_plan:
* using a screen reader, navigate to the video record
button in the tinymce editor
* should hear a message that the feature is inaccessible

closes #CNVS-1529

Change-Id: I555d2abaf09b25da2198d8198ff5c6db6578f98f
Reviewed-on: https://gerrit.instructure.com/17958
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Bracken Mosbacker <48b846001bca4166dc3fb7711563afc17525921e@instructure.com>
QA-Review: Adam Phillipps <0e18f44c1fec03ec4083422cb58ba6a09ac4fb2a@instructure.com>

--- app/coffeescripts/editor/editorAccessibility.coffee
+++ app/coffeescripts/editor/editorAccessibility.coffee
@@ -24,5 +24,12 @@
       @$el.find("##{@id_prepend}_forecolor_voice").text(I18n.t('accessibles.forecolor',"Text Color, press down to select"))
       @$el.find("##{@id_prepend}_backcolor_voice").text(I18n.t('accessibles.background_color',"Background Color, press down to select"))

+      @$el.find("##{@id_prepend}_instructure_record").attr('aria-disabled', 'true')
+      @$el.find("##{@id_prepend}_instructure_record").removeAttr('role')
+      @$el.find("##{@id_prepend}_instructure_record_voice").append('<br/>').append(I18n.t('accessibles.record', 'This feature is inaccessible for screen readers.'))
+      @$el.find("##{@id_prepend}_instructure_record img").attr('alt',
+        @$el.find("##{@id_prepend}_instructure_record img").attr('alt') + ", " + I18n.t('accessibles.record', 'This feature is inaccessible for screen readers.'));
+      debugger
+
     _addTitles: ->
       @$iframe.attr 'title', I18n.t('titles.rte_help', 'Rich Text Area. Press ALT F10 for toolbar. Press ALT 0 for help.')


LMS-1272: Create new assignments keeps some selections from the previous creation

--- app/coffeescripts/views/assignments/EditView.coffee
+++ app/coffeescripts/views/assignments/EditView.coffee
@@ -131,17 +131,8 @@

     setDefaultsIfNew: =>
       if @assignment.isNew()
-        if userSettings.contextGet('new_assignment_settings')
-          _.each(@settingsToCache(), (setting) =>
-            setting_from_cache = userSettings.contextGet('new_assignment_settings')[setting]
-            if setting_from_cache == "1" || setting_from_cache == "0"
-              setting_from_cache = parseInt setting_from_cache
-            if setting_from_cache && (!@assignment.get(setting) || @assignment.get(setting)?.length == 0)
-              @assignment.set(setting, setting_from_cache)
-          )
-        else
-          @assignment.set('submission_type','online')
-          @assignment.set('submission_types',['online'])
+        @assignment.set('submission_type','online')
+        @assignment.set('submission_types',['online'])

     cacheAssignmentSettings: =>
       new_assignment_settings = _.pick(@getFormData(), @settingsToCache()...)


Make sure Timer actually exports itself.

--- application/chrome/content/wesabe/util/Timer.coffee
+++ application/chrome/content/wesabe/util/Timer.coffee
@@ -87,3 +87,6 @@
       summary[label] = total

     return summary
+
+
+module.exports = Timer


lxc: adding stop to registry

lxc: adding stop to registry

--- packages/lxd/src/register.coffee
+++ packages/lxd/src/register.coffee
@@ -10,3 +10,4 @@
     init: '@nikitajs/lxd/src/init'
     network: '@nikitajs/lxd/src/network'
     start: '@nikitajs/lxd/src/start'
+    stop: '@nikitajs/lxd/src/stop'


added mongo connection string

added mongo connection string

--- services/contacts/config/settings.defaults.coffee
+++ services/contacts/config/settings.defaults.coffee
@@ -8,4 +8,4 @@
 			host: process.env["LISTEN_ADDRESS"] or "localhost"

 	mongo:
-		url: "mongodb://#{process.env["MONGO_HOST"] or "localhost"}/sharelatex"
+		url: process.env['MONGO_CONNECTION_STRING'] or "mongodb://#{process.env["MONGO_HOST"] or "localhost"}/sharelatex"


substitute circle with rect

substitute circle with rect

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -65,7 +65,7 @@
         <line x1="0" y1={crosshairSpace * selectedRadius} x2="0" y2={selectedRadius} strokeWidth={crosshairWidth} />
         <line x1={crosshairSpace * selectedRadius} y1="0" x2={selectedRadius} y2="0" strokeWidth={crosshairWidth} />
         <Draggable onDrag={@handleDrag}>
-          <circle r={radius} />
+          <rect width="100%" height={DEFAULT_HEIGHT} />
         </Draggable>

         { if @props.selected


select 2 bug fixes

select 2 bug fixes

--- app/assets/javascripts/rademade_admin/app/common/relation-list.coffee
+++ app/assets/javascripts/rademade_admin/app/common/relation-list.coffee
@@ -28,7 +28,7 @@
       dataType : 'json'
       data : (term) -> {q: term}
       results : (data) -> {results: data}
-  ).on 'change', (e) ->
+  ).unbind('change').bind 'change', (e) ->
     $select = $(this)
     $table = $select.siblings('.select2-items-list')
     addTable($select) if $table.length is 0
@@ -63,11 +63,12 @@
     '</li>'
   ].join(''))

-  $('li[data-id="' + item.id + '"] [data-remove]').on 'click', (e) ->
-    removeItem(item.id, $table) if confirm 'Вы действительно хотите удалить данную модель?'
+  $('li[data-id="' + item.id + '"] [data-remove]').unbind('click').bind 'click', (e) ->
+    e.preventDefault()
+    removeItem($(e.currentTarget).closest('li'), $table) if confirm 'Вы действительно хотите удалить данную модель?'

-removeItem = (id, $table) ->
-  $table.children('li[data-id="' + id + '"]').remove()
+removeItem = ($li, $table) ->
+  $li.remove()
   changeSelectValue $table, $table.siblings('.select-wrapper')

 changeSelectValue = ($table, $input) ->


simplified the keypress event selector

simplified the keypress event selector

--- app/assets/javascripts/states/monitoring/views/check_editor.js.coffee
+++ app/assets/javascripts/states/monitoring/views/check_editor.js.coffee
@@ -20,8 +20,7 @@
         @parent.parent.redraw()

       # save (on enter)
-      "keypress input#command_name, input#ps_command, input#ps_regex,
-      input#alert_when_command_not_found, input#host, input#port, input#interfaces": (e) ->
+      "keypress input": (e) ->
         if e.keyCode == 13
           e.preventDefault()
           @save_edits()


chat poss; further fixes...

chat poss; further fixes...

--- src/smc-webapp/smc_chat.cjsx
+++ src/smc-webapp/smc_chat.cjsx
@@ -319,6 +319,8 @@
             <Row>
                 {cols}
             </Row>
+
+SCROLL_DEBOUNCE_MS = 750

 ChatLog = rclass
     displayName: "ChatLog"
@@ -493,8 +495,8 @@
         # issue -- if the user is switching tabs back and forth in a session, that is very
         # unlikely, due to the browser caching the dynamic content.
         # The user is also unlikely to manually scroll the page then see it jump to
-        # this fixed position dwithin 500ms of mounting.
-        for tm in [0, 150, 500]
+        # this fixed position within 500ms of mounting.
+        for tm in [0, 200, SCROLL_DEBOUNCE_MS-250]
             setTimeout(fix_pos, tm)

     componentDidMount: ->
@@ -720,7 +722,7 @@
                     <Well
                         style    = {chat_log_style}
                         ref      = 'log_container'
-                        onScroll = {underscore.debounce(@save_scroll_position,250)}>
+                        onScroll = {underscore.debounce(@save_scroll_position,SCROLL_DEBOUNCE_MS)}>
                         <ChatLog
                             messages     = {@props.messages}
                             account_id   = {@props.account_id}


Added missing slash to avoid 302 when calling /notification_prefs/status

--- common/static/coffee/src/discussion/utils.coffee
+++ common/static/coffee/src/discussion/utils.coffee
@@ -86,7 +86,7 @@
       threads                 : "/courses/#{$$course_id}/discussion/forum"
       "enable_notifications"  : "/notification_prefs/enable/"
       "disable_notifications" : "/notification_prefs/disable/"
-      "notifications_status" : "/notification_prefs/status"
+      "notifications_status" : "/notification_prefs/status/"
     }[name]

   @activateOnEnter: (event, func) ->


remove console

remove console

--- client/landing/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/landing/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -296,7 +296,6 @@
     guestCreate      = new Date KD.whoami().meta.createdAt
     guestCreateTime  = guestCreate.getTime()

-    console.log guestTimeout
     endTime       = new Date(guestCreateTime + guestTimeout*60*1000)
     log endTime
     notification  = new GlobalNotification


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -74,6 +74,7 @@
 Get the file extension of a string.

     "README.md".extension() # => "md"
+    ""

 @name extension
 @methodOf String#


Skip on under-defined for linear downsample

--- bokehjs/src/coffee/source/server_data_source.coffee
+++ bokehjs/src/coffee/source/server_data_source.coffee
@@ -80,6 +80,13 @@
       domain_resolution = (screen_range.get('end') - screen_range.get('start')) / 2
       domain_resolution = Math.floor(domain_resolution)
       domain_limit = [domain_range.get('start'), domain_range.get('end')]
+
+      if plot_state['screen_x'].get('start') == plot_state['screen_x'].get('end') or
+         plot_state['screen_y'].get('start') == plot_state['screen_y'].get('end') or
+         domain_limit[0] > domain_limit[1]
+       console.log("Skipping due to under-defined view state")
+       return $.ajax()
+
       if (_.any(_.map(domain_limit, (x) -> _.isNaN(x))) or
          _.every(_.map(domain_limit, (x) -> _.isEqual(0,x))))
         domain_limit = 'auto'
@@ -96,7 +103,6 @@
             domain_range.set(
                 start : data.domain_limit[0],
                 end : data.domain_limit[1],
-                silent : true
             )
             #console.log('setting range', data.domain_limit)
           column_data_source.set('data', data.data)


add scrollModeSwitcher method

add scrollModeSwitcher method

--- resources/assets/coffee/react/beatmap-discussions/mode-switcher.coffee
+++ resources/assets/coffee/react/beatmap-discussions/mode-switcher.coffee
@@ -83,6 +83,11 @@
                   span className: 'page-mode-link__stripe'
     ]

+  scrollModeSwitcher: =>
+    return if !@scrollerRef.current?
+
+    # on mobile, ModeSwitcher becomes horizontally scrollable - scrollTo ensures that the selected tab is made visible
+    $(@scrollerRef.current).scrollTo(".#{@selectedClassName}", 0, {over: {left: -1}})

   switch: (e) =>
     e.preventDefault()


fix(uploads): Don't allow dragging of uploading images (Fixes #366)

--- internal_packages/composer/lib/image-file-upload.cjsx
+++ internal_packages/composer/lib/image-file-upload.cjsx
@@ -1,7 +1,7 @@
 path = require 'path'
 React = require 'react'
 FileUpload = require './file-upload'
-{RetinaImg, DraggableImg} = require 'nylas-component-kit'
+{RetinaImg} = require 'nylas-component-kit'

 class ImageFileUpload extends FileUpload
   @displayName: 'ImageFileUpload'
@@ -12,7 +12,7 @@
   render: =>

-        <RetinaImg name="image-cancel-button.png"/>
+        <RetinaImg name="image-cancel-button.png" mode={RetinaImg.Mode.ContentPreserve}/>

@@ -20,7 +20,7 @@

{@props.uploadData.fileName}

-        <DraggableImg src={@props.uploadData.filePath} />
+

@@ -28,4 +28,7 @@

+  _onDragStart: (event) ->
+    event.preventDefault()
+
 module.exports = ImageFileUpload


Add impersonate feature to UI for selected user

--- client/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -34,6 +34,30 @@
                 label         : "Flags:"
                 name          : "flags"
                 placeholder   : "no flags assigned"
+              Impersonate     :
+                label         : "Switch to User"
+                itemClass     : KDButtonView
+                title         : "Impersonate"
+                callback      : =>
+                  modal = new KDModalView
+                    title          : "Switch to this user?"
+                    content        : "
This action will reload Koding and log you in with this user.
"
+                    height         : "auto"
+                    overlay        : yes
+                    buttons        :
+                      Impersonate  :
+                        style      : "modal-clean-green"
+                        loader     :
+                          color    : "#FFF"
+                          diameter : 16
+                        callback   : =>
+                          accounts = @userController.getSelectedItemData()
+                          unless accounts.length is 0
+                            KD.impersonate accounts[0].profile.nickname, =>
+                              modal.destroy()
+                          else
+                            modal.destroy()
+
           "Migrate Kodingen Users" :
             buttons           :
               Migrate         :


minor change

minor change

--- client/app/lib/components/contentModal/index.coffee
+++ client/app/lib/components/contentModal/index.coffee
@@ -46,16 +46,16 @@
       tagName : 'main'
       cssClass : 'main-container'

-
-    if typeof content is 'string'
-      @main.setPartial content
-    else
-      @main.addSubView content
-
+    if content
+      if typeof content is 'string'
+        @main.setPartial content
+      else
+        @main.addSubView content

     if tabs
       @main.addSubView @modalTabs = new kd.TabViewWithForms tabs
       @setClass 'with-form'
+      @main.setClass 'tabview'

   createFooter: ->


Updated event names

Updated event names

--- client/Social/Members/ContentDisplays/profileview.coffee
+++ client/Social/Members/ContentDisplays/profileview.coffee
@@ -364,13 +364,13 @@
     @lastName.on "click",  -> @setEditingMode yes
     @bio.on "click",       -> @setEditingMode yes

-    @firstName.on "Enter", save
-    @lastName.on "Enter", save
-    @bio.on "Enter", save
-
-    @firstName.on "Blur", save
-    @lastName.on "Blur", save
-    @bio.on "Blur", save
+    @firstName.on "EnterPressed", save
+    @lastName.on "EnterPressed", save
+    @bio.on "EnterPressed", save
+
+    @firstName.on "BlurHappened", save
+    @lastName.on "BlurHappened", save
+    @bio.on "BlurHappened", save

     avatarOptions  =
       showStatus      : yes


opinions: header updatePartial error fix

--- client/app/CommonViews/opinions/opinionviewheader.coffee
+++ client/app/CommonViews/opinions/opinionviewheader.coffee
@@ -94,7 +94,7 @@
     list.on "NewOpinionHasArrived",=>
       @newAnswers++
       @updateRemainingText()
-      @newItemsLink.updatePartial "#{if @newAnswers is 0 then "No" else @newAnswers} new Answer#{if @newAnswers is 1 then "" else "s"}"
+      @newItemsLink?.updatePartial "#{if @newAnswers is 0 then "No" else @newAnswers} new Answer#{if @newAnswers is 1 then "" else "s"}"

       @setClass "has-new-items"
       @show()
@@ -139,26 +139,26 @@
     commentText =
       if repliesCount > 0 then " and #{repliesCount} Comments"
       else ''
-
-    if not @parent? or  @parent.constructor is DiscussionActivityOpinionView
-      if @getData().opinionCount > 1
-        @allItemsLink.updatePartial "View #{@getData().opinionCount} Answers"+commentText
-      else if @getData().opinionCount is 1
-        @allItemsLink.updatePartial "View one Answer"+commentText
+    if @allItemsLink?
+      if not @parent? or  @parent.constructor is DiscussionActivityOpinionView
+        if @getData().opinionCount > 1
+          @allItemsLink.updatePartial "View #{@getData().opinionCount} Answers"+commentText
+        else if @getData().opinionCount is 1
+          @allItemsLink.updatePartial "View one Answer"+commentText
+        else
+          @allItemsLink.updatePartial "No Answers yet#{if repliesCount > 0 then '. View '+repliesCount+' Comment'+(if repliesCount isnt 1 then 's' else '') else ''}"
       else
-        @allItemsLink.updatePartial "No Answers yet#{if repliesCount > 0 then '. View '+repliesCount+' Comment'+(if repliesCount isnt 1 then 's' else '') else ''}"
-    else
-      remainingOpinions = @getData().opinionCount-@getDelegate().items.length
-      if (remainingOpinions)<@maxCommentToShow
-          if remainingOpinions is 1
-            @allItemsLink.updatePartial "View remaining answer"
-          else if remainingOpinions > 1
-            @allItemsLink.updatePartial "View remaining #{remainingOpinions} #{@getOptions().itemTypeString}"
-          else
-            if @newAnswers is 0
-              @allItemsLink.updatePartial ""
+        remainingOpinions = @getData().opinionCount-@getDelegate().items.length
+        if (remainingOpinions)<@maxCommentToShow
+            if remainingOpinions is 1
+              @allItemsLink.updatePartial "View remaining answer"
+            else if remainingOpinions > 1
+              @allItemsLink.updatePartial "View remaining #{remainingOpinions} #{@getOptions().itemTypeString}"
             else
-              @allItemsLink.updatePartial "View new answers"
+              if @newAnswers is 0
+                @allItemsLink.updatePartial ""
+              else
+                @allItemsLink.updatePartial "View new answers"

   pistachio:->
     """


StackTemplateListController: Re-fetch updated stack template from back-end

--- client/app/lib/stacks/stacktemplatelistcontroller.coffee
+++ client/app/lib/stacks/stacktemplatelistcontroller.coffee
@@ -79,8 +79,9 @@

     return  unless target

-    target.setData stackTemplate
-    target.handleLabelStates()
+    @getListView().removeItem target
+
+    @addStackTemplateById stackTemplate._id

   handleRenderStacks: (stacks) ->


Code reorganization

Code reorganization

--- src/include/repository.coffee
+++ src/include/repository.coffee
@@ -195,7 +195,8 @@
         name: 'support.class.prelude.haskell'
         match: "{lb}(#{prelude.types.join('|')}){rb}"
       ,
-        include: '#generic_type'
+        name: 'variable.other.generic-type.haskell'
+        match: /{lb}{functionName}{rb}/
       ,
         include: '#type_name'
     ]
@@ -205,9 +206,6 @@
   empty_list:
     name: 'constant.language.empty-list.haskell'
     match: /\[\]/
-  generic_type:
-    name: 'variable.other.generic-type.haskell'
-    match: /{lb}{functionName}{rb}/
   deriving:
     patterns: [
         {include: '#deriving_list'}


Handle a single slide

Handle a single slide

--- src/jquery.superslides.coffee
+++ src/jquery.superslides.coffee
@@ -35,8 +35,9 @@
   is_mobile = navigator.userAgent.match(/mobile/i)

   start = ->
-    animate (if first_load then 0 else "next")
-    play()
+    if size > 1
+      animate(if first_load then 0 else "next")
+      play()

   stop = ->
     clearInterval play_interval
@@ -142,23 +143,24 @@
       )

   this.each ->
-    $control.css
-      position: 'relative'
-      width: width * 3
-      height: height
-      left: -width
-
-    $container.hide()
-
-    $children.css
-      display: 'none'
-      position: 'absolute'
-      overflow: 'hidden'
-      top: 0
-      left: width
-      zIndex: 0
-
-    adjust_slides_size $children
+    if size > 1
+      $control.css
+        position: 'relative'
+        width: width * 3
+        height: height
+        left: -width
+
+      $container.hide()
+
+      $children.css
+        display: 'none'
+        position: 'absolute'
+        overflow: 'hidden'
+        top: 0
+        left: width
+        zIndex: 0
+
+      adjust_slides_size $children

     # Event bindings
     $(window).resize (e) ->