each-* will now check for null on null array [Closes #110]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -339,17 +339,18 @@

       @iterated = []

-      for item in collection
-        data = {}
-        data[n] = m for n, m of @view.models
-        data[@args[0]] = item
-        itemEl = el.cloneNode true
-        if @iterated.length > 0
-          previous = @iterated[@iterated.length - 1].els[0]
-        else
-          previous = @marker
-        @marker.parentNode.insertBefore itemEl, previous.nextSibling ? null
-        @iterated.push rivets.bind itemEl, data
+      if collection
+        for item in collection
+          data = {}
+          data[n] = m for n, m of @view.models
+          data[@args[0]] = item
+          itemEl = el.cloneNode true
+          if @iterated.length > 0
+            previous = @iterated[@iterated.length - 1].els[0]
+          else
+            previous = @marker
+          @marker.parentNode.insertBefore itemEl, previous.nextSibling ? null
+          @iterated.push rivets.bind itemEl, data

   "class-*": (el, value) ->
     elClass = " #{el.className} "


Make all bindings hold reference to the actual key at which the model resides on the view.

This allows us to update the bindings' model property according to the
models currently set on the view.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -14,7 +14,7 @@
   # All information about the binding is passed into the constructor; the
   # containing view, the DOM node, the type of binding, the model object and the
   # keypath at which to listen for changes.
-  constructor: (@view, @el, @type, @model, @keypath, @options = {}) ->
+  constructor: (@view, @el, @type, @key, @keypath, @options = {}) ->
     unless @binder = @view.binders[type]
       for identifier, value of @view.binders
         if identifier isnt '*' and identifier.indexOf('*') isnt -1
@@ -25,11 +25,9 @@
             @args.shift()

     @binder or= @view.binders['*']
-
-    if @binder instanceof Function
-      @binder = {routine: @binder}
-
+    @binder = {routine: @binder} if @binder instanceof Function
     @formatters = @options.formatters || []
+    @model = @view.models[@key]

   # Applies all the current formatters to the supplied value and returns the
   # formatted value.
@@ -182,17 +180,17 @@
             options.formatters = pipes
             options.bypass = path.indexOf(':') != -1
             if splitPath[0]
-              model = @models[splitPath.shift()]
+              key = splitPath.shift()
             else
-              model = @models
+              key = null
               splitPath.shift()
             keypath = splitPath.join '.'

-            if model
+            if @models[key]?
               if dependencies = context.shift()
                 options.dependencies = dependencies.split /\s+/

-              @bindings.push new Rivets.Binding @, node, type, model, keypath, options
+              @bindings.push new Rivets.Binding @, node, type, key, keypath, options

         attributes = null if attributes


redid checking stuff

redid checking stuff

--- src/base.iced
+++ src/base.iced
@@ -8,6 +8,7 @@
 env                   = require './env'
 {json_checker}        = require './json_checker'
 {respond}             = require 'keybase-bjson-express'
+core                  = require 'keybase-bjson-core'

 util = require 'util'

@@ -35,7 +36,7 @@

   #-----------------------------------------

-  needed_inputs : -> []
+  input_template : -> {}

   #-----------------------------------------

@@ -49,52 +50,6 @@

   pub : (dict) -> @oo.body[k] = v for k,v of dict
   clear_pub : () -> @oo = { status : {}, body : {}}
-
-  #-----------------------------------------
-
-  get_unchecked_input_field: (f) ->
-    ###
-    does no error checking
-    ###
-    v       = null
-    arrays  = [ "body", "query", "params"]
-    for a in arrays
-      break if (v = @req[a][f])?
-    if (not v?) or (v.length is 0)
-      return null
-    return v
-
-  #-----------------------------------------
-
-  # Can override this as needs be, especially if you want to add new checkers
-  # For now, everything is good, and return the original value is given.
-  check_field : (f, v) -> [ null, v ]
-
-  #-----------------------------------------
-
-  get_input_field : (f, is_optional) ->
-    v = @get_unchecked_input_field f
-    if (not v?) and is_optional
-      return true
-    [e,v] = @check_field f, v
-    ret = true
-    if e
-      @_error_in_field[f] = e
-      ret = false
-    else
-      @[f] = v
-    return ret
-
-  #-----------------------------------------
-
-  get_input : ->
-    ret = true
-    for f in @needed_fields()
-      ret = false unless @get_input_field f, false
-    for f in @maybe_fields()
-      ret = false unless @get_input_field f, true
-    @set_error sc.INPUT_ERROR, "missing or invalid input", @_error_in_field unless ret
-    return ret

   #-----------------------------------------

@@ -173,14 +128,8 @@
   #------

   __check_inputs : () ->
-    ret = null
-    template = @needed_inputs()
-    for k,v of template
-      err = json_checker { key : k, checker : v, json : @input }
-      if err?
-        @_error_in_field[k] = err
-        ret = err
-    return ret
+    console.log core
+    core.check_template @input_template(), @input, "HTTP"

   #------


runner: Start network before sending packets

--- src/runner.coffee
+++ src/runner.coffee
@@ -72,6 +72,16 @@
   # FIXME: wait for responses. Maybe until "ports" message matches our setup?
   return callback null, graphId

+startNetwork = (runtime, graphId, callback) ->
+  debug 'startnetwork', graphId
+
+  runtime.once 'execution', (status) ->
+    return callback new Error 'Network not started after network:start' if not status.started
+    return callback null
+
+  runtime.sendNetwork 'start',
+    graph: graphId
+
 sendPackets = (client, graphId, packets, callback) ->
   debug 'sendpackets', graphId, packets

@@ -122,7 +132,9 @@
       graph = null
     sendGraph @client, graph, (err, graphId) =>
       @currentGraphId = graphId
-      return callback err
+      return callback err if err
+      startNetwork @client, graphId, (err) =>
+        return callback err

   teardownSuite: (suite, callback) ->
     debug 'teardown suite', "\"#{suite.name}\""


Ignore all  queries

Ignore all  queries

--- mongodb.coffee
+++ mongodb.coffee
@@ -15,8 +15,9 @@
 					options = {}
 					callback = () ->

-				if db_command.query? and (db_command.query["ping"] or db_command.query["ismaster"])
-					# Ignore noisy methods
+				collection = db_command.collectionName
+				if collection.match(/\$cmd$/)
+					# Ignore noisy command methods like authenticating, ismaster and ping
 					return _method.call this, db_command, options, callback

 				key = "mongo-requests.#{type}"
@@ -32,7 +33,7 @@
 					logger.log
 						query: db_command.query
 						type: type
-						collection: db_command.collectionName
+						collection: collection
 						"response-time": new Date() - start
 						"mongo request"
 					callback.apply this, arguments


Filter fix: Only set all channels when channelID filter not present

--- src/api/transactions.coffee
+++ src/api/transactions.coffee
@@ -43,7 +43,9 @@
 			// if not an admin, restrict by transactions that this user can view
 			var channels = yield authorisation.getUserViewableChannels(this.authenticated);

-			filtersObject.channelID = { $in: getChannelIDsArray(channels) };
+			if (!filtersObject.channelID) {
+				filtersObject.channelID = { $in: getChannelIDsArray(channels) };
+			}
 		}

 		this.body = yield transactions.Transaction.find(filtersObject).skip(filterSkip).limit(filterLimit).sort({ 'request.timestamp': -1 }).exec();


ForcedRegister: clear unload listeners

ForcedRegister: clear unload listeners

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -229,6 +229,8 @@
     path = location.pathname + location.search + "?"
     mainController = KD.getSingleton("mainController")
     mainController.on "accountChanged.to.loggedIn", =>
+      wc = KD.singleton 'windowController'
+      wc.clearUnloadListeners()
       location.replace path


Fixed a bug where the pinger could attempt to send a ping when the socket has already been removed.

--- src/api.coffee
+++ src/api.coffee
@@ -124,7 +124,7 @@
             @emit 'open'

             pinger = =>
-              @sock.ping()
+              @sock.ping() if @sock?

             # Ping every 15 seconds to keep the connection alive
             @pingerRef = setInterval pinger, 15000


always clear result highlight

always clear result highlight

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -141,9 +141,10 @@
           else if option.group_array_index? and @results_data[option.group_array_index].search_match
             option.search_match = true

+    this.result_clear_highlight()
+
     if results < 1 and searchText.length
       this.update_results_content ""
-      this.result_clear_highlight()
       this.no_results searchText
     else
       this.update_results_content this.results_option_build()


Ignore group options whose information is missing

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -82,7 +82,7 @@
     classes.push "active-result" if !option.disabled and !(option.selected and @is_multiple)
     classes.push "disabled-result" if option.disabled and !(option.selected and @is_multiple)
     classes.push "result-selected" if option.selected
-    classes.push "group-option" if option.group_array_index?
+    classes.push "group-option" if option.group_array_index? and @source.get_item(option.group_array_index)?
     classes.push "is-scope" if option.is_scope
     classes.push option.classes if option.classes != ""


Handler refactoring

Handler refactoring

--- src/server.coffee
+++ src/server.coffee
@@ -56,31 +56,49 @@
   args.push '-e' if config.getConf()['dhis-to-ilr']['empty']
   return args

+dhisToIlr = (out, callback) ->
+  out.info "Running dhis-to-ilr ..."
+  args = buildArgs()
+  script = spawn('bash', args)
+  out.info "Executing bash script #{args.join ' '}"
+
+  script.stdout.on 'data', out.info
+  script.stderr.on 'data', out.error
+
+  script.on 'close', (code) ->
+    out.info "Script exited with status #{code}"
+    callback null
+
 handler = (req, res) ->
   openhimTransactionID = req.headers['x-openhim-transactionid']
-  logger.info "[#{openhimTransactionID}] Running sync ..."

-  args = buildArgs()
-  script = spawn('bash', args)
-  logger.info "[#{openhimTransactionID}] Executing bash script #{args.join ' '}"
+  _out = ->
+    body = ""
+    append = (level, data) ->
+      logger[level]("[#{openhimTransactionID}] #{data}")
+      if data[-1..] isnt '\n'
+        body = "#{body}#{data}\n"
+      else
+        body = "#{body}#{data}"
+    return {
+      body: -> body
+      info: (data) -> append 'info', data
+      error: (data) -> append 'error', data
+    }
+  out = _out()

-  out = ""
-  appendToOut = (data) -> out = "#{out}#{data}"
-  script.stdout.on 'data', appendToOut
-  script.stderr.on 'data', appendToOut
+  out.info "Running sync with mode #{config.getConf()['sync-type']['mode']} ..."

-  script.on 'close', (code) ->
-    logger.info "[#{openhimTransactionID}] Script exited with status #{code}"
-
+  dhisToIlr out, (err) ->
     res.set 'Content-Type', 'application/json+openhim'
     res.send {
       'x-mediator-urn': config.getMediatorConf().urn
-      status: if code == 0 then 'Successful' else 'Failed'
+      status: if err then 'Failed' else 'Successful'
       response:
-        status: if code == 0 then 200 else 500
+        status: if err then 500 else 200
         headers:
           'content-type': 'application/json'
-        body: out
+        body: out.body()
         timestamp: new Date()
     }


invitations: switch handler order so there's no flicker between transitions

--- client/Login/AppController.coffee
+++ client/Login/AppController.coffee
@@ -16,29 +16,26 @@
         app.getView().animateToForm('reset')

   handleFailureOfRestriction =->
-    new KDNotificationView title: "Login restricted"
+    KD.utils.defer -> new KDNotificationView title: "Login restricted"

-    KD.introView = new IntroView
-    KD.introView.appendToDomBody()
-    KD.introView.setClass 'in'
+  handleRestriction = (handler) ->
+    ({params: {token : token }})->
+      return handleFailureOfRestriction()  unless token

-  handleRestriction = ({token}, callback)->
-    return handleFailureOfRestriction()  unless token
+      KD.remote.api.JInvitation.byCode token, (err, invite)->
+        if err or !invite?
+          return handleFailureOfRestriction()  unless token

-    KD.remote.api.JInvitation.byCode token, (err, invite)->
-      if err or !invite?
-        return handleFailureOfRestriction()  unless token
-
-      callback()
+        do handler()

   KD.registerAppClass this,
     name                         : "Login"
     routes                       :
-      '/:name?/Login/:token?'    : handler (app, params)->
-          handleRestriction params, -> app.getView().animateToForm 'login'
+      '/:name?/Login/:token?'    : handleRestriction (app)->
+          handler (app)-> app.getView().animateToForm 'register'
       '/:name?/Redeem'           : handler (app)-> app.getView().animateToForm 'redeem'
-      '/:name?/Register/:token?' : handler (app, params)->
-          handleRestriction params, -> app.getView().animateToForm 'register'
+      '/:name?/Register/:token?' : handleRestriction (app)->
+          handler (app)-> app.getView().animateToForm 'register'
       '/:name?/Recover'          : handler (app)-> app.getView().animateToForm 'recover'
       '/:name?/Reset'            : handler (app)-> app.getView().animateToForm 'reset'
       '/:name?/Reset/:token'     : handleResetRoute


static pages: storage vs total disk..inconsistent.

--- client/Pricing/views/plans.coffee
+++ client/Pricing/views/plans.coffee
@@ -54,7 +54,7 @@
       { partial: '1 VM '          , cssClass: 'vm-count' }
       { partial: '1 Core'         , cssClass: 'cpu' }
       { partial: '1GB RAM'        , cssClass: 'ram' }
-      { partial: '10GB Storage'   , cssClass: 'storage' }
+      { partial: '10GB Total Disk', cssClass: 'storage' }
       { partial: '1 Always on VM' , cssClass: 'always-on disabled' }
     ]
   ,


Fixed: progressbar was still visible for mailboxes

--- client/app/components/menu.coffee
+++ client/app/components/menu.coffee
@@ -209,7 +209,6 @@
                     if progress.get('errors').length
                         span className: 'refresh-error',
                             i className: 'fa warning', onClick: @displayErrors.bind null, progress
-
                     if progress.get('firstImport')
                         ThinProgress done: progress.get('done'), total: progress.get('total')

@@ -313,7 +312,7 @@
                         className: 'item-label',
                         "#{pusher}#{@props.mailbox.get 'label'}"

-                if progress
+                if progress and progress.get('firstImport')
                     ThinProgress done: progress.get('done'), total: progress.get('total')

                 if progress?.get('errors').length


client: revert gokmens small change

client: revert gokmens small change

--- client/About/team.coffee
+++ client/About/team.coffee
@@ -16,11 +16,11 @@
         username  : 'gokmen'
         title     : 'Software Engineer'
       ,
+        username  : 'arslan'
+        title     : 'Software Engineer'
+      ,
         username  : 'fatihacet'
         title     : 'Front-End Engineer'
-      ,
-        username  : 'arslan'
-        title     : 'Software Engineer'
       ,
         username  : 'sent-hil'
         title     : 'Software Engineer'


Team: Update team

Team: Update team

--- client/About/team.coffee
+++ client/About/team.coffee
@@ -16,11 +16,11 @@
         username  : 'gokmen'
         title     : 'Software Engineer'
       ,
+        username  : 'fatihacet'
+        title     : 'Front-End Engineer'
+      ,
         username  : 'arslan'
         title     : 'Software Engineer'
-      ,
-        username  : 'fatihacet'
-        title     : 'Front-End Developer'
       ,
         username  : 'sent-hil'
         title     : 'Software Engineer'
@@ -29,6 +29,9 @@
         title     : 'Software Engineer'
       ,
         username  : 'geraint'
+        title     : 'Director of Infrastructure'
+      ,
+        username  : 'leventyalcin'
         title     : 'System Administrator'
       ,
         username  : 'ybrs'
@@ -57,7 +60,27 @@
       ,
         username  : 'pablostanley'
         title     : 'Illustrator'
-
+      ,
+        username  : 'leeolayvar'
+        title     : 'Community Manager'
+      ,
+        username  : 'stefanbc'
+        title     : 'Community Relations'
+      ,
+        username  : 'dicle'
+        title     : 'Business Analysist'
+      ,
+        username  : 'niltuncer'
+        title     : 'Business Analysist'
+      ,
+        username  : 'zeynep'
+        title     : 'Finance Assistant'
+      ,
+        username  : 'nesli'
+        title     : 'Chef de cuisine'
+      ,
+        username  : 'aleksey-m'
+        title     : 'System Administrator'
       ]

     suspended     : []


加入cookies的过期时间

加入cookies的过期时间

--- src/app.coffee
+++ src/app.coffee
@@ -32,7 +32,7 @@
     keepExtensions: true
   ));
   _app.use(_express.cookieParser())
-  _app.use(_express.session(secret: 'hunantv.com'))
+  _app.use(_express.session(secret: 'hunantv.com', cookie:  maxAge: 1 * 60 * 60 * 1000 ))
   _app.use(_express.static(__dirname + '/static'))
   _app.set 'port', process.env.PORT || 14318


Better pay links

Better pay links

--- client/components/Sceleton.coffee
+++ client/components/Sceleton.coffee
@@ -54,16 +54,30 @@

 class PaidTill extends React.Component
     render: () ->
-        console.log "myuser=", @props.myUser
         if @props.myUser?.userList == "stud"
             if @props.myUser?.paidTill && isPaid(@props.myUser)
-                "Занятия оплачены до " + moment(@props.myUser.paidTill).format("DD.MM.YYYY")
+                preLink = "Занятия оплачены до " + moment(@props.myUser.paidTill).format("DD.MM.YYYY") + " "
+                inLink = "Продлить"
             else if @props.myUser?.paidTill
-                "Занятия были оплачены до " + moment(@props.myUser.paidTill).format("DD.MM.YYYY") + ", вы можете продлить"
+                preLink = "Занятия были оплачены до " + moment(@props.myUser.paidTill).format("DD.MM.YYYY") + " "
+                inLink = "Продлить"
             else
-                "Занятия не оплачены, вы можете оплатить"
+                preLink = ""
+                inLink = "Оплатить занятия"
         else
-            "Поддержать занятия"
+            preLink = ""
+            inLink = "Поддержать занятия"
+
+            {preLink}
+            <Link to="/pay">
+                {inLink}
+                {" "}
+                <FontAwesome name="cc-visa"/>
+                {" "}
+                <FontAwesome name="cc-mastercard"/>
+            </Link>
+
+

 paidTillOptions =
     urls: (props) ->
@@ -88,13 +102,7 @@
                 </Col>
                 <Col xs={12} sm={12} md={4} lg={4}>

-                        <Link to="/pay">
-                            <PaidTillConnected/>
-                            {" "}
-                            <FontAwesome name="cc-visa"/>
-                            {" "}
-                            <FontAwesome name="cc-mastercard"/>
-                        </Link>
+                        <PaidTillConnected/>

                 </Col>
             </Row>


Remove uneeded curlies and parens

Remove uneeded curlies and parens

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -264,12 +264,11 @@
     unless @packages?
       PackageManager = require './package-manager'
       fsUtils = require './fs-utils'
-      @packages = new PackageManager({
+      @packages = new PackageManager
         devMode: devMode
         configDirPath: fsUtils.absolute('~/.atom')
         resourcePath: @resourcePath
         urlToOpen: urlToOpen
-      })

     pack = _.find @packages.getAvailablePackageMetadata(), ({name}) -> name is packageName
     if pack?


Tests: CreateTeam: remove domainalias test

This test is not required and not part of team create flow, it's based on the provided default template

--- servers/lib/server/handlers/createteam.test.coffee
+++ servers/lib/server/handlers/createteam.test.coffee
@@ -104,15 +104,6 @@
           next()

       (next) ->
-        # expecting domainAlias to be saved
-        params = { domain : "#{username}.dev.koding.io" }
-
-        JDomainAlias.one params, (err, domainAlias) ->
-          expect(err).to.not.exist
-          expect(domainAlias).to.exist
-          next()
-
-      (next) ->
         # expecting name to be saved
         params = { name : username }


[config] add support for third party s3 which uses path-style buckets

Signed-off-by: Jakob Ackermann <caa3e4f01d56b797520cbfed54bdab4c5e3b0ebb@outlook.com>

--- services/filestore/config/settings.defaults.coffee
+++ services/filestore/config/settings.defaults.coffee
@@ -38,6 +38,7 @@
 				key: process.env['AWS_ACCESS_KEY_ID']
 				secret: process.env['AWS_SECRET_ACCESS_KEY']
 				endpoint: process.env['AWS_S3_ENDPOINT']
+				pathStyle: process.env['AWS_S3_PATH_STYLE']
 				partSize: process.env['AWS_S3_PARTSIZE'] or (100 * 1024 * 1024)

 		stores:


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -116,7 +116,7 @@
     img.onload = ->
       tile = Sprite(this)

-      spriteCacheObject.extend(proxy, tile)
+      spriteCache[url] = Object.extend(proxy, tile)

       if loadedCallback
         loadedCallback(proxy)


remove console.log

remove console.log

--- services/real-time/app.coffee
+++ services/real-time/app.coffee
@@ -1,8 +1,6 @@
 Metrics = require("metrics-sharelatex")
 Settings = require "settings-sharelatex"
 Metrics.initialize(Settings.appName or "real-time")
-
-console.log Settings.redis

 logger = require "logger-sharelatex"
 logger.initialize("real-time")


Refactored __processSyncIncludeTag and divided into multiple methods

--- meta-engine.coffee
+++ meta-engine.coffee
@@ -78,67 +78,92 @@
     unless @contentProvider
       throw new Error "contentProvider is not assigned"

+  # ---------------------------------------------------------- Actual Processing
+
+  __extractCommonOptions: (tagString, otherParameterList, resourcePath, content, tagStartIndex, linebreakCharacter, indentCharacter)->
+
+    # [ tagLineStartIndex, indentLevel ]
+    if tagStartIndex is 0
+      indentLevel = 0
+      tagLineStartIndex = 0
+    else
+      caseContent = content.slice 0, tagStartIndex
+      lastLineEnd = caseContent.lastIndexOf linebreakCharacter
+      tagLineStartIndex = lastLineEnd + 1 # NOTE: Also covers the scenario when lastLineEnd is -1
+      indentationString = content.slice tagLineStartIndex, tagStartIndex
+      indentLevel = 0
+      while (indentationString.indexOf indentCharacter, (indentCharacter.length * indentLevel)) > -1
+        indentLevel += 1
+
+    # [ tagLineEndIndex, tag ]
+    tagLineEndIndex = (content.indexOf linebreakCharacter, tagStartIndex)
+    if tagLineEndIndex is -1
+      tagLineEndIndex = content.length - 1
+    tag = (content.slice tagStartIndex, tagLineEndIndex)
+
+    # [ primaryQuotedString ]
+    indexQuote1 = tag.indexOf '"', tagString.length
+    if indexQuote1 is -1
+      throw new Error "Expected Double Quote in \"#{resourcePath}\""
+    indexQuote2 = tag.indexOf '"', indexQuote1 + 1
+    if indexQuote2 is -1
+      throw new Error "Expected Double Quote in \"#{resourcePath}\""
+    primaryQuotedString = name = tag.slice indexQuote1 + 1, indexQuote2
+
+    # [ otherParameterMap ]
+    otherParameterMap = {}
+    for parameter in otherParameterList
+      if (tag.indexOf parameter, indexQuote2 + 1) > -1
+        otherParameterMap[parameter] = true
+      else
+        otherParameterMap[parameter] = false
+
+    return [ tagLineStartIndex, indentLevel, tagLineEndIndex, tag, primaryQuotedString, otherParameterMap ]
+
+  __extractNextIncludeTag: (resourcePath, content, offset)->
+
+    tagString = @optionMap.prefix + 'include' + @optionMap.postfix
+    { linebreakCharacter, indentCharacter } = @optionMap
+
+    tagStartIndex = content.indexOf tagString, offset
+    return [ -1, null ] if tagStartIndex is -1
+
+    res = @__extractCommonOptions tagString, ['isolated'], resourcePath, content, tagStartIndex, linebreakCharacter, indentCharacter
+    return [ tagStartIndex, res ]
+
+  __insertSyncTagContents: ( resourcePath, subResourcePath, subContent, content, tagLineStartIndex, tagLineEndIndex ) ->
+
+    # insert comments
+    if @optionMap.insertComments
+      beginComments = @optionMap.commentPrefix + "start of inclusion from \"#{subResourcePath}\" into \"#{resourcePath}\"" + @optionMap.commentPostfix
+      endComments = @optionMap.commentPrefix + "end of inclusion \"#{subResourcePath}\"" + @optionMap.commentPostfix
+      subContent = beginComments + subContent + endComments
+
+    # replace
+    left = content.slice 0, tagLineStartIndex
+    middle = subContent
+    right = content.slice tagLineEndIndex, content.length
+    content = left + middle + right
+
+    return content
+
+
   # ---------------------------------------------------------- Sync Version

   __processSyncIncludeTag: (resourcePath, content)->

-    linebreakCharacter = @optionMap.linebreakCharacter
-
-    indentCharacter = @optionMap.indentCharacter
-
     offset = 0
-    while (tagStartIndex = content.indexOf '@include', offset) > -1
+    loop
+      [ tagStartIndex, res ] = @__extractNextIncludeTag resourcePath, content, offset
+      break if tagStartIndex is -1
+      [ tagLineStartIndex, indentLevel, tagLineEndIndex, tag, primaryQuotedString, otherParameterMap ] = res
       offset = tagStartIndex + 1

-      # figure out indent level
-      if tagStartIndex is 0
-        indentLevel = 0
-        tagLineStartIndex = 0
-      else
-        caseContent = content.slice 0, tagStartIndex
-        lastLineEnd = caseContent.lastIndexOf linebreakCharacter
-        tagLineStartIndex = lastLineEnd + 1 # NOTE: Also covers the scenario when lastLineEnd is -1
-        indentationString = content.slice tagLineStartIndex, tagStartIndex
-        indentLevel = 0
-        while (indentationString.indexOf indentCharacter, (indentCharacter.length * indentLevel)) > -1
-          indentLevel += 1
-
-      # extract tag
-      tagLineEndIndex = (content.indexOf linebreakCharacter, offset)
-      if tagLineEndIndex is -1
-        tagLineEndIndex = content.length - 1
-      tag = (content.slice tagStartIndex, tagLineEndIndex)
-
-      # extract name
-      indexQuote1 = tag.indexOf '"', '@include'.length
-      if indexQuote1 is -1
-        throw new Error "Expected Double Quote in \"#{resourcePath}\""
-      indexQuote2 = tag.indexOf '"', indexQuote1 + 1
-      if indexQuote2 is -1
-        throw new Error "Expected Double Quote in \"#{resourcePath}\""
-      name = tag.slice indexQuote1 + 1, indexQuote2
-
-      # extract other parameters
-      if ((tag.slice indexQuote2 + 1, tag.length).indexOf 'isolated') > -1
-        isolated = true
-      else
-        isolated = false
-
       # extract subContent (recursive)
-      subResourcePath = path.join (path.dirname resourcePath), name
-      subContent = @__processSync subResourcePath, isolated
-
-      # insert comments
-      if @optionMap.insertComments
-        beginComments = @optionMap.commentPrefix + "start of inclusion from \"#{subResourcePath}\" into \"#{subResourcePath}\"" + @optionMap.commentPostfix
-        endComments = @optionMap.commentPrefix + "end of inclusion \"#{subResourcePath}\"" + @optionMap.commentPostfix
-        subContent = beginComments + subContent + endComments
-
-      # replace
-      left = content.slice 0, tagLineStartIndex
-      middle = subContent
-      right = content.slice tagLineEndIndex, content.length
-      content = left + middle + right
+      subResourcePath = path.join (path.dirname resourcePath), primaryQuotedString
+      subContent = @__processSync subResourcePath, otherParameterMap.isolated
+
+      content = @__insertSyncTagContents resourcePath, subResourcePath, subContent, content, tagLineStartIndex, tagLineEndIndex

     return content


fix: fixed crash in Firefox when checking `Node.contains` for non-objects

--- src/coffee/isvisible.coffee
+++ src/coffee/isvisible.coffee
@@ -24,6 +24,10 @@
 # this should be as universal and cross-browser compatible as possible up to IE8
 isVisible = (element) ->

+  # don't even bother with wrong types
+  unless typeof element is 'object'
+    return false
+
   # don't even bother checking elements that do not exist or are not in BODY
   unless document.body?.contains element
     return false
@@ -35,6 +39,7 @@
     element = element.parentNode

   true
+

 isVisible.all = (list) ->
   for item in list


API change 'get_profile_info' to 'get_profile'

changed 'get_profile_info' to 'get_profile' as 'get_profile_info' no
longer works as intended

--- src/bot.coffee
+++ src/bot.coffee
@@ -653,7 +653,7 @@

   getProfile: ->
-    rq = api: 'user.get_profile_info'
+    rq = api: 'user.get_profile'
     callback = null
     if arguments.length == 1
       if typeof arguments[0] == 'function'


misc tweaks

misc tweaks

--- @client/legal.coffee
+++ @client/legal.coffee
@@ -19,10 +19,11 @@
   DIV
     style:
       width: '80%'
-      margin: 'auto'
-      maxWidth: 800
-      paddingTop: 100
-      paddingBottom: 50
+      margin: '40px auto'
+      maxWidth: 1000
+      padding: '25px 60px'
+      backgroundColor: 'white'
+      boxShadow: '0 1px 2px rgba(0,0,0,.2)'

     STYLE dangerouslySetInnerHTML: __html: #dangerously set html is so that the type="text" doesn't get escaped
         """


Bokeh.logger -> logger

Bokeh.logger -> logger

--- bokehjs/src/coffee/models/plots/plot.coffee
+++ bokehjs/src/coffee/models/plots/plot.coffee
@@ -580,7 +580,7 @@
     # Check that what we found is a bk-root. If not, this is probably a subplot, which we
     # can not currently make responsive in a good way
     if not node.classList.contains('bk-root')
-       Bokeh.logger.warn('subplots cannot be responsive')
+       logger.warn('subplots cannot be responsive')
        return

     @_resize_width_height(use_width, use_height, maintain_ar,


Removed unnecessary example for calculateCurve25519KeysFor

--- miniLockLib.coffee
+++ miniLockLib.coffee
@@ -78,13 +78,7 @@
   calculateCurve25519KeysFor hashDigestOfSecretPhrase, emailAddress, callback

-# Calculate curve25519 key pair for the given `secret` and `salt`. When the
-# task is complete `callback` receives its `keys` like this:
-#
-#     calculateCurve25519KeysFor secret, salt, (keys) ->
-#       keys.publicKey is [32-bit Uint8Array]
-#       keys.secretKey is [32-bit Uint8Array]
-#
+# Calculate a curve25519 key pair for the given `secret` and `salt`.
 calculateCurve25519KeysFor = (secret, salt, callback) ->
   # Define miniLock `scrypt` parameters for the calculation task:
   logN          = 17       # CPU/memory cost parameter (1 to 31).


append to p#age

append to p#age

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -17,7 +17,7 @@
     today = new Date()
     comparison = today - Date.parse(@storage.created_at)
     days_ago = moment.duration(comparison).days()
-    $("#created_at").append(days_ago)
+    $("#age").append(days_ago)
     @current = ""
     @fail = 0
     @reveal = ""


Revert "neo4j: minor fix"

Revert "neo4j: minor fix"

This reverts commit aa6f150d8ae5967795fbf3bcfaf4268e251e9d6c.

--- server/lib/server/graph.coffee
+++ server/lib/server/graph.coffee
@@ -31,10 +31,13 @@
       ' or koding.name = "JStatusUpdate"'
       'return *'
       'order by koding.`meta.createdAt` DESC'
-      'limit 20'
+      'limit 4'
     ].join('\n');

-    @db.query query, {}, (err, results)=>
+    params =
+      itemId : "515360d23af2fb6b6b000009"
+
+    @db.query query, params, (err, results)=>
 #      console.log results
       tempRes = []
       if err then callback err


neo4j: set newMemberBucketIndex only if there is member bucket

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -373,7 +373,7 @@
   response.isFull     = false
   response.from       = overview.first.createdAt.first
   response.to         = overview.last.createdAt.first
-  response.newMemberBucketIndex = newMemberBucketIndex
+  response.newMemberBucketIndex = newMemberBucketIndex  if newMemberBucketIndex

   return response


support not persistent volumes. these volumes can be used to share with other containers, but they won't persist on disk.

--- server/scripts/lib/bash.coffee
+++ server/scripts/lib/bash.coffee
@@ -9,7 +9,10 @@
       parentCtx = Template.parentData(1)
       @volumes?.reduce (prev, volume) =>
         if volume.indexOf(':') > -1
-          "#{prev}-v #{volume} "
+          if volume.indexOf(':do_not_persist', this.length - ':do_not_persist'.length) isnt -1
+            "-v #{volume.substr(0,volume.indexOf(':do_not_persist'))}"
+          else
+            "#{prev}-v #{volume} "
         else
           "#{prev}-v #{parentCtx.dataDir}/#{parentCtx.project}/#{parentCtx.instance}/#{@service}#{volume}:#{volume} "
       , ""


Testing some actual interactive behaviour

--- test/steps/unit/projects-controller-spec.coffee
+++ test/steps/unit/projects-controller-spec.coffee
@@ -31,7 +31,7 @@
     ]

     # We mock this directly to prevent calls to the InterMine
-    # back ends.
+    # back ends. We can't really intercept them.
     mockGetEntities = -> then: (f) ->
       f templates: mockTemplates, lists: mockLists

@@ -97,3 +97,20 @@
           Accept: "application/json, text/plain, */*"
         test.$httpBackend.flush()

+    describe 'Selecting a project', ->
+
+      beforeEach ->
+        test.projects = test.$controller 'ProjectsCtrl',
+          getMineUserEntities: mockGetEntities
+        test.$httpBackend.flush()
+        test.projects.setCurrentProject mockProjects[1]
+
+      it 'should have added a project to the path', ->
+        expect(test.projects.pathToHere.length).toEqual 1
+
+      it 'should have the right names on the path', ->
+        expect(s.name for s in test.projects.pathToHere).toEqual ['mock project 3']
+
+      it 'should now have that project as the current project', ->
+        expect(test.projects.currentProject.id).toBe 6
+


Split platform specific keybindings

Split platform specific keybindings

--- keymaps/go-to-line.cson
+++ keymaps/go-to-line.cson
@@ -1,6 +1,15 @@
-'body':
-  'meta-l': 'editor:go-to-line'
+'.platform-darwin':
+  'cmd-l': 'editor:go-to-line'
+
+'.platform-win32':
+  'ctrl-l': 'editor:go-to-line'
+
 '.go-to-line .mini.editor input':
   'enter': 'core:confirm',
   'escape': 'core:cancel'
-  'meta-w': 'core:cancel'
+
+'.platform-darwin .go-to-line .mini.editor input':
+  'cmd-w': 'core:cancel'
+
+'.platform-win32 .go-to-line .mini.editor input':
+  'ctrl-w': 'core:cancel'


added option for backward compatibility

added option for backward compatibility

--- cURLCodeGenerator.coffee
+++ cURLCodeGenerator.coffee
@@ -9,6 +9,7 @@
     ("#{str}").replace(/[\\']/g, '\\$&')

 cURLCodeGenerator = ->
+    self = this

     @headers = (request) ->
         headers = request.headers
@@ -29,6 +30,8 @@
         }

     @auth = (request, authHeader) ->
+        if self.options.useHeader
+            return null
         match = authHeader.match(/([^\s]+)\s(.*)/) || []
         scheme = match[1] || null
         params = match[2] || null
@@ -115,11 +118,9 @@
                 break
         lines.join("\n")

-    @generate = (context) ->
-        request = context.getCurrentRequest()
-
+    @generateRequest = (request) ->
         view =
-            "request": context.getCurrentRequest()
+            "request": request
             "headers": @headers request
             "body": @body request

@@ -130,7 +131,17 @@

         template = readFile "curl.mustache"
         rendered_code = Mustache.render template, view
-        @strip_last_backslash rendered_code
+        return @strip_last_backslash rendered_code
+
+    @generate = (context, requests, options) ->
+        self.options = (options || {}).inputs || {}
+        console.log('@@@@', JSON.stringify(options, null, 2))
+
+        curls = requests.map((request) ->
+            return self.generateRequest(request)
+        )
+
+        return curls.join('\n')

     return

@@ -141,5 +152,8 @@
     "cURL"
 cURLCodeGenerator.fileExtension = "sh"
 cURLCodeGenerator.languageHighlighter = "bash"
+cURLCodeGenerator.inputs = [
+    new InputField("useHeader", "do not use -u option", "Checkbox", {defaultValue: false})
+]

 registerCodeGenerator cURLCodeGenerator


Make a generator

Make a generator

--- components/StabilizePortal.coffee
+++ components/StabilizePortal.coffee
@@ -1,68 +1,60 @@
 noflo = require 'noflo'

-class StabilizePortal extends noflo.Component
-  icon: 'fire-extinguisher'
-  description: 'Resets a volatile portal to stable after a given time'
+exports.getComponent = ->
+  c = new noflo.Component
+  c.icon = 'fire-extinguisher'
+  c.description = 'Resets a volatile portal to stable after a given time'
+  c.inPorts.add 'state',
+    datatype: 'object'
+    description: 'Calculated state of the portal, e.g. under attack but blue'
+    required: yes
+  c.inPorts.add 'wait',
+    datatype: 'int'
+    description: 'How long to keep a portal volatile before stabilizing'
+    required: no
+    control: true
+    default: 5000
+  c.outPorts.add 'state',
+    datatype: 'object'
+    description: 'Calculated state of the portal, e.g. under attack but blue'
+    required: yes

-  constructor: ->
-    @canDisconnect = true
-    @timers = []
-    @wait = 5000
-    @inPorts = new noflo.InPorts
-      state:
-        datatype: 'object'
-        description: 'Calculated state of the portal, e.g. under attack but blue'
-        required: yes
-      wait:
-        datatype: 'int'
-        description: 'How long to keep a portal volatile before stabilizing'
-        required: no
-    @outPorts = new noflo.OutPorts
-      state:
-        datatype: 'object'
-        description: 'Calculated state of the portal, e.g. under attack but blue'
-        required: yes
+  c.setUp = (callback) ->
+    c.timers = []
+    callback()
+  c.tearDown = (callback) ->
+    return callback() unless c.timers
+    while c.timers.length
+      context = c.timers.shift()
+      clearTimeout context.timer
+      context.timer = null
+      context.deactivate()
+    callback()

-    @inPorts.state.on 'connect', =>
-      @canDisconnect = false
-    @inPorts.state.on 'data', (state) =>
-      # Pass the current state through
-      @outPorts.state.send state
-      # Set a stabilization timer
-      @stabilize state
+  c.process (input, output, context) ->
+    return unless input.hasData 'state', 'wait'
+    wait = input.getData 'wait'
+    state = input.getData 'state'

-    @inPorts.state.on 'disconnect', =>
-      @canDisconnect = true
-      do @checkDisconnect
+    # Pass the current state through
+    output.send
+      state: state

-    @inPorts.wait.on 'data', (@wait) =>
-
-  stabilize: (state) =>
-    return if state.state in [
+    # Deactivate in case of stable states
+    return output.done() if state.state in [
       'stable'
       'awesome'
       'bad'
       'disco'
-      ]
+    ]

-    timer = setTimeout =>
+    # Set a stabilization timer
+    context.timer = setTimeout ->
       state.state = 'stable'
-      @outPorts.state.send state
-      @timers.splice @timers.indexOf(timer), 1
-      do @checkDisconnect
-    , @wait
-
-    @timers.push timer
-
-  checkDisconnect: ->
-    return unless @canDisconnect
-    return if @timers.length
-    @outPorts.state.disconnect()
-
-  shutdown: ->
-    while @timers.length
-      timer = @timers.shift()
-      clearTimeout timer
-    do @checkDisconnect
-
-exports.getComponent = -> new StabilizePortal
+      output.send
+        state: state
+      c.timers.splice c.timers.indexOf(context), 1
+      context.timer = null
+      context.deactivate()
+    , wait
+    c.timers.push context


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -28,8 +28,8 @@
     y: 10

   equals o.closest([]), null
-
-  equals o.closest([other]), other
+
+  equals o.closest([other, other2]), other

 test "[event] create", 2, ->
   GameObject


testing CI

testing CI

--- test/grabURITest.coffee
+++ test/grabURITest.coffee
@@ -1,15 +1,15 @@
 Helper = require('hubot-test-helper')
 helper = new Helper('../scripts/grabURI.coffee')
+
 expect = require('chai').expect
 co     = require('co')

+
 describe 'Otis', ->
-  room = helper.createRoom()
-
   context 'User posts URL', ->
-    beforeEach ->
-      co =>
-        yield room.user.say('user1', 'http://fakewebsite.com')
+    room = helper.createRoom()
+    co =>
+      yield room.user.say('user1', 'http://fakewebsite.com')

     it 'should reply to user', ->
       expect(room.messages).to.eql [


Use fabric.js in Player class

Use fabric.js in Player class

--- player.coffee
+++ player.coffee
@@ -14,26 +14,30 @@
 #    You should have received a copy of the GNU Affero General Public License
 #    along with this program.  If not, see http://www.gnu.org/licenses/.

-define([], ->
+define(['fabric'], ->

   class Player

     constructor: (@name, pos) ->
       @positions = []
       @positions.push(pos)
-      @current_line = []
+      @current_line = new fabric.Polyline([], {
+        stroke: 'black',
+        stokeWidth: 3,
+        fille: 'white'
+      })

     lastPos: ->
       @positions[@positions.length - 1]

     currentLine: ->
-      @current_line.slice() # Copy array
+      @current_line.points.slice() # Copy array

     currentLinePos: ->
-      @current_line[@current_line.length - 1]
+      @current_line.points[@current_line.points.length - 1]

     addToLine: (pos) ->
-      @current_line.push(pos)
+      @current_line.points.push(pos)

   Player
 )


added deferred so we know when we're doing loading

--- src/coffee/ui_model.coffee
+++ src/coffee/ui_model.coffee
@@ -8,11 +8,14 @@
     columns : []
   default_view : Continuum.ui.DataTableView
   load : (offset) ->
+    @loaddeferred = $.Deferred()
     data_source = @get_ref('data_source')
     $.when(data_source.loaddeferred).then(() =>
+      console.log('setting columns', data_source.get('columns'))
       @set('columns', data_source.get('columns'))
+      @loaddeferred.resolve()
     )
-    return null
+    return @loaddeferred

 class DataTables extends Backbone.Collection
   model : DataTable


Added an option to edit an item when it is selected.

--- src/collectionTable.coffee
+++ src/collectionTable.coffee
@@ -171,7 +171,7 @@
   # data._template = this;
   @tableId = getNextId()
   @selectedIds = new ReactiveVar([])
-  configureSettings this
+  configureSettings(@)

 TemplateClass.rendered = ->
   template = this
@@ -228,13 +228,18 @@
   collection = @collection
   createRoute = @createRoute
   editRoute = @editRoute
-  template.createItem = createItem
-  template.editItem = editItem
-  template.deleteItem = deleteItem
+  @createItem = createItem
+  @editItem = editItem
+  @deleteItem = deleteItem
   if collection
     @autorun ->
       Collections.observe collection, removed: (doc) ->
         removeSelection domNode, [ doc._id ]
+
+  if settings.editOnSelect
+    @autorun =>
+      ids = @selectedIds.get()
+      unless _.isEmpty(ids) then @editItem(ids: ids)

 TemplateClass.events
   'click table.selectable tbody tr': (e, template) ->


Refactor getTemplate() and allow table ID

- Added support for passing either element or template instance to getTemplate().
- Allowed passing settings.id to use as the group for reactive-table.

--- src/collectionTable.coffee
+++ src/collectionTable.coffee
@@ -1,4 +1,5 @@
-TemplateClass = Template.collectionTable
+templateName = 'collectionTable'
+TemplateClass = Template[templateName]
 selectedClass = 'selected'
 domNodeField = 'domNodeField'
 selectEventName = 'select'
@@ -73,11 +74,17 @@
     throw new Error('No template provided')
   template.find '.collection-table'

-getTemplate = (domNode) ->
-  if !domNode
-    throw new Error('No domNode provided')
-  domNode = $(domNode)[0]
-  Blaze.getView(domNode).templateInstance()
+getTemplate = (arg) ->
+  if arg instanceof Blaze.TemplateInstance
+    template = arg
+  else
+    element = $(arg)[0]
+    if element
+      return Blaze.getView(element).templateInstance()
+  try
+    Templates.getNamedInstance(templateName, template)
+  catch err
+    throw new Error('No element provided')

 getSettings = (domNode) -> getTemplate(domNode).settings

@@ -119,7 +126,7 @@
     group: tableId
     rowsPerPage: 10
     showFilter: true
-  }, Setter.clone(data.settings))
+  }, Setter.clone(data.settings, shallow: true))
   fields = settings.fields = settings.fields or []
   checkbox = settings.checkbox
   if checkbox
@@ -169,7 +176,8 @@
 # Template methods.

 TemplateClass.created = ->
-  @tableId = getNextId()
+  settings = @data.settings ?= {}
+  @tableId = settings.id ? getNextId()
   @selectedIds = new ReactiveVar([])
   configureSettings(@)

@@ -177,7 +185,7 @@
   template = this
   data = @data
   domNode = getDomNode(this)
-  settings = data.settings
+  settings = getSettings()
   $table = $(@$('.reactive-table')).addClass('ui selectable table segment')
   $collectionTable = @$('.collection-table')


Bump the version to 1.0

Bump the version to 1.0

--- src/coffeescripts/transition-callbacks.js.coffee
+++ src/coffeescripts/transition-callbacks.js.coffee
@@ -9,7 +9,7 @@
   return o

 class window.TransitionCallbacks
-  @VERSION: '0.1'
+  @VERSION: '1.0'
   @timeout: 3000
   @clearAll: false


Test that relative paths work well for scripts.

--- test/script_test.coffee
+++ test/script_test.coffee
@@ -9,13 +9,13 @@

   describe "basic", ->
     before ->
-      brains.get "/script/living", (req, res)->
+      brains.get "/script/living/", (req, res)->
         res.send """
         <html>
           <head>
             <script src="/jquery.js"></script>
             <script src="/sammy.js"></script>
-            <script src="/app.js"></script>
+            <script src="app.js"></script>
           </head>
           <body>
             <div id="main">
@@ -31,7 +31,7 @@
         </html>
         """

-      brains.get "/app.js", (req, res)->
+      brains.get "/script/living/app.js", (req, res)->
         res.send """
         Sammy("#main", function(app) {
           app.get("#/", function(context) {
@@ -51,12 +51,12 @@
       browser = new Browser()

       before (done)->
-        browser.visit "http://localhost:3003/script/living", done
+        browser.visit "http://localhost:3003/script/living/", done

       it "should execute route", ->
         assert.equal browser.document.title, "The Living"
       it "should change location", ->
-        assert.equal browser.location.href, "http://localhost:3003/script/living#/"
+        assert.equal browser.location.href, "http://localhost:3003/script/living/#/"

       describe "move around", ->
         before (done)->
@@ -65,21 +65,21 @@
         it "should execute route", ->
           assert.equal browser.text("#main"), "The Living Dead"
         it "should change location", ->
-          assert.equal browser.location.href, "http://localhost:3003/script/living#/dead"
+          assert.equal browser.location.href, "http://localhost:3003/script/living/#/dead"

     describe "live events", ->
       browser = new Browser()

       before (done)->
-        browser.visit "http://localhost:3003/script/living", ->
+        browser.visit "http://localhost:3003/script/living/", ->
           browser.fill "Email", "armbiter@zombies"
           browser.fill "Password", "br41nz"
           browser.pressButton "Sign Me Up"
           browser.wait 500, done

       it "should change location", ->
-        assert.equal browser.location.href, "http://localhost:3003/script/living#/"
+        assert.equal browser.location.href, "http://localhost:3003/script/living/#/"
       it "should process event", ->
         assert.equal browser.document.title, "Signed up"

@@ -88,7 +88,7 @@
       title = null

       before (done)->
-        Browser.visit "http://localhost:3003/script/living", (_, browser)->
+        Browser.visit "http://localhost:3003/script/living/", (_, browser)->
           title = browser.evaluate "document.title"
           done()

@@ -404,7 +404,7 @@
     browser = new Browser()

     before (done)->
-      browser.visit "http://localhost:3003/script/living", done
+      browser.visit "http://localhost:3003/script/living/", done

     it "should construct an img tag", ->
       assert.equal browser.evaluate("new Image").tagName, "IMG"


remove unused modules

remove unused modules

--- services/docstore/app/coffee/DocManager.coffee
+++ services/docstore/app/coffee/DocManager.coffee
@@ -2,11 +2,6 @@
 Errors = require "./Errors"
 logger = require "logger-sharelatex"
 _ = require "underscore"
-async = require "async"
-settings = require("settings-sharelatex")
-request = require("request")
-crypto = require("crypto")
-thirtySeconds = 30 * 1000
 DocArchive = require "./DocArchiveManager"

 module.exports = DocManager =


Remove blocking mode when no hints found.

This fixes an error in 39adee9090fc5aadfd5dd681f91b80025084858a.

Specifically, if there are no hints to select from, or no
documentElement, then link-hints mode exits without initiating an actual
mode (i.e. without calling its super() constructor).

With 39adee9090fc5aadfd5dd681f91b80025084858a, that leaves a mode in
place which blocks all keyboard events, thereby rendering Vimium
entirely hung.

See this line:

  https://github.com/philc/vimium/commit/39adee9090fc5aadfd5dd681f91b80025084858a#diff-e9abcb9ebcdb5af8e9f33651364673a1R59.

Here:

   - we explicitly remove the keyboard-blocking mode
   - we add exitOnEscape (so that the situation is at least
     recoverable), and
   - we add an indicator (so that we can see what's going on).

It is proposed that the indicator is a temporary feature, while the
global link hints feature is in shake down.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -51,6 +51,8 @@

 HintCoordinator =
   onExit: []
+  localHints: null
+  suppressKeyboardEvents: null

   sendMessage: (messageType, request = {}) ->
     Frame.postMessage "linkHintsMessage", extend request, {messageType}
@@ -59,7 +61,11 @@
     # We need to communicate with the background page (and other frames) to initiate link-hints mode.  To
     # prevent other Vimium commands from being triggered before link-hints mode is launched, we install a
     # temporary mode to block keyboard events.
-    new SuppressAllKeyboardEvents singleton: "link-hints-mode"
+    @suppressKeyboardEvents = new SuppressAllKeyboardEvents
+      name: "link-hints/suppress-keyboard-events"
+      singleton: "link-hints-mode"
+      indicator: "Launching hints..."
+      exitOnEscape: true
     @onExit = [onExit]
     @sendMessage "prepareToActivateMode", modeIndex: availableModes.indexOf mode

@@ -75,6 +81,8 @@
   # We also propagate the key state between frames.  Therefore, the hint-selection process proceeds in lock
   # step in every frame, and @linkHintsMode is in the same state in every frame.
   activateMode: ({hintDescriptors, modeIndex, originatingFrameId}) ->
+    @suppressKeyboardEvents?.exit() if @suppressKeyboardEvents?.modeIsActive
+    @suppressKeyboardEvents = null
     # Ensure that the settings are loaded.  The request might have been initiated in another frame.
     Settings.onLoaded =>
       @onExit = [] unless frameId == originatingFrameId


added transform helper method

added transform helper method

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -266,6 +266,15 @@
         @panels[0].removeEventListener transitionEnd, true
         options.callback()
       , true
+
+
+  _transform: (translation, rotation) ->
+    [x, y] = translation
+    if !rotation
+      "translate3d(#{ x }px, #{ y }px, 0)"
+    else
+      [rX, rY, rZ, deg] = rotation
+      "translate3d(#{ x }px, #{ y }px, 0) rotate3d(#{ rX }, #{ rY }, #{ rZ }, #{ deg }deg)"

   _normalizeAngle: (percent) ->


simplified number negation

simplified number negation

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -172,12 +172,12 @@
         content = panel.getElementsByClassName('oridomi-content')[0]

         if anchor is 'top'
-          y = (i - 1) * @panelHeight * -1
+          y = -((i - 1) * @panelHeight)
           if i is 1
             panel.style[transformProp] = @_transform [0, 0]
         else
-          y = ((@hPanels * @panelHeight) - (@panelHeight * i)) * -1
           panel.style[transformOriginProp] = 'bottom'
+          y = -((@hPanels * @panelHeight) - (@panelHeight * i))
           if i is 1
             panel.style[transformProp] = @_transform [0, @panelHeight * (@hPanels - 1)]
           else
@@ -224,12 +224,12 @@
         content = panel.getElementsByClassName('oridomi-content')[0]

         if anchor is 'left'
-          x = (i - 1) * @panelWidth * -1
+          x = -((i - 1) * @panelWidth)
           if i is 1
             panel.style[transformProp] = @_transform [0, 0]
         else
-          x = ((@vPanels * @panelWidth) - (@panelWidth * i)) * -1
           panel.style[transformOriginProp] = 'right'
+          x = -((@vPanels * @panelWidth) - (@panelWidth * i))
           if i is 1
             panel.style[transformProp] = @_transform [@panelWidth * (@vPanels - 1), 0]
           else


added section for helper functions

added section for helper functions

--- oridomi.coffee
+++ oridomi.coffee
@@ -7,9 +7,13 @@

 # Copyright 2013, MIT License

+'use strict'
+
+# Helper Functions
+# ================
+
 # Setup
 # =====
-'use strict'

 # Set a reference to jQuery (or another `$`-aliased DOM library).
 # If it doesn't exist, set to null so oriDomi knows we are working without jQuery.


:art: Move common code to beforeEach in editor-linter-spec

--- spec/editor-linter-spec.coffee
+++ spec/editor-linter-spec.coffee
@@ -1,9 +1,14 @@
 describe 'editor-linter', ->
   EditorLinter = require('../lib/editor-linter')
+  editorLinter = null
+  textEditor = null
   beforeEach ->
     waitsForPromise ->
       atom.workspace.destroyActivePaneItem()
-      atom.workspace.open('test.txt')
+      atom.workspace.open('test.txt').then ->
+        editorLinter?.deactivate()
+        textEditor = atom.workspace.getActiveTextEditor()
+        editorLinter = new EditorLinter(textEditor)

   describe '::constructor', ->
     it "cries when provided argument isn't a TextEditor", ->
@@ -19,8 +24,6 @@

   describe '::onShouldLint', ->
     it 'ignores instant save requests', ->
-      textEditor = atom.workspace.getActiveTextEditor()
-      editorLinter = new EditorLinter(textEditor)
       timesTriggered = 0
       editorLinter.onShouldLint ->
         timesTriggered++
@@ -30,15 +33,11 @@
       textEditor.save()
       textEditor.save()
       expect(timesTriggered).toBe(5)
-      editorLinter.deactivate()

   describe '::onDidDestroy', ->
     it 'is called when TextEditor is destroyed', ->
-      textEditor = atom.workspace.getActiveTextEditor()
-      editorLinter = new EditorLinter(textEditor)
       didDestroy = false
       editorLinter.onDidDestroy ->
         didDestroy = true
       textEditor.destroy()
-      editorLinter.deactivate()
       expect(didDestroy).toBe(true)


Added isNumber() and isNaN() to the spec helpers.

--- spec/helpers/SpecHelper.coffee
+++ spec/helpers/SpecHelper.coffee
@@ -13,3 +13,6 @@
       clientX: center.x
       clientY: center.y
     center
+
+  @isNumber: (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
+  @isNaN: (obj) -> @isNumber(obj) and window.isNaN(obj)


enable use of engines without generators

--- src/crystal/generate.coffee
+++ src/crystal/generate.coffee
@@ -287,6 +287,10 @@

 		# get engine
 		engine = output.engine or generator.engine
+		if typeof(engine) == 'string'
+			if !imports[engine] or !imports[engine].engine
+				throw new Error "Engine was not imported: #{engine}"
+			engine = imports[engine].engine

 		# get helpers
 		if helpers


Minor changes to link-hint code.

Minor changes to link-hint code.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -125,8 +125,10 @@
     marker

   #
-  # Determine whether the element is visible and clickable. If it is, return the element and the rect
-  # bounding the element in the viewport.
+  # Determine whether the element is visible and clickable. If it is, return the element and the rect bounding
+  # the element in the viewport.  There may be more than one part of element which is clickable (for example,
+  # if it's an image), therefore we return a list of element/rect pairs.
+  #
   getVisibleClickable: (element) ->
     tagName = element.tagName.toLowerCase()
     isClickable = false
@@ -148,7 +150,7 @@
     # Check aria properties to see if the element should be ignored.
     if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or
         element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"])
-      return null # This element should never have a link hint.
+      return [] # This element should never have a link hint.

     # Check for attributes that make an element clickable regardless of its tagName.
     if (element.hasAttribute("onclick") or
@@ -187,7 +189,7 @@
     if isClickable
       clientRect = DomUtils.getVisibleClientRect element
       if clientRect != null
-        visibleElements.push {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex}
+        visibleElements.push {element: element, rect: clientRect, secondClassCitizen: onlyHasTabIndex}

     visibleElements

@@ -236,10 +238,11 @@
         nonOverlappingElements.push {element: visibleElement.element, rect: rects[0]}
       else
         # Every part of the element is covered by some other element, so just insert the whole element's
-        # rect. Except for elements with tabIndex set; these are often more trouble than they're worth.
+        # rect. Except for elements with tabIndex set (second class citizens); these are often more trouble
+        # than they're worth.
         # TODO(mrmr1993): This is probably the wrong thing to do, but we don't want to stop being able to
         # click some elements that we could click before.
-        nonOverlappingElements.push visibleElement unless visibleElement.onlyHasTabIndex
+        nonOverlappingElements.push visibleElement unless visibleElement.secondClassCitizen

     nonOverlappingElements


change pattern tag

change pattern tag

--- src-client/infolis.user.coffee
+++ src-client/infolis.user.coffee
@@ -1,5 +1,5 @@
 client = new InfolinkClient(
-	baseURI: 'http://infolis.gesis.org/infolink'
+	baseURI: 'http://localhost:3000'
 )

 $('head').append(
@@ -47,16 +47,20 @@
 				onSuccess : (uri) ->
 					notie.alert(1, 'Upload to ', 0.5)
 					Bootstrap.setProgressBar('upload', 100).text("100%")
-					client.applyPatternAndResolve [uri], 'demo3', {
+					client.applyPatternAndResolve [uri], 'mda_en_test', {
 						onProgress : (ev) ->
 							if ev.percent
 								Bootstrap.setProgressBar('apar', ev.percent).text(ev.percent + "%")
 						onError: ->
 							console.error arguments
-						onSuccess : (ev) ->
+						onComplete : (execution) ->
 							notie.alert(1, 'ApplyPatternAndResolve ', 0.5)
 							Bootstrap.setProgressBar('apar', 100).text("100%")
-							$("#infolis-modal").append(JSON.stringify(ev, null, 2))
+							list = $("
")
+							console.log execution
+							for link in execution.links
+								list.append($("").attr('href', link).text(link))
+							$("#infolis-modal").append($("
Links:
")).append(list)
 					}

 infolisTo = (elem) ->


Added 11-digit codes as persons

Added 11-digit codes as persons

--- fetsy-hammertag/scripts/utils/validation.coffee
+++ fetsy-hammertag/scripts/utils/validation.coffee
@@ -7,7 +7,7 @@
 .constant 'suppliesRegex', /^\d{13}$/

-.constant 'personRegex', /^\d{8}$/
+.constant 'personRegex', /^(\d{8}|\d{11})$/

 .factory 'ValidationFactory', [


dont show view student responses button when there are no free responses:

--- src/components/task-teacher-review/exercise.cjsx
+++ src/components/task-teacher-review/exercise.cjsx
@@ -45,6 +45,7 @@
       className={freeResponsesClasses}/>

   renderFreeResponse: ->
+    return null unless @expectsFreeResponse()
     {answers} = @props
     {showAnswers} = @state
     question = @getQuestion()


rename stadiums to schools in javascript

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -1,7 +1,7 @@
 #= require master-map
 render = ->
   width       = $(document.body).width()
-  height      = 800
+  height      = 1200
   projection  = d3.geo.albersUsa().scale(1).translate [ 0, 0 ]
   path        = d3.geo.path().projection(projection)
   fill        = d3.scale.log().clamp(true).range ['#111', '#ff00ff']
@@ -10,7 +10,7 @@
   geometries  = null
   colors      = ['#a634f4', '#f1f42f', '#bcf020', '#eeb016', '#ec180c']

-  map = d3.select('body').append('svg')
+  map = d3.select('#master-map').append('svg')
     .attr('width', width)
     .attr('height', height)

@@ -53,11 +53,11 @@
       .style('stroke-width', "#{1.5 / k}px")

   $.when($.ajax('/data/counties.json'),
-         $.ajax('/data/stadiums.csv'),
+         $.ajax('/data/schools.csv'),
          $.ajax('/data/recruits.csv')).then (r1, r2, r3) ->

     usa      = r1[0]
-    stadiums = d3.csv.parse(r2[0])
+    schools = d3.csv.parse(r2[0])
     recruits = d3.csv.parse(r3[0])

     # Convert to GeoJSON
@@ -126,16 +126,16 @@
 #     .attr("class", "arc")
 #     .attr("d", path)

-    for stadium in stadiums
-      stadium.position = projection [stadium.lat, stadium.lon]
+    for school in schools
+      school.position = projection [school.lat, school.lon]

-    geometries.selectAll('.stadiums')
-      .data(stadiums)
+    geometries.selectAll('.schools')
+      .data(schools)
     .enter().append('circle')
       .attr('cx', (d) -> d.position[0])
       .attr('cy', (d) -> d.position[1])
-      .attr('r', 3)
-      .attr('class', 'stadium')
+      .attr('r', 4)
+      .attr('class', 'school')
       .classed('gt', (d) -> d.name == 'Georgia Tech')
       .on('mouseover', tip.show)
       .on('mouseout', tip.hide)


Update language.

Update language.

--- grammars/sifu.cson
+++ grammars/sifu.cson
@@ -533,6 +533,12 @@
           }
           {
             'include': '#params'
+          }
+          {
+            'include': '#loadEndpointSetting'
+          }
+          {
+            'include': '#modelDtoSetting'
           }
           {
             'match': '[^(\\s\\{\\}\\[\\]\\(\\)#,)]+'
@@ -7591,6 +7597,12 @@
             'include': '#params'
           }
           {
+            'include': '#loadEndpointSetting'
+          }
+          {
+            'include': '#modelDtoSetting'
+          }
+          {
             'include': '#secured'
           }
           {


Testing

Testing

--- widgets/cusi_progress_bars/cusi_progress_bars.coffee
+++ widgets/cusi_progress_bars/cusi_progress_bars.coffee
@@ -40,7 +40,7 @@

         elem = rowsContainer.find("."+normalizedItemName+" .inner-progress-bar")
         if elem.length
-          @animateProgressBarContent(elem[0], parseFloat(elem[0].style.width),
+          @animateProgressBarContent(elem[0], parseFloat(elem[0].style.width / 100),
                                     parseFloat(item.progress), item.value, 250)#1000)
         ++counter


shrink funcktions

shrink funcktions

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -60,41 +60,47 @@
     collections = {}
     wantedOrder = []
     for obj in resultSet
-      collections[obj.name] ||= []
+      collections[obj.name] or= []
       collections[obj.name].push obj.id
       wantedOrder.push idx: obj.id+'_'+obj.name
     collections: collections, wantedOrder: wantedOrder

+
+  attachReplies:(options, callback)->
+    tempRes = []
+    collectRelations = race (i, res, fin)=>
+      res.replies = []
+      @fetchReplies res.getId(), (err, relatedResult)=>
+        if err
+          callback err
+          fin()
+        else
+            if relatedResult.reply?
+              {collections, wantedOrder} = @getIdsFromAResultSet relatedResult.reply
+              @fetchObjectsFromMongo collections, wantedOrder, (err, dbObjects)->
+                res.replies.push obj for obj in dbObjects
+                tempRes.push res
+                fin()
+            else
+              tempRes.push res
+              fin()
+    , =>
+      {groupName, groupId} = options.group if options.group?
+
+      if groupName? and groupName is "koding"
+        @removePrivateContent client, groupId, tempRes, callback
+      else
+        callback null, tempRes
+    collectRelations
+
   runQuery:(query, options, callback)->
     {startDate, client} = options
-    {groupName, groupId} = options.group if options.group?
     @db.query query, {}, (err, results)=>
-      tempRes = []
-      if err then callback err
+      if err
+        callback err
       else if results.length is 0 then callback null, []
       else
-        collectRelations = race (i, res, fin)=>
-          res.replies = []
-          @fetchReplies res.getId(), (err, relatedResult)=>
-            if err
-              callback err
-              fin()
-            else
-                if relatedResult.reply?
-                  {collections, wantedOrder} = @getIdsFromAResultSet relatedResult.reply
-                  @fetchObjectsFromMongo collections, wantedOrder, (err, dbObjects)->
-                    res.replies.push dbObj for dbObj in dbObjects
-                    tempRes.push res
-                    fin()
-                else
-                  tempRes.push res
-                  fin()
-        , =>
-          if groupName? and groupName is "koding"
-            @removePrivateContent client, groupId, tempRes, callback
-          else
-            callback null, tempRes
-
+        collectRelations = @attachReplies(options, callback)
         resultData = []
         {collections, wantedOrder} = @getIdsFromAResultSet _.map(results, (e)->e.content.data)
         @fetchObjectsFromMongo collections, wantedOrder, (err, dbObjects)->
@@ -151,17 +157,6 @@
         filteredContent.push content if content.group not in secretGroups
       return callback null, filteredContent

-  neo4jFacets = [
-    "JLink"
-    "JBlogPost"
-    "JTutorial"
-    "JStatusUpdate"
-    "JOpinion"
-    "JDiscussion"
-    "JCodeSnip"
-    "JCodeShare"
-  ]
-
   fetchAll:(requestOptions, callback)->
     {group:{groupName, groupId}, startDate, client} = requestOptions

@@ -226,7 +221,7 @@
       limit 2
     """
     @db.query query, {}, (err, results) ->
-      if err then throw err
+      if err then callback err
       resultData = []
       for result in results
         type = result.r.type
@@ -251,7 +246,7 @@
       order by r.createdAtEpoch DESC
     """
     @db.query query, {}, (err, results) ->
-      if err then throw err
+      if err then callback err
       resultData = []
       for result in results
         type = result.r.type


To debit VM resources, we need to access bundle first. Changing permission.

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1286,7 +1286,7 @@
   createBundle$: permit 'change bundle',
     success: (client, limits, callback) -> @createBundle limits, callback

-  fetchBundle$: permit 'change bundle',
+  fetchBundle$: permit 'commission resources',
     success: (client, rest...) -> @fetchBundle rest...

   getDefaultLimits:->


workers/group: removed the duplicate method

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -749,22 +749,6 @@
       kallback()

-  fetchHomepageView: (options, callback)->
-    {account} = options
-    @fetchMembershipPolicy (err, policy)=>
-      if err then callback err
-      else
-        homePageOptions = extend options, {
-          @slug
-          @title
-          @avatar
-          @body
-          @counts
-          @customize
-        }
-        prefix = if account.type is 'unregistered' then 'loggedOut' else 'loggedIn'
-        JGroup.render[prefix].groupHome homePageOptions, callback
-
   fetchRolesByClientId:(clientId, callback)->
     [callback, clientId] = [clientId, callback]  unless callback
     return callback null, []  unless clientId


SocialWorker: remove unused callback parameter

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -702,7 +702,7 @@
         dash queue, callback

   # isInAllowedDomain checks if given email's domain is in allowed domains
-  isInAllowedDomain: (email, callback) ->
+  isInAllowedDomain: (email) ->
     # allow for all domains for koding
     return yes if @slug is 'koding'


that one was true for every delegate therefore it's unnecessary

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -384,8 +384,8 @@
         unless hasPermission
           return callback new KodingError 'Access denied.'

-    unless delegate instanceof JAccount
-      return callback new KodingError 'Access denied.'
+    #unless delegate instanceof JAccount
+    #  return callback new KodingError 'Access denied.'

     @create formData, delegate, callback


geoip: fetch timezone information

geoip: fetch timezone information

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -856,9 +856,10 @@

   getLocationInfo: do (queue=[])->

-    ip      = null
-    country = null
-    region  = null
+    ip       = null
+    country  = null
+    region   = null
+    timezone = null

     fail = ->

@@ -870,7 +871,7 @@
     (callback = noop)->

       if ip? and country? and region?
-        callback null, { ip, country, region }
+        callback null, { ip, country, region, timezone }
         return

       return  if (queue.push callback) > 1
@@ -882,13 +883,14 @@
         dataType : 'json'
         success  : (data)->

-          { ip, country_code, region_code } = data
-
-          country = country_code
-          region  = region_code
+          { ip, country_code, region_code, time_zone } = data
+
+          country  = country_code
+          region   = region_code
+          timezone = time_zone

           for cb in queue
-            cb null, { ip, country, region }
+            cb null, { ip, country, region, timezone }

           queue = []


JGroup: fetchResources: add regex search in title if searchFor provided in selector

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -969,6 +969,10 @@

       [selector, options, callback] = Module.limitEdges 10, 19, rest

+      if selector.searchFor?
+        selector.title = ///#{selector.searchFor}///
+        delete selector.searchFor
+
       ComputeProvider = require '../computeproviders/computeprovider'
       ComputeProvider.fetchGroupResources this, selector, options, callback


make test case more difficult

make test case more difficult

--- test/unit/editor.coffee
+++ test/unit/editor.coffee
@@ -252,10 +252,10 @@
     it('local change', ->
       @editor.doc.setHTML('
0123
')
       @editor.checkUpdate()
-      delta = Tandem.Delta.makeInsertDelta(5, 2, '|')
+      delta = Tandem.Delta.makeInsertDelta(5, 4, '|')
       @editor.root.querySelector('p').innerHTML = '01a23'
       @editor.applyDelta(delta)
-      expected = '
01|a23
'
+      expected = '
01a23|
'
       expect(@editor.root).toEqualHTML(expected, true)
     )
   )


Fix error caused by missing stderr on windows

--- lib/clang-format.coffee
+++ lib/clang-format.coffee
@@ -52,8 +52,11 @@

     # Call clang-format synchronously to ensure that save waits for us
     # Don't catch errors to make them visible to users via atom's UI
+    # We need to explicitly ignore stderr since there is no parent stderr on
+    # windows and node.js will try to write to it - whether it's there or not
     args = ("-#{k}=#{v}" for k, v of options).join ' '
-    stdout = execSync("#{exe} #{args}", input: editor.getText()).toString()
+    options = input: editor.getText(), stdio: ['pipe', 'pipe', 'ignore']
+    stdout = execSync("#{exe} #{args}", options).toString()

     # Update buffer with formatted text. setTextViaDiff minimizes re-rendering
     buffer.setTextViaDiff @getReturnedFormattedText(stdout)


small bug in new command logs

small bug in new command logs

--- lib/commands/new.coffee
+++ lib/commands/new.coffee
@@ -12,7 +12,7 @@
   Roots.new(path: proj_path, template: tpl_name)
     .on('template:created', ->
       console.log "new project created at #{proj_path.replace(process.cwd(), '').slice(1)}".green
-      console.log "(using #{name} template)".grey
+      console.log "(using #{tpl_name} template)".grey
     )
     .on('deps:installing', -> process.stdout.write '\ninstalling dependencies... '.grey)
     .on('deps:finished', -> process.stdout.write 'done!\n'.green)


Add babel src

Add babel src

--- gulp/config.coffee
+++ gulp/config.coffee
@@ -1,6 +1,7 @@
 config =
   src :
     coffee : "./assets-src/coffee/**/*.coffee"
+    babel  : "./assets-src/js/**/*.js"
     scss   : "./assets-src/scss/**/*.scss"
     haml   : "./**/*.haml"
     fonts  : [
@@ -30,4 +31,4 @@
       "gulp-ruby-haml" : "haml"
   })

-module.exports = config+module.exports = config


add method to upload image useing FormData and xhr

raw XHR is used vs jQuery ajax since it allows easy access to onprogress

--- src/api.coffee
+++ src/api.coffee
@@ -90,4 +90,19 @@
     httpMethod: 'PUT'
     payload: obj

-module.exports = {start}
+uploadExerciseImage = (exerciseId, image, cb) ->
+  url = "/api/exercises/#{exerciseId}/save-image"
+  xhr = new XMLHttpRequest()
+  xhr.open('POST', url, true)
+  xhr.onload = ->
+    cb(if p.currentTarget.status is 200 then {progress: 100} else {error: p.currentTarget.status})
+  xhr.onprogress = (ev) ->
+    cb(progress: ev)
+
+  form = new FormData()
+  form.append("image", image, image.name)
+  form.append("id",    exerciseId)
+  form.append("type",  file.type)
+  xhr.send(form)
+
+module.exports = {start, uploadExerciseImage}


better inner scroll area height calculation

--- src/app.coffee
+++ src/app.coffee
@@ -240,8 +240,10 @@
   $pl_window.height $lib.height()

   # make the inside containers fit
-  $("#library-items").height $lib.height() - $lib.find(".window-header").height() - MARGIN
-  $("#playlist-items").height $pl_window.height() - $pl_window.find(".window-header").height() - MARGIN
+  $lib_header = $lib.find(".window-header")
+  $("#library-items").height $lib.height() - $lib_header.position().top - $lib_header.height() - MARGIN
+  $pl_header = $pl_window.find(".window-header")
+  $("#playlist-items").height $pl_window.height() - $pl_header.position().top - $pl_header.height() - MARGIN

 $(document).ready ->
   setUpUi()


Fix typo

Fix typo

--- src/app.coffee
+++ src/app.coffee
@@ -241,7 +241,7 @@
   breakpad.get '/symfiles/:id', (req, res, next) ->
     Symfile.findById(req.params.id).then (symfile) ->
       if not symfile?
-        resturn res.send 404, 'Symfile not found'
+        return res.send 404, 'Symfile not found'

       if 'raw' of req.query
         res.set 'content-type', 'text/plain'


Render popups after unit route map init.

--- src/app.coffee
+++ src/app.coffee
@@ -247,19 +247,14 @@
                 unit.fetch
                     data: include: 'department,municipality,services'
                     success: => @selectedUnits.trigger 'reset', @selectedUnits
-        selectUnitById: (id) ->
+        renderUnitById: (id) ->
             deferred = $.Deferred()
-            unit = @getUnit id
-            if unit?
-                @selectUnit unit
-            else
-                unit = new Models.Unit id: id
-                unit.fetch
-                    data:
-                        include: 'department,municipality'
-                    success: =>
-                        @selectUnit unit
-                        deferred.resolve()
+            unit = new Models.Unit id: id
+            unit.fetch
+                data:
+                    include: 'department,municipality'
+                success: =>
+                    deferred.resolve unit
             deferred.promise()
         clearSelectedUnit: ->
             @route.clear()
@@ -562,7 +557,11 @@
         renderUnit: (path) ->
             parsedPath = @_matchResourceUrl path
             if 'id' of parsedPath
-                @controller.selectUnitById parsedPath.id
+                def = $.Deferred()
+                @controller.renderUnitById(parsedPath.id).done (unit) =>
+                    def.resolve
+                        afterMapInit: => @controller.selectUnit unit
+                def.promise()
             else if parsedPath.filters?.service?
                 @controller.renderUnitsByServices parsedPath.filters.service

@@ -578,8 +577,9 @@
             # the state encoded in the route URL has been
             # reconstructed. The state affects the map
             # centering, zoom, etc.
-            callback?.apply(@, args)?.done ->
+            callback?.apply(@, args)?.done (opts) ->
                 makeMapView()
+                opts?.afterMapInit?()

     app.addInitializer (opts) ->


fixing other playlist buttons

fixing other playlist buttons

--- src/app.coffee
+++ src/app.coffee
@@ -150,13 +150,13 @@
     $(this).removeClass "ui-state-hover"

   $pl_window = $("#playlist-window")
-  $pl_window.on 'click', 'a.clear', ->
+  $pl_window.on 'click', 'button.clear', ->
     mpd.clear()
     return false
-  $pl_window.on 'click', 'a.random1', ->
+  $pl_window.on 'click', 'button.random1', ->
     mpd.queueRandomTracks 1
     return false
-  $pl_window.on 'click', 'a.random20', ->
+  $pl_window.on 'click', 'button.random20', ->
     mpd.queueRandomTracks 20
     return false
   $pl_window.on 'click', '#dynamic-mode', ->


fix tests

fix tests

--- jsapp/xlform/src/model.survey.coffee
+++ jsapp/xlform/src/model.survey.coffee
@@ -223,9 +223,10 @@
       if !parent
         parent = @
       parent.rows.add(row, {at: index})
-      # WTF: commented this line out because it looks like BAD CODE™
-      # it would be nice to know the reason for it
-      # row._parent = parent.rows
+
+      # line below looks like BAD CODE™ but in fact it enables row reordering
+      row._parent = parent.rows
+
       if opts.event
         parent.rows.trigger(opts.event)
       return


Added a missing 'require'.

Added a missing 'require'.

--- cms/System/Kernel.coffee
+++ cms/System/Kernel.coffee
@@ -1,4 +1,5 @@
 CoffeeScript = require "coffee-script"
+spawn = require "child_process"
 fs = require "fs"

 kernelCreated = false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -61,7 +61,7 @@
   ###
   each: (selector, fn) ->
     self.find(selector).each (obj, index) ->
-      fn
+      fn(obj,)

   find: (selector) ->
     results = []


remove strange comment

remove strange comment

--- spec/mark-manager-spec.coffee
+++ spec/mark-manager-spec.coffee
@@ -1,2 +1 @@
 # TODO
-# spec for ^


Open permissions

Open permissions

--- collections/needs.coffee
+++ collections/needs.coffee
@@ -1,6 +1,6 @@
 @Needs = new Meteor.Collection("needs")

 @Needs.allow
-  insert: (userId, doc) -> userId
-  update: (userId, doc) -> userId
-  remove: (userId, doc) -> userId
+  insert: (userId, doc) -> true
+  update: (userId, doc) -> true
+  remove: (userId, doc) -> true


workers: add migration property too

workers: add migration property too

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -746,6 +746,7 @@
         'meta.views'                : 1
         'meta.votes'                : 1
         onlineStatus                : 1
+        migration                   : 1
       }

       params = { 'profile.nickname' : toBeDeletedUsername }


[DOC] Add documentation for src/frontend/webRTC.coffee

--- src/frontend/webRTC.coffee
+++ src/frontend/webRTC.coffee
@@ -15,18 +15,21 @@

 ###

+# Initalize WebRTC functions into a unique function.
 window.RTCPeerConnection = window.webkitRTCPeerConnection or window.mozRTCPeerConnection or window.RTCPeerConnection
 window.SessionDescription = window.RTCSessionDescription or window.mozRTCSessionDescription or window.RTCSessionDescription
 window.IceCandidate = window.RTCIceCandidate or window.mozRTCIceCandidate or window.RTCIceCandidate

 navigator.getUserMedia = navigator.webkitGetUserMedia or navigator.mozGetUserMedia or navigator.getUserMedia

+# Initialize WebRTC context.
 window.defaults = {
     iceServers: { "iceServers": [{ "url": "stun:stun.ekiga.net" }] },
     constraints: { 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } }
     optional: { optional: [{ RtpDataChannels: true}] }
 }

+# Check if the WebRTC functions are available.
 Runnable                = () ->
     if RTCPeerConnection? and navigator.getUserMedia?
         return true
@@ -45,6 +48,7 @@
     peerConnection.tunnel   = options.tunnel
     peerConnection.channel  = null

+    # Initialize a datachannel with callbacks.
     setDataChannel          = (channel) =>
         channel.onopen      = () =>
             if options.onChannelOpen?
@@ -66,6 +70,7 @@
             if options.onChannelError?
                 do options.onChannelError

+    # Check if datachannels are available and create one.
     createDataChannel       = () =>
         try
             if peerConnection.createDataChannel?
@@ -77,20 +82,24 @@
     if options.onoffer?
         do createDataChannel

+    # Send ice candidate.
     onicecandidate          = (event) =>
         if !event or !event.candidate or !peerConnection
             return
         if options.getice
             options.getice peerConnection.tunnel, event

+    # Add a received stream into the peerconnection.
     onaddstream             = (event) =>
         if options.gotstream?
             options.gotstream event

+    # Remove a stream.
     onremovestream          = (event) =>
         if options.removestream?
             options.removestream event

+    # Set datachannel callbacks.
     ondatachannel           = (event) =>
         setDataChannel event.channel

@@ -102,6 +111,7 @@
     if options.stream
         peerConnection.addStream options.stream

+    # Create peerConnection offers and send them to a guest.
     peerConnection.peerCreateOffer = (onoffer) =>
         if onoffer?
             peerConnection.createOffer (sessionDescription) =>
@@ -111,6 +121,7 @@
                 return
             , constraints

+    # Create answer from offer and send the answer to the guest.
     peerConnection.peerCreateAnswer = (offer) =>
         if offer?
             peerConnection.setRemoteDescription new SessionDescription(offer)
@@ -124,9 +135,11 @@
     peerConnection.peerCreateOffer options.onoffer
     peerConnection.peerCreateAnswer options.offer

+    # Set the peerConnection description on answer.
     peerConnection.onanswer         = (sdp) =>
           peerConnection.setRemoteDescription new SessionDescription(sdp)

+    # Add new ice candidate.
     peerConnection.addice           = (candidate) =>
           peerConnection.addIceCandidate(new IceCandidate {
                 sdpMLineIndex: candidate.sdpMLineIndex,
@@ -134,6 +147,7 @@
                 })
     return peerConnection

+# Get audio and video from user.
 GetUserMedia            = (options) ->
     URL = window.webkitURL or window.URL


change color scheme

change color scheme

--- src/github-calendar.coffee
+++ src/github-calendar.coffee
@@ -34,9 +34,19 @@
 	date1.getUTCFullYear() is date2.getUTCFullYear() and date1.getUTCMonth() is date2.getUTCMonth() and date1.getUTCDate() is date2.getUTCDate()

 paintGrids = (row,col, model)->
+	green = '#8cc665'
+	lightgreen = '#d6e685'
+	grassgreen = '#44a340'
+	darkgreen = '#1e6823'
 	square = paper.rect(col * 11, row * 11 + 20, 10, 10)
-	if model.commitsLength > 0
-		square.attr("fill", "green")
+	if model.commitsLength > 0 && model.commitsLength < 3
+		square.attr("fill", lightgreen)
+	else if model.commitsLength >= 3 && model.commitsLength < 6
+		square.attr("fill", green)
+	else if model.commitsLength >= 6 && model.commitsLength < 8
+		square.attr("fill", grassgreen)
+	else if model.commitsLength >= 8
+		square.attr("fill", darkgreen)
 	else
 		square.attr("fill", "#ccc")
 	square.attr("stroke-opacity", "0")


Modified random_privkey to require getRandomValues(), and use a combination of getRandomValues, Math.random() and the current time. Refs #18

--- lib/bitcoin/index.coffee
+++ lib/bitcoin/index.coffee
@@ -1,6 +1,6 @@
 { Key, Script, Address, Message, BigInteger, Opcode, Util, Crypto, convert, base58, ecdsa } = require 'bitcoinjs-lib'
 getSECCurveByName = require 'bitcoinjs-lib/src/jsbn/sec'
-{ sha256ripe160 } = Util
+{ sha256ripe160, numToBytes } = Util
 { SHA256, charenc: { UTF8 }, util: { randomBytes } } = Crypto
 { bytesToHex, hexToBytes } = convert
 { OP_HASH160, OP_EQUAL } = Opcode.map
@@ -87,14 +87,19 @@
   else get_address script.toScriptHash(), ADDR_PUB

 # Generate random private key
+#
+# Based on crypto.getRandomValues, Math.random and the current time
 random_privkey = ->
-  # Use crypto.getRandomValues() if available
-  if window.crypto?.getRandomValues?
-    random = new Uint8Array 32
-    crypto.getRandomValues random
-    Array.apply [], random
-  # And fallback to Math.random()-based randomBytes
-  else randomBytes 32
+  throw new Error 'crypto.getRandomValues() is required' unless window.crypto?.getRandomValues?
+
+  window.crypto.getRandomValues crypto_random = new Uint8Array 32
+  crypto_random = Array.apply [], crypto_random
+
+  math_random = numToBytes Math.random()*Math.pow(2,53)
+
+  time = numToBytes Date.now()
+
+  sha256b [ crypto_random..., math_random..., time... ]

 # Verify the signature matches the public key
 verify_sig = (expected_pub, message, sig) ->


cleanup and TODOs

cleanup and TODOs

--- js/inner/billing.coffee
+++ js/inner/billing.coffee
@@ -291,9 +291,6 @@
     $.getJSON @apiURL('plan'), (data) =>
       @loadPlanData data if data
       @existing_plan_loaded(true)
-      # TODO: figure out how I want to do this
-      # if @chosenPlan()
-      #   @closeWizard()

   loadOrganizations: () =>
     @loadingOrganizations = VM.current_user.loadingOrganizations


might as well fix the favicon-updater while we're at it

--- js/inner/favicon.coffee
+++ js/inner/favicon.coffee
@@ -3,15 +3,19 @@

 CI.inner.Favicon = class Favicon extends CI.inner.Obj
   constructor: (@current_page) ->
-    @current_page.subscribe (current_page) =>
-      current_page.favicon_color.subscribe (favicon_color) =>
-        @set_color(current_page.favicon_color())

-  get_color: =>
+    @color = @komp =>
+      # evaluate current_page and favicon_color to set up dependency tracking
+      if @current_page() then @current_page().favicon_color()
+
+    @color.subscribe (color) =>
+      @set_color(color)
+
+  set_color: (color) ->
+    $("link[rel='icon']").attr('href', assetPath("/favicon-#{color}.ico?v=27"))
+
+  get_color: ->
     $("link[rel='icon']").attr('href').match(/favicon-([^.]+).png/)[1]
-
-  set_color: (color) =>
-    $("link[rel='icon']").attr('href', assetPath("/favicon-#{color}.ico?v=27"))

   reset_favicon: () =>
     @set_color() # undefined resets it


Fix key filter.

Fix key filter.

--- theme/default/src/coffee/codo.coffee
+++ theme/default/src/coffee/codo.coffee
@@ -218,7 +218,7 @@

   # Allow ESC to blur #search
   key.filter = (e) ->
-    tagname = (event.target || event.srcElement).tagName
+    tagname = (e.target || e.srcElement).tagName
     tagname isnt 'INPUT' || e.keyCode is 27 || e.ctrlKey is true

   # Focus search input


commandstream: Fix throw

commandstream: Fix throw

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -511,8 +511,9 @@
     throw new Error('Missing component library')
   cmdType = cmdData.readUInt8(0)
   responseParser = responseFromCommandId[cmdType]
-  if not responseParser
-    console.log 'Unknown/unsupported command received', cmdType
+  if typeof responseParser != 'function'
+    console.log 'Unknown/unsupported command received', cmdType, typeof responseParser
+    return

   messages = responseParser componentLib, graph, cmdData
   if not messages?


EditorView is now TextEditorView.

EditorView is now TextEditorView.

--- lib/covering-view.coffee
+++ lib/covering-view.coffee
@@ -1,4 +1,4 @@
-{EditorView, View, $} = require 'atom'
+{View, $} = require 'atom'
 _ = require 'underscore-plus'
 {EditorAdapter} = require './editor-adapter'

@@ -30,7 +30,7 @@
   getModel: -> null

   reposition: ->
-    # FIXME this is a workaround for an EditorView bug.
+    # FIXME this is a workaround for a TextEditorView bug.
     # https://github.com/atom/atom/pull/3517
     @editorView.component.checkForVisibilityChange()


imoort jQuery from electron

imoort jQuery from electron

--- lib/daisy-execute.coffee
+++ lib/daisy-execute.coffee
@@ -1,19 +1,13 @@
 #will be injected in to daisy
 window.electron = true #set identificatin

+#load jquery for web app
+window.$ = window.jQuery = require('jquery');
+#load lib
 EventBus = require "./event-bus"
 DaisyInterface = require "./daisy-interface"
 DaisyIPC = require "./daisy-ipc"

+#run lib
 daisyInterface = new DaisyInterface()
 daisyIPC = new DaisyIPC()
-
-
-# setTimeout ->
-#   window.eventbus.fire "DaisyInterface","uploadFile",
-#     appName: 'EventBusTestApp'
-#     url: 'http://www.geileapp.de'
-#     data: 'DATA!'
-#     fileName: 'dateiname'
-#     type: 'type'
-# , 10*1000


Tweak usage message

Tweak usage message

--- src/cli.coffee
+++ src/cli.coffee
@@ -4,11 +4,11 @@
 Highlights = require './highlights'

 module.exports = ->
-  cli = optimist.describe('help', 'Show this message').alias('h', 'help')
-                .describe('scope', 'Scope name of the grammar to use').alias('s', 'scope')
-                .describe('output', 'File path to write HTML output to').alias('o', 'output')
+  cli = optimist.describe('h', 'Show this message').alias('h', 'help')
+                .describe('s', 'Scope name of the grammar to use').alias('s', 'scope').string('s')
+                .describe('o', 'File path to write HTML output to').alias('o', 'output').string('o')
   optimist.usage """
-    Usage: highlights file
+    Usage: highlights [options] [file]

     Output the syntax highlighted HTML for a file.


Remove extra newline

Remove extra newline

--- src/cli.coffee
+++ src/cli.coffee
@@ -11,7 +11,6 @@

     Output the syntax highlighted HTML for a file.
   """
-

   if cli.argv.help
     cli.showHelp()


Performance and experience refactor

Performance and experience refactor

--- src/beautifiers/rubocop.coffee
+++ src/beautifiers/rubocop.coffee
@@ -5,8 +5,6 @@
 "use strict"
 Beautifier = require('./beautifier')
 path = require('path')
-fs = require('fs')
-temp = require('temp').track()

 module.exports = class Rubocop extends Beautifier
   name: "Rubocop"
@@ -19,28 +17,11 @@
       rubocop_path: true
   }

-  createTempFile: (originalFile) ->
-    new @Promise((resolve, reject) =>
-      tempOptions = {
-        prefix: "_beautify",
-        suffix: path.basename(originalFile),
-        dir: path.dirname(originalFile)}
-      temp.open(tempOptions, (err, info) =>
-        return reject(err) if err?
-        @debug('rubocopTempFile', info.path)
-        resolve(info.path)
-      )
-    ).disposer((filename) =>
-      if fs.existsSync(filename)
-        @debug("unlinking rubocop temp file", filename)
-        fs.unlink(filename)
-    )
-
   beautify: (text, language, options) ->
     editor = atom?.workspace?.getActiveTextEditor()
     if editor?
       fullPath = editor.getPath()
-      projectPath = atom.project.relativizePath(fullPath)[0]
+      [projectPath, relativePath] = atom.project.relativizePath(fullPath)
     else
       throw new Error("No active editor found!")

@@ -68,21 +49,20 @@
       }
       tempConfig = @tempFile("rubocop-config", yaml.safeDump(config))

-    @Promise.using(@createTempFile(fullPath), (tempFileName) =>
-      new @Promise((resolve, reject) ->
-        fs.writeFile(tempFileName, text, 'utf8', (err) ->
-          return reject(err) if err?
-          resolve tempFileName
-        )
-      )
-      .then(=>
-        rubocopArguments = [
-          "--auto-correct"
-          "--force-exclusion"
-          tempFileName
-        ]
-        rubocopArguments.push("--config", tempConfig) if tempConfig?
-        @run(rubocopPath, rubocopArguments, {ignoreReturnCode: true, cwd: projectPath})
-      )
-      .then(=> @readFile(tempFileName))
+    rubocopArguments = [
+      "--auto-correct"
+      "--force-exclusion"
+      "--stdin", relativePath
+    ]
+    rubocopArguments.push("--config", tempConfig) if tempConfig?
+    @debug("rubocop arguments", rubocopArguments)
+
+    @run(rubocopPath, rubocopArguments, {
+      ignoreReturnCode: true,
+      cwd: projectPath,
+      onStdin: (stdin) -> stdin.end text
+    }).then((stdout) =>
+      @debug("rubocop output", stdout)
+      result = stdout.split("====================\n")
+      result[result.length - 1]
     )


Better keymap arrangement

Better keymap arrangement

--- keymaps/latextools.cson
+++ keymaps/latextools.cson
@@ -7,7 +7,13 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-".platform-win32 atom-text-editor[data-grammar='text tex latex']":
+"atom-text-editor[data-grammar~='latex']":
+  '$' : 'latextools:dollar-sign'
+  '`' : 'latextools:backquote'
+  '\'': 'latextools:quote'
+  '"' : 'latextools:double-quote'
+
+"body:not([class~='platform-darwin']) atom-text-editor[data-grammar~='latex']":
   'ctrl-l ctrl-l': 'editor:select-line'
   'ctrl-l c': 'latextools:clear-log'
   'ctrl-l escape': 'latextools:hide-ltconsole'
@@ -22,32 +28,8 @@
   'ctrl-l u': 'latextools:wrap-in-underline'
   'ctrl-l m': 'latextools:wrap-in-monospace'
   'ctrl-l .': 'latextools:close-environment'
-  '$' : 'latextools:dollar-sign'
-  '`' : 'latextools:backquote'
-  '\'': 'latextools:quote'
-  '"' : 'latextools:double-quote'

-".platform-linux atom-text-editor[data-grammar='text tex latex']":
-  'ctrl-l ctrl-l': 'editor:select-line'
-  'ctrl-l c': 'latextools:clear-log'
-  'ctrl-l escape': 'latextools:hide-ltconsole'
-  'ctrl-l shift-escape': 'latextools:show-ltconsole'
-  'ctrl-alt-b': 'latextools:build'
-  'ctrl-l j': 'latextools:jump-to-pdf'
-  'ctrl-l x': 'latextools:ref-cite-complete'
-  'ctrl-l ctrl-c': 'latextools:wrap-in-command'
-  'ctrl-l ctrl-n': 'latextools:wrap-in-environment'
-  'ctrl-l e': 'latextools:wrap-in-emph'
-  'ctrl-l b': 'latextools:wrap-in-bold'
-  'ctrl-l u': 'latextools:wrap-in-underline'
-  'ctrl-l m': 'latextools:wrap-in-monospace'
-  'ctrl-l .': 'latextools:close-environment'
-  '$' : 'latextools:dollar-sign'
-  '`' : 'latextools:backquote'
-  '\'': 'latextools:quote'
-  '"' : 'latextools:double-quote'
-
-".platform-darwin atom-text-editor[data-grammar='text tex latex']":
+"body.platform-darwin atom-text-editor[data-grammar~='latex']":
   'cmd-l cmd-l': 'editor:select-line'
   'cmd-l c': 'latextools:clear-log'
   'cmd-l escape': 'latextools:hide-ltconsole'
@@ -62,7 +44,3 @@
   'cmd-l u': 'latextools:wrap-in-underline'
   'cmd-l m': 'latextools:wrap-in-monospace'
   'cmd-l .': 'latextools:close-environment'
-  '$' : 'latextools:dollar-sign'
-  '`' : 'latextools:backquote'
-  '\'': 'latextools:quote'
-  '"' : 'latextools:double-quote'


simplyfi installer, add comments

simplyfi installer, add comments

--- lib/ewg/installer.coffee
+++ lib/ewg/installer.coffee
@@ -14,20 +14,19 @@

 module.exports = m =
   init: (theme = 'ewg-theme-default') =>
-    theme = m.themeSource theme
+    themePath = m.detectThemeSource theme

-    log.green "installing theme: #{theme}"
+    log.green "installing theme: #{themePath}"

-    unless fs.existsSync srcPath
-      fs.mkdirSync srcPath
+    # remove previous downloaded zip file in case of broken installation
+    fs.unlink localZip if fs.existsSync localZip

-    if fs.existsSync localZip
-      fs.unlink localZip
+    # download zipped theme file
+    m.download themePath, localZip

-    m.download theme, localZip
-
-  themeSource: (theme) ->
+  detectThemeSource: (theme = 'ewg-theme-default') ->
     unless theme[...4] is 'http'
+      # if theme source is no url, it is a ewg hostet theme
       theme = "https://github.com/easy-website-generator/#{theme}/archive/master.zip"

     theme
@@ -60,19 +59,19 @@
         fs.rename src, target

   install: =>
+    # search for workspace.yml in downloaded and extractet folders
     wsConfig = m.detectWsConfig(srcPath)

     config = loader.loadRawYaml(wsConfig).development

-    zipFolder = path.dirname wsConfig
+    extractedThemeFolder = path.dirname wsConfig

-    m.moveFilesToParentDirectory zipFolder
+    # copy all files from unzipped folder to workspace
+    m.moveFilesToParentDirectory extractedThemeFolder

-    rmdir zipFolder
-
-    for entry in config.template.copy_to_root
-      fs.rename("#{srcPath}/#{entry}", entry)
+    # delete unzipped folder
+    rmdir extractedThemeFolder

     for entry in config.template.run_after_copy
-      exec entry, (error, stdout, stderr) ->
+      exec entry, (error, stdout, stderr) =>
         log.log error, stdout, stderr


fix a spell error

fix a spell error

--- lib/emulator/proxy-middleware.coffee
+++ lib/emulator/proxy-middleware.coffee
@@ -28,7 +28,7 @@
           'content-type': req.headers['content-type']
         }
       , (response) ->
-        res.writeHead reponse.statusCode, response.headers
+        res.writeHead response.statusCode, response.headers
         response.pipe(res)
         response.on 'data', (data) ->
           console.log "代理转发[服务器]响应: #{data.toString()}"


repeat commands arrayport

repeat commands arrayport

--- components/Repeat.coffee
+++ components/Repeat.coffee
@@ -5,30 +5,29 @@
   icon: 'cog'

   constructor: ->
-    @command = null
+    @commands = []
     @times = null

     @inPorts =
       times: new noflo.Port 'number'
-      command: new noflo.Port 'string'
+      commands: new noflo.ArrayPort 'string'
     @outPorts =
-      command: new noflo.Port 'string'
+      command: new noflo.Port 'array'

     @inPorts.times.on 'data', (data) =>
       @times = data
       @repeat()

-    @inPorts.command.on 'data', (data) =>
-      @command = data
+    @inPorts.commands.on 'data', (data, i) =>
+      @commands[i] = data
       @repeat()

   repeat: ->
     return unless @outPorts.command.isAttached()
-    return unless @command?
+    return unless @commands.length?
     return unless @times?

-    commands = (@command for i in [0...@times])
-    console.log commands
-    @outPorts.command.send commands
+    out = (@commands for i in [0...@times])
+    @outPorts.command.send out

 exports.getComponent = -> new Repeat