basic detection of request query parameters

--- app/routes/getHandler.coffee
+++ app/routes/getHandler.coffee
@@ -20,13 +20,19 @@
   # `params`
   #   *req* the incoming request
   handleRequest: (req, callback) ->
-    fs.readFile nconf.get('paths:jsonPath') + req.originalUrl + '/info.json', 'utf8', (err, data) ->
-      if err
-        callback err
-      else
-        data = JSON.parse data
-        if(data['info']?)
-          data['info']['expectedRuntime'] = Statistics.getMeanExecutionTime req.originalUrl
-        callback null, data
+    #check if incoming get request has a query param
+    if(Object.keys(req.query).length != 0)
+
       return
-    return
+    #else load info file
+    else
+      fs.readFile nconf.get('paths:jsonPath') + req.originalUrl + '/info.json', 'utf8', (err, data) ->
+        if err
+          callback err
+        else
+          data = JSON.parse data
+          if(data['info']?)
+            data['info']['expectedRuntime'] = Statistics.getMeanExecutionTime req.originalUrl
+          callback null, data
+        return
+      return


Fixed exceptions on empty selection

Fixed exceptions on empty selection

--- client/templates/includes/footer.coffee
+++ client/templates/includes/footer.coffee
@@ -1,6 +1,8 @@
 Template.footer.helpers
   phone_url: ->
     members = Session.get 'selectedItems'
+    if !members
+      members = []
     selected_users = Meteor.users.find({_id: {$in: members}}).fetch()
     if selected_users.length == 0
       "#"
@@ -13,13 +15,15 @@

   sms_url: ->
     members = Session.get 'selectedItems'
+    if !members
+      members = []
     selected_users = Meteor.users.find({_id: {$in: members}}).fetch()
     if selected_users.length == 0
       "#"
     else if selected_users.length == 1
       phone_number = selected_users[0].telephone_nr
+      #      "whatsapp://send?abid=#{phone_number}"
       "sms:#{phone_number}"
-#      "whatsapp://send?abid=#{phone_number}"

 Template.footer.events
   'click .logout': (e) ->


name: add registeredAt field to schema

name: add registeredAt field to schema

--- workers/social/lib/social/models/name.coffee
+++ workers/social/lib/social/models/name.coffee
@@ -29,6 +29,9 @@
       slugs           : Array # [collectionName, constructorName, slug, usedAsPath]
       constructorName : String
       usedAsPath      : String
+    registeredAt      :
+      type            : Date
+      default         : -> new Date

   @cycleSecretName =(name, callback)->
     JSecretName = require './secretname'
@@ -163,4 +166,4 @@
                       console.log "Couln't claim name #{name}"
                       callback err
                     else if ++j is docCount and ++i is konstructorCount
-                      callback null+                      callback null


renamed methods and added multineline values

--- lib/csss.coffee
+++ lib/csss.coffee
@@ -10,13 +10,12 @@
   eval: null
   _environments: {}

-  begin: '# csss definition starts here'
-
-  init: ->
+  # non public / protected
+
+  __init__: ->
     @_levels = []

-  # copy + paste from underscore
-  _extend: (obj) ->
+  __extend__: (obj) ->
     has = (obj, key) -> Object::hasOwnProperty.call(obj, key)
     each = (obj, iterator, context) ->
       return  unless obj?
@@ -37,66 +36,61 @@
           obj[prop] = source[prop]
     obj

-  import: (file) ->
-    @_import.push(file)
-
-  use: (yourClassName) ->
-    yourClass = @_environments[yourClassName]
-    if yourClass and typeof yourClass is 'function'
-      DocumentStyle::_extend(@,new yourClass)
-    else
-      throw new Error("Couldn't find/use '#{yourClassName}'")
-
-  add: ->
-    args = Array.prototype.slice.call(arguments)
-    @_levels.push(args)
-
-  addLine: (line, addTrailingSemicolon) ->
-    @add if addTrailingSemicolon then line.replace /\;+\s*$/, ';' else line
-
-  addEnvironment: (yourClass) ->
-    if typeof yourClass is 'function'
-      className = yourClass.toString()?.split('\n')?[0].replace(/^function\s(.+?)\(.*/, '$1')
-      @_environments[className] = yourClass
-
-  charset: (charset) -> @addLine("@charset '#{charset}';") if charset
-
-  page: (selector, values) ->
-    {selector, values} = @_selectorAndValues(selector, values)
-    # values = if values then values else ''
-    values = @object_to_css(values) if values
-    @addLine("@page #{selector} #{values}") if selector
-
-  _selectorAndValues: (selector,values) ->
-    if typeof selector is 'object'
-      { selector: '', values: selector }
-    else
-      selector ?= ''
-      { selector, values }
-  # charset: (charset) -> @addLine("@charset '#{charset}';") if charset
-  # charset: (charset) -> @addLine("@charset '#{charset}';") if charset
-
-  font_face: (query) ->
-    @add '@font-face', query
-
-  media: (query) ->
-    @add '@media', query
-
   object_to_css: (o) ->
     return '' if not o? or typeof o isnt 'object'
     parts = for attribute of o
       # we only escape on content attribute, or are there maybe more?
       escape = if attribute is 'content' then "'" else ''
-      "#{attribute}: #{escape}#{o[attribute]}#{escape};"
+      values = if o[attribute]?.constructor is Array then o[attribute].join(' ') else o[attribute]
+      "#{attribute}: #{escape}#{values}#{escape};"
     if parts.length > 0 then '{ '+parts.join('\n')+' }' else null

-
-class MyEnvironment
-
-  myPadding: ->
-    ( 12 + 'px' )
-
-DocumentStyle::addEnvironment(MyEnvironment)
+  # public for css usage
+
+  begin: true # @begin selector
+
+  import: (file) ->
+    @_import.push(file)
+
+  use: (yourClassName) ->
+    yourClass = @_environments[yourClassName]
+    if yourClass and typeof yourClass is 'function'
+      DocumentStyle::__extend__(@,new yourClass)
+    else
+      throw new Error("Couldn't find/use '#{yourClassName}'")
+
+  add: ->
+    args = Array.prototype.slice.call(arguments)
+    @_levels.push(args)
+
+  addLine: (line, addTrailingSemicolon) ->
+    @add if addTrailingSemicolon then line.replace /\;+\s*$/, ';' else line
+
+  __add_environment__: (yourClass) ->
+    if typeof yourClass is 'function'
+      className = yourClass.toString()?.split('\n')?[0].replace(/^function\s(.+?)\(.*/, '$1')
+      @_environments[className] = yourClass
+
+  charset: (charset) -> @addLine("@charset '#{charset}';") if charset
+
+  page: (selector, values) ->
+    {selector, values} = DocumentStyle::seperate_selector_and_values(selector, values)
+    # values = if values then values else ''
+    values = @object_to_css(values) if values
+    @addLine("@page #{selector} #{values}") if selector
+
+  seperate_selector_and_values: (selector,values) ->
+    if typeof selector is 'object'
+      { selector: '', values: selector }
+    else
+      selector ?= ''
+      { selector, values }
+  # @document, @keyframes, @viewport, @namespace, @support
+  font_face: (query) ->
+    @add '@font-face', query
+
+  media: (query) ->
+    @add '@media', query

 class CSSS

@@ -111,7 +105,7 @@
   declarationPart: null
   seperateDeclarations: true # TODO: make that obsolete

-  attributesTypes: 'animation|animation-name|animation-duration|animation-timing-function|animation-delay|animation-iteration-count|animation-direction|animation-play-state|background|background-attachment|background-color|background-image|background-position|background-repeat|background-clip|background-origin|background-size|border|border-bottom|border-bottom-color|border-bottom-style|border-bottom-width|border-color|border-left|border-left-color|border-left-style|border-left-width|border-right|border-right-color|border-right-style|border-right-width|border-style|border-top|border-top-color|border-top-style|border-top-width|border-width|outline|outline-color|outline-style|outline-width|border-bottom-left-radius|border-bottom-right-radius|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-radius|border-top-left-radius|border-top-right-radius|box-decoration-break|box-shadow|overflow-x|overflow-y|overflow-style|rotation|rotation-point|color-profile|opacity|rendering-intent|bookmark-label|bookmark-level|bookmark-target|float-offset|hyphenate-after|hyphenate-before|hyphenate-character|hyphenate-lines|hyphenate-resource|hyphens|image-resolution|marks|string-set|height|max-height|max-width|min-height|min-width|width|box-align|box-direction|box-flex|box-flex-group|box-lines|box-ordinal-group|box-orient|box-pack|font|font-family|font-size|font-style|font-variant|font-weight|@font-face|font-size-adjust|font-stretch|content|counter-increment|counter-reset|quotes|crop|move-to|page-policy|grid-columns|grid-rows|target|target-name|target-new|target-position|alignment-adjust|alignment-baseline|baseline-shift|dominant-baseline|drop-initial-after-adjust|drop-initial-after-align|drop-initial-before-adjust|drop-initial-before-align|drop-initial-size|drop-initial-value|inline-box-align|line-stacking|line-stacking-ruby|line-stacking-shift|line-stacking-strategy|text-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|marquee-direction|marquee-play-count|marquee-speed|marquee-style|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|padding|padding-bottom|padding-left|padding-right|padding-top|fit|fit-position|image-orientation|page|size|bottom|clear|clip|cursor|display|float|left|overflow|position|right|top|visibility|z-index|orphans|page-break-after|page-break-before|page-break-inside|widows|ruby-align|ruby-overhang|ruby-position|ruby-span|mark|mark-after|mark-before|phonemes|rest|rest-after|rest-before|voice-balance|voice-duration|voice-pitch|voice-pitch-range|voice-rate|voice-stress|voice-volume|border-collapse|border-spacing|caption-side|empty-cells|table-layout|color|direction|letter-spacing|line-height|text-align|text-decoration|text-indent|text-transform|unicode-bidi|vertical-align|white-space|word-spacing|hanging-punctuation|punctuation-trim|text-align-last|text-justify|text-outline|text-overflow|text-shadow|text-wrap|word-break|word-wrap|2transform|transform-origin|transform-style|perspective|perspective-origin|backface-visibility|transition|transition-property|transition-duration|transition-timing-function|transition-delay|appearance|box-sizing|icon|nav-down|nav-index|nav-left|nav-right|nav-up|outline-offset|resize'
+  attributesTypes: 'filter|animation|animation-name|animation-duration|animation-timing-function|animation-delay|animation-iteration-count|animation-direction|animation-play-state|background|background-attachment|background-color|background-image|background-position|background-repeat|background-clip|background-origin|background-size|border|border-bottom|border-bottom-color|border-bottom-style|border-bottom-width|border-color|border-left|border-left-color|border-left-style|border-left-width|border-right|border-right-color|border-right-style|border-right-width|border-style|border-top|border-top-color|border-top-style|border-top-width|border-width|outline|outline-color|outline-style|outline-width|border-bottom-left-radius|border-bottom-right-radius|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-radius|border-top-left-radius|border-top-right-radius|box-decoration-break|box-shadow|overflow-x|overflow-y|overflow-style|rotation|rotation-point|color-profile|opacity|rendering-intent|bookmark-label|bookmark-level|bookmark-target|float-offset|hyphenate-after|hyphenate-before|hyphenate-character|hyphenate-lines|hyphenate-resource|hyphens|image-resolution|marks|string-set|height|max-height|max-width|min-height|min-width|width|box-align|box-direction|box-flex|box-flex-group|box-lines|box-ordinal-group|box-orient|box-pack|font|font-family|font-size|font-style|font-variant|font-weight|@font-face|font-size-adjust|font-stretch|content|counter-increment|counter-reset|quotes|crop|move-to|page-policy|grid-columns|grid-rows|target|target-name|target-new|target-position|alignment-adjust|alignment-baseline|baseline-shift|dominant-baseline|drop-initial-after-adjust|drop-initial-after-align|drop-initial-before-adjust|drop-initial-before-align|drop-initial-size|drop-initial-value|inline-box-align|line-stacking|line-stacking-ruby|line-stacking-shift|line-stacking-strategy|text-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|marquee-direction|marquee-play-count|marquee-speed|marquee-style|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|padding|padding-bottom|padding-left|padding-right|padding-top|fit|fit-position|image-orientation|page|size|bottom|clear|clip|cursor|display|float|left|overflow|position|right|top|visibility|z-index|orphans|page-break-after|page-break-before|page-break-inside|widows|ruby-align|ruby-overhang|ruby-position|ruby-span|mark|mark-after|mark-before|phonemes|rest|rest-after|rest-before|voice-balance|voice-duration|voice-pitch|voice-pitch-range|voice-rate|voice-stress|voice-volume|border-collapse|border-spacing|caption-side|empty-cells|table-layout|color|direction|letter-spacing|line-height|text-align|text-decoration|text-indent|text-transform|unicode-bidi|vertical-align|white-space|word-spacing|hanging-punctuation|punctuation-trim|text-align-last|text-justify|text-outline|text-overflow|text-shadow|text-wrap|word-break|word-wrap|2transform|transform-origin|transform-style|perspective|perspective-origin|backface-visibility|transition|transition-property|transition-duration|transition-timing-function|transition-delay|appearance|box-sizing|icon|nav-down|nav-index|nav-left|nav-right|nav-up|outline-offset|resize'

   pattern:
     isInlineOperation: /\s+([a-zA-Z0-9\(]+[\(\)\%\/\*\+\-\.\s]*)+\s*$/
@@ -135,9 +129,7 @@
     variableWithUnit: -> /^(\@[a-zA-Z\_]+)\[(in|cm|mm|em|ex|pt|pc|px|s|\%)\]/g

   constructor: (@original = null) ->
-
-  createContext: ->
-    @context = new DocumentStyle()
+    @context = new DocumentStyle

   transformCssObjectsToJSON: (s, options = {}) ->
     # TODO: refactor
@@ -204,18 +196,19 @@
         escape = enclose = false
         s = s.replace(@pattern.variableWithUnit(), "$1 + '$2'")

+      # TODO: with or with whitespaces wraped? ` ' + $1..$3 +' `
       # enclose?, only if no operator are found
       if not hasOperators and /(@[a-zA-Z\_]+(\[[a-zA-Z0-9\_]+\])*(\(.*?\)))*/.test(s)
         enclose ?= true
         if enclose
           s = @escapeCSSValue(s) if escape
           s = "'#{s}'"
-          return s.replace(/(@[a-zA-Z\_]+)(\[[a-zA-Z0-9\_]+\])*(\(.*?\))*/g, " ' + $1$2 + ' ")
+          return s.replace(/(@[a-zA-Z\_]+)(\[[a-zA-Z0-9\_]+\])*(\(.*?\))*/g, "' + $1$2$3 + '")

       if onlyIfOperatorsExists and not hasOperators
         s = @escapeCSSValue(s) if escape
         return if enclose
-          "'#{s.replace(/(\s@[a-zA-Z]+)(\[[a-zA-Z0-9\_]+\])*(\(.*?\))*\s/g, " ' + $1$2 + ' ")}'"
+          "'#{s.replace(/(\s@[a-zA-Z]+)(\[[a-zA-Z0-9\_]+\])*(\(.*?\))*\s/g, "' + $1$2$3 + '")}'"
         else
           "#{s}"

@@ -293,7 +286,7 @@
     else
       # generate pattern
       regexString = @attributesTypes.split('-').join('\\-')#.replace(/([a-z\-]+)/g, '($1)')
-      regexString = "^\\s+(\\-moz\\-|\\-ms\\-|mso\\-|\\-khtml\\-|\\-webkit\\-|\\-o\\-){0,1}(#{regexString}){1}"
+      regexString = "^\\s+(\\-moz\\-|\\-ms\\-|mso\\-|\\-khtml\\-|\\-webkit\\-|\\-o\\-){0,1}(#{regexString}){1}([\\s\\:]{1}.*)*$"
       regex = @pattern.doesLineBeginWithAttribute = new RegExp(regexString)
     regex.test(line)

@@ -308,14 +301,22 @@
         line = line.split(name).join(newName)
     line

+  _indentSpacesOfLine: (line) ->
+    line.match(/^\s+/)?[0].length || 0
+
   processStyleText: ->
     styletext = @styletext
-    lines = styletext.split('\n')
+    lines            = styletext.split('\n')
+    originalLines    = styletext.split('\n')
     lastSelectorLine = null
+    isInListedValues = null
     for line, i in lines
       lineBeginsWithAttribute = @doesLineBeginsWithAttribute(line)
       line = @renameHyphenFunctionName(line)
-      lineBefore = lines[lastSelectorLine]
+      indentSpacesCount = @_indentSpacesOfLine(line)
+      lineBefore = originalLines[i-1] || ''
+      nextLine   = originalLines[i+1] || null
+      lastLineWithSelector = lines[lastSelectorLine]
       # media queries have an exception rule
       if @pattern.isCSSQuery.test(line)
         # @media
@@ -328,20 +329,36 @@
         line = @parseAttributeLine(line, { indent: line.match(/^\s+/)[0], escape: true }) # we have an escape for e.g. font: 'Lucida', Arial
       else
         line = @parseInlineArguments(line)
-        # functions, like `@pad('5px')`
-        line = line.replace /^(\s+)(\@[a-zA-Z]+\(.*\))/g, '\n  $2'
-        # many selectors, like `.a, i[t="ok"], #sidebar p:first-line, ul li:nth-child(3)`
-        line = line.replace /^(\s*)([a-zA-Z\.\#\&\>\:\*]+((?!\:\s).)*)$/, "\n@add '$2', '$1', "
-        # one selector, like `body.imprint`
-        line = line.replace /\n(\s*)([a-zA-Z\.\#\&\>\:\*]+((?!\:\s).)*)(\s*)$/, "\n@add '$2', '$1', $4"
-        lastSelectorLine = null
+        # s.th. like
+        # div#Container
+        #   filter
+        #     blur(2)
+        #     grayscale(1)
+        if @doesLineBeginsWithAttribute(lineBefore) or isInListedValues
+          lines[i-1] += ' [' unless isInListedValues
+          isInListedValues = /^\s+[\#a-zA-Z\_\-0-9\(\)\.]+/.test(line) or /^\s+\@/.test(line)
+          whitespaces = Array(indentSpacesCount+1).join(' ')
+          line        = whitespaces + @operateInline(line, {escape: false, enclose: true})
+          lines[i] = line
+          if not nextLine or ( nextLine and @_indentSpacesOfLine(nextLine) isnt indentSpacesCount )
+            line += ' ]'
+            isInListedValues = false
+        else
+          # functions, like `@pad('5px')`
+          line = line.replace /^(\s+)(\@[a-zA-Z]+\(.*\))/g, '\n$1$2'
+          # many selectors, like `.a, i[t="ok"], #sidebar p:first-line, ul li:nth-child(3)`
+          line = line.replace /^(\s*)([a-zA-Z\.\#\&\>\:\*]+((?!\:\s).)*)$/, "\n@add '$2', '$1', "
+          # one selector, like `body.imprint`
+          line = line.replace /\n(\s*)([a-zA-Z\.\#\&\>\:\*]+((?!\:\s).)*)(\s*)$/, "\n@add '$2', '$1', $4"
+          lastSelectorLine = null
       # check line before called a function/css query like @page
-      if lineBefore and lineBeginsWithAttribute and /^\s*(@[a-z\_\-]+)([^\,]\s*)*\s*$/i.test(lineBefore)
-        matches = lineBefore.match /^(\s*)(@[a-z\_\-]+)([^a-z\_\,].*)*\s*$/i
+      if lastLineWithSelector and lineBeginsWithAttribute and /^\s*(@[a-z\_\-]+)([^\,]\s*)*\s*$/i.test(lastLineWithSelector)
+        matches = lastLineWithSelector.match /^(\s*)(@[a-z\_\-]+)([^a-z\_\,].*)*\s*$/i
         changedLine  = matches[1]+matches[2]
         # TODO: what if @rule :pseudo, :pseudo ?
         lines[lastSelectorLine] = changedLine += ( if matches[3] then matches[3]+',' else '' ) + ' __$cssAttributes__ ='
       lastSelectorLine = i if /^@[a-zA-Z\_\-]+/.test(line)
+
       lines[i] = line

     @styletext = lines.join('\n').replace(/\n+/g, "\n")
@@ -385,7 +402,7 @@
         value = @parseInlineArguments(value)
       "#{indent}'#{attr}': #{value}"
     else
-      line.replace /^(\s+)([a-zA-Z\-]+)/, "#{indent}'$2':"
+      line.replace /^(\s+)([a-zA-Z\-\_]+)\:*/, "#{indent}'$2':"

   parseInlineArguments: (line) ->
     methodsFound = line.match /@[a-zA-Z\_]+\(.+\)/g
@@ -422,9 +439,9 @@
         CoffeeScript.__csss_context = @context
       @coffeescript = """
       doc = CoffeeScript.__csss_context || this
-      rgba = -> 'rgba('+Array.prototype.slice.call(arguments).join(", ")+')'
+      rgba = (colors...) -> 'rgba('+colors.join(", ")+')'
       rgb  = -> 'rgb('+Array.prototype.slice.call(arguments).join(", ")+')'
-      doc.init()
+      doc.__init__()
       doc.__eval__ = ->
         #{@coffeescript}
       doc.__eval__()
@@ -443,6 +460,9 @@
     s = s.replace /\;[^\S\n]*/g, ''
       .replace /\t/g, '  '
     s.trim()
+
+  use: (MyEnvironmentClass) ->
+    @context.__add_environment__ MyEnvironmentClass

   error: (e) ->
     @_error = e if e?
@@ -477,7 +497,7 @@
           # we have mixins here
           # apply on the current selector
           for i in [3...section.length]
-            DocumentStyle::_extend(section[2], section[i])
+            DocumentStyle::__extend__(section[2], section[i])

         if level < levelBefore and isReference and levels?[level-1]
           selectorString = levels[level-1].trim() + selectorString.trim().substring(1)# || ''
@@ -506,7 +526,6 @@
     cssString += " } " if mediaQuery # close {}
     cssString

-
 if window
   window.CSSS ?= CSSS
 else


Added some comments to request interceptor

--- src/request-interceptor.coffee
+++ src/request-interceptor.coffee
@@ -2,18 +2,16 @@
 js = require('./helpers').js

 pageAction = (tabId, normalizedUrl) ->
-	if tabId
-		if tabId > 0
-			chrome.pageAction.show tabId
-
 		stats.addBoost tabId, normalizedUrl

-
+# redirect page resource to webboost resource
 redirect = (url, tabId, normalizedUrl) ->
 	pageAction(tabId, normalizedUrl)
 	console.log "boosted", normalizedUrl, js url
 	{redirectUrl: js url}

+# used for msvp blocking
+# no resources will be loaded
 block = (url, tabId, normalizedUrl) ->
 	pageAction(tabId, normalizedUrl)
 	console.log "blocked", normalizedUrl


Fix logger.

Fix logger.

--- src/logger.coffee
+++ src/logger.coffee
@@ -26,16 +26,16 @@

   error: ->
     growl [arguments...].join(' '), title: 'Brunch error'
-    @log 'error', arguments...
+    logger.log 'error', arguments...

   warn: ->
-    @log 'warn', arguments...
+    logger.log 'warn', arguments...

   info: ->
-    @log 'info', arguments...
+    logger.log 'info', arguments...

   debug: ->
-    if @isDebug
-      @log 'debug', arguments...
+    if logger.isDebug
+      logger.log 'debug', arguments...

 module.exports = Object.freeze(logger)


typo in setting name

typo in setting name

--- src/atom/editor.coffee
+++ src/atom/editor.coffee
@@ -34,7 +34,7 @@

   settings:
     theme: 'twilight'
-    useSoftTabs: true
+    softTabs: true
     tabSize: 2
     showInvisibles: false
     marginColumn: 80


Exact route url hotfix

Exact route url hotfix

Signed-off-by: Caddy Dz <cbc6199cd1cb93e9d3589fb9aa7ecaece7b37833@gmail.com>

--- src/atom/snippets.cson
+++ src/atom/snippets.cson
@@ -8,7 +8,7 @@
     'body': '
\n\t$2:\n\t<input type="text" class="form-control" name="$1" id="$1">\n
\n$3'
   'Vue JS Router':
     'prefix' : 'router'
-    'body': '<router-link to="${1:url}">${2:title}</router-link>\n\t$3'
+    'body': '<router-link to="${1:url}" exact>${2:title}</router-link>\n\t$3'
 '.source.js':
   'vue.component':
     'prefix' : 'vue.component'


shoot one word at a time, save @neizod for the last

--- src/kick.coffee
+++ src/kick.coffee
@@ -7,6 +7,7 @@

 pool_words = []
 shoot_word = null
+shoot_name = null

 lvl = 1
 point = 0
@@ -17,13 +18,20 @@
         @words = []

     add: (word) ->
-        if word in @words
+        if word == shoot_name?.full
+            @words = []
+        else if word in @words
             @words.remove(word)
         else
             @words.push(word)
+        if not @words.length
+            shoot_name = null
+        else
+            shoot_name = new ShootingWord('@neizod')

     show: ->
-        (word for word in @words).join(' ')
+        follow = if shoot_name? then ' ' + shoot_name.show.html() else ''
+        (word for word in @words).join(' ') + follow

 class ShootingWord
@@ -69,9 +77,12 @@
     $('#keep').html(inventory.show())
     $('#playground').empty()
     if shoot_word?
-        shoot_word.move()
-        player_die = true if shoot_word.update()
-        $('#playground').append(shoot_word.show)
+        if shoot_word.full == shoot_name?.full
+            shoot_word.update()
+        else
+            shoot_word.move()
+            player_die = true if shoot_word.update()
+            $('#playground').append(shoot_word.show)
     for word in pool_words
         word.move()
         player_die = true if word.update()
@@ -97,6 +108,9 @@
             if c == word.remain[0]
                 shoot_word = pool_words.pop(i)
                 break
+    if not shoot_word? and shoot_name?
+        if c == shoot_name.remain[0]
+            shoot_word = shoot_name
     if c == shoot_word?.remain[0]
         shoot_word.shot()
     if shoot_word?.remain == ''


remove "require sys" as it is deprecated and not used, also organized and commented requires

--- lib/http.coffee
+++ lib/http.coffee
@@ -1,14 +1,18 @@
+# Node Internal Libs
 nodeHttp = require 'http'
 url = require 'url'
-sys = require 'sys'
 fs = require 'fs'
 path = require 'path'
-util = require './util'
-config = require './config'
+
+# NPM Libs (Dependencies)
 log = require 'node-log'
 mime = require 'mime'
 digest = require 'digest'
 async = require 'async'
+
+# Custom Files
+util = require './util'
+config = require './config'
 blacklist = require './blacklist'

 http = {}


Upgrade config

Upgrade config

Fixes #7

--- lib/init.coffee
+++ lib/init.coffee
@@ -1,8 +1,17 @@
 module.exports =
   activate: ->
     console.log 'activate linter-less'
-
-  configDefaults:
-    ieCompatibilityChecks: true
-    strictUnits: false
-    strictMath: false
+
+  config:
+    ieCompatibilityChecks:
+      title: 'IE Compatibility Checks'
+      type: 'boolean'
+      default: true
+    strictUnits:
+      type: 'boolean'
+      default: false
+      description: 'Allow mixed units, e.g. 1px+1em or 1px*1px which have units that cannot be represented.'
+    strictMath:
+      type: 'boolean'
+      default: false
+      description: 'Turn on or off strict math, where in strict mode, math requires brackets.'


Working top row

Working top row

--- src/main.coffee
+++ src/main.coffee
@@ -17,28 +17,107 @@

 	# The rectangle used for the cutout of the pieces
 	rectangle = new P.Rectangle 0, 0, P.view.size.width / 4, .7 * P.view.size.height / 4
-
-	###
-	Two pieces
-	###
+
+	###
+	Top row
+	###
+
+	pieceEdges = [{
+		left: 0
+		right: 1
+	}, {
+		left: -1,
+		right: -1
+	}, {
+		left: 1,
+		right: -1
+	}, {
+		left: 1,
+		right: 0
+	}]

 	dropZoneLayer = new P.Layer()
 	piecesLayer = new P.Layer()
 	pieces = []

-	for i in [0..1]
+	for i in [0..3]
+
+		# An actual puzzle piece shape
+		W = rectangle.width
+		H = rectangle.height
+
+		puzzle = new P.Path()
+		puzzle.strokeWidth = 0
+
+		# Top left
+		puzzle.moveTo 0, 0
+
+		# Top edge
+		puzzle.lineTo new P.Point W, 0
+
+		# Right edge
+		if pieceEdges[i].right is 1
+
+			# Outwards
+			puzzle.lineTo new P.Point W, H / 4
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W + H / 4, H / 4)
+			puzzle.cubicCurveTo new P.Point(W + H / 2, 0), new P.Point(W + H / 2, H), new P.Point(W + H / 4, H * 3 / 4)
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W, H * 3 / 4)
+			puzzle.lineTo new P.Point W, H
+
+		else if pieceEdges[i].right is -1
+
+			# Inwards
+			puzzle.lineTo new P.Point W, H / 4
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W - H / 4, H / 4)
+			puzzle.cubicCurveTo new P.Point(W - H / 2, 0), new P.Point(W - H / 2, H), new P.Point(W - H / 4, H * 3 / 4)
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W, H * 3 / 4)
+			puzzle.lineTo new P.Point W, H
+
+		else
+
+			# Straight
+			puzzle.lineTo new P.Point W, H
+
+		# Bottom edge
+		puzzle.lineTo new P.Point 0, H
+
+		# Left edge
+		if pieceEdges[i].left is 1
+
+			# Outwards
+			puzzle.lineTo new P.Point 0, H * 3 / 4
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0 - H / 4, H * 3 / 4)
+			puzzle.cubicCurveTo new P.Point(0 - H / 2, H), new P.Point(0 - H / 2, 0), new P.Point(0 - H / 4, H / 4)
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0, H / 4)
+
+		else if pieceEdges[i].left is -1
+
+			# Inwards
+			puzzle.lineTo new P.Point 0, H * 3 / 4
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0 + H / 4, H * 3 / 4)
+			puzzle.cubicCurveTo new P.Point(0 + H / 2, H), new P.Point(0 + H / 2, 0), new P.Point(0 + H / 4, H / 4)
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0, H / 4)
+
+		# Close
+		puzzle.closePath()

 		# The dropzone for the piece
-		dropZone = new P.Shape.Rectangle rectangle
+		dropZone = puzzle.clone()
+		dropZone.point = new P.Point 0, 0
 		dropZone.fillColor = 'white'
 		dropZone.opacity = 0.5
-		dropZone.position.x = i * rectangle.width + rectangle.width / 2
 		dropZoneLayer.addChild dropZone

+		difference = puzzle.bounds.width - rectangle.width
+		difference /= 2 if pieceEdges[i].left is 1 and pieceEdges[i].right is 1
+
+		# Set correct top left
+		dropZone.bounds.x = i * rectangle.width
+		dropZone.bounds.x -= difference if pieceEdges[i].left > 0
+
 		# The edge will serve as the mask for the piece
-		pieceEdge = new P.Shape.Rectangle rectangle
-		pieceEdge.set
-			selected: true
+		pieceEdge = puzzle.clone()

 		pieceBackground = new P.Raster
 			source: '//hufkens.net/wp-content/uploads/2012/10/bumba-win8.png'
@@ -50,7 +129,8 @@
 		# Create a Group using the mask and the background
 		pieceGroup = new P.Group pieceEdge, pieceBackground, pieceEdge.clone()
 		pieceGroup.clipped = true
-		pieceGroup.position.x = i * rectangle.width / 2 + rectangle.width / 2
+		pieceGroup.bounds.x = i * rectangle.width
+		pieceGroup.bounds.x -= difference if pieceEdges[i].left > 0
 		pieceGroup.strokeColor = 'black'
 		piecesLayer.addChild pieceGroup


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -54,7 +54,7 @@

       camera.fadeOut
         color: blue
-
+        duration:
       # => This effect will start off transparent and change to blue over 50 frames.

   @name fadeOut


feature(core): Add ngAnimate detection and add to Mac automatically

--- src/main.coffee
+++ src/main.coffee
@@ -1,4 +1,8 @@
-angular.module "Mac", ["Mac.Util"]
+modules = ["Mac.Util"]
+try
+  angular.module("ngAnimate")
+  modules.push "ngAnimate"
+angular.module "Mac", modules

 ###
 @chalk overview


add defaults

add defaults

--- src/main.coffee
+++ src/main.coffee
@@ -273,6 +273,8 @@
       firstHour: 6
       firstDay: 1
       weekNumbers: true
+      contentHeight: 600
+      defaultView: 'agendaWeek'

       events: load_events
       eventDrop: drop_event


cleanup

cleanup

--- src/main.coffee
+++ src/main.coffee
@@ -13,8 +13,8 @@

 class Controller
   constructor: (@app, @model, @client) ->
-    @app.on 'ready', @appReady.bind(@)
-  appReady: ->
+    @app.on 'ready', @appReady
+  appReady: =>
     menus = []
     menus.push
       label: 'YakYak'
@@ -62,7 +62,7 @@
       auth: -> prom
     @mainWindow.webContents.on 'did-finish-load', => @refresh()
     @model.connection = 'connecting..'
-    @client.connect(creds).then @clientConnectionSuccess.bind(@), @clientConnectionError.bind(@)
+    @client.connect(creds).then @clientConnectionSuccess, @clientConnectionError
     @client.on 'chat_message', @clientonchatmessage
     ipc.on 'conversation:select', @conversationSelect
     ipc.on 'message:send', @messageSend
@@ -82,14 +82,14 @@
     if (atBottom) then @refresh()
   loadAppWindow: -> @mainWindow.loadUrl 'file://' + __dirname + '/ui/index.html'
   refresh: -> @mainWindow.webContents.send 'model:update', @model
-  clientConnectionSuccess: ->
+  clientConnectionSuccess: =>
     @model.connection = 'online'
     @refresh()
     promise = @getselfinfo()
     promise.then =>
       @syncrecentconversations().fail (err) -> console.log 'error', err, err.stack
     pormise.fail (err) -> console.log 'error', err, err.stack
-  clientConnectionError: ->
+  clientConnectionError: =>
     @model.connection = 'error'
     @refresh()
   getselfinfo: ->


Added syncing via chat

Added syncing via chat

--- src/scripts/diagnostics.coffee
+++ src/scripts/diagnostics.coffee
@@ -100,11 +100,11 @@
     if body.token is process.env.SLASH_SYNC_TOKEN
       response_url = body.response_url
       if response_url
+        Logger.log "POSTing to #{response_url}"
         Organization.sync()
         .catch(
           (err) ->
             Logger.errorToSlack "Failed to resync", err
-            Logger.log "POSTing to #{response_url}"
             payload =
               text: 'Failed to resync'
             robot.http(response_url)
@@ -113,10 +113,9 @@
         )
         .done(
           (status) ->
-            Logger.log "Options have been re-loaded"
-            Logger.log "POSTing to #{response_url}"
+            Logger.log "Options have been reloaded"
             payload =
-              text: 'Re-synced with spreadsheet'
+              text: 'Resynced with spreadsheet'
             robot.http(response_url)
             .header('Content-Type', 'application/json')
             .post(JSON.stringify(payload)) (err, response, body) ->
@@ -143,6 +142,22 @@
         "text": "Bad token in Ibizan configuration"
       }

+  robot.respond /sync/i, (res) ->
+    Organization.sync()
+    .catch(
+      (err) ->
+        Logger.errorToSlack "Failed to resync", err
+    )
+    .done(
+      (status) ->
+        Logger.logToChannel "Resynced with spreadsheet",
+                            res.message.user.name
+    )
+    Logger.reactToMessage 'dog2',
+                          res.message.user.name,
+                          res.message.rawMessage.channel,
+                          res.message.id
+
   robot.router.post '/ibizan/diagnostics/help', (req, res) ->
     body = req.body
     if body.token is process.env.SLASH_HELP_TOKEN


Replace guides processing and {x,y}grid with more robust code

--- bokehjs/src/coffee/api/plotting.coffee
+++ bokehjs/src/coffee/api/plotting.coffee
@@ -76,41 +76,18 @@

     super(attrs)

-    x_axiscls = @_get_axis_class(x_axis_type, this.x_range)
-    if x_axiscls?
-      if x_axiscls == models.LogAxis
-        this.x_mapper_type = 'log'
-      xaxis = new x_axiscls({plot: this})
-      if xaxis.ticker instanceof models.ContinuousTicker
-        xaxis.ticker.num_minor_ticks = @_get_num_minor_ticks(x_axiscls, x_minor_ticks)
-      axis_label = x_axis_label
-      if axis_label.length != 0
-        xaxis.axis_label = axis_label
-      @xgrid = new models.Grid({plot: this, dimension: 0, ticker: xaxis.ticker})
-      if x_axis_location == "above"
-        this.above = (this.above ? []).concat([xaxis])
-      else if x_axis_location == "below"
-        this.below = (this.below ? []).concat([xaxis])
-      @add_renderers(xaxis, @xgrid)
-
-    y_axiscls = @_get_axis_class(y_axis_type, this.y_range)
-    if y_axiscls?
-      if y_axiscls == models.LogAxis
-        this.y_mapper_type = 'log'
-      yaxis = new y_axiscls({plot: this})
-      if yaxis.ticker instanceof models.ContinuousTicker
-        yaxis.ticker.num_minor_ticks = @_get_num_minor_ticks(y_axiscls, y_minor_ticks)
-      axis_label = y_axis_label
-      if axis_label.length != 0
-        yaxis.axis_label = axis_label
-      @ygrid = new models.Grid({plot: this, dimension: 1, ticker: yaxis.ticker})
-      if y_axis_location == "left"
-        this.left = (this.left ? []).concat([yaxis])
-      else if y_axis_location == "right"
-        this.right = (this.right ? []).concat([yaxis])
-      @add_renderers(yaxis, @ygrid)
+    @_process_guides(0, x_axis_type, x_axis_location, x_minor_ticks, x_axis_label)
+    @_process_guides(1, y_axis_type, y_axis_location, y_minor_ticks, y_axis_label)

     @add_tools(@_process_tools(tools)...)
+
+  Object.defineProperty this.prototype, "xgrid", {
+    get: () -> @renderers.filter((r) -> r instanceof models.Grid and r.dimension == 0)[0] # TODO
+  }
+
+  Object.defineProperty this.prototype, "ygrid", {
+    get: () -> @renderers.filter((r) -> r instanceof models.Grid and r.dimension == 1)[0] # TODO
+  }

   annular_wedge:     (args...) -> @_glyph(models.AnnularWedge, "x,y,inner_radius,outer_radius,start_angle,end_angle", args)
   annulus:           (args...) -> @_glyph(models.Annulus,      "x,y,inner_radius,outer_radius",                       args)
@@ -258,6 +235,31 @@
       if range.length == 2
         return new models.Range1d({start: range[0], end: range[1]})

+  _process_guides: (dim, axis_type, axis_location, minor_ticks, axis_label) ->
+    range = if dim == 0 then @x_range else @y_range
+    axiscls = @_get_axis_class(axis_type, range)
+
+    if axiscls?
+      if axiscls == models.LogAxis
+        if dim == 0
+          @x_mapper_type = 'log'
+        else
+          @y_mapper_type = 'log'
+
+      axis = new axiscls({plot: if axis_location? then this else null})
+
+      if axis.ticker instanceof models.ContinuousTicker
+        axis.ticker.num_minor_ticks = @_get_num_minor_ticks(axiscls, minor_ticks)
+      if axis_label.length != 0
+        axis.axis_label = axis_label
+
+      grid = new models.Grid({plot: this, dimension: dim, ticker: axis.ticker})
+
+      if axis_location?
+        this[axis_location] = (this[axis_location] ? []).concat([axis])
+
+      @add_renderers(axis, grid)
+
   _get_axis_class: (axis_type, range) ->
     if not axis_type?
       return null


added Chorus.getClass to check builtin types

--- src/chorus.core.coffee
+++ src/chorus.core.coffee
@@ -6,10 +6,24 @@

     destination

+getClass = (obj) ->
+    # The internal property [[Class]] of a given javascript
+    # object is a reliable way to identify various builtins
+    #
+    # ECMA-262 8.6.2 discusses the internal properties
+    # common to all Ecmascript Objects including [[Class]]
+    #
+    # ECMA-262 15.2.4.2 discusses the use of
+    # Object.prototype.toString to observe [[Class]]
+    switch obj
+        when null      then "Null"
+        when undefined then "Undefined"
+        else Object::toString.call(obj).slice(8, -1)
+
 $append = (el, children...) ->
     for child in children
-        switch typeof child
-            when 'string' then el.appendChild $fromHTML child
+        switch getClass child
+            when 'String' then el.appendChild $fromHTML child
             else el.appendChild child

     el
@@ -49,7 +63,7 @@

     return -1

-extend Chorus, {extend, jsonp, $append, $fromHTML}
+extend Chorus, {extend, getClass, jsonp, $append, $fromHTML}

 # OrderedSet
 # ----------
@@ -337,7 +351,10 @@

         if @options.container
             container = @options.container
-            container = if typeof container is 'string' then document.getElementById container else container
+            container = switch getClass(container)
+                when 'String' then document.getElementById container
+                else container
+
             $append container, @toElement()

     options:


Team donate page uses team slug instead of team id

--- static/src/scripts/app/AppRouter.coffee
+++ static/src/scripts/app/AppRouter.coffee
@@ -14,7 +14,7 @@
       'teams(/)':           'teams'
       'teams/:slug':        'team'
       'donate(/)':          'donate'
-      'donate/:teamId':     'donateTeam'
+      'donate/:slug':     'donateTeam'

     initialize: ->
       ga('create', jailbreak.ga_id, 'auto')
@@ -42,15 +42,14 @@
       donateView = new DonationFormView
       $("#body-container").html donateView.render().$el

-    donateTeam: (teamId) ->
-      console.log 'donate team ' + teamId
+    donateTeam: (slug) ->
       team = new Team
-        id: teamId
+        slug: slug
       team.fetch
         success: =>
           donateView = new DonationFormView
-            teamId: teamId
-            name: team.get('names')
+            teamId: team.get 'id'
+            name: team.get 'names'
           $("#body-container").html donateView.render().$el

     _trackPageview: ->


Fix some miscellaneous typos in the test descriptions

--- test/slack.coffee
+++ test/slack.coffee
@@ -24,19 +24,19 @@
     foo = @slackbot.removeFormatting 'foo > & < >&<'
     foo.should.equal 'foo > & < >&<'

-  it 'Should change <@U1234> links to @name', ->
+  it 'Should change <@U123> links to @name', ->
     foo = @slackbot.removeFormatting 'foo <@U123> bar'
     foo.should.equal 'foo @name bar'

-  it 'Should change <@U1234|label> links to label', ->
+  it 'Should change <@U123|label> links to label', ->
     foo = @slackbot.removeFormatting 'foo <@U123|label> bar'
     foo.should.equal 'foo label bar'

-  it 'Should change <#C1234> links to #general', ->
+  it 'Should change <#C123> links to #general', ->
     foo = @slackbot.removeFormatting 'foo <#C123> bar'
     foo.should.equal 'foo #general bar'

-  it 'Should change <#C1234|label> links to label', ->
+  it 'Should change <#C123|label> links to label', ->
     foo = @slackbot.removeFormatting 'foo <#C123|label> bar'
     foo.should.equal 'foo label bar'

@@ -72,7 +72,7 @@
     foo = @slackbot.removeFormatting 'foo <https://www.example.com|example.com> bar'
     foo.should.equal 'foo https://www.example.com bar'

-  it 'Should remove formatting around <https> links with a label containing entitles', ->
+  it 'Should remove formatting around <https> links with a label containing entities', ->
     foo = @slackbot.removeFormatting 'foo <https://www.example.com|label > & <> bar'
     foo.should.equal 'foo label > & < (https://www.example.com) bar'


sip #4 delete key

sip #4 delete key

--- lib/main.coffee
+++ lib/main.coffee
@@ -49,7 +49,7 @@

       @editorSubscriptions[editor.id].add editor.onDidDestroy =>
         @editorSubscriptions[editor.id].dispose()
-      #   delete @editorSubscriptions[editor.id]
+        delete @editorSubscriptions[editor.id]

     @subscriptions.add atom.workspace.observeActivePaneItem (item) =>
       if item instanceof TextEditor and item.getURI()


fix: dev grammar hotreload. (#184)

fix: dev grammar hotreload. (#184)

--- lib/main.coffee
+++ lib/main.coffee
@@ -99,7 +99,17 @@
           # only for compatibilty with older version of Atom
           editor.reloadGrammar()
         else
-          atom.textEditors.maintainGrammar(editor)
+          # Workaround because:
+          # - `reloadGrammar` is buggy before 1.11 (https://github.com/atom/atom/issues/13022)
+          # - `maintainGrammar` change this behavior and don't reload existing grammar.
+          #   - https://github.com/atom/atom/pull/12125
+          #   - https://github.com/atom/atom/blob/c844d0f099e6ed95c52f0b94e1f141759926aeb8/src/text-editor-registry.js#L201
+          grammarOverride = atom.textEditors.editorGrammarOverrides[editor.id]
+          atom.textEditors.setGrammarOverride editor, 'text.plain'
+          if grammarOverride?
+            atom.textEditors.setGrammarOverride editor, grammarOverride
+          else
+            atom.textEditors.clearGrammarOverride editor

     console.log 'AsciiDoc grammars reloaded'


Some refactoring

Some refactoring

* Set default `jshintExecutablePath` and update the description
* Revert e915c50dad6d9378e4e53f5d545f56e3c4560329 ([Should be fixed soon](https://github.com/AtomLinter/Linter/issues/726#issuecomment-120908640))

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,15 +1,14 @@
+path = require('path')
+jsHintName = if process.platform is 'win32' then 'jshint.cmd' else 'jshint'

 module.exports =
   config:
     jshintExecutablePath:
-      default: ''
       type: 'string'
-      description: 'Leave empty to use bundled'
+      default: path.join(__dirname, '..', 'node_modules', '.bin', jsHintName)
+      description: 'Path of the `jshint` executable'
+
   provideLinter: ->
-    if process.platform is 'win32'
-      jshintPath = require('path').join(__dirname, '..', 'node_modules', '.bin', 'jshint.cmd')
-    else
-      jshintPath = require('path').join(__dirname, '..', 'node_modules', '.bin', 'jshint')
     helpers = require('atom-linter')
     reporter = require('jshint-json') # a string path
     provider =
@@ -17,17 +16,16 @@
       scope: 'file'
       lintOnFly: true
       lint: (textEditor) ->
-        exePath = atom.config.get('linter-jshint.jshintExecutablePath') || jshintPath
+        executablePath = atom.config.get('linter-jshint.jshintExecutablePath')
         filePath = textEditor.getPath()
         text = textEditor.getText()
         parameters = ['--reporter', reporter, '--extract', 'auto', '--filename', filePath, '-']
-        return helpers.exec(exePath, parameters, {stdin: text}).then (output) ->
+        return helpers.exec(executablePath, parameters, {stdin: text}).then (output) ->
           try
             output = JSON.parse(output).result
           catch error
-            atom.notifications.addError("Invalid Result recieved from JSHint",
-              {detail: "Check your console for more info. It's a known bug on OSX. See https://github.com/AtomLinter/Linter/issues/726", dismissable: true})
-            console.log('JSHint Result:', output)
+            atom.notifications.addError('Invalid result recieved from JSHint', {detail: 'Check your console for more informations', dismissible: true})
+            console.log('JSHint result:', output)
             return []
           output = output.filter((entry) -> entry.error.id)
           return output.map (entry) ->


:new: Add provideIndie method

:new: Add provideIndie method

--- lib/main.coffee
+++ lib/main.coffee
@@ -150,5 +150,8 @@
   provideLinter: ->
     @instance

+  provideIndie: ->
+    @instance?.indieLinters
+
   deactivate: ->
     @instance?.deactivate()


Export mongodb instance to all unit tests

--- test/suite.coffee
+++ test/suite.coffee
@@ -2,8 +2,10 @@

 request = require 'supertest'
 assert = require 'assert'
+
 exports.app = app = require './../coffee/server.coffee'
 exports.data = data = require('./util/data-gen.coffee')(100)
+exports.mongo = mongo = null

 # @TODO ok, so we had to hack this
 data[50].status = "Offentlig"
@@ -16,13 +18,13 @@

 before (done) -> app.once 'ready', done
 beforeEach (done) ->
-  db = app.get 'db'
+  mongo = app.get 'db'
   cache = app.get 'cache'

   cache.flushall()
-  db.collection('turer').drop (err) ->
+  mongo.collection('turer').drop (err) ->
     #throw err if err
-    db.collection('turer').insert data, {safe: true}, (err) ->
+    mongo.collection('turer').insert data, {safe: true}, (err) ->
       throw err if err
       done()


Load available timestamp for parameters that don't exist

--- lib/db.coffee
+++ lib/db.coffee
@@ -127,176 +127,170 @@

 fetchDevice = (id, timestamp, patterns, callback) ->
   MAX_DEPTH = config.get('MAX_DEPTH', id)
+
+  projection = {_id: 1}
+  projectionTree = {}
+  func = (path, pats, projTree) ->
+    children = {}
+    for pat in pats
+      fragment = pat[0][path.length] or '*'
+      load = 0
+      if fragment == '*'
+        projection[path.join('.')] = 1
+        return
+
+      projTree[fragment] ?= {}
+
+      if pat[1] & (1 << path.length)
+        projection[path.concat('_timestamp').join('.')] = 1
+        projection[path.concat([fragment, '_timestamp']).join('.')] = 1
+        projection[path.concat([fragment, '_value']).join('.')] = 1
+        projection[path.concat([fragment, '_type']).join('.')] = 1
+        projection[path.concat([fragment, '_object']).join('.')] = 1
+        projection[path.concat([fragment, '_writable']).join('.')] = 1
+        projection[path.concat([fragment, '_orig']).join('.')] = 1
+
+      if pat[1] >> (path.length + 1)
+        children[fragment] ?= []
+        children[fragment].push(pat)
+
+    for k, v of children
+      func(path.concat(k), v, projTree[k])
+
+  func([], patterns, projectionTree)
+
+  if projection['']?
+    proj = {}
+  else
+    proj = JSON.parse(JSON.stringify(projection))
+
+  for k, v of proj
+    if k == '' or k == 'Events' or k.startsWith('Events.')
+      if k.length < 7 or k.startsWith('Events.Inform.') or k == 'Events.Inform'
+        proj['_lastInform'] = 1
+      if k.length < 7 or k.startsWith('Events.1_BOOT.') or k == 'Events.1_BOOT'
+        proj['_lastBoot'] = 1
+      if k.length < 7 or k.startsWith('Events.0_BOOTSTRAP.') or k == 'Events.0_BOOTSTRAP'
+        proj['_lastBootstrap'] = 1
+      if k.length < 7 or k.startsWith('Events.Registered.') or k == 'Events.Registered'
+        proj['_registered'] = 1
+      delete proj[k] if k != ''
+
+    if k == '' or k == 'DeviceID' or k.startsWith('DeviceID.')
+      if k.length < 9 or k.startsWith('DeviceID.ID.') or k == 'DeviceID.ID'
+        proj['_id'] = 1
+      if k.length < 9 or k.startsWith('DeviceID.Manufacturer.') or k == 'DeviceID.DeManufacturer'
+        proj['_deviceId._Manufacturer'] = 1
+      if k.length < 9 or k.startsWith('DeviceID.ProductClass.') or k == 'DeviceID.ProductClass'
+        proj['_deviceId._ProductClass'] = 1
+      if k.length < 9 or k.startsWith('DeviceID.OUI.') or k == 'DeviceID.OUI'
+        proj['_deviceId._OUI'] = 1
+      if k.length < 9 or k.startsWith('DeviceID.SerialNumber.') or k == 'DeviceID.SerialNumber'
+        proj['_deviceId._SerialNumber'] = 1
+      if k.length < 9
+        proj['_deviceId'] = 1
+      delete proj[k] if k != ''
+
+    if k == '' or k == 'Tags' or k.startsWith('Tags.')
+      proj['_tags'] = 1
+      delete proj[k] if k != ''
+
+  optimizeProjection(proj)
+
   res = []
   loaded = []

-  # Build projection
-  projection = {_id: 1}
-  for [pattern, depth] in patterns
-    loaded.push([pattern, depth])
-    if depth & 1 and (pattern.length < 1 or pattern[0] == '*')
-      projection[''] = 1
-
-    if depth & 1 and (pattern.length < 1 or pattern[0] == '*' or pattern[0] == 'Events')
-      res.push([['Events'], timestamp,
-        {object: [timestamp, 1], writable: [timestamp, 0]}])
-
-    if depth & 2 and (pattern.length < 2 or pattern[0] == '*' or pattern[0] == 'Events')
-      if not pattern[1]? or pattern[1] == '*'
-        projection['_registered'] = 1
-        projection['_lastInform'] = 1
-        projection['_lastBootstrap'] = 1
-        projection['_lastBoot'] = 1
-        res.push([['Events', '*'], timestamp])
-      else if pattern[1] == 'Registered'
-        projection['_registered'] = 1
-      else if pattern[1] == 'Inform'
-        projection['_lastInform'] = 1
-      else if pattern[1] == '0_BOOTSTRAP'
-        projection['_lastBootstrap'] = 1
-      else if pattern[1] == '1_BOOT'
-        projection['_lastBoot'] = 1
-
-    if depth & 1 and (pattern.length < 1 or pattern[0] == '*' or pattern[0] == 'DeviceID')
-      res.push([['DeviceID'], timestamp,
-        {object: [timestamp, 1], writable: [timestamp, 0]}])
-
-    if depth & 2 and (pattern.length < 2 or pattern[0] == '*' or pattern[0] == 'DeviceID')
-      if not pattern[1]? or pattern[1] == '*'
-        projection['_id'] = 1
-        projection['_deviceId._Manufacturer'] = 1
-        projection['_deviceId._ProductClass'] = 1
-        projection['_deviceId._SerialNumber'] = 1
-        projection['_deviceId._OUI'] = 1
-        res.push([['DeviceID', '*'], timestamp])
-      else if pattern[1] == 'ID'
-        projection['_id'] = 1
-      else if pattern[1] == 'Manufacturer'
-        projection['_deviceId._Manufacturer'] = 1
-      else if pattern[1] == 'ProductClass'
-        projection['_deviceId._ProductClass'] = 1
-      else if pattern[1] == 'OUI'
-        projection['_deviceId._OUI'] = 1
-      else if pattern[1] == 'SerialNumber'
-        projection['_deviceId._SerialNumber'] = 1
-
-    if depth & 1 and (pattern.length < 1 or pattern[0] == '*' or pattern[0] == 'Tags')
-      res.push([['Tags'], timestamp,
-        {object: [timestamp, 1], writable: [timestamp, 0]}])
-
-    if depth & 2 and (pattern.length < 1 or pattern[0] == '*' or pattern[0] == 'Tags')
-      res.push([['Tags', '*'], timestamp])
-      projection['_tags'] = 1
-
-    if pattern[0] not in ['Tags', 'Events', 'DeviceID']
-      i = 0
-      while (1 << i) <= depth
-        if pattern.length <= i or pattern[i] == '*'
-          p = pattern.slice(0, i)
-          loaded.push([p, ((1 << p.length) - 1) ^ ((1 << MAX_DEPTH) - 1)])
-          s = p.join('.')
-          projection[s] = 1
-          projection[pattern.slice(0, i - 1).concat('_timestamp').join('.')] = 1
-          break
-
-        if depth & (1 << i)
-          s = pattern.slice(0, i + 1).join('.')
-          projection["#{s}._value"] = 1
-          projection["#{s}._timestamp"] = 1
-          projection["#{s}._orig"] = 1
-          projection["#{s}._type"] = 1
-          projection["#{s}._writable"] = 1
-          projection["#{s}._object"] = 1
-
-          # Timestamp from parent is needed for writable timestamp
-          if i <= 1
-            projection['_timestamp'] = 1
-          else
-            projection["#{pattern.slice(0, i).join('.')}._timestamp"] = 1
-
-        ++ i
-
-  if projection['']
-    projection = {}
-
-  if projection?
-    optimizeProjection(projection)
-
-  devicesCollection.findOne({'_id' : id}, projection, (err, device) ->
+  devicesCollection.findOne({'_id' : id}, proj, (err, device) ->
     return callback(err) if err or not device?

-    storeParams = (obj, path, timestamp, descendantsFetched) ->
-      if not descendantsFetched and not obj['_value']? and projection[path.join('.')]
-        descendantsFetched = true
+    storeParams = (obj, path, timestamp, descendantsFetched, projTree) ->
+      if descendantsFetched
+        thisFetched = true
+      else
+        if projection[path.join('.')]
+          descendantsFetched = true
+          loaded.push([path, ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)])
+        if projection[path.concat('_writable').join('.')]
+          loaded.push([path, 1 << (path.length - 1)])
+          thisFetched = true

       if obj['_timestamp']?
         obj['_timestamp'] = +obj['_timestamp']

-      t = if obj['_timestamp'] > timestamp then obj['_timestamp'] else timestamp
-
-      attrs = {}
-
-      if obj['_value']?
-        attrs.value = [t, [obj['_value'], obj['_type']]]
-        obj['_object'] = false
-
-      if obj['_writable']?
-        attrs.writable = [timestamp, if obj['_writable'] then 1 else 0]
-
-      if obj['_object']?
-        attrs.object = [t, if obj['_object'] then 1 else 0]
-
-      if not descendantsFetched and attrs.object?[1] == 0
-        loaded.push([path, ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)])
-
-      res.push([path, t, attrs])
+      if thisFetched
+        attrs = {}
+
+        t = if obj['_timestamp'] > timestamp then obj['_timestamp'] else timestamp
+
+        if obj['_value']?
+          attrs.value = [obj['_timestamp'] or 1, [obj['_value'], obj['_type']]]
+          attrs.value[1][0] = +attrs.value[1][0] if obj['_type'] == 'xsd:dateTime'
+          obj['_object'] = false
+
+        if obj['_writable']?
+          attrs.writable = [timestamp, if obj['_writable'] then 1 else 0]
+
+        if obj['_object']?
+          attrs.object = [t, if obj['_object'] then 1 else 0]
+
+        res.push([path, t, attrs])

       for k, v of obj
         if k[0] != '_'
+          kk = k
           obj['_object'] = true
-          storeParams(v, path.concat(k), obj['_timestamp'] or 1, descendantsFetched)
-
-      if obj['_object'] and descendantsFetched
-        res.push([path.concat('*'), obj['_timestamp'] or 1])
+          storeParams(v, path.concat(k), obj['_timestamp'] or 1, descendantsFetched, projTree?[k])
+          delete projTree[kk] if projTree
+
+      if obj['_object']
+        if descendantsFetched
+          res.push([path.concat('*'), obj['_timestamp']]) if obj['_timestamp']
+        else
+          for k, v of projTree
+            p = path.concat(k)
+            res.push([p, +(obj['_timestamp'] ? 1)])
+            loaded.push([p, ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)])

     for k, v of device
-      if k == '_timestamp'
-        res.push([['*'], +v]) if not Object.keys(projection).length
-      else if k == '_lastInform'
-        res.push([['Events', 'Inform'], timestamp,
-          {object: [timestamp, 0], writable: [timestamp, 0], value: [+v, [+v, 'xsd:dateTime']]}])
+      if k == '_lastInform'
+        device['Events'] ?= {'_timestamp': timestamp, '_writable': false, '_object': true}
+        device['Events']['Inform'] = {'_writable': false, '_value': v, '_type' : 'xsd:dateTime', '_timestamp': v}
+        delete device[k]
       else if k == '_lastBoot'
-        res.push([['Events', '1_BOOT'], timestamp,
-          {object: [timestamp, 0], writable: [timestamp, 0], value: [+v, [+v, 'xsd:dateTime']]}])
+        device['Events'] ?= {'_timestamp': timestamp, '_writable': false, '_object': true}
+        device['Events']['1_BOOT'] = {'_writable': false, '_value': v, '_type' : 'xsd:dateTime', '_timestamp': v}
+        delete device[k]
       else if k == '_lastBootstrap'
-        res.push([['Events', '0_BOOTSTRAP'], timestamp,
-          {object: [timestamp, 0], writable: [timestamp, 0], value: [+v, [+v, 'xsd:dateTime']]}])
+        device['Events'] ?= {'_timestamp': timestamp, '_writable': false, '_object': true}
+        device['Events']['0_BOOTSTRAP'] = {'_writable': false, '_value': v, '_type' : 'xsd:dateTime', '_timestamp': v}
+        delete device[k]
       else if k == '_registered'
-        res.push([['Events', 'Registered'], timestamp,
-          {object: [timestamp, 0], writable: [timestamp, 0], value: [+v, [+v, 'xsd:dateTime']]}])
+        device['Events'] ?= {'_timestamp': timestamp, '_writable': false, '_object': true}
+        device['Events']['Registered'] = {'_writable': false, '_value': v, '_type' : 'xsd:dateTime', '_timestamp': v}
+        delete device[k]
       else if k == '_id'
-        res.push([['DeviceID', 'ID'], timestamp,
-          {object: [timestamp, 0], writable: [timestamp, 0], value: [timestamp, [v, 'xsd:string']]}])
+        device['DeviceID'] ?= {'_timestamp': timestamp, '_writable': false, '_object': true}
+        device['DeviceID']['ID'] = {'_writable': false, '_value': v, '_type' : 'xsd:string', '_timestamp': timestamp}
+        delete device[k]
       else if k == '_tags'
+        device['Tags'] ?= {'_timestamp': timestamp, '_writable': false, '_object': true}
         for t in v
-          res.push([['Tags', t], timestamp,
-            {object: [timestamp, 0], writable: [timestamp, 1], value: [timestamp, [true, 'xsd:boolean']]}])
+          device['Tags'][t] = {'_writable': false, '_value': true, '_type' : 'xsd:boolean', '_timestamp': timestamp}
+        delete device[k]
       else if k == '_deviceId'
+        device['DeviceID'] ?= {'_timestamp': timestamp, '_writable': false, '_object': true}
         for kk, vv of v
           if kk == '_Manufacturer'
-            res.push([['DeviceID', 'Manufacturer'], timestamp,
-              {object: [timestamp, 0], writable: [timestamp, 0], value: [timestamp, [vv, 'xsd:string']]}])
+            device['DeviceID']['Manufacturer'] = {'_writable': false, '_value': vv, '_type' : 'xsd:string', '_timestamp': timestamp}
           else if kk == '_OUI'
-            res.push([['DeviceID', 'OUI'], timestamp,
-              {object: [timestamp, 0], writable: [timestamp, 0], value: [timestamp, [vv, 'xsd:string']]}])
+            device['DeviceID']['OUI'] = {'_writable': false, '_value': vv, '_type' : 'xsd:string', '_timestamp': timestamp}
           if kk == '_ProductClass'
-            res.push([['DeviceID', 'ProductClass'], timestamp,
-              {object: [timestamp, 0], writable: [timestamp, 0], value: [timestamp, [vv, 'xsd:string']]}])
+            device['DeviceID']['ProductClass'] = {'_writable': false, '_value': vv, '_type' : 'xsd:string', '_timestamp': timestamp}
           if kk == '_SerialNumber'
-            res.push([['DeviceID', 'SerialNumber'], timestamp,
-              {object: [timestamp, 0], writable: [timestamp, 0], value: [timestamp, [vv, 'xsd:string']]}])
-      else if common.typeOf(v) is common.OBJECT_TYPE
-        storeParams(v, [k], +(device['_timestamp'] ? 1), Object.keys(projection).length == 0)
+            device['DeviceID']['SerialNumber'] = {'_writable': false, '_value': vv, '_type' : 'xsd:string', '_timestamp': timestamp}
+        delete device[k]
+
+    storeParams(device, [], 0, false, projectionTree)

     return callback(null, res, loaded)
   )


change recover errors

change recover errors

--- lib/paas.coffee
+++ lib/paas.coffee
@@ -1,5 +1,5 @@
 haibu = require('haibu-api')
-HttpClient = require("request-json").JsonClient
+HttpClient = require("../../request-json/main").JsonClient
 MemoryManager = require("./memory").MemoryManager
 haibuUrl = "http://localhost:9002/"
 controllerClient = new HttpClient haibuUrl
@@ -66,28 +66,36 @@
             if err
                 callback err
             else
-                @client.startApp app.getHaibuDescriptor(), token, (err, result) =>
-                    if err
+                @client.startApp app.getHaibuDescriptor(), token, (err, res, body) =>
+                    if err or res.statusCode isnt 200
                         console.log "Error spawning app: #{app.name}"
-                        console.log err.message
-                        console.log err.stack
-                        callback(err)
+                        if err
+                            console.log err.message
+                            console.log err.stack
+                            callback(err)
+                        else
+                            console.log res.body
+                            callback(res.body)
                     else
                         console.info "Successfully spawned app: #{app.name}"
                         console.info "Update proxy..."
-                        callback null, result.body
+                        callback null, res.body

     # Remove and reinstall app inside Haibu.
     updateApp: (app, token, callback) ->
         console.info "Request haibu for updating #{app.name}..."

         console.info "Step 1: remove #{app.name}..."
-        @client.uninstallApp app.getHaibuDescriptor(), token, (err, result) =>
-            if err
+        @client.uninstallApp app.getHaibuDescriptor(), token, (err, res, body) =>
+            if err or res.statusCode isnt 200
                 console.log "Error cleaning app: #{app.name}"
-                console.log err.message
-                console.log err.stack
-                callback(err)
+                if err
+                    console.log err.message
+                    console.log err.stack
+                    callback(err)
+                else
+                    console.log res.body
+                    callback(res.body)
             else
                 console.info "Step 2: re install #{app.name}..."
                 @memoryManager.isEnoughMemory (err, enoughMemory) =>
@@ -97,27 +105,35 @@
                         console.log err.message
                         callback err
                     else
-                        @client.startApp app.getHaibuDescriptor(), token, (err, result) =>
-                            if err
+                        @client.startApp app.getHaibuDescriptor(), token, (err, res, body) =>
+                            if err or res.statusCode isnt 200
                                 console.log "Error spawning app: #{app.name}"
-                                console.log err.message
-                                console.log err.stack
-                                callback(err)
+                                if err
+                                    console.log err.message
+                                    console.log err.stack
+                                    callback(err)
+                                else
+                                    console.log res.body
+                                    callback(res.body)
                             else
                                 console.info "Successfully update app: #{app.name}"
-                                callback null, result.body
+                                callback null, res.body

     # Send a uninstall request to haibu server ("clean" request).
     uninstallApp: (app, token, callback) ->

         console.info "Request haibu for cleaning #{app.name}..."
-        @client.uninstallApp app.getHaibuDescriptor(), token, (err, result) =>
-            if err
+        @client.uninstallApp app.getHaibuDescriptor(), token, (err, res, body) =>
+            if err or res.statusCode isnt 200
                 console.log "Error cleaning app: #{app.name}"
-                console.log err.message
-                console.log err.stack
-                callback(err)
+                if err
+                    console.log err.message
+                    console.log err.stack
+                    callback(err)
+                else
+                    console.log res.body
+                    callback(res.body)
             else
                 console.info "Successfully cleaning app: #{app.name}"
                 callback null
@@ -132,25 +148,33 @@
                 console.log err.message
                 callback err
             else
-                @client.startApp app.getHaibuDescriptor(), token, (err, result) =>
-                    if err
+                @client.startApp app.getHaibuDescriptor(), token, (err, res, body) =>
+                    if err or res.statusCode isnt 200
                         console.log "Error starting app: #{app.name}"
-                        console.log err.message
-                        console.log err.stack
-                        callback(err)
+                        if err
+                            console.log err.message
+                            console.log err.stack
+                            callback(err)
+                        else
+                            console.log res.body
+                            callback(res.body)
                     else
                         console.info "Successfully starting app: #{app.name}"
-                        callback null, result.body
+                        callback null, res.body

     # Send a stop request to haibu server
     stop: (app, token, callback) ->

-        @client.stopApp app.slug, token, (err, result) =>
-            if err
+        @client.stopApp app.slug, token, (err,res, body) =>
+            if err or res.statusCode isnt 200
                 console.log "Error stopping app: #{app.name}"
-                console.log err.message
-                console.log err.stack
-                callback(err)
+                if err
+                    console.log err.message
+                    console.log err.stack
+                    callback(err)
+                else
+                    console.log res.body
+                    callback(res.body)
             else
                 console.info "Successfully stoppingg app: #{app.name}"
                 callback null


Fix filename id and normalize paths

Fix filename id and normalize paths

--- lib/pile.coffee
+++ lib/pile.coffee
@@ -1,6 +1,7 @@
 fs = require "fs"
 path = require "path"
 crypto = require 'crypto'
+path = require "path"

 _ = require "underscore"
 async = require "async"
@@ -43,7 +44,8 @@

     if @type is "file"
       filename = _.last @filePath.split("/")
-      filename = filename.replace ".", "_"
+      filename = filename.replace /\./g, "_"
+      filename = filename.replace /\-/g, "_"
       hash = filename + "_" + hash

     return hash
@@ -79,8 +81,11 @@
     @rawPile = null
     @urls = []
     @devMapping = {}
+    @piledUp = false

   addFile: (filePath) ->
+    filePath = path.normalize filePath
+    @warnPiledUp "addFile"
     if filePath not in @getFilePaths()
       @code.push asCodeOb.call
         type: "file"
@@ -88,6 +93,7 @@

   addRaw: (raw) ->
+    @warnPiledUp "addRaw"
     @code.push asCodeOb.call
       type: "raw"
       raw: raw
@@ -140,8 +146,12 @@
     sum.update @rawPile
     @pileHash = sum.digest('hex')

+  warnPiledUp: (fnname) ->
+    if @piledUp
+      console.log "Warning pile #{ @name } has been already piled up. Calling #{ fnname } does not do anything."

   pileUp: (cb) ->
+    @piledUp = true

     async.map @code, (codeOb, cb) =>
       codeOb.getCode (err, code) =>
@@ -177,12 +187,14 @@

   addOb: (ob) ->
+    @warnPiledUp "addOb"
     @code.push asCodeOb.call
       type: "object"
       object: ob

   addExec: (fn) ->
+    @warnPiledUp "addExec"
     @code.push asCodeOb.call
       type: "exec"
       object: fn
@@ -286,13 +298,14 @@

       pile = @piles[asset.name]

+      if not pile
+        res.send "Cannot find pile #{ asset.name }"
+        return
+
       # Wrong asset type. Lets skip to next middleware.
       if asset.ext isnt pile.ext
         return next()

-      if not pile
-        res.send "Cannot find pile #{ pileName }"
-        return

       if asset.min
         res.end pile.rawPile
@@ -344,8 +357,8 @@
   contentType: "text/css"

   setDynamicHelper: (app) ->
-    app.dynamicHelpers renderStyleTags: (req, res) =>
-      return => @renderTags.apply this, arguments
+    app.dynamicHelpers renderStyleTags: (req, res) => =>
+      @renderTags.apply this, arguments

   setMiddleware: (app) ->


Fix bug.

Fix bug.

--- src/module.coffee
+++ src/module.coffee
@@ -24,7 +24,6 @@
     # absolute path to module requiring us
     @requiredBy = opts.requiredBy

-    # resolver
     @resolver = opts.resolver ? resolver()

     # compiler/extension opts
@@ -136,7 +135,7 @@
         extension = (path.extname @absolutePath).substr 1

         fs.readFile @absolutePath, 'utf8', (err, source) =>
-          unless (compiler = @compilers[extension] ? compilers[extension])?
+          unless (compiler = @compilers[extension])?
             throw new Error "No suitable compiler found for #{@absolutePath}"

           opts =


Ignore lower/uppercase on device type

Ignore lower/uppercase on device type

Also makes device objects conform better to JavaScript conventions, as
they are not constructors

--- lib/upnp.coffee
+++ lib/upnp.coffee
@@ -9,13 +9,13 @@
 xml        = require './xml'

 exports.createDevice = (type, name, address) ->
+    type = type.toLowerCase()
     unless type of devices
         device = new EventEmitter()
         device.emit 'error', new Error "UPnP device of type #{type} is not yet implemented."
         return device

-    device = Object.create devices[type],
-        type: { value: type, enumerable: true }
+    device = Object.create devices[type]

     device.name = name or type
     device.address = address if address?
@@ -30,14 +30,15 @@
     device

-Device = new EventEmitter()
+device = new EventEmitter()

-Device.announce = (callback = ->) ->
+device.announce = (callback = ->) ->
     ssdp.start.call @
     do callback
     @

-MediaServer = Object.create Device,
+mediaserver = Object.create device,
+    type: { value: 'MediaServer' }
     version: { value: 1 }
     schemaPrefix: { value: 'urn:schemas-upnp-org' }
     schemaVersion: { value: '1.0' }
@@ -45,7 +46,7 @@

 # Need an object to reference a device by its type.
-devices = MediaServer: MediaServer
+devices = mediaserver: mediaserver

 # Asynchronous operations to get some device properties.
 init = (device, callback) ->


Addition of modifier creation in node-sdk.

User reporting bug missing modifier creation and reported fix.

--- src/moltin.coffee
+++ src/moltin.coffee
@@ -48,6 +48,7 @@
     @Stats         = new Stats @
     @Transaction   = new Transaction @
     @Variation     = new Variation @
+    @Modifier      = new Modifier @
     @Webhook       = new Webhook @
     `// @endif
     `


if node constructor gets a string, consider it a name, not id

--- src/core/sphere.coffee
+++ src/core/sphere.coffee
@@ -51,7 +51,7 @@
       if @nodes[nodeId]
         @nodes[nodeId]
       else
-        new Node({id: nodeId}, {@keyLength})
+        new Node({name: nodeId}, {@keyLength})
     else
       new Node(data, {@keyLength})
     @nodes[node.id()] = node


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -147,7 +147,7 @@
           moduleName = Module
           Module = Module.constantize()
         else
-          # Attempt to look up module
+          # Attempt to look up module in global
           for key, value of root
             if value is Module
               moduleName = key


Don't count incomplete days.

Don't count incomplete days.

--- src/oncall.coffee
+++ src/oncall.coffee
@@ -27,7 +27,7 @@
     today = new Date
     date = new Date(today.getFullYear(), today.getMonth(), today.getDate() + offset)
     onejan = new Date(date.getFullYear(), 0, 1)
-    Math.ceil((((date - onejan) / 86400000) + onejan.getDay() + 1) / 7)
+    Math.floor((((date - onejan) / 86400000) + onejan.getDay() + 1) / 7)

   getDayOfWeek: (weekNumber, dayNumber) ->
     year = (new Date).getFullYear()


Fixed message reply on "silly rabbot"

Fixed message reply on "silly rabbot"

--- src/rabbot.coffee
+++ src/rabbot.coffee
@@ -54,4 +54,4 @@
     msg.reply msg.random maxims

   robot.hear /silly rabbot/, ->
-    msg.send "Trix are for kids."
+    msg.reply "Trix are for kids."


extract get

extract get

--- src/render.coffee
+++ src/render.coffee
@@ -6,6 +6,9 @@
   .replace /</g, '<'
   .replace />/g, '>'
   .replace /"/g, '"'
+
+get = (key, context) ->
+  key.split(/\./).reduce(((c, key) -> c[key]), context)

 renderElement = (element, context) ->
   return [element] if typeof element is 'string'
@@ -21,12 +24,12 @@
           [n, v] = a.split /\s*:\s*/
           attributes.push
             name: n
-            value: v.split(/\./).reduce(((c, key) -> c[key]), context)
+            value: get v, context
       when 'b-html'
-        html = attr.value.split(/\./).reduce(((c, key) -> c[key]), context)
+        html = get attr.value, context
         children = [html]
       when 'b-text'
-        html = attr.value.split(/\./).reduce(((c, key) -> c[key]), context)
+        html = get attr.value, context
         text = escapeHtml html
         children = [text]
       else


fulfill parameters for removeListener

fulfill parameters for removeListener

--- index.cjsx
+++ index.cjsx
@@ -133,11 +133,13 @@
         @tutuInitial window._teitokuId, window._nickName, window._teitokuExp
       else
         window.addEventListener 'game.response', @handleResponse
+
     componentWillUnmount: ->
       if !@state.tutuInitialed
-        window.removeEventListener @handleResponse
+        window.removeEventListener'game.response', @handleResponse
       else if !@state.updatedFlag
-        window.removeEventListener @handleRefreshList
+        window.removeEventListener 'game.response', @handleRefreshList
+
     handleResponse: (e) ->
       {path, body} = e.detail
       {isUpdated} = @state


revert changeset e1d5a95

revert changeset e1d5a95

--- index.cjsx
+++ index.cjsx
@@ -181,6 +181,8 @@
       @handleExpChange _currentLevel, _nextExp, _goalLevel, @state.mapValue, @state.mapPercent
     handleCurrentLevelChange: (e) ->
       @handleExpChange e.target.value, @state.nextExp, @state.goalLevel, @state.mapValue, @state.mapPercent
+    handleNextExpChange: (e) ->
+      @handleExpChange @state.currentLevel, e.target.value, @state.goalLevel, @state.mapValue, @state.mapPercent
     handleGoalLevelChange: (e) ->
       @handleExpChange @state.currentLevel, @state.nextExp, e.target.value, @state.mapValue, @state.mapPercent
     handleExpMapChange: (e) ->
@@ -275,7 +277,7 @@
             <Input type="number" label={__("Actual level")} value={@state.currentLevel} onChange={@handleCurrentLevelChange} />
           </Col>
           <Col xs={row}>
-            <Input type="number" label={__("To next")} value={@state.nextExp} readOnly />
+            <Input type="number" label={__("To next")} value={@state.nextExp} onChange={@handleNextExpChange} />
           </Col>
           <Col xs={row}>
             <Input type="number" label={__("Goal")} value={@state.goalLevel} onChange={@handleGoalLevelChange} />


Update author

Update author

--- index.cjsx
+++ index.cjsx
@@ -21,7 +21,8 @@
   priority: 0.1
   displayName: [<FontAwesome key={0} name='compass' />, ' 未卜先知']
   description: '战况预知'
-  author: [Chiba]
+  author: 'Chiba'
+  link: 'https://github.com/Chibaheit'
   reactClass: React.createClass
     getInitialState: ->
       afterFriendHp: [0, 0, 0, 0, 0, 0]


for new API

for new API

--- index.cjsx
+++ index.cjsx
@@ -144,10 +144,11 @@
       {path, body} = e.detail
       {isUpdated} = @state
       switch path
-        when '/kcsapi/api_get_member/basic'
+        when '/kcsapi/api_port/port'
           #remove useless judgement
           #if @state.memberId isnt body.api_member_id
-          @tutuInitial body.api_member_id, body.api_nickname, body.api_experience
+          basic = body.api_basic
+          @tutuInitial basic.api_member_id, basic.api_nickname, basic.api_experience

           window.removeEventListener 'game.response', @handleResponse


Partials added to routes logic

Partials added to routes logic

--- src/routes.coffee
+++ src/routes.coffee
@@ -12,20 +12,27 @@
   app.get '/', (req, res, next) ->
     routeMVC('home', null, req, res, next)

+  # Site partials
+  app.get '/partials/:filename', (req, res, next) ->
+    fileName = req.params.filename
+    viewName = if fileName.match /\.html$/ then fileName.replace /\.html$/, ''
+    try
+      res.render "partials/#{viewName}"
+      return
+    catch e
+      console.warn "partials not found: " + viewName, e
+      next()
+
   # Authentication
   app.post '/user/login', isValidUser

   app.get '/private', isLoggedIn, (req, res, next) ->
     routeMVC('private', null, req, res, next)

-  # Search controller and method with id if present
-  app.all '/:controller/:method?/:id?', (req, res, next) ->
-    routeMVC(req.params.controller, req.params.method, req, res, next)
-
   ## REST API interface
   # Get resource items list
   app.get '/a/:controller', (req, res, next) ->
-    routeMVC(req.params.controller, null, req, res, next)
+    routeMVC(req.params.controller, 'index', req, res, next)

   # Create new resource item
   app.post '/a/:controller', (req, res, next) ->
@@ -43,6 +50,11 @@
   app.delete '/a/:controller/:id', (req, res, next) ->
     routeMVC(req.params.controller, 'delete', req, res, next)

+
+  # Search controller and method with id if present
+  app.all '/:controller/:method?/:id?', (req, res, next) ->
+    routeMVC(req.params.controller, req.params.method, req, res, next)
+
   # If all else failed, show 404 page
   app.all '/*', (req, res) ->
     console.warn "error 404: ", req.url
@@ -57,20 +69,18 @@
   else res.redirect '/user/login'

 # Render the page based on controller name, method and id
-routeMVC = (controllerName, methodName = 'index', req, res, next) ->
-  controllerName = 'index' if not controllerName?
+routeMVC = (controllerName = 'index', methodName = 'index', req, res, next) ->
   controller = null
   try
     controller = require './controllers/' + controllerName
   catch e
-#    console.warn "controller not found: " + controllerName, e
+    console.warn "controller not found: " + controllerName, e
     next()
-    return

   if typeof controller[methodName] is 'function'
     actionMethod = controller[methodName].bind controller
     actionMethod req, res, next
   else
-#    console.warn 'method not found: ' + methodName
+    console.warn 'method not found: ' + methodName
     next()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -1,5 +1,5 @@
 ###*
-The `Flickerable</code> module provides a method to flicker a sprite between solid and 50% opacity.
+`Flickerable` module provides a method to flicker a sprite between solid and 50% opacity.

 @name Flickerable
 @module


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -7,6 +7,7 @@
     player.include 'Flickerable'

     # => called with no arguments, flicker will
+    # toggle
     player.flicker()

 @name Flickerable


Update: 実行ログのmodidをmod名に変更

Update: 実行ログのmodidをmod名に変更

--- src/gui/js/repo.coffee
+++ src/gui/js/repo.coffee
@@ -76,7 +76,7 @@
             <button type="button" class="list-group-item list-group-item-action flex-column align-items-start" :class="{applied: applied}" :data-path="val" @click="show">
               <div class="form-check mb-0">
                 <label class="form-check-label checkbox_text">
-                  <input type="checkbox" class="form-check-input checkbox" :data-path="val" v-model="checked">
+                  <input type="checkbox" class="form-check-input checkbox" :data-path="val" :data-name="name" v-model="checked">
                   {{name}}
                 </label>
               </div>
@@ -252,41 +252,42 @@
     addMods = []
     deleteMods = []
     for $mod in $("button:not(.applied) input:checked")
-      addMods.push({repo: repo, name: $mod.getAttribute("data-path")})
+      addMods.push({repo: repo, name: $mod.getAttribute("data-path"), showname: $mod.getAttribute("data-name")})
     for $mod in $("button.applied input:not(:checked)")
-      deleteMods.push({repo: repo, name: $mod.getAttribute("data-path")})
+      deleteMods.push({repo: repo, name: $mod.getAttribute("data-path"), showname: $mod.getAttribute("data-name")})

     $("#progress").modal({ keyboard: false, backdrop: "static" })
     errored = false
-    applyMod.applyMods(addMods, deleteMods, (done, type, mod, err) ->
-      $button = $("button[data-path=\"#{mod.name}\"]")
-      if done
+    applyMod.applyMods(addMods, deleteMods, (phase, type, mod, err) ->
+      if phase is "done"
+        $button = $("button[data-path=\"#{mod.name}\"]")
         switch type
           when "add"
             $button.addClass("applied")
             switch lang
-              when "ja" then p.addLog("#{mod.name} - 適用完了")
-              when "en" then p.addLog("#{mod.name} - Applied Successfully")
-              when "ru" then p.addLog("#{mod.name} - Применено успешно")
+              when "ja" then p.addLog("#{mod.showname} - 適用完了")
+              when "en" then p.addLog("#{mod.showname} - Applied Successfully")
+              when "ru" then p.addLog("#{mod.showname} - Применено успешно")
           when "delete"
             $button.removeClass("applied")
             switch lang
-              when "ja" then p.addLog("#{mod.name} - 解除完了")
-              when "en" then p.addLog("#{mod.name} - Removed Successfully")
-              when "ru" then p.addLog("#{mod.name} - Удалено успешно")
-      else
+              when "ja" then p.addLog("#{mod.showname} - 解除完了")
+              when "en" then p.addLog("#{mod.showname} - Removed Successfully")
+              when "ru" then p.addLog("#{mod.showname} - Удалено успешно")
+      else if phase is "fail"
+        $button = $("button[data-path=\"#{mod.name}\"]")
         errored = true
         switch type
           when "add"
             switch lang
-              when "ja" then p.addLog("#{mod.name} - 適用失敗(#{err})")
-              when "en" then p.addLog("#{mod.name} - Failed to Apply(#{err})")
-              when "ru" then p.addLog("#{mod.name} - Не удалось применить(#{err})")
+              when "ja" then p.addLog("#{mod.showname} - 適用失敗(#{err})")
+              when "en" then p.addLog("#{mod.showname} - Failed to Apply(#{err})")
+              when "ru" then p.addLog("#{mod.showname} - Не удалось применить(#{err})")
           when "delete"
             switch lang
-              when "ja" then p.addLog("#{mod.name} - 解除失敗(#{err})")
-              when "en" then p.addLog("#{mod.name} - Failed to Remove(#{err})")
-              when "ru" then p.addLog("#{mod.name} - Не удалось удалить(#{err})")
+              when "ja" then p.addLog("#{mod.showname} - 解除失敗(#{err})")
+              when "en" then p.addLog("#{mod.showname} - Failed to Remove(#{err})")
+              when "ru" then p.addLog("#{mod.showname} - Не удалось удалить(#{err})")
       return
     ).then( ->
       if !errored


Add deprecated KeyBinding::keystroke property

You should use ::keystrokes, since there are potentially multiple

--- src/key-binding.coffee
+++ src/key-binding.coffee
@@ -6,6 +6,7 @@

   constructor: (@source, @command, keystrokes, selector) ->
     @keystrokes = normalizeKeystrokes(keystrokes)
+    @keystroke = @keystrokes # deprecated property
     @selector = selector.replace(/!important/g, '')
     @specificity = calculateSpecificity(selector)
     @index = @constructor.currentIndex++


Make sure shift is not double added

Make sure shift is not double added

--- src/key-binding.coffee
+++ src/key-binding.coffee
@@ -23,10 +23,9 @@
       modifiers.sort()
       key = _.last(keys)

-      # Add the shift modifier if the key is an uppercased alpha char
-      if /^[A-Z]$/.test(key) or 'shift' in modifiers
-        modifiers.push 'shift' unless 'shift' in modifiers
-        key = key.toUpperCase()
+      modifiers.push 'shift' if /^[A-Z]$/.test(key) and 'shift' not in modifiers
+      key = key.toUpperCase() if /^[a-z]$/.test(key) and 'shift' in modifiers
+
       [modifiers..., key].join('-')

     normalizedKeystroke.join(' ')


Just go with iron-router built-in hooks

Just go with iron-router built-in hooks

- Can't remember why I did this, maybe iron-router fixed something

--- router.coffee
+++ router.coffee
@@ -1,7 +1,5 @@
-class @BlogController extends RouteController
-  onBeforeAction: ->
-    if not @ready() and Template['loading']
-      @render 'loading'
+Router.onBeforeAction 'loading'
+Router.onBeforeAction 'dataNotFound'

 Router.map ->

@@ -23,7 +21,6 @@

   @route 'blogIndex',
     path: '/blog'
-    controller: 'BlogController'

     onRun: ->
       if Blog.settings.blogIndexTemplate
@@ -52,7 +49,6 @@

   @route 'blogTagged',
     path: '/blog/tag/:tag'
-    controller: 'BlogController'

     onRun: ->
       if Blog.settings.blogIndexTemplate
@@ -79,7 +75,7 @@

   @route 'blogShow',
     path: '/blog/:slug'
-    controller: 'BlogController'
+
     notFoundTemplate: 'blogNotFound'

     onRun: ->
@@ -113,7 +109,6 @@

   @route 'blogAdmin',
     path: '/admin/blog'
-    controller: 'BlogController'

     onRun: ->
       if Blog.settings.blogAdminTemplate
@@ -137,7 +132,6 @@

   @route 'blogAdminEdit',
     path: '/admin/blog/edit/:id'
-    controller: 'BlogController'

     onRun: ->
       if Blog.settings.blogAdminEditTemplate


Removed detectr source

Removed detectr source

--- src/lib/detectr.coffee
+++ src/lib/detectr.coffee
@@ -1,295 +0,0 @@
-((window, document) ->
-  "use strict"
-
-  ###
-    String helper functions
-  ###
-
-  # Use ECMAScript 6 String.prototype.contains if available
-  contains = (bigString, smallString) ->
-    if String::contains then bigString.contains(smallString) else !!~bigString.indexOf(smallString)
-
-  ###
-    Default configuration for detectr
-  ###
-  defaultTests =
-    tests:
-      desktop:
-        run: -> not runTest('mobile')
-        result: 'desktop'
-      mobile:
-        run: ->
-          # Most mobile browsers have a mobile in their user agent
-          return true if contains detectr.Browser.get(), 'mobile'
-
-          runTest('android') or runTest('ios') or runTest('bada') or runTest('webos') or runTest('wp7') or runTest('blackberry')
-        result: 'mobile'
-      macosx:
-        run: -> (contains detectr.Browser.platform.name(), 'macosx') and not (contains detectr.Browser.platform.name(), 'likemacosx')
-        result: 'macosx'
-      linux:
-        run: -> contains detectr.Browser.platform.name(), 'linux'
-        result: 'linux'
-      windows:
-        run: -> contains detectr.Browser.platform.name(), 'windows'
-        result: 'windows'
-      android:
-        run: -> contains detectr.Browser.get(), 'android'
-        result: 'android'
-      ios:
-        run: -> contains detectr.Browser.platform.name(), 'likemacosx'
-        result: 'ios'
-      ipod:
-        run: -> contains detectr.Browser.get(), 'ipod'
-        result: 'ipod'
-      iphone:
-        run: -> (contains detectr.Browser.get(), 'iphone') and not runTest('ipod')
-        result: 'iphone'
-      ipad:
-        run: -> contains detectr.Browser.get(), 'ipad'
-        result: 'ipad'
-      bada:
-        run: -> contains detectr.Browser.get(), 'bada'
-        result: 'bada'
-      webos:
-        run: -> contains detectr.Browser.get(), 'webos'
-        result: 'webos'
-      wp7:
-        run: -> contains detectr.Browser.get(), 'windows phone os'
-        result: 'wp7'
-      blackberry:
-        run: -> (contains detectr.Browser.get(), 'rim') or (contains detectr.Browser.get(), 'blackberry')
-        result: 'blackberry'
-      landscape:
-        run: -> detectr.Display.pageWidth() >= detectr.Display.pageHeight()
-        result: 'landscape'
-      portrait:
-        run: -> !runTest('landscape')
-        result: 'portrait'
-      'browser-chrome':
-        run: -> contains detectr.Browser.get(), 'chrome'
-        result: 'browser-chrome'
-      'browser-firefox':
-        run: -> contains detectr.Browser.get(), 'firefox'
-        result: 'browser-firefox'
-      'browser-ie':
-        run: -> contains detectr.Browser.get(), 'msie'
-        result: 'browser-ie'
-      'browser-safari':
-        run: -> (contains detectr.Browser.get(), 'safari') and not (contains detectr.Browser.get(), 'chrome')
-        result: 'browser-safari'
-      'browser-opera':
-        run: -> contains detectr.Browser.get(), 'opera'
-        result: 'browser-opera'
-
-
-  detectCache = {}
-  detectResultCache = {}
-  globalOptions = {}
-  testQueue = {}
-
-  ###
-    Runs a defined test
-  ###
-  runTest = (testName, testObject) ->
-    return false unless testName
-
-    if testQueue[testName].status == 'tested'
-      return !!detectCache[testName]
-    else
-      if testObject
-        if testObject.run
-          testResultBool = !!testObject.run()
-          testResultString = testObject.result
-
-        console.log "Testing #{testName}: Result: #{testResultBool}" if globalOptions?.debug?
-
-        testQueue[testName].status = 'tested' if testQueue[testName]
-
-        detectCache[testName] = testResultBool
-
-        if testResultBool
-          htmlClassName = document.documentElement.className
-
-          htmlClassName += " " + testResultString
-          # Trim className just in case
-          htmlClassName = htmlClassName.trim()
-          document.documentElement.className = htmlClassName
-
-          detectResultCache[testName] = testResultString
-
-          !!detectCache[testName]
-      else
-        runTest testName, testQueue[testName]
-
-
-  ###
-    detectr constructor
-  ###
-  detectr = (config, options) ->
-    # Make sure the config object exists and it has some tests in it
-    return undefined unless (config and config.tests)
-
-    globalOptions = options
-
-    uaString = navigator.userAgent.toLowerCase()
-    uaAppName = navigator.appName
-    uaAppVersion = navigator.appVersion
-    uaPlatform = navigator.platform
-
-    parsedPlatform = uaString.match(/(.*?)\s(.*?)\((.*?);\s(.*?)\)/)
-
-    detectr.Browser or=
-      width: -> window.outerWidth
-      height: -> window.outerHeight
-      get: -> uaString
-      name: -> uaAppName
-      version: -> uaAppVersion
-      platform:
-        name: -> parsedPlatform[4].replace(/\s/gi, '').toLowerCase()
-        original: -> uaPlatform
-      language: ->
-        language = navigator.language or navigator.systemLanguage
-        language.split('-')[0] if language?
-
-    # Sets language attribute to browser language by default
-    document.documentElement.setAttribute('lang', detectr.Browser.language())
-
-    detectr.Display or=
-      width: -> window.screen.width
-      height: -> window.screen.height
-      pageWidth: -> window.innerWidth
-      pageHeight: -> window.innerHeight
-
-    detectr.clear()
-
-    detectr.add config
-
-    doOrientationChange = (condition) ->
-      if condition()
-        return false if detectr.Display.orientation == detectResultCache['landscape']
-
-        newResult = detectr.defaultTests.tests['landscape'].result
-
-        htmlClassName = document.documentElement.className
-
-        if contains htmlClassName, detectResultCache['portrait']
-          htmlClassName = htmlClassName.replace(detectResultCache['portrait'], newResult)
-          document.documentElement.className = htmlClassName
-
-        detectCache['landscape'] = true
-        detectCache['portrait'] = false
-
-        detectResultCache['landscape'] = newResult
-        detectResultCache['portrait'] = undefined
-
-      else
-        return false if detectr.Display.orientation == detectResultCache['portrait']
-
-        newResult = detectr.defaultTests.tests['portrait'].result
-
-        htmlClassName = document.documentElement.className
-
-        if contains htmlClassName, detectResultCache['landscape']
-          htmlClassName = htmlClassName.replace(detectResultCache['landscape'], newResult)
-          document.documentElement.className = htmlClassName
-
-        detectCache['landscape'] = true
-        detectCache['portrait'] = false
-
-        detectResultCache['landscape'] = undefined
-        detectResultCache['portrait'] = newResult
-
-
-      detectr.Display.orientation = detectResultCache['landscape'] or detectResultCache['portrait']
-
-    window.addEventListener 'resize', (-> doOrientationChange(-> detectr.Display.pageWidth() >= detectr.Display.pageHeight())), false
-
-    window.addEventListener 'orientationchange', (-> doOrientationChange(-> Math.abs(window.orientation) == 90)), false
-
-    detectr.Display.orientation = detectResultCache['landscape'] or detectResultCache['portrait']
-
-    detectr
-
-  ###
-    Checks for a test and returns a boolean value
-  ###
-  detectr.is = (value) ->
-    return undefined unless value
-
-    value = value.replace(/\s/gi, '').toLowerCase()
-
-    !!detectCache[value]
-
-  ###
-    Checks for a test and returns the result value of the test
-  ###
-  detectr.result = (value) ->
-    return undefined unless value
-
-    value = value.replace(/\s/gi, '').toLowerCase()
-
-    detectResultCache[value] if detectResultCache[value]
-
-  ###
-    Clear cache
-  ###
-  detectr.clear = ->
-    testQueue = {}
-    detectCache = {}
-    for key, value of detectResultCache
-      htmlClassName = document.documentElement.className
-
-      htmlClassName = htmlClassName.replace(value, '').trim()
-      document.documentElement.className = htmlClassName
-    detectResultCache = {}
-
-    detectr
-
-  ###
-    Remove a test
-  ###
-  detectr.remove = (testName) ->
-    delete detectCache[testName] if detectCache[testName]
-    if detectResultCache[testName]
-      htmlClassName = document.documentElement.className
-
-      htmlClassName = htmlClassName.replace(detectResultCache[testName], '').replace(/^\s+|\s+$/g,'').replace(/\s+/g,' ')
-      document.documentElement.className = htmlClassName
-      delete detectResultCache[testName]
-
-    detectr
-
-  ###
-    Add a test
-  ###
-  detectr.add = (testName, testObject) ->
-    # If it is a single test, run it directly, else iterate over it
-    if testName.tests
-      # Add all tests to the queue
-      for key, value of testName.tests
-        testQueue[key] =
-          status: 'untested'
-          run: value.run
-          result: value.result
-
-      # Run the tests
-      for key, value of testName.tests
-        detectr.add key, value
-    else
-      runTest testName, testObject
-
-    detectr
-
-  ###
-    Expose defaultTests for those who need re-execute the detectr function
-  ###
-  detectr.defaultTests = defaultTests
-
-  ###
-    Call detectr constructor with default configuration
-    and set the reference to the detectr object
-  ###
-  window.detectr = detectr defaultTests
-
-)(@, document)


simplify and more conservative protection

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -398,6 +398,7 @@
     return item # return items[0] as fallback

   syncToEditor: (editor) ->
+    return if @isActive() # Prevent UI cursor from being moved while UI is active.
     return if @preventSyncToEditor
     if item = @findClosestItemForEditor(editor)
       @selectItem(item)
@@ -522,12 +523,12 @@
         @syncToEditor(editor)

     @syncSubcriptions.add @onDidRefresh =>
-      @syncToEditor(editor) unless @isActive()
+      @syncToEditor(editor)

     if @provider.boundToEditor
       @syncSubcriptions.add editor.onDidStopChanging =>
         # Surppress refreshing while editor is active to avoid auto-refreshing while direct-edit.
-        @refresh(force: true) if isActiveEditor(editor)
+        @refresh(force: true) unless @isActive()
     else
       @syncSubcriptions.add editor.onDidSave =>
         @refresh(force: true) unless @isActive()


cleanup

cleanup

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -22,8 +22,7 @@
     @editor = atom.workspace.buildTextEditor(lineNumberGutterVisible: false)
     @gutter = @editor.addGutter(name: 'narrow')
     @editor.onDidDestroy =>
-      @originalPane.activate()
-      @provider?.destroy?()
+      @destroy()

     @editorElement = atom.views.getView(@editor)
     @editorElement.classList.add('narrow')
@@ -37,6 +36,11 @@

     @editor.getTitle = => ["Narrow", @provider?.getTitle()].join(' ')
     @editor.isModified = -> false
+
+  destroy: ->
+    @originalPane.activate()
+    @provider?.destroy?()
+    @gutterMarker?.destroy()

   updateGutter: (point) ->
     if point.row is 0


set objective command

set objective command

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -82,6 +82,7 @@
       'narrow-ui:toggle-auto-preview': => @toggleAutoPreview()
       'narrow-ui:move-to-prompt-or-selected-item': => @moveToPromptOrSelectedItem()
       'narrow-ui:move-to-prompt': => @moveToPrompt()
+      'narrow-ui:exclude-file': => @excludeFile()
       'narrow-ui:start-insert': => @setReadOnly(false)
       'narrow-ui:stop-insert': => @setReadOnly(true)
       'core:move-up': (event) => @moveUpOrDown(event, 'previous')


article_new: remove more recipient-list legacy code

--- app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
@@ -12,10 +12,6 @@
     '.js-cancel':                         'cancelContainer'
     '.textBubble':                        'textBubble'
     '.editControls-item':                 'editControlItem'
-    #'.editControls':                     'editControls'
-    #'.recipient-picker':                 'recipientPicker'
-    #'.recipient-list':                   'recipientList'
-    #'.recipient-list .list-arrow':       'recipientListArrow'

   events:
     'click .js-toggleVisibility':    'toggleVisibility'
@@ -25,10 +21,6 @@
     'click .js-writeArea':           'stopPropagation'
     'click .list-entry-type div':    'change_type'
     'focus .js-textarea':            'openTextarea'
-    #'dragenter':                    'onDragenter'
-    #'dragleave':                    'onDragleave'
-    #'drop':                         'onFileDrop'
-    #'change input[type=file]':      'onFilePick'

   constructor: ->
     super


logger: Add usage

logger: Add usage

--- logger.coffee
+++ logger.coffee
@@ -1,3 +1,25 @@
+###
+# Usage
+
+    logger = require './logger'
+
+    # Set log level to filter out unwanted log messages
+    logger.setLevel logger.LEVEL_INFO
+    logger.debug 'debug message'
+    logger.info 'info message'
+    logger.warn 'warn message'
+    logger.error 'error message'
+    logger.fatal 'fatal message'
+
+    # Enable a tag to activate log messages for the tag
+    logger.enableTag 'testtag'
+    logger.tag 'testtag', 'testtag message'
+    logger.tag 'anothertag', 'anothertag message'
+
+    # Print raw string. Equivalent of console.log().
+    logger.raw "hello\nraw\nstring"
+###
+
 # Current log level
 logLevel = null