Prefer the first location range at the position of block boundaries

--- src/trix/models/document.coffee
+++ src/trix/models/document.coffee
@@ -207,7 +207,17 @@
     [leftPosition, rightPosition ? leftPosition]

   locationRangeFromPosition: (position) ->
-    new Trix.LocationRange @blockList.findIndexAndOffsetAtPosition(position)
+    {index, offset} = @blockList.findIndexAndOffsetAtPosition(position)
+    # There are two cursor positions with the same document position
+    # at block boundaries. Prefer the first.
+    if offset is 0 and index > 0
+      leftIndex = index - 1
+      leftOffset = @getBlockAtIndex(leftIndex).getLength()
+      leftPosition = @blockList.findPositionAtIndexAndOffset(leftIndex, leftOffset)
+      if leftPosition is position
+        index = leftIndex
+        offset = leftOffset
+    new Trix.LocationRange {index, offset}

   locationRangeEndsAtEndOfBlock: (locationRange) ->
     {offset, index} = locationRange.end


Send dictionary answers to senders room

Send dictionary answers to senders room

--- handlers/dictionary.coffee
+++ handlers/dictionary.coffee
@@ -61,7 +61,7 @@
 			if err?
 				debug "Error while checking key “#{key}”: #{err}"
 			else if row?
-				api.sendMessage __("[%1$s] %2$s", key, row.value), no, callback
+				api.sendMessage "[#{key}] #{row.value}", no, message.roomID, callback
 	else if message.message[0] is '.' # check other dictionary commands
 		text = message.message[1..].split /\s/
 		[ command, parameters ] = [ text.shift(), text.join ' ' ]


use osu.updateQueryString

use osu.updateQueryString

--- resources/assets/lib/spotlight-select-options.coffee
+++ resources/assets/lib/spotlight-select-options.coffee
@@ -40,10 +40,7 @@

   href: (key) ->
-    url = new URL(window.location)
-    url.searchParams.set 'spotlight', key if key?
-
-    url
+    window.osu.updateQueryString(null, spotlight: key)

   onItemSelected: (item) =>


Show data which sort is based on for each result.

--- src/views/search-results.coffee
+++ src/views/search-results.coffee
@@ -37,7 +37,8 @@
             'keydown': keyhandler
             'focus': 'highlightResult'
             'mouseenter': 'highlightResult'
-
+        initialize: (opts) ->
+            @order = opts.order
         selectResult: (ev) ->
             object_type = @model.get('object_type') or 'unit'
             switch object_type
@@ -52,12 +53,19 @@
         serializeData: ->
             data = super()
             data.specifier_text = @model.getSpecifierText()
+            switch @order
+                when 'distance'
+                    fn = @model.getDistanceToLastPosition
+                    if fn?
+                        data.distance = fn.apply(@model)
             data

     class SearchResultsView extends base.SMCollectionView
         tagName: 'ul'
         className: 'main-list'
         itemView: SearchResultView
+        itemViewOptions: ->
+            order: @parent.getComparatorKey()
         initialize: (opts) ->
             super(opts)
             @parent = opts.parent
@@ -137,6 +145,9 @@
                 @render()
             @listenTo @fullCollection, 'show-all', @nextPage

+        getComparatorKey: ->
+            @fullCollection.getComparatorKey()
+
         serializeData: ->
             if @hidden or not @collection?
                 return hidden: true


Simplify some code in if-tag

Simplify some code in if-tag

--- src/liquid/tags/if.coffee
+++ src/liquid/tags/if.coffee
@@ -27,7 +27,7 @@
     super

   unknownTag: (tag, markup) ->
-    if ["elsif", "else"].indexOf(tag) >= 0
+    if tag in ["elsif", "else"]
       @pushBlock(tag, markup)
     else
       super


Update time reactively

Update time reactively

--- client/views/app/userInfo.coffee
+++ client/views/app/userInfo.coffee
@@ -49,4 +49,11 @@
 		return Session.get('selfVideoUrl')

 	userTime: ->
-		return moment().utcOffset(@utcOffset).format('HH:mm')+		return Template.instance().now?.get().utcOffset(@utcOffset).format('HH:mm')
+
+Template.userInfo.onCreated ->
+	@now = new ReactiveVar moment()
+	self = @
+	Meteor.setInterval ->
+		self.now.set moment()
+	, 30000


Fix scoping on PostView clear errors

Fix scoping on PostView clear errors

--- assets/javascripts/views/post/002_new_post_form.js.coffee
+++ assets/javascripts/views/post/002_new_post_form.js.coffee
@@ -106,7 +106,7 @@
     !errors

   clearErrors: =>
-    for el in DOM.querySelectorAll('.error')
+    for el in DOM.querySelectorAll('.error', @el)
       DOM.removeClass(el, 'error')
     DOM.hide(@elements.errors)


UI copy: sentence case "Popular Topics" => "Popular topics"

--- client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
+++ client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
@@ -56,7 +56,7 @@
   constructor:(options={}, data)->

     @itemClass       = ActiveTopicItemView
-    options.title    = "Popular Topics"
+    options.title    = "Popular topics"
     options.cssClass = "active-topics"

     super options, data


Disabled application session feature. It needs refactored and session button needs to moved into app settings button.

--- client/app/CommonViews/applicationview/applicationtabview.coffee
+++ client/app/CommonViews/applicationview/applicationtabview.coffee
@@ -11,7 +11,7 @@

     super options, data

-    # TODO: fatihacet: session logic should refactored to ui and ux
+    # TODO: fatihacet: session logic should refactored for ui and ux
     @isSessionEnabled = no #options.saveSession and options.sessionName

     appView = @getDelegate()
@@ -28,7 +28,8 @@

     @on 'PaneAdded', =>
       @tabHandleContainer.repositionPlusHandle @handles
-      @initSession @panes.last, => @updateSession() if @isSessionEnabled
+      if @isSessionEnabled
+        @initSession @panes.last, => @updateSession()

     @on 'SaveSession', (data) =>
       @appStorage.setValue @getOptions().sessionKey, data


ApplicationTabView: Code refactor.

ApplicationTabView: Code refactor.

--- client/Main/CommonViews/applicationview/applicationtabview.coffee
+++ client/Main/CommonViews/applicationview/applicationtabview.coffee
@@ -12,20 +12,22 @@

     super options, data

-    appManager = KD.getSingleton "appManager"
+    appManager        = KD.getSingleton "appManager"

     @on "PaneAdded", (pane) =>
       @tabHandleContainer.repositionPlusHandle @handles
+      tabView = this

       pane.on "KDTabPaneDestroy", ->
         # -1 because the pane is still there but will be destroyed after this event
-        if @panes.length - 1 is 0
+        if tabView.panes.length - 1 is 0
+
           if options.closeAppWhenAllTabsClosed
             appManager.quit appManager.getFrontApp()

-          @emit "AllTabsClosed"
+          tabView.emit "AllTabsClosed"

-        @tabHandleContainer.repositionPlusHandle @handles
+        tabView.tabHandleContainer.repositionPlusHandle tabView.handles

       {tabHandle}  = pane
       {plusHandle} = @getOptions().tabHandleContainer


make additional assertions

make additional assertions

--- tests/features/step_definitions/incident_steps.coffee
+++ tests/features/step_definitions/incident_steps.coffee
@@ -6,8 +6,6 @@
     scrollWithinModal = (client, selector, element, padding) ->
       client.execute (selector, element, padding) ->
         offset = $(element).first().offset()
-        if typeof offset == 'undefined'
-          throw new Error 'Suggested incident report does not have any annotations.'
         $(selector).scrollTop(offset.top + padding)
       , selector, element, padding

@@ -54,27 +52,45 @@
     @When /^I add the first suggested incident report$/, ->
       # SuggestedIncidentsModal
       @client.waitForVisible('#suggested-locations-form p.annotated-content')
-      # make sure the element is within view (webdriver.io scroll doesn't work
-      # inside eidr-connect modal)
-      scrollWithinModal(@client,
-          '#suggestedIncidentsModal div.suggested-incidents-wrapper',
-          'span.annotation.annotation-text', -200)
-      # we do not know if the suggestedIncidentsModal will have any annotations
+      if @client.isVisible('div.warn')
+        text = @client.getText('div.warn')
+        assert.equal(text.trim(), 'No incident reports could be automatically extracted from the article.')
+        @client.pause(2000)
+        return true
       if @client.isVisible('span.annotation.annotation-text')
-        @client.click('span.annotation.annotation-text')
+        scrollWithinModal(@client,
+            '#suggestedIncidentsModal div.suggested-incidents-wrapper',
+            'span.annotation.annotation-text', -200)
+        @client.clickWhenVisible('span.annotation.annotation-text')
         # SuggestedIncidentModal
         @client.waitForVisible('#suggestedIncidentModal div.modal-footer')
         scrollWithinModal(@client, '#suggestedIncidentModal',
             'button.save-modal[type="button"]', -200)
-        @client.click('button.save-modal[type="button"]')
+        @client.clickWhenVisible('button.save-modal[type="button"]')
         @client.pause(2000)
+        return true
+      throw new Error 'There was a problem loading suggested incident reports.'

     @Then /^I can "([^"]*)" suggestions$/, (action) ->
-      # store the number of incident reports / sources from the dom
       if action is 'cancel'
         @client.clickWhenVisible('button.confirm-close-modal[type="button"]')
         # confirm close modal
         @client.waitForVisible('#cancelConfirmationModal')
         @client.click('button.confirm[type="button"]')
-      else
-        @client.clickWhenVisible('#add-suggestions')
+        return true
+      # get the original number of incident reports before button has been clicked
+      elements = @client.elements('div.count :first-child')
+      try
+        expectedNumber = parseInt(@client.elementIdText(elements.value[0].ELEMENT).value, 10) + 1
+      catch
+        throw new Error 'Cound not get actual number of incident reports.'
+      # click add-suggestions button
+      @client.clickWhenVisible('#add-suggestions')
+      @client.pause(2000)
+      # get the actual number of incident reports after button has been clicked
+      elements = @client.elements('div.count :first-child')
+      try
+        actualNumber = parseInt(@client.elementIdText(elements.value[0].ELEMENT).value, 10)
+      catch
+        throw new Error 'Cound not get actual number of incident reports.'
+      assert.equal(expectedNumber, actualNumber)


Doesn't seem like this class is used anymore.

--- app/assets/javascripts/application/status_panel.js.coffee
+++ app/assets/javascripts/application/status_panel.js.coffee
@@ -1,224 +0,0 @@
-# Copyright 2014 Square Inc.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License");
-#    you may not use this file except in compliance with the License.
-#    You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS,
-#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#    See the License for the specific language governing permissions and
-#    limitations under the License.
-
-root = exports ? this
-
-# An overview panel that administrators can use to track the progress of
-# localization efforts, add new SHAs to import, and modify project settings.
-#
-class root.StatusPanel
-
-  # Constructs a new status panel manager for an element. Renders the status
-  # panel into that element.
-  #
-  # @param [jQuery element] element The element to contain the status panel.
-  # @param [String] projects_url A URL that returns a JSON-serialized list of
-  #   projects.
-  #
-  constructor: (@element, @dataSource) ->
-    @element.infiniteScroll @dataSource,
-      renderer: (projects) => @renderProjects projects
-
-  # @private
-  renderProjects: (projects) ->
-    for project in projects
-      do (project) =>
-        cell = $('<div/>').addClass('project-cell').appendTo(@element)
-        new Project(project, cell)
-
-# @private
-class Project
-  constructor: (@project, @element) ->
-    summary = @renderSummary().appendTo(@element)
-    details = @renderDetails().appendTo(@element).hide()
-    summary.find('a.toggle').click ->
-      summary.hide()
-      details.show()
-    details.find('a.toggle').click ->
-      details.hide()
-      summary.show()
-    @reload()
-
-  reload: ->
-    @dynamic_portion.empty()
-    loading = $('<p/>').addClass('loading').appendTo(@dynamic_portion)
-    $('<i/>').addClass('fa fa-spinner spinning').appendTo loading
-
-    $.ajax "#{@project.url}.json",
-      success: (project) =>
-        @dynamic_portion.empty()
-        @renderCommits project.commits
-      error: =>
-        @dynamic_portion.empty()
-        $('<i/>').addClass('fa fa-exclamation-circle').appendTo @dynamic_portion
-
-  renderSummary: ->
-    div = $('<div/>').addClass('project-summary')
-    @renderHeader().appendTo div
-    div
-
-  renderDetails: ->
-    div = $('<div/>').addClass('project-details')
-    @renderHeader().appendTo div
-    @dynamic_portion = $('<div/>').appendTo(div)
-    @renderFooter().appendTo div
-    div
-
-  renderHeader: ->
-    h1 = $('<h1/>')
-    $('<a/>').text(' ' + @project.name).addClass('toggle').appendTo h1
-    @renderButtons().appendTo h1
-    h1
-
-  renderFooter: ->
-    div = $('<div/>')
-    footer = $('<form/>').
-      addClass('form-inline').
-      attr({action: @project.commits_url, method: 'POST'}).
-      text("Localize a commit: ").
-      appendTo(div)
-    sha = $('<input/>').addClass('input-small').attr('type', 'text').appendTo(footer).attr
-      name: 'commit[revision]'
-      placeholder: 'SHA'
-      maxlength: 40
-    footer.append ' '
-    $('<input/>').attr({type: 'submit', value: 'Add'}).addClass('btn btn-primary btn-small').appendTo footer
-    new SmartForm footer, (xhr) =>
-      @reload()
-      success_div = $('<div/>').addClass('alert alert-success').text(xhr.message).insertBefore(footer)
-      $('<a/>').addClass('close').attr('data-dismiss', 'alert').html('×').prependTo success_div
-      sha.val ''
-    div
-
-  renderButtons: ->
-    buttons = $('<span/>').addClass('buttons pull-right')
-    edit = $('<a/>').attr({href: @project.edit_url, title: 'Edit'}).appendTo(buttons)
-    $('<i/>').addClass('fa fa-pencil-square-o').appendTo edit
-    buttons
-
-  renderCommits: (commits) ->
-    for commit in commits
-      do (commit) =>
-        commitDiv = $('<div/>').addClass('commit').appendTo @dynamic_portion
-        new Commit(this, commit, commitDiv)
-
-# @private
-class Commit
-  constructor: (@project, @commit, @element) ->
-    summary = @renderSummary().appendTo(@element)
-    details = @renderDetails().appendTo(@element).hide()
-
-    summary.click ->
-      summary.hide()
-      details.show()
-    details.find('a.toggle').click ->
-      details.hide()
-      summary.show()
-
-    @updateProgress()
-    @setRefreshTimer() if @commit.loading || @commit.translations_total == 0
-
-  renderSummary: ->
-    @summary_progress = @renderProgress()
-    @summary_progress.find('.text-progress').text "#{@commit.revision[0..6]}: #{@commit.message[0..40]}"
-    @summary_progress
-
-  renderDetails: ->
-    div = $('<div/>').addClass('commit-details')
-    h3 = $('<h3/>').appendTo(div)
-
-    header = $('<div/>').appendTo(h3)
-
-    $('<a/>').addClass('fa fa-chevron-up toggle').attr('href', '#').appendTo(header)
-    header.append ' '
-
-    link = $('<a/>').text("Commit ").attr({href: @commit.github_url, target: '_blank'}).appendTo(header)
-    $('<strong/>').text(@commit.revision[0..6]).appendTo link
-
-    @string_count = $('<span/>').addClass('badge pull-right').appendTo(header)
-    @string_count.addClass("ready") if @commit.ready
-    $('<span/>').text(" (" + @commit.committed_at[0..9] + ", " + @commit.committed_at[11..15] + ")").appendTo(h3)
-    $('<pre/>').text(@commit.message).appendTo div
-
-    @details_progress = @renderProgress().appendTo(div)
-    @renderImportForm().appendTo div
-
-    div
-
-  renderProgress: ->
-    progress = $('<div/>').addClass('progress')
-    $('<div/>').addClass('bar').appendTo progress
-    $('<span/>').addClass('text-progress').appendTo(progress)
-    progress
-
-  renderImportForm: ->
-    importForm = $('<form/>').addClass('form-inline').attr({method: 'POST', action: @commit.import_url})
-    label = $('<label/>').text('Import and approve a localization: ').appendTo(importForm)
-
-    localeField = $('<input/>').attr({type: 'text', name: 'locale'}).addClass('locale-field').appendTo(label)
-    if window.localesLoaded
-      new LocaleField(localeField)
-    else
-      $(document).bind 'locales_loaded', -> new LocaleField(localeField)
-
-    importForm.append ' '
-    $('<input/>').addClass('btn btn-primary').val("Import").attr('type', 'submit').appendTo importForm
-
-    new SmartForm importForm, => @project.reload()
-    @renderButtons().appendTo importForm
-
-    importForm
-
-
-  renderButtons: ->
-    buttons = $('<span/>').addClass('pull-right')
-
-    $('<a/>').attr('href', @commit.status_url).addClass('btn btn-mini').text('Status').appendTo buttons
-    buttons.append ' '
-
-    $('<a/>').attr({href: @commit.url, 'data-method': 'DELETE', 'data-confirm': "Are you sure you want to delete this commit?"}).addClass('btn btn-mini btn-danger disable-when-loading').text('Delete').appendTo buttons
-
-    buttons
-
-  setRefreshTimer: ->
-    @element.everyTime 1000, 'refresh', =>
-      $.ajax "#{@commit.url}.json",
-        success: (commit) =>
-          @commit = commit
-          @updateProgress()
-          @element.stopTime('refresh') unless @commit.loading
-
-  updateProgress: ->
-    @summary_progress.removeClass
-    @details_progress.removeClass
-
-    if @commit.loading
-      @element.find('.disable-when-loading').attr 'disabled', 'disabled'
-      @summary_progress.addClass 'progress-striped progress-warning active'
-      @details_progress.addClass 'progress-striped progress-warning active'
-    else if @commit.translations_done == @commit.translations_total
-      @summary_progress.addClass 'progress-success'
-      @details_progress.addClass 'progress-success'
-    else
-      @element.find('.disable-when-loading').removeAttr 'disabled'
-
-    @string_count.text numberWithDelimiter(@commit.strings_total)
-    if @commit.ready
-      @string_count.addClass("ready")
-    else
-      @string_count.removeClass("ready")
-
-    @summary_progress.find('.bar').css('width', "#{if @commit.loading then 100 else @commit.percent_done}%")
-    @details_progress.find('.bar').css('width', "#{if @commit.loading then 100 else @commit.percent_done}%")
-    @details_progress.find('.text-progress').text "#{numberWithDelimiter @commit.translations_done} / #{numberWithDelimiter @commit.translations_total}"


remove extra input file

remove extra input file

--- client/apps/edit/components/section_image_collection/input.coffee
+++ client/apps/edit/components/section_image_collection/input.coffee
@@ -1,69 +0,0 @@
-# Using `try` here b/c Scribe is an AMD module that doesn't play nice when
-# requiring it for testing in node.
-try
-  Scribe = require 'scribe-editor'
-  scribePluginToolbar = require 'scribe-plugin-toolbar'
-  scribePluginSanitizer = require '../../lib/sanitizer.coffee'
-  scribePluginLinkTooltip = require 'scribe-plugin-enhanced-link-tooltip'
-_ = require 'underscore'
-React = require 'react'
-icons = -> require('./icons.jade') arguments...
-toggleScribePlaceholder = require '../../lib/toggle_scribe_placeholder.coffee'
-{ div, a, p, nav, button } = React.DOM
-
-module.exports = React.createClass
-
-  getInitialState: ->
-    caption: @props.caption or ''
-    images: @props.images
-    url: @props.url
-
-  componentDidMount: ->
-    @attachScribe()
-
-  componentDidUpdate: ->
-    @attachScribe()
-
-  attachScribe: ->
-    return if @scribe? or not @props.editing
-    return unless @refs.editable
-    @scribe = new Scribe @refs.editable.getDOMNode()
-    @scribe.use scribePluginSanitizer {
-      tags:
-        i: true
-        a: { href: true, target: '_blank' }
-    }
-    @scribe.use scribePluginToolbar @refs.toolbar.getDOMNode()
-    @scribe.use scribePluginLinkTooltip()
-    toggleScribePlaceholder @refs.editable.getDOMNode()
-
-  onEditableKeyup: ->
-    toggleScribePlaceholder @refs.editable.getDOMNode()
-    url = $(@refs.editable.getDOMNode()).data('id')
-    newImages = _.clone @props.images
-    image = _.find(newImages, url: url)
-    image.caption = $(@refs.editable.getDOMNode()).html()
-
-  render: ->
-    div { className: 'esic-caption-container' },
-      div {
-        className: 'esic-caption bordered-input'
-        ref: 'editable'
-        onKeyUp: @onEditableKeyup
-        'data-id': @props.url
-        dangerouslySetInnerHTML: __html: @props.caption
-      }
-      nav { ref: 'toolbar', className: 'edit-scribe-nav esic-nav' },
-        button {
-          'data-command-name': 'italic'
-          dangerouslySetInnerHTML: __html: ' '
-          disabled: if @props.caption then false else true
-          onClick: @onEditableKeyup
-        }
-        button {
-          'data-command-name': 'linkPrompt'
-          dangerouslySetInnerHTML:
-            __html: " " + $(icons()).filter('.link').html()
-          disabled: if @props.caption then false else true
-          onClick: @onEditableKeyup
-        }


StackEditor: Enable autocomplete.

StackEditor: Enable autocomplete.

--- client/stacks/lib/views/stacks/editors/basestackeditorview.coffee
+++ client/stacks/lib/views/stacks/editors/basestackeditorview.coffee
@@ -40,7 +40,6 @@
       ace.setTheme 'github', no
       ace.setTabSize 2, no
       ace.setShowPrintMargin no, no
-      ace.setEnableAutocomplete no, no
       ace.setUseSoftTabs yes, no
       ace.setScrollPastEnd yes, no


Fix render call

Fix render call

--- src/angular-jed.coffee
+++ src/angular-jed.coffee
@@ -149,6 +149,7 @@
           render = (count, placeholders) ->
             _count = count
             _placeholders = placeholders
+            return unless ready
             if $scope.count.toString() == '0' and $scope.none
               result = i18n._ $scope.none
             else
@@ -164,7 +165,7 @@
             if typeof parseInt($scope.count) != 'number' or $scope.count == ''
               return

-            ready && render($scope.count, $scope.placeholders)
+            render($scope.count, $scope.placeholders)
           )
       )
   ]


Allow n/a for number inputs

Allow n/a for number inputs

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -140,7 +140,7 @@
     if not val
       return

-    if not val.val().toString().match(@numberRegex)
+    if not val.val().toString().match(@numberRegex) && val.val().toString().toLowerCase().trim() != "n/a"
       @addErrorMessage(question, "Not a valid number")

   validateTotalOverseas: () ->


Also mention the rating value in the tooltip.

--- app/assets/javascripts/helpers/community_rating.js.coffee
+++ app/assets/javascripts/helpers/community_rating.js.coffee
@@ -6,10 +6,13 @@
   if maxRating == 0
     maxRating = 1

+  currentRatingValue = 0.5
+
   html = '
'
   for rating in ratings
     height = rating * 100.0 / maxRating
-    html += '
'
+    html += '
'
+    currentRatingValue += 0.5
     html += '

'
     html += '
'
   html += '
'


log when a worker is removed from the pool

--- services/spelling/app/coffee/ASpellWorkerPool.coffee
+++ services/spelling/app/coffee/ASpellWorkerPool.coffee
@@ -17,6 +17,7 @@
 			return null
 		worker = new ASpellWorker(language, @options)
 		worker.pipe.on 'exit', () =>
+			logger.log process: worker.pipe.pid, lang: language, "removing aspell worker from pool"
 			@cleanup()
 		@PROCESS_POOL.push(worker)
 		metrics.gauge 'aspellWorkerPool-size', @PROCESS_POOL.length


Drops unused Coffeescript

Drops unused Coffeescript

--- app/assets/javascripts/users/omniauth_callbacks.js.coffee
+++ app/assets/javascripts/users/omniauth_callbacks.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/


Put anchor transparent background in front of circle

--- myapp/client/tools.coffee
+++ myapp/client/tools.coffee
@@ -265,8 +265,8 @@
 	})
 	Tools.makeHoverable background, anchor

+	group.add anchor
 	group.add background
-	group.add anchor
 	group


Add spaces.

Add spaces.

Signed-off-by: crynobone <e1a543840a942eb68427510a8a483282a7bfeddf@gmail.com>

--- src/application.coffee
+++ src/application.coffee
@@ -6,21 +6,27 @@
     throw new Error("underscore.js is missing")

 class Application
+
     array_make = (args) ->
         Array.prototype.slice.call(args)
+
     config: {}
     environment: 'production'
     instances: {}
+
     detectEnvironment: (environment) ->
         environment = environment.apply(root) if _.isFunction(environment) is yes

         @environment = environment
+
     env: ->
         @environment
+
     get: (key, alt) ->
         return @config[key] if typeof @config[key] isnt 'undefined'

         alt ?= null
+
     put: (key, value) ->
         config = key
         unless _.isObject(config)
@@ -28,8 +34,10 @@
             config[key] = value

         @config = _.defaults(config, @config)
+
     bind: (name, instance) ->
         @instances[name] = instance
+
     make: (name) ->
         options = array_make(arguments)
         name = options.shift()
@@ -38,16 +46,19 @@
         return base.apply(root, options) if _.isFunction(base)

         base
+
     resolve: (name) ->
         base = @instances[name]
         base = null if _.isUndefined(base)

         base
+
     when: (environment, callback) ->
         env = @ENV if @ENV?
         env = @environment if @environment?

         @run(callback) if env is environment or environment is '*'
+
     run: (callback) ->
         callback.call(@) if _.isFunction(callback)


InvitedItemView: Implemented resend logic.

--- client/admin/lib/views/invitations/inviteditemview.coffee
+++ client/admin/lib/views/invitations/inviteditemview.coffee
@@ -1,5 +1,6 @@
 kd                 = require 'kd'
 JView              = require 'app/jview'
+remote             = require('app/remote').getInstance()
 KDButtonView       = kd.ButtonView
 KDTimeAgoView      = kd.TimeAgoView
 KDListItemView     = kd.ListItemView
@@ -42,6 +43,23 @@
         duration : 5000

+  resend: ->
+
+    return  unless @getOptions().statusType is 'pending'
+
+    @resendButton.showLoader()
+
+    remote.api.JInvitation.sendInvitationByCode @getData().code, (err) =>
+      @resendButton.hideLoader()
+      title    = 'Invitation is resent.'
+      duration = 5000
+
+      if err
+        title  = 'Unable to resend the invitation. Please try again.'
+
+      return new KDNotificationView { title, duration }
+
+
   createViews: ->

     { hash, createdAt } = @getData()
@@ -76,9 +94,11 @@
         loader   : color : '#4a4e52'
         callback : @bound 'revoke'

-      @settings.addSubView new KDButtonView
+      @settings.addSubView @resendButton = new KDButtonView
         cssClass : 'solid compact outline'
         title    : 'RESEND INVITATION'
+        loader   : color : '#4a4e52'
+        callback : @bound 'resend'

   pistachio: ->


removing trailing whitespace

removing trailing whitespace

--- client/landing/app/MainApp/maincontroller/mainview.coffee
+++ client/landing/app/MainApp/maincontroller/mainview.coffee
@@ -1,7 +1,7 @@
 class MainView extends KDView
-
+
   viewAppended:->
-
+
     # @putLoader()
     @addHeader()
     @createMainPanels()
@@ -10,14 +10,14 @@
     # @removeLoader()
     @windowController = @getSingleton("windowController")
     @listenWindowResize()
-
+
     setTimeout =>
       @putWhatYouShouldKnowLink()
     ,5000

   addBook:->
     @addSubView new BookView
-
+
   setViewState:(state)->
     if state is 'background'
       @contentPanel.setClass 'no-shadow'
@@ -48,7 +48,7 @@
       # delete KD.singletons.loadingScreen
       $loadingScreen.remove()
       $('body').removeClass 'loading'
-
+
   createMainPanels:->
     @addSubView @panelWrapper = new KDView
       tagName  : "section"
@@ -62,7 +62,7 @@

     @registerSingleton "contentPanel", @contentPanel, yes
     @registerSingleton "sidebarPanel", @sidebarPanel, yes
-
+
   addHeader:()->
     @addSubView @header = new KDView
       tagName : "header"
@@ -71,7 +71,7 @@
       tagName   : "a"
       domId     : "koding-logo"
       # cssClass  : "hidden"
-      attributes:
+      attributes:
         href    : "#"
       click     : (pubInst,event)=>
         if KD.isLoggedIn()
@@ -80,19 +80,19 @@
           appManager.openApplication "Home"

     @addLoginButtons()
-
+
   addLoginButtons:->
     @header.addSubView @buttonHolder = new KDView
       cssClass  : "button-holder hidden"

     mainController = @getSingleton('mainController')
-
+
     # @buttonHolder.addSubView new KDButtonView
     #   title     : "About Koding"
     #   domId     : "about-button"
     #   callback  : =>
     #     mainController.propagateEvent KDEventType : "AboutButtonClicked", globalEvent : yes
-    #
+    #
     @buttonHolder.addSubView new KDButtonView
       title     : "Sign In"
       style     : "koding-blue"
@@ -112,7 +112,7 @@
       domId    : "main-tab-handle-holder"
       cssClass : "kdtabhandlecontainer"
       delegate : @
-
+
     @mainTabView = new MainTabView
       domId              : "main-tab-view"
       listenToFinder     : yes
@@ -128,7 +128,7 @@
     @sidebar = new Sidebar domId : "sidebar", delegate : @
     @emit "SidebarCreated", @sidebar
     @sidebarPanel.addSubView @sidebar
-
+
   changeHomeLayout:(isLoggedIn)->

   decorateLoginState:(isLoggedIn = no)->
@@ -147,14 +147,14 @@

     @changeHomeLayout isLoggedIn
     @utils.wait 300, => @notifyResizeListeners()
-
+
   _windowDidResize:->

     {winHeight} = @windowController
     @panelWrapper.setHeight winHeight - 51

   putWhatYouShouldKnowLink:->
-
+
     @header.addSubView link = new KDCustomHTMLView
       tagName     : "a"
       domId       : "what-you-should-know-link"
@@ -169,7 +169,7 @@
             # url       : KD.apiUri+'https://api.koding.com/1.0/logout'
             url       : "/beta.txt"
             success	  : (response)=>
-
+
               modal = new KDModalView
                 title       : "Thanks for joining our beta."
                 cssClass    : "what-you-should-know-modal"
@@ -181,7 +181,7 @@
                     title   : 'Close'
                     style   : 'modal-clean-gray'
                     callback: -> modal.destroy()
-
+
               {winHeight} = @getSingleton('windowController')
               modal.$('.kdmodal-content').css 'max-height', winHeight - 200
               modal.setY (winHeight - modal.getHeight())/2


Login: 2FA: Updated 2FA Code input placeholder

--- client/landing/site.landing/coffee/login/loginform.coffee
+++ client/landing/site.landing/coffee/login/loginform.coffee
@@ -40,7 +40,7 @@
     @tfcode = new LoginInputView
       inputOptions    :
         name          : 'tfcode'
-        placeholder   : '2factor verification code'
+        placeholder   : 'Two-Factor Authentication Code'
         testPath      : 'login-form-tfcode'
         attributes    :
           testpath    : 'login-form-tfcode'


Fix EugeniaNotation to work with Paper 0.99

--- app/scripts/controllers/notations/eugenia_notation.coffee
+++ app/scripts/controllers/notations/eugenia_notation.coffee
@@ -1,6 +1,7 @@
 define [
+  'paper'
   'models/node_shape'
-], (NodeShape) ->
+], (Paper, NodeShape) ->

   class EugeniaNotation
     reconstruct: (item) ->
@@ -114,7 +115,7 @@
       """

     serialiseColor: (color) ->
-      c = new paper.Color(color)
+      c = new Paper.Color(color)
       "#{c.red*255},#{c.green*255},#{c.blue*255}"

@@ -185,7 +186,7 @@

     deserialiseColor: (color) ->
       [all,r,g,b] = color.match("(\\d+),(\\d+),(\\d+)")
-      new paper.Color(r/255, g/255, b/255).toCssString()
+      new Paper.Color(r/255, g/255, b/255).toCSS()

     deserialiseName: (definition) ->
       class_pattern = /class\s+(\w*)/


fix: use userAsync

fix: use userAsync


Refactor setting DOM range

Refactor setting DOM range

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -20,9 +20,13 @@
     else
       new Trix.LocationRange start, end

-    if locationRange = @setDOMRange(locationRange)
+    if domRange = @createDOMRangeFromLocationRange(locationRange)
+      # Selection#addRange is unreasonably slow in WebKit when performed in the
+      # same call stack as a mouse or key event so defer calling it.
+      # https://code.google.com/p/chromium/issues/detail?id=423170
+      # https://code.google.com/p/chromium/issues/detail?id=138439
+      defer => setDOMRange(domRange) if document.activeElement is @element
       @updateCurrentLocationRange(locationRange)
-      locationRange

   setLocationRangeFromPoint: (point) ->
     if locationRange = @getLocationRangeAtPoint(point)
@@ -68,8 +72,7 @@
     point = @getSelectionEndPoints()[0]
     block()
     locationRange = @getLocationRangeAtPoint(point)
-    @setDOMRange(locationRange)
-    locationRange
+    @setLocationRange(locationRange)

   clearSelection: ->
     getDOMSelection()?.removeAllRanges()
@@ -88,33 +91,18 @@
       @currentLocationRange = locationRange
       @delegate?.locationRangeDidChange?(@currentLocationRange)

-  setDOMRange: benchmark "SelectionManager#setDOMRange", (locationRange) ->
+  createDOMRangeFromLocationRange: (locationRange) ->
     rangeStart = @findContainerAndOffsetForLocation(locationRange.start)
-    rangeEnd =
-      if locationRange.isCollapsed()
-        rangeStart
-      else
-        @findContainerAndOffsetForLocation(locationRange.end)
-
-    return unless rangeStart? and rangeEnd?
-
-    range = document.createRange()
-    range.setStart(rangeStart...)
-    range.setEnd(rangeEnd...)
-
-    applyRange = benchmark "  #setDOMRange -> applyRange", =>
-      if document.activeElement is @element
-        selection = window.getSelection()
-        selection.removeAllRanges()
-        selection.addRange(range)
-
-    # Selection#addRange is unreasonably slow in WebKit when performed in the
-    # same call stack as a mouse or key event so defer calling it.
-    # https://code.google.com/p/chromium/issues/detail?id=423170
-    # https://code.google.com/p/chromium/issues/detail?id=138439
-    defer(applyRange)
-
-    locationRange
+    rangeEnd = if locationRange.isCollapsed()
+      rangeStart
+    else
+      @findContainerAndOffsetForLocation(locationRange.end)
+
+    if rangeStart? and rangeEnd?
+      range = document.createRange()
+      range.setStart(rangeStart...)
+      range.setEnd(rangeEnd...)
+      range

   createLocationRangeFromDOMRange: (range) ->
     return unless range? and @rangeWithinElement(range)
@@ -273,6 +261,11 @@
   getDOMRange = ->
     getDOMSelection()?.getRangeAt(0)

+  setDOMRange = (domRange) ->
+    selection = window.getSelection()
+    selection.removeAllRanges()
+    selection.addRange(domRange)
+
   getClientRects = ->
     rects = getDOMRange()?.getClientRects()
     rects if rects?.length


Improved username extraction

Improved username extraction

--- collections/tabulartables.coffee
+++ collections/tabulartables.coffee
@@ -8,12 +8,7 @@
     columns: [
         {data: "timestamp", title: "Time", render: (val) -> moment(val).format(Session.get("momentLogDateFormat")) }
         {data: "level", title: "Level" }
-        {
-            data: (row) ->
-                a = 2
-                row.data?.user?.username
-            title: "User"
-        }
+        {data: "data.user.username", title: "User" }
         {data: "message", title: "Message" }
     ]
     extraFields: ['data']


Ensure console methods are forwarded in all browsers

--- src/trix/core/helpers.coffee
+++ src/trix/core/helpers.coffee
@@ -25,7 +25,7 @@

   forwardMethods: ({ofConstructor, ofObject, onConstructor, toMethod, toProperty} = {}) ->
     source = ofObject ? ofConstructor.prototype
-    methodNames = Object.getOwnPropertyNames(source)
+    methodNames = getPropertyNames(source)
     destination = onConstructor.prototype
     reservedNames = ["constructor", "toString", "valueOf"]

@@ -52,3 +52,11 @@

 formatValue = (value) ->
   value?.inspect?() ? (try JSON.stringify(value)) ? value
+
+getPropertyNames = (object) ->
+  result = {}
+  while object
+    for name in Object.getOwnPropertyNames(object)
+      result[name] = true
+    object = object.__proto__
+  Object.keys(result)


Remove unused method

Remove unused method

--- packages/rocketchat-lib/server/models/Users.coffee
+++ packages/rocketchat-lib/server/models/Users.coffee
@@ -26,19 +26,7 @@

 	findOneByEmailAddress: (emailAddress, options) ->
 		query =
-			'emails.address': new RegExp(s.escapeRegExp(emailAddress), 'i')
-
-		return @findOne query, options
-
-	findOneVerifiedFromSameDomain: (email, options) ->
-		domain = s.strRight(email, '@')
-		query =
-			emails:
-				$elemMatch:
-					address:
-						$regex: new RegExp "@" + domain + "$", "i"
-						$ne: email
-					verified: true
+			'emails.address': new RegExp("^" + s.escapeRegExp(emailAddress) + "$", 'i')

 		return @findOne query, options


[skip] Deleted unused code (1m)

[skip] Deleted unused code (1m)

--- app/assets/javascripts/houston/roadmap/views/thumbnail_roadmap_view.coffee
+++ app/assets/javascripts/houston/roadmap/views/thumbnail_roadmap_view.coffee
@@ -93,10 +93,6 @@
       .key (milestone)-> milestone.band
       .entries(visibleMilestones)

-    startDate = d3.min(visibleMilestones, (milestone)-> milestone.startDate)
-    startDate ||= d3.time.format('%Y-%m-%d').parse('2014-08-01')
-    endDate = 2.years().after startDate
-
     bands = @roadmap.selectAll('.roadmap-thumbnail-band')
       .data(milestoneBands, (band)-> band.key)


Initialize shipping method when empty

Initialize shipping method when empty

--- app/assets/javascripts/sprangular/directives/shippingRatesSelection.coffee
+++ app/assets/javascripts/sprangular/directives/shippingRatesSelection.coffee
@@ -32,4 +32,7 @@

               order.shipping_method_id = null unless _.find(results, (rate) -> rate.shippingMethodId == order.shipping_method_id)

+              if order.shipping_method_id == null && results.length > 0
+                order.shipping_method_id = results[0].shippingMethodId
+
               $scope.loading = false


KD: nav items setting all together

KD: nav items setting all together

--- core/KD.coffee
+++ core/KD.coffee
@@ -176,6 +176,8 @@

   getNavItems        : -> @navItems.sort (a, b)-> a.order - b.order

+  setNavItems        : (navItems)-> @navItems = navItems.sort (a, b)-> a.order - b.order
+
   unregisterAppClass :(name)-> delete KD.appClasses[name]

   getAppClass        :(name)-> KD.appClasses[name]?.fn or null


Added general toJSON method and edge case for  attribute

--- src/mixins/storage.coffee
+++ src/mixins/storage.coffee
@@ -29,6 +29,13 @@
     storage: ( ) ->
         return localStorage

+    toJSON: ( obj ) ->
+      if obj.has?("$el")
+        obj.get("$el").toJSON = () ->
+          @clone().wrap('<div/>').parent().html()
+
+      JSON.stringify obj
+
     indexRoot: ( ) ->
       return inflection.transform(@name, ['underscore', 'pluralize'])

@@ -37,12 +44,12 @@
       indexRoot = @indexRoot?() ? @indexRoot
       key = "#{indexRoot}/#{instance.id}"
       if data?
-        key += "/"+Tails.Utils.Hash JSON.stringify data
-        json = JSON.stringify
+        key += "/"+Tails.Utils.Hash @toJSON data
+        json = @toJSON
           "instance" : instance
           "data" : data
       else
-        json = JSON.stringify instance
+        json = @toJSON instance
       @storage().setItem key, json
       return key

@@ -63,7 +70,7 @@
     InstanceMethods: @with Tails.Mixins.Debug,
       included : () =>
         @after initialize: () ->
-          console.log JSON.stringify @name
+          console.log @toJSON @name
           @after store: () ->
             @log "Stored instances", @constructor.pluck("id")

@@ -74,5 +81,5 @@
         extended: () ->
           @after store: () ->
             key = @indexRoot?() ? @indexRoot
-            json = JSON.stringify @constructor.pluck("id")
+            json = @toJSON @constructor.pluck("id")
             @constructor.storage().setItem key, json


more specific

more specific

--- scripts/stylez.coffee
+++ scripts/stylez.coffee
@@ -4,7 +4,7 @@

 imageMe = (msg) ->
   msg.http('http://ajax.googleapis.com/ajax/services/search/images')
-    .query(v: "1.0", rsz: '8', q: 'furious stylez pee wees playhouse')
+    .query(v: "1.0", rsz: '8', q: 'cowboy curtis pee wees playhouse')
     .get() (err, res, body) ->
       images = JSON.parse(body)
       images = images.responseData.results


ipython publish: fix bug when file not in root path.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -4633,7 +4633,7 @@
             path        : @path
             project_id  : @editor.project_id
             command     : 'ipython'
-            args        : ['nbconvert', @filename, "--to=#{opts.format}"]
+            args        : ['nbconvert', @file, "--to=#{opts.format}"]
             bash        : false
             err_on_exit : true
             timeout     : 30


Remove hack

Remove hack

--- spec/buffer/buffer-find-and-replace-view-spec.coffee
+++ spec/buffer/buffer-find-and-replace-view-spec.coffee
@@ -22,11 +22,11 @@
       rootView.open('sample.js')
       rootView.enableKeymap()
       editor = rootView.getActiveView()
-      editor.attached = true #hack as I cant get attachToDom() to work
+      rootView.attachToDom()

       subject = FindAndReplace.activateForBuffer()

-    describe "when find-and-replace:display-find is triggered", ->
+    ffdescribe "when find-and-replace:display-find is triggered", ->
       it "attaches to the root view", ->
         editor.trigger 'find-and-replace:display-find'
         expect(subject.hasParent()).toBeTruthy()


channel spec: verify the token is set before dispatching event

--- spec/javascripts/websocket_rails/channel_spec.coffee
+++ spec/javascripts/websocket_rails/channel_spec.coffee
@@ -19,6 +19,12 @@
         queue = @channel._queue
         expect(queue[0].name).toEqual 'someEvent'
         expect(queue[0].data).toEqual 'someData'
+
+    describe 'when channel token is set', ->
+      it 'adds token to event metadata and dispatches event', ->
+        @channel._token = 'valid token'
+        @channel.trigger 'someEvent', 'someData'
+        expect(@dispatcher.trigger_event.calledWith(['someEvent',{token: 'valid token', data: 'someData'}]))

   describe 'public channels', ->
     beforeEach ->


components: fix ListenProperty

components: fix ListenProperty

--- components/ListenProperty.coffee
+++ components/ListenProperty.coffee
@@ -29,12 +29,10 @@
         required: no

     @inPorts.readback.on 'data', (@readback) =>
-    noflo.helpers.WirePattern @,
-      in: ['object', 'property']
-      out: []
-      forwardGroups: false,
-      (data, groups, out) =>
-        @updateListener(data.object, data.property)
+    @inPorts.object.on 'data', (object) =>
+      @updateListener object, @property
+    @inPorts.property.on 'data', (property) =>
+      @updateListener @object, property

   updateListener: (object, property) ->
     @disconnectListener()


Optional defer boolean can be passed for autofocus

Commonly needed when wanting to autofocus anything rendered client-side

--- components/form/utilities.coffee
+++ components/form/utilities.coffee
@@ -1,3 +1,4 @@
+_ = require 'underscore'
 { isTouchDevice } = require '../util/device.coffee'

 module.exports =
@@ -8,7 +9,11 @@
     val = $input.val()
     $input.val('').val val

-  autofocus: ($input) ->
+  autofocus: ($input, defer = false) ->
     return if isTouchDevice()
-    $input.focus()
-    @moveCursorToEnd $input
+
+    focus = =>
+      $input.focus()
+      @moveCursorToEnd $input
+
+    if defer then _.defer(focus) else focus()


Fixed Couchbase bug (N1QL with wrong spelling).

--- server/src/Component/Builtin/DataSource/Couchbase.coffee
+++ server/src/Component/Builtin/DataSource/Couchbase.coffee
@@ -23,7 +23,7 @@
         dataSourceName = dataSourceName or 'default'
         @_couchbase = require 'couchbase'
         @_dataSourceName = dataSourceName
-        @n1ql = require('couchbase').N1q1Query
+        @n1ql = require('couchbase').N1qlQuery

     # Component initialization
     # Check if the data source specified in the constructor exists


Fixed an embarrasing bug.. no amount of testing will replace actually trying to build stuff with a lib.

--- src/node.module.js.coffee
+++ src/node.module.js.coffee
@@ -38,16 +38,16 @@

 	@deactivate: (node) ->
 		if node.isActivated() and not @lineageIsBusy(node)
-			if node.mode == Node.Modes.EXCLUSIVE
+			if node.mode() == Node.Modes.EXCLUSIVE
 				if child = node.activatedChildren()[0]
 					child.setOnDeactivatedAction
 						node: node
 						transition: Node.Transitions.DEACTIVATE
-					return ControllerHelper.deactivate(child)
+					return Node.deactivate(child)

 			else if node.mode == Node.Modes.MULTI
 				for child in node.activatedChildren()
-					ControllerHelper.deactivate(child)
+					Node.deactivate(child)

 			node.attemptTransition Node.Transitions.DEACTIVATE


Use HTML title attributes for Group permission grid, until we implement the KDMarqueeView its fine to use this.

--- client/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
+++ client/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
@@ -170,12 +170,8 @@
       itemClass     : KDView
       partial       : readableText current
       cssClass      : 'text header-item role-'+__utils.slugify(current)
+      attributes    :
         title       : readableText current
-        placement   : 'top'
-        direction   : 'center'
-        offset      :
-          top       : 5
-          left      : 0
     if current and remainder.length > 0
       cascadeData[current].nextElementFlat = cascadeHeaderElements remainder
     return cascadeData
@@ -199,13 +195,8 @@
           itemClass     : KDView
           partial       : readableText permission
           cssClass      : 'text'
-          tooltip       :
+          attributes    :
             title       : readableText permission
-            direction   : 'center'
-            placement   : 'left'
-            offset      :
-              top       : 3
-              left      : 0
           nextElementFlat :
             cascadeFormElements set, roles, module, permission
     permissionOptions


hopefully postpones a long series of ulimit issues on mac os x, at least until node 0.12 is out / stable

--- options.coffee
+++ options.coffee
@@ -4,11 +4,11 @@
     path: "harp"
     port: 9000
   compass:
-    watch: "{harp,sites,apps}/**/*.{sass,scss}"
+    watch: ["{harp,sites,apps}/**/*.{sass,scss}", "!**/node_modules/**"]
     config: "{harp,sites,apps}/**/{config,compass}.rb"
     ensure: true # compile everything to be sure
   livereload:
-    watch: "{harp,sites,apps}/**/*.{jade,html,css,js,coffee}"
+    watch: ["{harp,sites,apps}/**/*.{jade,html,css,js,coffee}", "!**/node_modules/**"]
   gulp:
     default: ["server", "compass", "livereload"]


add Non Breaking Space entities (` ` and `&~;`)


added foldUp and unfold methods

added foldUp and unfold methods

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -460,6 +460,7 @@
     curl:
       twist: false
     ramp: {}
+    foldUp: {}

   reset: (callback) ->
@@ -564,6 +565,60 @@
     @_callback options

+  foldUp: (anchor, callback) ->
+    if !anchor
+      anchor = 'left'
+    else if typeof anchor is 'function'
+      callback = anchor
+
+    normalized = @_normalizeArgs 'foldUp', [0, anchor, {}]
+    return if !normalized
+    anchor = normalized[1]
+    @isFoldedUp = true
+    limit = @panels[anchor].length - 1
+
+    for i in [limit..1] by -1
+      delay = (limit - i) * @settings.speed * .6
+      angle = 100
+
+      do (i, delay, angle) =>
+
+        setTimeout =>
+
+          if i isnt limit
+            do (j = i) =>
+              setTimeout =>
+                @panels[anchor][j].style.display = 'none'
+              , delay * .8
+
+          @panels[anchor][i].style[css.transform] = @_transform angle
+          if @shading
+            @_setShader i, anchor, angle
+
+        , delay
+
+
+
+  unfold: (callback) ->
+    if !@isFoldedUp
+      if typeof callback is 'function'
+        callback()
+
+    @isFoldedUp = false
+    for panel, i in @panels[@lastAnchor]
+      if i isnt 0
+        delay = i * @settings.speed * .6
+        console.log delay
+
+        do (i, delay) =>
+
+          setTimeout =>
+            @panels[@lastAnchor][i].style.display = 'block'
+            @panels[@lastAnchor][i].style[css.transform] = @_transform 0
+            if @shading
+              @_setShader i, @lastAnchor, 0
+
+          , delay


Ticker: forward LikeIsAdded event which comes to client groupsControlller

--- client/Main/maincontroller/groupscontroller.coffee
+++ client/Main/maincontroller/groupscontroller.coffee
@@ -33,6 +33,7 @@

     @forwardEvent @groupChannel, "MemberJoinedGroup"
     @forwardEvent @groupChannel, "FollowHappened"
+    @forwardEvent @groupChannel, "LikeIsAdded"

     @groupChannel.once 'setSecretNames', callback


vmController: simplify switching methods

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -19,26 +19,16 @@
         withArgs : command

     @fetchVmName options, (err, vmName) =>
-
-      # if /^vm\./.test options.method
-      #   options.kiteName = "osk"
-
       if vmName is "local-#{KD.whoami().profile.nickname}"
         if /^fs\./.test options.method
-            options.kiteName = "fs"
-
-        if /^webterm\./.test options.method
-            options.kiteName = "terminal"
-
-        if /^vm\./.test options.method
           options.kiteName = "fs"
-
-      if options.method in ['exec', 'spawn']
-        options.kiteName = "os-local"
+        else if /^webterm\./.test options.method
+          options.kiteName = "terminal"
+        else if /^vm\./.test options.method
+          options.kiteName = "fs"

       options.correlationName = vmName
       @fetchRegion vmName, (region)=>
-        # NEWKITE
         options.kiteName or= "os-#{region}"
         @kc.run options, (rest...) ->
           # log rest...


Remove debugging

Remove debugging

--- src/pipe/umd/index.coffee
+++ src/pipe/umd/index.coffee
@@ -26,7 +26,6 @@
       )

     lib.pipe.lazypipe()
-    .pipe -> lib.debug.debug({ title: '(1)' })
     .pipe -> lib.pipe.if(
       config.glob.coffeeScript
       (
@@ -53,14 +52,12 @@
             )()
           )
         )
-        .pipe -> lib.debug.debug({ title: '(2)' })
         .pipe -> lib.pipe.mirror(
           module.exports.umdCoffeeScript(options.umd, options.umdCoffeeScript)
           (
             lib.pipe.lazypipe()
             .pipe -> transpile(options.transpile, { coffeeCoverage: null })
             .pipe -> umd()
-            .pipe -> lib.debug.debug({ title: '(3)' })
           )()
           (
             lib.pipe.lazypipe()
@@ -69,13 +66,12 @@
               suffix: '.coverage'
             })
             .pipe -> umd()
-            .pipe -> lib.debug.debug({ title: '(4)' })
+
           )()
         )
       )()
       umd()
     )
-    .pipe -> lib.debug.debug({ title: '(end)' })
     .pipe -> lib.pipe.if(
       config.glob.javaScript
       lib.pipe.mirror(
@@ -83,9 +79,7 @@
         (
           lib.pipe.lazypipe()
           .pipe -> lib.transform.uglify(options.uglify)
-          .pipe -> lib.debug.debug({ title: 'uglify' })
           .pipe -> lib.metadata.rename((pathBits) ->
-            console.log(pathBits)
             pathBits.extname = '.min' + pathBits.extname
             return
           )


Allow requests for last byte of data

Allow requests for last byte of data

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -181,7 +181,7 @@

                     console.log "requested start, end is", requestStart, requestEnd

-                    rangeStart  = if (requestStart  < fend)     then requestStart   else fstart
+                    rangeStart  = if (requestStart  <= fend)     then requestStart   else fstart
                     rangeEnd    = if (requestEnd    <= fend)    then requestEnd     else fend
                     console.log "rangeStart, rangeEnd, rangeRequest is", rangeStart, rangeEnd, rangeRequest

@@ -229,7 +229,7 @@
                 console.log "read stream opts are", readStreamOpts
                 rstream = fs.createReadStream filename, readStreamOpts
                 rstream.pipe res, end:false
-
+
                 rstream.on "end", =>
                     # got to the end of the file.  close our response
                     console.log "wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."


Fix typo in menu bar

Fix typo in menu bar

`Togggle` should be `Toggle`

--- menus/atom-solidity.cson
+++ menus/atom-solidity.cson
@@ -21,7 +21,7 @@
           'command': 'eth-interface:build'
         }
         {
-          'label': 'Togggle view'
+          'label': 'Toggle view'
           'command': 'eth-interface:toggle'
         }
         {


:bug: Update menu commands

:bug: Update menu commands

Some commands changed in the latest update, and they were not updated
in this file. I also added missing commands, and updated the menu item
labels to be more complete & accurate

Fixes atom/atom#4993

--- menus/settings-view.cson
+++ menus/settings-view.cson
@@ -4,11 +4,12 @@
     'submenu': [
       'label': 'Settings View'
       'submenu': [
-        { 'label': 'Change Themes', 'command': 'settings-view:change-themes' }
-        { 'label': 'Install Themes', 'command': 'settings-view:install-themes' }
-        { 'label': 'Uninstall Themes', 'command': 'settings-view:uninstall-themes' }
-        { 'label': 'Install Packages', 'command': 'settings-view:install-packages' }
-        { 'label': 'Uninstall Packages', 'command': 'settings-view:uninstall-packages' }
+        { 'label': 'Open', 'command': 'settings-view:open' }
+        { 'label': 'Show Keybindings', 'command': 'settings-view:show-keybindings' }
+        { 'label': 'Install Packages/Themes', 'command': 'settings-view:install-packages-and-themes' }
+        { 'label': 'Update Packages/Themes', 'command': 'settings-view:check-for-package-updates' }
+        { 'label': 'Manage Packages', 'command': 'settings-view:uninstall-packages' }
+        { 'label': 'Manage Themes', 'command': 'settings-view:uninstall-themes' }
       ]
     ]
   }


Bugfix project status class was not removed after update

--- app/assets/javascripts/backbone/views/build_view.js.coffee
+++ app/assets/javascripts/backbone/views/build_view.js.coffee
@@ -10,5 +10,6 @@

   render: ->
     @$el.html(@template(@model.toJSON()))
+    @$el.removeClass("offline success failure indeterminate")
     @$el.addClass(@model.get('status'))
     @


Fix more specs

Fix more specs

--- lib/assets/javascripts/unpoly/classes/change/update_layer.coffee
+++ lib/assets/javascripts/unpoly/classes/change/update_layer.coffee
@@ -108,7 +108,7 @@

       return promise

-    else if keepPlan = @findKeepPlan(step.oldElement, step.newElement, step)
+    else if keepPlan = @findKeepPlan(step)
       # Since we're keeping the element that was requested to be swapped,
       # there is nothing left to do here, except notify event listeners.
       up.fragment.emitKept(keepPlan)


Adds automatic appController creation for apps that listen to URL changes.

--- src/chip.coffee
+++ src/chip.coffee
@@ -128,6 +128,8 @@

 # Set up the listeners for path changes. Chip uses the Path.js library for routing.
 chip.listen = ->
+	unless chip.appController
+		$ -> chip.createAppController()
 	Path.history.listen()
 	if Path.history.supported
 		# Set listeners on links to catch their clicks and use pushState instead


Add ContentAPI::loadData.

Add ContentAPI::loadData.

Loads all data types from the content API. It also normalizes (not schema-based), converting content objects to references once all loaded. SHOULD NOT be used with any of the other data loading functions (they’re redundant and would not have the normalization).

--- compiler/ContentAPI.coffee
+++ compiler/ContentAPI.coffee
@@ -88,7 +88,9 @@
 class Model
     constructor: (source_data) ->
         @_data = source_data
+        @_keys = []
         @_configureProperties()
+

     _configureProperties: ->
         for k,v of @_data
@@ -97,6 +99,7 @@
                 @_configureProp('cover_image')

     _configureProp: (name) ->
+        @_keys.push(name)
         Object.defineProperty this, name,
             get : => @get(name)
             set : (new_val) => @set(name, new_val)
@@ -104,6 +107,8 @@
             configurable : true

     toJSON: -> @_data
+
+    keys: -> @_keys

     get: (name) ->
         # Don't try to make the entry contents into Models
@@ -515,6 +520,13 @@
             SDKError.log("Got #{ result.length } topics from API.")
             cb?(result)

+    loadData: ->
+        new Promise (resolve, reject) =>
+            Promise.all([
+                @entries(), @packages(), @people(), @locations(), @topics()
+            ]).then(normalizeContentData).then(resolve)
+            .catch(reject)
+
     ENTRY       : ENTRY
     PACKAGE     : PACKAGE
     POST        : POST
@@ -526,6 +538,45 @@
     LOCATION    : LOCATION
     TOPIC       : TOPIC

+normalizeContentData = (all_collections) ->
+    object_index = new Map()
+    all_collections.forEach (collection) ->
+        collection.forEach (model) ->
+            object_index.set(model.id, model)
+
+    _getFromIndex = (obj_or_id) ->
+        if not obj_or_id
+            return
+        if obj_or_id.id
+            return object_index.get(obj_or_id.id)
+        return object_index.get(obj_or_id)
+
+    all_collections.forEach (collection) ->
+        collection.forEach (model) ->
+            model.keys().forEach (key) ->
+                unless model[key]
+                    return
+                _type = key.split('_').pop()
+
+                if _type in ['content', 'entities', 'entity'] and not (model.type is ENTRY and key is 'content')
+                    if Array.isArray(model[key])
+                        model[key] = model[key].map(_getFromIndex)
+                    else if typeof model[key] is 'object'
+                        if model[key].id
+                            if object_index[model[key].id]
+                                model[key] = object_index.get(model[key].id)
+                            else
+                                console.warn 'UNKNOWN?', key, model[key]
+                        else
+                            Object.keys(model[key]).forEach (k) ->
+                                model[key][k] = _getFromIndex(model[key][k])
+                    else if typeof model[key] is 'string' and object_index.get(model[key])
+                        model[key] = object_index.get(model[key])
+
+    [entries, packages, people, locations, topics] = all_collections
+    return { entries, packages, people, locations, topics }
+
+
 module.exports              = ContentAPI
 module.exports.CDNImage     = CDNImage
 module.exports.Model        = Model


Timeline: general fix with real use case

--- components/Timeline.coffee
+++ components/Timeline.coffee
@@ -22,10 +22,10 @@

     @running = false
     @duration = 500
-    @position = 0.0
     @repeat = @repeatCount = 0
     @reverse = false
     @autoreverse = false
+    @direction = true

     # control
     @inPorts.start.on 'data', () =>
@@ -44,6 +44,8 @@
       @repeatCount = value
     @inPorts.autoreverse.on 'data', (value) =>
       @autoreverse = value
+    @inPorts.reverse.on 'data', (value) =>
+      @direction = !value

     # tick
     @inPorts.tick.on 'data', () =>
@@ -51,8 +53,8 @@

   start: () ->
+    @lastTime = @currentTime()
     @elapsedTime = 0
-    @position = if @reverse then 1.0 else 0.0
     @repeat = @repeatCount
     @running = true
     return unless @outPorts.started.isAttached()
@@ -70,12 +72,13 @@
     @running = false

   unpause: () ->
+    @lastTime = @currentTime()
     @running = true

   emitPosition: () ->
     return unless @outPorts.value.isAttached()
     pos = @elapsedTime / @duration
-    pos = 1 - pos unless @direction
+    pos = 1.0 - pos unless @direction
     @outPorts.value.send(pos)
     @outPorts.value.disconnect()

@@ -92,32 +95,30 @@
     return unless @running

     # Measure delta
-    delta = 0
-    if @lastTime
-      t = @currentTime()
-      delta = t - @lastTime
-      @lastTime = t
+    t = @currentTime()
+    delta = t - @lastTime
+    @lastTime = t

     # Add to elapsedTime
-    if @direction then @elapsedTime += delta else @elapsedTime -= delta
+    @elapsedTime += delta

     # End case
     if @isComplete()
-      @position = if @direction then 1.0 else 0.0
+      # fix value on bounds
+      @elapsedTime = @duration
       @emitPosition()
       @stop()
       return

     # Continue case
-    if @repeat < 1
+    if @elapsedTime < @duration
       @emitPosition()
       return

     # Loop case
     @elapsedTime = @elapsedTime - @duration
-
     @direction = !@direction if @autoreverse
-    @repeat -= 1
+    @repeat -= 1 if @repeat > 0

     @emitPosition()


Modified rand-nth function assertion

Modified rand-nth function assertion

--- src/closer-core.coffee
+++ src/closer-core.coffee
@@ -677,11 +677,12 @@

   'rand_$_nth': (coll) ->
     assertions.arity 1, arguments.length
-    assertions.stack coll
+    assertions.sequential coll
     m.nth coll, _.random m.count(coll) - 1

   'get_$_in': (coll,keys,not_found) ->
     assertions.arity 2, 3, arguments.length
+    assertions.seqable keys
     m.get_in(coll,keys,not_found)


Update game.coffee to Latest EDSL

Update game.coffee to Latest EDSL

--- src/coffee/game.coffee
+++ src/coffee/game.coffee
@@ -5,12 +5,12 @@
     quality 'punchiness',
       'Punchiness'
       'One\'s capability for punching.'
-      defaultValue: 7
+      value: 7

     quality 'luck',
       'Luck'
       'This text should never display.'
-      defaultValue: -2
+      value: -2
       visible: false

     return


Consistency

Consistency

--- test/lib/node_jquery_xhr.coffee
+++ test/lib/node_jquery_xhr.coffee
@@ -1,3 +1,3 @@
 require 'node-jquery-xhr'
-delete global.window
+global.document = global.window.document; delete global.window
 module.exports = require('backbone').$ = global.$


more documentation

more documentation

--- src/coffee/helpers/project-credentials-config.coffee
+++ src/coffee/helpers/project-credentials-config.coffee
@@ -6,17 +6,17 @@

 csv = require 'csv'

-###
- Provides sphere credentials based on the project key.
-
- Following files are used to store the credentials and would be searched (descending priority):
-
- ./.sphere-project-credentials
- ./.sphere-project-credentials.json
- ~/.sphere-project-credentials
- ~/.sphere-project-credentials.json
- /etc/.sphere-project-credentials
- /etc/.sphere-project-credentials.json
+###*
+ * Provides sphere credentials based on the project key.
+ *
+ * Following files are used to store the credentials and would be searched (descending priority):
+ *
+ * ./.sphere-project-credentials
+ * ./.sphere-project-credentials.json
+ * ~/.sphere-project-credentials
+ * ~/.sphere-project-credentials.json
+ * /etc/.sphere-project-credentials
+ * /etc/.sphere-project-credentials.json
 ###
 class ProjectCredentialsConfig
   @create: (options = {}) ->
@@ -82,10 +82,20 @@

     d.promise

+  ###*
+   * Returns project credentials for the project key.
+   *
+   * @returns Credentials have fiollowing structure: {project_key: 'key', client_id: 'foo', client_secret: 'bar'}
+  ###
   getCredentialsForProjectKey: (key) ->
     @getCredentials
       project_key: key

+  ###*
+   * Enriches project credentials if client_id or client_secret are missing.
+   *
+   * @returns Credentials have fiollowing structure: {project_key: 'key', client_id: 'foo', client_secret: 'bar'}
+  ###
   getCredentials: (credentials) ->
     if credentials.client_id? and credentials.client_secret?
       credentials


Progran-family: Fixed source and exec file pattern regexes, added support to disable guessing

--- src/program-family.coffee
+++ src/program-family.coffee
@@ -10,17 +10,16 @@
     @OPTIMIZED_PROGRAMS_SUFFIX: "-opt"

     guessOthers = ({ name, execExtension, srcExtension }, last) =>
-        pattern = "^#{name}#{@OPTIMIZED_PROGRAMS_SUFFIX}((?!\\.).)*"
-        executablesPattern = new RegExp(pattern + "#{execExtension.replace(".", "\\.")}$")
+        pattern = "^#{name}#{@OPTIMIZED_PROGRAMS_SUFFIX}[^.]*\\."
+        executablesPattern = new RegExp(pattern + "#{execExtension}$")
         executables = attemptShell("ls").filter((s) -> s.match(executablesPattern))
-        sourcesPattern = new RegExp(pattern + "#{srcExtension.replace(".", "\\.")}$")
+        sourcesPattern = new RegExp(pattern + "#{srcExtension}$")
         sources =
             for source in attemptShell("ls").filter((s) -> s.match(sourcesPattern))
                 { dir, name } = path.parse source
                 "#{path.join(dir, name)}#{execExtension}"

         found = _.union(sources, executables).sort()
-

         programs =
             for execFile, index in found
@@ -35,12 +34,12 @@

         programs

-    constructor: (@original, @others, { last = no } = {}) ->
+    constructor: (@original, @others = [], { last = no, shouldGuess = yes } = {}) ->
         @original = new Program(@original) if _.isString @original

         assert(@original instanceof Program, "original parameter is not a program nor string")

-        if not @others? or @others.length is 0
+        if shouldGuess and @others.length is 0
             @others = guessOthers(@original, last)
         else
             @others[i] = new Program(v) for v, i in @others


fix exception in error log

fix exception in error log

--- services/clsi/app/coffee/OutputCacheManager.coffee
+++ services/clsi/app/coffee/OutputCacheManager.coffee
@@ -85,7 +85,7 @@
 						# clean up the directory we just created
 						fse.remove cacheDir, (err) ->
 							if err?
-								logger.error err: err, dir: dir, "error removing cache dir after failure"
+								logger.error err: err, dir: cacheDir, "error removing cache dir after failure"
 					else
 						# pass back the list of new files in the cache
 						callback(err, results)


make the build id a secure random token

make the build id a secure random token

we allow existing build ids to work for backwards compatibility
this can be removed after some time

--- services/clsi/app/coffee/OutputCacheManager.coffee
+++ services/clsi/app/coffee/OutputCacheManager.coffee
@@ -5,13 +5,16 @@
 logger = require "logger-sharelatex"
 _ = require "underscore"
 Settings = require "settings-sharelatex"
+crypto = require "crypto"

 OutputFileOptimiser = require "./OutputFileOptimiser"

 module.exports = OutputCacheManager =
 	CACHE_SUBDIR: '.cache/clsi'
 	ARCHIVE_SUBDIR: '.archive/clsi'
-	BUILD_REGEX: /^[0-9a-f]+$/  # build id is Date.now() converted to hex
+	# build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes
+	# for backwards compatibility, make the randombytes part optional
+	BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/
 	CACHE_LIMIT: 2  # maximum number of cache directories
 	CACHE_AGE: 60*60*1000 # up to one hour old

@@ -23,12 +26,24 @@
 			# for invalid build id, return top level
 			return file

+	generateBuildId: (callback = (error, buildId) ->) ->
+		# generate a secure build id from Date.now() and 8 random bytes in hex
+		crypto.randomBytes 8, (err, buf) ->
+			return callback(err) if err?
+			random = buf.toString('hex')
+			date = Date.now().toString(16)
+			callback err, "#{date}-#{random}"
+
 	saveOutputFiles: (outputFiles, compileDir, callback = (error) ->) ->
+		OutputCacheManager.generateBuildId (err, buildId) ->
+			return callback(err) if err?
+			OutputCacheManager.saveOutputFilesInBuildDir outputFiles, compileDir, buildId, callback
+
+	saveOutputFilesInBuildDir: (outputFiles, compileDir, buildId, callback = (error) ->) ->
 		# make a compileDir/CACHE_SUBDIR/build_id directory and
 		# copy all the output files into it
 		cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR)
 		# Put the files into a new cache subdirectory
-		buildId = Date.now().toString(16)
 		cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId)

 		# let file expiry run in the background
@@ -36,7 +51,7 @@

 		# Archive logs in background
 		if Settings.clsi?.archive_logs or Settings.clsi?.strace
-			OutputCacheManager.archiveLogs outputFiles, compileDir, (err) ->
+			OutputCacheManager.archiveLogs outputFiles, compileDir, buildId, (err) ->
 				if err?
 					logger.warn err:err, "erroring archiving log files"

@@ -71,9 +86,8 @@
 					else
 						# pass back the list of new files in the cache
 						callback(err, results)
-
-	archiveLogs: (outputFiles, compileDir, callback = (error) ->) ->
-		buildId = Date.now().toString(16)
+
+	archiveLogs: (outputFiles, compileDir, buildId, callback = (error) ->) ->
 		archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId)
 		logger.log {dir: archiveDir}, "archiving log files for project"
 		fse.ensureDir archiveDir, (err) ->
@@ -104,8 +118,9 @@
 				return false if options?.keep == dir
 				# remove any directories over the hard limit
 				return true if index > OutputCacheManager.CACHE_LIMIT
-				# we can get the build time from the directory name
-				dirTime = parseInt(dir, 16)
+				# we can get the build time from the first part of the directory name DDDD-RRRR
+				# DDDD is date and RRRR is random bytes
+				dirTime = parseInt(dir.split('-')?[0], 16)
 				age = currentTime - dirTime
 				return age > OutputCacheManager.CACHE_AGE


removed test char

removed test char

--- app/assets/javascripts/annotation_document_manager.coffee
+++ app/assets/javascripts/annotation_document_manager.coffee
@@ -32,7 +32,7 @@

     requestOptions = {
       type: 'PATCH',
-      url: "#{baseUrl}/annotation_documentsX/#{annotationDocument.id}",
+      url: "#{baseUrl}/annotation_documents/#{annotationDocument.id}",
       data: { annotation_document: annotationDocument }
     }


Just valid if article body exists.

Just valid if article body exists.

--- app/assets/javascripts/app/controllers/ticket_zoom.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.coffee
@@ -568,7 +568,7 @@
     console.log('ticket validateion ok')

     articleParams = @articleNew.params()
-    if articleParams
+    if articleParams && articleParams.body
       article = new App.TicketArticle
       article.load(articleParams)
       errors = article.validate()


FIX: Ensure Quality points > 0 to show details in task view

--- src/app/tasks/task-sheet-viewer/task-sheet-viewer.coffee
+++ src/app/tasks/task-sheet-viewer/task-sheet-viewer.coffee
@@ -31,7 +31,7 @@
           show: $scope.task.definition.is_graded
           active: _.isNumber($scope.task.grade)
         quality:
-          show: $scope.qualityPoints.max?
+          show: $scope.qualityPoints.max? && $scope.qualityPoints.max > 0
           active: $scope.qualityPoints.assigned > 0
       $scope.showAssessmentPanel = _.chain($scope.assessmentPanels)
                                     .map('show')


dont show edit option for members

dont show edit option for members

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -7,6 +7,7 @@
 StackUpdatedWidget        = require './stackupdatedwidget'
 getBoundingClientReact    = require 'app/util/getBoundingClientReact'
 SidebarMachinesListItem   = require 'app/components/sidebarmachineslistitem'
+isAdmin = require 'app/util/isAdmin'
 { findDOMNode } = require 'react-dom'

 MENU = null
@@ -103,7 +104,8 @@
     if managedVM
       menuItems['VMs'] = { callback }
     else
-      ['Edit', 'Reinitialize', 'VMs', 'Destroy VMs'].forEach (name) ->
+      menuItems['Edit'] = { callback }  if isAdmin()
+      ['Reinitialize', 'VMs', 'Destroy VMs'].forEach (name) ->
         menuItems[name] = { callback }

     { top } = findDOMNode(this).getBoundingClientRect()


docstring: Tweaked wording

docstring: Tweaked wording

--- client/app/lib/providers/managed/managedkitechecker.coffee
+++ client/app/lib/providers/managed/managedkitechecker.coffee
@@ -25,8 +25,9 @@
 # kite.
 #
 # If (for some reason) you need to listen for all new kites, kites
-# are also emitted via @emit, and can be subscribed to with @on and
-# @off, as expected.
+# are also emitted via an @emit on 'NewKite', and can be subscribed to
+# with @on and @off, as expected. It's worth noting that a listener is
+# required for the NewKite event to be emitted, currently.
 #
 module.exports = class ManagedKiteChecker extends kd.Object


fix B2.2 validation for floats

fix B2.2 validation for floats

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -421,7 +421,7 @@
     missingOverseasTradeValue = false
     totalOverseasTradeInputs.each ->
       if $(this).val().toString().trim().length
-        totalOverseasTradePercentage += parseInt $(this).val()
+        totalOverseasTradePercentage += parseFloat($(this).val())
       else
         missingOverseasTradeValue = true
     if !missingOverseasTradeValue


pass the correct id for should delete time entry

--- test/routes/time_entries.coffee
+++ test/routes/time_entries.coffee
@@ -107,9 +107,10 @@
     teb = new TimeEntry ops.body
     teb.validate().should.be.true
     request.post (base '/time_entries'), ops, (e,r,b)->
+      teb = new TimeEntry b
       r.statusCode.should.be.equal 200
-      request.del (base "/time_entries/#{b.id}"), _.clone(options), (e,r,b)->
+      request.del (base "/time_entries/#{teb.id}"), _.clone(options), (e,r,b)->
         r.statusCode.should.be.equal 200
-        request (base "/time_entries/#{b.id}"), _.clone(options), (e,r,b)->
+        request (base "/time_entries/#{teb.id}"), _.clone(options), (e,r,b)->
           r.statusCode.should.be.equal 404
           done()


fixed buggy backbone routing.

fixed buggy backbone routing.

--- client/coffeescript/main.coffee
+++ client/coffeescript/main.coffee
@@ -23,6 +23,7 @@
       Session.set('showing_post', result)
     )
     Meteor.subscribe( "current_posts", post_id )
+    #history.replaceState(null,'Socrenchus','/')

   use_instance: (args) ->
     args = args.split( '/' )
@@ -34,10 +35,10 @@
     l.attr( 'rel', 'stylesheet' )
     l.attr( 'href', "//#{domain}/style.css" )
     $('head').append( l )
+    Backbone.history.options.root = "/i/#{domain}/"
     if other?
       Backbone.history.navigate("/#{other}", trigger: true)
-      @navigate("/i/#{domain}/#{other}")
-
+
   link: (url) ->
     unless window.instance?
       return "#{window.location.host}#{url}"


Link to problem fron dashboard

Link to problem fron dashboard

--- client/components/Result.coffee
+++ client/components/Result.coffee
@@ -15,7 +15,7 @@
     problem = r.table._id.substr(1)
     href =  'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + problem + '&submit&user_id=' + r.user._id
     userHref = "/user/" + r.user._id
-    problemHref = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + problem
+    problemHref = '/material/' + r.table._id
     problemName = r.table.name
     contests = r.table.tables
     userName = makeUserName(r.user)
@@ -29,7 +29,7 @@
                 <Link to={userHref}>{userName}</Link>

-                {problemName}
+                <Link to={problemHref}>{problemName}</Link>

                 {contests.join("\n")}


primeira tentativa de confirmation, ainda não testado

--- app/assets/javascripts/jqvr/built_in_validators.js.coffee
+++ app/assets/javascripts/jqvr/built_in_validators.js.coffee
@@ -1,4 +1,40 @@
 jQuery ->
+  # confirmation
+  jQuery.validator.addMethod("confirmation", (value,element,params) ->
+    fieldConfirmationNameSelector = getFieldNameSelector(element.name + '_confirmation')
+    fieldConfirmation=jQuery(fieldConfirmationNameSelector)[0]
+
+    if fieldConfirmation.value isnt ""
+      value is fieldConfirmation.value
+    else
+      true
+  )
+
+  jQuery('[name$="_confirmation"]').blur(->
+    fieldToConfirm=getFieldToConfirm(@)
+    isValid=true
+
+    isValid = jQuery(fieldToConfirm).valid()
+    if not isValid
+      jQuery(fieldToConfirm).addClass('was_invalid')
+  )
+
+  jQuery('[name$="_confirmation"]').keyup(->
+    fieldToConfirm= getFieldToConfirm(@)
+
+    if jQuery(fieldToConfirm).hasClass('was_invalid')
+      jQuery(fieldToConfirm).valid()
+  )
+
+  getFieldToConfirm= (fieldConfirmation) ->
+    fieldToConfirmName= fieldConfirmation.name.substring(0,(fieldConfirmation.name.length-13))
+    fieldToConfirmNameSelector=getFieldNameSelector(fieldToConfirmName)
+
+    jQuery(fieldToConfirmNameSelector)
+
+  getFieldNameSelector=(fieldToConfirmName) ->
+    '[name="' + fieldToConfirmName + '"]'
+
   # exclusion
   jQuery.validator.addMethod("exclusionIn", (value,element,params) ->
     jQuery.inArray(value, params)==-1


Fixed: alarm tests initialization

Fixed: alarm tests initialization

--- test/alarms_tests.coffee
+++ test/alarms_tests.coffee
@@ -19,7 +19,9 @@

     before ->
         notifhelper = new NotificationsHelper 'home'
-        @alarmManager = new AlarmManager 'Europe/Paris', Alarm, Event, notifhelper
+        @alarmManager = new AlarmManager
+            timezone: 'Europe/Paris'
+            notificationHelper: notifhelper

     before ->
         @sandbox = sinon.sandbox.create()


Show credits in VM modal.

Show credits in VM modal.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -379,11 +379,19 @@
         index      = (parseInt form.inputs.selector.getValue(), 10) or 0
         monthlyFee = @paymentPlans[index].feeMonthly

-        console.log "Group Limit:", limit
-        console.log "User Balance:", balance
-
-        if limit >= balance + monthlyFee
-          modal.modalTabs.forms["Create VM"].buttons.expensed.show()
+        if limit > 0
+          credits = (limit / 100).toFixed(2)
+
+          creditsMessage = "
This group gives you $#{credits} credits.
"
+          if balance > 0
+            spent = (balance / 100).toFixed(2)
+            creditsMessage += "
You've spent $#{spent}.
"
+
+          form.fields.desc.setPartial creditsMessage
+          form.fields.desc.show()
+
+          if limit >= balance + monthlyFee
+            modal.modalTabs.forms["Create VM"].buttons.expensed.show()

       hideLoaders = ->
         {group, user, expensed} = form.buttons


Destroy modal after personal VM

Destroy modal after personal VM

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -161,7 +161,7 @@
                   onSuccess result
                   _createUserVM callback
           else
-            _createUserVM()
+            _createUserVM callback

     vmCreateCallback  = (err, vm)->
       if err


Collaboration: Show avatars when RTM is ready.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1104,8 +1104,9 @@
   continuePrivateMessage: (callback) ->

     log 'continuePrivateMessage'
-    @statusBar.avatars.show()
     @chat.emit 'CollaborationStarted'
+    @once 'RTMIsReady', =>
+      @statusBar.emit 'ShowAvatars', @participants.asArray()

   isRealtimeSessionActive: (id, callback) ->


Collaboration: Tell chat view to create participants list when rtm is ready.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1016,6 +1016,8 @@
     @getView().addSubView @chat = new IDE.ChatView { @rtm }, channel
     @chat.show()

+    @once 'RTMIsReady', => @chat.settingsPane.createParticipantsList()
+

   createChatPane: ->


AppController: Fixed public path in previewFile method.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -432,7 +432,9 @@

     if FSHelper.isPublicPath file.path
       # FIXME: Take care of https.
-      @createNewBrowser KD.getPublicURLOfPath FSHelper.getFullPath file
+      prefix      = "[#{@mountedMachineUId}]/home/#{KD.nick()}/Web/"
+      [temp, src] = file.path.split prefix
+      @createNewBrowser "#{@mountedMachine.domain}/#{src}"
     else
       @notify 'File needs to be under ~/Web folder to preview.', 'error'


AppController: Remove status menu items when all tabs are closed.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -248,6 +248,12 @@
   registerIDEView: (ideView) ->
     @ideViews.push ideView

+    ideView.on 'PaneRemoved', =>
+      ideViewLength  = 0
+      ideViewLength += ideView.tabView.panes.length  for ideView in @ideViews
+
+      @statusBar.empty()  if ideViewLength is 0
+
   forEachSubViewInIDEViews_: (callback = noop, paneType) ->
     for ideView in @ideViews
       for pane in ideView.tabView.panes


comment debug

comment debug

--- engine/curve.coffee
+++ engine/curve.coffee
@@ -68,7 +68,7 @@
             n += 1
         va = @va = new Float32Array vertices
         ia = @ia = new Uint16Array indices
-        @phy_debug_mesh = render_manager.debug.debug_mesh_from_va_ia va, ia
+        # @phy_debug_mesh = render_manager.debug.debug_mesh_from_va_ia va, ia
         @phy_he = [1, 1, 1]

         curve_index = 0


Fix the numbering for total slides

Fix the numbering for total slides

--- app/assets/javascripts/showboat/plugins/page_title.coffee
+++ app/assets/javascripts/showboat/plugins/page_title.coffee
@@ -3,7 +3,7 @@
   constructor: (@reveal, @prefix) ->
     @title = document.querySelector 'title'
     @prefix ?= @title.innerHTML
-    @total = document.querySelectorAll(".reveal .slides > section:not(.stack)").length + 1
+    @total = document.querySelectorAll(".reveal .slides > section:not(.stack)").length
     @reveal.addEventListener 'slidechanged', => @render arguments...
     @render(indexh: @reveal.getIndices().h)


Untangled some ifs

Untangled some ifs

--- app/assets/javascripts/uploadcare/widget/widget.js.coffee
+++ app/assets/javascripts/uploadcare/widget/widget.js.coffee
@@ -43,14 +43,14 @@
         id = utils.uuidRegex.exec @element.val()
         id = if id then id[0] else null

-        if !id || @currentId != id
+        if @currentId != id
           @currentId = id
-
           if id
             info = uploads.fileInfo(id, @settings)
             @__setLoaded(info)
-          else
-            @__reset()
+
+        if !id
+          @__reset()

       __changed: (e) =>
         @reloadInfo()


Added metrics pins

Added metrics pins

--- client/src/app/util/pins.coffee
+++ client/src/app/util/pins.coffee
@@ -23,6 +23,9 @@
     # Fill in mouse pins
     for pin in ["down", "position", "cursor", "justDown", "justUp"] then keys.push("io.mouse.#{pin}")

+    # Fill in metrics pins
+    for pin in ["player", "events"] then keys.push("io.metrics.#{pin}")
+
     # Fill in layer pins
     layers = currentGame.version.circuits[circuits.currentCircuitMeta.type].io.layers
     for layer in layers
@@ -39,4 +42,7 @@
     for pinName, pinDef of pins
       keys.push("circuit.#{pinName}")

+    # Fill in metrics pins
+    for pin in ["down", "position", "cursor", "justDown", "justUp"] then keys.push("io.mouse.#{pin}")
+
     return keys


verify that addFunction is passed from column to column appender

--- spec/javascripts/manuscript_manager_spec.js.coffee
+++ spec/javascripts/manuscript_manager_spec.js.coffee
@@ -12,12 +12,26 @@
     describe "#render", ->
       context "when this is a manuscript manager column (has tasks)", ->
         beforeEach ->
-          @component = Tahi.manuscriptManager.Column title: 'Column Title', tasks: [{}]
+          @addFn = -> console.log 'Arbitrary Function'
+          component = Tahi.manuscriptManager.Column title: 'Column Title', tasks: [{}], addFunction: @addFn
+          @children = component.render().props.children

         it "renders Card components", ->
-          result = @component.render()
-          element = result.props.children[2].props.children.props.children[0]
+          element = @children[2].props.children.props.children[0]
           expect(element.props.children.constructor).toEqual Tahi.manuscriptManager.Card.componentConstructor
+
+        it "passes its addFunction through to the ColumnAppender", ->
+          expect(@children[0].props.addFunction).toEqual @addFn
+
+  describe "ColumnAppender component", ->
+    describe "#render", ->
+      beforeEach ->
+        task =
+          cardName: "upload-manuscript"
+          taskId: 1
+          taskPath: "/papers/1/tasks/1"
+        @component = Tahi.manuscriptManager.ColumnAppender task: task
+        @result = @component.render()

   describe "Card component", ->
     describe "#render", ->


Fixes bug in undo/redo on SystemsTabView

--- app/views/editor/level/systems/LevelSystemEditView.coffee
+++ app/views/editor/level/systems/LevelSystemEditView.coffee
@@ -116,10 +116,16 @@
     button.find('> span').toggleClass('secret')

   undo: ->
-    if @$el.find('#editor-level-system-edit-view li.active a#system-config-schema-tab')
+    if @$el.find('li.active > a#system-config-schema-tab')
       @configSchemaTreema.undo()
-    if @$el.find('#editor-level-system-edit-view li.active a#system-settings-tab')
+    if @$el.find('li.active > a#system-settings-tab')
       @systemSettingsTreema.undo()
+
+  redo: ->
+    if @$el.find('li.active > a#system-config-schema-tab')
+      @configSchemaTreema.redo()
+    if @$el.find('li.active > a#system-settings-tab')
+      @systemSettingsTreema.redo()

   destroy: ->
     @editor?.destroy()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -67,6 +67,6 @@
   equals player.I.velocity.y, 0

 test "should correctly track facing direction", ->
-  player.I.velocity.equal(Point(1, ))
+  ok player.I.velocity.equal(Point(1, 0))

 module()


Minor clarification in TreeCursor configurtion docs

--- lib/tree_search/tree_cursor/configuration.coffee
+++ lib/tree_search/tree_cursor/configuration.coffee
@@ -5,7 +5,8 @@
 # of adjacent nodes. Please mind the type signature.
 # See TreeCursor class in DOMUtilities component for an example.
 #
-# TODO These should be defined primarily on a node (?)
+# Note: These are defined on TreeCursor and not on Node as to not
+# pollute your Node class with navigation logic.

 TreeSearch.TreeCursor.reopen


Tests: IDE: File: Move to Folder tests.

Tests: IDE: File: Move to Folder tests.

--- client/test/lib/ide/file.coffee
+++ client/test/lib/ide/file.coffee
@@ -63,31 +63,6 @@
       .end()

-  createFolderFromMachineHeader: (browser) ->
-
-    user = helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-    helpers.createFolder(browser, user)
-    browser.end()
-
-
-  saveFileToFolder: (browser) ->
-
-    user = helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-    folderData = helpers.createFolder(browser, user)
-
-    helpers.createFile(browser, user, 'li.new-file', folderData.name)
-
-
-  createFolderFromContextMenu: (browser) ->
-
-    user = helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-    helpers.createFile(browser, user, 'li.new-folder')
-    browser.end()
-
-
   deleteFile: (browser) ->

     user = helpers.beginTest(browser)
@@ -99,20 +74,6 @@
     fileSelector = "span[title='" + webPath + '/' + filename + "']"

     helpers.deleteFile(browser, fileSelector)
-    browser.end()
-
-
-  deleteFolder: (browser) ->
-
-    user = helpers.beginTest(browser)
-
-    helpers.waitForVMRunning(browser)
-
-    folderData = helpers.createFolder(browser, user)
-    webPath    = '/home/' + user.username
-    selector   = "span[title='" + webPath + '/' + folderData.name + "']"
-
-    helpers.deleteFile(browser, folderData.selector)
     browser.end()


Don't leave silly comments in there.

Don't leave silly comments in there.

--- app.coffee
+++ app.coffee
@@ -28,12 +28,6 @@
   database = url.pathname.slice(1) if url.pathname

   server = new mongo.Server(host, port, opts)
-
-  # replica_set = new mongo.ReplSetServers([
-  #   new mongo.Server( "127.0.0.1", 27017, mongoconfig ),
-  #   new mongo.Server( "127.0.0.1", 27018, mongoconfig ),
-  #   new mongo.Server( "127.0.0.1", 27019, mongoconfig )
-  # ])

   db = new mongo.Db(database, server, safe: true)


Add json response

Add json response

--- app.coffee
+++ app.coffee
@@ -10,24 +10,28 @@
 middleware(app)

 app.get '/', (req, res) ->
-  res.set('Content-Type', 'text/plain')
-  textResponse =  'Voiko tänään mennä baariin Töölössä?\n\n'
   today = dateLookup(moment().format("YYYY-MM-DD"))
   tomorrow = dateLookup(moment().add('days', 1).format('YYYY-MM-DD'))
-  console.log today, tomorrow
-  if today
-    textResponse += 'No ei voi ku on peli. '
-    if !tomorrow
-      textResponse += 'Mut huomen voi!'
-    else
-      textResponse += 'Eikä voi muuten huomennakaan.'
-  else
-    textResponse += 'Näköjään voi! '
-    if tomorrow
-      textResponse += 'Mut huomenna ei voi.'
-    else
-      textResponse += 'Niin voi huomennakin!'
-  res.send 200, textResponse
+  res.format(
+
+    'text/plain': ->
+      textResponse =  'Voiko tänään mennä baariin Töölössä?\n\n'
+      if today
+        textResponse += 'No ei voi ku on peli. '
+        if !tomorrow
+          textResponse += 'Mut huomen voi!'
+        else
+          textResponse += 'Eikä voi muuten huomennakaan.'
+      else
+        textResponse += 'Näköjään voi! '
+        if tomorrow
+          textResponse += 'Mut huomenna ei voi.'
+        else
+          textResponse += 'Niin voi huomennakin!'
+      res.send 200, textResponse
+    'application/json': ->
+      res.send(200, text: 'Voiko Töölössä mennä baariin?', today: !today, tomorrow: !tomorrow)
+  )

 port = Number(process.env.PORT || 5000)


oops, forgot to init

oops, forgot to init

--- app.coffee
+++ app.coffee
@@ -10,6 +10,7 @@
 IncomingMessage = require "./models/IncomingMessage"
 StravaUploader = require "./services/StravaUploader"
 DropboxUploader = require "./services/DropboxUploader"
+CartoDbUploader = require "./services/CartoDbUploader"

 # Load environment vars from .env file.
 dotenv = require 'dotenv'


Remove test1

Remove test1

--- test1.coffee
+++ test1.coffee
@@ -1,31 +0,0 @@
-'use strict'
-
-fs = require 'fs'
-path = require 'path'
-
-DS_STORE     = '.DS_Store'
-corpusFolder = './documents'
-klassFolders = fs.readdirSync corpusFolder
-
-doc2kls  = {}
-docWords = {}
-idf = []
-
-for klass in klassFolders when klass isnt DS_STORE
-
-  docs = fs.readdirSync path.join(corpusFolder, klass)
-
-  for doc in docs when doc isnt DS_STORE
-    doc2kls[doc] = klass
-    docWords[doc] = {}
-
-    content = fs.readFileSync path.join(corpusFolder, klass, doc), 'utf8'
-    words = content.split ' '
-
-    # count dict by doc
-    for word in words
-      docWords[doc][word] = (docWords[doc][word]+1 or 1)
-
-    # count idf
-    for word in Object.keys(docWords)
-      idf[word] = (idf[word]+1 or 1)


Direct adjustments of auto build params - tweak 7-e: TypeError: undefined is not a function at Object.<anonymous> (/home/travis/build/AndrewRevinsky/crunchtask.js/test/unit/crunchtaskSpec.js:1084:26)

--- test/unit/crunchtaskSpec.coffee
+++ test/unit/crunchtaskSpec.coffee
@@ -1111,12 +1111,14 @@

     it 'implements a Collatz conjecture, aka 3n + 1 problem, algorithm', (done)->

-      expect(type(done)).toEqual('function')
+

       collatzTask.onIdle( ()->
         expect(type(setTimeout)).toBeDefined()
         expect(type(setTimeout)).toEqual('function')
-        setTimeout done, 100
+        expect(type(done)).toBeDefined()
+        expect(type(done)).toEqual('function')
+        setTimeout(done, 100)
         return
       )

@@ -1138,16 +1140,16 @@
         expect(count).toEqual(949)
         return

-      for v, k in [0, 1, 7, 2, 5, 8, 16, 3, 19, 6, 14, 9, 9, 17, 17, 4, 12, 20, 20, 7, 7, 15, 15, 10, 23, 10, 111, 18, 18, 18, 106, 5, 26, 13, 13, 21, 21, 21, 34, 8, 109, 8, 29, 16, 16, 16, 104, 11, 24, 24]
-        ((v, k) ->
-          collatzTask.run(k + 1).done (arr)->
-            [n, count] = arr
-#              console.log(arr)
-            expect(n).toEqual(k + 1)
-            expect(count).toEqual(v)
-            return
-          return
-        )(v, k)
+#      for v, k in [0, 1, 7, 2, 5, 8, 16, 3, 19, 6, 14, 9, 9, 17, 17, 4, 12, 20, 20, 7, 7, 15, 15, 10, 23, 10, 111, 18, 18, 18, 106, 5, 26, 13, 13, 21, 21, 21, 34, 8, 109, 8, 29, 16, 16, 16, 104, 11, 24, 24]
+#        ((v, k) ->
+#          collatzTask.run(k + 1).done (arr)->
+#            [n, count] = arr
+##              console.log(arr)
+#            expect(n).toEqual(k + 1)
+#            expect(count).toEqual(v)
+#            return
+#          return
+#        )(v, k)

       return


groups: LoginLink renamed, group view hiding moved to method

--- client/landing/app/MainApp/maincontroller/mainview.coffee
+++ client/landing/app/MainApp/maincontroller/mainview.coffee
@@ -66,7 +66,9 @@
       attributes:
         href    : "#"
       click     : (event)=>
-        return if @groupsEnabled()
+        if @groupsEnabled()
+          @closeGroupView()
+          return

         event.stopPropagation()
         event.preventDefault()
@@ -198,38 +200,44 @@
     $('body').addClass "login"
     console.log "LOGGED IN WITH GROUPS"

-    LoginLink = new KDCustomHTMLView
+    groupLink = new KDCustomHTMLView
       partial: "Login"
       cssClass: "bigLink"

-
-
     if state
       KD.getSingleton('router').handleRoute "/#{@getSingleton("router")?.getCurrentPath()+'/Activity'}", state:{}
-      LoginLink.click = =>
-        @mainTabView.showHandleContainer()
-        @groupLandingView._windowDidResize = =>
-        # @utils.wait 200, =>
-        $('.group-landing').css 'height', 0
-
-      LoginLink.updatePartial 'Go to Group'
+      groupLink.click = =>
+        @closeGroupView()
+
+      groupLink.updatePartial 'Go to Group'

     else
-      LoginLink.click = =>
+      groupLink.click = =>
         @groupLandingView._windowDidResize = =>

         @getSingleton('mainController').loginScreen.show()
         @getSingleton('mainController').loginScreen.animateToForm 'login'
         $('.group-landing').css 'height', 0

-    LoginLink.appendToSelector '.group-login-buttons'
+    groupLink.appendToSelector '.group-login-buttons'

     $('.group-landing').css 'height', window.innerHeight - 50
+
+  closeGroupView:->
+    @mainTabView.showHandleContainer()
+    @groupLandingView._windowDidResize = noop
+    $('.group-landing').css 'height', 0

   decorateLoginState:(isLoggedIn = no)->

     @groupLandingView = new KDView
       lazyDomId : 'group-landing'
+
+    groupLandingContentView = new KDView
+      lazyDomId : 'group-landing-content'
+
+    groupLandingGroupContentView = new KDView
+      lazyDomId : 'group-content-wrapper'

     @groupLandingView.listenWindowResize()

@@ -241,12 +249,6 @@
         height : groupLandingContentView.getHeight() - (256)

-    groupLandingContentView = new KDView
-      lazyDomId : 'group-landing-content'
-
-    groupLandingGroupContentView = new KDView
-      lazyDomId : 'group-content-wrapper'
-

     if isLoggedIn
       if @groupsEnabled()


Add object caching support to OutPort

Add object caching support to OutPort

--- src/lib/OutPort.coffee
+++ src/lib/OutPort.coffee
@@ -60,11 +60,11 @@
       socket.disconnect()

   sendIP: (type, data, options, socketId) ->
-    ip = new IP type, data
-    for key, val of options
-      ip[key] = val
+    ip = new IP type, data, options
     sockets = @getSockets socketId
     @checkRequired sockets
+    if @isCaching() and data isnt @cache[socketId]?.data
+      @cache[socketId] = ip
     for socket in sockets
       continue unless socket
       socket.post ip


model: attribute spec

model: attribute spec

--- test/specs/source/base_model_spec.coffee
+++ test/specs/source/base_model_spec.coffee
@@ -1,2 +1,21 @@
 describe "BaseModel", ->
-  it "example", ->
+  it "Work with attributes", ->
+    model = new MyModel()
+
+    expect(model.get('id')).toBeNull()
+    expect(model.id()).toBeNull()
+    expect(model.get('title')).toEqual("default title")
+    expect(model.title()).toEqual("default title")
+    expect(model.get('description')).toBeNull()
+    expect(model.description()).toBeNull()
+
+    model.set('id', 10)
+    expect(model.id()).toEqual(10)
+
+    model.id(100)
+    expect(model.get('id')).toEqual(100)
+
+    model = new MyModel({id: 1, title: "new title", description: "description"})
+    expect(model.id()).toEqual(1)
+    expect(model.title()).toEqual("new title")
+    expect(model.description()).toEqual("description")


oauth: remove another accidental commit

oauth: remove another accidental commit

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -76,7 +76,7 @@
     numberOfWorkers: 1
     watch       : yes
   emailConfirmationCheckerWorker :
-    enabled              : no
+    enabled              : yes
     login                : 'prod-social'
     queueName            : socialQueueName+'emailConfirmationCheckerWorker'
     numberOfWorkers      : 1


Using crazy new include lookup for non-string modules

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -173,7 +173,8 @@

     # Initial module inclue, for reconstructing objects from JSON
     for moduleName is I.includedModules
-
+      Module
+      self.extend Module(I, self)

     return self
 )()


change color function

change color function

--- graphics/001-terrain-gen/viewer.coffee
+++ graphics/001-terrain-gen/viewer.coffee
@@ -16,11 +16,11 @@
     color = (height) ->
         r = 128
         b = 192
-        r = Math.floor(height / 32) if height > 8192
+        r = Math.floor(height / 48) if height > 8192
         r = 255 if r > 255
         b = Math.floor(320 - height / 64) if height < 8192
         b = 255 if b > 255
-        g = Math.floor((r + b) / 1.5)
+        g = Math.floor((r + b) / 1.7)
         g = 255 if g > 255

         hexR = Math.round(r).toString(16)