issue warning if question independence and/or difficulty are changed

--- app/assets/javascripts/questions.coffee
+++ app/assets/javascripts/questions.coffee
@@ -4,13 +4,23 @@

 # highlight 'Ungespeicherte Änderungen' if something is entered in question basics

-$(document).on 'keyup', '#question-basics-edit', ->
-  $('#question-basics-options').removeClass("no_display")
-  $('#question-basics-warning').removeClass("no_display")
+$(document).on 'turbolinks:load', ->
+
+  $(document).on 'keyup', '#question-basics-edit', ->
+    $('#question-basics-options').removeClass("no_display")
+    $('#question-basics-warning').removeClass("no_display")
+    return
+
+
+  $(document).on 'change', '#question-basics-edit :input', ->
+    console.log 'Hi!'
+    $('#question-basics-options').removeClass("no_display")
+    $('#question-basics-warning').removeClass("no_display")
+    return

 # restore status quo if editing of question basics is cancelled

-$(document).on 'click', '#question-basics-cancel', ->
+  $(document).on 'click', '#question-basics-cancel', ->
     $.ajax Routes.cancel_question_basics_path(),
       type: 'GET'
       dataType: 'script'
@@ -19,3 +29,13 @@
       }
       error: (jqXHR, textStatus, errorThrown) ->
         console.log("AJAX Error: #{textStatus}")
+    return
+
+  return
+
+# clean up everything before turbolinks caches
+$(document).on 'turbolinks:before-cache', ->
+  $(document).off 'keyup', '#question-basics-edit'
+  $(document).off 'change', '#question-basics-edit :input'
+  $(document).off 'click', '#question-basics-cancel'
+  return


Focus field

Focus field

--- app/assets/javascripts/search.js.coffee
+++ app/assets/javascripts/search.js.coffee
@@ -72,3 +72,4 @@
     $('.js-search-input')
       .val ''
       .trigger 'keyup'
+      .focus()


Increment number by 1000s until almost at target

--- app/assets/javascripts/static.js.coffee
+++ app/assets/javascripts/static.js.coffee
@@ -1,6 +1,9 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
+
+roundUpToThousand = (value) ->
+  return 1000 * Math.ceil(value / 1000)

 $ ->
   animateMetric = $(".metric-box strong").each (index) ->
@@ -14,5 +17,7 @@
     tween = createjs.Tween.get($this).to( {counter: $target_count}, (1000 + index * 1000) , createjs.Ease.quintOut)

     tween.addEventListener 'change', (event) ->
+      # if the number is under 90 of the target, increment number by thousands
+      $this.counter = roundUpToThousand($this.counter) if $this.counter < $target_count * 0.9
       $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))
       return


fixing double h2 update bug on store#show

--- app/assets/javascripts/stores.js.coffee
+++ app/assets/javascripts/stores.js.coffee
@@ -12,7 +12,7 @@
     clear_alert_and_notice = ->
         ($ '.rails-notice, .rails-alert').text('').addClass('hide')

-    update_h2 = (text) -> ($ '#product_form_wrapper').find('h2').text(text)
+    update_h2 = (text) -> ($ '#product_form_wrapper').find('h2').first().text(text)

     reset_h2 = -> update_h2('Add Product')


fixed small number formatter (closes #338)

--- src/number/format.coffee
+++ src/number/format.coffee
@@ -33,7 +33,14 @@
       ret = format.numberFormat(".2g") number
     ret += "%"
   else if number < 10 and number > -10
-    ret = d3.round number, 2
+    length = number.toString().split(".")
+    sigs = 1
+    if length.length > 1
+      sigs = d3.min [parseFloat(length[1]).toString().length, 2]
+      unless -1 < number < 1
+        zeros = length[1].length - parseFloat(length[1]).toString().length
+        sigs += 1 + zeros
+    ret = format.numberFormat("."+sigs+"g") number
   else if length > 3
     symbol = d3.formatPrefix(number).symbol
     symbol = symbol.replace("G", "B") # d3 uses G for giga


added note about rollback

added note about rollback

--- app/coffee/RedisMigrationManager.coffee
+++ app/coffee/RedisMigrationManager.coffee
@@ -81,7 +81,8 @@
 #  - deploy project history with the "old queue" pointing to the new redis and
 #    the "new queue" to the old redis to clear the new queue before processing
 #    the new queue (i.e. add a rollback:true property in new_project_history in
-#    the project-history settings)
+#    the project-history settings via the environment variable
+#    MIGRATION_PHASE="rollback").
 #  - projects will now clear gradually from the new redis back to the old redis
 #  - get a list of all the projects in the new redis and flush them, which will
 #    cause the new queues to be cleared and the old redis to be used for those


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/oscillator.coffee
+++ src/oscillator.coffee
@@ -1,7 +1,5 @@
 ###*
 Creates an oscillator function with the given parameters.
-
-

 @name Oscillator
 @constructor


Fix bad parameter in sweepPrivateKey

Fix bad parameter in sweepPrivateKey

--- app/src/wallet/sweep_private_key.coffee
+++ app/src/wallet/sweep_private_key.coffee
@@ -2,7 +2,8 @@

 _.extend ledger.wallet,

-  sweepPrivateKey: ({privateKey, account, txFee: 10000}, callback) ->
+  sweepPrivateKey: ({privateKey, account, txFee}, callback) ->
+    txFee ?= 10000
     completion = new CompletionClosure(callback)
     recipientAddress = account.getHDAccount().getCurrentPublicAddress()
     ecKey = new window.bitcoin.ECKey.fromWIF(privateKey)


Dont use infinity for score as it gets adjusted by ace

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -5567,6 +5567,9 @@
   removeFromMap topScope, 'is-null-or-undefined'
   addToMap topScope, '{}',
     type: quantifyAll toConstrained new TypeApp arrayType, (new TypeVariable 'a', star)
+  # TODO: suggest function calls, possibly given prefix
+  # addToMap topScope, '(sqrt )',
+  #   type: quantifyAll toConstrained numType
   findMatchingDefinitionsOnType type, join scoped, [topScope]

 findMatchingDefinitionsOnType = (type, definitionLists) ->
@@ -5578,12 +5581,13 @@
     # typesUnify = (def) ->
     #   not isFailed mostGeneralUnifier (freshInstance ctx, def.type).type, type.type
     # [typed, notTyped] = partitionMap typesUnify, validDefinitions
+    UNTYPED_PENALTY = -1000000
     scoreAndPrint = (def) ->
       freshedType = freshenType type.type
       checkedType = (freshInstance ctx, def.type).type
       sub = mostGeneralUnifier checkedType, freshedType
       if isFailed sub
-        score = -Infinity
+        score = UNTYPED_PENALTY
       else
         score = -((subMagnitude checkedType, freshedType) + i * 100)
       type: plainPrettyPrint def.type#score + ' ' +
@@ -5592,7 +5596,7 @@
       rawType: def.type
       score: score
     isTyped = (completion) ->
-      completion.score isnt -Infinity
+      completion.score isnt UNTYPED_PENALTY

     partitionMap isTyped, (mapMap scoreAndPrint, validDefinitions))
   values concatMaps typed..., untyped...


Don't allow changing blocks when not inside a DIV

--- src/plugins/block.coffee
+++ src/plugins/block.coffee
@@ -28,11 +28,21 @@
     _prepareDropdown: (contentId) ->
       contentArea = jQuery "

"

+      containingElement = @options.editable.element.get(0).tagName.toLowerCase()
+
       addElement = (element) =>
         el = jQuery "<#{element}>#{element}</#{element}>"
+
+        if containingElement is element
+          el.addClass 'selected'
+
+        unless containingElement is 'div'
+          el.addClass 'disabled'
+
         el.bind 'click', =>
+          if el.hasClass 'disabled'
+            return
           @options.editable.execute 'formatBlock', element.toUpperCase()
-
         queryState = (event) =>
           block = document.queryCommandValue 'formatBlock'
           if block.toLowerCase() is element
@@ -46,6 +56,7 @@
           @options.editable.element.unbind 'keyup paste change mouseup', queryState

         el
+
       for element in @options.elements
         contentArea.append addElement element
       contentArea


Fix Admin PayPal Verified Tag Update

Fix Admin PayPal Verified Tag Update

Fix the Admin page to ensure that when a User verifies their account,
the corresponding PayPal verified tag in the header is updated.

--- app/views/admin/update_paypal.js.coffee
+++ app/views/admin/update_paypal.js.coffee
@@ -1,3 +1,4 @@
+$(".verified-status-tag").html "<%= escape_javascript(render :partial => 'shared/verified_status_tag', :formats => [ :html ]) %>"
 $(".club-admin-status-text").html "<%= escape_javascript(render :partial => 'admin/club_live_status', :formats => [ :html ]) %>"
 $(".club-promote-text").html "<%= escape_javascript(render :partial => 'admin/club_promote', :locals => { :club => @club }, :formats => [ :html ]) %>"
 $(".modal").removeClass "paypal-confirm-modal" if $(".modal").hasClass("paypal-confirm-modal")


Some more notes

Some more notes

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -12,6 +12,7 @@
 # * Fix 1 column layout, test on phone/tablet
 # * Partial analogy icons (0.6 of an avatar)
 # * More ships, auto select ship that has a couple hundred kills
+# * Empty charts/tables, don't proper empty message

 # Data requests / questions
 # * Incursions?


Added jsNew to simple macros

Added jsNew to simple macros

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2557,7 +2557,7 @@
     callTyping ctx, call
     assignCompile ctx, call, macroFn args...

-for jsMethod in ['binary', 'ternary', 'unary', 'access', 'call', 'method', 'assign']
+for jsMethod in ['binary', 'ternary', 'unary', 'access', 'call', 'method', 'assign', 'new']
   do (jsMethod) ->
     ms["Js.#{jsMethod}"] = (ctx, call) ->
       call.tea = toConstrained jsType


Support 0 arity functions

Support 0 arity functions

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -829,10 +829,13 @@
 callTyping = (ctx, call) ->
   return if ctx.shouldDefer()
   terms = _terms call
-  if terms.length is 1
-    malformed ctx, call, 'Missing an argument (for now)'
-    terms = join terms, [tea: toConstrained (markOrigin (ctx.freshTypeVariable star), call)]
-  call.tea = callInfer ctx, (_operator call), terms
+  op = _operator call
+  call.tea =
+    if terms.length is 1
+      # terms = join terms, [tea: toConstrained (markOrigin (ctx.freshTypeVariable star), call)]
+      callZeroInfer ctx, op, op.tea
+    else
+      callInfer ctx, (_operator call), terms

 callInfer = (ctx, operator, terms) ->
   # Curry the call
@@ -848,6 +851,12 @@
   callType = markOrigin (typeFn argTea.type, returnType), originalOperator
   unify ctx, operatorTea.type, callType
   new Constrained (join operatorTea.constraints, argTea.constraints), returnType
+
+callZeroInfer = (ctx, originalOperator, operatorTea) ->
+  returnType = markOrigin (ctx.freshTypeVariable star), originalOperator
+  callType = markOrigin (typeFn returnType), originalOperator
+  unify ctx, operatorTea.type, callType
+  new Constrained operatorTea.constraints, returnType

 termsCompile = (ctx, list) ->
   termCompile ctx, term for term in list
@@ -4367,14 +4376,20 @@
   else
     new KindFn arg, kindFnOfArgs args...

-typeFn = (from, to, args...) ->
+typeFn = (argType, args...) ->
+  if _empty args
+    new TypeApp zeroArrowType, argType
+  else
+    properTypeFn argType, args...
+
+properTypeFn = (from, to, args...) ->
   if args.length is 0
     if not to
       from
     else
       new TypeApp (new TypeApp arrowType, from), to
   else
-    typeFn from, (typeFn to, args...)
+    properTypeFn from, (properTypeFn to, args...)

 applyKindFn = (fn, arg, args...) ->
   if not arg
@@ -4460,6 +4475,7 @@

 star = '*'
 arrowType = new TypeConstr 'Fn', kindFn 2
+zeroArrowType = new TypeConstr 'Fn0', kindFn 1
 arrayType = new TypeConstr 'Array', kindFn 1
 listType = new TypeConstr 'List', kindFn 1
 hashmapType = new TypeConstr 'Map', kindFn 2
@@ -4538,6 +4554,8 @@
 flattenType = (types) ->
   if types[0].match /^\[\d+\]$/
     "[#{types[1..].join ' '}]"
+  else if types[0] is 'Fn0'
+    "(Fn #{types[1..].join ' '})"
   else
     "(#{types.join ' '})"

@@ -6120,6 +6138,14 @@
   """
   '(g 3)', 3

+  'zero arity'
+  """
+  f (fn [] 4)
+
+  g (f)
+  """
+  'g', 4
+
   # TODO: support matching with the same name
   #       to implement this we need the iife to take as arguments all variables
   #       with the same names, since JavaScript shadows it too strongly and


Preserve dependency sets after successful sorting and filter them during hoisting. Relies on them being sorted first.

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -667,6 +667,9 @@
   reversedDependencies = reverseGraph graph
   independent = []

+  for node in graph
+    node.origSet = cloneSet node.set
+
   moveToIndependent = (node) ->
     independent.push node
     delete dependencies[name] for name in node.names
@@ -681,6 +684,9 @@
     for child in reversedDependencies[finishedParent.names[0]] or []
       removeDependency child.set, name for name in finishedParent.names
       moveToIndependent child if child.set.numDependencies is 0
+
+  for node in sorted
+    node.set = node.origSet

   for parent of dependencies
     throw new Error "Cyclic definitions between #{(name for name of dependencies).join ','}"
@@ -947,7 +953,8 @@
     {missing, names, def, set} = where
     stillMissingNames = addAllTo dependencySet(),
       (name for name in (setToArray missing) when not inSet defined, name)
-    if stillMissingNames.numDependencies == 0 and set.numDependencies == 0
+    stillMissingDeps = removeAll (cloneSet set), setToArray hoistedNames
+    if stillMissingNames.numDependencies == 0 and stillMissingDeps.numDependencies == 0
       hoisted.push def
       addAllTo hoistedNames, names
     else
@@ -955,7 +962,7 @@
         def: def
         names: names
         missing: stillMissingNames
-        set: removeAll (cloneSet set), setToArray hoistedNames
+        set: stillMissingDeps
   [hoisted, notHoisted]

 addAllTo = (set, array) ->


Leave computing origin to the actual showError function

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1425,7 +1425,7 @@
 declaredTypeMissingConstraint = (ctx, name, constraint) ->
   ctx.extendSubstitution substitutionFail
     message: "#{name}'s declared type is too weak, missing #{printType replaceQuantifiedByOrigin constraint}"
-    conflicts: [(originOf constraint), (originOf constraint.types.types[0])]
+    conflicts: [constraint, constraint.types.types[0]]

 replaceQuantifiedByOrigin = (type) ->
   if type.QuantifiedVar
@@ -2746,7 +2746,7 @@
   if _notEmpty ambiguous
     {error: substitutionFail
       message: "Constraint #{printType ambiguous[0]} is ambiguous for inferred type #{printType finalType}"
-      conflicts: [(originOf type.type), (originOf ambiguous[0])]}
+      conflicts: [type.type, ambiguous[0]]}
   else
     success: [deferred, retained]

@@ -2849,7 +2849,7 @@
   [first, second] = sortBasedOnOriginPosition constraint, constraint.types.types[0]
   substitutionFail
     message: "No instance found to satisfy #{safePrintType constraint}"
-    conflicts: [(originOf first), (originOf second)]
+    conflicts: [first, second]

 _names = (list) ->
   map _symbol, list
@@ -3332,7 +3332,7 @@
   if not classParams
     ctx.extendSubstitution substitutionFail
       message: "Constraint #{printType constraint} is ambiguous"
-      conflicts: [originOf constraint]
+      conflicts: [constraint]
     return {}
   # return "{}" unless classParams
   for className, dict of values classParams
@@ -4716,7 +4716,7 @@
   [first, second] = sortBasedOnOriginPosition t1, t2
   substitutionFail
     message: "#{message} #{(safePrintType first)}, #{(safePrintType second)}"
-    conflicts: [(originOf first), (originOf second)]
+    conflicts: [first, second]
     types: [t1, t2]

 sortBasedOnOriginPosition = (t1, t2) ->
@@ -7350,6 +7350,7 @@

 exports.library = library

+exports.originOf = originOf
 exports.sortedArgs = sortedArgs

 exports.isForm = isForm


Allow views to be appended

Allow views to be appended

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -10,14 +10,21 @@
       @el = el

     show: (view) ->
-      @$el = @$el or @parent.$el.find(@el)
+      @close()
+      @append(view)
+
+    append: (view) ->
+      @$el = @$el or @parent.$el.find(@el).eq(0)
       view.parent = @parent
-      @view = view
-      @view.setElement(@$el).render()
+      @views = @views or []
+      @views.push(view)
+      view.setElement($('<div>').appendTo(@$el)).render()

     close: () ->
-      @view?.close()
-      @$el.empty()
+      _.each @views, (view) ->
+        view.close()
+
+      @$el?.empty()

   class Regions
     constructor: (regions, $context) ->
@@ -35,7 +42,7 @@

     close: () ->
       _.each @regions, (region) ->
-        region.view.close()
+        region.close()

       @stopListening()
       @remove()


fix for editing pages

fix for editing pages

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -88,14 +88,14 @@
       data = JSON.stringify({id: @model.asPage().get('id')})
       options =
         success: (model) =>
-          @model.asPage()?.set('version', 'draft')
-          @model.asPage()?.set('editable',true)
-          href = linksHelper.getPath 'contents',
-            model: @model
-            page: @model.getPageNumber()
-          router.navigate(href, {trigger: false, analytics: true})
+         pageNumber = @model.getPageNumber()
+         options =
+           success: (model) =>
+             router.navigate("/contents/#{model.id}@draft:#{pageNumber}", {trigger: false})

       @model.editOrDeriveContent(options, data)
+      @model.asPage()?.set('version', 'draft')
+      @model.asPage()?.set('editable',true)

     derivePage: () ->


Fix comment on xhr.coffee

Fix comment on xhr.coffee

--- src/zombie/xhr.coffee
+++ src/zombie/xhr.coffee
@@ -150,7 +150,7 @@
           return

       # Since the request was not aborted, we set all the fields here and change
-      # the state to HEADERS_RECIEVED.
+      # the state to HEADERS_RECEIVED.
       @status           = response.statusCode
       @statusText       = response.statusText
       @_responseHeaders = response.headers


do not send TeX code comment markers where not needed


always use `jr` to report warnings

always use `jr` to report warnings

--- src/tex-writer.coffee
+++ src/tex-writer.coffee
@@ -1718,7 +1718,7 @@
       #   first             = name
       #   last              = type
       # event_txt         = first + last + ' ' + text
-      event_txt = "unhandled event: #{JSON.stringify event, null, ' '}"
+      event_txt = "unhandled event: #{jr event}"
       send [ '.', 'warning', event_txt, ( copy meta ), ]
       # send stamp hide copy event
     else
@@ -1848,8 +1848,8 @@
       # debug '82341', event
       send event
     else unless ( type is '~' ) or ( is_stamped event )
-      warn "unhandled event: #{JSON.stringify event}"
-      send.error new Error "unhandled events not allowed at this point; got #{JSON.stringify event}"
+      warn "unhandled event: #{jr event}"
+      send.error new Error "unhandled events not allowed at this point; got #{jr event}"

 #-----------------------------------------------------------------------------------------------------------
 @$show_events = ( S ) ->


fix issues with currency utils

fix issues with currency utils

--- src/util/index.coffee
+++ src/util/index.coffee
@@ -6,6 +6,44 @@

 digitsOnlyRe = new RegExp('[^\\d.-]', 'g')

+isZeroDecimal = (code)->
+  if code == 'bif' || code == 'clp' || code == 'djf' || code == 'gnf' || code == 'jpy' || code == 'kmf' || code == 'krw' || code == 'mga' || code == 'pyg' || code == 'rwf' || code == 'vnd' || code == 'vuv' || code == 'xaf' || code == 'xof' || code == 'xpf'
+    return true
+  return false
+
+decimalFromCents = (code, jsonCurrency)->
+  if isNaN jsonCurrency
+    jsonCurrency = 0
+
+  currentCurrencySign = currencySigns[code]
+
+  jsonCurrency = '' + jsonCurrency
+  # jsonCurrency is not cents
+  if isZeroDecimal code
+    return currentCurrencySign + jsonCurrency
+
+  # jsonCurrency is cents
+  while jsonCurrency.length < 3
+    jsonCurrency = '0' + jsonCurrency
+
+  return currentCurrencySign + jsonCurrency.substr(0, jsonCurrency.length - 2) + '.' + jsonCurrency.substr(-2)
+
+centsFromDecimal = (code, uiCurrency) ->
+  currentCurrencySign = currencySigns[code]
+
+  if isZeroDecimal code
+    return parseInt(('' + uiCurrency).replace(digitsOnlyRe, '').replace(currencySeparator, ''), 10)
+
+  # uiCurrency is a whole unit of currency
+  parts = uiCurrency.split currencySeparator
+  if parts.length > 1
+    parts[1] = parts[1].substr(0, 2)
+    while parts[1].length < 2
+      parts[1] += '0'
+  else
+    parts[1] = '00'
+  return parseInt(parseFloat(parts[0].replace(digitsOnlyRe, '')) * 100 + parseFloat(parts[1].replace(digitsOnlyRe, '')), 10)
+
 export default util =
   time:
     rfc3339:    'YYYY-MM-DDTHH:mm:ssZ'
@@ -13,46 +51,10 @@
     moment:     moment

   currency:
-    isZeroDecimal: (code)->
-      if code == 'bif' || code == 'clp' || code == 'djf' || code == 'gnf' || code == 'jpy' || code == 'kmf' || code == 'krw' || code == 'mga' || code == 'pyg' || code == 'rwf' || code == 'vnd' || code == 'vuv' || code == 'xaf' || code == 'xof' || code == 'xpf'
-        return true
-      return false
+    isZeroDecimal: isZeroDecimal
+    centsFromDecimal: centsFromDecimal
+    decimalFromCents: decimalFromCents
+    updateDecimal: (code, uiCurrency)->
+      return decimalFromCents code, centsFromDecimal(code, uiCurrency)

-    updateDecimal: (code, uiCurrency)->
-      currentCurrencySign = currencySigns[code]

-      return Util.renderUICurrencyFromJSON Util.renderJSONCurrencyFromUI(uiCurrency)
-
-    decimalFromCents: (code, jsonCurrency)->
-      if isNaN jsonCurrency
-        jsonCurrency = 0
-
-      currentCurrencySign = currencySigns[code]
-
-      jsonCurrency = '' + jsonCurrency
-      # jsonCurrency is not cents
-      if isZeroDecimal code
-        return currentCurrencySign + jsonCurrency
-
-      # jsonCurrency is cents
-      while jsonCurrency.length < 3
-        jsonCurrency = '0' + jsonCurrency
-
-      return currentCurrencySign + jsonCurrency.substr(0, jsonCurrency.length - 2) + '.' + jsonCurrency.substr(-2)
-
-    centsFromDecimal: (code, uiCurrency) ->
-      currentCurrencySign = currencySigns[code]
-
-      if isZeroDecimal code
-        return parseInt(('' + uiCurrency).replace(digitsOnlyRe, '').replace(currencySeparator, ''), 10)
-
-      # uiCurrency is a whole unit of currency
-      parts = uiCurrency.split currencySeparator
-      if parts.length > 1
-        parts[1] = parts[1].substr(0, 2)
-        while parts[1].length < 2
-          parts[1] += '0'
-      else
-        parts[1] = '00'
-      return parseInt(parseFloat(parts[0].replace(digitsOnlyRe, '')) * 100 + parseFloat(parts[1].replace(digitsOnlyRe, '')), 10)
-


Fixed catchAll responding to itself and errors

--- src/scripts/access.coffee
+++ src/scripts/access.coffee
@@ -21,12 +21,12 @@
     command = context.listener.options.id
     message = context.response.message
     username = context.response.message.user.name
-    if command is null
+    if username is 'hubot' or username is 'ibizan'
+      # Ignore myself and messages overheard
+      done()
+    else if command is null
       # Ignore unknown commands or catch-alls
       next()
-    else if username is 'hubot' or username is 'ibizan'
-      # Ignore myself and messages overheard
-      done()
     else
       if not Organization.ready()
         # Organization is not ready, ignore command


Object: Code styling.

Object: Code styling.

--- src/lib/core/Object.coffee
+++ src/lib/core/Object.coffee
@@ -28,7 +28,6 @@
     @setOptions options if options
     @setData data       if data
     @freeze()           if options.frozen
-

   ###*


Update description when receiving components

--- src/runtimes/base.coffee
+++ src/runtimes/base.coffee
@@ -84,6 +84,9 @@
       # Update instances we already have
       if @instances[definition.name]
         @instances[definition.name].forEach @updateInstancePorts
+      # Update the description
+      if definition.description
+        @types[definition.name].description = definition.description

   updateInstancePorts: (instance) =>
     for port in @components[instance.type].inPorts


Remove conditional polyfilling again. es6-promise should always be included since native Promise in Chrome is buggy. object-assign is so small it makes no sense to conditionally load on its own. fetch is a bit unfinished, and doesn't support cookies, so giving up on that for now.

--- src/scripts/index.coffee
+++ src/scripts/index.coffee
@@ -1,38 +1,33 @@
-run = () ->
-	ReactDOM = require 'react-dom'
-	cookie = require 'cookie'
-	{ XmlEntities } = require 'html-entities'
+require('es6-promise').polyfill()
+assign = require 'object-assign'
+ReactDOM = require 'react-dom'
+cookie = require 'cookie'
+{ XmlEntities } = require 'html-entities'

-	init = require './init.coffee'
-	{ store } = require './store.coffee'
-	userActions = require './actions/user-actions.coffee'
-	routes = require './views/routes.coffee'
-	createFactory = require './create-factory.coffee'
-	Root = createFactory require './views/root.coffee'
-	appActions = require './actions/app-actions.coffee'
+init = require './init.coffee'
+{ store } = require './store.coffee'
+userActions = require './actions/user-actions.coffee'
+routes = require './views/routes.coffee'
+createFactory = require './create-factory.coffee'
+Root = createFactory require './views/root.coffee'
+appActions = require './actions/app-actions.coffee'

-	cookies = cookie.parse document.cookie
-	if cookies.session
-		session = JSON.parse atob cookies.session
-		if session.user && session.timestamp
-			store.dispatch userActions.setUser session.user, session.timestamp
+Object.assign || (Object.assign = assign)

-	entities = new XmlEntities()
-	serverState = document.getElementById('state').textContent
-	data = JSON.parse entities.decode serverState
-	store.dispatch appActions.init data
-	state = store.getState()
-	params = state.routing.state || {}
-	init params, document.documentElement.lang
-		.then ->
-			ReactDOM.render Root(), document.getElementById 'app'
-		.catch (err) ->
-			console.error err.stack
+cookies = cookie.parse document.cookie
+if cookies.session
+	session = JSON.parse atob cookies.session
+	if session.user && session.timestamp
+		store.dispatch userActions.setUser session.user, session.timestamp

-if window.Promise && Object.assign
-	run()
-else
-	require [ 'es6-promise', 'object-assign' ], (promiseLib, assign) ->
-		window.Promise || (window.Promise = promiseLib.Promise)
-		Object.assign || (Object.assign = assign)
-		run()
+entities = new XmlEntities()
+serverState = document.getElementById('state').textContent
+data = JSON.parse entities.decode serverState
+store.dispatch appActions.init data
+state = store.getState()
+params = state.routing.state || {}
+init params, document.documentElement.lang
+	.then ->
+		ReactDOM.render Root(), document.getElementById 'app'
+	.catch (err) ->
+		console.error err.stack


Fixed a single line return if statement.

--- client/Framework/components/tabs/KDTabHandleView.coffee
+++ client/Framework/components/tabs/KDTabHandleView.coffee
@@ -89,7 +89,7 @@

   handleDragInAction: (x, y) ->
     return unless @dragIsAllowed
-    return if -(@draggedItemIndex * @getWidth()) > x @$().css 'left': 0
+    return @$().css 'left': 0 if -(@draggedItemIndex * @getWidth()) > x

     @cloneElement x
     @$().css opacity: 0.01


AccountReferralListItem: revive JAccounts while listing them.

--- client/account/lib/accountreferralsystemlistitem.coffee
+++ client/account/lib/accountreferralsystemlistitem.coffee
@@ -6,20 +6,17 @@

 module.exports = class AccountReferralSystemListItem extends KDListItemView

-  viewAppended: ->
-    @getData().isEmailVerified (err, status)=>
-      unless (err or status)
-        @addSubView editLink = new KDCustomHTMLView
-           tagName      : "a"
-           partial      : "Mail Verification Waiting"
-           cssClass     : "action-link"
   constructor: (options = {}, data)->
     options.tagName ?= 'li'
     super options, data

-  partial: (data)->
-    """
-     #{data.profile.firstName} #{data.profile.lastName}
-    """
+  viewAppended: ->

+    {providedBy} = @getData()
+    remote.cacheable "JAccount", providedBy, (err, account)=>
+      {profile} = account  if account
+      @addSubView new KDCustomHTMLView
+        partial: err or """
+           #{profile.firstName} #{profile.lastName}
+        """


reactivity: chatlistitem: tests for edit callbacks

--- client/activity/lib/components/chatlistitem/test.coffee
+++ client/activity/lib/components/chatlistitem/test.coffee
@@ -17,6 +17,8 @@
 ChannelToken            = require 'activity/components/chatinputwidget/tokens/channeltoken'
 EmojiToken              = require 'activity/components/chatinputwidget/tokens/emojitoken'
 MentionToken            = require 'activity/components/chatinputwidget/tokens/mentiontoken'
+ChatInputWidget         = require 'activity/components/chatinputwidget'
+ActivityFlux            = require 'activity/flux'

 describe 'ChatListItem', ->

@@ -150,3 +152,68 @@
       expect(inputEmbedExtractor.props.channelId).toEqual 1
       expect(inputEmbedExtractor.props.value).toEqual editingMessage.get 'body'
       expect(inputEmbedExtractor.props.tokens).toEqual [ChannelToken, EmojiToken, MentionToken]
+
+
+  describe '::onSubmit', ->
+
+    it 'should be called when edited message is saved', ->
+
+      props =
+        message   : editingMessage
+        channelId : 1
+        onSubmit  : kd.noop
+      spy = expect.spyOn props, 'onSubmit'
+
+      result = TestUtils.renderIntoDocument(
+        <ChatListItem {...props} />
+      )
+
+      newValue = "#{editingMessage.get('body')}!!!"
+
+      inputWidget = TestUtils.findRenderedComponentWithType result, ChatInputWidget.Container
+      inputWidget.setState { value : newValue }
+
+      submitButton = TestUtils.findRenderedDOMComponentWithClass result, 'submit'
+      TestUtils.Simulate.click submitButton
+
+      expect(spy).toHaveBeenCalledWith newValue
+
+
+  describe '::onCancelEdit', ->
+
+    it 'should be called when message editing is cancelled', ->
+
+      props =
+        message      : editingMessage
+        channelId    : 1
+        onCancelEdit : kd.noop
+      spy = expect.spyOn props, 'onCancelEdit'
+
+      result = TestUtils.renderIntoDocument(
+        <ChatListItem {...props} />
+      )
+
+      cancelButton = TestUtils.findRenderedDOMComponentWithClass result, 'cancel'
+      TestUtils.Simulate.click cancelButton
+
+      expect(spy).toHaveBeenCalled()
+
+
+  describe '::onCloseEmbedBox', ->
+
+    it 'should be called when embed box is closed', ->
+
+      props =
+        message         : editingMessage
+        channelId       : 1
+        onCloseEmbedBox : kd.noop
+      spy = expect.spyOn props, 'onCloseEmbedBox'
+
+      result = TestUtils.renderIntoDocument(
+        <ChatListItem {...props} />
+      )
+
+      closeButton = TestUtils.findRenderedDOMComponentWithClass result, 'close-button'
+      TestUtils.Simulate.click closeButton
+
+      expect(spy).toHaveBeenCalled()


fixed infinite loop of logout route

fixed infinite loop of logout route

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -144,21 +144,20 @@

   doLogout:->

+    KD.logout()
+    KD.remote.api.JUser.logout (err, account, replacementToken)=>
+      $.cookie 'clientId', replacementToken if replacementToken
+      @accountChanged account
+
     # fixme: make a old tv switch off animation and reload
     # $('body').addClass "turn-off"
-    return location.reload yes
+    return location.reload()

     @getSingleton("lazyDomController").showLandingPage =>
       # @loginScreen.showView =>
       KD.getSingleton("appManager").quitAll =>
         @mainViewController.sidebarController.accountChanged account
         @mainViewController.getView().decorateLoginState no
-
-    KD.logout()
-    KD.remote.api.JUser.logout (err, account, replacementToken)=>
-      $.cookie 'clientId', replacementToken if replacementToken
-      @accountChanged account
-
       # @mainViewController.getView().$().css 'opacity', 0

       new KDNotificationView


Login: doReset: route to login page after password reset

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -231,8 +231,12 @@
         @resetForm.button.hideLoader()
         @resetForm.reset()
         @headBanner.hide()
-        @doLogin {username, password}
-
+
+        new KDNotificationView title : 'Password changed, you can login now'
+
+        { config: { entryPoint }, singletons: { router } } = KD
+        router.handleRoute '/Login', { entryPoint }
+

   doRecover:({ email })->
     $.ajax


avoid crashing when root pass is not set

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -221,7 +221,7 @@
     log.info "server to mpd connect"
     connect_success = true
     my_mpd.handleConnectionStart()
-    if root_pass.length > 0
+    if root_pass? and root_pass.length > 0
       my_mpd.authenticate root_pass

     # connect socket clients to mpd


minor: typo fix

minor: typo fix

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -19,7 +19,7 @@
     @tabView.on 'MachineWebPageRequested',  @bound 'openMachineWebPage'
     @tabView.on 'ShortcutsViewRequested',   @bound 'createShortcutsView'
     @tabView.on 'TerminalPaneRequested',    @bound 'createTerminal'
-    #absolete: 'preview file' feature was removed (bug #82710798)
+    # obsolete: 'preview file' feature was removed (bug #82710798)
     @tabView.on 'PreviewPaneRequested',     (url) -> window.open "http://#{url}"
     @tabView.on 'DrawingPaneRequested',     @bound 'createDrawingBoard'
     @tabView.on 'ViewNeedsToBeShown',       @bound 'showView'


Deprecated .param() fix

Deprecated .param() fix

--- main.coffee
+++ main.coffee
@@ -181,12 +181,12 @@

 fetchCharacterFromURL = ((request, response) ->
-	request.uonline.fetched_character = lib.game.getCharacter.sync null, dbConnection, request.param('name')
+	request.uonline.fetched_character = lib.game.getCharacter.sync null, dbConnection, request.params.name
 ).asyncMiddleware()

 fetchMonsterFromURL = ((request, response) ->
-	chars = lib.game.getCharacter.sync null, dbConnection, request.param 'id'
+	chars = lib.game.getCharacter.sync null, dbConnection, request.params.id
 	if not chars?
 		throw new Error '404'
 	for i of chars
@@ -247,7 +247,7 @@

 fetchStatsFromURL = ((request, response) ->
-	chars = lib.game.getUserCharacters.sync null, dbConnection, request.param 'username'
+	chars = lib.game.getUserCharacters.sync null, dbConnection, request.params.username
 	if not chars?
 		throw new Error '404'
 	for i of chars
@@ -417,7 +417,7 @@
 app.get '/action/go/:to',
 	mustBeAuthed,
 	(request, response) ->
-		result = lib.game.changeLocation.sync null, dbConnection, request.uonline.user.character_id, request.param 'to'
+		result = lib.game.changeLocation.sync null, dbConnection, request.uonline.user.character_id, request.params.to
 		if result.result != 'ok'
 			console.error "Location change failed: #{result.reason}"
 		response.redirect '/game/'
@@ -443,7 +443,7 @@
 		lib.game.hitOpponent.sync(
 			null, dbConnection,
 			request.uonline.user.character_id,
-			request.param('id')
+			request.params.id
 		)
 		response.redirect '/game/'

@@ -451,8 +451,8 @@
 app.get '/ajax/isNickBusy/:nick',
 	(request, response) ->
 		response.json
-			nick: request.param('nick')
-			isNickBusy: lib.user.userExists.sync null, dbConnection, request.param('nick')
+			nick: request.params.nick
+			isNickBusy: lib.user.userExists.sync null, dbConnection, request.params.nick

 app.get '/ajax/cheatFixAll',
@@ -474,7 +474,7 @@
 			'UPDATE armor '+
 				'SET equipped = false '+
 				'WHERE id = $1 AND owner = $2',
-			[request.param('id'), request.uonline.user.character_id]
+			[request.params.id, request.uonline.user.character_id]
 		response.redirect '/inventory/'

@@ -485,14 +485,14 @@
 			'UPDATE armor '+
 				'SET equipped = true '+
 				'WHERE id = $1 AND owner = $2',
-			[request.param('id'), request.uonline.user.character_id]
+			[request.params.id, request.uonline.user.character_id]
 		response.redirect '/inventory/'

 app.get '/action/switch_character/:id',
 	mustBeAuthed,
 	(request, response) ->
-		lib.game.switchCharacter dbConnection, request.uonline.user.id, request.param('id')
+		lib.game.switchCharacter dbConnection, request.uonline.user.id, request.params.id
 		response.redirect 'back'


Clean up main.coffee a bit.

Clean up main.coffee a bit.

--- main.coffee
+++ main.coffee
@@ -33,6 +33,7 @@
 sync = require 'sync'

+# Connect to database
 dbConnection = anyDB.createPool config.DATABASE_URL, min: 2, max: 20
 dbConnection.query 'SELECT version()', [], (error, result) ->
 	if error?
@@ -60,7 +61,9 @@
 			# console.log "\n#{time}: t: #{logged}\n"
 			if cb? then cb(error, result)

+
 # Set up Express
+
 app = express()
 app.enable 'trust proxy'
 app.use express.logger()
@@ -94,6 +97,8 @@
 if newrelic?
 	app.locals.newrelic = newrelic

+
+# Middlewares

 mustBeAuthed = (request, response, next) ->
 	if request.uonline.basicOpts.loggedIn is true
@@ -133,7 +138,14 @@
 ).asyncMiddleware()

-# routing routines
+# Trivial pages
+
+quickRender = (template) ->
+	(request, response) ->
+		options = request.uonline.basicOpts
+		options.instance = template
+		response.render template, options
+

 app.get '/node/', (request, response) ->
 	response.send 'Node.js is up and running.'
@@ -141,22 +153,6 @@

 app.get '/explode/', (request, response) ->
 	throw new Error 'Emulated error.'
-
-
-# real ones
-
-quickRender = (request, response, template) ->
-	options = request.uonline.basicOpts
-	options.instance = template
-	response.render template, options
-
-
-quickRenderError = (request, response, code) ->
-	options = request.uonline.basicOpts
-	options.code = code
-	options.instance = 'error'
-	response.status code
-	response.render 'error', options

 app.get '/', (request, response) ->
@@ -166,13 +162,12 @@
 		response.redirect config.defaultInstanceForGuests

-app.get '/about/', (request, response) ->
-	quickRender request, response, 'about'
-
-
-app.get '/register/', mustNotBeAuthed, (request, response) ->
-	quickRender request, response, 'register'
-
+app.get '/about/', quickRender 'about'
+app.get '/register/', mustNotBeAuthed, quickRender 'register'
+app.get '/login/', mustNotBeAuthed, quickRender 'login'
+
+
+# And the rest

 app.post '/register/', mustNotBeAuthed, (request, response) ->
 	options = request.uonline.basicOpts
@@ -200,10 +195,6 @@
 		response.render 'register', options

-app.get '/login/', mustNotBeAuthed, (request, response) ->
-	quickRender request, response, 'login'
-
-
 app.post '/login/', mustNotBeAuthed, (request, response) ->
 	if lib.user.accessGranted.sync null, dbConnection, request.body.user, request.body.pass
 		sessid = lib.user.createSession.sync null, dbConnection, request.body.user
@@ -332,20 +323,22 @@

 # Exception handling
 app.use (error, request, response, next) ->
-	if error.message is '404'
-		quickRenderError request, response, 404
-	else
-		console.error error.stack
-		quickRenderError request, response, 500
+	code = if error.message is '404' then 404 else 500
+	options = request.uonline.basicOpts
+	options.code = code
+	options.instance = 'error'
+	response.status code
+	response.render 'error', options

 # main

 DEFAULT_PORT = 5000
 port = process.env.PORT or process.env.OPENSHIFT_NODEJS_PORT or DEFAULT_PORT
-ip = process.env.OPENSHIFT_NODEJS_IP or undefined
+ip = process.env.OPENSHIFT_NODEJS_IP or process.env.IP or undefined
 console.log "Starting up on port #{port}, and IP is #{ip}"
-startupFinished = () ->
+
+startupFinished = ->
 	console.log "Listening on port #{port}"
 	if port is DEFAULT_PORT then console.log "Try http://localhost:#{port}/"


:couplekiss::link: Updated at https://danielx.net/editor/

--- main.coffee
+++ main.coffee
@@ -16,7 +16,7 @@
 doItLive = false

 setTimeout ->
-  gh.api('orgs/distri/repos')
+  gh.api('users/STRd6/repos')
   .then (data) ->
     convertResultPage(data)
   .catch (e) ->
@@ -66,15 +66,22 @@
     files.map convertFile
   .then (files) ->
     if doItLive
-      repo.commitTree
-        baseTree: true
-        branch: "gh-pages"
-        message: "Converting .json.js to .json"
-        tree: files
+      if files.length > 0
+        repo.commitTree
+          baseTree: true
+          branch: "gh-pages"
+          message: "Converting .json.js to .json"
+          tree: files
     else
       console.log "Dry run #{uri}"
   .then ->
     log "converted #{uri}"
+  .catch (e) ->
+    if e.status is 404
+      log "Skipped:", uri
+    else
+      console.error e
+      throw e

 convertFile = (file) ->
   path: file.path.replace /\.js$/, ""


refactor li

refactor li

--- src/simple-select.coffee
+++ src/simple-select.coffee
@@ -5,5 +5,6 @@
 	result.text 'Select...'
 	ul = $('
').css 'display', 'none'
 	Object.keys(list).forEach (el)->
-		ul.append("
#{el}
")
+		li = $('
').attr('data-value', list[el]).text(el)
+		ul.append(li)
 	@.append(result).append(ul)


bugfix

bugfix

--- controllers/notifications.coffee
+++ controllers/notifications.coffee
@@ -26,6 +26,7 @@
   url = req.body.url
   Team.findOne 'entry.url': req.body.url, (err, team) ->
     return next(err) if err
+    return res.send(200) if not team

     # save when the judge viewed the entry
     team.judgeVisitedAt = new Date
@@ -36,7 +37,7 @@

       # if the url doesn't belong to team, or the team is does not have alerts
       # enabled, just ignore it
-      return res.send(200) unless team?.entry?.alert
+      return res.send(200) unless team.entry?.alert

       # load the twitter handles for the team
       team.twitterScreenNames (err, handles) ->


Fixed conditional with comment

Fixed conditional with comment

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -43,7 +43,9 @@
         @main.tabsRegion.show(@getUserTabs(user, options.active_tab))
         options.render(@main.contentRegion, user)

-        if Factlink.Global.can_haz['sees_channels'] && user.get('deleted')
+        # Remove when permanently disabling sees_channels feature toggle,
+        # as this is only for the channels sidebar
+        if user.get('deleted')
           FactlinkApp.leftMiddleRegion.close()

   switchToPage: (username, user, path, options)->


Use package name from config

Use package name from config

--- src/tools/remotes.coffee
+++ src/tools/remotes.coffee
@@ -81,7 +81,7 @@
   npmrc.WriteLine """

     # <hack dirty src="#{PACKAGE.homepage}">
-    prefix=${APPDATA}\\nvm$\\current
+    prefix=${APPDATA}\\#{PACKAGE.mingzi}\\current
     # </hack>
     """
   npmrc.Close()


Add more cyber™

Add more cyber™

--- app/assets/javascripts/application/files.js.coffee
+++ app/assets/javascripts/application/files.js.coffee
@@ -31,7 +31,7 @@
     $path.append "/"

   # Set window title
-  document.title = $("h1").text()
+  document.title = "cyber of " + $("h1").text()

   # Load directory content
   $.getJSON "/data/#{server_id}/entries/#{path}index.json", (data, status, xhr)->


disable slotEventOverlap for agendaDay view

--- app/assets/javascripts/controllers/calendar.coffee
+++ app/assets/javascripts/controllers/calendar.coffee
@@ -13,7 +13,7 @@

   ### PUBLIC SCOPE ###

-  ## bind the availabilities slots with full-Calendar events
+  ## add availabilities url to event sources
   $scope.eventSources = []
   $scope.eventSources.push
     url: '/api/availabilities/public'
@@ -21,7 +21,7 @@

   ## fullCalendar (v2) configuration
   $scope.calendarConfig = CalendarConfig
-    slotEventOverlap: false
+    slotEventOverlap: true
     header:
       left: 'month agendaWeek agendaDay'
       center: 'title'
@@ -31,20 +31,41 @@
     eventClick: (event, jsEvent, view)->
       calendarEventClickCb(event, jsEvent, view)
     viewRender: (view, element) ->
-      if view.type == 'agendaDay'
-        uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents')
+      viewRenderCb(view, element)

   ### PRIVATE SCOPE ###

   calendarEventClickCb = (event, jsEvent, view) ->
-    console.log event
     ## current calendar object
     calendar = uiCalendarConfig.calendars.calendar
     if event.available_type == 'machines'
+      calendar.fullCalendar('changeView', 'agendaDay')
       calendar.fullCalendar('gotoDate', event.start)
-      calendar.fullCalendar('changeView', 'agendaDay')
     else
       if event.available_type == 'event'
         $state.go('app.public.events_show', {id: event.event_id})
+
+  ## agendaDay view: disable slotEventOverlap
+  ## agendaWeek view: enable slotEventOverlap
+  toggleSlotEventOverlap = (view) ->
+    # set defaultView, because when we change slotEventOverlap
+    # ui-calendar will trigger rerender calendar
+    $scope.calendarConfig.defaultView = view.type
+    today = moment().startOf('day')
+    if today > view.start and today <= view.end and today != view.start
+      $scope.calendarConfig.defaultDate = today
+    else
+      $scope.calendarConfig.defaultDate = view.start
+    if view.type == 'agendaDay'
+      $scope.calendarConfig.slotEventOverlap = false
+    else
+      $scope.calendarConfig.slotEventOverlap = true
+
+  ## function is called when calendar view is rendered or changed
+  viewRenderCb = (view, element) ->
+    toggleSlotEventOverlap(view)
+    if view.type == 'agendaDay'
+      # get availabilties by 1 day for show machine slots
+      uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents')
 ]


Terser is better.

Terser is better.

--- app/assets/javascripts/roofpig/Positions.js.coffee
+++ app/assets/javascripts/roofpig/Positions.js.coffee
@@ -27,58 +27,25 @@

   @for_side: (side) ->
-    return this._for_u() if side == Side.U
-    return this._for_d() if side == Side.D
-    return this._for_f() if side == Side.F
-    return this._for_b() if side == Side.B
-    return this._for_l() if side == Side.L
-    return this._for_r() if side == Side.R
+    return [@at.UFR, @at.UFL, @at.UBR, @at.UBL, @at.UF, @at.UB, @at.UL, @at.UR, Pieces3D.U]  if side == Side.U
+    return [@at.DFR, @at.DFL, @at.DBR, @at.DBL, @at.DF, @at.DB, @at.DL, @at.DR, Pieces3D.D]  if side == Side.D
+    return [@at.UFL, @at.UFR, @at.DFR, @at.DFL, @at.UF, @at.FR, @at.DF, @at.FL, Pieces3D.F]  if side == Side.F
+    return [@at.UBL, @at.UBR, @at.DBR, @at.DBL, @at.UB, @at.BR, @at.DB, @at.BL, Pieces3D.B]  if side == Side.B
+    return [@at.UFL, @at.DFL, @at.DBL, @at.UBL, @at.UL, @at.FL, @at.DL, @at.BL, Pieces3D.L]  if side == Side.L
+    return [@at.UFR, @at.DFR, @at.DBR, @at.UBR, @at.UR, @at.FR, @at.DR, @at.BR, Pieces3D.R]  if side == Side.R

   @move_pieces: (side, turns) ->
-    this._u_move(turns)  if side == Side.U
-    this._d_move(turns)  if side == Side.D
-    this._f_move(turns)  if side == Side.F
-    this._b_move(turns)  if side == Side.B
-    this._l_move(turns)  if side == Side.L
-    this._r_move(turns)  if side == Side.R
+    this._move(turns, ['UBR', 'UBL', 'UFL', 'UFR'], ['UR', 'UB', 'UL', 'UF'])  if side == Side.U
+    this._move(turns, ['DFR', 'DFL', 'DBL', 'DBR'], ['DF', 'DL', 'DB', 'DR'])  if side == Side.D
+    this._move(turns, ['DFL', 'DFR', 'UFR', 'UFL'], ['FL', 'DF', 'FR', 'UF'])  if side == Side.F
+    this._move(turns, ['UBL', 'UBR', 'DBR', 'DBL'], ['UB', 'BR', 'DB', 'BL'])  if side == Side.B
+    this._move(turns, ['UBL', 'DBL', 'DFL', 'UFL'], ['BL', 'DL', 'FL', 'UL'])  if side == Side.L
+    this._move(turns, ['UFR', 'DFR', 'DBR', 'UBR'], ['UR', 'FR', 'DR', 'BR'])  if side == Side.R

+  @_move: (turns, corners, edges) ->
+    for n in [1..turns]
+      this._permute(corners)
+      this._permute(edges)

-  @_for_u: -> [@at.UFR, @at.UFL, @at.UBR, @at.UBL, @at.UF, @at.UB, @at.UL, @at.UR, Pieces3D.U]
-  @_for_d: -> [@at.DFR, @at.DFL, @at.DBR, @at.DBL, @at.DF, @at.DB, @at.DL, @at.DR, Pieces3D.D]
-  @_for_f: -> [@at.UFL, @at.UFR, @at.DFR, @at.DFL, @at.UF, @at.FR, @at.DF, @at.FL, Pieces3D.F]
-  @_for_b: -> [@at.UBL, @at.UBR, @at.DBR, @at.DBL, @at.UB, @at.BR, @at.DB, @at.BL, Pieces3D.B]
-  @_for_l: -> [@at.UFL, @at.DFL, @at.DBL, @at.UBL, @at.UL, @at.FL, @at.DL, @at.BL, Pieces3D.L]
-  @_for_r: -> [@at.UFR, @at.DFR, @at.DBR, @at.UBR, @at.UR, @at.FR, @at.DR, @at.BR, Pieces3D.R]
-
-  @_u_move: (turns) ->
-    for n in [1..turns]
-      this._permute('UBR', 'UBL', 'UFL', 'UFR')
-      this._permute('UR', 'UB', 'UL', 'UF')
-
-  @_d_move: (turns) ->
-    for n in [1..turns]
-      this._permute('DFR', 'DFL', 'DBL', 'DBR')
-      this._permute('DF', 'DL', 'DB', 'DR')
-
-  @_f_move: (turns) ->
-    for n in [1..turns]
-      this._permute('DFL', 'DFR', 'UFR', 'UFL')
-      this._permute('FL',  'DF',  'FR',  'UF')
-
-  @_b_move: (turns) ->
-    for n in [1..turns]
-      this._permute('UBL', 'UBR', 'DBR', 'DBL')
-      this._permute('UB', 'BR', 'DB', 'BL')
-
-  @_l_move: (turns) ->
-    for n in [1..turns]
-      this._permute('UBL', 'DBL', 'DFL', 'UFL')
-      this._permute('BL', 'DL', 'FL', 'UL')
-
-  @_r_move: (turns) ->
-    for n in [1..turns]
-      this._permute('UFR', 'DFR', 'DBR', 'UBR')
-      this._permute('UR', 'FR', 'DR', 'BR')
-
-  @_permute: (p1, p2, p3, p4) ->
-    [@at[p1], @at[p2], @at[p3], @at[p4]] = [@at[p2], @at[p3], @at[p4], @at[p1]]
+  @_permute: (p) ->
+    [@at[p[0]], @at[p[1]], @at[p[2]], @at[p[3]]] = [@at[p[1]], @at[p[2]], @at[p[3]], @at[p[0]]]


Client: Activity: Feed: Added showPopularMessages handler to set route

--- client/activity/lib/components/publicfeedpane/feedpanetabcontainer.coffee
+++ client/activity/lib/components/publicfeedpane/feedpanetabcontainer.coffee
@@ -1,14 +1,24 @@
-kd        = require 'kd'
-React     = require 'kd-react'
-ReactDOM  = require 'react-dom'
-immutable = require 'immutable'
-Link      = require 'app/components/common/link'
-
+kd           = require 'kd'
+React        = require 'kd-react'
+ReactDOM     = require 'react-dom'
+immutable    = require 'immutable'
+Link         = require 'app/components/common/link'
+classnames   = require 'classnames'
+ActivityFlux = require 'activity/flux'

 module.exports = class FeedPaneTabContainer extends React.Component

   @defaultProps=
-    thread = immutable.Map()
+    thread              : immutable.Map()
+    showPopularMessages : no
+
+
+  showPopularMessages: (event) ->
+
+    kd.utils.stopDOMEvent event
+
+    @handleRoute 'Liked'
+

   render: ->


groups: fixed invitation tab not hiding when invitations are switched off

--- client/landing/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Groups.kdapplication/AppController.coffee
@@ -612,13 +612,13 @@
     if policy.invitationsEnabled
       unless view.tabView.getPaneByName 'Invitations'
         @showInvitationsTab group, view
-      unless view.tabView.getPaneByName 'Approvals'
+      if view.tabView.getPaneByName 'Approvals'
         @hideApprovalTab view
     else
       if policy.approvalEnabled
         unless view.tabView.getPaneByName 'Approvals'
           @showApprovalTab group, view
-        unless view.tabView.getPaneByName 'Invitations'
+        if view.tabView.getPaneByName 'Invitations'
           @hideInvitationsTab view
       else
         @hideInvitationsTab view


fix scale

fix scale

--- app/classifier/drawing-tools/column-rectangle.cjsx
+++ app/classifier/drawing-tools/column-rectangle.cjsx
@@ -45,19 +45,20 @@
   initCoords: null

   render: ->
+    console.log('props', @props)
     {x, y, width} = @props.mark

     <DrawingToolRoot tool={this}>
       <Draggable onDrag={@handleMainDrag} disabled={@props.disabled}>
-        <rect x={x} y={y} width={width} height={@props.containerRect.height} />
+        <rect x={x} y={y} width={width} height={@props.containerRect.height / @props.scale.vertical} />
       </Draggable>

       {if @props.selected
         <g>
           <DeleteButton tool={this} x={x + width + 15} y={y + 10} />

-          <DragHandle x={x} y={@props.containerRect.height / 2} scale={@props.scale} onDrag={@handleLeftDrag} onEnd={@normalizeMark} />
-          <DragHandle x={x + width} y={@props.containerRect.height / 2} scale={@props.scale} onDrag={@handleRightDrag} onEnd={@normalizeMark} />
+          <DragHandle x={x} y={(@props.containerRect.height / @props.scale.vertical) / 2} scale={@props.scale} onDrag={@handleLeftDrag} onEnd={@normalizeMark} />
+          <DragHandle x={x + width} y={(@props.containerRect.height / @props.scale.vertical) / 2} scale={@props.scale} onDrag={@handleRightDrag} onEnd={@normalizeMark} />
         </g>}
     </DrawingToolRoot>


added a handler for text/plain files sent back

--- app/helper/resultHandlers/fileResultHandler.coffee
+++ app/helper/resultHandlers/fileResultHandler.coffee
@@ -59,8 +59,15 @@
                   file.file['url'] = process.outputImageUrl
                   if file.file.options.visualization
                     visualization = true
-
                   delete file.file.content
+                else if file.file['mime-type'] == 'text/plain'
+                  IoHelper.saveFile(process.outputFolder + '/' + file.file.name + '.txt', file.file.content, () ->
+                    if process.hasImages
+                      file.file['url'] = IoHelper.getStaticImageUrl(process.rootFolder + '/' + process.methodFolder, file.file.name + '.txt')
+                    else if process.hasFiles
+                      file.file['url'] = IoHelper.getStaticDataUrl(process.rootFolder + '/' + process.methodFolder, file.file.name + '.txt')
+                    delete file.file.content
+                  )
                 else
                   IoHelper.saveFileBase64(process.outputFolder + '/' + file.file.filename, file.file.content, () ->
                     if process.hasImages


View -> Element in docs

View -> Element in docs

--- src/view-registry.coffee
+++ src/view-registry.coffee
@@ -89,22 +89,22 @@
   #
   # ## Examples
   #
-  # ### Getting An Editor View
+  # ### Getting An Editor Element
   # ```coffee
   # textEditor = atom.workspace.getActiveTextEditor()
-  # textEditorView = atom.views.getView(textEditor)
+  # textEditorElement = atom.views.getView(textEditor)
   # ```
   #
-  # ### Getting A Pane View
+  # ### Getting A Pane Element
   # ```coffee
   # pane = atom.workspace.getActivePane()
-  # paneView = atom.views.getView(pane)
+  # paneElement = atom.views.getView(pane)
   # ```
   #
-  # ### Getting The Workspace View
+  # ### Getting The Workspace Element
   #
   # ```coffee
-  # workspaceView = atom.views.getView(atom.workspace)
+  # workspaceElement = atom.views.getView(atom.workspace)
   # ```
   #
   # * `object` The object for which you want to retrieve a view. This can be a


Do not print the email...

Do not print the email...

--- app.coffee
+++ app.coffee
@@ -52,7 +52,6 @@

 app.post '/subscribe', (req, res) ->
   email = req.body.email
-  console.log req.body

   # Validate email
   if not /@/.test(email)


fix(routes/groups.coffee): 回滚错误代码

fix(routes/groups.coffee): 回滚错误代码

--- routes/groups.coffee
+++ routes/groups.coffee
@@ -10,10 +10,7 @@
     console.log('Error' + err)

   client.smembers 'groups', (err, replies) ->
-    group_alias = {}
-    for group in replies
       res.render 'groups',
-        group_alias: group_alias
         groups: replies
         title: "Alarm"
   client.quit()


restore pagination in logs table

restore pagination in logs table

--- app/assets/javascripts/chaltron/datatables.js.coffee
+++ app/assets/javascripts/chaltron/datatables.js.coffee
@@ -13,7 +13,6 @@
     # logs
     div = $('table#logs')
     @initTable(container: div, params: {
-      paging: false,
       # default sorting: date (1st column) asc
       aaSorting: [[0,'desc']]
       }


Only respond to button/link elements for collapse

@gvarela, this is more of hot fix for #2 than anything else
and may break on some weird edge cases I can think of.

I think there might be something inherently wrong trying to
use `Toggle` and `ToggleGroup` through composition within
`Collapse`. Would love some feedback when you have some time.

--- app/assets/utensils/toggle_group/toggle_group.coffee
+++ app/assets/utensils/toggle_group/toggle_group.coffee
@@ -60,7 +60,9 @@

   triggered: (e, target) ->
     @setTargets() unless @targets
-    element = $(target).closest("#{@data.target}:not(#{@data.ignore})", @targets)
+    $target = $(target)
+    return unless $target.parent('a, button').length or $target.is('a,button')
+    element = $target.closest("#{@data.target}:not(#{@data.ignore})", @targets)
     return if !element.length or @behavior is 'radio' and element.hasClass(@toggle_classes)
     if @behavior is 'radio' then @radio(element) else @checkbox(element)
     @el.trigger("#{@namespace}:triggered", element)


remove a temporary hack

remove a temporary hack

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -1009,16 +1009,6 @@
                 opts.path = '.sagemathcloud-local/root' + opts.path  # use root symlink, which is created by start_smc
             else
                 opts.path = '.sagemathcloud/root' + opts.path  # use root symlink, which is created by start_smc
-
-            # TODO: this is temporary code *only* until the project servers get restarted
-            if not @_created_root_link?
-                @exec
-                    project_id : opts.project_id
-                    command    : 'cd "$SAGEMATHCLOUD"; ln -s / root'
-                    bash       : true
-                    cb         : (err, output) =>   # ignored
-                        #console.log("create root link: ", err, output)
-                        @_created_root_link = true

         url = "#{base}/#{opts.project_id}/raw/#{opts.path}"
         #console.log(url)


Add an opt-out for transition cache

Add an opt-out for transition cache

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -15,11 +15,15 @@
   cacheCurrentPage()
   reflectNewUrl url

-  if cachedPage = pageCache[url]
+  if cachedPage = transitionCacheFor url
     fetchHistory cachedPage
     fetchReplacement url
   else
     fetchReplacement url, resetScrollPosition
+
+transitionCacheFor = (url) ->
+  cachedPage = pageCache[url]
+  cachedPage if cachedPage and !cachedPage.transitionCacheDisabled

 fetchReplacement = (url, onLoadFunction = =>) ->
   triggerEvent 'page:fetch', url: url
@@ -55,11 +59,12 @@

 cacheCurrentPage = ->
   pageCache[currentState.url] =
-    body:             document.body,
-    title:            document.title,
-    positionY:        window.pageYOffset,
-    positionX:        window.pageXOffset,
-    cachedAt:         new Date().getTime()
+    body:                     document.body,
+    title:                    document.title,
+    positionY:                window.pageYOffset,
+    positionX:                window.pageXOffset,
+    cachedAt:                 new Date().getTime()
+    transitionCacheDisabled:  document.querySelector('[data-no-transition-cache]')?

   constrainPageCacheTo cacheSize


buncle-bundler android 'window' typo fixed

--- lib/bundle-builder/emulators/android.coffee
+++ lib/bundle-builder/emulators/android.coffee
@@ -1,7 +1,7 @@
 'use strict'

 module.exports = ->
-    windiow: global
+    window: global
     requestAnimationFrame: ->
     android: {}
     setImmediate: ->


Fix tests

Fix tests

--- test/classes.coffee
+++ test/classes.coffee
@@ -663,7 +663,7 @@

     test 'super function', ->
       class Super
-        func: (@a = 1) ->
+        func: (@a = 1) -> @a

       class Sub extends Super
         func: ->
@@ -671,7 +671,9 @@
           eq @a, 1
           super 3
           eq @a, 3
-          # a = super # TODO: must fix
+          a = super
+          v = 2 * super 3
+          eq v, 6

       a = new Sub
       a.func()


Get block key from Markup

Get block key from Markup

If the block has no text content, generate a unique key from the markup.
In this particular case, we get the key from the image src attribute.

Fixes #53

--- templates/embed/Block.coffee
+++ templates/embed/Block.coffee
@@ -32,6 +32,8 @@
         k = first.concat(last)
         max = if k.length > len then len else k.length
         key += k[i].substring(0, 1) for i in [0..max-1]
+    if key == ""
+      key = @keyFromMarkup()
     return key

   getSentences: (el) ->
@@ -80,3 +82,9 @@

   top: ->
     @element.offsetTop - @element.scrollTop + @element.clientTop
+
+  keyFromMarkup: ->
+    if @element.querySelector('img')?
+      @element.querySelector('img').src.replace(/[^a-z ]+/gi, '')
+    else
+      ''


PrivateMessage: remove the limit for initial request.

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -121,7 +121,6 @@

   fetch: (options = {}, callback) ->

-    options.limit or= 3
     super options, (err, data) =>
       channel = @getData()
       channel.replies = data


Temporarily hide the add-new-event button when no select2 results

--- client/controllers/incidentReports/incidentTable.coffee
+++ client/controllers/incidentReports/incidentTable.coffee
@@ -18,6 +18,14 @@

 _incidentsSelected = (instance) ->
   _selectedIncidents(instance).count()
+
+select2NoResults = ->
+  """
+

+
No Results Found

+

+    <button class='btn btn-default add-new-event'>Add New Event
+  """

 Template.incidentTable.onCreated ->
   @subscribe('useEvents')
@@ -74,15 +82,6 @@
             data: select2Data
             placeholder: 'Search for an Event...'
             minimumInputLength: 0
-            language:
-              noResults: ->
-                """
-

-
No Results Found

-

-                  <button class='btn btn-default add-new-event'>Add New Event
-                """
-            escapeMarkup: (markup) -> markup

           $(document).on 'click', '.add-new-event', (event) =>
             $select2.select2('close')


add tests for process.argv and process.env

--- test/process.coffee
+++ test/process.coffee
@@ -30,3 +30,11 @@
   test 'process.chdir changes process.cwd result', ->
     fixtures '/a.js': 'process.chdir("/dir"); module.exports = process.cwd()'
     eq '/dir', @bundleEval 'a.js'
+
+  test 'process.argv is an empty array', ->
+    fixtures '/a.js': 'module.exports = process.argv'
+    arrayEq [], @bundleEval 'a.js'
+
+  test 'process.env is an empty object', ->
+    fixtures '/a.js': 'module.exports = Object.keys(process.env)'
+    arrayEq [], @bundleEval 'a.js'


fix unit tests

fix unit tests

--- services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee
+++ services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee
@@ -40,8 +40,8 @@
 		it "should create read stream", (done)->
 			@fs.createReadStream.returns
 				pipe:->
-				on: (type, cb)->
-					if type == "end"
+				on: (type, cb)->
+					if type == "open"
 						cb()
 			@handler.uploadFileFromDisk @project_id, @file_id, @fsPath, =>
 				@fs.createReadStream.calledWith(@fsPath).should.equal true
@@ -51,7 +51,7 @@
 			@request.returns(@writeStream)
 			@fs.createReadStream.returns
 				on: (type, cb)->
-					if type == "end"
+					if type == "open"
 						cb()
 				pipe:(o)=>
 					@writeStream.should.equal o
@@ -62,7 +62,7 @@
 			@fs.createReadStream.returns
 				pipe:->
 				on: (type, cb)->
-					if type == "end"
+					if type == "open"
 						cb()
 			@handler.uploadFileFromDisk @project_id, @file_id, @fsPath, =>
 				@request.args[0][0].method.should.equal "post"
@@ -73,7 +73,7 @@
 			@fs.createReadStream.returns
 				pipe:->
 				on: (type, cb)->
-					if type == "end"
+					if type == "open"
 						cb()
 			@handler.uploadFileFromDisk @project_id, @file_id, @fsPath, =>
 				@handler._buildUrl.calledWith(@project_id, @file_id).should.equal true
@@ -83,7 +83,7 @@
 			@fs.createReadStream.returns
 				pipe:->
 				on: (type, cb)->
-					if type == "end"
+					if type == "open"
 						cb()
 			@handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err) =>
 				expect(err).to.not.exist
@@ -113,7 +113,7 @@
 				@fs.createReadStream.returns
 					pipe:->
 					on: (type, cb)->
-						if type == "end"
+						if type == "open"
 							cb()
 				@handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err) =>
 					expect(err).to.exist


Fix soft wrapping with tags array, including soft-wrap indent

--- src/tokenized-line.coffee
+++ src/tokenized-line.coffee
@@ -5,6 +5,7 @@
 SoftTab = Symbol('SoftTab')
 HardTab = Symbol('HardTab')
 PairedCharacter = Symbol('PairedCharacter')
+SoftWrapIndent = Symbol('SoftWrapIndent')

 NonWhitespaceRegex = /\S/
 LeadingWhitespaceRegex = /^\s*/
@@ -33,9 +34,6 @@

     @subdivideTokens()
     @buildEndOfLineInvisibles() if @invisibles? and @lineEnding?
-
-    # @softWrapIndentationTokens = @getSoftWrapIndentationTokens()
-    # @softWrapIndentationDelta = @buildSoftWrapIndentationDelta()

   subdivideTokens: ->
     text = ''
@@ -160,6 +158,9 @@
         when PairedCharacter
           tokenProperties.isAtomic = true
           tokenProperties.hasPairedCharacter = true
+        when SoftWrapIndent
+          tokenProperties.isAtomic = true
+          tokenProperties.isSoftWrapIndentation = true

       if offset < @firstNonWhitespaceIndex
         tokenProperties.firstNonWhitespaceIndex =
@@ -221,7 +222,7 @@
       tokenStartColumn += token.screenDelta

     if @isColumnInsideSoftWrapIndentation(tokenStartColumn)
-      @softWrapIndentationDelta
+      @getSoftWrapIndentationDelta()
     else if token.isAtomic and tokenStartColumn < column
       if clip is 'forward'
         tokenStartColumn + token.screenDelta
@@ -235,24 +236,59 @@
     else
       column

-  screenColumnForBufferColumn: (bufferColumn, options) ->
-    bufferColumn = bufferColumn - @startBufferColumn
+  screenColumnForBufferColumn: (targetBufferColumn, options) ->
+    bufferColumn = @startBufferColumn
     screenColumn = 0
-    currentBufferColumn = 0
-    for token in @tokens
-      break if currentBufferColumn + token.bufferDelta > bufferColumn
-      screenColumn += token.screenDelta
-      currentBufferColumn += token.bufferDelta
-    @clipScreenColumn(screenColumn + (bufferColumn - currentBufferColumn))
-
-  bufferColumnForScreenColumn: (screenColumn, options) ->
+    for tag, index in @tags
+      if tag > 0
+        switch @specialTokens[index]
+          when HardTab
+            bufferDelta = 1
+            screenDelta = tag
+          when SoftWrapIndent
+            bufferDelta = 0
+            screenDelta = tag
+          else
+            bufferDelta = screenDelta = tag
+
+        nextBufferColumn = bufferColumn + bufferDelta
+        if nextBufferColumn > targetBufferColumn
+          overshoot = targetBufferColumn - bufferColumn
+          bufferColumn += overshoot
+          screenColumn += Math.min(screenDelta, overshoot)
+          break
+        else
+          bufferColumn = nextBufferColumn
+          screenColumn += screenDelta
+
+    screenColumn
+
+  bufferColumnForScreenColumn: (targetScreenColumn) ->
     bufferColumn = @startBufferColumn
-    currentScreenColumn = 0
-    for token in @tokens
-      break if currentScreenColumn + token.screenDelta > screenColumn
-      bufferColumn += token.bufferDelta
-      currentScreenColumn += token.screenDelta
-    bufferColumn + (screenColumn - currentScreenColumn)
+    screenColumn = 0
+    for tag, index in @tags
+      if tag > 0
+        switch @specialTokens[index]
+          when HardTab
+            bufferDelta = 1
+            screenDelta = tag
+          when SoftWrapIndent
+            bufferDelta = 0
+            screenDelta = tag
+          else
+            bufferDelta = screenDelta = tag
+
+        nextScreenColumn = screenColumn + screenDelta
+        if nextScreenColumn > targetScreenColumn
+          overshoot = targetScreenColumn - screenColumn
+          screenColumn += overshoot
+          bufferColumn += Math.min(bufferDelta, overshoot)
+          break
+        else
+          screenColumn = nextScreenColumn
+          bufferColumn += bufferDelta
+
+    bufferColumn

   getMaxScreenColumn: ->
     if @fold
@@ -286,17 +322,6 @@

       return maxColumn

-  buildSoftWrapIndentationTokens: (token, hangingIndent) ->
-    totalIndentSpaces = (@indentLevel * @tabLength) + hangingIndent
-    indentTokens = []
-    while totalIndentSpaces > 0
-      tokenLength = Math.min(@tabLength, totalIndentSpaces)
-      indentToken = token.buildSoftWrapIndentationToken(tokenLength)
-      indentTokens.push(indentToken)
-      totalIndentSpaces -= tokenLength
-
-    indentTokens
-
   softWrapAt: (column, hangingIndent) ->
     return [null, this] if column is 0

@@ -304,70 +329,117 @@
     rightText = @text.substring(column)

     leftTags = []
+    rightTags = []
+
+    leftSpecialTokens = {}
+    rightSpecialTokens = {}
+
     rightParentScopes = @parentScopes.slice()

     screenColumn = 0
+
+    for tag, index in @tags
+      # tag represents a token
+      if tag >= 0
+        # token ends before the soft wrap column
+        if screenColumn + tag <= column
+          if specialToken = @specialTokens[index]
+            leftSpecialTokens[index] = specialToken
+          leftTags.push(tag)
+          screenColumn += tag
+
+        # token starts before and ends after the split column
+        else if screenColumn <= column
+          leftSuffix = column - screenColumn
+          rightPrefix = screenColumn + tag - column
+
+          leftTags.push(leftSuffix) if leftSuffix > 0
+
+          softWrapIndent = @indentLevel * @tabLength + (hangingIndent ? 0)
+          rightText = ' ' + rightText for i in [0...softWrapIndent] by 1
+          while softWrapIndent > 0
+            indentToken = Math.min(softWrapIndent, @tabLength)
+            rightSpecialTokens[rightTags.length] = SoftWrapIndent
+            rightTags.push(indentToken)
+            softWrapIndent -= indentToken
+
+          rightTags.push(rightPrefix) if rightPrefix > 0
+
+          screenColumn += tag
+
+         # token is after split column
+        else
+          if specialToken = @specialTokens[index]
+            rightSpecialTokens[rightTags.length] = specialToken
+          rightTags.push(tag)
+
+      # tag represents the start or end of a scop
+      else if (tag % 2) is -1
+        if screenColumn < column
+          leftTags.push(tag)
+          rightParentScopes.push(tag)
+        else
+          rightTags.push(tag)
+      else
+        if screenColumn < column
+          leftTags.push(tag)
+          rightParentScopes.pop()
+        else
+          rightTags.push(tag)
+
+    splitBufferColumn = @bufferColumnForScreenColumn(column)
+
+    leftFragment = new TokenizedLine
+    leftFragment.parentScopes = @parentScopes
+    leftFragment.text = leftText
+    leftFragment.tags = leftTags
+    leftFragment.specialTokens = leftSpecialTokens
+    leftFragment.startBufferColumn = @startBufferColumn
+    leftFragment.bufferDelta = splitBufferColumn - @startBufferColumn
+    leftFragment.ruleStack = @ruleStack
+    leftFragment.invisibles = @invisibles
+    leftFragment.lineEnding = null
+    leftFragment.indentLevel = @indentLevel
+    leftFragment.tabLength = @tabLength
+    leftFragment.firstNonWhitespaceIndex = Math.min(column, @firstNonWhitespaceIndex)
+    leftFragment.firstTrailingWhitespaceIndex = Math.min(column, @firstTrailingWhitespaceIndex)
+
+    rightFragment = new TokenizedLine
+    rightFragment.parentScopes = rightParentScopes
+    rightFragment.text = rightText
+    rightFragment.tags = rightTags
+    rightFragment.specialTokens = rightSpecialTokens
+    rightFragment.startBufferColumn = splitBufferColumn
+    rightFragment.bufferDelta = @bufferDelta - splitBufferColumn
+    rightFragment.ruleStack = @ruleStack
+    rightFragment.invisibles = @invisibles
+    rightFragment.lineEnding = @lineEnding
+    rightFragment.indentLevel = @indentLevel
+    rightFragment.tabLength = @tabLength
+    rightFragment.endOfLineInvisibles = @endOfLineInvisibles
+    rightFragment.firstNonWhitespaceIndex = Math.max(0, @firstNonWhitespaceIndex - column)
+    rightFragment.firstTrailingWhitespaceIndex = Math.max(0, @firstTrailingWhitespaceIndex - column)
+
+    [leftFragment, rightFragment]
+
+  isSoftWrapped: ->
+    @lineEnding is null
+
+  isColumnInsideSoftWrapIndentation: (targetColumn) ->
+    targetColumn < @getSoftWrapIndentationDelta()
+
+  getSoftWrapIndentationDelta: ->
+    delta = 0
     for tag, index in @tags
       if tag >= 0
-        if screenColumn + tag < column
-          screenColumn += tag
-        else
-          leftTags.push(column - screenColumn)
-          rightTags = @tags.slice(index + 1)
-          rightPrefix = screenColumn + tag - column
-          rightTags.unshift(rightPrefix) if rightPrefix > 0
+        if @specialTokens[index] is SoftWrapIndent
+          delta += tag
+        else
           break
-      else if (tag % 2) is -1
-        rightParentScopes.push(tag)
-      else
-        rightParentScopes.pop()
-      leftTags.push(tag)
-
-    softWrapIndent = @indentLevel * @tabLength + (hangingIndent ? 0)
-    rightTags.unshift(softWrapIndent) if softWrapIndent > 0
-
-    leftFragment = new TokenizedLine(
-      parentScopes: @parentScopes
-      text: leftText
-      tags: leftTags
-      startBufferColumn: @startBufferColumn
-      ruleStack: @ruleStack
-      invisibles: @invisibles
-      lineEnding: null,
-      indentLevel: @indentLevel,
-      tabLength: @tabLength
-    )
-    rightFragment = new TokenizedLine(
-      parentScopes: rightParentScopes
-      text: rightText
-      tags: rightTags
-      startBufferColumn: @bufferColumnForScreenColumn(column)
-      ruleStack: @ruleStack
-      invisibles: @invisibles
-      lineEnding: @lineEnding
-      indentLevel: @indentLevel
-      tabLength: @tabLength
-    )
-    [leftFragment, rightFragment]
-
-  isSoftWrapped: ->
-    @lineEnding is null
-
-  isColumnInsideSoftWrapIndentation: (column) ->
-    false
-    # return false if @softWrapIndentationTokens.length is 0
-    #
-    # column < @softWrapIndentationDelta
-
-  getSoftWrapIndentationTokens: ->
-    []
-    # _.select(@tokens, (token) -> token.isSoftWrapIndentation)
-
-  buildSoftWrapIndentationDelta: ->
-    _.reduce @softWrapIndentationTokens, ((acc, token) -> acc + token.screenDelta), 0
+    delta

   hasOnlySoftWrapIndentation: ->
-    @tokens.length is @softWrapIndentationTokens.length
+    @getSoftWrapIndentationDelta() is @text.length

   tokenAtBufferColumn: (bufferColumn) ->
     @tokens[@tokenIndexAtBufferColumn(bufferColumn)]


Don't create empty text nodes

Don't create empty text nodes

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -127,10 +127,11 @@
         position += 1
         element.appendChild(node)

-      node = document.createTextNode(preserveSpaces(substring))
-      node.trixPosition = position
-      position += substring.length
-      element.appendChild(node)
+      if substring.length
+        node = document.createTextNode(preserveSpaces(substring))
+        node.trixPosition = position
+        position += substring.length
+        element.appendChild(node)

     element


Don't scroll on modal window background

Don't scroll on modal window background

--- core/app/backbone/modules/modal_window_container.coffee
+++ core/app/backbone/modules/modal_window_container.coffee
@@ -14,6 +14,7 @@
     onRender: ->
       @modalRegion.show @options.content_view
       @$el.fadeIn 'fast'
+      @$el.preventScrollPropagation()

     stopPropagation: (e) ->
       e.stopPropagation()


stderr and exit now opens a error notification

--- lib/atom-ternjs-server.coffee
+++ lib/atom-ternjs-server.coffee
@@ -27,10 +27,12 @@
     @process = null

   stderr: (output) ->
-    console.error output
+    content = "atom-ternjs
" + output
+    atom.notifications.addError(content, dismissable: true)

   exit: (code) ->
-    console.log("tern exited with code: #{code}")
+    content = "tern exited with code: #{code}.
Restart the server via Packages -> Atom Ternjs -> Restart server"
+    atom.notifications.addError(content, dismissable: true)

   isPlatformWindows: ->
     document.getElementsByTagName('body')[0].classList.toString().indexOf('platform-win') > -1


fix require Disposable

fix require Disposable

--- lib/autohide-tree-view.coffee
+++ lib/autohide-tree-view.coffee
@@ -1,4 +1,5 @@
 'use strict'
+{Disposable} = require 'atom'
 SubAtom = null

 # generic error logging function


lots of comments

lots of comments

--- lib/autohide-tree-view.coffee
+++ lib/autohide-tree-view.coffee
@@ -6,11 +6,13 @@
 error = (e) ->
   console.error e.message, '\n', e.stack

+# shallow clone object
 clone = (obj) ->
   res = {}
   res[key] = obj[key] for own key of obj
   res

+# process.nextTick that returns a promise
 promiseNextTick = ->
   new Promise (resolve) ->
     process.nextTick ->
@@ -67,6 +69,8 @@
   activate: ->
     SubAtom ?= require 'sub-atom'
     @disposables = new SubAtom()
+
+    # keep a reference to the package config
     @conf = atom.config.get 'autohide-tree-view'
     {pushEditor} = @conf

@@ -91,19 +95,24 @@
     # the stylesheet will be removed before the animation is finished
     # so set minWidth on the element
     @treeViewEl.style.minWidth = '1px'
+    # apply new pushEditor value, then show the tree view
     @update().then =>
       # show the tree view
       @show 0
     .then =>
+      # finally dispose of everything
       @disposables.dispose()
       [@disposables, @conf, @visible] = []
     .catch error

   registerTreeView: (treeViewPkg) ->
+    # keep references to the tree view element
     @treeView = treeViewPkg.mainModule.createView()
     @treeViewEl = @treeView.element
     @treeViewEl.classList.add 'autohide'

+    # register a disposable that disposes of the references
+    # and resets the styles
     @disposables.add new Disposable =>
       @treeViewEl.classList.remove 'autohide', 'autohide-hover'
       @treeViewEl.style.position = ''
@@ -113,8 +122,10 @@
       [@treeView, @treeViewEl] = []

   handleEvents: ->
-    # observe config
+    # keep config reference updated
     @disposables.add atom.config.onDidChange 'autohide-tree-view', ({newValue}) => @conf = clone newValue
+
+    # changes to these settings should trigger an update
     @disposables.add atom.config.onDidChange 'autohide-tree-view.pushEditor', => @update()
     @disposables.add atom.config.onDidChange 'autohide-tree-view.hiddenWidth', => @update()
     @disposables.add atom.config.onDidChange 'tree-view.showOnRightSide', => @update()
@@ -122,20 +133,25 @@
     @disposables.add atom.config.onDidChange 'tree-view.hideVcsIgnoredFiles', => @update()
     @disposables.add atom.config.onDidChange 'core.ignoredNames', => @update()

-    # add listeners for mouse events
+    # add listeners for mouse events and update them each time config.showOn gets updated
     mouseEventDisposables = null
     @disposables.add atom.config.observe 'autohide-tree-view.showOn', (value) =>
+      # dispose of previously set listeners
       mouseEventDisposables?.dispose()
       @disposables.add mouseEventDisposables = new SubAtom()
       if value.match 'hover'
+        # show the tree view when it is hovered
         mouseEventDisposables.add 'atom-workspace', 'mouseenter', '.tree-view-resizer.autohide-hover', => @show()
         mouseEventDisposables.add 'atom-workspace', 'mouseleave', '.tree-view-resizer.autohide-hover', => @hide()
         # disable the tree view from showing/hiding during a selection
         mouseEventDisposables.add 'atom-text-editor', 'mousedown', => @disableHoverEvents()
         mouseEventDisposables.add 'atom-text-editor', 'mouseup', => @enableHoverEvents()
       if value.match 'click'
+        # toggle the tree view when it is clicked
         mouseEventDisposables.add 'atom-workspace', 'click', '.tree-view-resizer', (event) => @toggle value is 'click'
+        # hide the tree view when another element is focused
         mouseEventDisposables.add '.tree-view', 'blur', =>
+          # clear the focused element so the user clicked element will be focused
           @clearFocusedElement()
           @hide 0

@@ -166,9 +182,10 @@
       'tree-view:collapse-directory': => @resize()
       'tree-view:recursive-collapse-directory': => @resize()

+    # resize when opening/closing a directory
     @disposables.add 'atom-workspace', 'mouseup', '.tree-view-resizer .entry.directory', => @resize()

-    # these commands cause the tree view to hide
+    # hide the tree view when a command opens a file
     for direction in ['', '-right', '-left', '-up', '-down']
       @disposables.add atom.commands.add 'atom-workspace',
         "tree-view:open-selected-entry#{direction}", => @hide 0
@@ -182,6 +199,7 @@
       @disposables.add atom.commands.add 'atom-workspace',
         "tree-view:#{command}", => @clearFocusedElement()

+  # updates styling on the .tree-view-resizer and the panel element
   update: ->
     promiseNextTick().then =>
       if @conf.pushEditor
@@ -193,37 +211,56 @@
       @resize()
     .catch error

+  # show the tree view
   show: (delay = @conf.showDelay, disableHoverEvents = false) ->
+    # disable hover events on the tree view when not triggered
+    # by a hover event
     @disableHoverEvents() if disableHoverEvents
+    # keep a reference to the currently focused element
+    # so we can restore focus when the tree view hides
     @storeFocusedElement()
+    # show the content of the tree view
     @treeView.scroller[0].style.display = ''
     @animate @treeView.list[0].clientWidth, delay
     .then (finished) =>
       @visible = true
+      # focus the tree view when the animation is done
       @treeView.focus() if finished

+  # hide the tree view
   hide: (delay = @conf.hideDelay) ->
     @visible = false
+    # enable hover events again
     @enableHoverEvents()
+    # focus the element that was focused before the tree view
+    # was opened
     @recoverFocus()
     @animate @conf.hiddenWidth, delay
     .then (finished) =>
+      # hide the tree view content
       @treeView.scroller[0].style.display = 'none' if finished

+  # toggle the tree view
   toggle: (disableHoverEvents = true) ->
     if @visible then @hide 0 else @show 0, disableHoverEvents

+  # resize the tree view when its contents might change size
   resize: ->
     promiseNextTick.then() =>
       if @visible then @show 0  else @hide 0
     .catch error

+  # keep a reference to the currently focused element
   storeFocusedElement: ->
     @focusedElement = document.activeElement

+  # clear the reference to the element that was focused
+  # when the tree view was opened
   clearFocusedElement: ->
     @focusedElement = null

+  # recover focus on the element that was focused when
+  # the tree view was opened
   recoverFocus: ->
     return unless @focusedElement?
     if typeof @focusedElement.focused is 'function'
@@ -232,38 +269,53 @@
       @focusedElement.focus()
     @clearFocusedElement()

+  # enable hover events on the tree view
   enableHoverEvents: ->
     if @conf.showOn.match 'hover'
       @treeViewEl.classList.add 'autohide-hover'

+  # disable hover events on the tree view
   disableHoverEvents: ->
     if @conf.showOn.match 'hover'
       @treeViewEl.classList.remove 'autohide-hover'

   # resolves true if animation finished, false if animation cancelled
   animate: (targetWidth, delay) ->
+    # get the initial width of the element
     initialWidth = @treeViewEl.clientWidth
+    # calculate the animation duration
     duration = Math.abs targetWidth - initialWidth / (@conf.animationSpeed or Infinity)

+    # cancel any current animation
     if @currentAnimation? and @currentAnimation.playState isnt 'finished'
       @currentAnimation.cancel()
       @currentAnimation = null
+      # if an animation was already occurring, this one
+      # should trigger immediately
       delay = 0

+    # explicitly set the elements initial width
+    # @treeViewEl.style.width = "#{initialWidth}px"
     @treeViewEl.style.width = "#{initialWidth}px"
     new Promise (resolve) =>
+      # no animation necessary
       if targetWidth is initialWidth
         return resolve true

+      # cache the current animationPlayer so we can
+      # cancel it as another animation begins
       @currentAnimation = animation = @treeViewEl.animate [
         {width: initialWidth}
         {width: targetWidth}
       ], {duration, delay}

       animation.onfinish = =>
+        # if the animation we resolve with false
         if animation.playState isnt 'finished'
           return resolve false
+        # prevent tree view from resetting its width to initialWidth
         @treeViewEl.style.width = "#{targetWidth}px"
+        # remove the currentAnimation reference
         @currentAnimation = null
         resolve true
     .catch error


A more useful example

A more useful example

--- quests/affqisexample/affqisexample.coffee
+++ quests/affqisexample/affqisexample.coffee
@@ -2,6 +2,18 @@

 module.exports =
 class AffqisExampleQuest extends Quest
-  databases: ["pg", "hive"]
+  databases: ["hive", "pg"]
   adventure: ->
-    @sql "select 1"
+    x = @sql """CREATE TABLE sqltest (x int);
+                INSERT OVERWRITE TABLE sqltest
+                  SELECT * FROM (
+                    SELECT STACK(3, 1, 2, 3)
+                  ) s;
+                SELECT * FROM sqltest;"""
+    console.log x
+
+    y = @sql """DROP TABLE sqltest;"""
+    console.log y
+
+    z = @sql text: """SELECT 1, 2, 3""", db: "pg"
+    console.log z


remove invalid comment

remove invalid comment

--- lib/connection/process.coffee
+++ lib/connection/process.coffee
@@ -100,7 +100,6 @@
                                                     ["-NoProfile", "$PSVersionTable.PSVersion.Major"])
                                           .output[1].toString()) > 2
       if @useWrapper
-        # get a random and hopefully free port:
         @getFreePort =>
           @proc = child_process.spawn("powershell",
                                       ["-NoProfile", "-ExecutionPolicy", "bypass",


Remove logging

Remove logging

--- lib/encoding-list-view.coffee
+++ lib/encoding-list-view.coffee
@@ -51,7 +51,6 @@
       return unless iconv.encodingExists(encoding)

       encoding = encoding.toLowerCase().replace(/[^0-9a-z]|:\d{4}$/g, '')
-      console.log 'setting', encoding
       @editor.setEncoding(encoding)

   confirmed: (encoding) ->


Update example.pre.coffee

Update example.pre.coffee

--- demo/input/example.pre.coffee
+++ demo/input/example.pre.coffee
@@ -49,6 +49,7 @@

 	default const
 	let x = 2 ** 2
+	type callback = (err:boolean, result:any) -> void

 	table1 =
 	  1, 2, 3
@@ -58,6 +59,7 @@
 	table2 =
 	  a:1, b:2, c:3
 	  a:4, b:5, c:6
+
 	return {input, output, first, second}

 macros ->


use YouTube GData API to get thumbnail and title

--- scripts/media.coffee
+++ scripts/media.coffee
@@ -72,9 +72,21 @@
   doesUriMatch: (uri) ->
     uri.match(LONG_PATTERN)? or uri.match(SHORT_PATTERN)?

-  loadThumbnailImage: ->
+  loadThumbnail: ->
     [_, id] = @uri.match(LONG_PATTERN) || @uri.match(SHORT_PATTERN)
-    @setThumbnailImage "http://img.youtube.com/vi/#{id}/1.jpg"
+
+    $.get "https://gdata.youtube.com/feeds/api/videos/#{id}?v=2", (xml) =>
+      # jump through hoops for the XPath API
+      resolver = (ns) ->
+        if ns is 'atom'
+          xml.documentElement.namespaceURI
+        else
+          xml.lookupNamespaceURI ns
+      xpath = (query) -> xml.evaluate(query, xml, resolver, XPathResult.STRING_TYPE, null).stringValue
+
+      @setThumbnailImage xpath '/atom:entry/media:group/media:thumbnail[@yt:name="default"]/@url'
+      @setThumbnailTitle xpath '/atom:entry/atom:title'
+      @setThumbnailLink @uri

 class ImgurScraper extends Media.Scraper
   Media.register this


activity: don't re-sort messages on rendering layer

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -45,8 +45,6 @@
   renderBody: ->
     return null  unless @props.messages

-    messages = @props.messages.sortBy (m) -> m?.get 'createdAt'
-
     <section className="ChatPane-body" ref="ChatPaneBody">
       <Scroller
         onTopThresholdReached={@bound 'onTopThresholdReached'}


Return promise rejection to prevent Bluebird from complaining

--- lib/models/application.coffee
+++ lib/models/application.coffee
@@ -15,7 +15,7 @@

 	.then (application) ->
 		if not application?
-			Promise.reject(new Error('Not found'))
+			return Promise.reject(new Error('Not found'))

 		return application


fix reseting select in toolbar

fix reseting select in toolbar

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -73,7 +73,7 @@
   updateActive: (activeFormats = {}) ->
     @triggering = true
     range = @editor.getSelection()
-    _.each(@container.querySelectorAll('sc-select'), ScribeDOM.resetSelect.bind(this))
+    _.each(@container.querySelectorAll('select'), ScribeDOM.resetSelect.bind(this))
     _.each(@container.querySelectorAll('.sc-active'), (button) =>
       ScribeDOM.removeClass(button, 'sc-active')
     )


Re-enabled CSRF tokens.

Re-enabled CSRF tokens.

--- web/coffee/dependencies.coffee
+++ web/coffee/dependencies.coffee
@@ -1,4 +1,8 @@
 @apiCall = (type, url, data) ->
+
+  if type == "POST":
+    data.token = $.cookie("token")
+
   $.ajax {url: url, type: type, data: data, cache: false}
   .fail (jqXHR, text) ->
     $.notify "API is offline. :(", "error"


Add blur method to SearchInputView.

Add blur method to SearchInputView.

--- web/js/views/search_input_view.coffee
+++ web/js/views/search_input_view.coffee
@@ -17,12 +17,14 @@
     afterTypeahead: () =>
       idx = @$el.val().lastIndexOf(',')
       @$el.selectRange(idx) if idx? and idx > 0
-
+
     indicateError: () ->
       @$el.closest('.control-group').addClass('error')
+      this

     clearError: () ->
       @$el.closest('.control-group').removeClass('error')
+      this

     validate: () ->
       if $.trim(@val()) is ''
@@ -33,6 +35,9 @@

     focus: () =>
       @$el.focus()
+
+    blur: ->
+      @$el.blur()

     val: (v) =>
       if arguments.length > 0


using linksHelper to update url path, setting version to draft and editable to true in success method

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -88,14 +88,14 @@
       data = JSON.stringify({id: @model.asPage().get('id')})
       options =
         success: (model) =>
-          pageNumber = @model.getPageNumber()
-          options =
-            success: (model) ->
-              router.navigate("/contents/#{model.id}@draft:#{pageNumber}", {trigger: false})
+          @model.asPage()?.set('version', 'draft')
+          @model.asPage()?.set('editable', true)
+          href = linksHelper.getPath 'contents',
+            model: @model
+            page: @model.getPageNumber()
+          router.navigate(href, {trigger: false})

       @model.editOrDeriveContent(options, data)
-      @model.asPage()?.set('version', 'draft')
-      @model.asPage()?.set('editable',true)

     derivePage: () ->


Maincontroller: make sure the avatararea will be created once

--- client/Main/maincontroller/mainviews/mainview.coffee
+++ client/Main/maincontroller/mainviews/mainview.coffee
@@ -123,7 +123,12 @@

     @headerContainer.addSubView @accountArea

-    unless KD.isLoggedIn()
+    if KD.isLoggedIn()
+
+      @createLoggedInAccountArea()
+
+    else
+
       @loginLink = new CustomLinkView
         cssClass    : 'header-sign-in'
         title       : 'Login'
@@ -135,13 +140,9 @@
       @accountArea.addSubView @loginLink

       mc = KD.getSingleton "mainController"
-      mc.on "accountChanged.to.loggedIn", =>
+      mc.once "accountChanged.to.loggedIn", =>
         @loginLink.destroy()
         @createLoggedInAccountArea()
-
-      return
-
-    @createLoggedInAccountArea()

   createLoggedInAccountArea:->
     @accountArea.destroySubViews()


Client: Activity: DesktopNotifications: Updated desktop notifications controller to add focusToRoute default handler to handle route with given route option after notification onclick

--- client/app/lib/desktopnotificationscontroller.coffee
+++ client/app/lib/desktopnotificationscontroller.coffee
@@ -25,6 +25,7 @@
 defaultOptions =
   title   : ''
   message : ''
+  route   : '/'
   timeout : 4000
   iconUrl : '/a/images/logos/logo_notifications.png'

@@ -43,10 +44,15 @@

     return unless  isSupported()
     isPermitted ->
-      options = _.assign {}, defaultOptions, options
+      options      = _.assign {}, defaultOptions, options
+
+      focusToRoute = ->
+        window.focus()
+        kd.singletons.router.handleRoute options.route

       notification = new Notification options.title, { body: options.message, icon: options.iconUrl }
-      notification.onclick = options.onClick
+
+      notification.onclick = options.onClick || focusToRoute
       setTimeout  ->
         notification.close()
       , options.timeout


changed URL for help for permanent shared VMs

--- client/app/lib/providers/machinesettingspopup.coffee
+++ client/app/lib/providers/machinesettingspopup.coffee
@@ -287,7 +287,7 @@
         sharedWith     :
           label        : "
             VM Shared With
-
+


fact facts aren't funny

fact facts aren't funny

--- scripts/fact.coffee
+++ scripts/fact.coffee
@@ -1,18 +0,0 @@
-# Description:
-#   Fact facts
-# Commands:
-#   hubot fact - Retuns a fact fact
-# Facts from http://en.wikipedia.org/wiki/Fact
-module.exports = (robot) ->
-  robot.respond /fact/i, (msg) ->
-    quotes = [
-      'The word fact derives from the Latin factum, and was first used in English with the same meaning: "a thing done or performed", a use that is now obsolete.[1] The common usage of "something that has really occurred or is the case" dates from the middle of the sixteenth century.',
-      'Fact is sometimes used synonymously with truth, as distinct from opinions, falsehoods, or matters of taste. This use is found in such phrases as, It is a fact that the cup is blue or Matter of fact, and "... not history, nor fact, but imagination." Filmmaker Werner Herzog distinguishes clearly between the two, claiming that "fact creates norms, and truth illumination".'
-      'Fact also indicates a matter under discussion deemed to be true or correct, such as to emphasize a point or prove a disputed issue; (e.g., "... the fact of the matter is ...").',
-      'Fact may also indicate an allegation or stipulation of something that may or may not be a "true fact", (e.g., "the author\'s facts are not trustworthy"). This alternate usage, although contested by some, has a long history in standard English.',
-      'Fact may also indicate findings derived through a process of evaluation, including review of testimony, direct observation, or otherwise; as distinguishable from matters of inference or speculation. This use is reflected in the terms "fact-find" and "fact-finder" (e.g., "set up a fact-finding commission").',
-      'Facts may be checked by reason, experiment, personal experience, or may be argued from authority. Roger Bacon wrote "If in other sciences we should arrive at certainty without doubt and truth without error, it behooves us to place the foundations of knowledge in mathematics."'
-      'In philosophy, the concept fact is considered in epistemology and ontology. Questions of objectivity and truth are closely associated with questions of fact. A "fact" can be defined as something which is the case, that is, a state of affairs.',
-      'Facts may be understood as that which makes a true sentence true. Facts may also be understood as those things to which a true sentence refers. The statement "Jupiter is the largest planet in the solar system" is about the fact Jupiter is the largest planet in the solar system.'
-    ]
-    msg.send msg.random quotes


Isolate scope test

Isolate scope test

--- coffee_test/spec/directives/supermanDirective.coffee
+++ coffee_test/spec/directives/supermanDirective.coffee
@@ -27,6 +27,24 @@
     scope = $rootScope.$new()
     scope.fly = jasmine.createSpy('fly')
     elem = $compile('<superman enter="sky" transport="fly()"></superman>')(scope)
-    supermanCtrl = elem.controller('superman')
+    #supermanCtrl = elem.controller('superman')
     elem.triggerHandler('click')
-    expect(scope.fly).toHaveBeenCalled()+    expect(scope.fly).toHaveBeenCalled()
+
+  it 'should have no ability by default', ->
+    scope = $rootScope.$new()
+    scope.ability = []
+    elem = $compile('<superman enter="sky" transport="fly()" ability="ability"></superman>')(scope)
+    expect(scope.ability).toEqual []
+
+
+  it 'should have strength and speed when add called', ->
+    scope = $rootScope.$new()
+    scope.ability = []
+    elem = $compile('<superman enter="sky" transport="fly()" ability="ability"></superman>')(scope)
+    ctrl = elem.controller('superman')
+    ctrl.addStrength()
+    ctrl.addSpeed()
+    expect(scope.ability).toContain "strength"
+    expect(scope.ability).toContain "speed"
+


Fix kneejerk refactoring.

Fix kneejerk refactoring.

--- server/battle.coffee
+++ server/battle.coffee
@@ -168,7 +168,7 @@
       delete @playerActions[id]

     if @isOver()
-      @continueTurn()
+      @sendMessages()
       @endBattle()
       return

@@ -177,11 +177,11 @@
       @requestAction(player, validActions)

     # Send a message to each player.
-    @continueTurn()
+    @sendMessages()

     if @areAllRequestsCompleted() then @startNewTurn()

-  continueTurn: =>
+  sendMessages: =>
     @message 'The turn continued.'
     for id, player of @players
       player.updateChat('SERVER', @buffer.join("<br>"))


Rename class for consistency reasons

Rename class for consistency reasons

--- core/app/backbone/views/react_feed_activities.coffee
+++ core/app/backbone/views/react_feed_activities.coffee
@@ -105,7 +105,7 @@
             comment.creator().get('name')
           _div ["comment-content",
             dangerouslySetInnerHTML: {__html: stripLinks(comment.get('formatted_content'))}]
-      _div ["feed-lowest-comment subcomment-content",
+      _div ["feed-lowest-comment sub-comment-content",
         dangerouslySetInnerHTML: {__html: stripLinks(sub_comment.get('formatted_content'))}]

 ReactFollowedUserActivity = React.createBackboneClass


:art:

:art:

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -41,8 +41,7 @@

     userStylesheetPath = atom.styles.getUserStyleSheetPath()
     stylesChanged = (element) =>
-      if element.getAttribute('source-path') is userStylesheetPath
-        @refreshSelectorsButton.show()
+      @refreshSelectorsButton.show() if element.getAttribute('source-path') is userStylesheetPath
     @subscriptions.add atom.styles.onDidUpdateStyleElement(stylesChanged)
     @subscriptions.add atom.styles.onDidAddStyleElement(stylesChanged)