advice: only emit AddEventListener on elements visible on the client.

--- src/server/advice.coffee
+++ src/server/advice.coffee
@@ -139,9 +139,10 @@
     # client.
     # TODO: wrap removeEventListener.
     adviseMethod events.EventTarget, 'addEventListener', (elem, args, rv) ->
-        emitter.emit 'AddEventListener',
-            target      : elem
-            type        : args[0]
+        if isVisibleOnClient(elem, emitter)
+            emitter.emit 'AddEventListener',
+                target      : elem
+                type        : args[0]

     # Advice for: all possible attribute event listeners
     #
@@ -156,9 +157,10 @@
                 # TODO: remove listener if this is set to something not a function
                 html.HTMLElement.prototype.__defineSetter__ name, (func) ->
                     rv = this["__#{name}"] = func
-                    emitter.emit 'AddEventListener',
-                        target      : this
-                        type        : type
+                    if isVisibleOnClient(this, emitter)
+                        emitter.emit 'AddEventListener',
+                            target      : this
+                            type        : type
                     return rv
                 html.HTMLElement.prototype.__defineGetter__ name, () ->
                     return this["__#{name}"]


tweak(sheet-container): No deferred calls to setState! Will fix perf issues in another patch (Sentry 1525)

--- src/sheet-container.cjsx
+++ src/sheet-container.cjsx
@@ -92,7 +92,7 @@
     window.dispatchEvent(new Event('resize'))

   _onStoreChange: =>
-    _.defer => @setState(@_getStateFromStores())
+    @setState(@_getStateFromStores())

   _getStateFromStores: =>
     stack: WorkspaceStore.sheetStack()


Auto zoom cam when at least one people is in the room with you

--- src/frontend/network.coffee
+++ src/frontend/network.coffee
@@ -47,6 +47,13 @@
                 )
             baliseName          = '#' + baliseVideoId
             $(baliseName).attr 'src', window.URL.createObjectURL(event.stream)
+            mainCamId = $('#mainCam video').attr 'id'
+            if not mainCamId
+                video = $('#videos li.thumbnail').first()
+                newCam = $(video).find("video").clone()
+                newCam.removeClass 'thumbnailVideo'
+                newCam.addClass 'mainCam'
+                $('#mainCam').append newCam
         options.removestream        = (event) =>
             return
         options.getice              = (tunnel, event) =>


Reject fetch promise when there is an error

--- lib/notification-issue.coffee
+++ lib/notification-issue.coffee
@@ -27,10 +27,8 @@
             for issue in data.items
               issues[issue.state] = issue if issue.title.indexOf(@getIssueTitle()) > -1 and not issues[issue.state]?
             return resolve(issues) if issues.open? or issues.closed?
-
           resolve(null)
-        error: ->
-          resolve(null)
+        error: (e) -> reject(e)

   getIssueUrlForSystem: ->
     new Promise (resolve, reject) =>


Comment unused file callback

Comment unused file callback

--- lib/csv-editor.coffee
+++ lib/csv-editor.coffee
@@ -31,11 +31,11 @@
   subscribeToFile: ->
     @fileSubscriptions = new CompositeDisposable

-    @fileSubscriptions.add @file.onDidChange =>
-      console.log 'changed'
-
-    @fileSubscriptions.add @file.onDidDelete =>
-      console.log 'deleted'
+    # @fileSubscriptions.add @file.onDidChange =>
+    #   console.log 'changed'
+    #
+    # @fileSubscriptions.add @file.onDidDelete =>
+    #   console.log 'deleted'

     @fileSubscriptions.add @file.onDidRename =>
       newPath = @getPath()
@@ -45,8 +45,8 @@
       @emitter.emit 'did-change-title', @getTitle()
       @previousPath = newPath

-    @fileSubscriptions.add @file.onWillThrowWatchError (errorObject) =>
-      console.log 'error', errorObject
+    # @fileSubscriptions.add @file.onWillThrowWatchError (errorObject) =>
+    #   console.log 'error', errorObject

   getTitle: ->
     if sessionPath = @getPath()


Save table layout on cvs editor destruction

--- lib/csv-editor.coffee
+++ lib/csv-editor.coffee
@@ -77,7 +77,10 @@
   destroy: ->
     return if @destroyed

-    @editor?.destroy()
+    if @editor?
+      @saveLayout()
+      @editor.destroy()
+
     @destroyed = true
     @emitter.emit('did-destroy', this)
     @emitter.dispose()


default arg values for color function (lighten, darken etc)

--- framer/Color.coffee
+++ framer/Color.coffee
@@ -66,32 +66,32 @@
 		if @_a < 1 then return false
 		return cssNames[rgbToHex(@_r, @_g, @_b, @)] or false

-	lighten: (amount) ->
+	lighten: (amount = 10) ->
 		hsl = @toHsl()
 		hsl.l += amount / 100
 		hsl.l = Math.min(1, Math.max(0, hsl.l))
 		return new Color(hsl)

-	brighten: (amount) ->
+	brighten: (amount = 10) ->
 		rgb = @toRgb()
 		rgb.r = Math.max(0, Math.min(255, rgb.r - Math.round(255 * -(amount / 100))))
 		rgb.g = Math.max(0, Math.min(255, rgb.g - Math.round(255 * -(amount / 100))))
 		rgb.b = Math.max(0, Math.min(255, rgb.b - Math.round(255 * -(amount / 100))))
 		return new Color(rgb)

-	darken: (amount) ->
+	darken: (amount = 10) ->
 		hsl = @toHsl()
 		hsl.l -= amount / 100
 		hsl.l = Math.min(1, Math.max(0, hsl.l))
 		return new Color(hsl)

-	desaturate: (amount) ->
+	desaturate: (amount = 10) ->
 		hsl = @toHsl()
 		hsl.s -= amount / 100
 		hsl.s = Math.min(1, Math.max(0, hsl.s))
 		new Color(hsl)

-	saturate: (amount) ->
+	saturate: (amount = 10) ->
 		hsl = @toHsl()
 		hsl.s += amount / 100
 		hsl.s = Math.min(1, Math.max(0, hsl.s))


require remote from electron

require remote from electron

which required for this to work with newer electron versions (I think; will have to test more)

--- lib/runtime/evaluation.coffee
+++ lib/runtime/evaluation.coffee
@@ -1,5 +1,5 @@
 path = require 'path'
-remote = require 'remote'
+remote = require('electron').remote
 dialog = remote.require 'dialog'
 BrowserWindow = remote.require 'browser-window'


Add support for Ruby bang and question methods

* The ::find method of tag-reader.coffee uses getCurrentWordBufferRange()
  to pull in the current word to pull in the current tag. Passing in a
  custom regexp to getCurrentWordBufferRange() determines what constitutes
  a word.

* Obtain the charAfterWord by incrementing the row by one. If the character
  is a question mark or exclamation point, append charAfterWord to word
  and search the whole thing.

--- lib/tag-reader.coffee
+++ lib/tag-reader.coffee
@@ -16,11 +16,13 @@
     return tagsFile if fs.isFileSync(tagsFile)

   find: (editor, callback) ->
-    word = editor.getTextInRange(editor.getCursor().getCurrentWordBufferRange())
+    range = editor.getCursor().getCurrentWordBufferRange()
+    symbol = getSymbolFromRange(editor, range)
+
     tagsFile = @getTagsFile()

-    if word?.length > 0 and tagsFile
-      ctags.findTags(tagsFile, word, callback)
+    if symbol?.length > 0 and tagsFile
+      ctags.findTags(tagsFile, symbol, callback)
     else
       process.nextTick -> callback(null, [])

@@ -29,3 +31,15 @@
     task = Task.once handlerPath, atom.project.getPath(), -> callback(projectTags)
     task.on 'tags', (paths) -> projectTags.push(paths...)
     task
+
+getSymbolFromRange = (editor, range) ->
+  word = editor.getTextInRange(range)
+  charAfterWord = getCharAfterWord(editor, range)
+
+  if charAfterWord == "?" or charAfterWord == "!"
+    return word + charAfterWord
+  else
+    return word
+
+getCharAfterWord = (editor, range) ->
+  return editor.getTextInRange([[range.end.row, range.end.column], [range.end.row, range.end.column + 1]])


After triggering a `liszt:updated`, only winnow results if search results are open.

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -99,7 +99,7 @@
     this.result_clear_highlight()
     @result_single_selected = null
     this.results_build()
-    this.winnow_results()
+    this.winnow_results() if @results_showing

   results_toggle: ->
     if @results_showing


merging nodes for Huffman working

merging nodes for Huffman working

--- tree2.coffee
+++ tree2.coffee
@@ -34,6 +34,7 @@
 node_style = stroke: 'white', width: 1.5, fill: 'black'
 node_emph_style = stroke: 'white', width: 5.5, fill: 'black'
 link_style = stroke: 'white', width: 1.5
+light_link_style = stroke: 'white', width: .5
 node_text_style = fill: 'white', font: '16px monospace'

 setStyle = (ctx, s) ->
@@ -45,6 +46,7 @@
 class Node
   render: (ctx) ->
     ctx.save()
+    ctx.translate(@x, @y)
     setStyle(ctx, node_style)
     ctx.beginPath()
     ctx.arc(0,0,@radius,0,Math.PI*2)
@@ -65,6 +67,8 @@

   render: (ctx) ->
     super ctx
+    ctx.save()
+    ctx.translate(@x, @y)
     setStyle(ctx, node_text_style)
     ctx.textAlign = 'center'
     ctx.textBaseline = 'middle'
@@ -73,6 +77,47 @@
       ctx.fillText(@label,0,2*@radius)

     ctx.fillText(@value, 0, 0)
+    ctx.restore()
+
+class Inner extends Node
+  constructor: (@value, @child0, @child1) ->
+
+  render: (ctx) ->
+    ctx.save()
+    setStyle(ctx, link_style)
+    ctx.moveTo(@x, @y)
+    ctx.lineTo(@child0.x, @child0.y)
+    ctx.moveTo(@x, @y)
+    ctx.lineTo(@child1.x, @child0.y)
+    ctx.stroke()
+    ctx.restore()
+
+    super ctx
+
+    ctx.save()
+    ctx.translate(@x, @y)
+    setStyle(ctx, node_text_style)
+    ctx.textAlign = 'center'
+    ctx.textBaseline = 'middle'
+
+    ctx.fillText(@value, 0, 0)
+    ctx.restore()
+
+    @child0.render(ctx)
+    @child1.render(ctx)
+
+  forAll: (fn) ->
+    if @child0.forAll?
+      @child0.forAll(fn)
+    else
+      fn(@child0)
+
+    if @child1.forAll?
+      child1.forAll(fn)
+    else
+      fn(@child1)
+
+    fn(this)

 class NodeCollection
   constructor: () ->
@@ -93,9 +138,16 @@
       i += 1

   render: (ctx) ->
+    if @merging?
+      ctx.save()
+      setStyle(ctx, light_link_style)
+      ctx.moveTo(@merging.node.x, @merging.node.y)
+      ctx.lineTo(@merging.x, @merging.y)
+      ctx.stroke()
+      ctx.restore()
+
     for n in @nodes
       ctx.save()
-      ctx.translate(n.x, n.y)
       n.render(ctx)
       ctx.restore()

@@ -104,14 +156,23 @@
   mousedown: (pos) ->
     for n in @nodes
       if n.isHit(pos)
-        @selected =
-          node: n
-          ox: n.x # original location
-          oy: n.y
-          mx: pos.x # mousedown location
-          my: pos.y
-          t: Date.now()
+        if @merging?
+          if n isnt @merging.node
+            @mergeNodes(@merging.node, n)
+            @merging = null
+        else
+          @selected =
+            node: n
+            ox: n.x # original location
+            oy: n.y
+            mx: pos.x # mousedown location
+            my: pos.y
+            t: Date.now()
         true
+
+    if @merging?
+      @merging = null
+
     false

   mousemove: (pos) ->
@@ -119,10 +180,18 @@
       s = @selected
       dx = (pos.x - s.mx + s.ox) - s.node.x
       dy = (pos.y - s.my + s.oy) - s.node.y
-      s.node.x += dx
-      s.node.y += dy
-
-      # TODO: apply to children
+
+      moveFn = (node) ->
+        node.x += dx
+        node.y += dy
+      if s.node.forAll?
+        s.node.forAll(moveFn)
+      else
+        moveFn(s.node)
+
+    if @merging?
+      @merging.x = pos.x
+      @merging.y = pos.y

   mouseup: (pos) ->
     if @selected?
@@ -130,8 +199,22 @@
         mdx = pos.x - @selected.mx
         mdy = pos.y - @selected.my
         if mdx*mdx+mdy*mdy < 10*10
-          console.log('looks like a click')
+          # call it a click
+
+          @merging =
+            node: @selected.node
+            x: pos.x
+            y: pos.y
+
     @selected = null
+
+  mergeNodes: (node0, node1) ->
+    # filter both out of the list
+    @nodes = ( n for n in @nodes when n isnt node0 and n isnt node1 )
+    newnode = new Inner(node0.value + node1.value, node0, node1)
+    newnode.x = (node0.x + node1.x) / 2
+    newnode.y = (node0.y + node1.y) / 2 - Math.abs(node0.x - node1.x)
+    @nodes.push(newnode)

 # render loop
 canvas = document.getElementById('cnv')


Newline

Newline

--- game.coffee
+++ game.coffee
@@ -76,6 +76,7 @@
         when 'down'  then @imageX = 32;
         when 'left'  then @imageX = 64;
         when 'right' then @imageX = 96;
+
       context.drawImage(@image, @x, @y)
       return


Avoid returning a reference when getting current lines

--- game.coffee
+++ game.coffee
@@ -32,7 +32,7 @@
       p.lastPos() for p in @players

     getCurrentLines: ->
-      p.current_line for p in @players
+      p.currentLine() for p in @players

     timer_tick: ->
       console.log "Tick!"


s/ignoreCurLine/ignoreCurrentLine/

s/ignoreCurLine/ignoreCurrentLine/

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -26,13 +26,13 @@

   removeTrailingWhitespace: (editor, grammarScopeName) ->
     buffer = editor.getBuffer()
-    ignoreCurLine = atom.config.get('whitespace.ignoreWhitespaceOnCurrentLine')
+    ignoreCurrentLine = atom.config.get('whitespace.ignoreWhitespaceOnCurrentLine')

     buffer.scan /[ \t]+$/g, ({lineText, match, replace}) ->
       whitespaceRow = buffer.positionForCharacterIndex(match.index).row
       cursorRow = editor.getCursor().getBufferRow()

-      return if ignoreCurLine and whitespaceRow is cursorRow
+      return if ignoreCurrentLine and whitespaceRow is cursorRow

       if grammarScopeName is 'source.gfm'
         # GitHub Flavored Markdown permits two spaces at the end of a line


Clean up

Clean up

--- components/search_bar/view.coffee
+++ components/search_bar/view.coffee
@@ -1,11 +1,10 @@
-_           = require 'underscore'
-Backbone    = require 'backbone'
-sd          = require('sharify').data
-Search      = require './collections/search.coffee'
-mediator    = require '../../lib/mediator.coffee'
-analytics   = require '../../lib/analytics.coffee'
-{ fill }    = require '../../lib/resizer.coffee'
-
+_ = require 'underscore'
+Backbone = require 'backbone'
+sd = require('sharify').data
+Search = require './collections/search.coffee'
+mediator = require '../../lib/mediator.coffee'
+analytics = require '../../lib/analytics.coffee'
+{ fill } = require '../../lib/resizer.coffee'
 itemTemplate = -> require('./templates/item.jade') arguments...

 module.exports = class SearchBarView extends Backbone.View
@@ -20,20 +19,20 @@
     throw new Error('Requires an input field') unless @$input?

     @search = new Search
-      restrictType : @restrictType
-      mode         : @mode
-      fairId       : @fairId
+      restrictType: @restrictType
+      mode: @mode
+      fairId: @fairId

-    @on 'search:start',    @indicateLoading
+    @on 'search:start', @indicateLoading
     @on 'search:complete', @concealLoading
-    @on 'search:opened',   @displaySuggestions
-    @on 'search:closed',   @hideSuggestions
+    @on 'search:opened', @displaySuggestions
+    @on 'search:closed', @hideSuggestions

     @setupTypeahead()

   events:
-    'keyup input' : 'checkSubmission'
-    'focus input' : 'trackFocusInput'
+    'keyup input': 'checkSubmission'
+    'focus input': 'trackFocusInput'

   trackFocusInput: ->
     analytics.track.click "Focused on search input"
@@ -70,15 +69,15 @@

   setupBloodHound: ->
     @hound = new Bloodhound
-      limit          : @limit
-      datumTokenizer : Bloodhound.tokenizers.obj.whitespace 'value'
-      queryTokenizer : Bloodhound.tokenizers.whitespace
+      limit: @limit
+      datumTokenizer: Bloodhound.tokenizers.obj.whitespace 'value'
+      queryTokenizer: Bloodhound.tokenizers.whitespace
       remote:
-        url    : "#{@search._url()}&term=%QUERY"
-        filter : (results) =>
+        url: "#{@search._url()}&term=%QUERY"
+        filter: (results) =>
           @trackSearchResults results
           @search._parse results
-        ajax   :
+        ajax:
           beforeSend: (xhr) =>
             xhr.setRequestHeader 'X-XAPP-TOKEN', sd.ARTSY_XAPP_TOKEN
             @trigger 'search:start', xhr
@@ -107,8 +106,8 @@
   selectResult: (e, model) ->
     return false unless model and model.get('published')
     analytics.track.click 'Selected item from search',
-      query : @query
-      label : analytics.modelNameAndIdToLabel model.get('display_model'), model.id
+      query: @query
+      label: analytics.modelNameAndIdToLabel model.get('display_model'), model.id
     @selected = true
     window.location = model.get 'location'


adding mtime

adding mtime

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -59,7 +59,7 @@
   Sprite.loadSheet = (name, tileWidth, tileHeight) ->
     directory = App?.directories?.images || "images"

-    url = "#{BASE_URL}/#{directory}/#{name}.png"
+    url = "#{BASE_URL}/#{directory}/#{name}.png?#{MTIME}"

     console.log url

@@ -166,7 +166,7 @@
   ###
   window.Sprite.loadByName = (name, callback) ->
     directory = App?.directories?.images || "images"
-    url = "#{BASE_URL}/#{directory}/#{name}.png"
+    url = "#{BASE_URL}/#{directory}/#{name}.png#{MTIME}"
     Sprite.load(url, callback)

   window.Sprite.create = Sprite


brackets extension

brackets extension

--- coffee/codemirror-syntax.coffee
+++ coffee/codemirror-syntax.coffee
@@ -222,5 +222,23 @@
   return mode

-new Mode CodeMirror
-
+if define? and brackets?
+ define (require, exports, module) ->
+  "use strict"
+
+  LanguageManager = brackets.getModule "language/LanguageManager"
+  CodeMirror = brackets.getModule "thirdparty/CodeMirror2/lib/codemirror"
+
+  new Mode CodeMirror
+
+  lang = LanguageManager.defineLanguage "docscript",
+   name: "Docscript"
+   mode: "docscript"
+   fileExtensions: [".ds"]
+   lineComment: ["\/\/"]
+
+  lang.done ->
+   console.log "[Docscript] Module loaded."
+
+else if CodeMirror?
+ new Mode CodeMirror


Pass element to callback functions

Pass element to callback functions

--- src/scripts/helpers/backbone/views/editable.coffee
+++ src/scripts/helpers/backbone/views/editable.coffee
@@ -41,7 +41,7 @@
             when 'contenteditable' then $editable.attr('contenteditable', true)
             when 'aloha' then console.log 'FIX: enable aloha'

-          options.onEditable?()
+          options.onEditable?($editable)
           $editable.each (index) =>
             if @observers[selector] then @observers[selector].disconnect()

@@ -69,7 +69,7 @@
             when 'contenteditable' then $editable.attr('contenteditable', false)
             when 'aloha' then console.log 'FIX: disable aloha'

-          options.onUneditable?()
+          options.onUneditable?($editable)
           @observers[selector].disconnect()
           delete @observers[selector]


fix issue #8

fix issue #8

--- node-vault.coffee
+++ node-vault.coffee
@@ -132,8 +132,10 @@
       json: data
       uri: uri
     , (err, res, body)->
-      debug err if err
-      debug "RES #{res.statusCode}"
+      if err
+        debug err
+        return done err
+      debug "RES #{res?.statusCode}"
       if body
         body = JSON.parse body if typeof body != 'object'
         debug body


fix what appears to be a mistake

fix what appears to be a mistake

--- library/membrane/mongo.coffee
+++ library/membrane/mongo.coffee
@@ -87,14 +87,15 @@
         logger.info message.cyan.underline, host, port
         server = new mongodb.Server host, port, options
         kernel.mongo = new mongodb.MongoClient server
-        kernel.mongo.open @openMongoConnection.bind @
+        return kernel.mongo.open (error, client) =>
+            @openMongoConnection next, error, client

     # A presumably internal method that gets invoked by the primary
     # implementation to actually open the connection to previously
     # configured MongoDB database and if relevant - set up required
     # premises, such as the database name, among other things. This
     # method should not be called directly in most if cases at hand.
-    openMongoConnection: (error, client) ->
+    openMongoConnection: (next, error, client) ->
         assert _.isObject config = nconf.get "mongo"
         assert.ifError error, "mongo failed: #{error}"
         assert.ok _.isObject @kernel.mongo = client


add , *, <

add , *, <

--- a-lisp.coffee
+++ a-lisp.coffee
@@ -38,6 +38,28 @@
       sum += arg for arg in args when typeof arg == 'number'
       sum
     )
+    '-': new ALFunction( '-', ['&rest','values'], (args...)->
+      sum = 0
+      sum -= arg for arg in args when typeof arg == 'number'
+      sum
+    )
+    '*': new ALFunction( '*', ['&rest','values'], (args...)->
+      sum = 1
+      sum *= arg for arg in args when typeof arg == 'number'
+      sum
+    )
+    '<': new ALFunction( '<', ['&rest','values'], (args...)->
+
+      prev = args[0]
+      for arg in args[1..]
+        if prev >= arg
+          return 'NIL'
+        else
+          prev = arg
+
+      return 'T'
+    )
+    # TODO: implement lispy division, reduce fractions but don't return floating point values
     CONS: new ALFunction('CONS', ['first','&rest','values'], (first, rest)->
       throw {message: "wrong number of arguments to CONS"} if arguments.length != 2


:bug: Fix error when updating dependencies if a null variable is present

Fixes #48

--- lib/variables-collection.coffee
+++ lib/variables-collection.coffee
@@ -343,6 +343,7 @@

     variables = variables.concat(updated) if updated?
     variables = variables.concat(destroyed) if destroyed?
+    variables = variables.filter (v) -> v?

     for variable in variables
       if dependencies = @dependencyGraph[variable.name]


Support for custom native android files added

--- lib/tasks/build/bundle/android.coffee
+++ lib/tasks/build/bundle/android.coffee
@@ -7,6 +7,7 @@
 cp = require 'child_process'

 OUT_DIR = './build/android/'
+CUSTOM_NATIVE_OUT_DIR = "#{OUT_DIR}app/src/main/java/io/neft/CustomApp/"
 STATIC_OUT_DIR = "#{OUT_DIR}app/src/main/assets/static"
 ANDROID_BUNDLE_DIR = './build/android/'

@@ -30,6 +31,10 @@
 				mustacheFiles.push path
 				return false
 			true
+	log.end logtime
+
+	logtime = log.time "Copy custom native files into `#{CUSTOM_NATIVE_OUT_DIR}`"
+	fs.copySync "./native/android", CUSTOM_NATIVE_OUT_DIR
 	log.end logtime

 	logtime = log.time "Copy static files into '#{STATIC_OUT_DIR}'"


Add attributes in to resulting product types files. Impement master/retailer logic.

--- src/coffee/producttypegenerator.coffee
+++ src/coffee/producttypegenerator.coffee
@@ -36,25 +36,33 @@
   Creates sphere product type representation files using JSON format.
   @param {array} types Entire types CSV as an array of records.
   @param {array} attributes Entire attributes CSV as an array of records.
-  @param {function} callback The callback function to be invoked when the method finished its work.
   @param {string} target The target folder for the generated files.
   @param {boolean} masterRetailerProject Set to true if product type files are used for a master/retailer projects, otherwise false.
+  @param {function} callback The callback function to be invoked when the method finished its work.
   @return Result of the given callback
   ###
-  run: (types, attributes, callback, target, masterRetailerProject) ->
+  run: (types, attributes, target, masterRetailerProject, callback) ->

     # build object with all attribute defintions for later usage
     attributeDefinitions = @_createAttributeDefinitions attributes

-    # add default attribute definiion 'masterSKU'
-    attributeDefinitions[ATTRIBUTE_NAME_MASTER_SKU] = @_createAttributeDefinitionMasterSku unless masterRetailerProject false
-
     # build product type definitions
     productTypeDefinitions = @_createProductTypesDefinitions types, attributeDefinitions

     # outpur product type files
     for productTypeDefinition in productTypeDefinitions
-      @_writeFile productTypeDefinition
+      @_writeFile productTypeDefinition, target
+
+    # handle master/retailer product types
+    if masterRetailerProject
+      # create attribute definition 'masterSKU'
+      attributeDefinitionMasterSku = @_createAttributeDefinitionMasterSku()
+      # build product type definitions used in retailer projects
+      productTypeDefinitionsRetailers = @_createProductTypesDefinitions types, attributeDefinitions, attributeDefinitionMasterSku
+
+      # outpur product type files
+      for productTypeDefinition in productTypeDefinitionsRetailers
+        @_writeFile productTypeDefinition, target, 'retailer-product-type'

     callback true

@@ -101,7 +109,6 @@
             key: row["enum#{@_capitalize(ATTRIBUTE_TYPE_ENUM_KEY)}"]
             label: @_i18n row, "#{ATTRIBUTE_TYPE_ENUM}#{@_capitalize(ATTRIBUTE_LABEL)}"

-
     attributeDefinitions

   ###
@@ -165,9 +172,10 @@
   Returns an object containing a key/value pairs (language/value) for each language.
   @param {array} types Entire types CSV as an array of records.
   @param {object} attributeDefinitions The object containing all attribute definitions
+  @pearm {object} defaultAttributeDefinition If given, the default attribute will be added to all resulting product typ definitions.
   @return Array containing product type definition objects
   ###
-  _createProductTypesDefinitions: (types, attributeDefinitions) ->
+  _createProductTypesDefinitions: (types, attributeDefinitions, defaultAttributeDefinition) ->

     productTypeDefinitions = []

@@ -176,6 +184,18 @@
         name: row['name']
         description: row['description']

+      for header, value of row
+        if value is 'x'
+          if attributeDefinitions[header]
+            productTypeDefinition['attributes'] = _.union (productTypeDefinition['attributes'] or []), attributeDefinitions[header]
+          else
+            console.log "Product type '#{row['name']}': no attribute definition found with name '#{header}'. Please check your CSV files. Skipping attribute..."
+
+
+      # add default attribute definition to attributes
+      if defaultAttributeDefinition
+        productTypeDefinition['attributes'] = _.union (productTypeDefinition['attributes'] or []), defaultAttributeDefinition
+
       productTypeDefinitions.push productTypeDefinition

     productTypeDefinitions
@@ -183,12 +203,14 @@
   ###
   Outputs given product definition as a file in JSON format.
   @param {object} productTypeDefinition The object containing product type definition.
-  ###
-  _writeFile: (productTypeDefinition, path) ->
+  @param {string} target The target folder for the file.
+  @param {string} prefix The prefix will be added to the resulting file name.
+  ###
+  _writeFile: (productTypeDefinition, target, prefix = 'product-type') ->

     prettified = JSON.stringify productTypeDefinition, null, 4

-    fileName = "#{@_options.target}/product-type-#{productTypeDefinition[PRODUCT_TYPE_NAME]}.json"
+    fileName = "#{target}/#{prefix}-#{productTypeDefinition[PRODUCT_TYPE_NAME]}.json"
     fs.writeFile fileName, prettified, (error) ->
       if error
         console.log "Error while writing file #{fileName}: #{error}"


Simplify diff.

Simplify diff.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -71,7 +71,7 @@
     @linkHintsMode = new LinkHintsMode hints, mode

   postKeyState: (request) ->
-    @linkHintsMode.updateVisibleMarkersFromCoordinator request
+    @linkHintsMode.postKeyState request

   activateActiveHintMarker: ->
     @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker
@@ -126,9 +126,9 @@
       HUD.showForDuration "No links to select.", 2000
       return

-    @hintMarkers = (@createMarkerFor(el) for el in elements)
+    hintMarkers = (@createMarkerFor(el) for el in elements)
     @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints)
-    @markerMatcher.fillInMarkers @hintMarkers
+    @markerMatcher.fillInMarkers hintMarkers

     @hintMode = new Mode
       name: "hint/#{mode.name}"
@@ -138,27 +138,26 @@
       suppressTrailingKeyEvents: true
       exitOnEscape: true
       exitOnClick: true
-      keydown: @onKeyDownInMode.bind this, @hintMarkers
-      keypress: @onKeyPressInMode.bind this, @hintMarkers
+      keydown: @onKeyDownInMode.bind this, hintMarkers
+      keypress: @onKeyPressInMode.bind this, hintMarkers
+
+    @hintMode.onExit (event) =>
+      if event?.type == "click" or (event?.type == "keydown" and KeyboardUtils.isEscape event) or
+          (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ])
+        HintCoordinator.sendMessage "exitFailure"

     @setOpenLinkMode mode
-    @hintMode.onExit (event) =>
-      HintCoordinator.sendMessage "exitFailure" if @isExitFailureEvent event

     # Note(philc): Append these markers as top level children instead of as child nodes to the link itself,
     # because some clickable elements cannot contain children, e.g. submit buttons.
-    @hintMarkerContainingDiv = DomUtils.addElementList @hintMarkers,
+    @hintMarkerContainingDiv = DomUtils.addElementList hintMarkers,
       id: "vimiumHintMarkerContainer", className: "vimiumReset"
     # Hide markers from other frames.
-    @hideMarker marker for marker in @hintMarkers when marker.hint.frameId != frameId
+    @hideMarker marker for marker in hintMarkers when marker.hint.frameId != frameId
+    @postKeyState = @postKeyState.bind this, hintMarkers

   setOpenLinkMode: (@mode) ->
     @hintMode.setIndicator @mode.indicator if DomUtils.isTopFrame()
-
-  # This tests whether this event is an event which indicates that we're exiting wthout selecting a link.
-  isExitFailureEvent: (event) ->
-    (event?.type == "keydown" and KeyboardUtils.isEscape event) or
-      (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ])

   #
   # Creates a link marker for the given link.
@@ -171,8 +170,7 @@
       marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker"
       marker.clickableItem = link.element
       marker.stableSortCount = ++stableSortCount
-      # Keep track of the original hint.  We'll need this to decide which markers to display, and to activate
-      # hints in other frames.
+      # Keep track of the original hint descriptor.  We'll need this to decide which markers to display.
       marker.hint = link
       marker.linkText = link.linkText
       marker.showLinkText = link.showLinkText
@@ -185,9 +183,8 @@

       marker

-# TODO(smblott) It is not intended that the code remain structured this way.  This temporary in order to keep
-# the diff smaller and clearer.  Basically, we need to move the whole of ClickableElements, here, out of the
-# LinkHintsMode class.
+# TODO(smblott) It is not intended that this remain structured this way.  This is temporary, just to keep the
+# diff smaller and clearer.  We need to move the whole of ClickableElements out of the LinkHintsMode class.
 ClickableElements =
   #
   # Determine whether the element is visible and clickable. If it is, find the rect bounding the element in
@@ -372,8 +369,8 @@

     {linkText, showLinkText}

-# TODO(smblott) It is not intended that the code remain structured this way.  This a temporary in order to
-# keep the diff smaller and clearer.  Basically, we need to move a lot of lines around.
+# TODO(smblott) It is not intended that this remain structured this way.  This is temporary, just to keep the
+# diff smaller and clearer.  We need to move the whole of ClickableElements out of the LinkHintsMode class.
 class LinkHintsMode extends LinkHintsModeBase
   constructor: (args...) -> super args...

@@ -411,7 +408,7 @@

     else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
       if @markerMatcher.popKeyChar()
-        @updateVisibleMarkers()
+        @updateVisibleMarkers hintMarkers
       else
         # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and
         # knows not to restart hints mode.
@@ -423,7 +420,7 @@

     else if event.keyCode == keyCodes.tab
       @tabCount = previousTabCount + (if event.shiftKey then -1 else 1)
-      @updateVisibleMarkers @tabCount
+      @updateVisibleMarkers hintMarkers, @tabCount

     else
       return
@@ -438,25 +435,25 @@
     keyChar = String.fromCharCode(event.charCode).toLowerCase()
     if keyChar
       @markerMatcher.pushKeyChar keyChar, @keydownKeyChar
-      @updateVisibleMarkers()
+      @updateVisibleMarkers hintMarkers

     # We've handled the event, so suppress it.
     DomUtils.suppressEvent event

-  updateVisibleMarkers: (tabCount = 0) ->
+  updateVisibleMarkers: (hintMarkers, tabCount = 0) ->
     {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher
     HintCoordinator.sendMessage "postKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}

-  updateVisibleMarkersFromCoordinator: ({hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
+  postKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
     extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue}

-    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints @hintMarkers, tabCount
+    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount
     if linksMatched.length == 0
       @deactivateMode()
     else if linksMatched.length == 1
       @activateLink linksMatched[0], userMightOverType ? false
     else
-      @hideMarker marker for marker in @hintMarkers
+      @hideMarker marker for marker in hintMarkers
       @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched

   #
@@ -464,7 +461,6 @@
   #
   activateLink: (linkMatched, userMightOverType=false) ->
     @removeHintMarkers()
-    HintCoordinator.onExit.push => @deactivateMode()
     clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hint)?.element

     if clickEl?
@@ -487,6 +483,7 @@
       if document.hasFocus()
         startKeyboardBlocker -> HintCoordinator.sendMessage "exit"

+    HintCoordinator.onExit.push => @deactivateMode()
     if userMightOverType and Settings.get "waitForEnterForFilteredHints"
       installKeyBoardBlocker (callback) -> new WaitForEnter callback
     else if userMightOverType


Trim linkText when it's collected, not repreatedly.

There's no need to repeatedly trim linkText.  Instead, just trim it when
it's first collected, then we can assume from there on that it's
trimmed.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -476,12 +476,13 @@
   # Filter link hints by search string, renumbering the hints as necessary.
   filterLinkHints: (hintMarkers) ->
     scoreFunction = @scoreLinkHint @linkTextKeystrokeQueue.join ""
-    matchingHintMarkers = hintMarkers
-      .filter (linkMarker) ->
-        linkMarker.score = scoreFunction linkMarker
-        0 < linkMarker.score
-      .sort (a, b) ->
-        if b.score == a.score then b.stableSortCount - a.stableSortCount else b.score - a.score
+    matchingHintMarkers =
+      hintMarkers
+        .filter (linkMarker) ->
+          linkMarker.score = scoreFunction linkMarker
+          0 < linkMarker.score
+        .sort (a, b) ->
+          if b.score == a.score then b.stableSortCount - a.stableSortCount else b.score - a.score

     if matchingHintMarkers.length == 0 and @hintKeystrokeQueue.length == 0 and 0 < @linkTextKeystrokeQueue.length
       # We don't accept typed text which doesn't match any hints.
@@ -499,8 +500,7 @@
   scoreLinkHint: (linkSearchString) ->
     searchWords = linkSearchString.trim().toLowerCase().split @splitRegexp
     (linkMarker) =>
-      text = linkMarker.linkText.trim()
-      linkWords = linkMarker.linkWords ?= text.toLowerCase().split @splitRegexp
+      linkWords = linkMarker.linkWords ?= linkMarker.linkText.toLowerCase().split @splitRegexp

       searchWordScores =
         for searchWord in searchWords
@@ -522,8 +522,8 @@
         addFunc = (a,b) -> a + b
         score = searchWordScores.reduce addFunc, 0
         # Prefer matches in shorter texts.  To keep things balanced for links without any text, we just weight
-        # them as if their length was 50.
-        score / Math.log 1 + (text.length || 50)
+        # them as if their length was 100 (so, quite long).
+        score / Math.log 1 + (linkMarker.linkText.length || 100)

 #
 # Make each hint character a span, so that we can highlight the typed characters as you type them.
@@ -761,9 +761,9 @@
       linkText = hint.reason
       showLinkText = true
     else
-      linkText = (element.textContent.trim() || element.innerHTML.trim())[...512]
-
-    {linkText, showLinkText}
+      linkText = element.textContent[...256] || element.innerHTML[...256]
+
+    {linkText: linkText.trim(), showLinkText}

 # Suppress all keyboard events until the user stops typing for sufficiently long.
 class TypingProtector extends Mode


Removing typo from menus.

Removing typo from menus.

--- menus/language-eml.cson
+++ menus/language-eml.cson
@@ -11,11 +11,11 @@
         'command': 'language-eml:base64-encode'
       },
       {
-        'label': 'Quoted-Printable decode decode',
+        'label': 'Quoted-Printable decode',
         'command': 'language-eml:quoted-printable-decode'
       },
       {
-        'label': 'Quoted-Printable decode encode',
+        'label': 'Quoted-Printable encode',
         'command': 'language-eml:quoted-printable-encode'
       }
     ]
@@ -40,11 +40,11 @@
           'command': 'language-eml:base64-encode'
         },
         {
-          'label': 'Quoted-Printable decode decode',
+          'label': 'Quoted-Printable decode',
           'command': 'language-eml:quoted-printable-decode'
         },
         {
-          'label': 'Quoted-Printable decode encode',
+          'label': 'Quoted-Printable encode',
           'command': 'language-eml:quoted-printable-encode'
         }
       ]


update with help link

update with help link

--- src/components/task-step/exercise.cjsx
+++ src/components/task-step/exercise.cjsx
@@ -40,8 +40,6 @@
     taskId: React.PropTypes.string.isRequired

   mixins: [ ChapterSectionMixin ]
-
-  updateFreeResponse: (freeResponse) -> TaskStepActions.updateTempFreeResponse(@props.id, freeResponse)

   contextTypes:
     router: React.PropTypes.func
@@ -225,6 +223,7 @@
       task={task}
       footer={footer}
       parts={parts}
+      helpLink={@renderHelpLink(part.related_content)}

       onStepCompleted={onStepCompleted}
       controlButtons={controlButtons}


Account for the wrapping

Account for the wrapping

--- library/visualize/remote.coffee
+++ library/visualize/remote.coffee
@@ -49,8 +49,8 @@
 # directly creating new objectables. Refer to the later for info.
 module.exports.objectable = (parameters...) ->
     objectable = new Objectable parameters...
-    objectable.sourcing().remote = objectable
-    check = _.isFunction objectable.sourcing()
+    objectable.sourcing()().remote = objectable
+    check = _.isObject objectable.sourcing()()
     assert.ok check; objectable.sourcing()()

 # A shorthand method for creating new instances of the executables.


Fixes team name display #1003 #1013

Fixes team name display #1003 #1013

--- app/assets/javascripts/admin/views/team/team_list_item_view.coffee
+++ app/assets/javascripts/admin/views/team/team_list_item_view.coffee
@@ -6,12 +6,12 @@

   tagName : "tr"
   template : _.template("""
-    <%= name %>
-    <% if(parent){ %><%= parent %><% } %>
-    <% if(owner){ %> <%= owner.firstName %> <%= owner.lastName %> <% }else{ %> - <% } %>
+    <%- name %>
+    <% if(parent){ %><%- parent %><% } %>
+    <% if(owner){ %> <%- owner.firstName %> <%- owner.lastName %> <% }else{ %> - <% } %>

       <% _.each(roles, function(role){ %>
-          <span class="label label-default" style="background-color: <%= TemplateHelpers.stringToColor(role.name) %>"><%= role.name %>
+          <span class="label label-default" style="background-color: <%= TemplateHelpers.stringToColor(role.name) %>"><%- role.name %>
       <% }) %>


#143472453 add honeybadger error in conditional logic

--- app/assets/javascripts/hanuman/hanuman-conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-conditional-logic.js.coffee
@@ -17,12 +17,17 @@
         $ruleContainer = $ruleContainer
         # the condition container
         $conditionContainer = $ruleContainer.siblings("[data-question-id=" + conditionQuestionId + "]")
-
         # if condition is outside of $ruleContainer siblings context, let's looks for it globally
         if $conditionContainer.length < 1
           $conditionContainer = $("[data-question-id=" + conditionQuestionId + "]")

-        #TODO add some kind of message to the user and honeybadger notification so that we know the rule is not working on the web
+        if $conditionContainer.length == 0 || $conditionContainer.length > 1
+          e = new Error("conditional Logic # findRules")
+          e.name = 'FAILED: conditional logic'
+          Honeybadger.notify e, context:
+            type: "FAILED: conditional logic => condition container not found or found more than once"
+            details: window.location.href
+

         # the condition element, which we need to check value for conditional logic


Allow prepareOutgoingRequestScript to stop execution and return a message

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -41,6 +41,25 @@

 	if word?
 		data.trigger_word = word
+
+	sendMessage = (message) ->
+		user = RocketChat.models.Users.findOneByUsername(trigger.username)
+
+		message.bot =
+			i: trigger._id
+
+		defaultValues =
+			alias: trigger.alias
+			avatar: trigger.avatar
+			emoji: trigger.emoji
+
+		if room.t is 'd'
+			defaultValues.channel = '@'+room._id
+		else
+			defaultValues.channel = '#'+room._id
+
+		message = processWebhookMessage message, user, defaultValues
+

 	opts =
 		params: {}
@@ -77,6 +96,8 @@
 			vmScript.runInNewContext sandbox
 			opts = sandbox.result
 			console.log 'result', opts
+			if opts.message?
+				return sendMessage opts.message
 		catch e
 			console.error "[Error running Script:]"
 			console.error script.replace(/^/gm, '  ')
@@ -150,25 +171,8 @@
 					return RocketChat.API.v1.failure 'error-running-script'

-			if not result?.data?.text? and not result?.data?.attachments?
-				return
-
-			user = RocketChat.models.Users.findOneByUsername(trigger.username)
-
-			result.data.bot =
-				i: trigger._id
-
-			defaultValues =
-				alias: trigger.alias
-				avatar: trigger.avatar
-				emoji: trigger.emoji
-
-			if room.t is 'd'
-				defaultValues.channel = '@'+room._id
-			else
-				defaultValues.channel = '#'+room._id
-
-			message = processWebhookMessage result.data, user, defaultValues
+			if result?.data?.text? or result?.data?.attachments?
+				sendMessage result.data

 ExecuteTrigger = (trigger, message, room) ->


swapped TOP and WRP menu items

swapped TOP and WRP menu items

--- client/prefs.coffee
+++ client/prefs.coffee
@@ -39,8 +39,8 @@
     _View
       Show Language Bar        =LBR
       Float New Editors        =FLT
+      Editors on Top           =TOP {!browser}
       Line Wrapping in Session =WRP
-      Editors on Top           =TOP {!browser}
       -                             {!browser}
       Increase Font Size       =ZMI {!browser}
       Decrease Font Size       =ZMO {!browser}


Alternative fix for #432 that also works with formats added via quill.addFormat()

--- src/modules/keyboard.coffee
+++ src/modules/keyboard.coffee
@@ -92,7 +92,7 @@
     )
     _.each(['bold', 'italic', 'underline'], (format) =>
       this.addHotkey(Keyboard.hotkeys[format.toUpperCase()], (range) =>
-        if (@quill.options.formats.indexOf(format) > -1)
+        if (@quill.editor.doc.formats[format])
           this.toggleFormat(range, format)
         return false
       )


adapt keyboard to new dom interface

adapt keyboard to new dom interface

--- src/modules/keyboard.coffee
+++ src/modules/keyboard.coffee
@@ -6,13 +6,13 @@

 class Keyboard
   @hotkeys:
-    BOLD:       { key: 'B',          meta: true }
-    INDENT:     { key: DOM.KEYS.TAB, shift: false }
-    ITALIC:     { key: 'I',          meta: true }
-    OUTDENT:    { key: DOM.KEYS.TAB, shift: true }
-    UNDERLINE:  { key: 'U',          meta: true }
-    UNDO:       { key: 'Z',          meta: true, shift: false }
-    REDO:       { key: 'Z',          meta: true, shift: true }
+    BOLD:       { key: 'B',          metaKey: true }
+    INDENT:     { key: DOM.KEYS.TAB, shiftKey: false }
+    ITALIC:     { key: 'I',          metaKey: true }
+    OUTDENT:    { key: DOM.KEYS.TAB, shiftKey: true }
+    UNDERLINE:  { key: 'U',          metaKey: true }
+    UNDO:       { key: 'Z',          metaKey: true, shiftKey: false }
+    REDO:       { key: 'Z',          metaKey: true, shiftKey: true }

   @NAVIGATION: [DOM.KEYS.UP, DOM.KEYS.DOWN, DOM.KEYS.LEFT, DOM.KEYS.RIGHT]

@@ -24,7 +24,6 @@

   addHotkey: (hotkey, callback) ->
     hotkey = if _.isObject(hotkey) then _.clone(hotkey) else { key: hotkey }
-    hotkey.key = hotkey.key.toUpperCase().charCodeAt(0) if _.isString(hotkey.key)
     hotkey.callback = callback
     @hotkeys[hotkey.key] ?= []
     @hotkeys[hotkey.key].push(hotkey)
@@ -62,15 +61,14 @@

   _initListeners: ->
     DOM.addEventListener(@editorContainer, 'keydown', (event) =>
-      if @hotkeys[event.which]?
-        prevent = false
+      prevent = false
+      if @hotkeys[event.key]?
         range = @quill.getSelection()
-        if range?   # Should only not be the case if keydown was programmatically triggered
-          _.each(@hotkeys[event.which], (hotkey) =>
-            return if hotkey.meta? and (event.metaKey != hotkey.meta and event.ctrlKey != hotkey.meta)
-            return if hotkey.shift? and event.shiftKey != hotkey.shift
-            prevent = hotkey.callback(range) == false or prevent
-          )
+        _.each(@hotkeys[event.key], (hotkey) =>
+          return if hotkey.metaKey? and (event.metaKey != hotkey.metaKey and event.ctrlKey != hotkey.metaKey)
+          return if hotkey.shiftKey? and event.shiftKey != hotkey.shiftKey
+          prevent = hotkey.callback(range) == false or prevent
+        )
       return !prevent
     )


Disabled html minify

Disabled html minify

--- .metalsmith/gulpfile.coffee
+++ .metalsmith/gulpfile.coffee
@@ -78,10 +78,14 @@
 # Note: This is a "hack" until i can find a clear
 # metalsmith->gulp pipe solution. When that happens, we'll
 # just pipe straight from metalsmith into gulp-minify-html.
-gulp.task 'metalsmith:minify', ['metalsmith'], ->
-  gulp.src 'build/**/*.html'
-    .pipe minifyHtml()
-    .pipe gulp.dest 'build'
+gulp.task 'metalsmith:minify', ['metalsmith']
+# DISABLED
+# The minify-html module was returning a callstack error. To deal with this
+# i am disabling it.
+#gulp.task 'metalsmith:minify', ['metalsmith'], ->
+#  gulp.src 'build/**/*.html'
+#    .pipe minifyHtml()
+#    .pipe gulp.dest 'build'

 # ## preview


Fixing a bug in IE11 where the event was being read incorrectly

--- flaps.coffee
+++ flaps.coffee
@@ -12,11 +12,12 @@
 		tab.addEventListener('click', @eventListener.bind(@)) for tab in @tabheaders

 	eventListener: (e) ->
-		item = e.toElement.dataset.itemPosition
+		target = e.target || window.event.srcElement;
+		item = target.dataset.itemPosition

 		if item != @current
 			current = document.querySelector('.flap:nth-of-type('+@current+')')
 			newCurrent = document.querySelector('.flap:nth-of-type('+item+')')
 			current.classList.add('flap-closed');
 			newCurrent.classList.remove('flap-closed');
-			@current = item+			@current = item


parse timestamps

parse timestamps


no dompurify in comment

no dompurify in comment

--- models/comment.coffee
+++ models/comment.coffee
@@ -1,7 +1,6 @@
 #
 # Model for a comment
 #
-DOMPurify = require 'dompurify'
 Base = require "./base.coffee"
 sd = require("sharify").data
 _ = require 'underscore'
@@ -37,7 +36,6 @@
         lastPosition = entity.end

     html += text.slice(lastPosition)
-    DOMPurify.sanitize(html, {KEEP_CONTENT: false})
     html

   getPermissions: (user)->


[add] get mongodb server url from environment variable

--- models/reports.coffee
+++ models/reports.coffee
@@ -1,5 +1,10 @@
 mongoose = require('mongoose')
-mongoose.connect('mongodb://192.168.113.51/brittyscenes');
+if process.env.MONGO_URL?
+  url = process.env.MONGO_URL
+else
+  url = 'mongodb://192.168.113.51/brittyscenes'
+mongoose.connect(url);
+
 #mongoose.connect('mongodb://212.71.249.18/brittyscenes');
 io = undefined


update joyent description

update joyent description

--- models/sponsor.coffee
+++ models/sponsor.coffee
@@ -4,15 +4,9 @@
   url:   'http://www.joyent.com/'
   description:
     '''
-    Joyent is the high-performance cloud infrastructure company, offering
-    enterprises and developers the best public, private and hybrid cloud
-    infrastructure for today’s web and mobile applications. Joyent delivers
-    public cloud services to some of the most innovative companies in the
-    world, including LinkedIn, Voxer, and Modcloth, and licenses the software
-    that runs its public cloud to enterprises wishing to operate their own
-    private or hybrid clouds. Joyent is also the sponsor and corporate steward
-    of Node.js, the widely embraced server-side JavaScript development
-    environment. For more information, visit <http://www.joyent.com/>
+    Joyent is the high-performance cloud infrastructure and big data analytics company. The Joyent Cloud is built from the ground up to meet the requirements of today's demanding real-time web and mobile applications. The Joyent Manta Storage Service is the first compute-on-storage innovation that eliminates data movement and enables inexpensive, lightning fast big data analysis in-place, transforming the economics and utility of object storage and real-time big data. Joyent is the steward of Node.js, the open source server-side JavaScript project that provides developers and enterprises with the most powerful runtime for developing data-intensive, real-time apps.
+
+    For more information visit <http://www.joyent.com/>.
     '''
 ,
   name:  'groupon'


fixes blog post formatting

fixes blog post formatting

--- models/blog_post.coffee
+++ models/blog_post.coffee
@@ -10,4 +10,4 @@
     authorDescriptions[@get('author').login] || ''

   getFormattedDate: ->
-    moment(@get('date')).format('d MMMM YYYY')
+    moment(@get('date')).format('D MMMM YYYY')


:lipstick:

:lipstick:

--- lib/motions.coffee
+++ lib/motions.coffee
@@ -274,15 +274,14 @@
         true

   confirm: (view) =>
-    window.motion = @
     @editor.trigger 'vim-mode:search-complete'

   findOrBeep: (callback)->
-      pos = @findNext(@view.value)
-      if pos?
-        callback(pos)
-      else
-        shell.beep()
+    pos = @findNext(@view.value)
+    if pos?
+      callback(pos)
+    else
+      shell.beep()

   findNext: (term) ->
     regexp = new RegExp(term, 'g')


Delete index.coffee

Delete index.coffee

--- moon-phase/index.coffee
+++ moon-phase/index.coffee
@@ -1,377 +0,0 @@
-
-# set locale
-locale =
-  city             : ''
-  region           : ''
-  country          : ''
-
-# set preferences
-option =
-  fontName         : 'Futura'
-  fontSize         : 14        # scales the overall widget size
-  fontColor        : '#FFF'    #
-  fontColorMuted   : '#FFF'    # for RUS label; at 50% opacity
-  iconSet          : 'lit'     # pixels are "lit" or "shadow" side of moon
-  iconColor        : '#FFF'    # pixels
-  iconBackground   : '#FFF'    #
-  iconOpacity      : 0.10      # percentage (0.01-1.00); 0 is transparent
-  widgetBackground : '#FFF'    #
-  widgetOpacity    : 0.02      # percentage (0.01-1.00); 0 is transparent
-  showPhase        : true      # the current moon phase
-  showIcon         : true      #
-  showCity         : true      #
-  showCoords       : true      # latitude and longitude
-  showAge          : true      # age in days
-  showIllum        : true      # percentage illumination
-  showRUS          : true      # w/separator, upcoming times for rise, upper transit, set
-  showClosest      : true      # w/separator, closest phase, either ahead or behind
-  showCloseDay     : true      #  +-> closest phase date, iff showClosest is true
-  showAMPM         : false     # default is military time
-
-refreshFrequency   : '1hr'     # 1hr is best accuracy for moon illumination data
-
-
-# do the heavy lifting; escape spaces
-command: "moon-phase.widget/get-phase.sh \"#{locale.city}\"   \
-                                         \"#{locale.region}\" \
-                                         \"#{locale.country}\""
-
-
-style: """
-  left             : 85px
-  top              : 15px
-  border-radius    : 20px
-  padding          : 10px
-  background       : rgba(#{option.widgetBackground}, #{option.widgetOpacity})
-  display          : inline-block
-  vertical-align   : bottom
-  line-height      : 1.5
-
-  color            : #{option.fontColor}
-  font-family      : #{option.fontName}
-  text-align       : center
-
-  icon-size = #{option.fontSize * 5}px
-
-  .error
-    font-size      : #{option.fontSize}px
-    color          : #FF0000
-    background     : rgba(#000000, 0.5)
-
-  .icon
-    display        : inline-block
-    font-family    : 'Weather'
-    vertical-align : middle
-    font-size      : #{option.fontSize}px
-    vertical-align : middle
-    text-align     : center
-
-    fill           : #{option.iconBackground}
-    width          : icon-size * 3
-    height         : icon-size * 3
-    max-width      : icon-size * 3
-    max-height     : icon-size * 3
-    border-radius  : 50%
-    background     : rgba(#{option.iconBackground}, #{option.iconOpacity})
-
-    img
-      width        : 100%
-
-    @font-face
-      font-family  : 'Weather'
-      src url(weather.widget/moon-icons.svg) format('svg')
-
-  .current
-    position       : relative
-    display        : inline-block
-    white-space    : nowrap
-    text-align     : center
-
-  .current .phase
-    font-size      : #{option.fontSize * 1.7}px
-    font-weight    : bold
-  .current .illum
-    font-size      : #{option.fontSize}px
-  .current .age
-    font-size      : #{option.fontSize}px
-
-  .current .phenomenae
-    display        : flex
-    justify-content: space-between
-  .current .phenomenae div
-    color          : rgba(#{option.fontColorMuted}, 0.5)
-    font-size      : #{option.fontSize * 0.75}px
-    width          : 30%
-
-  .current .phentimes
-    display        : flex
-    justify-content: space-between
-  .current .phentimes div
-    font-size      : #{option.fontSize}px
-    width          : 30%
-
-  .current .location
-    font-size      : #{option.fontSize * 1.5}px
-
-  .current .coords
-    font-size      : #{option.fontSize}px
-
-  .current .separator
-    background     : #666666
-    border-top     : #{option.fontSize / 10}px solid rgba(#{option.fontColor}, 0.5)
-    height         : #{option.fontSize / 10}px
-
-  .closest .clphase
-    font-size      : #{option.fontSize * 0.85}px
-  .closest .cldate
-    font-size      : #{option.fontSize * 0.85}px
-
-"""
-
-
-option : option
-
-
-render: -> """
-

-

-
-

-

-

-

-

-

-
-

-

-

-
-

-

-

-

-

-
-

-"""
-
-
-renderMoonData: (data) ->
-  moonEl = @$domEl.find('.current')
-
-  today = new Date()
-
-  # calculate current phase based on known new moon, modulo 29.53
-  # https://en.wikipedia.org/wiki/Lunar_phase#Calculating_phase
-  synodic_month = 29.530588853
-
-  # the most recent new moon; TODO: log last new moon seen to a file?
-  new_moon = new Date('03/09/2016 01:54 GMT')
-
-  #today = new Date('03/08/2016 18:35 CST')
-  #new_moon = new Date('02/08/2016 14:39 GMT')
-
-  ms_per_day = 1000 * 60 * 60  * 24
-  days_passed = (today.getTime() - new_moon.getTime()) / ms_per_day
-  moon_age = days_passed % synodic_month
-  moon_age = (moon_age).toFixed(1)
-
-  if @option.showPhase
-    # if moon phase isn't modulo 25, curphase is provided
-    if data.curphase?
-      curphase = data.curphase
-    else
-      curphase = data.closestphase.phase
-    moonEl.find('.phase').text "#{curphase}"
-
-# ###FIXME
-  if @option.showIcon
-    #icon = @getIcon(Math.floor(moon_age), @option.iconSet)
-    #console.error icon
-    #console.error 'Moon age:', moon_age
-    #console.error 'Floored :', Math.floor(moon_age)
-    moonEl.find('.icon').html @getIcon(Math.floor(moon_age), @option.iconSet)
-
-  if @option.showCity
-    moonEl.find('.location').text "#{data.city}"
-
-  if @option.showAge
-    moonEl.find('.age').text "#{moon_age} days old"
-
-  if @option.showIllum
-    # handle leading zeroes
-    fracillum = Number(data.illum)
-    moonEl.find('.illum').text "#{fracillum}% illumination"
-
-  if @option.showCoords
-    if data.lat > 0 then latcard = 'N' else latcard = 'S'
-    if data.lon > 0 then loncard = 'E' else loncard = 'W'
-    lat = Math.abs((data.lat).toFixed(2))
-    lon = Math.abs((data.lon).toFixed(2))
-    moonEl.find('.coords').text "#{lat}#{latcard} #{lon}#{loncard}"
-
-  # upcoming phenomenon and times; order based on current time
-  if @option.showRUS
-    count = Object.keys(data.moondata).length
-
-    phen = moonEl.find('.phenomenae')
-    phen.empty()
-    if count < 3
-      phenName = @returnPhenomenae("#{data.prevmoondata[0].phen}")
-      phen.append "
#{phenName}
"
-    for d in data.moondata
-      phenName = @returnPhenomenae("#{d.phen}")
-      phen.append "
#{phenName}
"
-
-    time = moonEl.find('.phentimes')
-    time.empty()
-    if count < 3
-      if @option.showAMPM
-        my_time = @returnAMPM(data.prevmoondata[0].time)
-      else
-        my_time = data.prevmoondata[0].time
-      time.append "
#{my.time}
"
-    for d in data.moondata
-      if @option.showAMPM
-        my_time = @returnAMPM(d.time)
-      else
-        my_time = d.time
-      time.append "
#{my_time}
"
-
-  if @option.showClosest
-    # closest phase looks both backwards and fowards: determine which
-    clday = new Date(data.closestphase.date + " " + data.closestphase.time)
-
-    # compare in milliseconds to avoid string issues
-    if clday.getTime() > today.getTime()
-      clause = 'is'
-    else
-      clause = 'was'
-
-    moonEl.find('.clphase').text "Closest phase #{clause} #{data.closestphase.phase}"
-
-    if @option.showCloseDay
-      # prepare time
-      if @option.showAMPM
-        my_time = @returnAMPM(data.closestphase.time)
-      else
-        my_time = data.closestphase.time
-      moonEl.find('.cldate').text "on #{data.closestphase.date} at #{my_time}"
-
-
-renderError: (data) ->
-  moonEl = @$domEl.find('.moon')
-  moonEl.find('.current').text "moon-phase: #{data.message}"
-  moonEl.append "
moon-phase: #{data.message}
"
-
-
-update: (output, domEl) ->
-  @$domEl = $(domEl)
-
-  data = JSON.parse(output)
-  if data?.error
-    console.error 'moon-phase: ', data.message
-    return @renderError(data)
-  @renderMoonData data
-
-
-returnAMPM: (time) ->
-  my_time = time.split(':')
-  hh = Number(my_time[0])
-  mm = Number(my_time[1])
-  if hh >= 12 then time_suffix = 'PM' else time_suffix = 'AM'
-  if hh > 12 then hh = hh - 12
-  if mm < 10 then mm = '0' + mm
-  my_time = hh + ':' + mm + ' ' + time_suffix
-  return my_time
-
-
-returnPhenomenae: (code) ->
-  if code is "R" then return "Rises"
-  if code is "U" then return "Upper Transit"
-  if code is "S" then return "Sets"
-
-
-getIcon: (code, iconSet) ->
-  if iconSet is 'lit'
-    @getLitIcon(code)
-  else
-    @getShadowIcon(code)
-
-getLitIcon: (code) ->
-  if code < 0 or code > 29
-    return @iconLitMapping['na']
-  else
-    @iconLitMapping[code]
-
-getShadowIcon: (code) ->
-  if code < 0 or code > 29
-    return @iconShadowMapping['na']
-  else
-    @iconShadowMapping[code1]
-
-iconLitMapping:
-  0  : ""
-  1  : ""
-  2  : ""
-  3  : ""
-  4  : ""
-  5  : ""
-  6  : ""
-  7  : ""
-  8  : ""
-  9  : ""
-  10 : ""
-  11 : ""
-  12 : ""
-  13 : ""
-  14 : ""
-  15 : ""
-  16 : ""
-  17 : ""
-  18 : ""
-  19 : ""
-  20 : ""
-  21 : ""
-  22 : ""
-  23 : ""
-  24 : ""
-  25 : ""
-  26 : ""
-  27 : ""
-  28 : ""
-  na : ""
-
-iconShadowMapping:
-  0  : ""
-  1  : ""
-  2  : ""
-  3  : ""
-  4  : ""
-  5  : ""
-  6  : ""
-  7  : ""
-  8  : ""
-  9  : ""
-  10 : ""
-  11 : ""
-  12 : ""
-  13 : ""
-  14 : ""
-  15 : ""
-  16 : ""
-  17 : ""
-  18 : ""
-  19 : ""
-  20 : ""
-  21 : ""
-  22 : ""
-  23 : ""
-  24 : ""
-  25 : ""
-  26 : ""
-  27 : ""
-  28 : ""
-  na : ""
-


Making tests better.

Making tests better.

--- test/index.coffee
+++ test/index.coffee
@@ -48,12 +48,14 @@

 describe 'Document', ->
-  before ->
+  before (done) ->
     # Empty the collection defensively
-    simple_collection.remove {}
-    MyDoc.remove {}
-    # Insert our simple doc into our collection
-    simple_collection.insert(simple_doc)
+    simple_collection.remove {}, (err) ->
+      MyDoc.remove {}, (err) ->
+        # Insert our simple doc into our collection
+        simple_collection.insert simple_doc, (err, doc) ->
+          throw err if err
+          done()

   after ->
     # Empty the collection back out
@@ -68,12 +70,10 @@
     MyDoc.attr.should.equal 'a'

   it "should be able to find a document", (done) ->
-    SimpleDoc.save _id: 'simple_doc', (err, doc) ->
-      throw err if err
-      SimpleDoc.findOne _id: 'simple_doc', (err, doc) ->
-        throw err if err
-        expect(doc).to.not.be.null
-        done()
+    SimpleDoc.findOne _id: 'simple_doc', (err, doc) ->
+      throw err if err
+      expect(doc).to.not.be.null
+      done()

   it "should not include findOne when you iterate over the document", ->
     for key, value of MyDoc
@@ -94,7 +94,8 @@
       throw err if err
       expect(doc).to.not.be.undefined
       doc.should.have.property '__schema'
-      doc.foobar.should.equal simple_doc.foo
+      doc.should.have.property 'foobar'
+      expect(doc.foobar).to.equal simple_doc.foo
       doc._id.should.equal simple_doc._id
       done()


workers: added accept method and modified noEmail invitation

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -40,6 +40,10 @@

       instance:
         remove:[
+          (signature Function)
+          (signature Object, Function)
+        ]
+        accept:[
           (signature Function)
           (signature Object, Function)
         ]
@@ -99,9 +103,10 @@
         type        : String
         default     : -> 'member'

-  accept$: secure (client, callback) ->
-    { delegate } = client.connection
-    @accept delegate, callback
+  accept$: permit 'send invitations',
+    success: (client, callback) ->
+      { delegate } = client.connection
+      @accept delegate, callback

   accept: (account, callback) ->
     operation = { $set : { status: 'accepted' } }
@@ -201,15 +206,16 @@
     inviteInfo = null

     queue = [
-      (fin) -> JInvitation.one { email, groupName }, fin
+      (fin) ->
+        JInvitation.one { email, groupName }, fin

     , (invite, fin) ->
       [fin, invite] = paramSwapper invite, fin

       return fin null, no  unless invite
       return invite.remove fin  if forceInvite
-
-      inviteInfo = { email, code: invite.code, alreadyInvited: yes }
+      invite.alreadyInvited = yes
+      inviteInfo = invite
       return fin null, yes

     , (alreadyInvited, fin) ->
@@ -226,7 +232,7 @@

     , (invite, fin) ->
       [fin, invite] = paramSwapper invite, fin
-      inviteInfo = { email, code: invite.code }  if invite
+      inviteInfo = invite

       return fin()  if noEmail or not invite
       JInvitation.sendInvitationEmail client, invite, fin


Modified test for private variables

Modified test for private variables

--- test/scope.coffee
+++ test/scope.coffee
@@ -48,14 +48,15 @@
   ok not x?

 test "#1183: super + fat arrows", ->
+  # removed _i var because _ prefix is for private
   dolater = (cb) -> cb()

   class A
   	constructor: ->
-  		@_i = 0
+  		@i = 0
   	foo : (cb) ->
   		dolater =>
-  			@_i += 1
+  			@i += 1
   			cb()

   class B extends A
@@ -64,11 +65,11 @@
   	foo : (cb) ->
   		dolater =>
   			dolater =>
-  				@_i += 2
+  				@i += 2
   				super cb

   b = new B
-  b.foo => eq b._i, 3
+  b.foo => eq b.i, 3

 test "#1183: super + wrap", ->
   class A


state machine

state machine

--- test/state.coffee
+++ test/state.coffee
@@ -1,9 +1,10 @@
 test "State events should check if event can be fired", ->
   ###
-    animation = {}
-
+    animation = Core
+      initialState: 'stand'
+
     animation.include(StateMachine)
-
+
     state = StateMachine car, { initial: 'parked' }, (machine) ->
       machine.event 'start', (event) ->
         event.transition({ from: 'parked', to: 'idling' })


Remove empty test suite

Remove empty test suite

--- test/suite.coffee
+++ test/suite.coffee
@@ -1 +0,0 @@
-


Fall back to `auction_state` for use on the server

--- models/sale.coffee
+++ models/sale.coffee
@@ -73,13 +73,17 @@
     @isAuction() and _.include(['preview'], @get('auction_state'))

   isOpen: ->
-    @get('clockState') is 'open'
+    @state() is 'open'

   isPreview: ->
-    @get('clockState') is 'preview'
+    @state() is 'preview'

   isClosed: ->
-    @get('clockState') is 'closed'
+    @state() is 'closed'
+
+  # We desire a state when accessing from the server
+  state: ->
+    if @has('clockState') then @get('clockState') else @get('auction_state')

   # @param {CurrentUser, Artwork (optional)}
   # @return {Object}
@@ -96,4 +100,4 @@
       {}

   date: (attr) ->
-    moment(@get attr)+    moment(@get attr)


code cleanup: remove unused file

code cleanup: remove unused file

--- client/Social/Members/ContentDisplays/staticprofilesettings.coffee
+++ client/Social/Members/ContentDisplays/staticprofilesettings.coffee
@@ -1,53 +0,0 @@
-class StaticProfileSettingsModalView extends KDModalView
-  constructor:(options={},data)->
-    options.title ?= 'Public Profile Settings'
-    options.content ?= 'Public Profile Settings live here'
-
-    options.buttons ?= {}
-    options.buttons["Save Changes"] ?=
-      cssClass : 'modal-clean-gray'
-      callback :=>
-        @saveSettings =>
-          @destroy()
-    options.buttons["Cancel"] ?=
-      cssClass : 'modal-cancel'
-      callback :=>
-        @destroy()
-
-    super options,data
-
-  saveSettings:(callback)->
-    callback()
-
-
-class StaticProfileTooltip extends KDView
-  constructor:(options,data)->
-    super options,data
-    @setClass 'static-profile-tooltip'
-
-    @staticPageSwitch = new KDOnOffSwitch
-      size          : 'tiny'
-      title         : 'Enable Public Page'
-      cssClass      : 'static-page-switch'
-      tooltip:
-        title : 'Enabling Public Page will expose your profile to the internet. Non-Koding users will be able to read your content, depending on your settings.'
-      defaultValue  : @getData().profile.staticPage?.show
-      callback:(state)=>
-        @getData().setStaticPageVisibility state, =>
-          # log 'done', arguments
-
-  viewAppended:->
-    super
-    @setTemplate @pistachio()
-    @template.update()
-
-  pistachio:->
-    {nickname}=@getData().profile
-    """
-

-    {{> @staticPageSwitch}}
-

-

-    Visit your Public Page
-

-    """


Tooltip for the referrer link and a minor styling fix

--- client/landing/app/MainApp/navigation/navigationpromotelink.coffee
+++ client/landing/app/MainApp/navigation/navigationpromotelink.coffee
@@ -4,6 +4,15 @@

     options.tagName  = "a"
     options.cssClass = "title"
+    options.tooltip  =
+      placement      : "right"
+      # offset         : -10
+      title          :
+        """
+        If anyone registers with your referrer code,
+        you will get 250MB Free disk space for your VM.
+        Up to 16GB!.
+        """

     super options, data


Revert "Text of old file in texteare fixed"

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -541,11 +541,10 @@
           @message_wrapper.html('')
           @child_state = 'initial'
           @coe.after(response.html).remove()
-          @allow_reset = "False"
+          @allow_reset="False"
           @reinitialize(@element)
           @has_been_reset = true
           @rebind()
-          @answer_area.val('')
           @reset_button.hide()
         else
           @errors_area.html(response.error)


Regexp quoting is hard

Regexp quoting is hard

--- src/parsers/slackapp.coffee
+++ src/parsers/slackapp.coffee
@@ -4,13 +4,13 @@
 _ = require 'underscore'

 # RegExp snippets for reuse
-TS = '\\[(\d{1,2}:\d{2}( [aApP][mM])?)\\]' # [11:22 AM] *or* [16:00]
+TS = '\\[(\\d{1,2}:\\d{2}( [aApP][mM])?)\\]' # [11:22 AM] *or* [16:00]

 # RegExps
 rxWs = /^\s*$/
-rxNickLine = new RegExp("^\s*(\S+)\s+#{TS}\s*$")
-rxTsLine = new RegExp("^\s*#{TS}\s*")
-rxNewMessagesLine = new RegExp("^\snew messages*\s*$")
+rxNickLine = new RegExp("^\\s*(\\S+)\\s+#{TS}\\s*$")
+rxTsLine = new RegExp("^\\s*#{TS}\\s*")
+rxNewMessagesLine = new RegExp("^\\snew messages*\\s*$")

 parseTs = (ts) ->
   parsed = moment(ts, ['h:mm a', 'H:mm'], true)


spurious space

spurious space

--- src/playback_session.coffee
+++ src/playback_session.coffee
@@ -1,7 +1,7 @@
 jQuery.fn.extend
   playback_recording: (options) ->
     return @each (_, playback_window) ->
-       playback_window = $(playback_window)
+      playback_window = $(playback_window)
       recording_name = playback_window.data("from")
       Recording.load recording_name, (recording_data) ->
           player = new Player(recording_data, playback_window)


Added highlighting for "w-for"

Added highlighting for "w-for"

--- grammars/marko.cson
+++ grammars/marko.cson
@@ -470,7 +470,7 @@
     'match': '(?<=[^=])\\b([a-zA-Z0-9:-]+)'
     'name': 'entity.other.attribute-name.html'
   'tag-marko-attribute':
-    'match': '(?<=[^=])\\b((if|for|else-if|body-only-if|attrs|w-bind|w-config|w-el-id|w-id)\\s*=|(else)\\b)'
+    'match': '(?<=[^=])\\b((if|for|else-if|body-only-if|attrs|w-bind|w-config|w-el-id|w-for|w-id)\\s*=|(else)\\b)'
     'captures':
       '2':
         'name': 'support.function.marko_attribute'