createColumsn works

createColumsn works

--- src/driver/driverUtil.coffee
+++ src/driver/driverUtil.coffee
@@ -154,7 +154,12 @@
     return header + '\r\n' + content

 exports.createColumns = createColumns = (query) ->
-  split = query.filter((op) -> op.operation is 'split').map((op) -> op.name)
+  split = flatten(query.filter((op) -> op.operation is 'split').map((op) ->
+    if op.bucket is 'tuple'
+      return op.splits.map((o) -> o.name)
+    else
+      return [op.name]
+  ))
   tempApply = query.filter((op) -> op.operation is 'apply').map((op) -> op.name)
   apply = []
   for applyName in tempApply


不要なコードを削除

不要なコードを削除

--- src/coffee/background.coffee
+++ src/coffee/background.coffee
@@ -854,8 +854,6 @@
         data.description or= c.description if c.description
         LOGGER.log "Use cache #{@id}", data
         return true
-    data = null
-    cache = null
     return false

   fetchDetailSuccess: (index, results) ->


engine_save_state

--- src/engine_save_state.coffee
+++ src/engine_save_state.coffee
@@ -18,9 +18,9 @@
   Save the current game state and returns a JSON object representing that state.

-    engine.bind 'step', ->
-      if justPressed.s
-        engine.saveState()
+  engine.bind 'step', ->
+    if justPressed.s
+      engine.saveState()

   @name saveState
@@ -33,6 +33,16 @@

   ###*
   Loads the game state passed in, or the last saved state, if any.
+
+

+  engine.bind 'step', ->
+    if justPressed.l
+      # loads the last saved state
+      engine.loadState()
+
+    if justPressed.o
+      engine.loadState([{x: 40, y: 50, class: "Player"}, {x: 0, y: 0, class: "Enemy"}, {x: 500, y: 400, class: "Boss"}])
+

   @name loadState
   @methodOf Engine.SaveState#


Refine tip logging.

Refine tip logging.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -193,9 +193,11 @@
     # available, otherwise manually at hopefully 60fps.
     _bindTrigger: ($trigger) ->
       $trigger.on @evt('truemouseenter'), (event) =>
-        @debugLog event
+        @debugLog event.type
         @_onTriggerMouseMove event
-      $trigger.on @evt('truemouseleave'), (event) => @sleepByTrigger $trigger
+      $trigger.on @evt('truemouseleave'), (event) =>
+        @debugLog event.type
+        @sleepByTrigger $trigger
       if @doFollow is on
         if window.requestAnimationFrame?
           onMouseMove = (event) =>
@@ -304,6 +306,7 @@
       triggerHeight   = $trigger.outerHeight()
       tipSize         = @sizeForTrigger $trigger
       newDirection    = _.clone @defaultDirection
+      @debugLog { triggerPosition, triggerWidth, triggerHeight, tipSize }
       for component in @defaultDirection
         if not @_bounds? then @_setBounds()
         ok = yes
@@ -312,7 +315,7 @@
           when 'east'  then ok = (edge = triggerPosition.left + tipSize.width) and @_bounds.right > edge
           when 'north' then ok = (edge = triggerPosition.top - tipSize.height) and @_bounds.top < edge
           when 'west'  then ok = (edge = triggerPosition.left - tipSize.width) and @_bounds.left < edge
-        @debugLog 'checkDirectionComponent', "'#{$trigger.html()}'", component, edge, tipSize
+        @debugLog 'checkDirectionComponent', { component, edge }
         if not ok
           switch component
             when 'south' then newDirection[0] = 'north'


Revert multiline regex changes to constants


return if textChanged fixes documentationView not disappearing

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -92,7 +92,7 @@
   registerEvents: ->
     @disposables.push atom.workspace.observeTextEditors (editor) =>
       @disposables.push editor.onDidChangeCursorPosition (event) =>
-        return unless event.textChanged
+        return if event.textChanged
         @documentationView.hide()
     @disposables.push atom.workspace.onDidChangeActivePaneItem =>
       @documentationView.hide()


Fix word spacing

Fix word spacing

We were mutating a cached position object. Fixes #687.

--- lib/mixins/text.coffee
+++ lib/mixins/text.coffee
@@ -252,7 +252,11 @@
         positions.push positionsWord...

         # add the word spacing to the end of the word
-        positions[positions.length - 1].xAdvance += wordSpacing
+        # clone object because of cache
+        space = {}
+        space[key] = val for key, val of positions[positions.length - 1]
+        space.xAdvance += wordSpacing
+        positions[positions.length - 1] = space
     else
       [encoded, positions] = @_font.encode(text, options.features)


add underlineColor and url option to doc.text (to support word wrapping)

--- lib/mixins/text.coffee
+++ lib/mixins/text.coffee
@@ -102,6 +102,7 @@
         indent = (wrap.firstLine and options.indent) or 0
         wordSpacing = options.wordSpacing or 0
         characterSpacing = options.characterSpacing or 0
+        usedWidth = @widthOfString(text)

         # text alignments
         if options.width
@@ -109,10 +110,10 @@

             switch align
                 when 'right'
-                    x += lineWidth - @widthOfString(text)
+                    x += lineWidth - usedWidth

                 when 'center'
-                    x += lineWidth / 2 - @widthOfString(text) / 2
+                    x += lineWidth / 2 - usedWidth / 2

                 when 'justify'
                     # don't justify the last line of paragraphs
@@ -172,6 +173,11 @@
         state.mode = mode
         state.wordSpacing = wordSpacing

+        if options.underlineColor and options.width
+            @underline @x, @y, usedWidth, @currentLineHeight(false), color: options.underlineColor
+        if options.url and options.width
+            @link @x, @y, usedWidth, @currentLineHeight(false), options.url
+
     _wrap: (text, options) ->
         wrap = @_wrapState
         width = @widthOfString.bind this


add an optional param for search commands

--- lib/phabricator.coffee
+++ lib/phabricator.coffee
@@ -76,6 +76,7 @@
     return new Promise (res, err) =>
       query['api.token'] = process.env.PHABRICATOR_API_KEY
       body = querystring.stringify(query)
+      # console.log body
       @robot.http(process.env.PHABRICATOR_URL)
         .path("api/#{endpoint}")
         .get(body) (error, result, payload) ->
@@ -374,22 +375,22 @@
     query = { 'names[]': name }
     @request query, 'phid.lookup'

-  searchTask: (phid, terms) ->
+  searchTask: (phid, terms, limit = 3) ->
     query = {
       'constraints[fulltext]': terms,
       'constraints[statuses][0]': 'open',
       'constraints[projects][0]': phid,
       'order': 'newest',
-      'limit': '3'
+      'limit': limit
     }
     @request query, 'maniphest.search'

-  searchAllTask: (phid, terms) ->
+  searchAllTask: (phid, terms, limit = 3) ->
     query = {
       'constraints[fulltext]': terms,
       'constraints[projects][0]': phid,
       'order': 'newest',
-      'limit': '3'
+      'limit': limit
     }
     @request query, 'maniphest.search'


refactor

refactor

--- lib/tag-matcher.coffee
+++ lib/tag-matcher.coffee
@@ -1,24 +1,27 @@
 class TagMacher
+  startRegex: /\S/
+  endRegex: /\S(\s+)?$/
+
   constructor: (editor) ->
     @editor = editor

-  lineStartsWithOpeningTag: (bufferRow) ->
-    if match = @editor.lineTextForBufferRow(bufferRow).match(/\S/)
+  lineStartsWithOpeningTag: (bufferLine) ->
+    if match = bufferLine.match(/\S/)
       scopeDescriptor = @editor.tokenForBufferPosition([bufferRow, match.index])
       return scopeDescriptor.scopes.indexOf('tag.open.js') > -1 and
              scopeDescriptor.scopes.indexOf('meta.tag.attribute-name.js') == -1

     return false

-  lineStartWithAttribute: (bufferRow) ->
-    if match = @editor.lineTextForBufferRow(bufferRow).match(/\S/)
+  lineStartWithAttribute: (bufferLine) ->
+    if match = bufferLine.match(/\S/)
       scopeDescriptor = @editor.tokenForBufferPosition([bufferRow, match.index])
       return scopeDescriptor.scopes.indexOf('meta.tag.attribute-name.js') > -1

     return false

   lineStartsWithClosingTag: (bufferRow) ->
-    if match = @editor.lineTextForBufferRow(bufferRow).match(/\S/)
+    if match = bufferLine.match(/\S/)
       scopeDescriptor = @editor.tokenForBufferPosition([bufferRow, match.index])
       return scopeDescriptor.scopes.indexOf('tag.closed.js') > -1


:art: now most of text object respect visible cursor position in vL mode

behavior improve example reveals when you `i w` in vL mode
- previous: try to `i w` from next line of column 0( since this is
where ACTUAL cursor is at )
- new behavior: `i w` from where cursor is visible(this is where
selection prop is preserved in vL)

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -58,13 +58,6 @@
   isBlockwise: ->
     @wise is 'blockwise'

-  getNormalizedHeadBufferPosition: (selection) ->
-    point = selection.getHeadBufferPosition()
-    if @isMode('visual') and not selection.isReversed()
-      translatePointAndClip(@editor, point, 'backward')
-    else
-      point
-
   resetState: ->
     @selectSucceeded = null

@@ -111,7 +104,7 @@
   @deriveInnerAndA()

   getRange: (selection) ->
-    point = @getNormalizedHeadBufferPosition(selection)
+    point = @getCursorPositionForSelection(selection)
     {range} = @getWordBufferRangeAndKindAtBufferPosition(point, {@wordRegex})
     if @isA()
       expandRangeToWhiteSpaces(@editor, range)
@@ -192,7 +185,7 @@

   getRange: (selection) ->
     originalRange = selection.getBufferRange()
-    pairInfo = @getPairInfo(@getNormalizedHeadBufferPosition(selection))
+    pairInfo = @getPairInfo(@getCursorPositionForSelection(selection))
     # When range was same, try to expand range
     if pairInfo?.targetRange.isEqual(originalRange)
       pairInfo = @getPairInfo(pairInfo.aRange.end)
@@ -368,7 +361,8 @@

   getRange: (selection) ->
     originalRange = selection.getBufferRange()
-    fromRow = @getNormalizedHeadBufferPosition(selection).row
+    fromRow = swrap(selection).getRowFor('head')
+    # fromRow = @getCursorPositionForSelection(selection).row

     if @isMode('visual', 'linewise')
       if selection.isReversed()
@@ -385,7 +379,8 @@
   @deriveInnerAndA()

   getRange: (selection) ->
-    fromRow = @getNormalizedHeadBufferPosition(selection).row
+    fromRow = swrap(selection).getRowFor('head')
+    # fromRow = @getCursorPositionForSelection(selection).row

     baseIndentLevel = @getIndentLevelForBufferRow(fromRow)
     predict = (row) =>
@@ -405,7 +400,7 @@
   wise: 'linewise'

   getRange: (selection) ->
-    row = @getNormalizedHeadBufferPosition(selection).row
+    row = @getCursorPositionForSelection(selection).row
     rowRange = @editor.languageMode.rowRangeForCommentAtBufferRow(row)
     rowRange ?= [row, row] if @editor.isBufferRowCommented(row)
     if rowRange?
@@ -431,7 +426,7 @@
     getCodeFoldRowRangesContainesForRow(@editor, row).reverse()

   getRange: (selection) ->
-    row = @getNormalizedHeadBufferPosition(selection).row
+    row = @getCursorPositionForSelection(selection).row
     selectedRange = selection.getBufferRange()
     for rowRange in @getFoldRowRangesContainsForRow(row)
       range = @getBufferRangeForRowRange(@adjustRowRange(rowRange))
@@ -466,7 +461,7 @@
   @deriveInnerAndA()

   getRange: (selection) ->
-    row = @getNormalizedHeadBufferPosition(selection).row
+    row = @getCursorPositionForSelection(selection).row
     range = @editor.bufferRangeForBufferRow(row)
     if @isA()
       range


bug: modifying stream_opts in mixin permanently modifies defaults

e.g. forcing non-object mode with `delete stream_opts.objectMode`

--- lib/understream.coffee
+++ lib/understream.coffee
@@ -70,7 +70,7 @@
         # if this is a class and argument length is < constructor length, prepend defaults to arguments list
         args = _(arguments).toArray()
         if args.length < FunctionOrReadableStreamKlass.length
-          args = [@defaults].concat args
+          args = [_(@defaults).clone()].concat args
         else if args.length is FunctionOrReadableStreamKlass.length
           _(args[0]).defaults @defaults
         else


Log any hapi error

Log any hapi error

--- lib/utils/serve.coffee
+++ lib/utils/serve.coffee
@@ -90,6 +90,8 @@

         reply.continue()

-    server.start ->
+    server.start (err) ->
+      if err
+        console.log err
       console.log "Listening at:", server.info.uri


Debundeling @metadata patch (issue #115)

--- anon.coffee
+++ anon.coffee
@@ -51,7 +51,6 @@
     for account in config.accounts
       if typeof account.ranges == 'string'
         account.ranges = loadJson account.ranges
-        delete account.ranges['@metadata']
   console.log "loaded config from", path
   config


Remove deprecated action handling and use actions key.

--- app/assets/javascripts/voluntary_ranking/controllers/user/rankings/show_controller.js.coffee
+++ app/assets/javascripts/voluntary_ranking/controllers/user/rankings/show_controller.js.coffee
@@ -6,23 +6,17 @@
   isBestClass: 'btn active', isWorstClass: 'btn'
   is1Star: '', is2Star: '', is3Star: 'active', is4Star: '', is5Star: ''

-  setBest: (without_stars) ->
+  _setBest: (without_stars) ->
     return if @get('best')
     @set('best', true); @set('isBestClass', 'btn active'); @set('isWorstClass', 'btn')
-    @setStars(3) if !without_stars and @get('stars') <= 2
+    @_setStars(3) if !without_stars and @get('stars') <= 2

-  setWorst: (without_stars) ->
+  _setWorst: (without_stars) ->
     return unless @get('best', 'btn active')
     @set('best', false); @set('isBestClass', 'btn'); @set('isWorstClass', 'btn active')
-    @setStars(0) if !without_stars and @get('stars') > 0
-
-  set1Star: -> @setStars(1)
-  set2Star: -> @setStars(2)
-  set3Star: -> @setStars(3)
-  set4Star: -> @setStars(4)
-  set5Star: -> @setStars(5)
+    @_setStars(0) if !without_stars and @get('stars') > 0

-  setStars: (stars) ->
+  _setStars: (stars) ->
     if @get('stars') == stars
       @set('stars', stars - 1)
       $.each [1,2,3,4,5], (index, value) => @set('is' + value + 'Star', if value < stars then 'active' else '')
@@ -31,24 +25,35 @@
       $.each [1,2,3,4,5], (index, value) => @set('is' + value + 'Star', if value <= stars then 'active' else '')

     if stars >= 3 and !@get('best')
-      @setBest(true)
+      @_setBest(true)
     else if stars <= 2 and @get('best')
-      @setWorst(true)
+      @_setWorst(true)
+
+  actions:
+
+    setBest: -> @_setBest()
+    setWorst: -> @_setWorst()
+
+    set1Star: -> @_setStars(1)
+    set2Star: -> @_setStars(2)
+    set3Star: -> @_setStars(3)
+    set4Star: -> @_setStars(4)
+    set5Star: -> @_setStars(5)
+
+    addItem: ->
+      if !@get('thingName').trim() || !@get('adjective').trim() || !@get('negativeAdjective').trim() || !@get('topic').trim() || !@get('scope').trim()
+        alert 'Please set thing, adjective, negative adjective, topic and scope.'
+        return
+
+      user = VoluntaryOnEmberjs.User.current()
+      user_ranking_item = user.get('user_ranking_items').createRecord(
+        thingName: @get('thingName'), best: @get('best'), stars: @get('stars'),
+        adjective: @get('adjective'), negativeAdjective: @get('negativeAdjective'), topic: @get('topic'), scope: @get('scope')
+      )
+      @get('store').commit()
+      @set('thingName', ''); @_setStars(3)
+      @send('reload')

-  addItem: ->
-    #breadcrumb = VoluntaryRanking.get('Router.router.currentHandlerInfos')
-    #@set('routeName', breadcrumb[breadcrumb.length - 1].name)
-    console.log 'addItem scope1: ' + @get('scope')
-    console.log 'addItem scope2: ' + @controllerFor('rankings.show').get('scope')
-    user = VoluntaryRanking.User.find VoluntaryRanking.currentUser.id
-    user_ranking_item = user.get('user_ranking_items').createRecord(
-      thingName: @get('thingName'), best: @get('best'), stars: @get('stars'), thingType: @get('thingType'),
-      adjective: @get('adjective'), negativeAdjective: @get('negativeAdjective'), topic: @get('topic'), scope: @get('scope')
-    )
-    @get('store').commit()
-    @set('thingName', ''); @set('stars', 3); @set('best', true)
-    @set('is1Star', 'active'); @set('is2Star', 'active'); @set('is3Star', 'active');
-    @set('is4Star', ''); @set('is5Star', '');
-
-    @transitionToRoute('rankings.reload')
+    reload: ->
+      @set('model', VoluntaryOnEmberjs.UserRankingItem.find(user_id: VoluntaryOnEmberjs.User.current().id, adjective: @get('adjective'), negative_adjective: @get('negativeAdjective'), topic: @get('topic'), scope: @get('scope')))
 )


Added test for pretty_assertion_attributes.

--- test/saml2.coffee
+++ test/saml2.coffee
@@ -1,5 +1,5 @@
 _             = require 'underscore'
-saml2         = require '../lib/saml2'
+saml2         = require "#{__dirname}/../index"
 assert        = require 'assert'
 async         = require 'async'
 url           = require 'url'
@@ -34,6 +34,20 @@
     xit 'contains expected fields', (done) ->
       assert false
       done()
+
+  describe 'pretty_assertion_attributes', ->
+    it 'creates a correct user object', ->
+      test_attributes =
+        "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": [ "tuser@example.com" ]
+        "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": [ "Test User" ]
+        "http://schemas.xmlsoap.org/claims/Group": [ "Test Group" ]
+
+      expected =
+        email: "tuser@example.com"
+        name: "Test User"
+        group: "Test Group"
+
+      assert.deepEqual saml2.pretty_assertion_attributes(test_attributes), expected

   # Assert
   describe 'assert', ->


less top level vars

less top level vars

--- monocat.coffee
+++ monocat.coffee
@@ -1,14 +1,12 @@
 fs       = require 'fs'
+path     = require 'path'
 uglify   = require 'uglify-js'
+CleanCss = require 'clean-css'
 cheerio  = require 'cheerio'
-cleanCss = require 'clean-css'
-
-
-args = process.argv
-$ = fileName = null
-len = completed = 0
-log        = console.log.bind   console, '\x1b[32m  '
-logErr     = console.error.bind console, '\x1b[31m  '
+args     = process.argv
+log      = console.log.bind   console, '\x1b[32m  '
+logErr   = console.error.bind console, '\x1b[31m  '
+fileName = $ = null

 init = ->


set initialDir to process.cwd

set initialDir to process.cwd

--- monocat.coffee
+++ monocat.coffee
@@ -5,7 +5,7 @@

 args = process.argv
-initialDir = __dirname
+initialDir = process.cwd()
 $ = null
 fileName = null
 len = 0


Product reference resolution methods refactor into a generic method.

--- src/coffee/product-import.coffee
+++ src/coffee/product-import.coffee
@@ -9,7 +9,13 @@
   constructor: (@logger, options = {}) ->
     @sync = new ProductSync
     @client = new SphereClient options
-    @_cache = {}
+    @_resetCache()
+
+  _resetCache: ->
+    @_cache =
+      productType: {}
+      categories: {}
+      taxCategory: {}

   _resetSummary: ->
     @_summary =
@@ -88,9 +94,9 @@

   _prepareNewProduct: (product) ->
     Promise.all [
-      @_resolveProductTypeReference(product.productType?)
+      @_resolveReference(@client.productTypes, 'productType', product.productType?, "name=\"#{productTypeRef.id}\"")
       @_resolveProductCategories(product.categories?)
-      @_resolveTaxCategoryReference(product.taxCategory?)
+      @_resolveReference(@client.taxCategories, 'taxCategory', product.taxCategory?, "name=\"#{taxCategoryRef.id}\"")
     ]
     .spread(prodType, prodCats, taxCat) =>
       if not prodType.isRejected
@@ -100,57 +106,26 @@
       if not taxCat.isRejected
         product.taxCategory = taxCat.value

-
-  _resolveProductTypeReference: (productTypeRef) ->
-    new Promise (resolve, reject) =>
-      if not productTypeRef?
-        reject("Product type reference is undefined")
-      if @_cache.productType[productTypeRef.id]
-        resolve(@_cache.productType[productTypeRef.id])
-      else
-        @client.productTypes.where("name=\"#{productTypeRef.id}\"").fetch()
-        .then(result) =>
-          # Todo: Handle multiple response, currently taking first response.
-          @_cache.productType[productTypeRef.id] = result.results[0]
-          resolve(result.results[0])
-
-
   _resolveProductCategories: (cats) ->
     new Promise (resolve, reject) =>
-      if not cats?
+      if _.isEmpty(cats)
         reject("Product categories are undefined.")
       else
-        Promise.all [
-          for cat in cats
-            @_resolveProductCategories(cat)
-        ].then(result) =>
-          resolve(result)
+        Promise.all cats.map(cat =>
+          @_resolveReference(@client.categories, 'categories', cat, "externalId=\"#{cat.id}\""))
+        .then (result) -> resolve(result)

-  _resolveProductCategoryReference: (categoryRef) ->
+  _resolveReference: (service, refKey, ref, predicate) ->
     new Promise (resolve, reject) =>
-      if not categoryRef?
-        reject("Product category is undefined")
-      if @_cache.productCategory[categoryRef.id]
-        resolve(@_cache.productCategory[categoryRef.id])
+      if not ref
+        reject("Missing #{refKey}")
+      else if @_cache[refKey][ref.id]
+        resolve(@_cache[refKey][ref.id])
       else
-        @client.categories.where("externalId=\"#{categoryRef.id}\"").fetch()
-        .then(result) =>
+        service.where(predicate).fetch()
+        .then (result) =>
           # Todo: Handle multiple response, currently taking first response.
-          @_cache.productCategory[categoryRef.id] = result.results[0]
-          resolve(result.results[0])
-
-
-  _resolveTaxCategoryReference: (taxCategoryRef) ->
-    new Promise (resolve, reject) =>
-      if not taxCategoryRef?
-        reject("Tax category is undefined")
-      if @_cache.taxCategory[taxCategoryRef.id]
-        resolve(@_cache.taxCategory[taxCategoryRef.id])
-      else
-        @client.taxCategories.where("name=\"#{taxCategoryRef.id}\"").fetch()
-        .then(result) =>
-          # Todo: Handle multiple response, currently taking first response.
-          @_cache.taxCategory[taxCategoryRef.id] = result.results[0]
-          resolve(result.results[0])
+          @_cache[refKey][ref.id] = result.body.results[0]
+          resolve(result.body.results[0])

 module.exports = ProductImport


remove console.log

remove console.log

--- src/components/qa/exercises.cjsx
+++ src/components/qa/exercises.cjsx
@@ -53,7 +53,6 @@
     @setState(isShowing2StepPreview: ev.target.checked)

   onOnlySelection: (onlyType) ->
-    console.log onlyType
     ignored =  {}
     _.each ExerciseStore.getPageExerciseTypes(@state.pageId), (pt) ->
       ignored[pt] = pt isnt onlyType


Define class properties with `:`, not `=`

--- src/app/language-mode.coffee
+++ src/app/language-mode.coffee
@@ -11,9 +11,9 @@

 module.exports =
 class LanguageMode
-  buffer = null
-  grammar = null
-  editSession = null
+  buffer: null
+  grammar: null
+  editSession: null
   currentGrammarScore: null

   # Public: Sets up a `LanguageMode` for the given {EditSession}.


Unload all records before sign-out

Unload all records before sign-out

--- src/app/routes/logout.coffee
+++ src/app/routes/logout.coffee
@@ -1,2 +1,4 @@
 Menglifang.App.LogoutRoute = Ember.Route.extend
-  beforeModel: -> @send('invalidateSession')
+  beforeModel: ->
+    @store.unloadAll(v.type) for _, v of @store.typeMaps
+    @send('invalidateSession')


Documentando o app.coffee para facilitar quem quiser forkar

--- src/assets/coffee/app.coffee
+++ src/assets/coffee/app.coffee
@@ -4,10 +4,12 @@

 @App.controller 'PageController', ($scope, $http) ->
-  $http.get('https://raw.githubusercontent.com/pedrorocha-net/' +
-      'eleicoes2016-rj-comparadoacoes/master/data/' +
-      'candidatos_dados_processados.json'
-  ).then (result) ->
+  # Se quiser clonar o repositório para fazer para outra cidade, altere aqui
+  # e aponte para o JSON em seu repositório
+  url_json_dados = 'https://raw.githubusercontent.com/' +
+    'pedrorocha-net/eleicoes2016-rj-comparadoacoes' +
+    '/master/data/candidatos_dados_processados.json'
+  $http.get(url_json_dados).then (result) ->
     $scope.candidatos = result.data
     $scope.selecao1 = null
     $scope.selecao2 = null


Clean up nomination buttons

Clean up nomination buttons

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -317,8 +317,7 @@

   nominationResetMessage: =>
     showHype = @props.beatmapset.can_be_hyped
-    nominations = @props.beatmapset.nominations
-    nominationReset = nominations.nomination_reset
+    nominationReset = @props.beatmapset.nominations.nomination_reset
     mapIsQualified = @props.beatmapset.status == 'qualified'

     return null unless showHype && !mapIsQualified && nominationReset?
@@ -385,13 +384,9 @@

   disqualifyButton: =>
-    showHype = @props.beatmapset.can_be_hyped
-    requiredHype = @props.beatmapset.nominations.required_hype
-    hypeRaw = @props.currentDiscussions.totalHype
-    mapCanBeNominated = @props.beatmapset.status == 'pending' && hypeRaw >= requiredHype
     mapIsQualified = @props.beatmapset.status == 'qualified'

-    return null unless showHype && (mapCanBeNominated || mapIsQualified) && @props.currentUser.id? && mapIsQualified && @userCanDisqualify()
+    return null unless mapIsQualified && @userCanDisqualify()

     el BigButton,
       text: osu.trans 'beatmaps.nominations.disqualify'
@@ -401,13 +396,11 @@

   nominationButton: =>
-    showHype = @props.beatmapset.can_be_hyped
     requiredHype = @props.beatmapset.nominations.required_hype
     hypeRaw = @props.currentDiscussions.totalHype
     mapCanBeNominated = @props.beatmapset.status == 'pending' && hypeRaw >= requiredHype
-    mapIsQualified = @props.beatmapset.status == 'qualified'
-
-    return null unless showHype && (mapCanBeNominated || mapIsQualified) && @props.currentUser.id? && mapCanBeNominated && @userCanNominate()
+
+    return null unless mapCanBeNominated && @userCanNominate()

     nominationButton = (disabled = false) =>
       el BigButton,


Fixing typo

Fixing typo

--- src/device.module.contact.coffee
+++ src/device.module.contact.coffee
@@ -7,3 +7,58 @@
 @class      Contact
 @author     Javier Jimenez Villar <javi@tapquo.com> || @soyjavi
 ###
+
+Device.Contact = do (dvc = Device) ->
+
+  _instance = navigator.contacts or null
+
+
+  create = (properties) ->
+    # id: A globally unique identifier. (DOMString)
+    # displayName: The name of this Contact, suitable for display to end-users. (DOMString)
+    # name: An object containing all components of a persons name. (ContactName)
+    # nickname: A casual name to address the contact by. (DOMString)
+    # phoneNumbers: An array of all the contact's phone numbers. (ContactField[])
+    # emails: An array of all the contact's email addresses. (ContactField[])
+    # addresses: An array of all the contact's addresses. (ContactAddress[])
+    # ims: An array of all the contact's IM addresses. (ContactField[])
+    # organizations: An array of all the contact's organizations. (ContactOrganization[])
+    # birthday: The birthday of the contact. (Date)
+    # note: A note about the contact. (DOMString)
+    # photos: An array of the contact's photos. (ContactField[])
+    # categories: An array of all the contacts user defined categories. (ContactField[])
+    # urls: An array of web pages associated to the contact. (ContactField[])
+    contact = _instance.create properties
+    contact.save()
+    @
+
+  update = (id, properties, options) ->
+    _instance.find properties, ((success) ->), ((error) ->), options
+    @
+
+  delete = ->
+    @
+
+  find = ->
+    _instance.find properties, ((success) ->), ((error) ->), options
+    @
+
+
+
+# contacts.create
+# contacts.find
+# Arguments
+
+# contactFields
+# contactSuccess
+# contactError
+# contactFindOptions
+# Objects
+
+# Contact
+# ContactName
+# ContactField
+# ContactAddress
+# ContactOrganization
+# ContactFindOptions
+# ContactError


Safeguard dist calculation

Safeguard dist calculation

--- src/gui/tabs/remote/index.coffee
+++ src/gui/tabs/remote/index.coffee
@@ -27,9 +27,9 @@
         best: z
         down: {}
       t.n++
-      if t.dist != false and t.dist != z.dist
+      if z.dist and t.dist != 0 and t.dist != z.dist
         t.dist = if t.dist
-          false
+          0
         else
           z.dist
       t = t.down


check for duplicates in addFolder

check for duplicates in addFolder

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -341,7 +341,7 @@
 				callback(null, folders, lastFolder)

 	addFolder: (project_id, parentFolder_id, folderName, callback) ->
-		ProjectGetter.getProjectWithOnlyFolders project_id, (err, project)=>
+		ProjectGetter.getProject project_id, {}, (err, project)=>
 			if err?
 				logger.err project_id:project_id, err:err, "error getting project for add folder"
 				return callback(err)
@@ -459,17 +459,20 @@
 				return callback(error) if error?
 				projectLocator.findElement {project:project, element_id:entity_id, type:entityType}, (error, entity, entPath)=>
 					return callback(error) if error?
-					endPath = path.join(path.dirname(entPath.fileSystem), newName)
-					conditions = {_id:project_id}
-					update = "$set":{}
-					namePath = entPath.mongo+".name"
-					update["$set"][namePath] = newName
-					tpdsUpdateSender.moveEntity({project_id:project_id, startPath:entPath.fileSystem, endPath:endPath, project_name:project.name, rev:entity.rev})
-					Project.findOneAndUpdate conditions, update, { "new": true}, (error, newProject) ->
-						return callback(error) if error?
-						ProjectEntityHandler.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) =>
+					ProjectEntityHandler.checkElementName entity, newName, (err) =>
+						return callback(err) if err?
+						endPath = path.join(path.dirname(entPath.fileSystem), newName)
+						conditions = {_id:project_id}
+						update = "$set":{}
+						namePath = entPath.mongo+".name"
+						update["$set"][namePath] = newName
+						# FIXME check if this would create a duplicate file!
+						tpdsUpdateSender.moveEntity({project_id:project_id, startPath:entPath.fileSystem, endPath:endPath, project_name:project.name, rev:entity.rev})
+						Project.findOneAndUpdate conditions, update, { "new": true}, (error, newProject) ->
 							return callback(error) if error?
-							DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback
+							ProjectEntityHandler.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) =>
+								return callback(error) if error?
+								DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback

 	_cleanUpEntity: (project, entity, entityType, path, userId, callback = (error) ->) ->
 		if(entityType.indexOf("file") != -1)


order app on display name instead of name

--- client/app/collections/application.coffee
+++ client/app/collections/application.coffee
@@ -23,11 +23,11 @@
         rightIsOfficial = modelRight.isOfficial()

         if leftIsOfficial and rightIsOfficial
-            modelLeft.get('name').localeCompare modelRight.get 'name'
+            modelLeft.get('name').localeCompare modelRight.get 'displayName'
         else if leftIsOfficial
             -1
         else if rightIsOfficial
             1
         else
-            modelLeft.get('name').localeCompare modelRight.get 'name'
+            modelLeft.get('name').localeCompare modelRight.get 'displayName'


Call callback even when started

Call callback even when started

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -73,9 +73,9 @@
     , true

   start: (callback) ->
-    return if @started
     unless callback
       callback = ->
+    return callback() if @started
     unless @isReady()
       @on 'ready', =>
         @start callback


Generalize filter payload format

Generalize filter payload format

--- src/coffee/queryutils.coffee
+++ src/coffee/queryutils.coffee
@@ -5,16 +5,24 @@
   @mapMatchFunction: (matchBy) ->
     switch matchBy
       when 'id' then QueryUtils.matchById
+      when 'slug' then QueryUtils.matchBySlug
       when 'sku' then QueryUtils.matchBySku
-      when 'slug' then QueryUtils.matchBySlug
       else QueryUtils.matchByCustomAttribute(matchBy)

   # Matches products by `id` attribute
   # @param {object} service - SDK service object
   # @param {Array} products
   @matchById: (products) ->
-    ids = products.map((p) -> "\"#{p.product.id}\"")
-    filterInput = "id:in (#{ids.join(',')})"
+    ids = products.map((p) -> p.product.id)
+    QueryUtils.formatAttributePredicate("id", ids)
+
+  # Matches products by `slug` attribute
+  # @param {object} service - SDK service object
+  # @param {Array} products
+  @matchBySlug: (products) ->
+    slugs = products.map((p) ->
+      p.product.slug[GLOBALS.DEFAULT_LANGUAGE])
+    QueryUtils.formatAttributePredicate("slug", slugs)

   # Matches products by `sku` attribute
   # @param {object} service - SDK service object
@@ -22,34 +30,38 @@
   @matchBySku: (products) ->
     skus = _.flatten(products.map((p) ->
       [p.product.masterVariant.sku].concat(p.product.variants.map((v) ->
-        v.sku))))
-    predicate = "sku in (\"#{skus.join('", "')}\")"
-    filterInput = "masterVariant(#{predicate}) or variants(#{predicate})"
-
-  # Matches products by `slug` attribute
-  # @param {object} service - SDK service object
-  # @param {Array} products
-  @matchBySlug: (products) ->
-    slugs = products.map((p) ->
-      p.product.slug[GLOBALS.DEFAULT_LANGUAGE])
-    filterInput = "slug in (\"#{slugs.join('", "')}\")"
+        v.sku)))).filter(x ->
+          !!x)
+    predicate = QueryUtils.formatAttributePredicate("sku", skus)
+    QueryUtils.applyPredicateToVariants(predicate)

   # Matches products by custom attribute
   # @param {object} service - SDK service object
   # @param {Array} products
   @matchByCustomAttribute: (attribute) ->
     (products) ->
-      attributes = _.flatten(products.map((p) ->
+      values = _.flatten(products.map((p) ->
         [
           QueryUtils.valueOf(p.product.masterVariant.attributes, attribute)
         ].concat(p.product.variants.map((v) ->
-          QueryUtils.valueOf(v.attributes, attribute)))))
-      predicate = "#{attribute} in (\"#{attributes.join('", "')}\")"
-      filterInput = "masterVariant(#{predicate}) or variants(#{predicate})"
+          QueryUtils.valueOf(v.attributes, attribute))))).filter(x ->
+            !!x)
+      predicate = QueryUtils.formatAttributePredicate(attribute, values)
+      QueryUtils.applyPredicateToVariants(predicate)

+  # Withdraw particular attribute value out of attributes collection
+  # @param {Array} attributes - attributes collection
+  # @param {String} name - name of the attribute
+  # @return {Any} attribute value if found
   @valueOf: (attributes, name) ->
     attrib = _.find(attributes || [], (attribute) ->
       attribute.name is name)
     attrib?.value

+  @formatAttributePredicate: (name, items) ->
+    "#{attribute} in (\"#{items.join('", "')}\")"
+
+  @applyPredicateToVariants: (predicate) ->
+    "masterVariant(#{predicate}) or variants(#{predicate})"
+
 module.exports = QueryUtils


Fix regression following acff884f7d5c5e2496a9384b38c2ad7754a7bccd

- one call to `Page()` hadn't been changed

--- src/main/assets/ops/javascript/orbeon/xforms/control/Calendar.coffee
+++ src/main/assets/ops/javascript/orbeon/xforms/control/Calendar.coffee
@@ -195,7 +195,7 @@
                 YD.setStyle calendarDiv, "z-index", ORBEON.xforms.Globals.lastDialogZIndex

                 # Find resource for selected language
-                lang = Page().getLang()
+                lang = Page.getLang()
                 resources = CalendarResources[lang]
                 # Default to English if resources are not found
                 resources = CalendarResources["en"] unless resources?


tweak for removing formatting

tweak for removing formatting

--- src/components/editor.coffee
+++ src/components/editor.coffee
@@ -29,7 +29,7 @@
     mdiClass = "mdi-black " + options.s
     delete options.s

-    if options.commandValue is ""
+    if options.commandValue == ""
       options['data-wysihtml5-command-blank-value'] = "true"
     else
       options['data-wysihtml5-command-value'] = options.commandValue


Rename "Free Response" to "Requires viewing ..."

--- src/components/question.cjsx
+++ src/components/question.cjsx
@@ -80,8 +80,8 @@
         <input onChange={@freeResponseClicked}
           id="freeResponseFormat#{id}"
           type="checkbox"
-          defaultChecked={QuestionStore.isFreeResponse(id)} />
-        Free Response
+          defaultChecked={QuestionStore.isFreeResponse(id)}
+        /> Requires viewing choices to answer question

       { if not hideStimulus


move hit-similator event out of event source function

--- src/GameBundle/Resources/coffee/game.coffee
+++ src/GameBundle/Resources/coffee/game.coffee
@@ -23,9 +23,6 @@
     $(window).on 'unload', (source) ->
       source.close

-    $('#hit-simulator select[name="esn"]').change ->
-      $(this).trigger 'focusout'
-
     countdown_ref = $('#countdown')
     game_end = new Date(countdown_ref.data('gameEndTime'))
     countdown_ref.countdown(game_end)
@@ -41,6 +38,9 @@
   copies = $('tr[id^="player-"]').length
   printScores copies

+$('#hit-simulator select[name="esn"]').change ->
+  $(this).trigger 'focusout'
+
 pushHit = (selector, zone) ->
   value = parseInt($(selector).text()) + 1
   $(selector).animate {color: '#a50b00'}, 500,  ->


Add Meteor support

Add Meteor support

--- src/daemon/apps/index.coffee
+++ src/daemon/apps/index.coffee
@@ -26,7 +26,9 @@
 module.exports =

   add: (path) ->
-    port += 1
+    # Basic Meteor support
+    # See https://github.com/typicode/katon/issues/17
+    port += 2

     group.add path,
       env         : env.get path, port


Add a test that sending too few updates does not flush history

--- test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee
+++ test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee
@@ -176,3 +176,32 @@

 		it "should flush project history", ->
 			MockProjectHistoryApi.flushProject.calledWith(@project_id).should.equal true
+
+	describe "with too few updates to flush to the history service", ->
+		before (done) ->
+			@project_id = DocUpdaterClient.randomId()
+			@user_id = DocUpdaterClient.randomId()
+
+			updates = []
+			for v in [0..42] # Should flush after 500 ops
+				updates.push
+					id: DocUpdaterClient.randomId(),
+					pathname: '/file-' + v
+					docLines: 'a\nb'
+
+			sinon.spy MockProjectHistoryApi, "flushProject"
+
+			# Send updates in chunks
+			projectId = @project_id
+			userId = @project_id
+			DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 10), [], (error) ->
+				throw error if error?
+				DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(10), [], (error) ->
+					throw error if error?
+					setTimeout done, 2000
+
+		after ->
+			MockProjectHistoryApi.flushProject.restore()
+
+		it "should not flush project history", ->
+			MockProjectHistoryApi.flushProject.calledWith(@project_id).should.equal false


forgot to commit flux store changes

forgot to commit flux store changes

--- src/flux/cc-dashboard.coffee
+++ src/flux/cc-dashboard.coffee
@@ -9,7 +9,10 @@

 DashboardConfig =
   exports:
-    getPeriods: (courseId) -> @_get(courseId)?.course?.periods
+    getPeriods: (courseId) ->
+      _.chain(@_get(courseId)?.course?.periods)
+        .tap(PeriodHelper.sort)
+        .value()

     getPeriodChapters: (courseId, periodId) ->


added the logging middleware

added the logging middleware

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -148,6 +148,8 @@
     (Q.all nonPrimaryPromises).then ->
       messageStore.storeResponse ctx, ->
         logger.info 'Storing non primary route responses'
+        stats.incrementTransactionCount ctx, ->
+        stats.measureTransactionDuration ctx, ->

 # function to build fresh promise for transactions routes
 buildNonPrimarySendRequestPromise = (ctx, route, options, path) ->


Show posts

Show posts

--- apps/home/routes.coffee
+++ apps/home/routes.coffee
@@ -7,8 +7,8 @@

 @index = (req, res, next) ->
   res.render 'index', posts: []
-  # posts = new Posts
-  # posts.fetch
-  #   complete: ->
-  #     res.locals.sd.POSTS
+  posts = new Posts
+  posts.fetch
+    complete: ->
+      res.locals.sd.POSTS


Registration uses callback

Registration uses callback

It now emits on Navigation channel

--- src/concept-coach/base.cjsx
+++ src/concept-coach/base.cjsx
@@ -86,7 +86,7 @@
     else if not isLoggedIn
       <UserLogin onComplete={@updateUser} />
     else if not isRegistered
-      <CourseRegistration {...@props} onComplete={@updateUser} />
+      <CourseRegistration {...@props} />
     else
       course = User.getCourse(@props.collectionUUID)

@@ -98,6 +98,8 @@
         <Dashboard cnxUrl={@props.cnxUrl}/>
       else if view is 'profile'
         <UserProfile onComplete={@showTasks} />
+      else if view is 'registration'
+        <CourseRegistration {...@props} />
       else

bad internal state, no view is set


Fixed splitting and merging tab views.

Fixed splitting and merging tab views.

--- client/ide/views/tabview/idetabview.coffee
+++ client/ide/views/tabview/idetabview.coffee
@@ -56,45 +56,49 @@
     KD.getSingleton('appManager').tell 'IDE', 'setActiveTabView', this

   convertToSplitView: (type = 'vertical') ->
-    {parent} = this
+    oldTabView = new IDETabView
+    newTabView = new IDETabView

-    subView.unsetParent() for subView in @subViews
-    subView.unsetParent() for subView in @holderView.subViews
+    for pane in @tabView.panes
+      pane.unsetParent()
+      pane.detach()
+      oldTabView.tabView.addPane pane

-    @detach()
-    @unsetParent()
+    @tabView.detach()
+    @holderView.detach()

-    newTabView = new IDETabView
-    splitView  = new KDSplitView
-      type     : type
-      views    : [ this, newTabView ]
+    @tabView    = oldTabView.tabView
+    @holderView = oldTabView.holderView

-    @setOption           'splitView', splitView
-    newTabView.setOption 'splitView', splitView
-    @setOption           'parent'   , parent
-    newTabView.setOption 'parent'   , parent
+    splitView   = new KDSplitView
+      type      : type
+      views     : [ oldTabView, newTabView ]

-    parent.addSubView splitView
+    for tabView in [ oldTabView, newTabView ]
+      tabView.setOption 'splitView', splitView
+
+    @addSubView splitView
+    KD.getSingleton('appManager').tell 'IDE', 'setActiveTabView', oldTabView

   mergeSplitView: ->
     {splitView} = @getOptions()
-    return unless splitView
+    {parent}    = splitView

     splitView.once 'SplitIsBeingMerged', (views) =>
-      @handleSplitMerge views
+      @handleSplitMerge views, parent

     splitView.merge()

-  handleSplitMerge: (views) ->
-    tabView   = new IDETabView
+  handleSplitMerge: (views, parent) ->
+    newTabView = new IDETabView

     for view in views
-      oldTabView = view.tabView
-      for pane in oldTabView.panes
+      {tabView} = view
+      for pane in tabView.panes
         pane.unsetParent()
-        tabView.tabView.addPane pane
+        newTabView.tabView.addPane pane

-    @getOptions().parent.addSubView tabView
+    parent.addSubView newTabView

   openFile: (file, content) ->
     if @openFiles.indexOf(file) > -1


Replaces H. to @ on FormOptions helpers

Replaces H. to @ on FormOptions helpers

--- app/assets/javascripts/javascript_renderer/helpers/form_options.jsh.coffee
+++ app/assets/javascripts/javascript_renderer/helpers/form_options.jsh.coffee
@@ -20,7 +20,7 @@
 stringOption = (value, selected_selector, disabled_selector) ->
   options = { value: value }
   disabledAndSelector options, selected_selector, disabled_selector
-  H.contentTag 'option', H.htmlEscape(value), H.htmlEscapeAttributes(options)
+  @contentTag 'option', @htmlEscape(value), @htmlEscapeAttributes(options)

 selectorForOptions = (reference) ->
   reference = reference.toString() if typeof reference == 'number' || reference == true || reference == false
@@ -76,13 +76,13 @@
     value = '' if value == null

     if typeof value == 'string'
-      select_options += stringOption(value, selected_selector, disabled_selector)
+      select_options += stringOption.call(this, value, selected_selector, disabled_selector)
     if value instanceof Array
       [ content, value_option, options ] = if value.length == 3
-        [ value[0], value[1], H._clone(value[2]) ]
+        [ value[0], value[1], @_clone(value[2]) ]
       else if value.length == 2
         if typeof value[1] == 'object'
-          [ value[0], value[0], H._clone(value[1]) ]
+          [ value[0], value[0], @_clone(value[1]) ]
         else
           [ value[0], value[1], {} ]
       else
@@ -95,7 +95,7 @@
       options['value'] ||= value_option || content

       disabledAndSelector options, selected_selector, disabled_selector
-      select_options += H.contentTag 'option', H.htmlEscape(content), H.htmlEscapeAttributes(options)
+      select_options += @contentTag 'option', @htmlEscape(content), @htmlEscapeAttributes(options)

   select_options

@@ -117,7 +117,7 @@
     selectors['selected'].push(value) if selected_selector(object)
     selectors['disabled'].push(value) if disabled_selector(object)

-  H.optionsForSelect container, selectors
+  @optionsForSelect container, selectors

 helper 'groupedOptionsForSelect', (grouped_options, selectors, options = {}) ->
   [ selected_selector, disabled_selector ] = selectorForOptions selectors
@@ -127,8 +127,8 @@
     prompt = if typeof options['prompt'] == 'string' || options['prompt'] == 'number'
       options['prompt'].toString()
     else
-      H.translate 'helpers.select.prompt', default: 'Please select'
-    options_for_select += H.contentTag( 'option', H.htmlEscape(prompt), { value: '' } )
+      @translate 'helpers.select.prompt', default: 'Please select'
+    options_for_select += @contentTag( 'option', @htmlEscape(prompt), { value: '' } )

   for index, options_in_group of grouped_options
     [ options_in_group, optgroup_options ] = if options['divider']
@@ -142,7 +142,7 @@
       else
         [ options_in_group, { label: index } ]

-    options_for_select += H.contentTag('optgroup', H.optionsForSelect(options_in_group, selectors), H.htmlEscapeAttributes(optgroup_options) )
+    options_for_select += @contentTag('optgroup', @optionsForSelect(options_in_group, selectors), @htmlEscapeAttributes(optgroup_options) )

   options_for_select

@@ -152,7 +152,7 @@

   options_for_select = ''
   for index, object of collection
-    options_for_select += H.contentTag('optgroup', H.optionsFromCollectionForSelect(group_fetcher(object), option_key_method, option_value_method, selectors), { label: H.htmlEscape( group_label_fetcher object ) } )
+    options_for_select += @contentTag('optgroup', @optionsFromCollectionForSelect(group_fetcher(object), option_key_method, option_value_method, selectors), { label: @htmlEscape( group_label_fetcher object ) } )

   options_for_select

@@ -167,23 +167,23 @@
     class_name = object['_class'] || object['_type'] || object['class'] || object['type']
     if class_name
       class_name = class_name() if typeof class_name == 'function'
-      [ H.underscore(class_name), method_or_prefix_and_method ]
+      [ @underscore(class_name), method_or_prefix_and_method ]
     else
       [ null, method_or_prefix_and_method ]
   else
     method_or_prefix_and_method

   [ choices, options ] = if typeof options_or_choices == 'function'
-    [ options_or_choices, H._clone( choices_or_options || {} ) ]
-  else
-    [ choices_or_options, H._clone( options_or_choices || {} ) ]
+    [ options_or_choices, @_clone( choices_or_options || {} ) ]
+  else
+    [ choices_or_options, @_clone( options_or_choices || {} ) ]

   name = if prefix then prefix + '[' + method + ']' else method

   multiple_hidden_input = if options['multiple']
     name = name + '[]' if name[-2..-1] != '[]'
     if options['include_hidden'] == true || options['include_hidden'] == undefined
-      H.tag 'input', type: 'hidden', name: name, value: '', disabled: options['disabled']
+      @tag 'input', type: 'hidden', name: name, value: '', disabled: options['disabled']
     else
       ''
   else
@@ -209,9 +209,9 @@
       choices_options            = {}
       choices_options['divider'] = options['divider'] unless options['divider'] == undefined
       options['divider']         = undefined
-      return multiple_hidden_input + H.selectTag(name, H.groupedOptionsForSelect(choices, selector, choices_options), options)
-
-  multiple_hidden_input + H.selectTag(name, H.optionsForSelect(choices, selector), options)
+      return multiple_hidden_input + @selectTag(name, @groupedOptionsForSelect(choices, selector, choices_options), options)
+
+  multiple_hidden_input + @selectTag(name, @optionsForSelect(choices, selector), options)

 # TODO: Finish form_options helpers
 # collection_check_boxes


Find right buffer:

Find right buffer:

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -5,7 +5,7 @@
 COMMANDS = 'commands'
 EXECUTING = 'executing'
 STRATEGY = 'strategy'
-TARGET = 'buffer'
+TARGET = 'buffer:'

 BUTTON_AUTHORITY = {
     fill: my.color.button
@@ -87,9 +87,10 @@

   command: (world, args) ->
     {name, command} = args
-    program = world.find_child(STRATEGY).find_child(name)
+    program = world.find_child(STRATEGY).find_child().find_child(name)
+    console.log 'command', name, command, world.find_child(STRATEGY), program
+    my.assert program, "No program for #{name}"
     program.add_child command
-    console.log 'append_program', program, command, world.find_child(STRATEGY)
     # TODO: Force Buffer to Update Visually
     world.send 'render'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -78,7 +78,7 @@

 4.circularPoints (p) ->

-# p gets Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, )
+# p gets Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1)

 @name circularPoint


Register subgraph last

Register subgraph last

--- src/lib/loader/NodeJs.coffee
+++ src/lib/loader/NodeJs.coffee
@@ -73,7 +73,7 @@
         return callback err if err
         callback null, modules

-exports.register = (loader, callback) ->
+registerSubgraph = (loader) ->
   # Inject subgraph component
   if path.extname(__filename) is '.js'
     graphPath = path.resolve __dirname, '../../src/components/Graph.coffee'
@@ -81,13 +81,20 @@
     graphPath = path.resolve __dirname, '../../components/Graph.coffee'
   loader.registerComponent null, 'Graph', graphPath

+exports.register = (loader, callback) ->
   manifestOptions = manifestLoader.prepareManifestOptions loader

   if loader.cache
-    manifestLoader.listComponents loader, manifestOptions, callback
+    manifestLoader.listComponents loader, manifestOptions, (err, modules) ->
+      return callback err if err
+      registerSubgraph loader
+      callback null, modules
     return

-  dynamicLoader.listComponents loader, manifestOptions, callback
+  dynamicLoader.listComponents loader, manifestOptions, (err, modules) ->
+    return callback err if err
+    registerSubgraph loader
+    callback null, modules

 exports.dynamicLoad = (name, cPath, metadata, callback) ->
   try


NavListTabView: styling

NavListTabView: styling

--- coffee/src/views/nav_list_tab_view.coffee
+++ coffee/src/views/nav_list_tab_view.coffee
@@ -6,12 +6,12 @@
 class athena.lib.NavListTabView extends athena.lib.View

-  className: @classNameExtend 'nav-list-tab-view'
+  className: @classNameExtend 'nav-list-tab-view row-fluid'

   template: _.template '''
-

-

+

+

     '''

@@ -45,15 +45,16 @@
     super
     @$el.empty()
     @$el.html @template()
-    @$('#side').append @navListView.render().el
+    @$('.nav-list-tab-side').append @navListView.render().el
     @renderView @selected
     @

   renderView: (id) =>
+    @navListView.highlightItem id
     view = @views[id]
     if view
-      @$('#content').append view.render().el
+      @$('.nav-list-tab-content').append view.render().el
     @


refactor: error logging

refactor: error logging

--- bin/cli.coffee
+++ bin/cli.coffee
@@ -40,7 +40,9 @@
       process.exit()

 catch e
-  console.log e
-  console.log "Generator '#{generatorName}' not installed, try:"
-  console.log "$ npm install -g gentry-#{generatorName}"
-  process.exit()
+  if e.code = 'MODULE_NOT_FOUND'
+    return console.log """
+    Generator #{generatorName} not installed, try:
+    $ npm install -g gentry-#{generatorName}
+    """
+  return console.log e


Groups: Loader is added fo create group form

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -134,15 +134,21 @@
     @workflow.addForm "group", @groupForm, ["group"]

   createGroupForm: ->
-    return new KDFormViewWithFields
+    return groupForm = new KDFormViewWithFields
       title                 : "Enter new group name"
       cssClass              : "pricing-create-group"
-      callback              : -> @emit "Submit"
+      callback              : ->
+        groupForm.buttons.Create.showLoader()
+        @emit "Submit"
       buttons               :
         Create              :
           title             : "CREATE YOUR GROUP"
           type              : "submit"
           style             : "solid green"
+          loader            :
+            color           : "#ffffff"
+            diameter        : 26
+          callback          : ->
       fields                :
         GroupName           :
           label             : "Group Name"


Adding sprite for audio song

Adding sprite for audio song

--- client/components/player.cjsx
+++ client/components/player.cjsx
@@ -1,6 +1,7 @@
 _ = require 'lodash'
 React = require 'react'
-Howl = require('howler').Howl
+Howler = require 'howler'
+Howl = Howler.Howl
 Meshblu = require 'meshblu'
 MessageSchema =
   type : 'object'
@@ -36,10 +37,12 @@

   playAudio:  (audioUrl) =>
     console.log 'Play ', audioUrl
-    @sound.stop() if @sound
+    @sound = @sound.stop() if @sound
     @sound = new Howl
       urls: [audioUrl]
       volume: 1.0
+      sprite :
+        key : [0, 2000, false]
       buffer: true
       autoplay: false
       onload: =>


Remove merge-conflicts package

Remove merge-conflicts package

The merge-conflicts package has been deprecated in favor of the github
package bundled with Atom v1.18.0.

--- atom/packages.cson
+++ atom/packages.cson
@@ -31,7 +31,6 @@
   "linter-jsonlint"
   "linter-php"
   "linter-ui-default"
-  "merge-conflicts"
   "monokai"
   "package-sync"
   "pigments"


Ajout de `goto-definition`

Ajout de `goto-definition`

--- atom/packages.cson
+++ atom/packages.cson
@@ -8,6 +8,7 @@
   "editorconfig"
   "file-icons"
   "flex-tool-bar"
+  "goto-definition"
   "intentions"
   "isotope-ui"
   "language-applescript"


Update Atom snippets

Update Atom snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -16,25 +16,3 @@
 # This file uses CoffeeScript Object Notation (CSON).
 # If you are unfamiliar with CSON, you can read more about it here:
 # https://github.com/bevry/cson#what-is-cson
-
-# HTML
-'.text.html':
-  'Script':
-    'prefix': 'script'
-    'body': """
-      <script>
-        $1
-      </script>
-    """
-
-  'Script with source':
-    'prefix': 'scriptsrc'
-    'body': '<script src="$1"></script>'
-
-  'Style':
-    'prefix': 'style'
-    'body': """
-      <style>
-        $1
-      </style>
-    """


Add snippet for enabling verbose mode in Bash

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -894,6 +894,10 @@
     'prefix': 'fd'
     'body': '${1:fname}() {\n\t$2\n}'

+  'verbose mode':
+    'prefix': 'verbose'
+    'body': 'set -o verbose'
+
 # AppleScript

 '.source.applescript':


use msg.send instead of reply

use msg.send instead of reply

--- src/cool-ascii-faces.coffee
+++ src/cool-ascii-faces.coffee
@@ -20,4 +20,4 @@

 module.exports = (robot) ->
   robot.respond /face me/, (msg) ->
-    msg.reply getRandomFace()
+    msg.send getRandomFace()


Remove existence check in def and set defined properties as writable.

--- src/core/types/utils.coffee
+++ src/core/types/utils.coffee
@@ -10,9 +10,8 @@
 #     {foo: 10}.merge bar: 20 # {foo: 10, bar: 20}
 def = (ctor, o) ->
   for name, value of o
-    unless ctor.prototype[name]?
-      Object.defineProperty? ctor.prototype,
-                             name,
-                             enumerable: false, value: value
+    Object.defineProperty? ctor.prototype,
+                           name,
+                           enumerable: false, value: value, writable: true

 module.exports = {def}


Remove old event docs

Remove old event docs

--- src/grammar-registry.coffee
+++ src/grammar-registry.coffee
@@ -8,21 +8,6 @@
 NullGrammar = require './null-grammar'

 # Public: Registry containing one or more grammars.
-#
-# ## Events
-#
-# ### grammar-added
-#
-# Fired when a grammar has been added to the registry.
-#
-# * `grammar` The {Grammar} that was added.
-#
-# ### grammar-updated
-#
-# Fired whenever a grammar has been updated due to a grammar it depends on
-# being added to or removed from the registry.
-#
-# * `grammar` The {Grammar} that was updated.
 module.exports =
 class GrammarRegistry
   EmitterMixin.includeInto(this)


Updated html5-definition

Updated html5-definition

--- src/html5-definition.coffee
+++ src/html5-definition.coffee
@@ -1,10 +1,14 @@
+
+@selfClosingHtml5TagList = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']
+
+@nonClosingHtml5TagList = ['!doctype']

 class Html5Node

   constructor: (@parent = null, @tag = null, @attributeMap = {}, @innerText = null)->
-  	@childrenList = []
-  	@isSelfClosing = false
-  	@isNonClosing = false
+    @childrenList = []
+    @isSelfClosing = false
+    @isNonClosing = false
     return

 class Html5TextNode


+ refactor coding style.

+ refactor coding style.

--- src/hubot-order-menu.coffee
+++ src/hubot-order-menu.coffee
@@ -30,14 +30,14 @@
     constructor: (options) ->
       {@name, @userId, @date, @category, @food, @note, @money} = options

-    toString: () ->
-      GetReplyMsg @
+    toString: ->
+      GetReplyMsg this

   class Store
     constructor: (options) ->
       {@name, @phone, @link} = options

-    toString: () ->
+    toString: ->
       "#{@name} #{@phone} - #{@link}"

   class CommandStore
@@ -48,7 +48,7 @@
     Add: (message) ->
       @commands.push message

-    Send: () ->
+    Send: ->
       message = @commands.join('\n')
       @bot.reply message


There is no member

There is no member

--- client/landing/app/Applications/Members.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppController.coffee
@@ -72,6 +72,7 @@
       itemClass             : MembersListItemView
       listControllerClass   : MembersListViewController
       limitPerPage          : 10
+      noItemFoundText       : "There is no member."
       # singleDataSource      : (selector, options, callback)=>
         # filterFunc selector, options, callback
       help                  :


added a notification when vm cannot be started - https://app.asana.com/0/3716548652471/7348827130975

--- client/landing/app/MainApp/filetree/itemsubviews/vmtogglebuttonview.coffee
+++ client/landing/app/MainApp/filetree/itemsubviews/vmtogglebuttonview.coffee
@@ -17,6 +17,14 @@
     return unless vm is @getData().vmName

     if err or not info
+
+      @notify?.destroy()
+      @notify = new KDNotificationView
+        type    : "mini"
+        cssClass: "error"
+        duration: 5000
+        title   : "I cannot turn this machine on, please give it a few seconds."
+
       @toggle.setDefaultValue no
       KD.utils.notifyAndEmailVMTurnOnFailureToSysAdmin vm, err.message
       return warn err


KDView emits paste event now

KDView emits paste event now

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -39,6 +39,7 @@
     dragenter   : "dragEnter"
     dragleave   : "dragLeave"
     dragover    : "dragOver"
+    paste       : "paste"

   overrideAndMergeObjects = (objects)->
@@ -468,7 +469,7 @@
   bindEvents:($elm)->
     $elm or= @getDomElement()
     # defaultEvents = "mousedown mouseup click dblclick dragstart dragenter dragleave dragover drop resize"
-    defaultEvents = "mousedown mouseup click dblclick"
+    defaultEvents = "mousedown mouseup click dblclick paste"
     instanceEvents = @getOptions().bind

     eventsToBeBound = if instanceEvents
@@ -528,6 +529,8 @@
   mouseLeave:(event)-> yes

   mouseUp:(event)->    yes
+
+  paste:(event)->      yes

   mouseDown:(event)->
     (@getSingleton "windowController").setKeyView null


app: add AppController#getConfig method that returns bant config for this app type

--- client/app/lib/appcontroller.coffee
+++ client/app/lib/appcontroller.coffee
@@ -17,12 +17,17 @@
     { mainController } = kd.singletons
     { name, version } = @getOptions()

+
     mainController.ready =>
       # defer should be removed
       # this should be listening to a different event - SY
       kd.utils.defer  =>
         { appStorageController } = kd.singletons
         @appStorage = appStorageController.storage name, version or "1.0.1"
+
+
+  getConfig: ->
+    return globals.modulesIndex[@getOption('name').toLowerCase()]

   handleQuery:(query)->


myteam: add error controls and minor changes

--- client/home/lib/myteam/index.coffee
+++ client/home/lib/myteam/index.coffee
@@ -52,25 +52,33 @@

     groupsController.on 'GroupJoined', (data) ->

-      if data.contents.actionType is 'groupJoined'
-        { roles, email, username } = data.contents.memberData
-        remote.cacheable username, (err, account) ->
-          unless err
-            account = toImmutable account[0]
-            account = account.setIn ['profile', 'email'], email
-            account = account.set 'role', roles
+      return console.warm 'We couldn\'t fetch neccessary information'  unless data.contents.member
+      return  unless data.contents.actionType is 'groupJoined'

-            id = account.get '_id'
+      { roles, email, username } = data.contents.member

-            reactor.dispatch 'UPDATE_TEAM_MEMBER', { account }
-            reactor.dispatch 'ADD_MEMBER_TO_TEAM', { id }
-            reactor.dispatch 'REMOVE_PENDING_INVITATION', { email }
+      remote.cacheable username, (err, accounts) ->
+
+        return  if err or not accounts
+
+        account = toImmutable accounts[0]
+        account = account.setIn ['profile', 'email'], email
+        account = account.set 'role', roles
+
+        id = account.get '_id'
+
+        reactor.dispatch 'UPDATE_TEAM_MEMBER', { account }
+        reactor.dispatch 'ADD_MEMBER_TO_TEAM', { id }
+        reactor.dispatch 'REMOVE_PENDING_INVITATION', { email }

     groupsController.on 'GroupLeft', (data) ->

-      { username } = data.contents
-      remote.cacheable username, (err, account) ->
-        account = account[0]
+      { username } = data.contents.member
+      remote.cacheable username, (err, accounts) ->
+
+        return  if err or not accounts
+
+        account = accounts[0]
         reactor.dispatch 'DELETE_TEAM_MEMBER', account._id

     @wrapper.addSubView header  'Team Settings'


Use the arrow keys to select next member

--- client/standup/standup.coffee
+++ client/standup/standup.coffee
@@ -1,3 +1,11 @@
+do ->
+  $(document).keydown (e) ->
+    up = [38, 75]
+    down = [40, 74]
+    if up.indexOf(e.keyCode) >= 0
+      Standup.Members.previous()
+    else if down.indexOf(e.keyCode) >= 0
+      Standup.Members.next()
 Standup = do ->
   class Members
     findSelected: ->


Strategies now get refreshed very minute.

--- etfdiction/app/assets/javascripts/components/etf_strategy_result.js.coffee
+++ etfdiction/app/assets/javascripts/components/etf_strategy_result.js.coffee
@@ -15,6 +15,7 @@

   componentDidMount: ->
     @getStrategiesResult()
+    setInterval @getStrategiesResult, 60000
     @getEtfTechnicals()
     setInterval @getEtfTechnicals, 60000


cli generates message schema for the one swagger file we tested

--- command.coffee
+++ command.coffee
@@ -4,6 +4,7 @@
 _  = require 'lodash'
 commander = require 'commander'
 debug = require('debug')('device-generator:cli')
+DeviceGenerator = require './device-generator'

 commander
   .version 1.0
@@ -14,7 +15,18 @@
   # .option '-a, --all',  'generate all json files related to proxy devices'
   .parse process.argv

+
 commander.help() unless commander.args[0]?
+
+swaggerPath = commander.args[0]
+
+generator = new DeviceGenerator commander.args[0]
+
+if commander.messageSchema?
+  messageSchemaPath = commander.messageSchema
+  generator.toMessageSchema (error, messageSchema) =>
+    return console.error error.message if error?
+    fs.writeFileSync messageSchemaPath, JSON.stringify(messageSchema, null, 2)

 # channel = JSON.parse fs.readFileSync(commander.infile)


Basic notifications done

Basic notifications done

--- lib/integrated-learn-environment.coffee
+++ lib/integrated-learn-environment.coffee
@@ -43,8 +43,19 @@
       updater = new LearnUpdater
       updater.checkForUpdate()

+    ipc.send 'register-for-notifications', @oauthToken
+
     ipc.on 'remote-log', (msg) ->
       console.log(msg)
+
+    ipc.on 'new-notification', (data) ->
+      notif = new Notification data.displayTitle,
+        body: data.message
+
+      notif.onclick = ->
+        notif.close()
+
+      console.log(data)

     @fsViewEmitter.on 'toggleTerminal', =>
       @termView.toggle()


remove all reference of the team popup

remove all reference of the team popup

--- coffee/popup-animation.coffee
+++ coffee/popup-animation.coffee
@@ -1,8 +0,0 @@
-###
-code that displays the individual team copy on popup
-###
-
-$("#teamCopyLinkChelsea").click ()->
-  $("#teamCopy").addClass "team-copy-popup"
-                .css "display", "block"
-  return


More fixes on Wave 7 and changes since FAQ 3.02

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -512,7 +512,7 @@
             name: "Graz der Jäger"
             text: """Wirf 1 zusätzlichen Verteidigungswürfel, wenn der Angreifer in deinem Feuerwinkel ist, sobald du verteidigst."""
         "Esege Tuketu":
-            text: """Sobald ein anderes freundliches Schiff in Reichweite 1-2 angreifst, darf es deine Fokusmarker wie seine eigenen behandeln."""
+            text: """Sobald ein anderes freundliches Schiff in Reichweite 1-2 angreift, darf es deine Fokusmarker wie seine eigenen behandeln."""
         "Moralo Eval":
             text: """Du darfst Angriffe mit %CANNON%-Sekundärwaffen gegen Schiffe in deinem Zusatz-Feuerwinkel durchführen."""
         "Warden Squadron Pilot":
@@ -784,7 +784,7 @@
             text: """%DE_REBELONLY%%LINEBREAK%Nachdem du einen Stressmarker von deinem Schiff entfernt hast, darfst du deinem Schiff einen Fokusmarker geben."""
         "Jan Ors":
             name: "Jan Ors (Crew)"
-            text: """%DE_REBELONLY%%LINEBREAK%Ein Mal pro runde darfst du einem freundlichem Schiff in Reichweite 1-3, das gerade die Aktion Fokussierung durchführt oder einen Fokusmarker erhalten würde, stattdessen einen Ausweichmarker geben."""
+            text: """%DE_REBELONLY%%LINEBREAK%Sobald ein freundliches Schiff in Reichweite 1-3 eine Aktion Fokussierung durchführt oder ihm ein Fokusmarker zugeordnet werden würde, darfst du diesem Schiff stattdessen ein Mal pro Runde einen Ausweichmarker zuordnen."""
         "Toryn Farr":
             text: """%DE_HUGESHIPONLY%%LINEBREAK%%DE_REBELONLY%%LINEBREAK%Aktion: Gib X Energie aus, um X feindliche Schiffe in Reichweite 1-2 zu wählen. Sämtliche Fokus-, Ausweich- und blauen Zielerfassungsmarker dieser Schiffe werden entfernt."""
         # TODO Check card formatting
@@ -843,10 +843,10 @@
             text: """Energie: Du kannst 1 Energie ausgeben, um 1 freundliches Schiff in Reichweite1-2 zu wählen. Nimm dann ein Schiff in die Zielerfassung und gibt den blauen Zielerfassungsmarker dem gewählten Schiff."""
         "Lone Wolf":
             name: "Einsamer Wolf"
-            text: """Wenn keine freundlichen Schiffe in Reichweite 1-2 sind, darfst du beim Angreifen und Verteidigen 1 gewürfelte Leerseite neu würfeln."""
+            text: """Sobald du angreifst oder verteidigst und wenn keine anderen freundlichen Schiffe in Reichweite 1-2 sind, darfst du 1 gewürfelte Leerseite neu würfeln."""
         "Stay On Target":
             name: "Am Ziel bleiben"
-            text: """Nach dem Aufdecken des Manöverrads darfst du ein anderes Manöver mit gleicher Geschwindigkeit auf deinem Rad einstellen.

Dieses Manöver wird wie ein rotes Manöver behandelt."""
+            text: """Sobald du ein Manöverrad aufdeckst, darfst du ein anderes Manöver mit gleicher Geschwindigkeit auf deinem Rad einstellen.

Dieses Manöver wird wie ein rotes Manöver behandelt."""
         "Dash Rendar":
             name: "Dash Rendar (Crew)"
             text: """%DE_REBELONLY%%LINEBREAK%Du darfst auch angreifen während du dich mit einem Hindernis überschneidest.

Deine Schussbahn kann nicht versperrt werden."""
@@ -874,7 +874,7 @@
             text: """Sobald du angreifst, darfst du einen Fokusmarker ausgeben, um 1 deiner %FOCUS% in ein %CRIT% zu ändern."""
         "Accuracy Corrector":
             name: "Zielvisor"
-            text: """Sobald du angreifst, darfst du alle deine Würfelergebnisse negieren. Dann darfst du 2 %HIT% hinzufügen.%LINEBREAK%Deine Würfel können während dieses Angriffs nicht noch einmal modifiziert werden."""
+            text: """Wenn du angreifst, darfst du während des Schritts "Angriffswürfel modifizieren" alle deine Würfelergebnisse negieren. Dann darfst du 2 %HIT% hinzufügen.%LINEBREAK%Deine Würfel können während dieses Angriffs nicht noch einmal modifiziert werden."""
         "Inertial Dampeners":
             name: "Trägheitsdämpfer"
             text: """Sobald du dein Manöverrad aufdeckst, darfst du diese Karte ablegen, um stattdessen ein weißes [0%STOP%]-Manöver auszuführen. Dann erhältst du 1 Stressmarker."""
@@ -902,12 +902,12 @@
             text: """Nur für Y-Wing.%LINEBREAK%Füge deiner Aufwertungsleiste das %BOMB%-Symbol hinzu."""
         '"Genius"':
             name: '"Genie"'
-            text: """Wenn du eine Bombe ausgerüstet hast, die vor dem Aufdecken deines Manövers gelegt werden kann, darfst du sie stattdessen auch nach Ausführung des Manövers legen."""
+            text: """Wenn du eine Bombe ausgerüstet hast, die gelegt werden kann, sobald du ein Manöver aufdeckst, darfst du die Bombe legen, nachdem du dein Manöver ausgeführt hast."""
         "Unhinged Astromech":
             name: "Ausgeklinkter Astromech-Droide"
             text: """Du darfst alle Manöver mit Geschwindigkeit 3 wie grüne Manöver behandeln."""
         "R4-B11":
-            text: """Sobald du angreifst, darfst du, falls du den Verteidiger in der Zielerfassung hast, den Zielerfassungsmarker ausgeben, um einen oder alle Verteidigungswürfel zu wählen. Diese muss der Verteidiger neu würfeln."""
+            text: """Sobald du angreifst, darfst du, falls du den Verteidiger in der Zielerfassung hast, den Zielerfassungsmarker ausgeben, um beliebig viele Verteidigungswürfel zu wählen. Diese muss der Verteidiger neu würfeln."""
         "Autoblaster Turret":
             name: "Autoblastergeschütz"
             text: """Angriff: Greife 1 Schiff (auch außerhalb deines Feuerwinkels) an. %LINEBREAK%Deine %HIT% können von Verteidigungswürfeln nicht negiert werden. Der Verteidiger darf %CRIT% vor %HIT% negieren."""
@@ -953,7 +953,7 @@
             text: """%DE_SMALLSHIPONLY%%LINEBREAK%Nachdem du ein weißes oder grünes Manöver von deinem Rad ausgeführt hast, darfst du diese Karte ablegen, um dein Schiff um 180° zu drehen. Dann erhältst du 1 Stressmarker nach dem Schritt "Stress des Piloten überprüfen"."""
         "Twin Laser Turret":
             name: "Zwillingslasergeschütz"
-            text: """Angriff: Führe diesen Angriff zwei Mal durch (auch gegen ein Schiff außerhalb deines Feuerwinkels). Jedes Mal wenn dieser Angriff triffst, nimmt der Verteidiger 1 Schaden. Dann werden alle Würfelergebnisse negiert."""
+            text: """Angriff: Führe diesen Angriff zwei Mal durch (auch gegen ein Schiff außerhalb deines Feuerwinkels). Jedes Mal wenn dieser Angriff trifft, nimmt der Verteidiger 1 Schaden. Dann werden alle Würfelergebnisse negiert."""
         "Plasma Torpedoes":
             name: "Plasma Torpedos"
             text: """Angriff (Zielerfassung): Gib deinen Zielerfassungsmarker aus und lege diese Karte ab, um diesen Angriff durchzuführen.

Falls dieser Angriff trifft, entferne 1 Schildmarker des Verteidigers, nachdem du ihm Schaden zugefügt hast."""


Update Translation of WAVE 4

Update Translation of WAVE 4

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -61,7 +61,7 @@
         titlePlaceholder: "kein Titel"
         upgradeHeader: (translator, language, slot) ->
             "#{translator language, 'slot', slot} Upgrade"
-        unreleased: "unveröffentlichte"
+        unreleased: "unveröffentlicht"
         epic: "Episch"
     byCSSSelector:
         '.translate.sort-cards-by': 'Sortiere Karten per'
@@ -69,7 +69,7 @@
         '.xwing-card-browser option[value="source"]': 'Quelle'
         '.xwing-card-browser option[value="type-by-points"]': 'Typ (Punkte)'
         '.xwing-card-browser option[value="type-by-name"]': 'Typ (Name)'
-        '.xwing-card-browser .translate.select-a-card': 'Wähle eine Karte aus der Liste links.'
+        '.xwing-card-browser .translate.select-a-card': 'Wähle eine Karte aus der Liste.'
     singular:
         'pilots': 'Piloten'
         'modifications': 'Modifikationen'
@@ -88,8 +88,16 @@

     exportObj.ships = basic_cards.ships

-    # Move TIE Interceptor to TIE Abfangjäger
-    exportObj.renameShip 'TIE Interceptor', 'TIE Abfangjäger'
+    # Move TIE Interceptor to TIE-Abfangjäger
+    exportObj.renameShip 'TIE Interceptor', 'TIE-Abfangjäger'
+    # Move Z-95 Headhunter to Z-95-Kopfjäger
+    exportObj.renameShip 'Z-95 Headhunter', 'Z-95-Kopfjäger'
+    # Move TIE Defender to TIE-Jagdbomber
+    exportObj.renameShip 'TIE Defender', 'TIE-Jagdbomber'
+    # Move Lambda-Class Shuttle to Raumfähre der Lambda-Klasse
+    exportObj.renameShip 'Lambda-Class Shuttle', 'Raumfähre der Lambda-Klasse'
+    # Move GR-75 Medium Transport to Medium-Transporter GR-75
+    exportObj.renameShip 'GR-75 Medium Transport', 'Medium-Transporter GR-75'

     pilot_translations =
         "Wedge Antilles":
@@ -143,21 +151,21 @@
             text: """Im Schritt "Aktionen durchführen" darfst du 2 Aktionen durchführen."""
         "Alpha Squadron Pilot":
             name: "Pilot der Alpha-Staffel"
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
         "Avenger Squadron Pilot":
             name: "Pilot der Avenger-Staffel"
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
         "Saber Squadron Pilot":
             name: "Pilot der Saber-Staffel"
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
         "\"Fel's Wrath\"":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Wenn die Summe deiner Schadenskarten deinen Hüllenwert erreicht oder übersteigt, wirst du nicht sofort zerstört, sondern erst am Ende der Kampfphase."""
         "Turr Phennir":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Nachdem du angegriffen hast, darfst du eine freie Aktion Schub oder Fassrolle durchführen."""
         "Soontir Fel":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Immer wenn du einen Stressmarker erhältst, darfst du deinem Schiff auch einen Fokusmarker geben."""
         "Tycho Celchu":
             text: """Du darfst auch dann Aktionen durchführen, wenn du Stressmarker hast."""
@@ -208,66 +216,79 @@
         "Major Rhymer":
             text: """Beim Angreifen mit einer Sekundärwaffe darfst du die Reichweite der Waffe um 1 erhöhen oder verringern, bis zu einer Reichweite von 1-3."""
         "Captain Kagi":
+            ship: "Raumfähre der Lambda-Klasse"
             text: """Wenn ein feindliches Schiff eine Zielerfassung durchführt, muss es wenn möglich dich als Ziel erfassen."""
         "Colonel Jendon":
+            ship: "Raumfähre der Lambda-Klasse"
             text: """Zu Beginn der Kampfphase darfst du 1 deiner blauen Zielerfassungsmarker auf ein freundliches Schiff in Reichweite 1 legen, das noch keinen blauen Zielerfassungsmarker hat."""
         "Captain Yorr":
+            ship: "Raumfähre der Lambda-Klasse"
             text: """Wenn ein anderes freundliches Schiff in Reichweite 1-2 einen Stressmarker erhalten würde und du 2 oder weniger Stressmarker hast, darfst du statt ihm diesen Marker nehmen."""
         "Omicron Group Pilot":
+            ship: "Raumfähre der Lambda-Klasse"
             name: "Pilot der Omikron-Gruppe"
         "Lieutenant Lorrir":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Wenn du die Aktion Fassrolle ausführst, kannst du 1 Stressmarker erhalten, um die (%BANKLEFT% 1) oder (%BANKRIGHT% 1) Manöverschablone anstatt der (%STRAIGHT% 1) Manöverschablone zu benutzen."""
         "Royal Guard Pilot":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             name: "Royal Guard Pilot"
         "Tetran Cowall":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Immer wenn du ein %UTURN% Manöver aufdeckst, kannst du das Manöver mit einer Geschwindigkeit von "1," "3," oder "5" ausführen."""
         "Kir Kanos":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Wenn du ein Ziel in Reichweite 2-3 angreifst, darfst du einen Ausweichmarker ausgeben, um 1 %HIT% zu deinem Wurf hinzuzufügen."""
         "Carnor Jax":
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Feindliche Schiffe in Reichweite 1 können weder Fokussierung und Ausweichen Aktionen durchführen noch Ausweichmarker und Fokusmarker ausgeben."""
         "GR-75 Medium Transport":
+            ship: "Medium-Transporter GR-75"
             name: "Medium-Transporter GR-75"
         "Bandit Squadron Pilot":
-            name: "Bandit Squadron Pilot"
+            ship: "Z-95-Kopfjäger"
+            name: "Pilot der Bandit-Staffel"
         "Tala Squadron Pilot":
-            name: "Tala Squadron Pilot"
+            ship: "Z-95-Kopfjäger"
+            name: "Pilot der Tala-Staffel"
         "Lieutenant Blount":
-            name: "Leutnant Blount"
-            text: """When attacking, the defender is hit by your attack, even if he does not suffer any damage."""
+            ship: "Z-95-Kopfjäger"
+            name: "Lieutenant Blount"
+            text: """Wenn du angreifst, triffst du immer, selbst wenn das verteidigende Schiff keinen Schaden nimmt."""
         "Airen Cracken":
+            ship: "Z-95-Kopfjäger"
             name: "Airen Cracken"
-            text: """After you perform an attack, you may choose another friendly ship at Range 1.  That ship may perform 1 free action."""
+            text: """Nachdem du angegriffen hast, darfst du ein anderes freundliches Schiff in Reichweite 1 wählen. Dieses Schiff darf 1 freie Aktion durchführen."""
         "Delta Squadron Pilot":
-            name: "Delta Squadron Pilot"
+            ship: "TIE-Jagdbomber"
+            name: "Pilot der Delta-Staffel"
         "Onyx Squadron Pilot":
-            name: "Onyx Squadron Pilot"
+            ship: "TIE-Jagdbomber"
+            name: "Pilot der Onyx-Staffel"
         "Colonel Vessery":
-            text: """When attacking, immediately after you roll attack dice, you may acquire a target lock on the defender if it already has a red target lock token."""
+            ship: "TIE-Jagdbomber"
+            text: """Wenn du angreifst und der Verteidiger bereits einen roten Zielerfassungsmarker hat, darfst du ihn unmittelbar nach dem Angriffswurf in die Zielerfassung nehmen."""
         "Rexler Brath":
-            text: """After you perform an attack that deals at least 1 Damage card to the defender, you may spend a focus token to flip those cards faceup."""
+            ship: "TIE-Jagdbomber"
+            text: """Nachdem du angegriffen und damit dem Verteidiger mindestens 1 Schadenskarte zugeteilt hast, kannst du einen Fokusmarker ausgeben, um die soeben zugeteilte Schadenskarte aufzudecken."""
         "Knave Squadron Pilot":
-            name: "Knave Squadron Pilot"
+            name: "Pilot der Schurken-Staffel"
         "Blackmoon Squadron Pilot":
-            name: "Blackmoon Squadron Pilot"
+            name: "Pilot der Schwarzmond-Staffel"
         "Etahn A'baht":
-            text: """When an enemy ship inside your firing arc at Range 1-3 is defending, the attacker may change 1 of its %HIT% results to a %CRIT% result."""
+            text: """Wenn sich ein feindliches Schiff in Reichweite 1-3 und innerhalb deines Feuerwinkels verteidigt, darf der Angreifer 1 seiner gewürfelten %HIT% in ein %CRIT% ändern."""
         "Corran Horn":
-            text: """At the start of the End phase, you may perform one attack.  You cannot attack during the next round."""
+            text: """Zu Beginn der Endphase kannst du einen Angriff durchführen. Tust du das, darfst du in der nächsten Runde nicht angreifen."""
         "Sigma Squadron Pilot":
-            name: "Sigma Squadron Pilot"
+            name: "Pilot der Sigma-Staffel"
         "Shadow Squadron Pilot":
-            name: "Shadow Squadron Pilot"
+            name: "Pilot der Schatten-Staffel"
         '"Echo"':
             name: '"Echo"'
-            text: """When you decloak, you must use the (%BANKLEFT% 2) or (%BANKRIGHT% 2) template instead of the (%STRAIGHT% 2) template."""
+            text: """Wenn du dich enttarnst, muss du statt der (%STRAIGHT% 2)-Manöverschablone die (%BANKRIGHT% 2)- oder (%BANKLEFT% 2)-Schablone verwenden."""
         '"Whisper"':
-            name: '"Whisper"'
-            text: """After you perform an attack that hits, you may assign 1 focus to your ship."""
+            name: '"Geflüster"'
+            text: """Nachdem du mit einem Angriff getroffen hast, darfst du deinem Schiff 1 Fokusmarker geben."""
         "CR90 Corvette (Fore)":
             name: "CR90 Corvette (Fore)"
         "CR90 Corvette (Aft)":
@@ -442,32 +463,32 @@
             name: "Schildprojektor"
             text: """Wenn ein feindliches Schiff in der Kampfphase an die Reihe kommt, kannst du 3 Energie ausgeben, um das Schiff bis zum Ende der Phase dazu zu zwingen dich anzugreifen, falls möglich.."""
         "Ion Pulse Missiles":
-            name: "Ion Pulse Missiles"
-            text: """Attack (target lock): Discard this card to perform this attack.

If this attack hits, the defender suffers 1 damage and receives 2 ion tokens.  Then cancel all dice results."""
+            name: "Ionenpuls-Raketen"
+            text: """Angriff (Zielerfassung): Lege diese Karte ab, um mit dieser Sekundärwaffe anzugreifen.

Wenn du triffst, nimmt das verteidigende Schiff 1 Schaden und erhält 2 Ionenmarker. Dann werden alle übrigen Würfelergebnisse negiert."""
         "Wingman":
-            name: "Wingman"
-            text: """At the start of the Combat phase, remove 1 stress token from another friendly ship at Range 1."""
+            name: "Flügelmann"
+            text: """Entferne zu Beginn der Kampfphase 1 Stressmarker von einem anderen freundlichen Schiff in Reichweite 1."""
         "Decoy":
-            name: "Decoy"
-            text: """At the start of the Combat phase, you may choose 1 friendly ship at Range 1-2.  Exchange your pilot skill with that ship's pilot skill until the end of the phase."""
+            name: "Täuschziel"
+            text: """Zu Beginn der Kampfphase darfst du 1 freundliches Schiff in Reichweite 1-2 wählen. Bis zum Ende der Phase tauscht du mit diesem Schiff den Pilotenwert."""
         "Outmaneuver":
-            name: "Outmaneuver"
-            text: """When attacking a ship inside your firing arc, if you are not inside that ship's firing arc, reduce its agility value by 1 (to a minimum of 0)."""
+            name: "Ausmanövrieren"
+            text: """Wenn du ein Schiff innerhalb deines Feuerwinkels angreifst und selbst nicht im Feuerwinkel dieses Schiffs bist, wird seine Wendigkeit um 1 reduziert (Minimum 0)"""
         "Predator":
-            name: "Predator"
-            text: """When attacking, you may reroll 1 attack die.  If the defender's pilot skill value is "2" or lower, you may instead reroll up to 2 attack dice."""
+            name: "Jagdinstinkt"
+            text: """Wenn du angreifst, darfst du 1 Angriffswürfel neu würfeln. Ist der Pilotenwert des Verteidigers2 oder niedriger, darfst du stattdessen bis zu 2 Angriffswürfel neu würfeln."""
         "Flechette Torpedoes":
             name: "Flechet-Torpedos"
             text: """Angriff (Zielerfassung): Gib deine Zielerfassungsmarker aus und lege diese Karte ab, um mit dieser Sekundärwaffe anzugreifen.

Nachdem du angegriffen hast, bekommt der Verteidiger 1 Stressmarker, sofern sein Hüllenwert 4 oder weniger beträgt."""
         "R7 Astromech":
-            name: "R7 Astromech"
-            text: """Once per round when defending, if you have a target lock on the attacker, you may spend the target lock to choose any or all attack dice.  The attacker must reroll the chosen dice."""
+            name: "R7-Astromech-Droide"
+            text: """Ein Mal pro Runde kannst du beim Verteidigen gegen den Angriff eines Schiffs, das du in Zielerfassung hast, die Zielerfassungsmarker ausgeben, um beliebige (oder alle) Angriffswürfel zu wählen. Diese muss der Angreifer neu würfeln."""
         "R7-T1":
             name: "R7-T1"
-            text: """Action: Choose an enemy ship at Range 1-2.  If you are inside that ship's firing arc, you may acquire a target lock on that ship.  Then, you may perform a free boost action."""
+            text: """Aktion: Wähle ein feindliches Schiff in Reichweite 1-2. Wenn du im Feuerwinkel dieses Schiffs bist, kannst du es in die Zielerfassung nehmen. Dann darfst du als freie Aktion einen Schub durchführen."""
         "Tactician":
-            name: "Tactician"
-            text: """After you perform an attack against a ship inside your firing arc at Range 2, that ship receives 1 stress token."""
+            name: "Taktiker"
+            text: """Nachdem du ein Schiff in Reichweite 2 und innerhalb deines Feuerwinkels angegriffen hast, erhält es 1 Stressmarker."""
         "R2-D2 (Crew)":
             name: "R2-D2 (Crew)"
             text: """At the end of the End phase, if you have no shields, you may recover 1 shield and roll 1 attack die.  On a %HIT% result, randomly flip 1 of your facedown Damage cards faceup and resolve it."""
@@ -567,14 +588,14 @@
             name: "Verbesserte Hülle"
             text: """Erhöhe deinen Hüllenwert um 1."""
         "Munitions Failsafe":
-            name: "Munitions Failsafe"
-            text: """When attacking with a secondary weapon that instructs you to discard it to perform the attack, do not discard it unless the attack hits."""
+            name: "Ausfallsichere Munition"
+            text: """Wenn du mit einer Sekundärwaffe angreifst, deren Kartentext besagt, dass sie zum Angriff abgelegt werden muss, legst du sie nur ab, falls du triffst."""
         "Stygium Particle Accelerator":
-            name: "Stygium Particle Accelerator"
-            text: """When you either decloak or perform a cloak action, you may perform a free evade action."""
+            name: "Stygium-Teilchen-Beschleuniger"
+            text: """immer wenn du dich enttarnst oder die Aktion Tarnen durchführst, darfst du als freie Aktion ausweichen."""
         "Advanced Cloaking Device":
-            name: "Advanced Cloaking Device"
-            text: """After you perform an attack, you may perform a free cloak action."""
+            name: "Verbesserte Tarnvorrichtung"
+            text: """Nachdem du angegriffen hast, darfst du dich als freie Aktion tarnen."""
         "Combat Retrofit":
             name: "Umrüstung für den Kampfeinsatz"
             text: """Erhöhe deinen Hüllenwert um 2 und deinen Schildwert um 1."""
@@ -590,10 +611,11 @@
         "Moldy Crow":
             text: """In der Endphase werden von diesem Schiff keine unbenutzen Fokusmarker entfernt."""
         "ST-321":
+            ship: "Raumfähre der Lambda-Klasse"
             text: """Wenn du eine Zielerfassung durchführst, darfst du ein beliebiges feindliches Schiff auf der Spielfläche als Ziel erfassen."""
         "Royal Guard TIE":
             name: "TIE der Roten Garde"
-            ship: "TIE Abfangjäger"
+            ship: "TIE-Abfangjäger"
             text: """Du kannst bis zu 2 verschiedene Modifikationen verwenden (statt einer).

Du kannst diese Karte nicht verwenden, wenn der Pilotenwert "4" oder kleiner ist."""
         "Dodonna's Pride":
             name: "Dodonna's Pride"
@@ -606,10 +628,13 @@
         "Tantive IV":
             text: """Your fore section upgrade bar gains 1 additional %CREW% and 1 additional %TEAM% upgrade icon."""
         "Bright Hope":
+            ship: "Medium-Transporter GR-75"
             text: """Wenn neben deiner Bugsektion ein Verstärkungsmarker liegt, fügt er 2 %EVADE% hinzu (anstatt 1)."""
         "Quantum Storm":
+            ship: "Medium-Transporter GR-75"
             text: """Wenn du zu Beginn der Endphase 1 Energiemarker oder weniger hast, gewinnst du 1 Energiemarker."""
         "Dutyfree":
+            ship: "Medium-Transporter GR-75"
             text: """Bei der Aktion Störsignal kannst du ein feindliches Schiff in Reichweite 1-3 (statt 1-2) wählen."""
         "Jaina's Light":
             text: """When defending, once per attack, if you are dealt a faceup Damage card, you may discard it and draw another faceup Damage card."""


modify script

modify script

--- src/scripts/buzzfeedstats.coffee
+++ src/scripts/buzzfeedstats.coffee
@@ -18,7 +18,7 @@
   robot.respond /realtime$/i, (msg) ->
     gaCallback msg, "buzzfeed"

-  robot.hear /buzzfeed .* (users|viewers)$/i, (msg) ->
+  robot.hear /buzzfeed .* (users|viewers)/i, (msg) ->
     gaCallback msg, "buzzfeed"


Added semantics to the result set for elasticsearch queries

--- src/scripts/elasticsearch.coffee
+++ src/scripts/elasticsearch.coffee
@@ -20,7 +20,7 @@
       .get() (err, res, body) ->
         json = JSON.parse(body)
         firsthit = JSON.stringify(json.hits.hits[0])
-        msg.send("There are #{json.hits.total} for the search http://#{server}/_search?#{query} The first result is /n#{firsthit}")
+        msg.send("There are #{json.hits.total} results for the search http://#{server}/_search?#{query} \nThe first result is \n#{firsthit}")

   cluster_health = (msg, server) ->
     msg.http("http://#{server}/_cluster/health")


Remove unnecessary return

Remove unnecessary return

--- components/ParseSource.coffee
+++ components/ParseSource.coffee
@@ -31,4 +31,3 @@
       out: new noflo.IP 'closeBracket', fileName

     output.done()
-  c


make dns lookup nonfatal

make dns lookup nonfatal

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -240,7 +240,9 @@
                     require('dns').lookup hostname, {all:true}, (err, ips) =>
                         if err
                             winston.debug("Got #{hostname} --> err=#{err}")
-                            cb(err)
+                            # NON-FATAL -- we just don't include these and hope to
+                            # have at least one total working host...
+                            cb()
                         else
                             winston.debug("Got #{hostname} --> #{JSON.stringify(ips)}")
                             # In kubernetes the stateful set service just has


Display none in formula as empty cell

Display none in formula as empty cell

--- src/main/js/worksheet/TableWorksheet.coffee
+++ src/main/js/worksheet/TableWorksheet.coffee
@@ -51,7 +51,7 @@

   dataFromDefAndValues = (defs) ->
     defs.map (d) ->
-      {name: d.name, formula: d.definition.expr.text, value: displayValue(d.value)}
+      {name: d.name, formula: displayFormula(d.definition.expr), value: displayValue(d.value)}

   formulaError = (error) -> "Formula error on line #{error.line} at position #{error.columnInExpr}"
   calculationError = (error) -> "#{error.functionName}: #{error.message}"
@@ -60,6 +60,7 @@
       when v instanceof FunctionError then formulaError v
       else v

+  displayFormula = (expr) -> if expr.text == 'none' then '' else expr.text

   emptyRow = () -> {name: null, formula: null, value: null}


Fix tests

Fix tests

--- client/test/src/report_index.coffee
+++ client/test/src/report_index.coffee
@@ -89,7 +89,7 @@

   assert.match(
     $('#test-container').html(),
-    new RegExp(".*/report/#{id}.*")
+    new RegExp(".*/reports/#{id}.*")
   )

   view.close()


smokeTestのリトライ処理の負荷が高いようなのでリトライ間隔を延ばしてみる

--- bin/js/test/smokeTest.coffee
+++ bin/js/test/smokeTest.coffee
@@ -124,7 +124,7 @@
       yield assertNotBuilt(name)
       yield build(name)
       console.log("    start : #{name}")
-      yield retry(assertBuilt.bind(this, name), {retries: 200, interval: 5000, factor : 1})
+      yield retry(assertBuilt.bind(this, name), {retries: 100, interval: 10000, factor : 1})
       console.log("    end   : #{name}")
       yield assertBlue(name)


fix dangling selected tree nodes

fix dangling selected tree nodes

--- src/textorum/tinymce/tree.coffee
+++ src/textorum/tinymce/tree.coffee
@@ -241,6 +241,7 @@
       secondNode = treeInstance._get_parent(firstNode)
       treeInstance.open_node(secondNode)
       treeInstance.open_node(firstNode)
+      treeInstance.deselect_all()
       treeInstance.select_node(firstNode)
     ignoreNavigation = false
     return null


call setSort after collection is fetched

--- app/assets/javascripts/admin/views/workload/workload_list_view.coffee
+++ app/assets/javascripts/admin/views/workload/workload_list_view.coffee
@@ -26,11 +26,10 @@

   initialize : ->

-    @collection.setSort("availableTaskCount", "asc")
-
     @collection.fetch(
       silent : true
     ).done( =>
+      @collection.setSort("availableTaskCount", "asc")
       @collection.goTo(1)
     )


addEvents and deleteEvents now return proper arguments

--- UoACalendarClient.coffee
+++ UoACalendarClient.coffee
@@ -525,19 +525,25 @@

   addEvents: (calendarId, events) ->
     action = (resolve, reject) ->
+      data = []
+      httpResponses = []
       numEvents = events.length
       count = 0
       i = 0
       while i < numEvents
         @addEvent(calendarId, events[i])
         .then (args) ->
+          httpResponses.push args.res
+          data.push args.data
           count++
           if count >= numEvents
-            resolve()
+            resolve(httpResponses, data)
         , (err) ->
+          httpResponses.push args.res
+          data.push args.data
           count++
           if count >= numEvents
-            reject()
+            reject(httpResponses, data)
         i++
       return
     new Promise(action.bind(@))
@@ -611,6 +617,8 @@

   deleteEvents: (calendarId, eventIds) ->
     action = (resolve, reject) ->
+      data = []
+      httpResponses = []
       numEvents = eventIds.length
       count = 0
       i = 0
@@ -618,13 +626,17 @@
         eventId = eventIds[i]
         @deleteEvent(calendarId, eventId)
         .then (args) ->
+          httpResponses.push args.res
+          data.push args.data
           count++
           if count >= numEvents
-            resolve()
+            resolve(httpResponses, data)
         , (err) ->
+          httpResponses.push args.res
+          data.push args.data
           count++
           if count >= numEvents
-            reject()
+            reject(httpResponses, data)
         i++
       return
     new Promise(action.bind(@))


Fix this one-character indent error, which causes the CoffeeScript compiler to crash on worksheet.coffee (!).

--- salvus/page/worksheet.coffee
+++ salvus/page/worksheet.coffee
@@ -590,13 +590,13 @@
     #######################################################################

     current_cell: () =>
-         if not @_current_cell?
+        if not @_current_cell?
             @_current_cell = @_cells.find(".salvus-cell:first").data('cell')
         return @_current_cell

     focus: () =>
         @_focus_cell(@current_cell())
-
+
     selected_cells: () =>
         # Return array of all cells with checkboxes, or if there are none checked,
         # return the last focused cell.


Bug fix: compiling more than once would raise non-unique label errors.

--- coffee/AstToBytecodeCompiler.coffee
+++ coffee/AstToBytecodeCompiler.coffee
@@ -19,13 +19,14 @@
   bytecode[0] == 'token'

 compile = (pairs) ->
+  newCompiler = Opal.AstToBytecodeCompiler.$new Opal.top
   bytecodes = []
   bytecodes = bytecodes.concat bytecodesRuntime, [['discard']]
   for pair in pairs
     [sectionName, code] = pair
     sexp = parser.$parse code
     #dump sexp, 0
-    bytecodesNew = compiler.$compile_program sectionName, sexp
+    bytecodesNew = newCompiler.$compile_program sectionName, sexp
     #for bytecode in bytecodesNew
     #  console.log bytecode.join(' ')
     bytecodes = bytecodes.concat bytecodesNew, [['discard']]


playerView: open acorn page on acorn-icon click

--- coffee/src/views/player_view.coffee
+++ coffee/src/views/player_view.coffee
@@ -121,3 +121,4 @@

   openAcornWebsite: =>
+    window.open @model.pageUrl(), '_blank'


dead code

dead code

On branch master
Changes not staged for commit:
	modified:   packages/shrub-angular/index.coffee
Untracked files:
	packages/shrub-user/email/
no changes added to commit (use "git add" and/or "git commit -a")

--- packages/shrub-skin/index.coffee
+++ packages/shrub-skin/index.coffee
@@ -56,8 +56,6 @@
 		middleware: [

 			(req, res, next) ->
-
-				skinKey = exports.activeKey()

 				# } Render app.html
 				return exports.renderAppHtml().then((html) ->
@@ -117,7 +115,7 @@
 # ### ::renderAppHtml
 #
 # *Render the application HTML.*
-exports.renderAppHtml = (locals) ->
+exports.renderAppHtml = ->

 	skinKey = exports.activeKey()
 	skinDirectory = exports.skinDirectory skinKey


Using default RoR message for missing translations

--- app/assets/javascrips/i18n_screwdriver/config.coffee
+++ app/assets/javascrips/i18n_screwdriver/config.coffee
@@ -1,3 +1,3 @@
 I18n.locale = $("html").attr("lang")
 I18n.onMissingTranslation ?= (md5, message) ->
-  "[#{I18n.locale}] #{message}"
+  "translation missing: #{I18n.locale}.#{message}"