Fix <ellipse> size detection, and add <image> support

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -48,7 +48,7 @@
          node.tagName in ['defs', 'symbol', 'use']
         return [null, null, null, null]
       switch node.tagName
-        when 'rect'
+        when 'rect', 'image'
           [parseFloat node.getAttribute('x') or 0
            parseFloat node.getAttribute('y') or 0
            parseFloat node.getAttribute('width') or '100%'
@@ -62,7 +62,7 @@
           cx = parseFloat node.getAttribute('cx') or 0
           cy = parseFloat node.getAttribute('cy') or 0
           rx = parseFloat node.getAttribute('rx') or 0
-          ry = parseFloat node.getAttribute('rx') or 0
+          ry = parseFloat node.getAttribute('ry') or 0
           [cx - rx, cy - ry, 2*rx, 2*ry]
         when 'line'
           x1 = parseFloat node.getAttribute('x1') or 0


Factor out limit

Factor out limit

--- src/range.coffee
+++ src/range.coffee
@@ -10,6 +10,21 @@
 TEXT_NODE = 3

 # Public interface.
+
+limit = (range, bounds) ->
+  {startContainer, startOffset, endContainer, endOffset} = range
+
+  if not contains(bounds, startContainer)
+    startContainer = bounds
+    startOffset = 0
+
+  if not contains(bounds, endContainer)
+    endContainer = bounds
+    endOffset = bounds.length or bounds.childNodes.length
+
+  range.setStart(startContainer, startOffset)
+  range.setEnd(endContainer, endOffset)
+

 normalizeBoundaries = (range) ->
   {startContainer, startOffset, endContainer, endOffset} = range
@@ -182,35 +197,19 @@
   #
   # Returns updated self or null.
   limit: (bounds) ->
-    if @commonAncestor == bounds or contains(bounds, @commonAncestor)
+    range = document.createRange()
+    range.setStart(@start, 0)
+    range.setEnd(@end, @end.length)
+    limit(range, bounds)
+
+    try
+      normalizeBoundaries(range)
+      @commonAncestor = range.commonAncestorContainer
+      @start = range.startContainer
+      @end = range.endContainer
       return this
-
-    if not contains(@commonAncestor, bounds)
+    catch
       return null
-
-    document = bounds.ownerDocument
-
-    if not contains(bounds, @start)
-      node = bounds
-      next = (node) -> node isnt last and documentForward(node) or null
-      last = lastLeaf(bounds)
-      node = next(node) while not isTextNode(node)
-      @start = node
-
-    if not contains(bounds, @end)
-      node = bounds
-      next = (node) -> node isnt last and documentReverse(node) or null
-      last = firstLeaf(bounds)
-      node = next(node) while not isTextNode(node)
-      @end = node
-
-    return null unless @start and @end
-
-    @commonAncestor = @start
-    while not contains(@commonAncestor, @end)
-      @commonAncestor = @commonAncestor.parentNode
-
-    this

   # Convert this range into an object consisting of two pairs of (xpath,
   # character offset), which can be easily stored in a database.


Change routes to match current loomio routes

--- lineman/app/js/router.coffee
+++ lineman/app/js/router.coffee
@@ -1,7 +1,7 @@
 angular.module('loomioApp').config ($routeProvider, $locationProvider) ->
   $locationProvider.html5Mode(true)

-  $routeProvider.when('/discussions/:id',
+  $routeProvider.when('/d/:id',
     templateUrl: 'generated/templates/discussion.html'
     controller: 'DiscussionController'
     resolve:
@@ -9,14 +9,14 @@
         DiscussionService.fetchByKey($route.current.params.id)
       currentUser: ($http, UserAuthService) ->
         UserAuthService.fetchCurrentUser()
-  ).when('/groups/new',
+  ).when('/g/new',
     templateUrl: 'generated/templates/group_form.html',
     controller: 'GroupFormController',
     resolve:
-      group: ($route, GroupService, GroupModel, MainRecordStore) ->
+      group: ($route, GroupService, GroupModel, CollectionWrapper) ->
         GroupService.fetchByKey($route.current.params.parent_id).then(->
-          new GroupModel(parent_id: MainRecordStore.get('groups', $route.current.params.parent_id).id))
-  ).when('/groups/:id',
+          new GroupModel(parent_id: CollectionWrapper.get('groups', $route.current.params.parent_id).id))
+  ).when('/g/:id',
     templateUrl: 'generated/templates/group.html',
     controller: 'GroupController',
     resolve:
@@ -25,7 +25,7 @@
         GroupService.fetchByKey($route.current.params.id)
       currentUser: ($http, UserAuthService) ->
         UserAuthService.fetchCurrentUser()
-  ).when('/groups/:id/edit',
+  ).when('/g/:id/edit',
     templateUrl: 'generated/templates/group_form.html',
     controller: 'GroupFormController',
     resolve:
@@ -33,7 +33,7 @@
         GroupService.fetchByKey($route.current.params.id)
       currentUser: ($http, UserAuthService) ->
         UserAuthService.fetchCurrentUser()
-  ).when('/groups/:id/memberships',
+  ).when('/g/:id/memberships',
     templateUrl: 'generated/templates/memberships_page.html',
     controller: 'MembershipsPageController',
     resolve:


Add custom event handlers to Game.State

Add custom event handlers to Game.State

--- src/state.coffee
+++ src/state.coffee
@@ -2,6 +2,7 @@
   constructor: () ->
     @objects = {}
     @eventCounters = {}
+    @eventHandlers = {}
     super
   onEvent: (e) ->
     # Add timestamped instance to the event counters object
@@ -10,6 +11,9 @@
     @eventCounters[e.type].push new Date()
     # relay the event upward
     @fire(e)
+    # call added callbacks
+    @eventHandlers[e.type].forEach (cb) -> cb()
+    return
   addObject: (obj) ->
     if obj not instanceof Game.Object
       throw new Game.Error(Game.Error.ErrorType.NOT_OBJECT)
@@ -18,4 +22,10 @@
     @objects[obj.getId()] = obj
     @listen(obj, @onEvent)
     # call object added method
-    obj.added()+    obj.added()
+  addEventHandler: (evtType, callback) ->
+    if evtType not of @eventHandlers
+      @eventHandlers[evtType] = []
+    if callback not in @eventHandlers[evtType]
+      @eventHandlers[evtType].push callback
+    return


Update Atom look

Update Atom look

--- atom/atom/config.cson
+++ atom/atom/config.cson
@@ -64,10 +64,6 @@
     ]
     projectHome: "/Users/Felix/Development"
     telemetryConsent: "limited"
-    themes: [
-      "dracula-ui"
-      "dracula-theme"
-    ]
     titleBar: "custom-inset"
   docblockr: {}
   editor:
@@ -139,7 +135,7 @@
     locked: true
     silent: true
   "one-dark-ui":
-    fontSize: 13
+    fontSize: 11
     layoutMode: "Compact"
   "one-light-ui":
     fontSize: 10


Added max results environment variable

Added max results environment variable

--- src/steam.coffee
+++ src/steam.coffee
@@ -3,6 +3,7 @@
 #
 # Configuration:
 #   STEAM_API_KEY
+#   DOTA_MAX_RESULTS
 #
 # Commands:
 #   hubot steam id [me] <custom URL> - Returns the Steam ID for the user under http://steamcommunity.com/id/<custom URL>
@@ -28,7 +29,7 @@
 STEAM_API_KEY = process.env.STEAM_API_KEY
 STEAM_API_URL = "https://api.steampowered.com"
 DOTA_HEROES_DATA_PATH = "data/heroes.bin"
-DOTA_MAX_RESULTS = 5
+DOTA_MAX_RESULTS = process.env.DOTA_MAX_RESULTS or 5

 #
 # Definitions


Changed logging level

Changed logging level

--- src/steam.coffee
+++ src/steam.coffee
@@ -165,7 +165,7 @@
 #
 Init = (robot) ->
     if not STEAM_API_KEY?
-        return robot.logger.debug "Missing STEAM_API_KEY in environment. Please set and try again."
+        return robot.logger.error "Missing STEAM_API_KEY in environment. Please set and try again."

     fs.readFile DOTA_HEROES_DATA_PATH, (err, data) ->
         return robot.logger.error if err


revealer added

revealer added

--- client/app/Applications/WebTerm.kdapplication/webtermappview.coffee
+++ client/app/Applications/WebTerm.kdapplication/webtermappview.coffee
@@ -179,9 +179,10 @@
     @description.hide()

     @revealer = new KDCustomHTMLView
-      tagName: "a"
-      partial: "Reveal Koding"
-      click  : => @revealKoding()
+      tagName  : "a"
+      cssClass : "action"
+      partial  : "Reveal Koding"
+      click    : => @revealKoding()

   revealKoding: (route)->
     @finder.mountVm "vm-0.#{KD.nick()}.guests.kd.io" unless KD.isLoggedIn()
@@ -191,7 +192,10 @@
   pistachio: ->
     if KD.isLoggedIn()
       """
-      {{> @revealer}}
+
+

+        {{> @revealer}}
+

       """
     else
       """


Fix copy file url issue

Fix copy file url issue

--- client/landing/app/MainApp/filetree/itemsubviews/copyurlview.coffee
+++ client/landing/app/MainApp/filetree/itemsubviews/copyurlview.coffee
@@ -5,7 +5,7 @@

     @path = FSHelper.plainPath @getData().path
     @publicPath = @path.replace \
-      ///.*\/(.*\.#{KD.config.userSitesDomain})\/(.*)///, 'http://$1/$2'
+      ////home/(.*)/Web/(.*)///, "http://$1.#{KD.config.userSitesDomain}/$2"

     @inputUrlLabel  = new KDLabelView
       cssClass      : 'public-url-label'


A little more lodash in my life.

A little more lodash in my life.

--- functions/summoners_rift.coffee
+++ functions/summoners_rift.coffee
@@ -88,7 +88,7 @@
     !gg.championData.firstItems.highestWinPercent.winPercent
   ]

-  if undefArray.indexOf(true) > -1
+  if _.contains(undefArray, true)
     window.undefinedBuilds.push(champ + ' ' + _.capitalize(currentPosition))
     return step()

@@ -201,7 +201,7 @@
     if window.cSettings.consumables
       # If champ has no mana, remove mana pot from consumables
       consumables = prebuilts.consumables.concat([])  # Lazy fix for pointer issue.
-      if manaless.indexOf(champ) > -1
+      if _.contains(manaless, champ)
         consumables.splice(1, 1)

       builds.push {
@@ -225,7 +225,7 @@
     builds = []

     # If freqStart and highestStart are the same, only push once.
-    if JSON.stringify(freqStart.build) == JSON.stringify(highestStart.build)
+    if _.eq(freqStart.build, highestStart.build)
       builds.push {
         items: freqStart.build
         type: templates.combindedStart({wins: freqStart.wins, games: freqStart.games})
@@ -242,7 +242,7 @@
       }

     # If freqCore and highestCore are the same, only push once.
-    if JSON.stringify(freqCore.build) == JSON.stringify(highestCore.build)
+    if _.eq(freqCore.build, highestCore.build)
       builds.push {
         items: freqCore.build
         type: templates.combinedCore({wins: freqCore.wins, games: freqCore.games})


Fixes success log

Fixes success log


fixed errors

fixed errors

--- spec/lib/router-spec.coffee
+++ spec/lib/router-spec.coffee
@@ -32,7 +32,7 @@

       When -> @router.route @msg, @end
       Then -> expect(@end).toHaveBeenCalled()
-      And -> expect(EventEmitter.prototype.emit.apply).toHaveBeenCalledWith @router, ['next', @msg]
+      And -> expect(EventEmitter.prototype.emit.apply).toHaveBeenCalledWith @router, ['next', @msg, jasmine.any(Function)]

     describe '#route(err:Error, msg:Message, end:Function)', ->

@@ -41,14 +41,14 @@
       Given -> @router.use (msg, next) => next @err
       When -> @router.route @msg, @end
       Then -> expect(@end).toHaveBeenCalled()
-      And -> expect(EventEmitter.prototype.emit.apply).toHaveBeenCalledWith @router, ['error', @err, @msg]
+      And -> expect(EventEmitter.prototype.emit.apply).toHaveBeenCalledWith @router, ['error', @err, @msg, jasmine.any(Function)]

     describe '#route(msg:Message, sock:Socket, end:Function)', ->

       Given -> @sock = new EventEmitter()
       When -> @router.route @msg, @sock, @end
       Then -> expect(@end).toHaveBeenCalled()
-      And -> expect(EventEmitter.prototype.emit.apply).toHaveBeenCalledWith @router, ['next', @msg, @sock]
+      And -> expect(EventEmitter.prototype.emit.apply).toHaveBeenCalledWith @router, ['next', @msg, @sock, jasmine.any(Function)]

     describe '#use(fn:Function)', ->

@@ -62,7 +62,7 @@
     describe '#_event(name:String)', ->

       Given -> @name = 'some event'
-      Given -> @args = [1, 2, 3]
+      Given -> @args = [undefined, 2, 3]
       When -> @res = @router._event @name, @end, @args
       Then -> expect(typeof @res).toBe 'function'


Split automatics tests into variables and constants

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -252,9 +252,14 @@
           expect(tokens[0]).not.toHaveScopes ["source.powershell", "keyword.operator.comparison.powershell"]
           expect(tokens[1]).not.toHaveScopes ["source.powershell", "keyword.operator.comparison.powershell"]

+  # Automatic variables. Ref: https://technet.microsoft.com/en-us/library/hh847768.aspx
   describe "Automatic variables", ->
+    automaticConstants = [
+      "$null", "$true", "$false"
+    ]
+
     automaticVariables = [
-      "$null", "$true", "$false", "$$", "$?", "$^", "$_",
+      "$$", "$?", "$^", "$_",
       "$Args", "$ConsoleFileName", "$Error", "$Event", "$EventArgs",
       "$EventSubscriber", "$ExecutionContext", "$ForEach", "$Host", "$Home", "$Input",
       "$LastExitCode", "$Matches", "$MyInvocation", "$NestedPromptLevel", "$OFS",
@@ -264,14 +269,22 @@
       "$ShellID", "$StackTrace", "$This"
     ]

-    it "tokenizes automatic language variables", ->
-      for variable in automaticVariables
-        {tokens} = grammar.tokenizeLine variable
+    it "tokenizes automatic language constants", ->
+      for automaticConstant in automaticConstants
+        {tokens} = grammar.tokenizeLine automaticConstant
         expect(tokens[0].value).toEqual "$"
-        expect(tokens[0]).toHaveScopes ["variable.language.powershell", "punctuation.variable.begin.powershell"]
-        expect(tokens[1].value).toEqual variable.substr(1)
-        expect(tokens[1]).toHaveScopes ["variable.language.powershell"]
-        expect(tokens[1]).not.toHaveScopes ["punctuation.variable.begin.powershell"]
+        expect(tokens[0]).toHaveScopes ["source.powershell", "keyword.other.powershell"]
+        expect(tokens[1].value).toEqual automaticConstant.substr(1)
+        expect(tokens[1]).toHaveScopes ["source.powershell", "constant.language.powershell"]
+
+    it "tokenizes automatic variables", ->
+      for automaticVariable in automaticVariables
+        {tokens} = grammar.tokenizeLine automaticVariable
+        expect(tokens[0].value).toEqual "$"
+        expect(tokens[0]).toHaveScopes ["source.powershell", "keyword.other.powershell"]
+        expect(tokens[1].value).toEqual automaticVariable.substr(1)
+        expect(tokens[1]).toHaveScopes ["source.powershell", "variable.language.powershell"]
+        expect(tokens[1]).not.toHaveScopes ["source.powershell", "punctuation.variable.begin.powershell"]

   describe "Cmdlets", ->
     cmdlets = ["Get-ChildItem","_-_","underscores_are-not_a_problem"]


Expect DOM element in spec

Expect DOM element in spec

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -163,7 +163,7 @@

       atom.workspaceView.attachToDom()
       editor.setSelectedBufferRange([[0, 0], [0, 2]])
-      expect(StatusBar.selectionCount.text()).toBe '(2)'
+      expect(StatusBar.selectionCount.textContent).toBe '(2)'

   describe "git branch label", ->
     beforeEach ->


logging

logging

--- assets/js/student.coffee
+++ assets/js/student.coffee
@@ -23,15 +23,18 @@
         false

 dollarCharacterConsideredHarmful = () ->
+        console.log 'hm'
         ($("#scratch").append $("
"+
                                 "Sorry, but for security reasons the dollar character is not allowed"+
                                 "
"))
                       .animate 'blind', 1000
+        return

 $('#scratch').keyup ->
     disallowDollar = /\$/
     console.log disallowDollar.test $(this).val()
     if disallowDollar.test $(this).val()
+        console.log 'weirdness'
         dollarCharacterConsideredHarmful()
         return
     if !~($(".alert-error").length-1)


spec: fix fragile timezone TimeBlockRow spec

when the browser timezone is later than Detroit (e.g. UTC on a
continuous integration box), and the time is pre-midnight Detroit but
post-midnight in the browser timezone, then the former construction of
a "fudged midnight" would choose the wrong day (the UTC date instead of
the Detroit date), and as such would come later than the current time
instead of the intended earlier.

while fixing, also clarify the behavior with appropriate variable names,
and fix the spec (and other similar specs) to produce failed tests
instead of uncaught errors. (when validation is expected to fail, but
doesn't, the associated_error_box won't be there.)

test-plan:
 - wait until after 8am Detroit time (or set system clock)
 - set system timezone to Auckland (New Zealand)
 - specs still pass

Change-Id: Ifb587455fbcdb76dbefaac8b17ff2b3ad76e4da9
Reviewed-on: https://gerrit.instructure.com/56019
Reviewed-by: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Tested-by: Jenkins
Product-Review: Jacob Fugal <f862f167b85d41b225785c70d70808bc7337c1fe@instructure.com>
QA-Review: Jacob Fugal <f862f167b85d41b225785c70d70808bc7337c1fe@instructure.com>

--- spec/coffeescripts/calendar/TimeBlockRowSpec.coffee
+++ spec/coffeescripts/calendar/TimeBlockRowSpec.coffee
@@ -66,24 +66,25 @@
     me = new TimeBlockRow(@timeBlockList, {start, end})
     me.$date.val('1/1/2000').change()
     ok !me.validate()
-    ok me.$end_time.data('associated_error_box').is(':visible'), 'error box is visible'
     ok me.$end_time.hasClass('error'), 'has error class'
+    ok me.$end_time.data('associated_error_box')?.is(':visible'), 'error box is visible'

   test 'validate: just time in past', ->
-    twelveOClock = new Date(new Date().toDateString())
-    twelveOOne = new Date(twelveOClock)
-    twelveOOne.setMinutes(1)
+    fudgedNow = $.fudgeDateForProfileTimezone(new Date())
+    fudgedMidnight = new Date(fudgedNow.toDateString())
+    fudgedEnd = new Date(fudgedMidnight)
+    fudgedEnd.setMinutes(1)

-    me = new TimeBlockRow(@timeBlockList, {start: twelveOClock, end: twelveOOne})
+    me = new TimeBlockRow(@timeBlockList, {start: fudgedMidnight, end: fudgedEnd})
     ok !me.validate(), 'not valid if time in past'
-    ok me.$end_time.data('associated_error_box').is(':visible'), 'error box is visible'
     ok me.$end_time.hasClass('error'), 'has error class'
+    ok me.$end_time.data('associated_error_box')?.is(':visible'), 'error box is visible'

   test 'validate: end before start', ->
     me = new TimeBlockRow(@timeBlockList, {start: end, end: start})
     ok !me.validate()
-    ok me.$start_time.data('associated_error_box').parents('#fixtures'), 'error box is visible'
     ok me.$start_time.hasClass('error'), 'has error class'
+    ok me.$start_time.data('associated_error_box')?.is(':visible'), 'error box is visible'

   test 'valid if whole row is blank', ->
     me = new TimeBlockRow(@timeBlockList)


Bugfix: remove typo related to models always referring to kmeans.

--- src/ext/components/model-input.coffee
+++ src/ext/components/model-input.coffee
@@ -150,7 +150,7 @@
 #     return

     parameters = collectParameters no
-    _.insertAndExecuteCell 'cs', "buildModel 'kmeans', #{stringify parameters}"
+    _.insertAndExecuteCell 'cs', "buildModel '#{_algorithm}', #{stringify parameters}"

   form: _form
   exception: _exception


Set a -1 tabindex on on the tab bar

Set a -1 tabindex on on the tab bar

This prevents it from gaining focus when a close icon is clicked.

--- src/packages/tabs/lib/tab-bar-view.coffee
+++ src/packages/tabs/lib/tab-bar-view.coffee
@@ -6,7 +6,7 @@
 module.exports =
 class TabBarView extends View
   @content: ->
-    @ul class: "tabs sortable-list"
+    @ul tabindex: -1, class: "tabs sortable-list"

   initialize: (@pane) ->
     @on 'dragstart', '.sortable', @onDragStart


Add token value directly to column

Add token value directly to column

--- src/token.coffee
+++ src/token.coffee
@@ -48,13 +48,12 @@
       column = startColumn

       for token in @breakOutSurrogatePairs()
-        nextColumn += token.value.length
         if token.isAtomic
           outputTokens.push(token)
         else
           outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingSoftTabs, column)...)
         breakOutLeadingSoftTabs = token.isOnlyWhitespace() if breakOutLeadingSoftTabs
-        column = nextColumn
+        column += token.value.length

       outputTokens
     else


Stubbed remaining string functions

Stubbed remaining string functions

--- src/builtin/string.coffee
+++ src/builtin/string.coffee
@@ -186,8 +186,14 @@
       partitions = ["", "", splitValues[0]]
     return new Tuple(partitions)

+  rsplit: -> return
+  rstrip: -> return
+  split: -> return
+  splitlines: -> return
   startswith: (prefix, start = 0, end = @__len__()) ->
     return @value.slice(start, end).indexOf(suffix) == 0
+
+  strip: -> return

   swapcase: ->
     result = ''
@@ -206,6 +212,9 @@
         result += char
     return result

+  title: -> return
+  translate: -> return
+
   upper: ->
     return @value.toUpperCase()


optimization: we know that o is not null

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -454,7 +454,7 @@
       rs.push o
     else
       target_class = c2t(@class).toExternalString() # class we wish to cast to
-      candidate_class = if o? then o.type.toExternalString() else "null"
+      candidate_class = o.type.toExternalString()
       java_throw rs, 'java/lang/ClassCastException', "#{candidate_class} cannot be cast to #{target_class}"
   }
   193: new root.ClassOpcode 'instanceof', { execute: (rs) -> o=rs.pop(); rs.push if o? then types.check_cast(rs,o,@class)+0 else 0 }


closes #483

closes #483

--- src/reports.coffee
+++ src/reports.coffee
@@ -50,9 +50,11 @@
 #     Pre-Fetch report data into Channel Map
       for key , obj of channelMap
         innerDeferred = Q.defer()
-        fetchChannelReport obj.channel, obj.user, flag, (item) ->
-          channelReportMap[key] = item
-          innerDeferred.resolve()
+        do (innerDeferred,key, obj) ->
+          fetchChannelReport obj.channel, obj.user, flag, (item) ->
+
+            channelReportMap[key] = item
+            innerDeferred.resolve()
         innerPromises.push innerDeferred.promise

       (Q.all innerPromises).then ->
@@ -118,7 +120,6 @@

     .then (statusData) ->
       item.statusData = statusData
-#      console.log item
       callback item

 fetchDailySubscribers = (callback) ->


Fix the expect

Fix the expect

--- spec/ComponentLoader.coffee
+++ spec/ComponentLoader.coffee
@@ -269,6 +269,6 @@
           done()
       it 'should not be a loadable component', (done) ->
         l.load 'foo/NotWorking', (err, inst) ->
-          return done err if err
+          chai.expect(err).to.be.an 'object'
           chai.expect(inst).to.be.an 'undefined'
           done()


GameObject.Effect

GameObject.Effect

--- src/tween.coffee
+++ src/tween.coffee
@@ -21,7 +21,6 @@
         delete I.activeTweens[property]
       else
         f = Easing[data.easing](data.start, data.end)
-
         I[property] = f((t - data.startTime) / data.duration)

   ###*


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -16,11 +16,10 @@
     for property, data of I.activeTweens
       if I.age >= data.endTime
         I[property] = data.end
-
       else
         f = Easing[data.easing](data.start, data.end)
         t = (I.age - data.startTime) / data.duration
-        I[property] = f()
+        I[property] = f(t)

   tween: (duration, properties) ->
     for property, target of properties


Fix test message blowup

Fix test message blowup

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -8,7 +8,9 @@
     this.addMatchers
       toHaveScopes: (scopes) ->
         notText = if @isNot then "not" else ""
-        this.message = =>"Expected token \"#{@actual.value}\" to #{notText} have scope \"#{scope.toString}\". Instead found: [#{@actual.scopes.toString()}]"
+        this.message = (expected) =>
+          "Expected token \"#{@actual.value}\" to #{notText} have scopes \"#{expected}\". Instead found: [#{@actual.scopes.toString()}]"
+
         allScopesPresent = scopes.every (scope) =>
           return scope in @actual.scopes


Fixed label

Fixed label

--- spec/properties-spec.coffee
+++ spec/properties-spec.coffee
@@ -3,7 +3,7 @@
 suite = [
   ['should generate plain-old objects', [require('./fixtures/person-schema.json')]]
   ['should handle allOf, anyOf, oneOf', ['allOf', 'anyOf', 'oneOf'].map (v) -> require("./fixtures/#{v}-schema.json")]
-  ['should skip some required properties', [require('./fixtures/requires-schema.json')]]
+  ['should skip some non-required properties', [require('./fixtures/requires-schema.json')]]
   ['should handle enum, minLength, maxLength', [require('./fixtures/strings-schema.json')]]
   ['should handle enum, multipleOf, minimum, exclusiveMinimum, maximum, exclusiveMaximum', [require('./fixtures/numbers-schema.json')]]
   ['should handle minItems, maxItems, uniqueItems', [require('./fixtures/arrays-schema.json')]]


Start the fs watch after the VPN directory has been created - Use finally and not then

--- src/utils.coffee
+++ src/utils.coffee
@@ -74,7 +74,8 @@
 checkHost = (options) ->
 	if disableConnectivityCheck or pauseConnectivityCheck
 		return true
-	return networkCheck.checkHost(options)
+	else
+		return networkCheck.checkHost(options)

 # Custom monitor that uses checkHost function above.
 customMonitor = (options, fn) ->
@@ -103,6 +104,8 @@
 	parsedUrl = url.parse(config.apiEndpoint)
 	fs.mkdirAsync(config.vpnStatusPath)
 	.then ->
+		console.log('Creating directory for watching VPN status...')
+	.finally ->
 		fs.watch(config.vpnStatusPath, vpnStatusInotifyCallback)
 	.catch EEXIST, (err) ->


Fix build failure.

Fix build failure.

--- src/utils.coffee
+++ src/utils.coffee
@@ -30,7 +30,9 @@
 applyColors = exports.applyColors = (message, colorList) ->
     return message if !message?

-    message = (colors[color](message) for color in colorList)
+    for color in colorList
+        message = colors[color](message)
+
     return message

 # Transforms "/src/foo/bar.coffee" to "/s/f/bar".


speed up logs meemoo/dataflow#77

speed up logs meemoo/dataflow#77

--- src/plugins/nofloGraph.coffee
+++ src/plugins/nofloGraph.coffee
@@ -210,10 +210,11 @@
           if edge.from.node is payload.from.node and edge.from.port is payload.from.port
             eventEdge = edge
       return unless eventEdge
-      eventEdge.dataflowEdge.get('log').add
-        type: command
-        group: payload.group
-        data: payload.data
+      if payload.data?
+        eventEdge.dataflowEdge.get('log').push
+          type: command
+          group: payload.group
+          data: payload.data

   addNodeDataflow: (nfNode, dfGraph) ->
     return unless nfNode


don't save bang IIP to graph; closes #9

don't save bang IIP to graph; closes #9

--- src/plugins/nofloGraph.coffee
+++ src/plugins/nofloGraph.coffee
@@ -135,7 +135,10 @@
         if iip.to.node is node.nofloNode.id and iip.to.port is port
           metadata = iip.metadata
           graph.nofloGraph.removeInitial node.nofloNode.id, port
+      # Transmit bang to the NoFlo runtime
       graph.nofloGraph.addInitial true, node.nofloNode.id, port, metadata
+      # Don't save bang IIP to the graph (#9)
+      graph.nofloGraph.removeInitial node.nofloNode.id, port

   subscribeDataflowEdge: (edge, graph) ->
     unless edge.nofloEdge


fix https://github.com/sagemathinc/cocalc/issues/2385 -- course "require students to pay" date selector behaves a bit surprisingly

--- src/smc-webapp/course/settings_panel.cjsx
+++ src/smc-webapp/course/settings_panel.cjsx
@@ -178,8 +178,12 @@
         settings    : rtypes.immutable.Map.isRequired
         project_map : rtypes.immutable.Map.isRequired

+    shouldComponentUpdate: (next, next_state) ->
+        return next.settings != @props.settings or next.project_map != @props.project_map or \
+            next_state.show_students_pay_dialog != @stateshow_students_pay_dialog
+
     getInitialState: ->
-        show_students_pay_dialog        : false
+        show_students_pay_dialog : false

     ###
     # Editing title/description
@@ -296,7 +300,6 @@

         content += 'students = [\n'

-        console.log "exporting py"
         for student in store.get_sorted_students()
             grades   = (("'#{store.get_grade(assignment, student) ? ''}'") for assignment in assignments).join(',')
             grades   = grades.replace(/\n/g, "\\n")
@@ -380,14 +383,14 @@
         </Button>

     render_require_students_pay_desc: ->
-        date = @props.settings.get('pay')
+        date = new Date(@props.settings.get('pay'))
         if date > webapp_client.server_time()

-                Your students will see a warning until <TimeAgo date={date} />.  They will then be required to upgrade for a one-time fee of $9.
+                Your students will see a warning until <TimeAgo date={date} />.  They will then be required to upgrade for a special discounted one-time fee of $9.

         else

-                Your students are required to upgrade their project.
+                Your students are required to upgrade their project now to use it.  If you want to give them more time to upgrade, move the date forward.

     render_require_students_pay_when: ->
@@ -441,7 +444,7 @@
         <Alert bsStyle='info'>

<Icon name='arrow-circle-up' /> Require students to upgrade

-            Click the following checkbox to require that all students in the course pay a one-time $9 fee to move their projects to members-only computers and enable full internet access, for four months.  Members-only computers are not randomly rebooted constantly and have far fewer users. Student projects that are already on members-only hosts will not be impacted.  You will not be charged.
+            Click the following checkbox to require that all students in the course pay a special discounted one-time $9 fee to move their projects to members-only computers and enable full internet access, for four months.  Members-only computers are not randomly rebooted constantly and have far fewer users. Student projects that are already on members-only hosts will not be impacted.  You will not be charged.

             {@render_students_pay_checkbox()}
             {@render_require_students_pay_when() if @props.settings.get('pay')}


Remove extraneous variable from specs

Remove extraneous variable from specs

--- spec/sort-lines-spec.coffee
+++ spec/sort-lines-spec.coffee
@@ -2,11 +2,10 @@

 describe "SortLines", ->
   describe "sortLines(editor)", ->
-    [editor, buffer] = []
+    [editor] = []

     beforeEach ->
       editor = atom.project.openSync()
-      buffer = editor.getBuffer()
       editor.setText """
         Hydrogen
         Helium


if supplied, server.config will be passed to server

--- src/watch.coffee
+++ src/watch.coffee
@@ -68,7 +68,11 @@
       logger.error "couldn\'t load server #{config.server.path}: #{error}"
     unless server.startServer?
       throw new Error 'Brunch server file needs to have startServer function'
-    server.startServer port, publicPath, log
+    if serverOpts.config
+      opts = {port, path: publicPath, logger}
+      server.startServer helpers.extend(opts, serverOpts.config)
+    else
+      server.startServer port, publicPath, log
   else
     opts = noLog: yes, path: publicPath
     pushserve helpers.extend(opts, serverOpts), log


Kill weird hidden character

Kill weird hidden character

--- src/watch.coffee
+++ src/watch.coffee
@@ -418,7 +418,7 @@
         }

     if config.persistent and config.server.run
-      server = startServer config, launchWatcher
+      server = startServer config, launchWatcher
     else
       launchWatcher()


Remove code added for debugging

Remove code added for debugging

--- app/utils/auth.coffee
+++ app/utils/auth.coffee
@@ -82,13 +82,14 @@
     @sendToApp('afterSignIn')

   refreshUserData: (user) ->
+    debugger
     unless user
       if data = @userDataFrom(@sessionStorage) || @userDataFrom(@storage)
         user = data.user

     if user
       Ajax.get("/users/#{user.id}").then (data) =>
-        if data.user.correct_scopes && false
+        if data.user.correct_scopes
           userRecord = @loadUser(data.user)
           userRecord.get('permissions')
           # if user is still signed in, update saved data


Update Atom RuboCop Linter command path

Update Atom RuboCop Linter command path

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -30,7 +30,7 @@
   "exception-reporting":
     userId: "a16fb11c-80f5-993c-dac9-27e736ff535e"
   "linter-rubocop":
-    executablePath: "/Users/jpalumickas/.rbenv/shims"
+    command: "/Users/jpalumickas/.rbenv/shims"
   "linter-scss-lint":
     scssLintExecutablePath: "/Users/jpalumickas/.rbenv/shims/"
   linter:


change atom config

change atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -20,6 +20,7 @@
       "linter-php"
       "linter-rubocop"
       "linter-ruby"
+      "minimap"
     ]
     themes: [
       "isotope-ui"


Update atom's config

Update atom's config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -49,6 +49,10 @@
     onChanges: true
   linter:
     showErrorPanel: false
+  minimap:
+    plugins:
+      "highlight-selected": true
+      "highlight-selectedDecorationsZIndex": 0
   "one-dark-ui": {}
   "relative-numbers": {}
   "tree-view":


add Atom projectHome

add Atom projectHome

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,4 +1,5 @@
 "*":
+  "atom-autocomplete-php": {}
   "atom-beautify":
     css:
       beautify_on_save: true
@@ -24,6 +25,7 @@
       "metrics"
       "exception-reporting"
     ]
+    projectHome: "~/code"
     themes: [
       "one-dark-ui"
       "atom-panda-syntax"


More atom customization

More atom customization

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,7 +1,7 @@
 'editor':
   'fontSize': 13
+  'showInvisibles': true
   'showIndentGuide': true
-  'showInvisibles': true
 'core':
   'hideGitIgnoredFiles': false
   'ignoredNames': [
@@ -14,13 +14,14 @@
   ]
   'themes': [
     'atom-light-ui'
-    'atom-light-syntax'
+    'solarized-light-syntax'
   ]
   'disabledPackages': [
     'background-tips'
     'exception-reporting'
     'metrics'
     'autosave'
+    'atom-lint'
   ]
   'excludeVcsIgnoredPaths': false
 'spell-check':


Remove output listener when connection is closed

--- src/packages/collaboration/lib/session-utils.coffee
+++ src/packages/collaboration/lib/session-utils.coffee
@@ -7,10 +7,14 @@
     new Peer(id, host: 'ec2-54-218-51-127.us-west-2.compute.amazonaws.com', port: 8080)

   connectDocument: (doc, connection) ->
-    doc.outputEvents.on 'changed', (event) ->
+    outputListener = (event) ->
       console.log 'sending event', event
       connection.send(event)
+    doc.outputEvents.on('changed', outputListener)

     connection.on 'data', (event) ->
       console.log 'receiving event', event
       doc.handleInputEvent(event)
+
+    connection.on 'close', ->
+      doc.outputEvents.removeListener('changed', outputListener)


Attach editor and update display in spec

This is required now that the editor checks visibility
before updating the display.

--- src/packages/status-bar/spec/status-bar-spec.coffee
+++ src/packages/status-bar/spec/status-bar-spec.coffee
@@ -102,7 +102,9 @@

   describe "when the associated editor's cursor position changes", ->
     it "updates the cursor position in the status bar", ->
+      editor.attachToDom()
       editor.setCursorScreenPosition([1, 2])
+      editor.updateDisplay()
       expect(statusBar.cursorPosition.text()).toBe '2,3'

   describe "git branch label", ->


创建 ObjectID 失败时返回 null

创建 ObjectID 失败时返回 null

--- core/db.coffee
+++ core/db.coffee
@@ -18,7 +18,10 @@
   return exports.mongo.collection name

 exports.ObjectID = (id) ->
-  return new ObjectID id
+  try
+    return new ObjectID id
+  catch e
+    return null

 exports.buildModel = (that, mongo) ->
   that.find = (selector, options, callback) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Fix #597, lightbox breaks with single images

Trying to remove nodes from grandparent rather than parent,
changed to DOM2 .remove() call.

--- _coffee/lightbox.coffee
+++ _coffee/lightbox.coffee
@@ -90,8 +90,8 @@

     # Remove L&R if single image
     if @lightboxGallery.galleryLinks.length == 1
-      @lb.removeChild(prevEl)
-      @lb.removeChild(nextEl)
+      prevEl.remove()
+      nextEl.remove()

   nextImage: =>
     if (@i + 1) < @lightboxGallery.galleryLinks.length


Add AtomEnvironment::restoreStateIntoEnvironment

This takes an existing Atom environment and restores a saved state from
the IndexDB state storage into it by:

1. Serializing the existing pane items
2. Restoring the saved state
3. Deserializing the saved pane items into the newly restored
environment

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -863,6 +863,22 @@
     @pickFolder (selectedPaths = []) =>
       @project.addPath(selectedPath) for selectedPath in selectedPaths

+  restoreStateIntoEnvironment: (state) ->
+    shouldSerializeItem = (item) ->
+      return true unless item instanceof TextEditor
+      item.getPath() or item.isModified()
+    serializedOpenItems = (item.serialize() for item in @workspace.getPaneItems() when shouldSerializeItem(item))
+    serializedBuffers = (buffer.serialize() for buffer in @project.buffers)
+
+    state.fullScreen = @isFullScreen()
+    pane.destroy() for pane in @workspace.getPanes()
+    @deserialize(state)
+    savedBuffers = (TextBuffer.deserialize(serializedBuffer) for serializedBuffer in serializedBuffers)
+    @project.buffers = @project.buffers.concat(savedBuffers)
+
+    items = (@deserializers.deserialize(itemState) for itemState in serializedOpenItems)
+    @workspace.getPanes()[0].addItems(items, 0)
+
   showSaveDialog: (callback) ->
     callback(@showSaveDialogSync())


Layer

Layer

- Improved positioning when body has a scrollbar.

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -339,7 +339,9 @@
 		if not @isShown()
 			return @

-		dim_window = CUI.DOM.getDimensions(window)
+		dim_window =
+			width: document.body.clientWidth
+			height: window.innerHeight

 		get_pointer_direction = (placement) ->
 			# return direction


Adding a small comment to the select() logic in properties.coffee

--- static/coffee/renderers/properties.coffee
+++ static/coffee/renderers/properties.coffee
@@ -148,7 +148,10 @@
       if (@[attrname].field of obj)
         return obj[@[attrname].field]

-    # otherwise, if the attribute exists on the object, return that value
+    # otherwise, if the attribute exists on the object, return that value.
+    # (This is a convenience case for when the object passed in has a member
+    # that has the same name as the glyphspec name, e.g. an actual field
+    # named "x" or "radius".)
     if obj[attrname]?
       return obj[attrname]


name validation added close #63

name validation added
close #63

--- src/client/app_admin.coffee
+++ src/client/app_admin.coffee
@@ -34,23 +34,34 @@
     })

   $scope.submit = ()->
-    dict = {'Мирный': 0, 'Шериф': 0, 'Мафия': 0, 'Дон': 0}
+    roleDict = {'Мирный': 0, 'Шериф': 0, 'Мафия': 0, 'Дон': 0}
     noBestPlayers = true
     $scope.errors = []
+    namesDict = []
     for player in $scope.players
-      ++dict[player.role]
+      namesDict[player.name] = 0
+    for player in $scope.players
+      ++roleDict[player.role]
+      ++namesDict[player.name]
       if player.isBest
         noBestPlayers = false
-    if dict['Мирный'] != 6
-      $scope.errors.push("мирных должно быть 6")
-    if dict['Шериф'] != 1
-      $scope.errors.push('должен быть 1 шериф')
-    if dict['Мафия'] != 2
-      $scope.errors.push('должно быть 2 мафии')
-    if dict['Дон'] != 1
-      $scope.errors.push('должен быть 1 дон')
+      if player.name == $scope.referee.value
+        $scope.errors.push("Имя игрока #{player.name} совпадает с именем ведущего")
+
+    for name, count of namesDict
+      if count > 1
+        $scope.errors.push("Игрок #{name} встречается больше 1 раза")
+
+    if roleDict['Мирный'] != 6
+      $scope.errors.push("Мирных должно быть 6")
+    if roleDict['Шериф'] != 1
+      $scope.errors.push('Должен быть 1 шериф')
+    if roleDict['Мафия'] != 2
+      $scope.errors.push('Должно быть 2 мафии')
+    if roleDict['Дон'] != 1
+      $scope.errors.push('Должен быть 1 дон')
     if noBestPlayers
-      $scope.errors.push('в игре отсутствуют лучшие игроки')
+      $scope.errors.push('В игре отсутствуют лучшие игроки')

     if $scope.errors.length != 0
       $scope.openErrorPopup()
@@ -94,7 +105,6 @@
   $scope.refresh = ()->
     $modalInstance.close()
     $window.location.reload()
-

 @datePickerCtrl = ($scope)->
   $scope.today = ()->


removed semicolons

removed semicolons

--- src/coffee/dbHandler.coffee
+++ src/coffee/dbHandler.coffee
@@ -8,8 +8,8 @@
          process.stdout.write '\nDatabase Type: '.red + credentials.type + ' not supported.\n Supported types: mysql, pgsql\n'
          process.exit 1

-      @currentFile = 0;
-      @files = filesToProcess;
+      @currentFile = 0
+      @files = filesToProcess
       @database = require __dirname+'/orms/'+credentials.type+'.js'

       process.stdout.write '\nConnecting as '.green + credentials.username + ' ... '


Implement pro page processing in order to consume less memory.

--- src/coffee/pricesync.coffee
+++ src/coffee/pricesync.coffee
@@ -36,56 +36,59 @@
       @inventoryUpdater.ensureChannelByKey(@masterClient._rest, @retailerProjectKey, CHANNEL_ROLES)
       @getCustomerGroup(@masterClient, CUSTOMER_GROUP_SALE)
       @getCustomerGroup(@retailerClient, CUSTOMER_GROUP_SALE)
-      @getPublishedProducts(@retailerClient)
-    ]).spread (retailerChannelInMaster, masterCustomerGroup, retailerCustomerGroup, retailerProducts) =>
-      console.log "Retailer products: #{_.size retailerProducts}"
-      if _.size(retailerProducts) is 0
-        Q("Nothing to do.")
-      else
-
-        gets = _.map retailerProducts, (retailerProduct) =>
-          current = retailerProduct.masterData.current
-          current.variants or= []
-          variants = [current.masterVariant].concat current.variants
-          v = _.map variants, (retailerVariant) =>
-            @taskQueue.addTask _.bind(@_processVariant, this, retailerVariant, retailerCustomerGroup, masterCustomerGroup, retailerChannelInMaster)
-          Q.all(v)
-
-        Q.all(gets)
+    ]).spread (retailerChannelInMaster, masterCustomerGroup, retailerCustomerGroup) =>
+      @getPublishedProducts @retailerClient, ((page, count) -> console.error "Page #{page} processed - #{count} price update(s) done."), (retailerProduct) =>
+        current = retailerProduct.masterData.current
+        current.variants or= []
+        variants = [current.masterVariant].concat current.variants
+        v = _.map variants, (retailerVariant) =>
+          @taskQueue.addTask _.bind(@_processVariant, this, retailerVariant, retailerCustomerGroup, masterCustomerGroup, retailerChannelInMaster)
+        Q.all(v)
+        .then (infos) ->
+          x = _.reduce infos, ((acc, info) -> acc + info.updates), 0

   _processVariant: (retailerVariant, retailerCustomerGroup, masterCustomerGroup, retailerChannelInMaster) ->
     @getPublishedVariantByMasterSku(@masterClient, retailerVariant)
     .then (variantDataInMaster) =>
       @syncVariantPrices(variantDataInMaster, retailerVariant, retailerCustomerGroup, masterCustomerGroup, retailerChannelInMaster)
     .fail (msg) =>
-      msg
-
+      console.warn msg
+      Q({ updates: 0 })

   syncVariantPrices: (variantDataInMaster, retailerVariant, retailerCustomerGroup, masterCustomerGroup, retailerChannelInMaster) ->
     prices = @_filterPrices(retailerVariant, variantDataInMaster.variant, retailerCustomerGroup, masterCustomerGroup, retailerChannelInMaster)
     actions = @_updatePrices(prices.retailerPrices, prices.masterPrices, retailerChannelInMaster.id, variantDataInMaster.variant, retailerCustomerGroup.id, masterCustomerGroup.id)

-    data =
-      version: variantDataInMaster.productVersion
-      variantId: variantDataInMaster.productId
-      actions: actions
-
-    @masterClient.products.byId(variantDataInMaster.productId).save(data)
-
-  getPublishedProducts: (client, offsetInDays = 2) ->
+    if _.isEmpty actions
+      Q({ updates: 0 })
+
+    else
+      data =
+        version: variantDataInMaster.productVersion
+        variantId: variantDataInMaster.productId
+        actions: actions
+
+      @masterClient.products.byId(variantDataInMaster.productId).save(data)
+      .then ->
+        Q({ updates: _.size(actions) })
+
+  getPublishedProducts: (client, pageProcessedCb, processFn) ->
     deferred = Q.defer()

-    date = new Date()
-    date.setDate(date.getDate() - offsetInDays)
-    offSet = "#{date.toISOString().substring(0,10)}T00:00:00.000Z"
-
-    pageProducts = (page = 0, perPage = 50, total, acc = []) ->
+    pageProducts = (page = 0, perPage = 50, total, acc = 0) ->
       if total? and page * perPage > total
         deferred.resolve acc
       else
         client.products.page(page).perPage(perPage).fetch()
         .then (payload) ->
-          pageProducts page + 1, perPage, payload.total, acc.concat(payload.results)
+          processes = _.map payload.results, (elem) ->
+            processFn(elem)
+          Q.all(processes)
+          .then (counts) ->
+            [_.reduce(counts, ((acc, count) -> acc + count), 0), payload]
+        .then ([count, payload]) ->
+          pageProcessedCb(page, count)
+          pageProducts page + 1, perPage, payload.total, count + acc
         .fail (error) ->
           deferred.reject error
         .done()


Fixing bug on facebook login

Fixing bug on facebook login

--- src/server/Application.coffee
+++ src/server/Application.coffee
@@ -72,7 +72,7 @@

 	onLoginRequest: (session, request, response) =>
 		data = request.getData();
-		if (data.method? and data.method == "facebook")
+		if (data? and data.method? and data.method == "facebook")
 			user = new User();
 			promise = user.loadFromFacebook(data.token);
 			promise.then () =>


Do not calculate findMistake for too big source length

--- server/findMistake/processForFindMistake.coffee
+++ server/findMistake/processForFindMistake.coffee
@@ -1,9 +1,12 @@
 import FindMistake from '../models/FindMistake'

 DISTANCE_THRESHOLD = 15
+MAX_LENGTH = 3000

 distance = (s1, s2) ->
     if (not s1) or (not s2)
+        return DISTANCE_THRESHOLD + 1
+    if s1.length > MAX_LENGTH or s2.length > MAX_LENGTH
         return DISTANCE_THRESHOLD + 1
     d = new Array(s1.length + 1)
     for _, i in d


prevalidate isn't a view

prevalidate isn't a view

The process is now handled by the regular registration view

--- src/concept-coach/base.cjsx
+++ src/concept-coach/base.cjsx
@@ -132,8 +132,6 @@
     switch view
       when 'loading'
          Loading ...
-      when 'prevalidate'
-        <CourseRegistration {...@props} validateOnly />
       when 'logout'
         <AccountsIframe type='logout' />
       when 'login'


add table that will be used for coordinating copying between projects

--- src/smc-util/db-schema.coffee
+++ src/smc-util/db-schema.coffee
@@ -912,6 +912,62 @@
                 project_id  : true
                 path        : true

+###
+Requests and status related to copying files between projects.
+###
+schema.copy_files =
+    primary_key : 'id'
+    fields:
+        id                 :
+            type : 'uuid'
+            desc : 'random unique id assigned to this copy request'
+        time               :
+            type : 'timestamp'
+            desc : 'when this request was made'
+        source_project_id  :
+            type : 'uuid'
+            desc : 'the project_id of the source project'
+        source_path        :
+            type : 'string'
+            desc : 'the path of the source file or directory'
+        target_project_id  :
+            type : 'uuid'
+            desc : 'the project_id of the target project'
+        target_path        :
+            type : 'string'
+            desc : 'the path of the target file or directory'
+        overwrite_newer    :
+            type : 'boolean'
+            desc : 'if new, overwrite newer files in destination'
+        delete_missing     :
+            type : 'boolean'
+            desc : "if true, delete files in the target that aren't in the source path"
+        backup             :
+            type : 'boolean'
+            desc : 'if true, make backup of files before overwriting'
+        bwlimit            :
+            type : 'string'
+            desc : 'optional limit on the bandwidth dedicated to this copy (passed to rsync)'
+        timeout            :
+            type : 'number'
+            desc : 'fail if the transfer itself takes longer than this number of seconds (passed to rsync)'
+        started :
+            type : 'timestamp'
+            desc : 'when the copy request actually started running'
+        finished :
+            type : 'timestamp'
+            desc : 'when the copy request finished'
+        error :
+            type : 'string'
+            desc : 'if the copy failed or output any errors, they are put here.'
+    pg_indexes : ['time']
+    # TODO: for now there are no user queries -- this is used entirely by backend servers,
+    # actually only in kucalc; later that may change, so the user can make copy
+    # requests this way, check on their status, show all current copies they are
+    # causing in a page (that is persistent over browser refreshes, etc.).
+    # That's for later.
+
+
 schema.remember_me =
     primary_key : 'hash'
     durability  : 'soft' # dropping this would just require a user to login again


fix bacon

fix bacon

--- app/graphite.coffee
+++ app/graphite.coffee
@@ -1,6 +1,6 @@
 _ = require 'underscore'
 $ = require 'jquery'
-bacon = require 'baconjs'
+Bacon = require 'bacon.model'
 Q = require 'q'
 URI = require 'URIjs'
 moment = require 'moment'


toFrom and fromTo now accepted aliases for animators -

--- src/art/engine/animation/persistant_animator.coffee
+++ src/art/engine/animation/persistant_animator.coffee
@@ -356,7 +356,7 @@
     @_element = null
     @_animate = options.animate
     @_continuous = options.continuous
-    @_voidValue = rubyOr options.voidValue, options.toFromVoid, options.fromToVoid, options.void
+    @_voidValue = rubyOr options.voidValue, options.toFromVoid, options.fromToVoid, options.void, options.toFrom, options.fromTo
     @_toVoid    = options.toVoid
     @_fromVoid  = options.fromVoid
     @on options.on if options?.on


testing broken test

testing broken test


Add underscore so download update menu item works

--- src/application-menu.coffee
+++ src/application-menu.coffee
@@ -1,5 +1,6 @@
 ipc = require 'ipc'
 Menu = require 'menu'
+_ = require 'underscore'

 module.exports =
 class ApplicationMenu


:lipstick:

:lipstick:

--- src/application-menu.coffee
+++ src/application-menu.coffee
@@ -22,7 +22,6 @@
     for index, item of menu.items or {}
       items.push(item)
       items = items.concat(@allItems(item.submenu)) if item.submenu
-
     items

   enableWindowSpecificItems: (enable) ->
@@ -125,10 +124,10 @@
     template

   acceleratorForCommand: (command, keystrokesByCommand) ->
-    keyBinding = keystrokesByCommand[command]?[0]
-    return null unless keyBinding
+    keystroke = keystrokesByCommand[command]?[0]
+    return null unless keystroke

-    modifiers = keyBinding.split('-')
+    modifiers = keystroke.split('-')
     key = modifiers.pop()

     modifiers.push("Shift") if key != key.toLowerCase()


Remove unused devMenu

Remove unused devMenu

--- src/application-menu.coffee
+++ src/application-menu.coffee
@@ -144,10 +144,6 @@
         { label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
       ]

-    devMenu =
-      label: '\uD83D\uDC80' # Skull emoji
-      submenu: [ { label: 'In Development Mode', enabled: false } ]
-
     template = [atomMenu, fileMenu, editMenu, viewMenu, windowMenu]

     @translateTemplate template, keystrokesByCommand


fixed typo

fixed typo

--- app/assets/javascripts/admin/views/team/team_list_view.coffee
+++ app/assets/javascripts/admin/views/team/team_list_view.coffee
@@ -97,7 +97,7 @@
     )

-  filter : -> (filterQuery)
+  filter : (filterQuery) ->

     @collection.setFilter(["name", "owner"], filterQuery)
     @collection.pager()


修复Turbolink下跨页面修改失效的情况

修复Turbolink下跨页面修改失效的情况

在Turbolink下页面加载后会触发`page:load`和`page:change`两个事件，但页面跳转只会触发`page:change`。这里将`page:load`换成`page:change`保证了在页面跳转后仍能初始化。

--- app/assets/javascripts/china_city/jquery.china_city.js.coffee
+++ app/assets/javascripts/china_city/jquery.china_city.js.coffee
@@ -13,6 +13,6 @@
             # init value after data completed.
             next_selects.trigger('china_city:load_data_completed');

-  $(document).on 'ready page:load', ->
+  $(document).on 'ready page:change', ->
     $('.city-group').china_city()
 )(jQuery)


Just cleaning a bit the regex.

Just cleaning a bit the regex.

--- src/dom_text_matcher.coffee
+++ src/dom_text_matcher.coffee
@@ -201,7 +201,7 @@

   # Do some normalization to get a "canonical" form of a string.
   # Used to even out some browser differences.
-  _normalizeString: (string) -> (string.replace /\s{2,}/g, " ").trim()
+  _normalizeString: (string) -> (string.trim().replace /\s+/g, " ")

   # Search for text with a custom matcher object
   #


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -543,6 +543,18 @@
   this.inject 1, (product, n) ->
     product * n

+###*
+Merges together the values of each of the arrays with the values at the corresponding position.
+
+

+['a', 'b', 'c'].zip([1, 2, 3])
+# => [['a', 1], ['b', 2], ['c', 3]]
+

+###
+
+@name zip
+@methodOf Array#
+@returns {Array} Array groupings whose values are arranged by their positions in the original input arrays.
 Array::zip = (args...) ->
   this.map (element, index) ->
     output = args.map (arr) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -1,3 +1,4 @@
+
 Array::average = ->
   @sum()/@length


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -92,11 +92,9 @@

 @param {String} method The name of the method to invoke.
 @param [arg...] Optional arguments to pass to the method being invoked.
-
 @name invoke
 @methodOf Array#
-@returns {Array} A new array containing the results of invoking the
-named method on each element.
+@returns {Array} A new array containing the results of invoking the named method on each element.
 ###
 Array::invoke = (method, args...) ->
   this.map (element) ->


engine_collision

--- src/engine_collision.coffee
+++ src/engine_collision.coffee
@@ -4,7 +4,6 @@
 @name Collision
 @fieldOf Engine
 @module
-
 @param {Object} I Instance variables
 @param {Object} self Reference to the engine
 ###


engine init binding

engine init binding

--- src/engine_developer.coffee
+++ src/engine_developer.coffee
@@ -1,83 +1,78 @@
-###*
-The Developer module provides a debug overlay and methods for debugging and live coding.
+( ($) ->
+  ###*
+  The Developer module provides a debug overlay and methods for debugging and live coding.

-@name Developer
-@fieldOf Engine
-@module
+  @name Developer
+  @fieldOf Engine
+  @module

-@param {Object} I Instance variables
-@param {Object} self Reference to the engine
-###
-Engine.Developer = (I, self) ->
-  self.bind "draw", (canvas) ->
-    if I.paused
-      #TODO: Move this into the debug draw method of the objects themselves
-      canvas.withTransform I.cameraTransform, (canvas) ->
-        I.objects.each (object) ->
-          canvas.fillColor 'rgba(255, 0, 0, 0.5)'
-          canvas.fillRect(object.bounds().x, object.bounds().y, object.bounds().width, object.bounds().height)
+  @param {Object} I Instance variables
+  @param {Object} self Reference to the engine
+  ###
+  Engine.Developer = (I, self) ->
+    self.bind "draw", (canvas) ->
+      if I.paused
+        #TODO: Move this into the debug draw method of the objects themselves
+        canvas.withTransform I.cameraTransform, (canvas) ->
+          I.objects.each (object) ->
+            canvas.fillColor 'rgba(255, 0, 0, 0.5)'
+            canvas.fillRect(object.bounds().x, object.bounds().y, object.bounds().width, object.bounds().height)

-      canvas.fillColor 'rgba(0, 0, 0, 0.5)'
-      canvas.fillRect(430, 10, 200, 60)
-      canvas.fillColor '#fff'
-      canvas.fillText("Developer Mode. Press Esc to resume", 440, 25)
-      canvas.fillText("Shift+Left click to add boxes", 440, 43)
-      canvas.fillText("Right click red boxes to edit properties", 440, 60)
+        canvas.fillColor 'rgba(0, 0, 0, 0.5)'
+        canvas.fillRect(430, 10, 200, 60)
+        canvas.fillColor '#fff'
+        canvas.fillText("Developer Mode. Press Esc to resume", 440, 25)
+        canvas.fillText("Shift+Left click to add boxes", 440, 43)
+        canvas.fillText("Right click red boxes to edit properties", 440, 60)

-  return {}
+    self.bind "init", ->
+      window.updateObjectProperties = (newProperties) ->
+        if objectToUpdate
+          $.extend objectToUpdate, GameObject.construct(newProperties)

-developerMode = false
+      $(document).unbind ".#{namespace}"

-objectToUpdate = null
-window.updateObjectProperties = (newProperties) ->
-  if objectToUpdate
-    $.extend objectToUpdate, GameObject.construct(newProperties)
+      $(document).bind "mousedown.#{namespace}", developerModeMousedown

-# Unbind events on code reload
-if window.developerModeMousedown
-  $(document).unbind(window.developerModeMousedown)
+      for key, fn of developerHotkeys
+        $(document).bind "keydown.#{namespace}", key, (event) ->
+          event.preventDefault()
+          fn()

-if window.developerHotkeys
-  for key, fn of developerHotkeys
-    $(document).unbind "keydown", key, fn
+    return {}

-window.developerModeMousedown = (event) ->
-  if developerMode
-    console.log event.which
+  namespace = "engine_developer"
+  developerMode = false
+  objectToUpdate = null

-    if event.which == 3
-      if object = engine.objectAt(event.pageX, event.pageY)
-        parent.editProperties(object.I)
+  developerModeMousedown = (event) ->
+    if developerMode
+      console.log event.which

-        objectToUpdate = object
+      if event.which == 3
+        if object = engine.objectAt(event.pageX, event.pageY)
+          parent.editProperties(object.I)

-      console.log object
-    else if event.which == 2 || keydown.shift
-      window.developerAddObject?(event)
+          objectToUpdate = object

-# Development Events
-$(document).mousedown window.developerModeMousedown
+        console.log object
+      else if event.which == 2 || keydown.shift
+        window.developerAddObject?(event)

-window.developerHotkeys =
-  esc: ->
-    developerMode = !developerMode
+  developerHotkeys =
+    esc: ->
+      developerMode = !developerMode

-    if developerMode
-      engine.pause()
-    else
-      engine.play()
-  f3: ->
-    Local.set("level", engine.saveState())
-  f4: ->
-    engine.loadState(Local.get("level"))
-  f5: ->
-    engine.reload()
+      if developerMode
+        engine.pause()
+      else
+        engine.play()
+    f3: ->
+      Local.set("level", engine.saveState())
+    f4: ->
+      engine.loadState(Local.get("level"))
+    f5: ->
+      engine.reload()

-for key, fn of window.developerHotkeys
-  window.developerHotkeys[key] = (event) ->
-    event.preventDefault()
-    fn()
+)(jQuery)

-for key, fn of window.developerHotkeys
-  $(document).bind "keydown", key, fn
-


Add basic error handling.

Add basic error handling.

--- lib/product-discount-import.coffee
+++ lib/product-discount-import.coffee
@@ -28,7 +28,9 @@
     message

   performStream: (chunk, cb) ->
-    @_processBatches(chunk).then -> cb()
+    @_processBatches(chunk)
+    .then -> cb()
+    .catch (err) -> cb(err.body)

   _createProductDiscountFetchByNamePredicate: (discounts) ->
     names = _.map discounts, (d) =>


mark form initialized

mark form initialized

--- app/assets/javascripts/effective_orders/providers/stripe.js.coffee
+++ app/assets/javascripts/effective_orders/providers/stripe.js.coffee
@@ -8,7 +8,7 @@
     @card = null

   initialize: ->
-    @form = $('form[data-stripe-form]').first()
+    @form = $('form[data-stripe-form]:not(.initialized)').first()
     return false unless @form.length > 0

     @paymentIntent = @form.find("input[name$='[payment_intent_id]']").first()
@@ -17,6 +17,7 @@
     @card = @stripe.elements().create('card', @style())

     @mount()
+    @form.addClass('initialized')

   style: ->
     style: {


MC.Banner - Force play video when autoplay attr is set

--- app/assets/javascripts/martian_components/components/banner.coffee
+++ app/assets/javascripts/martian_components/components/banner.coffee
@@ -24,3 +24,6 @@
     video.on 'canplaythrough', =>
       MC.Utils.FullscreenIntoParent.perform(video)
       video.css 'margin-left', '0'
+
+    if video.attr('autoplay')
+      video.on 'canplay', => video[0].play()


Adds watch for credit card attributes to check validation after submission

--- app/assets/javascripts/sprangular/directives/creditCardForm.coffee
+++ app/assets/javascripts/sprangular/directives/creditCardForm.coffee
@@ -4,12 +4,22 @@
   scope:
     creditCard: '='
     disabled: '='
+    submitted: '='
   controller: ($scope, $locale) ->
     $scope.months = _.map $locale.DATETIME_FORMATS.MONTH, (month, index) ->
       {"index": index + 1, "name": month}

     currentYear = (new Date).getFullYear()
     $scope.years = [currentYear .. currentYear+15]
+    $scope.hasErrors = false
+
+    $scope.$watchGroup ['creditCard.number', 'creditCard.year', 'creditCard.month', 'creditCard.cvc'], ->
+      return unless $scope.submitted
+
+      creditCard = $scope.creditCard
+      creditCard.validate()
+      errors = creditCard.errors
+      $scope.hasErrors = errors && Object.keys(errors).length > 0

     $scope.$watch 'creditCard.number', (number) ->
       return unless number


ace: bind shortcut handlers to right scope

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -194,9 +194,9 @@
           cb  =
           switch key

-            when 'save'     then @requestSave
-            when 'saveAs'   then @requestSaveAs
-            when 'gotoLine' then @showGoToLine
+            when 'save'     then @requestSave.bind this
+            when 'saveAs'   then @requestSaveAs.bind this
+            when 'gotoLine' then @showGotoLine.bind this
             else
               if match = /^find$|^replace$/.exec key
                 replace = match.input is 'replace'


Fixed coffeescript syntax mistake

Fixed coffeescript syntax mistake

--- lib/supplier/plugins/assets.coffee
+++ lib/supplier/plugins/assets.coffee
@@ -41,9 +41,10 @@
         app = container.get "app"

         app.configure ->
-            app.use stylus.middleware
+            app.use stylus.middleware (
                 src: publicDirectory
                 compile: compilerFactory
+            )

             app.use jade publicDirectory
             app.use coffeescript publicDirectory


Only show uniq stop names

Only show uniq stop names


KD: @getSingleton -> KD.singleton

KD: @getSingleton -> KD.singleton

--- components/tooltip/KDTooltip.coffee
+++ components/tooltip/KDTooltip.coffee
@@ -31,7 +31,7 @@
     if @getOptions().animate then @setClass 'out' else @hide()

     @addListeners()
-    @getSingleton("windowController").on "ScrollHappened", @bound "hide"
+    KD.singleton("windowController").on "ScrollHappened", @bound "hide"

     @once "viewAppended", =>
       o = @getOptions()
@@ -63,7 +63,7 @@
     return unless @visible
     super
     @getDomElement().remove()
-    @getSingleton("windowController").removeLayer this
+    KD.singleton("windowController").removeLayer this
     @visible = no

   update:(o = @getOptions(), view = null)->
@@ -135,7 +135,7 @@

     # converts NESW-Values to topbottomleftright and retains them in @getOptions
     @appendToDomBody()
-    @getSingleton("windowController").addLayer this
+    KD.singleton("windowController").addLayer this
     o = @translateCompassDirections o if o.gravity
     o.gravity = null


Expose upload port

Expose upload port

--- nodejs/MicroFloComponent.coffee
+++ nodejs/MicroFloComponent.coffee
@@ -31,6 +31,9 @@
       graph:
         datatype: 'string'
         description: 'Path to a JSON or FBP graph definition'
+      upload:
+        datatype: 'boolean'
+        description: 'Whether to upload the graph or use the one flashed in'
     @outPorts = new noflo.OutPorts
       error:
         datatype: 'object'
@@ -38,6 +41,7 @@

     @inPorts.graph.on 'data', (graph) =>
       @loadGraph graph
+

     @inPorts.devicename.on 'data', (@devname) =>
       do @checkConnect


Explictly stop updates if the version is a SHA

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -79,11 +79,15 @@
     app.commandLine.appendSwitch 'js-flags', '--harmony_collections'

   checkForUpdates: ->
-    return if /\w{7}/.test @version # Don't check for updates if version is a short sha
-
-    autoUpdater.setAutomaticallyDownloadsUpdates true
-    autoUpdater.setAutomaticallyChecksForUpdates true
-    autoUpdater.checkForUpdatesInBackground()
+    versionIsSha = /\w{7}/.test @version
+
+    if versionIsSha
+      autoUpdater.setAutomaticallyDownloadsUpdates false
+      autoUpdater.setAutomaticallyChecksForUpdates false
+    else
+      autoUpdater.setAutomaticallyDownloadsUpdates true
+      autoUpdater.setAutomaticallyChecksForUpdates true
+      autoUpdater.checkForUpdatesInBackground()

   buildApplicationMenu: (version, continueUpdate) ->
     menus = []


Conflicts fixed github-credentials / working-on

 - I am Bob AND I am working on Foo conflict with each other
 - Credentials should follow a stricter match

--- src/scripts/github-credentials.coffee
+++ src/scripts/github-credentials.coffee
@@ -19,7 +19,7 @@

 module.exports = (robot) ->

-  robot.respond /who do you know/i, (msg) ->
+  robot.respond /who do you know(\?)?$/i, (msg) ->
     theReply = "Here is who I know:\n"

     for own key, user of robot.brain.data.users
@@ -28,12 +28,12 @@

     msg.send theReply

-  robot.respond /i am ([a-z0-9-]+)/i, (msg) ->
+  robot.respond /i am ([a-z0-9-]+)\s*$/i, (msg) ->
     githubLogin = msg.match[1]
     msg.message.user.githubLogin = githubLogin
     msg.send "Ok, you are " + githubLogin + " on GitHub"

-  robot.respond /who am i/i, (msg) ->
+  robot.respond /who am i\s*$/i, (msg) ->
     user = msg.message.user
     if user.githubLogin
       msg.reply "You are known as " + user.githubLogin + " on GitHub"


Layer

Layer

- Adjust Layer to margin need by Pointer. Covers most cases.

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -610,6 +610,30 @@
 				when "center"
 					layer_pos.top = dim_element.viewportCenterVertical - layer_pos.height / 2

+
+			if vp.dim_pointer
+				# now align the pointer within the available viewport
+				switch vp.pointer_align_horizontal
+					when "left"
+						pointer_pos.left = dim_element.viewportRight + vp.dim_pointer.marginLeft
+					when "right"
+						pointer_pos.left = dim_element.viewportLeft - vp.dim_pointer.borderBoxWidth - vp.dim_pointer.marginLeft
+					when "center"
+						pointer_pos.left = dim_element.viewportCenterHorizontal - vp.dim_pointer.borderBoxWidth / 2
+
+				switch vp.pointer_align_vertical
+					when "top"
+						pointer_pos.top = dim_element.viewportBottom + vp.dim_pointer.marginTop
+					when "bottom"
+						pointer_pos.top = dim_element.viewportTop - vp.dim_pointer.marginBoxHeight + vp.dim_pointer.marginTop
+					when "center"
+						pointer_pos.top = dim_element.viewportCenterVertical - vp.dim_pointer.borderBoxHeight / 2
+
+				pointer_pos.width = vp.dim_pointer.borderBoxWidth
+				pointer_pos.height = vp.dim_pointer.borderBoxHeight
+				pointer_pos.direction = vp.dim_pointer.direction
+
+
 			# move layer into viewport in case we overlap
 			if layer_pos.top < vp.top
 				layer_pos.top = vp.top
@@ -659,27 +683,52 @@
 							vp.overlap_height = Math.min(vp.window_right - layer_pos.right, overlap_width)
 							layer_pos.width = layer_pos.width + vp.overlap_width

-			if vp.dim_pointer
-				# now align the pointer within the available viewport
-				switch vp.pointer_align_horizontal
-					when "left"
-						pointer_pos.left = dim_element.viewportRight + vp.dim_pointer.marginLeft
-					when "right"
-						pointer_pos.left = dim_element.viewportLeft - vp.dim_pointer.borderBoxWidth - vp.dim_pointer.marginLeft
-					when "center"
-						pointer_pos.left = dim_element.viewportCenterHorizontal - vp.dim_pointer.borderBoxWidth / 2
-
-				switch vp.pointer_align_vertical
-					when "top"
-						pointer_pos.top = dim_element.viewportBottom + vp.dim_pointer.marginTop
-					when "bottom"
-						pointer_pos.top = dim_element.viewportTop - vp.dim_pointer.marginBoxHeight + vp.dim_pointer.marginTop
-					when "center"
-						pointer_pos.top = dim_element.viewportCenterVertical - vp.dim_pointer.borderBoxHeight / 2
-
-				pointer_pos.width = vp.dim_pointer.borderBoxWidth
-				pointer_pos.height = vp.dim_pointer.borderBoxHeight
-				pointer_pos.direction = vp.dim_pointer.direction
+
+			if @__pointer
+
+				layer_pos_right = vp.layer_pos.left + vp.layer_pos.width
+				layer_pos_bottom = vp.layer_pos.top + vp.layer_pos.height
+
+				pointer_pos_right = vp.pointer_pos.left + vp.pointer_pos.width
+				pointer_pos_bottom = vp.pointer_pos.top + vp.pointer_pos.height
+
+				# push layer further, so the pointer has enough margin to the
+				# edge of the layer
+				switch vp.pointer_pos.direction
+					when "n", "s"
+						marginLeft = vp.pointer_pos.left - vp.layer_pos.left
+						pushNeeded = marginLeft - vp.dim_pointer.marginLeft
+
+						if pushNeeded < 0
+							spaceAvailable = vp.layer_pos.left - vp.window_left
+							vp.push_left = Math.min(spaceAvailable, -pushNeeded)
+							vp.layer_pos.left = vp.layer_pos.left - vp.push_left
+
+						marginRight = layer_pos_right - pointer_pos_right
+						pushNeeded = marginRight - vp.dim_pointer.marginRight
+
+						if pushNeeded < 0
+							spaceAvailable = vp.window_right - layer_pos_right
+							vp.push_right = Math.min(spaceAvailable, -pushNeeded)
+							vp.layer_pos.left = vp.layer_pos.left + vp.push_right
+
+					when "e", "w"
+						marginTop = vp.pointer_pos.top - vp.layer_pos.top
+						pushNeeded = marginTop - vp.dim_pointer.marginTop
+
+						if pushNeeded < 0
+							spaceAvailable = vp.layer_pos.top - vp.window_top
+							vp.push_top = Math.min(spaceAvailable, -pushNeeded)
+							vp.layer_pos.top = vp.layer_pos.top - vp.push_top
+
+						marginBottom = layer_pos_bottom - pointer_pos_bottom
+						pushNeeded = marginBottom - vp.dim_pointer.marginBottom
+
+						if pushNeeded < 0
+							spaceAvailable = vp.window_bottom - layer_pos_bottom
+							vp.push_bottom = Math.min(spaceAvailable, -pushNeeded)
+							vp.layer_pos.top = vp.layer_pos.top + vp.push_bottom
+

 			if @_onPosition
 				# link dimensions, so the callback can
@@ -821,6 +870,10 @@
 		vp = vp_pl[placement]

 		# console.debug "Layer.position: Placement:", placement, "Wanted:", wanted_placement, "Allowed:", allowed_placements, "Viewports:", vp_pl, @
+
+		# console.info "PLACEMENT --- ", placement, "---"
+		# console.debug "Layer POS", vp.layer_pos, "align:", vp.align_horizontal, "/", vp.align_vertical, "overlap:", vp.overlap_align, vp.push_left, vp.push_right
+		# console.debug "Pointer POS", vp.pointer_pos, "align:", vp.pointer_align_horizontal, "/", vp.pointer_align_vertical

 		# set layer
 		CUI.DOM.setStyle @__layer.DOM,


fixed bug in largest rectangle when polygon is singular (has regions of zero height or width)

--- src/geom/largestRect.coffee
+++ src/geom/largestRect.coffee
@@ -89,7 +89,7 @@

   ########################################
-  area = d3.geom.polygon(poly).area()
+  area = Math.abs(d3.geom.polygon(poly).area()) # take absolute value of the signed area
   # get the width of the bounding box of the original polygon to determine tolerance
   [minx, maxx] = d3.extent poly, (d) -> d[0]
   [miny, maxy] = d3.extent poly, (d) -> d[1]
@@ -136,10 +136,10 @@
       # generate improved origins
       [p1W, p2W] = intersectPoints poly, origOrigin, angleRad
       [p1H, p2H] = intersectPoints poly, origOrigin, angleRad + Math.PI/2
-      modifOrigins = [
-        [(p1W[0] + p2W[0])/2, (p1W[1] + p2W[1])/2], # average along with width axis
-        [(p1H[0] + p2H[0])/2, (p1H[1] + p2H[1])/2] # average along with height axis
-      ]
+      modifOrigins = []
+      if p1W? and p2W? then modifOrigins.push [(p1W[0] + p2W[0])/2, (p1W[1] + p2W[1])/2] # average along with width axis
+      if p1H? and p2H? then modifOrigins.push [(p1H[0] + p2H[0])/2, (p1H[1] + p2H[1])/2] # average along with height axis
+
       if options.vdebug then events.push type: 'modifOrigin', idx: i, p1W: p1W, p2W: p2W, p1H: p1H, p2H: p2H, modifOrigins: modifOrigins
       for origin in modifOrigins
         if options.vdebug then events.push type: 'origin', cx: origin[0], cy: origin[1]


#44 progress

#44 progress

--- src/server/models/RebuildCache.coffee
+++ src/server/models/RebuildCache.coffee
@@ -1,17 +1,76 @@
+fs = require('fs')
 db = require('./db')
+GAME_RESULT = require('./constants').GAME_RESULT
+PLAYER_ROLES = require('./constants').PLAYER_ROLES
+rating = require('./rating_formula_342')
 Sequelize = require('sequelize')

 module.exports = (done)->
   db.Player.all().success((players)->
-    chainer = new Sequelize.Utils.QueryChainer
+    commonStat = []
     for player in players
+      cachedPlayer = {
+        "name": null
+        "rating": 0.0
+        "winsCitizen": 0
+        "winsSheriff": 0
+        "winsMafia": 0
+        "winsDon": 0
+        "gamesCitizen": 0
+        "gamesSheriff": 0
+        "gamesMafia": 0
+        "gamesDon": 0
+        "likes": 0
+        "bestPlayer": 0
+        "bestMoveAccuracy": 0.0
+        "firstKilledNight": 0
+        "firstKilledDay": 0
+        "fouls": 0
+      }
+      cachedPlayer["name"] = player.name
+      cachedPlayer["rating"] = 0.0
+      bestMoveAttempts = 0
+      foundedMafiaOnBestMove = 0
+      chainer = new Sequelize.Utils.QueryChainer
       chainer.add(db.PlayerGame.findAll({where: {PlayerId: player.id}}))
+      chainer.run().success((results)->
+        playerGames = results[0]
+        for playerGame in playerGames
+          chainer2 = new Sequelize.Utils.QueryChainer
+          chainer2.add(playerGame.getGame())
+          chainer2.run().success((game)->
+            cachedPlayer["rating"] += rating(playerGame, game.result)
+            cachedPlayer["winsCitizen"] += 1 if game.result == GAME_RESULT.CITIZENS_WIN and playerGame.role == PLAYER_ROLES.CITIZEN
+            cachedPlayer["winsSheriff"] += 1 if game.result == GAME_RESULT.CITIZENS_WIN and playerGame.role == PLAYER_ROLES.SHERIFF
+            cachedPlayer["winsMafia"] += 1 if game.result == GAME_RESULT.MAFIA_WIN and playerGame.role == PLAYER_ROLES.MAFIA
+            cachedPlayer["winsDon"] += 1 if game.result == GAME_RESULT.MAFIA_WIN and playerGame.role == PLAYER_ROLES.DON
+          ).error((err)->
+            done(err, null)
+          )
+          cachedPlayer["gamesCitizen"] += 1 if playerGame.role == PLAYER_ROLES.CITIZEN
+          cachedPlayer["gamesSheriff"] += 1 if playerGame.role == PLAYER_ROLES.SHERIFF
+          cachedPlayer["gamesMafia"] += 1 if playerGame.role == PLAYER_ROLES.MAFIA
+          cachedPlayer["gamesDon"] += 1 if playerGame.role == PLAYER_ROLES.DON
+          cachedPlayer["likes"] += playerGame.likes
+          cachedPlayer["bestPlayer"] += 1 if playerGame.is_best
+          cachedPlayer["firstKilledNight"] += 1 if playerGame.is_killed_first_at_night
+          cachedPlayer["firstKilledDay"] += 1 if playerGame.is_killed_first_by_day
+          cachedPlayer["fouls"] += playerGame.fouls
+          bestMoveAttempts += 1 if playerGame.took_best_move
+          foundedMafiaOnBestMove += playerGame.best_move_accuracy if playerGame.took_best_move

-    chainer.run()
-    .success((playerHistories)->
-      done(null, playerHistories)
-    ).error((err)->
-      done(err, null)
+        if bestMoveAttempts > 0
+          cachedPlayer["bestMoveAccuracy"] = foundedMafiaOnBestMove / bestMoveAttempts
+        commonStat.push(cachedPlayer)
+        console.log(cachedPlayer)
+      ).error((err)->
+        done(err, null)
+      )
+    fs.writeFile("#{__dirname}/../../static/common_stat_responce.json", JSON.stringify(commonStat), (err)->
+      if err
+        done(err, null)
+      else
+        done(null, commonStat)
     )
   ).error((err)->
     done(err, null)


Remove old line drawing code.

Remove old line drawing code.

--- source/javascripts/elements/line.coffee
+++ source/javascripts/elements/line.coffee
@@ -73,61 +73,3 @@
     @line.set("x#{target.point_index}", target.left)
     @line.set("y#{target.point_index}", target.top)
     @updateArrowAngle()
-
-
-# Old functions for drawing lines:
-
-
-# lineMouseDown = (e) ->
-#   position = $jq_canvas.offset()
-#   x1 = e.e.pageX - position.left
-#   y1 = e.e.pageY - position.top
-
-#   $line = new fabric.Line [x1, y1, x1, y1], stroke: "blue", strokeWidth: 5
-
-#   canvas.add($line)
-#   canvas.renderAll()
-
-#   $trackingLine = true
-#   canvas.selection = false
-#   canvas.on "mouse:up", lineMouseUp
-#   canvas.on "mouse:move", lineMouseMove
-
-# lineMouseUp = (e) ->
-#   return unless $trackingLine
-#   position = $jq_canvas.offset()
-#   x2 = e.e.pageX - position.left
-#   y2 = e.e.pageY - position.top
-
-#   $line.set("x2", x2).set("y2", y2)
-#   canvas.renderAll()
-
-#   canvas.off "mouse:up", lineMouseUp
-#   canvas.off "mouse:move", lineMouseMove
-#   $trackingLine = false
-#   canvas.selection = true
-
-# lineMouseMove = (e) ->
-#   return unless $trackingLine
-
-#   position = $jq_canvas.offset()
-#   x2 = e.e.pageX - position.left
-#   y2 = e.e.pageY - position.top
-
-#   $line.set("x2", x2).set("y2", y2)
-#   canvas.renderAll()
-
-# lineMouseDown = (e) ->
-#   position = $jq_canvas.offset()
-#   x1 = e.e.pageX - position.left
-#   y1 = e.e.pageY - position.top
-
-#   $line = new fabric.Line [x1, y1, x1, y1], stroke: "blue", strokeWidth: 5
-
-#   canvas.add($line)
-#   canvas.renderAll()
-
-#   $trackingLine = true
-#   canvas.selection = false
-#   canvas.on "mouse:up", lineMouseUp
-#   canvas.on "mouse:move", lineMouseMove


Master: Persist StreamGroup sources across handoffs

--- src/streammachine/master/index.coffee
+++ src/streammachine/master/index.coffee
@@ -290,11 +290,39 @@
     sendHandoffData: (rpc,cb) ->
         streams_sent = {}

-        fFunc = _.after Object.keys(@streams).length, =>
+        fFunc = _.after (Object.keys(@streams).length+Object.keys(@stream_groups).length), =>
             @log.info "Rewind buffers and sources sent."
             cb null

-        rpc.on "rewind_buffer", (msg,handle,cb) =>
+        # -- Stream Group Sources -- #
+
+        # no rewind buffers, only sources
+
+        rpc.on "group_sources", (msg,handle,cb) =>
+            @log.info "StreamGroup sources requested for #{ msg.key }"
+
+            sg = @stream_groups[ msg.key ]
+
+            af = _.after sg._stream.sources.length, =>
+                fFunc()
+                cb null
+
+            for source in sg._stream.sources
+                if source._shouldHandoff
+                    do (source) =>
+                        rpc.request "group_source",
+                            group:      sg.key
+                            type:       source.HANDOFF_TYPE
+                            opts:       format:source.opts.format, uuid:source.uuid
+                        , source.opts.sock
+                        , (err,reply) =>
+                            @log.error "Error sending group source #{msg.key}/#{source.uuid}: #{err}" if err
+                            af()
+
+
+        # -- Stream Rewind Buffers and Sources -- #
+
+        rpc.on "stream_rewind", (msg,handle,cb) =>
             @log.info "Rewind buffer requested for #{ msg.key }"
             stream = @streams[msg.key]

@@ -302,8 +330,6 @@
             for source in stream.sources
                 if source._shouldHandoff
                     do (source) =>
-                        meta =
-
                         rpc.request "stream_source",
                             stream:     stream.key
                             type:       source.HANDOFF_TYPE
@@ -345,10 +371,25 @@
             stream = @streams[ msg.stream ]
             source = new (require "../sources/#{msg.type}") _.extend {}, msg.opts, sock:handle, logger:stream.log
             stream.addSource source
-            @log.info "Added source: #{stream.key}/#{source.uuid}"
-
-        af = _.after Object.keys(@streams).length, =>
+            @log.info "Added stream source: #{stream.key}/#{source.uuid}"
+
+        rpc.on "group_source", (msg,handle,cb) =>
+            sg = @stream_groups[ msg.group ]
+            source = new (require "../sources/#{msg.type}") _.extend {}, msg.opts, sock:handle, logger:stream.log
+            sg._stream.addSource source
+            @log.info "Added group source: #{stream.key}/#{source.uuid}"
+
+        af = _.after (Object.keys(@streams).length+Object.keys(@stream_groups).length), =>
             cb null
+
+        for key,group of @stream_groups
+            do (key,group) =>
+                # we don't need rewind buffers for stream groups, but we do
+                # need sources.
+                rpc.request "group_sources", key:key, (err) =>
+                    @log.error "Error getting StreamGroup sources: #{err}" if err
+                    @log.info "Sources received for StreamGroup #{ key }."
+                    af()

         for key,stream of @streams
             do (key,stream) =>
@@ -366,7 +407,7 @@
                             @log.error "Error loading rewind buffer: #{err}" if err
                             c.end()

-                    rpc.request "rewind_buffer", key:key,path:spath, (err) =>
+                    rpc.request "stream_rewind", key:key,path:spath, (err) =>
                         return @log.error "Error loading rewind buffer for #{key}: #{err}" if err
                         @log.info "Rewind buffer loaded for #{key}"


Make string replacing more specific to avoid possible issues

--- src/views/unit-details.coffee
+++ src/views/unit-details.coffee
@@ -231,7 +231,7 @@
                 _.max(x, (y) -> p13n.getTranslatedAttr(y.name)?.length)

             data.periods = @_serviceDetailsToPeriods data.services
-            data.data_source = data.data_source?.replace("www.", "")
+            data.data_source = data.data_source?.replace(/^www./, "")
             data

         renderEvents: (events) ->


sidebar: make managed widget to work w/ sidebar ctrl

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/sidebar/components/managedmachinewidget.coffee
+++ client/app/lib/sidebar/components/managedmachinewidget.coffee
@@ -1,9 +1,6 @@
 kd = require 'kd'
 React = require 'app/react'
 SidebarWidget = require './widget'
-EnvironmentFlux = require 'app/flux/environment'
-KDReactorMixin = require 'app/flux/base/reactormixin'
-actions = require 'app/flux/environment/actions'

 PROVIDERS         =
@@ -23,35 +20,19 @@
   @defaultProps =
     className : 'ConnectedManagedVm sidebar-info-modal'

-  getDataBindings: ->
-    connectedManagedMachine: EnvironmentFlux.getters.connectedManagedMachine
+  onClose: ->
+
+    kd.singletons.sidebar.removeManaged @props.machine.getId()

-  onClose: ->
+  onButtonClick: ->

-    kd.utils.defer =>
-      actions.hideManagedMachineAddedModal @props.machine.get '_id'
-
-
-  handleButtonClick: ->
-
-    { router } = kd.singletons
-    currentPath = router.getCurrentPath()
-    newPath = "/IDE/#{@props.machine.slug}"
+    kd.singletons.router.handleRoute newPath

     @onClose()
-    router.handleRoute newPath  unless newPath is currentPath

   render: ->
-
-    connectedMachine = @state.connectedManagedMachine.get @props.machine.get('_id')
-
-    return null  unless connectedMachine
-
-    provider = if PROVIDERS[connectedMachine.providerName]
-    then connectedMachine.providerName
-    else 'UnknownProvider'

     <SidebarWidget {...@props} onClose={@bound 'onClose'}>

@@ -72,13 +53,10 @@

           <button
             className='GenericButton'
-            onClick={@bound 'handleButtonClick'}
+            onClick={@bound 'onButtonClick'}
             children='START CODING' />

         </div>
       </div>

     </SidebarWidget>
-
-
-ManagedMachineWidget.include [KDReactorMixin]


teams: welcome: other members improved

teams: welcome: other members improved

--- client/landing/site.landing/coffee/team/tabs/teamwelcometab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamwelcometab.coffee
@@ -47,6 +47,9 @@
           success : -> formData.alreadyMember = no; go()
           error   : -> formData.alreadyMember = yes; go()

+    @membersDesc = new KDCustomHTMLView
+      tagName: 'span'
+
     @decorateTeamMembers()

@@ -58,6 +61,11 @@
     KD.utils.fetchTeamMembers { name, token }, (err, members) =>

       return  if err or not members
+
+      if memberCount = KD.config.group.counts?.members
+        @membersDesc.updatePartial "#{memberCount} of your team members are
already here."
+      else
+        @membersDesc.updatePartial "@#{members.first.profile.nickname} has invited you to join."

       @$('.team-members ul').append createAvatar profile  for { profile } in members
       @$('.team-members').removeClass 'hidden'
@@ -72,7 +80,7 @@

You are about to join to #{KD.config.group.title} on Koding

-        and 34 of your team members are already here.
+        {{> @membersDesc}}

         Your invitation was sent to:


Fix indentation on test

Fix indentation on test

--- test/classes.coffee
+++ test/classes.coffee
@@ -119,22 +119,22 @@

 test "super with plain ol' functions as the original constructors", ->

-TopClass = (arg) ->
-  @prop = 'top-' + arg
-  this
-
-SuperClass = (arg) ->
-  super 'super-' + arg
-  this
-
-SubClass = ->
-  super 'sub'
-  this
-
-SuperClass extends TopClass
-SubClass extends SuperClass
-
-ok (new SubClass).prop is 'top-super-sub'
+  TopClass = (arg) ->
+    @prop = 'top-' + arg
+    this
+
+  SuperClass = (arg) ->
+    super 'super-' + arg
+    this
+
+  SubClass = ->
+    super 'sub'
+    this
+
+  SuperClass extends TopClass
+  SubClass extends SuperClass
+
+  ok (new SubClass).prop is 'top-super-sub'

 test "'@' referring to the current instance, and not being coerced into a call", ->


test for invalid command behaviour

test for invalid command behaviour

--- test/decking.coffee
+++ test/decking.coffee
@@ -1,5 +1,8 @@
-expect = require("chai").expect
-sinon = require "sinon"
+chai   = require "chai"
+expect = chai.expect
+sinon  = require "sinon"
+
+chai.use require("sinon-chai")

 Decking = require "../src/decking"

@@ -87,3 +90,13 @@

         it "should invoke the help method", ->
           expect(@stub).to.have.been.called
+
+    describe "with an unknown command", ->
+      beforeEach ->
+        try
+          @execute "foo"
+        catch e
+          @e = e
+
+      it "throws the expected error", ->
+        expect(@e.message).to.eql "Unknown method foo"


Fix ORA weight attribute.

Fix ORA weight attribute.

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -163,6 +163,7 @@
     @accept_file_upload = @coe.data('accept-file-upload')
     @location = @coe.data('location')
     @data_score = @coe.data('score')
+    @data_weight = @coe.data('weight')
     # set up handlers for click tracking
     @rub = new Rubric(@coe)
     @rub.initialize(@location)


Fixed parameter name typo in the virtual test CPE.

--- test/methods.coffee
+++ test/methods.coffee
@@ -14,7 +14,7 @@
   inform = body.node('cwmp:Inform')

   deviceId = inform.node('DeviceId')
-  deviceId.node('Manfucturer', device['InternetGatewayDevice.DeviceInfo.Manufacturer'][1])
+  deviceId.node('Manufacturer', device['InternetGatewayDevice.DeviceInfo.Manufacturer'][1])
   deviceId.node('OUI', device['InternetGatewayDevice.DeviceInfo.ManufacturerOUI'][1])
   deviceId.node('ProductClass', device['InternetGatewayDevice.DeviceInfo.ProductClass'][1])
   deviceId.node('SerialNumber', device['InternetGatewayDevice.DeviceInfo.SerialNumber'][1])


fix old coffeescript tests syntax

fix old coffeescript tests syntax

--- test/offside.coffee
+++ test/offside.coffee
@@ -20,7 +20,7 @@
 		spaces_and_tab:
 			'  \t'

-	block:
+	block: [
 		['head line'],
 		['head line', '\tbody line 1', '\tbody line 2'],
 		['\thead line', '\t\tbody line 1', '\t\tbody line 2'],
@@ -63,6 +63,7 @@
 			'\tline',
 			'\tline',
 		],
+	]
 	source:
 		require('fs').readFileSync(require.resolve('./offside.txt')).
 			toString().split(/(?:^|\r?\n)\r?\n?={4}.*\r?\n?(?:\r?\n|$)/)


:lemon: fixing bug when torrent is null

:lemon: fixing bug when torrent is null


Add more keywords to Tree-Sitter grammar

--- grammars/tree-sitter/tree-sitter-kotlin.cson
+++ grammars/tree-sitter/tree-sitter-kotlin.cson
@@ -21,8 +21,10 @@
 		"break",
 		"continue",
 		"return",
-		"for"
+		"for",
+		"package"
 	''': 'keyword.control.kotlin'
+	'"import"': 'keyword.control.import.kotlin'
 	'''
 		"catch",
 		"finally",
@@ -66,6 +68,7 @@
 	'"fun"': 'storage.type.function.kotlin'
 	'"object"': 'storage.type.object.kotlin'
 	'"class"': 'storage.type.class.kotlin'
+	'"interface'": 'storage.type.interface.kotlin'
 	'"typealias"': 'storage.type.alias.kotlin'
 	'''
 		"val",


Fixed the "Copy" bug.

Fixed the "Copy" bug.

--- stage/src/scripts/main.coffee
+++ stage/src/scripts/main.coffee
@@ -163,7 +163,7 @@

   prepareLabel: (e) ->
     $el = $(e.currentTarget).find("input").eq(0)
-    $el.val("") if $el.val() == Formbuilder.options.dict.DEFAULT_LABEL
+    $el.val("") if $el.val().indexOf("\x1e") > -1

 class BuilderView extends Backbone.View
   SUBVIEWS: []


Simplified hex encoding using `toString(16)`.

--- Base64ToHexDynamicValue.coffee
+++ Base64ToHexDynamicValue.coffee
@@ -1,8 +1,6 @@
 require "b64.js"

 Base64ToHexDynamicValue = () ->
-
-    @hexDigits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]

     @createHexString = (byteArray) ->
         result = ""
@@ -10,7 +8,7 @@
         for byte, idx in byteArray
             d1 = byte % 16
             d2 = (byte - d1) / 16
-            result += "#{ @hexDigits[d2] }#{ @hexDigits[d1] }"
+            result += "#{ d2.toString(16) }#{ d1.toString(16) }"

         return result


Dia: Active Passive States

Dia: Active Passive States

--- client/Framework/components/dia/kddiascene.coffee
+++ client/Framework/components/dia/kddiascene.coffee
@@ -9,29 +9,34 @@
     options.lineWidth         ?= 2
     options.lineColor        or= "#ccc"
     options.lineColorActive  or= "orange"
-    options.lineColorPassive or= "#dedede"
+    options.lineColorPassive or= "red"
     options.lineColorHelper  or= "green"
     options.lineDashes        ?= []
     options.curveDistance     ?= 50

     super

-    @containers   = []
-    @connections  = []
-    @activeDias   = []
-    @activeJoints = []
-    @passiveDias  = []
+    @containers    = []
+    @connections   = []
+    @activeDias    = []
+    @activeJoints  = []
+    @passiveDias   = []
+    @passiveJoints = []

   diaAdded:(container, diaObj)->
     diaObj.on "JointRequestsLine", @bound "handleLineRequest"
     diaObj.on "DragInAction",   => @setActiveDia diaObj

+  passivateLines:()->
+
+
   addContainer:(container, pos = {})->
     @addSubView container

     container.on "NewDiaObjectAdded", @bound "diaAdded"
     container.on "DragInAction",      @bound "updateScene"
     container.on "HighlightDia",      @bound "setActiveDia"
+    container.on "PassivateLines",    @bound "setPassiveDia"

     @containers.push container

@@ -118,6 +123,9 @@
               joint.on 'DeleteRequested', @bound 'disconnectHelper'
               @activeJoints.push joint

+  setPassiveDia:()->
+
+
   deselectAllDias:->
     joint.off 'DeleteRequested'      for joint in @activeJoints
     container.emit 'UnhighlightDias' for container in @containers
@@ -165,6 +173,9 @@
         @realContext.strokeStyle = @getOption 'lineColorActive'
       else
         @realContext.strokeStyle = @getOption 'lineColor'
+
+      if (source.dia in @passiveDias) or (target.dia in @passiveDias)
+        @realContext.strokeStyle = @getOption 'lineColorPassive'

       sJoint = source.dia.getJointPos source.joint
       tJoint = target.dia.getJointPos target.joint


Speed up client-side locale tests a lot, like several minutes to instant

--- test/app/core/locale.spec.ozar.coffee
+++ test/app/core/locale.spec.ozar.coffee
@@ -36,16 +36,20 @@
 #               """

 # describe 'Check keys', ->
+#   enKeysFlattened = _.flatten (section + '.' + key for key of english.translation[section] for section of english.translation)
 #   langs.forEach (language) =>
+#     langKeysFlattened = _.flatten (section + '.' + key for key of language.translation[section] for section of language.translation)
+#     diff = _.difference langKeysFlattened, enKeysFlattened
 #     describe "when language is #{language.englishDescription}", ->
-#       en = english.translation
-#       Object.keys(language.translation or {}).forEach (key) ->
-#         Object.keys(language.translation[key] or {}).forEach (keyChild) ->
-#           it 'should have the same keys in each entry as in English', ->
-#             if en[key][keyChild] == undefined
-#               return fail """
-#                 Expected english to have translation '#{key}.#{keyChild}'
-#                 This can occur when:
-#                   * Parent key for '#{keyChild}' is accidentally commented.
-#                   * English translation for '#{key}.#{keyChild}' has been deleted.
-#               """
+#       it 'should have the same keys in each entry as in English', ->
+#         if diff.length
+#           diff.slice(0, 100).forEach (key) ->
+#             return fail """
+#               Expected english to have translation '#{key}'
+#               This can occur when:
+#                 * Parent key for '#{key.split('.')[0]}' is accidentally commented.
+#                 * English translation for '#{key}' has been deleted.
+#               You may need to run copy-i18n-tags.js
+#             """
+#         else
+#           expect(diff.length).toBe(0)


Remove click event test due to phantomjs suckage

Phantomjs doesn't support HTMLElement.click(), and
document.createEvent('MouseEvent') and dispatch doesn't seem to buble :(

--- test/components/scroll-to.spec.coffee
+++ test/components/scroll-to.spec.coffee
@@ -30,16 +30,6 @@
     Testing.renderComponent( TestComponent, props: @props ).then =>
       expect(@props.windowImpl.scroll).to.have.been.called

-  it 'scrolls when a link is clicked', (done) ->
-    Testing.renderComponent( TestComponent, props: @props ).then ({dom}) =>
-      expect(@props.windowImpl.scroll).not.to.have.been.called
-      # Testing.actions.click() isn't used because the mixin uses plain DOM events
-      dom.querySelector('a').click()
-      _.delay( =>
-        expect(@props.windowImpl.scroll).to.have.been.called
-        done()
-      , 4)
-
   it 'scrolls to an element', ->
     Testing.renderComponent( TestComponent, props: @props ).then ({dom, element}) =>
       element.scrollToElement(dom.querySelector('#foo'))


Fix output from fullscale component

Fix output from fullscale component

--- components/ToFullscale.coffee
+++ components/ToFullscale.coffee
@@ -105,15 +105,14 @@
     superagent.head newUrl
     .redirects(1)
     .end (err, res) ->
-      return callback err if err
-      # If the newUrl exists, send it
-      if res and res.statusCode is 200
-        # Use redirection URL
-        if res.redirects?.length > 0
-          newUrl = tryRedirect url, res.redirects[0]
-        out.send newUrl
-      # Otherwise, keep the original one
-      else
+      # If the response is not 200, send the original URL
+      unless res and res.statusCode is 200
         out.send url
-      callback null
+        do callback
+        return
+      # Use redirection URL
+      if res.redirects?.length > 0
+        newUrl = tryRedirect url, res.redirects[0]
+      out.send newUrl
+      do callback
   c


Verify if url exists for all replacements

--- components/ToFullscale.coffee
+++ components/ToFullscale.coffee
@@ -51,16 +51,7 @@
   return URI.build parts

 tryFindingFullscale = (url, out, callback) ->
-  # Convert
-  newUrl = url
-  fullUrl = url.replace /[-_](small|thumbnail|thumb|tm)/, ''
-  # Verify that it exists
-  superagent.head fullUrl
-  .end (err, res) ->
-    return callback err if err
-    newUrl = fullUrl if res and res.statusCode is 200
-    out.send newUrl
-    callback null
+  return url.replace /[-_](small|thumbnail|thumb|tm)/, ''

 exports.getComponent = ->
   c = new noflo.Component
@@ -101,7 +92,7 @@
       newUrl = convertGravatar url, callback

     if url.match /[-_](small|thumb)/
-      return tryFindingFullscale url, out, callback
+      newUrl = tryFindingFullscale url, callback

     # Verify that the newUrl exists
     superagent.head newUrl


Helper for determining retina

Helper for determining retina

--- components/util/device.coffee
+++ components/util/device.coffee
@@ -9,3 +9,6 @@

   autofocus: ->
     if @isTouchDevice() then undefined else true
+
+  isRetina: ->
+    (window.devicePixelRatio or 1) > 1


manually applied #458

manually applied #458

--- src/main/coffeescript/view/OperationView.coffee
+++ src/main/coffeescript/view/OperationView.coffee
@@ -109,7 +109,7 @@
     contentTypeModel.produces = @model.produces

     for param in @model.parameters
-      type = param.type || param.dataType
+      type = param.type || param.dataType || ''
       if typeof type is 'undefined'
         schema = param.schema
         if schema and schema['$ref']


test hookup order

test hookup order

--- coffeescript/004-webworkers/multiplication.coffee
+++ coffeescript/004-webworkers/multiplication.coffee
@@ -10,12 +10,12 @@
     define [
       'underscore'
     ], (_) ->
-        global.postMessage('before binding')
         global.onmessage = (e) ->
-            postMessage('hello from worker!')
+            global.postMessage('hello from worker!')
             result = _.reduce(_.values(e.data), ((memo, elem) -> memo * elem), 1)
-            postMessage(result)
+            global.postMessage(result)
             false
+        global.postMessage('after binding')
         false
 )()


Indentation

Indentation

--- text-fade-in.coffee
+++ text-fade-in.coffee
@@ -11,7 +11,7 @@
 ###

 # Coffeescript compile command: coffee --compile text-fade-in.coffee
-# Uglify command: uglifyjs text-fade-in.js --mangle --compress --comments '/!/' --output text-fade-in.min.js
+# Uglify command:               uglifyjs text-fade-in.js --mangle --compress --comments '/!/' --output text-fade-in.min.js
 class TextFadeIn

   BLANK_REPLACE_REGEX = /[^\n]/g