Add special case for Variable#render with only one filter

Apparently bluebird switched to using the native Array#reduce function
which doesn't call the reducer if the array contains only a single
element.

--- src/liquid/variable.coffee
+++ src/liquid/variable.coffee
@@ -41,28 +41,33 @@
   render: (context) ->
     return '' unless @name?

-    reducer = (output, filter) =>
+    reducer = (input, filter) =>
       filterArgs = filter[1].map (a) -> context.get a

       Promise
-      .join(output, filterArgs...)
-      .spread (output, filterArgs...) =>
+      .join(input, filterArgs...)
+      .spread (input, filterArgs...) =>
         try
-          context.invoke filter[0], output, filterArgs...
+          context.invoke filter[0], input, filterArgs...
         catch e
           throw e unless e instanceof Liquid.FilterNotFound
           throw new Liquid.FilterNotFound("Error - filter '#{filter[0]}' in '#{@markup}' could not be found.")

-    Promise
-    .cast(context.get(@name))
-    .then (value) =>
-      Promise
-      .reduce(@filters, reducer, value)
-      .then (value) ->
-        if value instanceof Liquid.Drop
-          value.context = context
-          value.toString()
-        else
-          value
-      , (e) ->
-        context.handleError e
+    value = Promise.cast context.get(@name)
+
+    switch @filters.length
+      when 0
+        filtered = value
+      when 1
+        # Special case since Array#reduce doesn't call
+        # reducer if element has only a single element.
+        filtered = reducer value, @filters[0]
+      else
+        filtered = Promise.reduce @filters, reducer, value
+
+    filtered.then (f) ->
+      return f unless f instanceof Liquid.Drop
+      f.context = context
+      f.toString()
+    .catch (e) ->
+      context.handleError e


Simplify css methods

Simplify css methods

--- src/css.coffee
+++ src/css.coffee
@@ -4,33 +4,10 @@

 exports.addStyle = (css) ->

-	# styleSheet = document.getElementById STYLESHEET_ID
-	#
-	# if not styleSheet
-	#
-	# 	head = document.getElementsByTagName "head"
-	# 	head = head[0] if head
-	#
-	# 	if not head
-	# 		head = document.body or document.documentElement
-	#
-	# 	styleSheet = document.createElement "style"
-	# 	styleSheet.id = STYLESHEET_ID
-	#
-	# 	head.appendChild styleSheet
-	#
-	# styleSheet.appendChild document.createTextNode css
+	styleSheet = document.createElement "style"
+	styleSheet.innerHTML = css

-	# if _STYLESHEET is null
-	_STYLESHEET = document.createElement('style');
-	document.head.appendChild(_STYLESHEET);
-
-	_STYLESHEET.innerHTML += css
-
-	# style = document.createElement('style');
-	# style.innerHTML = css
-
-
+	document.head.appendChild styleSheet

 exports.addStyle "


off by ONE error

off by ONE error

--- src/dom.coffee
+++ src/dom.coffee
@@ -98,7 +98,7 @@
   setText: (node, text) ->
     switch node.nodeType
       when Scribe.DOM.ELEMENT_NODE
-        if node.textContent
+        if node.textContent?
           node.textContent = text
         else
           node.innerText = text


Updated import enzyme snippet

Updated import enzyme snippet

--- snippets/enzyme.cson
+++ snippets/enzyme.cson
@@ -2,5 +2,5 @@
   "import 'enzyme'":
     "prefix": "enzyme"
     "body": """
-      import $1 from "enzyme";
+      import {$1} from "enzyme";
       """


don't think we need this code anymore

don't think we need this code anymore

A window's cad should be constant.

--- plugins/project/project.coffee
+++ plugins/project/project.coffee
@@ -16,10 +16,7 @@
     'Command-Ctrl-N': 'toggle'

   initialize: ->
-    @dir ?= File.workingDirectory()
-
-    activeWindow.document.ace.on 'open', ({filename}) =>
-      @reload filename if File.isDirectory filename
+    @dir = File.workingDirectory()

     $('#project li').live 'click', (event) =>
       $('#project .active').removeClass 'active'
@@ -48,9 +45,8 @@

     @showing = not @showing

-  reload: (dir=@dir) ->
-    @dir = dir
-    $('#project .cwd').text _.last dir.split '/'
+  reload: ->
+$('#project .cwd').text _.last dir.split '/'
     $('#project li').remove()
     $('#project .files').append @createList dir


Add sign out

Add sign out

--- lib/app.coffee
+++ lib/app.coffee
@@ -59,6 +59,14 @@
                     logger.error "Invalid password for #{request.body.username}"
                     response.status(400).json 'Invalid username or password!'

+app.post '/signout', (request, response) ->
+    if request.session.authenticated?
+        request.session.authenticated = no
+        request.session.destroy (err) ->
+            response.json ''
+    else
+        response.status(400).json 'Not authenticated!'
+

 app.get '/identity', (request, response) ->
     identity =


Better ID handling here too

Better ID handling here too

--- src/runtimes/base.coffee
+++ src/runtimes/base.coffee
@@ -29,14 +29,14 @@
     unless @graph
       throw new Error 'No graph defined for execution'
     @sendNetwork 'start',
-      graph: @graph.properties.id
+      graph: @graph.name or @graph.properties.id

   # Stop a NoFlo network
   stop: ->
     unless @graph
       throw new Error 'No graph defined for execution'
     @sendNetwork 'stop',
-      graph: @graph.properties.id
+      graph: @graph.name or @graph.properties.id

   # Set the parent element that some runtime types need
   setParentElement: (parent) ->


remove dead code

remove dead code

--- lib/bot.coffee
+++ lib/bot.coffee
@@ -94,8 +94,6 @@
 FIND_REGEX = 'find (dashboard|look )? ?(.+)'
 GET_REGEX = 'get (.+)=(.+)'

-CLI_HELP = "pie model/view/field,another_field[filter_value],more_field desc"
-
 controller.on "slash_command", (bot, message) ->
   processCommand(bot, message)


Track page views in kissmetrics

Track page views in kissmetrics

--- js/inner.coffee
+++ js/inner.coffee
@@ -853,13 +853,19 @@

     # Google analytics
     @bind 'event-context-after', ->
+      if window._kmq?
+        _kmq.push(['record', 'Viewed ' + @path]);
       if window._gaq? # we dont use ga in test mode
         window._gaq.push @path

+
+
+
 $(document).ready () ->
   SammyApp.run window.location.pathname.replace(/(.+)\/$/, "$1")
-  _kmq.push(['identify', VM.current_user().login])
+  if window._kmq?
+    _kmq.push(['identify', VM.current_user().login])


Add route for knowing the IP where the bot is host

--- src/scripts/httpd.coffee
+++ src/scripts/httpd.coffee
@@ -15,6 +15,7 @@
 #   /hubot/ping
 #   /hubot/time
 #   /hubot/info
+#   /hubot/ip

 spawn = require('child_process').spawn

@@ -35,3 +36,7 @@
     child.stdout.on 'data', (data) ->
       res.end "#{data.toString().trim()} running node #{process.version} [pid: #{process.pid}]"
       child.stdin.end()
+
+  robot.router.get "/hubot/ip", (req, res) ->
+    robot.http('http://checkip.dyndns.org').get() (err, r, body) ->
+      res.end body


remove websocket fn

remove websocket fn

--- app/builtins.coffee
+++ app/builtins.coffee
@@ -264,14 +264,4 @@
     Context.add_component ctx, nested_context.component
     Context.apply_to nested_context, fn

-  fn 'websocket', 'Runs commands from a web socket', (ctx, url) ->
-    ws = new WebSocket url
-    ctx.async ->
-      ws.onopen = => ctx.text 'Connected'
-      ws.onclose = =>
-        ctx.text 'Closed. Reconnect:'
-        ctx.example "websocket #{JSON.stringify url}"
-      ws.onmessage = (e) => ctx.run e.data
-      ws.onerror = => ctx.error 'Error'
-
   {help_component, ExampleComponent, PromiseStatusComponent, ComponentAndError, PromiseResolvedComponent, ErrorComponent}


Fix infinite loading loop in new devices

--- lib/session.coffee
+++ lib/session.coffee
@@ -79,6 +79,7 @@
       sessionData.new = true
       path = sessionData.deviceData.paths.add(['*'])
       sessionData.deviceData.loaded.set(path, 99)
+      delete sessionData.toLoad
       return callback()

     for p in loaded
@@ -124,7 +125,7 @@
             sessionData.deviceData.values.exist.set(p, 1, 0)
             sessionData.deviceData.values.object.set(p, 0, 0)

-    sessionData.toLoad = null
+    delete sessionData.toLoad
     return callback()
   )

@@ -768,6 +769,7 @@

 loadPath = (sessionData, path) ->
+  return true if sessionData.new
   tl = []
   for i in [path.length...0] by -1
     p = path.slice(0, i)


Fix product update

Fix product update

--- public/collections/cart.coffee
+++ public/collections/cart.coffee
@@ -17,6 +17,7 @@
     Backbone.sync("create", this, timeout: 10 * 1000)

   sync: ->
+    amount = 0
     @deliveryPrice = undefined # reset to avoid render previous delivery prices
     Backbone.sync("create", this, timeout: 10 * 1000)
       .done (response) =>
@@ -24,9 +25,13 @@
           product = _(@models).chain().pluck('attributes').flatten().findWhere(productId: p.productId).value()
           if product.quantity != p.quantity.amount
             localStorage.setItem('epages-shop-quantity-change', 1)
+          else
+            localStorage.setItem('epages-shop-quantity-change', 0)
           product.lineItemPrice = p.lineItemPrice.formatted
           product.quantity = p.quantity.amount
+          amount += p.quantity.amount

+        $('.epages-shop-cart span').html(amount)
         @subTotal = response.lineItemContainer.lineItemsSubTotal.formatted
         @deliveryPrice = response.lineItemContainer.shippingPrice.formatted
         shippingMethod = (response.shippingData || {}).shippingMethod


Add more documentation

Add more documentation

--- src/smc-util/misc.coffee
+++ src/smc-util/misc.coffee
@@ -554,12 +554,17 @@

 # Expects a comma or semicolon separated list of strings and/or email addresses
 # If an email address is included, only searces for the email address
-# Accepts the following examples
-# Firstname lastname, Email@something.com; ... ("firstname lastname" will be included in string_queries)
-# Firstname lastname Email@something.com, ... (Will ignore "firstname lastname")
-# First Last <EmailAddress@email.com>, ...
-# Firstname Lastname, ...
-# <JustAnEmail@mail.com>; ...
+#
+# Accepts the following example terms
+#     Firstname lastname, Email@something.com; ... ("firstname lastname" will be included in string_queries)
+#     Firstname lastname Email@something.com, ... (Will ignore "firstname lastname")
+#     First Last <EmailAddress@email.com>, ...
+#     Firstname Lastname, ...
+#     <JustAnEmail@mail.com>; ...
+#
+# Will SILENTLY REJECT the following terms:
+#    <Email@noEndingBracket, ...
+#    Email@NoStartingBracket>, ...
 #
 # returns an object with the queries in lowercase
 #    string_queries: ["firstname lastname"]


Fix spelling error

Fix spelling error

--- lib/git.coffee
+++ lib/git.coffee
@@ -76,7 +76,7 @@

 tag = (repo, oid) ->
   new Promise (resolve, reject) ->
-    repo.createLightWeightTag oid, "#{oid}-tag"
+    repo.createLightweightTag oid, "#{oid}-tag"
     .then (reference) ->
       resolve reference
     .catch (err) ->


Fixed typo (wasn't working)

Fixed typo (wasn't working)

--- src/zombie/window.coffee
+++ src/zombie/window.coffee
@@ -174,7 +174,7 @@
   # Evaulate in context of window. This can be called with a script (String) or a function.
   window._evaluate = (code, filename)->
     # Surpress JavaScript validation and execution
-    if !browser.runScript
+    if !browser.runScripts
       return

     try


run: emit error and end

run: emit error and end

--- lib/run.coffee
+++ lib/run.coffee
@@ -102,14 +102,11 @@
               disregard_done = true
             , middleware.timeout
           run()
-        .then (err) =>
-          console.log "ERROR on #{ctx.config.host}" if err
-          next()
-          # @emit 'server', ctx, err
-          # if err
-          # then (ctx.emit 'error', err if ctx.listeners('error').length)
-          # else if params.end is true then ctx.emit 'end'
-          # next err
+        .then (err) ->
+          if err
+          then ctx.emit 'error', err
+          else ctx.emit 'end'
+          next err
     .then (err) =>
       if err
       then @emit 'error', err


Always focus the first visible field

Always focus the first visible field

--- components/inquiry_questionnaire/views/step.coffee
+++ components/inquiry_questionnaire/views/step.coffee
@@ -1,5 +1,6 @@
 _ = require 'underscore'
 Backbone = require 'backbone'
+{ isTouchDevice } = require '../../util/device.coffee'

 module.exports = class StepView extends Backbone.View
   __events__: null
@@ -21,12 +22,18 @@
     e.preventDefault()
     @state.trigger 'abort'

+  autofocus: ->
+    unless isTouchDevice()
+      _.defer =>
+        @$el.find('input:visible, textarea:visible').first().focus()
+
   render: ->
     @$el.html @template
       user: @user
       state: @state
       artwork: @artwork
     @postRender()
+    @autofocus()
     this

   postRender: -> #


Test for file-not-found in Client#readFile.

--- test/src/client_test.coffee
+++ test/src/client_test.coffee
@@ -329,6 +329,16 @@
         expect(stat.isFile).to.equal true
         bytes = (buffer.readUInt8(i) for i in [0...buffer.length])
         expect(bytes).to.deep.equal @imageFileBytes
+        done()
+
+    it 'reports non-existing files correctly', (done) ->
+      @client.readFile @textFile + '-not-found', (error, text, stat) =>
+        expect(text).not.to.be.ok
+        expect(stat).not.to.be.ok
+        expect(error).to.be.instanceOf Dropbox.ApiError
+        unless Dropbox.Util.Xhr.ieXdr  # IE's XDR doesn't do error codes.
+          expect(error.status).to.equal Dropbox.ApiError.NOT_FOUND
+          expect(error.url).to.contain '-not-found'
         done()

     describe 'with an onXhr listener', ->


test/system/discover: return if disable_system_tmpfs

--- test/system/discover.coffee
+++ test/system/discover.coffee
@@ -7,7 +7,7 @@

   @timeout 30000
   config = test.config()
-  return if config.disable_discover
+  return if config.disable_system_tmpfs

   they 'they detect OS type and release', (ssh, next) ->
     mecano


Add Git to parent process path

Add Git to parent process path

--- src/git.coffee
+++ src/git.coffee
@@ -52,9 +52,8 @@
     globalconfig: config.getGlobalConfigPath()
   npm.load npmOptions, ->
     git = npm.config.get('git') ? 'git'
-    env = _.extend({}, process.env)
-    exports.addGitToEnv(env) if process.platform is 'win32'
-    spawned = spawn(git, ['--version'], {env})
+    exports.addGitToEnv(process.env) if process.platform is 'win32'
+    spawned = spawn(git, ['--version'])
     outputChunks = []
     spawned.stderr.on 'data', (chunk) -> outputChunks.push(chunk)
     spawned.stdout.on 'data', (chunk) -> outputChunks.push(chunk)


remove os argument that is no longer used

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -289,23 +289,22 @@
         }
     tmpl

-templateWindow = (viewstate, os) ->
-    tmpl = [
-        {
-            label: 'Minimize'
-            accelerator: acceleratorMap['minimize'][os]
-            selector: 'performMiniaturize:'
-        }, {
-            label: 'Close'
-            accelerator: acceleratorMap['close'][os]
-            selector: 'performClose:'
-        }, {
-            type: 'separator'
-        }, {
-            label: 'Bring All to Front',
-            selector: 'arrangeInFront:'
-        }
-    ]
+templateWindow = (viewstate) -> [
+    {
+        label: 'Minimize'
+        accelerator: acceleratorMap['minimize'][os]
+        selector: 'performMiniaturize:'
+    }, {
+        label: 'Close'
+        accelerator: acceleratorMap['close'][os]
+        selector: 'performClose:'
+    }, {
+        type: 'separator'
+    }, {
+        label: 'Bring All to Front',
+        selector: 'arrangeInFront:'
+    }
+]

 templateMenu = (viewstate) ->
     tmpl = [{


Allow to overwrite the name of the attribute we're trying to compare with

--- src/validators.js.coffee
+++ src/validators.js.coffee
@@ -136,11 +136,12 @@
   constructor: ( attr, opts ) ->
     super attr, opts
     @attribute = attr
+    @confirmed_attribute = @options.confirmed_attribute || @attribute + "_confirmation"
     @options.message ?= I18n.t("errors.messages.confirmed")

   run: ( obj ) ->
     value           = obj.get( @attribute )
-    confirmed_value = obj.get( @attribute + "_confirmation" )
+    confirmed_value = obj.get( @confirmed_attribute )
     return unless value? and ( value + "" ).length > 0
     unless value is confirmed_value
       obj.addError @attribute, @options.message


Better checking of adapter arguments

Better checking of adapter arguments

--- test/adapter_test.coffee
+++ test/adapter_test.coffee
@@ -84,8 +84,8 @@
           .send(payload)
           .end ->
             expect(stub).callCount(payload.length)
-            for obj in payload
-              expect(stub).calledWith("people", obj, {}, sinon.match.func)
+            for obj, i in payload
+              expect(stub.getCall(i)).calledWith("people", obj, {}, sinon.match.func)
             done()

   describe "with DELETE enabled", ->
@@ -172,8 +172,8 @@
           .send(payload)
           .end ->
             expect(stub).callCount(payload.length)
-            for obj in payload.zip(ids)
-              expect(stub).calledWith("people", obj[1], obj[0], {update: true}, sinon.match.func)
+            for obj, i in payload.zip(ids)
+              expect(stub.getCall(i)).calledWith("people", obj[1], obj[0], {update: true}, sinon.match.func)
             done()

   describe "with PATCH enabled", ->
@@ -244,7 +244,7 @@

             expect(update_stub).callCount(records.length)

-            for obj in records.zip(ids)
-              expect(update_stub).calledWith("people", obj[1], obj[0], {update: true}, sinon.match.func)
-
-            done()
+            for obj, i in records.zip(ids)
+              expect(update_stub.getCall(i)).calledWith("people", obj[1], obj[0], {update: true}, sinon.match.func)
+
+            done()


Tentando utilizar o logger no azure

Tentando utilizar o logger no azure

--- app/app.coffee
+++ app/app.coffee
@@ -22,10 +22,10 @@
 app.use bodyParser.json()
 app.use bodyParser.urlencoded extended: true

-if app.get('env') == 'development'
-    app.use (err, req, res, next) ->
-        res.status err.status || 500
-        res.render 'error', { message: err.message, error: err }
+#if app.get('env') == 'development'
+app.use (err, req, res, next) ->
+    res.status err.status || 500
+    res.render 'error', { message: err.message, error: err }

 load './app/controllers', verbose: true
 	.then './app/routes', verbose: true


Apply changes from BWC

Apply changes from BWC

--- app/app.coffee
+++ app/app.coffee
@@ -26,13 +26,16 @@
     # Mediator is a global message broker which implements pub / sub pattern.
     @initMediator()

-    # Register all routes and start routing.
+    # Register all routes.
     # You might pass Router/History options as the second parameter.
     # Chaplin enables pushState per default and Backbone uses / as
     # the root per default. You might change that in the options
     # if necessary:
     # @initRouter routes, pushState: false, root: '/subdir/'
     @initRouter routes
+
+    # Actually start routing.
+    @startRouting()

     # Freeze the application instance to prevent further changes.
     Object.freeze? this


always show Appknox in all places

always show Appknox in all places

--- app/app.coffee
+++ app/app.coffee
@@ -12,6 +12,8 @@
     " | Appknox"
   else if config.isDevknox
     " | Devknox"
+  else
+    " | Appknox"

 installIntercom()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -21,7 +21,7 @@

   equals player.I.x, 8

-
+test ""

 module()


Extend map bounds to accommodate islands

--- src/map.coffee
+++ src/map.coffee
@@ -10,7 +10,7 @@
     PUBLIC_TRANSIT_STOPS_MIN_ZOOM_LEVEL = 14

     getMaxBounds = (layer) ->
-        L.latLngBounds L.latLng(59.4, 23.8), L.latLng(61.5, 25.8)
+        L.latLngBounds L.latLng(59.1, 23.6), L.latLng(61.5, 25.8)

     wmtsPath = (style, language) ->
         stylePath =


mmd: refactor analyze node

mmd: refactor analyze node

--- src/mmd.coffee
+++ src/mmd.coffee
@@ -28,13 +28,12 @@
   root.children

 analyzeNode = (parent_type, node) ->
+  this_type = parent_type.getChild node.tag
   children = node.getchildren()
-  this_type = parent_type.getChild node.tag

   if children.length is 0
     this_type.addValue node.text
   else
-    for child in children
-      analyzeNode this_type, child
+    analyzeNode this_type, child for child in children

 module.exports = analyze


dashIndexFilePath is not necessary for a Docset

--- test/plist-reader.coffee
+++ test/plist-reader.coffee
@@ -10,7 +10,6 @@
             data.should.have.property 'CFBundleIdentifier'
             data.should.have.property 'CFBundleName'
             data.should.have.property 'DocSetPlatformFamily'
-            data.should.have.property 'dashIndexFilePath'
             data.should.have.property 'isDashDocset', true
         files = fs.readdirSync 'Docsets'
         promises = []


add #googleMapsLink to metadata

add #googleMapsLink to metadata

--- classify/metadata.coffee
+++ classify/metadata.coffee
@@ -9,12 +9,14 @@

   setSubject: (subject) -> @subject = subject

+  googleMapsLink: (lat, long) ->
+    "https://www.google.com/maps/@#{lat},#{long},12z'>#{@roundTo(3, lat)} N, #{@roundTo(3, long)} W"
+
   load: ->
     [lat, long] = @subject.coords
     timestamp = @subject.metadata.timestamp
-    $("#subject-coords").html """
-      #{@roundTo(3, lat)} N, #{@roundTo(3, long)} W, #{@formattedTimestamp(timestamp)}
-    """
+    $("#subject-coords")
+      .html "<a target='_tab' href='#{@googleMapsLink(lat, long)}', #{@formattedTimestamp(timestamp)}"

   init: ->
     $(".readymade-classification-interface")


updating some stuff to coresspond with the backend

--- src/Game.coffee
+++ src/Game.coffee
@@ -6,16 +6,16 @@
     @textBox = new TextInput(game)
     @player  = new Player(game)

-    ### TODO get this working with the server
-    @socket  = io.connect('link to server socket')
+    @socket  = io.connect('https://snapgame.herokuapp.com/')
+    @socket.emit  'new player'
+
     @socket.on 'snap', (data) =>
       # update appropriate word for snap
-    ###

   create: ->
     console.log @game

-  sendWords: ->
-    @socket.emit 'words', @player.words
+  sendWord: (word) ->
+    @socket.emit 'new word', word

 module.exports = Game


Fall back to empty document if window state cannot be deserialized

If the telepath serialization version has changed it will return null
from Site::deserializeDocument.

--- src/atom.coffee
+++ src/atom.coffee
@@ -231,7 +231,7 @@
     {site, document} = windowState ? {}
     if site? and document?
       window.site = telepath.Site.deserialize(site)
-      window.site.deserializeDocument(document)
+      window.site.deserializeDocument(document) ? window.site.createDocument({})
     else
       window.site = new telepath.Site(1)
       window.site.createDocument({})


Document darwin check

Document darwin check

--- src/atom.coffee
+++ src/atom.coffee
@@ -212,6 +212,7 @@
   #   :height - The new height.
   #   :maximized - A {Boolean} for the maximized window state.
   setWindowDimensions: ({x, y, width, height, maximized}) ->
+    # Maximized state only applies on Windows and Linux
     if maximized and process.platform isnt 'darwin'
       @maximize()
     else


Show console when window error happened, fixes #1805.

--- src/atom.coffee
+++ src/atom.coffee
@@ -133,6 +133,7 @@
   initialize: ->
     window.onerror = =>
       @openDevTools()
+      @executeJavaScriptInDevTools('InspectorFrontendAPI.showConsole()')
       @emit 'uncaught-error', arguments...

     @unsubscribe()
@@ -383,6 +384,10 @@
   toggleDevTools: ->
     ipc.sendChannel('call-window-method', 'toggleDevTools')

+  # Public: Execute code in dev tools.
+  executeJavaScriptInDevTools: (code) ->
+    ipc.sendChannel('call-window-method', 'executeJavaScriptInDevTools', code)
+
   # Public: Reload the current window.
   reload: ->
     ipc.sendChannel('call-window-method', 'restart')


Call getLoadPackages() instead of referencing ivar

--- src/atom.coffee
+++ src/atom.coffee
@@ -53,7 +53,7 @@

   watchThemes: ->
     @themes.on 'reloaded', =>
-      pack.reloadStylesheets?() for name, pack of @loadedPackages
+      pack.reloadStylesheets?() for name, pack of @getLoadedPackages()
       null

   open: (options) ->


Set sdk features object default

Set sdk features object default

--- src/auth.coffee
+++ src/auth.coffee
@@ -78,8 +78,7 @@

 # Return an authed SDK object for use with a single authorized user
 TeamSnap::auth = (token, options) ->
-  if options?.features?
-    this.features = options.features
+  this.features = options?.features? || {}

   if typeof token is 'function'
     callback = token


Remove Moi

Remove Moi

--- src/client.cjsx
+++ src/client.cjsx
@@ -17,7 +17,6 @@

-            Moi
             <CodeEditor />


changed signature of off() to allow event type as only argument

--- assets/javascripts/app/timeline/event-dispatcher.coffee
+++ assets/javascripts/app/timeline/event-dispatcher.coffee
@@ -1,5 +1,5 @@
 define [], () ->
-
+
   class EventDispatcher

     callbacks = {}
@@ -9,19 +9,20 @@
         callbacks[eventType] = []
       callbacks[eventType].push(callback)

-    off: (eventType, callback) ->
+    off: (eventType, callback=null) ->
       if @hasListener(eventType)
-        index = callbacks[eventType].indexOf(callback)
-        if index > -1
-          callbacks[eventType].splice(index, 1)
+        if callback?
+          index = callbacks[eventType].indexOf(callback)
+          if index > -1
+            callbacks[eventType].splice(index, 1)
+        else
+          callbacks[eventType] = null

     hasListener: (eventType) ->
       callbacks[eventType] and callbacks[eventType].length > 0

     dispatchEvent: (eventType, args) ->
-      #console.log "looking for #{eventType} in", callbacks
       if @hasListener(eventType)
-        #console.log 'callback found'
         callback.apply(null, [args]) for callback in callbacks[eventType]

     removeAllListeners: ->


remove tracking scroll events in frontend

--- client/Intro/home.coffee
+++ client/Intro/home.coffee
@@ -116,18 +116,6 @@
       offset    :
         top     : 25
         left    : 25
-
-    new MixpanelScrollTracker
-      attribute: 'section',
-      event: '/ scroll to',
-      markers: [
-        { position: 362,  value: 'Teamwork screenshot'    }
-        { position: 627,  value: 'Feature explanation'    }
-        { position: 1495, value: 'Activity screenshot'    }
-        { position: 1995, value: 'Enterprise explanation' }
-        { position: 2270, value: 'Enterprise contact'     }
-        { position: 2864, value: 'Scrolled to bottom'     }
-      ]

   pistachio:->


Workspace: Set username option passed to `loadWorkspace`

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -216,4 +216,6 @@

         findWorkspace params, (workspace) ->

+          params.username = KD.nick()
+
           loadWorkspace params, workspace


Extend the round function and add randomImage

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -146,6 +146,14 @@
 Utils.randomNumber = (a=0, b=1) ->
 	# Return a random number between a and b
 	Utils.mapRange Math.random(), 0, 1, a, b
+
+Utils.randomImage = (layer, offset=50) ->
+
+	width = Utils.round(layer.width, 0, 100, 100)
+	height = Utils.round(layer.height, 0, 100, 100)
+
+	# return "https://source.unsplash.com/category/nature/#{width}x#{height}"
+	return "https://unsplash.it/#{width}/#{height}?image=#{layer.id + offset}"

 Utils.defineEnum = (names = [], offset = 0, geometric = 0) ->
 	Enum = {}
@@ -290,8 +298,6 @@
 	(/safari/).test(navigator.userAgent.toLowerCase())

 Utils.isTouch = ->
-	# This needs to be a little more extensive because we
-	# patch ontouchstart to fake Hammer
 	window.ontouchstart is null and
 	window.ontouchmove is null and
 	window.ontouchend is null
@@ -373,9 +379,16 @@
 ######################################################
 # MATH FUNCTIONS

-Utils.round = (value, decimals=0) ->
-	d = Math.pow 10, decimals
-	Math.round(value * d) / d
+Utils.round = (value, decimals=0, increment=null, min=null, max=null) ->
+
+	d = Math.pow(10, decimals)
+
+	value = Math.round(value / increment) * increment if increment
+	value = Math.round(value * d) / d
+
+	return min if min and value < min
+	return max if max and value > max
+	return value

 Utils.clamp = (value, a, b) ->


fix formatter

fix formatter

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -13,7 +13,8 @@
   triggerDefaultOptions:
     autocompleter: Autocompleter
     formatter: (data)->
-      $('').text(data.name).prepend("#{data.trigger}")
+      $trigger = $("").text(data.trigger)
+      $('').text(data.name).prepend($trigger)
     serializer: (data)->
       "[#{data.trigger}#{data.name}](#{data.triggerOptions.triggerName}:#{data.value})"


remove global reference used for debugging

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -26,8 +26,6 @@

     @_setupElements()
     @_setupEvents()
-
-    window.foo = @

   handleInput: (e)=>
     charCode = e.charCode || e.which || e.keyCode


Add support for timezone in chron

Add support for timezone in chron

--- src/cron.coffee
+++ src/cron.coffee
@@ -13,7 +13,11 @@
         """
         Tried to fetch deploy status for app #{name} in #{env} but ran into error:
         #{err.toString()}
+
+        This job has been disabled, you need to restart #{robot.name} or enable the job by telling me "deploy-status:auto on for #{name} in #{env}"
         """
+      robot.logger.info "Unscheduling deploy check for app #{name} in #{env} after an error"
+      unscheduleEnv(name, env)
     else
       if res.noDeployments()
         robot.logger.info "App #{name} does not have deployments in #{env}."
@@ -27,20 +31,22 @@
   config: config.environments
   environments: Object.keys(config.environments)
   room: config.room
+  timezone: config.timezone

 unscheduleEnv = (name, env) ->
   activeJobs[name]?[env]?.stop()
   activeJobs[name]?[env] = undefined

 scheduleEnv = (robot, name, app, env) ->
-  {config, room} = parseConfig(app.check_deploy_status)
+  {config, room, timezone} = parseConfig(app.check_deploy_status)
   activeJobs[name] ?= {}
   if !config[env]
     throw new Error('No configuration for that environment')
   robot.logger.info "Scheduling deploy nagger for #{name} in #{env} (#{config[env]})"
   try
-    activeJobs[name][env] = new CronJob(config[env], -> runNagger(robot, room, name, env))
-    activeJobs[name][env].start()
+    runner = ->
+      runNagger(robot, room, name, env)
+    activeJobs[name][env] = new CronJob(config[env], runner, undefined, true, timezone)
   catch e
     throw new Error('Invalid cron pattern in configuration: ' + config[env])


Don't get stack traces in production.

Don't get stack traces in production.

Getting stack traces make additional requests for JavaScript files and
thus additional bandwidth consumption.

--- lib.coffee
+++ lib.coffee
@@ -521,7 +521,7 @@
     if not meta.abstract
       meta._name = name # "name" is a reserved property name on functions in some environments (eg. node.js), so we use "_name"
       # For easier debugging and better error messages
-      meta._location = StackTrace.getCaller()
+      meta._location = if CODE_MINIMIZED then '<code_minimized>' else StackTrace.getCaller()
       meta.document = @

       if meta.collection is null or meta.collection


md5 is now a multimethod that will hash using binary instead of utf-8 when fed a Buffer

--- src/hash.coffee
+++ src/hash.coffee
@@ -1,7 +1,14 @@
 Crypto = require "crypto"
+{Method} = require "fairmont-multimethods"
+{isBuffer, isString} = require "./type"

-md5 = (string) ->
-  Crypto.createHash('md5').update(string, 'utf-8').digest("hex")
+md5 = Method.create()
+
+Method.define md5, isString
+  (string) -> Crypto.createHash('md5').update(string, 'utf-8').digest("hex")
+
+Method.define md5, isBuffer,
+  (buffer) -> Crypto.createHash('md5').update(buffer, 'binary').digest("hex")

 base64 = (string) ->
   new Buffer(string).toString('base64').replace(/\=+$/, '')


Ace: use KD._isLoggedIn for fallback condition check

--- client/Ace/AppController.coffee
+++ client/Ace/AppController.coffee
@@ -11,7 +11,7 @@
     openWith      : "lastActive"
     behavior      : "application"
     preCondition  :
-      condition   : (options, cb)-> cb KD.isLoggedIn()
+      condition   : (options, cb)-> cb KD.isLoggedIn() or KD._isLoggedIn
       failure     : (options, cb)->
         KD.singletons.appManager.open 'Ace', conditionPassed : yes
         KD.showEnforceLoginModal()


payment: improve isValid input logic

payment: improve isValid input logic

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -36,7 +36,8 @@

   observeForm: ->

-    isVisible = (key) => @form.inputs[key]?.getOption('cssClass') isnt 'hidden'
+    isVisible = (key) =>
+      if input = @form.inputs[key] then input.getOption('cssClass') isnt 'hidden' else no

     Object.keys(@form.inputs).filter(isVisible).map (name) =>


builder: create social models schema ol' way if not found in config file

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -1,31 +1,32 @@
-fs           = require 'fs'
-vfs          = require 'vinyl-fs'
-path         = require 'path'
-gloh         = require 'gloh'
-defined      = require 'defined'
-bant         = require 'bant'
-JSONStream   = require 'JSONStream'
-through      = require 'through2'
-coffeeify    = require 'coffeeify'
-pistachioify = require 'pistachioify'
-uglifyify    = require 'uglifyify'
-xtend        = require 'xtend'
-async        = require 'async'
-inspect      = require('util').inspect
-pretty       = require 'pretty-bytes'
-exorcist     = require 'exorcist'
-asStream     = require 'as-stream'
-notifier     = require 'node-notifier'
-mkdirp       = require 'mkdirp'
-nub          = require 'nub'
-spritesmith  = require 'gulp.spritesmith'
-chalk        = require 'chalk'
-chokidar     = require 'chokidar'
-events       = require 'events'
-nib          = require 'nib'
-stylus       = require 'gulp-stylus'
-concat       = require 'gulp-concat'
-throttle     = require 'throttleit'
+fs            = require 'fs'
+vfs           = require 'vinyl-fs'
+path          = require 'path'
+gloh          = require 'gloh'
+defined       = require 'defined'
+bant          = require 'bant'
+JSONStream    = require 'JSONStream'
+through       = require 'through2'
+coffeeify     = require 'coffeeify'
+pistachioify  = require 'pistachioify'
+uglifyify     = require 'uglifyify'
+xtend         = require 'xtend'
+async         = require 'async'
+inspect       = require('util').inspect
+pretty        = require 'pretty-bytes'
+exorcist      = require 'exorcist'
+asStream      = require 'as-stream'
+notifier      = require 'node-notifier'
+mkdirp        = require 'mkdirp'
+nub           = require 'nub'
+spritesmith   = require 'gulp.spritesmith'
+chalk         = require 'chalk'
+chokidar      = require 'chokidar'
+events        = require 'events'
+nib           = require 'nib'
+stylus        = require 'gulp-stylus'
+concat        = require 'gulp-concat'
+throttle      = require 'throttleit'
+child_process = require 'child_process'

 JS_OUTFILE                  = 'bundle.js'
 THIRDPARTY_OUTDIR           = 'thirdparty'
@@ -57,21 +58,20 @@
       outdir    : defined opts.outdir, process.cwd()
       baseurl   : defined opts.baseurl, '/'
       defaults  : defined opts.globalsFile, {}
-      cfg       : defined opts.configFile, {}
+      config    : defined opts.configFile, {}
       manifests : defined opts.use, []

     opts.basedir = path.resolve __dirname, opts.basedir
     opts.outdir = path.resolve __dirname, opts.outdir

-    if 'string' is typeof opts.cfg
-      opts.cfg = require @_resolve(opts.cfg)
-
-    opts.config = defined opts.cfg.config, {}
-    opts.schema = defined opts.cfg.schema, {}
-    opts.rev    = defined opts.config.version, '2.0'
+    if 'string' is typeof opts.config
+      opts.config = require @_resolve(opts.config)
+
+    opts.rev    = defined opts.config.rev, '2.0'
+    opts.schema = defined opts.config.schema, null

     if opts.revId
-      opts.outdir = path.join opts.outdir, opts.rev
+      opts.outdir  = path.join opts.outdir, opts.rev
       opts.baseurl = "#{opts.baseurl}/#{opts.rev}"

     opts.spriteTmpCssOutdir = @_resolve SPRITES_TMPDIR
@@ -113,6 +113,24 @@
     pending = 0

     dirs = [ opts.outdir ]
+
+    getModels = =>
+      if opts.schema is null and 'object' is typeof opts.config
+        @_time "#{chalk.blue('config')}: write bongo schema to #{opts.configFile}"
+        child_process.exec "node #{__dirname}/get-bongo-schema.js", (err, res) =>
+          throw err if err
+          opts.schema = JSON.parse res
+          ws = fs.createWriteStream opts.configFile
+          s = JSONStream.stringifyObject()
+          s.pipe ws
+          s.write [ 'rev', opts.rev ]
+          s.write [ 'schema', opts.schema ]
+          s.end()
+          ws.on 'finish', =>
+            @_timeEnd "#{chalk.blue('config')}: write bongo schema to #{opts.configFile}"
+            @_build()
+      else
+        @_build()

     getFiles opts.manifests, opts.basedir,
       (err, files) =>
@@ -144,7 +162,7 @@
               createDirs dirs, =>
                 opts.manifests = manifests
                 opts.rsManifests = rsManifests
-                @_build()
+                getModels()
             cb()

           s.pipe(parse).pipe(tr)


Collaboration: Fixed reauth timer.

Collaboration: Fixed reauth timer.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -769,7 +769,7 @@
       @rtm.isReady = yes
       @emit 'RTMIsReady'

-      KD.utils.repeat 60 * 55, => @rtm.reauth()
+      KD.utils.repeat 60 * 55 * 1000, => @rtm.reauth()
       @rtm.on 'ReauthSucceed', (token) =>
         log 'reauth succeed, replace existing token with', token


Fix instant notifications

Fix instant notifications

--- src/smc-webapp/desktop_app.cjsx
+++ src/smc-webapp/desktop_app.cjsx
@@ -59,6 +59,7 @@
             cookie_warning    : rtypes.bool
             show_file_use     : rtypes.bool
         file_use :
+            file_use         : rtypes.immutable.Map
             get_notify_count : rtypes.func
         account :
             get_fullname : rtypes.func


Clean up Gulpfile

Clean up Gulpfile


remove old cjsx

remove old cjsx

--- tutor/src/components/student-dashboard/index.cjsx
+++ tutor/src/components/student-dashboard/index.cjsx
@@ -1,46 +0,0 @@
-React = require 'react'
-{CourseStore} = require '../../flux/course'
-{StudentDashboardStore, StudentDashboardActions} = require '../../flux/student-dashboard'
-LoadableItem = require '../loadable-item'
-isStepComplete = (step) -> step.is_completed
-StudentDashboard = require './dashboard'
-WindowHelpers = require '../../helpers/window'
-{NotificationActions} = require 'shared'
-{default: TermsModal} = require '../terms-modal'
-
-StudentDashboardShell = React.createClass
-  displayName: 'StudentDashboardShell'
-
-  propTypes:
-    params: React.PropTypes.object.isRequired
-
-  statics:
-    # Called before the router mounts and renders the component
-    # Will display the redirect screen if course is a concept coach one
-    willTransitionTo: (transition, params, query, callback) ->
-      {courseId} = params
-      course = CourseStore.get(courseId)
-      if course
-        {is_concept_coach, webview_url} = course
-        if is_concept_coach and webview_url
-          transition.redirect('viewStudentCCRedirect', {courseId})
-      else
-        NotificationActions.display(message: 'That is not a valid course', icon: 'exclamation-circle')
-        transition.redirect('dashboard')
-      callback()
-
-  render: ->
-    {params} = @props
-    {courseId} = params
-
-    <div className='student-dashboard '>
-      <TermsModal />
-      <LoadableItem
-        id={courseId}
-        store={StudentDashboardStore}
-        actions={StudentDashboardActions}
-        renderItem={ -> <StudentDashboard params={params} courseId={courseId}/> }
-      />
-    </div>
-
-module.exports = {StudentDashboardShell}


View: Added test for options.

View: Added test for options.

--- src/tests/core/test_view.coffee
+++ src/tests/core/test_view.coffee
@@ -30,6 +30,11 @@

     view            = new spark.core.View options
     element         = view.getElement()
+
+
+  it 'should has default options if no options passed', ->
+    v = new spark.core.View
+    expect(v.getOptions()).not.toBe {}

   it 'should create a DOM element with passed options', ->


added support flag to constructor

added support flag to constructor

--- maskew.coffee
+++ maskew.coffee
@@ -167,6 +167,8 @@

   @VERSION: '0.1.0'

+  @isSupported: hasSupport
+

 $ = window.jQuery? or window.$?.data?
 if $


Run iframe in sandbox, fixes noflo/noflo-ui#99

--- src/runtimes/iframe.coffee
+++ src/runtimes/iframe.coffee
@@ -19,6 +19,7 @@

   setParentElement: (parent) ->
     @iframe = document.createElement 'iframe'
+    @iframe.setAttribute 'sandbox', 'allow-scripts'
     parent.appendChild @iframe

   connect: ->
@@ -45,6 +46,7 @@
     window.addEventListener 'message', @onMessage, false

   updateIframe: =>
+    return
     return if !@iframe or !@graph
     body = @iframe.contentDocument.querySelector 'body'
     return unless body


pac updated.

pac updated.

--- pac.coffee
+++ pac.coffee
@@ -16,16 +16,7 @@
 		blocked_by_company:
 			ways: 'aliyun direct'
 			list: [
-				'tmall.com'
-				'amazon.*'
-				'jd.com'
-				'paipai.com'
-				'meituan.com'
-				'taobao.com'
-				'appspot.com'
-				'360buy.com'
-				'hjav.org'
-				'yyv.co'
+				'qq.com'
 			]
 		blocked_by_gfw:
 			ways: 'shadowsocks direct'


Add /admin to first time startup message URL

--- bin/www.coffee
+++ bin/www.coffee
@@ -69,7 +69,7 @@
 bind = if typeof port == 'string' then "Pipe #{port}" else "Port #{port}"

 console.log chalk.cyan 'SiseTV is now running'
-console.log chalk.cyan "Visit #{chalk.magenta 'http://localhost:' + app.get 'port'} to see it"
+console.log chalk.cyan "Visit #{chalk.magenta 'http://localhost:' + app.get 'port'}/admin to see it"


Revert "render the usual error page now that errors will be reported"

This reverts commit 42ca01c7aa5a4d54970c09b9ef35d463752de2f8.

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -2054,6 +2054,9 @@
             info  : info

     render: ->
+        # This is way worse than nothing, because it surpresses reporting the actual error to the
+        # backend!!!  I'm disabling it completely.
+        return @props.children
         if @state.info?
             <Alert
                 bsStyle = 'warning'


webapp/wizard: basic functionality works, all more sophisticated features are missing

--- src/smc-webapp/wizard.cjsx
+++ src/smc-webapp/wizard.cjsx
@@ -40,7 +40,7 @@

 # the json from the server, where the entries for the documents are
 # double-nested objects (two hiearchies of categories) mapping to title/code/description documents
-data = null
+DATA = null

 # react wizard
 {React, ReactDOM, redux, Redux, Actions, Store, rtypes, rclass} = require('./smc-react')
@@ -53,37 +53,96 @@
 class WizardStore extends Store

 class WizardActions extends Actions
-    get_store: =>
-        @redux.getStore(@name)
     get: (key) =>
-        @get_store().get(key)
+        @redux.getStore(@name).get(key)
     set: (update) =>
         @setState(update)
     show: (lang='sage') =>
-        @set(show: true, lang: lang)
-    hide: () =>
+        @set
+            show: true
+            lang: lang
+    reset: ->
+        @set
+            cat0: null
+            cat1: null
+            cat2: null
+            catlist0 : []
+            catlist1 : []
+            catlist2 : []
+    hide: () ->
         @set(show: false)
+    init: (lang='sage') =>
+        @reset()
+        @show(lang=lang)
+        @load_data()
+    init_data: (data) =>
+        @set(data: data)
+        nav_entries = []
+        for key in _.keys(data)
+            entry = switch key
+                        when 'gap'
+                            ['gap', 'GAP']
+                        else
+                            [key, key[0].toUpperCase() + key[1..]]
+            if _.keys(data[key]).length > 0
+                nav_entries.push(entry)
+        @set(nav_entries: nav_entries)
+        @select_lang(@get('lang'))
     insert: (cb) =>
+        # this is the essential task of the wizard:
+        # call the callback with the selected code snippet
         cb
             code  : 'code'
             lang  : @get('lang')
+            title : 'title'
             descr : 'description'
     load_data: () =>
-        if not data?
+        if not DATA?
             require.ensure [], =>
-                data = require('wizard/wizard.json')
-                @set(data: data)
-                nav_entries = []
-                for key in _.keys(data)
-                    entry = switch key
-                                when 'gap'
-                                    ['gap', 'GAP']
-                                else
-                                    [key, key[0].toUpperCase() + key[1..]]
-                    nav_entries.push(entry)
-                @set(nav_entries: nav_entries)
+                # DATA is a global variable!
+                DATA = require('wizard/wizard.json')
+                @init_data(DATA)
         else
-            @set(data: data)
+            @init_data(DATA)
+    select_lang: (lang) =>
+        @reset()
+        @set
+            lang     : lang
+            catlist0 : @get('data').get(lang).keySeq().toArray()
+    set_selected_category: (level, selected, idx) =>
+        console.log 'category_selection', level, selected, idx
+        lang = @get('data').get(@get('lang'))
+        switch level
+            when 0, 1
+                @set(code: '', descr: '', cat2 : null)
+        switch level
+            when 0
+                catlist1 = lang.get(selected).keySeq().toArray()
+                @set
+                    cat0     : selected
+                    cat1     : null
+                    cat2     : null
+                    catlist1 : catlist1
+                    catlist2 : []
+                if catlist1.length == 1
+                    @set_selected_category(1, catlist1[0], 0)
+            when 1
+                cat0     = @get('cat0')
+                catlist2 = lang.getIn([cat0, selected]).map((el) -> el.get(0)).toArray()
+                @set
+                    cat1     : selected
+                    cat2     : null
+                    catlist2 : catlist2
+                if catlist2.length == 1
+                    @set_selected_category(2, catlist2[0], 0)
+            when 2
+                cat0 = @get('cat0')
+                cat1 = @get('cat1')
+                doc  = lang.getIn([cat0, cat1, idx])
+                @set
+                    cat2  : idx
+                    code  : doc.getIn([1, 0])
+                    descr : doc.getIn([1, 1])

 WizardHeader = rclass
     displayName : 'WizardHeader'
@@ -93,25 +152,25 @@
         lang        : rtypes.string.isRequired
     langSelect: (key) ->
         #console.log 'language selected', key
-        @props.actions.set(lang:key)
+        @props.actions.select_lang(key)
     search: () ->
         console.log 'search:', @refs.search.getValue()
     render_nav : ->
-        if @props.nav_entries?
-            navs = []
-            for entry in @props.nav_entries
-                [key, name] = entry
-                navs.push(<NavItem key={key} eventKey={key} title={name}>{name}</NavItem>)
-            return navs
+        entries = @props.nav_entries
+        if entries?
+
+                {entries.map (entry, idx) =>
+                        [key, name] = entry
+                        <NavItem key={key} eventKey={key} title={name}>{name}</NavItem>
+                }
+
         else
             return <Loading />
     render : ->
         <Row>
             <Col sm={3}>
<Icon name='magic' /> Wizard
</Col>
             <Col sm={5}>
-
-                    {@render_nav()}
-
+                {@render_nav()}
             </Col>
             <Col sm={3}>
                 <Input ref='search'
@@ -127,38 +186,47 @@
     propTypes:
         actions  : rtypes.object
         data     : rtypes.object
-        cat0     : rtypes.number
-        cat1     : rtypes.number
         lang     : rtypes.string.isRequired
-    category_selection: (level, idx) ->
-        console.log 'category_selection', level, idx
+        code     : rtypes.string
+        descr    : rtypes.string
+        cat0     : rtypes.string
+        cat1     : rtypes.string
+        cat2     : rtypes.number
+        catlist0 : rtypes.arrayOf(rtypes.string)
+        catlist1 : rtypes.arrayOf(rtypes.string)
+        catlist2 : rtypes.arrayOf(rtypes.string)
+    category_selection: (level, selected, idx) ->
+        @props.actions.set_selected_category(level, selected, idx)
     category_list: (level) ->
-        items = []
-        for i in [1..20]
-            active = props["cat#{level}"] == i
-            do (i, active) ->
-                items.push <ListGroupItem key={i} active={active}>entry {i}</ListGroupItem>
-
-        <ListGroup>
-            {items}
-        </ListGroup>
-    doc_list: ->
-        <ListGroup>
-            <ListGroupItem href="#" active>A 1</ListGroupItem>
-            <ListGroupItem href="#">A 2</ListGroupItem>
-            <ListGroupItem href="#" disabled>A 3</ListGroupItem>
-        </ListGroup>
+        cat  = @props["cat#{level}"]
+        list = @props["catlist#{level}"]
+        if not list?
+            list = []
+        # don't use ListGroup & ListGroupItem with onClick, because then there are div/buttons (instead of ul/li) and layout is f'up
+

+            {list.map (name, idx) =>
+                click  = @category_selection.bind(this, level, name, idx)
+                # level 0 and 1 by name, level 2 by index
+                comp   = if level == 2 then idx else name
+                active = if comp == cat then 'active' else ''
+
{name}

+            }
+

     render : ->
         if @props.data?
             <Modal.Body className='modal-body'>
                 <Row>
                     <Col sm={3}>{@category_list(0)}</Col>
                     <Col sm={3}>{@category_list(1)}</Col>
-                    <Col sm={6}>{@doc_list()}</Col>
+                    <Col sm={6}>{@category_list(2)}</Col>
                 </Row>
                 <Row>
-                    <Col sm={6}>

</Col>
-                    <Col sm={6}>

</Col>
+                    <Col sm={6}>
+
{@props.code}

+                    </Col>
+                    <Col sm={6}>
+                        <Panel ref='descr' className='smc-wizard-descr'>{@props.descr}</Panel>
+                    </Col>
                 </Row>
             </Modal.Body>
         else
@@ -172,15 +240,21 @@
         "#{name}" :
             show        : rtypes.bool
             lang        : rtypes.string
+            code        : rtypes.string
+            descr       : rtypes.string
             data        : rtypes.object
             search      : rtypes.string
-            nav_entries : rtypes.array
+            nav_entries : rtypes.arrayOf(rtypes.arrayOf(rtypes.string))
+            catlist0    : rtypes.arrayOf(rtypes.string)
+            catlist1    : rtypes.arrayOf(rtypes.string)
+            catlist2    : rtypes.arrayOf(rtypes.string)
+            cat0        : rtypes.string
+            cat1        : rtypes.string
+            cat2        : rtypes.number

     propTypes :
         cb      : rtypes.func
         actions : rtypes.object.isRequired
-        cat0    : rtypes.number
-        cat1    : rtypes.number

     getInitialState : ->
         search  : ''
@@ -194,34 +268,33 @@
                <WizardHeader actions={@props.actions} lang={@props.lang} nav_entries={@props.nav_entries} />
             </Modal.Header>

-            <WizardBody actions={@props.actions}
-                        lang={@props.lang}
-                        cat0={@props.cat0}
-                        cat1={@props.cat1}
-                        data={@props.data} />
+            <WizardBody actions    = {@props.actions}
+                        lang       = {@props.lang}
+                        code       = {@props.code}
+                        descr      = {@props.descr}
+                        cat0       = {@props.cat0}
+                        cat1       = {@props.cat1}
+                        cat2       = {@props.cat2}
+                        catlist0   = {@props.catlist0}
+                        catlist1   = {@props.catlist1}
+                        catlist2   = {@props.catlist2}
+                        data       = {@props.data} />

             <Modal.Footer>
                 <Button onClick={@props.actions.hide}>Cancel</Button>
-                <Button onClick={=> @props.actions.insert(@props.cb)}>Insert Code</Button>
+                <Button onClick={=> @props.actions.insert(@props.cb)} bsStyle='success'>Insert Code</Button>
             </Modal.Footer>
         </Modal>

-render = (name, lang, cb) ->
-    if not redux.getActions(name)?
+exports.render_wizard = (target, project_id, path, lang = 'sage', cb = null) ->
+    name = redux_name(project_id, path)
+    actions = redux.getActions(name)
+    if not actions?
         actions = redux.createActions(name, WizardActions)
         store   = redux.createStore(name)
+    actions.init(lang=lang)
     W = RWizard(name)
-    actions.show(lang=lang)
-    <Redux redux={redux}>
-        <W cb={cb} actions={actions}/>
-    </Redux>
-
-exports.render_wizard = (target, project_id, path, lang = 'sage', cb = null) ->
-    # console.log 'render_wizard: ', project_id, path, lang, cb
-    name = redux_name(project_id, path)
-    ReactDOM.render(render(name, lang, cb), target)
-    actions = redux.getActions(name)
-    actions.load_data()
+    ReactDOM.render(<Redux redux={redux}><W cb={cb} actions={actions}/></Redux>, target)
     return actions

 # old wizard code


protect team updates (security hole)

protect team updates (security hole)

--- nodeko.coffee
+++ nodeko.coffee
@@ -71,20 +71,21 @@
 # update team
 put '/teams/:id', ->
   Team.first @param('id'), (error, team) =>
-    team.joyent_count: or 0
-    team.update @params.post
-    save: =>
-      team.save (errors, result) =>
-        if errors?
-          @errors: errors
-          @team: team
-          @render 'teams/edit.html.haml'
-        else
-          @redirect '/teams/' + team.id()
-    # TODO shouldn't need this
-    if @params.post.emails
-      team.setMembers @params.post.emails, save
-    else save()
+    @ensurePermitted team, =>
+      team.joyent_count: or 0
+      team.update @params.post
+      save: =>
+        team.save (errors, result) =>
+          if errors?
+            @errors: errors
+            @team: team
+            @render 'teams/edit.html.haml'
+          else
+            @redirect '/teams/' + team.id()
+      # TODO shouldn't need this
+      if @params.post.emails
+        team.setMembers @params.post.emails, save
+      else save()

 # delete team
 del '/teams/:id', -> # delete not working


2014-03-17

--- tasks/sass-format.coffee
+++ tasks/sass-format.coffee
@@ -26,6 +26,48 @@
       config = grunt.config('sassFormat')

       files = config.files
+
+      errMsg = []
+      okMsg = []
+      indentChar = '\t'
+      indentStep = 1
+
+
+      #インデントチェック
+      #!!要改良!!
+      indentCheck =
+        (indent,txt)->
+          indent = indent
+          text = txt
+
+          console.log indent
+
+          if indent is 0
+            if /^[ \t]{1,}/.test(text)
+              errMsg.push('インデントあり')
+
+          else
+            #タブ
+            if /^[^ \t]+/.test(text)
+              errMsg.push('インデント無し')
+              #msg += 'インデント無し'
+            if /^[\t]{1,}/.test(text)
+              okMsg.push('インデントがタブ')
+              #msg += 'インデントがタブ'
+            #ホワイトスペース
+            if /^[ ]{1,}/.test(text)
+              okMsg.push('インデントがホワイトスペース')
+              #msg += 'インデントがホワイトスペース'
+
+            #インデント個数チェック
+            regex = new RegExp('^(['+indentChar+']{'+indentStep+','+indentStep+'}){1,}[^ \t]')
+
+            if regex.test(text)
+              okMsg.push('インデント'+indentStep+'個')
+              #msg += 'インデント'+indentStep+'個'
+            else
+              errMsg.push('インデント指定通りじゃない')
+              #msg += 'インデント指定通りじゃない'

       ## Iterate over all specified file groups.
       #this.files.forEach(
@@ -47,7 +89,7 @@
             text = []
             line = 1
             indent = 0
-            msg = ''
+            #msg = ''

             fs.readFileSync(f).toString().split('\n').forEach(
               (txt) ->
@@ -57,68 +99,71 @@
             #console.log text

             for i in [0..text.length-1]
+              errMsg = []
+              okMsg = []
+
               #セレクタ記述
               if /^([ \t]+)?.*[{,]([ \t]+)?$/.test(text[i])
                 #grunt.log.warn 'start:'+indent+'===================='

+                # セレクタ前インデントチェック
+                indentCheck(indent,text[i])
+
                 # セレクタ後スペース有無チェック
                 if /[^ ][ ][{,]$/.test(text[i])
+                  okMsg.push('スペース有り')
                   #msg += 'スペース有り'
-                  msg += ''
+                  #msg += ''
                 if /[^ ][ ]{2,}[{,]$/.test(text[i])
+                  errMsg.push('スペース多い')
                   #msg += '!!スペース多い!!'
-                  msg += ''
+                  #msg += ''
                 if /[^ ][{,]$/.test(text[i])
+                  errMsg.push('スペースなし')
                   #msg += '!!スペースなし!!'
-                  msg += ''
+                  #msg += ''

                 # セレクタ並び方チェック
                 if /^[^,]+[{,]$/.test(text[i])
+                  okMsg.push('1行1プロパティ')
                   #msg += '1行1プロパティ'
-                  msg += ''
+                  #msg += ''
                 if /^([^,]+,){1,}[^,{]+{$/.test(text[i])
+                  errMsg.push('1行に複数ある')
                   #msg += '!!1行に複数ある!!'
-                  msg += ''
+                  #msg += ''

                 # セレクタ記述終了
                 if /^([ \t]+)?.*{([ \t]+)?$/.test(text[i])
                   indent++

-                console.log line+': '+msg+': '+text[i]
-                msg = ''
+                if grunt.log.wordlist(okMsg).length > 0
+                  grunt.log.ok grunt.log.wordlist(okMsg,{color:'green'})
+                if grunt.log.wordlist(errMsg).length > 0
+                  grunt.log.error grunt.log.wordlist(errMsg,{color:'red'})
+                console.log line+': '+text[i]
+                #msg = ''

               #セレクタ記述
               if /^[^;]+;$/.test(text[i])

-                #インデントチェック
-                #!!要改良!!
-
-                #タブ
-                if /^[^\t ]{1,}/.test(text[i])
-                  msg += 'インデント無し'
-                if /^[\t]{1,}/.test(text[i])
-                  msg += 'インデントがタブ'
-                #ホワイトスペース
-                if /^[ ]{1,}/.test(text[i])
-                  msg += 'インデントがホワイトスペース'
-
-                #インデント個数チェック
-                num = 1
-                regex = new RegExp('^([ \t]{'+num+','+num+'}){1,}[^ \t]')
-
-                if regex.test(text[i])
-                  msg += 'インデント'+num+'個'
-                else
-                  msg += 'インデント指定通りじゃない'
+
+                indentCheck(indent,text[i])

                 #コロンの後のスペース
                 if /: /.test(text[i])
-                  msg += 'スペース有り'
+                  okMsg.push('コロンの後にスペース有り')
+                  #msg += 'スペース有り'
                 if /:[^ ]/.test(text[i])
-                  msg += 'スペース無し'
-
-
-                console.log line+': '+msg+': '+text[i]
+                  errMsg.push('コロンの後にスペース無し')
+                  #msg += 'スペース無し'
+
+
+                if grunt.log.wordlist(okMsg).length > 0
+                  grunt.log.ok grunt.log.wordlist(okMsg,{color:'green'})
+                if grunt.log.wordlist(errMsg).length > 0
+                  grunt.log.error grunt.log.wordlist(errMsg,{color:'red'})
+                console.log line+': '+text[i]
                 msg = ''

               line++


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/approachable.coffee
+++ test/approachable.coffee
@@ -97,4 +97,7 @@

   equals obj.I.age, 1, "should successfully update"

+# TODO: tests to make sure value isn't overwritten if the instance variable already exists
+# TODO: tests to make sure value is defaulted to 100, but only if the instance variable doesn't exist
+
 module()


remove test

remove test

wrong file, removing

--- tests/rename_spec.coffee
+++ tests/rename_spec.coffee
@@ -1,5 +1,2 @@
 require('chai').should()

-describe 'rename.getNames', ->
-  it 'should convert root name to camel case', ->
-    'foo'.should.equal 'bar'


proper redirect after login/user creation

--- client/admin_login.coffee
+++ client/admin_login.coffee
@@ -8,15 +8,23 @@
     e.preventDefault()
     email = $('input[name="email"]').val()
     password = $('input[name="password"]').val()
+
+    afterLogin = (error) ->
+      # TODO error case that properly displays
+      if error
+        alert error
+      else
+        Router.go 'home'
+
     if Meteor.users.findOne('profile.admin': true)
-      Meteor.loginWithPassword(email, password)
+      Meteor.loginWithPassword email, password, afterLogin
     else
-      # TODO: unbreak if this fails
-      Accounts.createUser
+      Accounts.createUser {
         email: $('input[name="email"]').val()
         password: $('input[name="password"]').val()
         profile:
           admin: true
+      }, afterLogin

   'click .logout': (e) ->
     e.preventDefault()


#641 `v * n` then `.` repeat in different editor throw error

Because Motion::Search did not correctly clear this.operator prop at
clone timing.

As I wrote FIXME: memo, current cloning/manually reset/overriding
property approach is bug-prone.
Should fix. in future…

--- lib/base.coffee
+++ lib/base.coffee
@@ -152,9 +152,13 @@
   newInputUI: ->
     new Input(@vimState)

+  # FIXME: This is used to clone Motion::Search to support `n` and `N`
+  # But manual reseting and overriding property is bug prone.
+  # Should extract as search spec object and use it by
+  # creating clean instance of Search.
   clone: (vimState) ->
     properties = {}
-    excludeProperties = ['editor', 'editorElement', 'globalState', 'vimState']
+    excludeProperties = ['editor', 'editorElement', 'globalState', 'vimState', 'operator']
     for own key, value of this when key not in excludeProperties
       properties[key] = value
     klass = this.constructor


make visits array optional

make visits array optional

--- imports/api/vessels/vessels.coffee
+++ imports/api/vessels/vessels.coffee
@@ -41,6 +41,7 @@
 		optional: true
 	visits:
 		type: Array
+		optional: true
 	'visits.$': new SimpleSchema
 		userId:
 			type: String


Fix grunt "build:client" task for windows.

Normally it would scream with errors and generate a whole bunch of
"undefined....file.coffee" everywhere in the tower folder. Plus it
didn't build the client javascript file. This fixes it.

--- packages/tower-tasks/tasks/index.coffee
+++ packages/tower-tasks/tasks/index.coffee
@@ -322,7 +322,7 @@
         data = data.replace(/module\.exports\s*=.*\s*/g, "")
         data + "\n\n"
         if level == 1
-          outputPath = './dist/' + nodePath.resolve(path, '..').split('/').pop()
+          outputPath = ".#{_path.sep}dist#{_path.sep}" + nodePath.resolve(path, '..').split(_path.sep).pop()
           fs.writeFileSync outputPath + '.coffee', data
           mint.coffee data, bare: false, (error, result) ->
             # result = JS_COPYRIGHT + result


check obsolete settings name also

check obsolete settings name also

--- test/dummy/spec/javascripts/spec/files.js.coffee
+++ test/dummy/spec/javascripts/spec/files.js.coffee
@@ -123,6 +123,7 @@
     runs ->
       expect(file.state()).toBe('rejected')

+
   it "should passes if validator added after", ->
     file = null
     runs ->
@@ -140,3 +141,23 @@

     runs ->
       expect(file.state()).toBe('resolved')
+
+
+  it "should work with obsolete settings name", ->
+
+    file = null
+    runs ->
+      mocks.use 'jsonp'
+      mocks.jsonp.addHandler /\/info\/$/, (url, data) ->
+        doc.info
+
+      file = uploadcare.fileFrom('uploaded', doc.uuid, {
+        __validators: [imagesOnly],
+      })
+
+    waitsFor ->
+      file.state() isnt 'pending'
+    , "successfuly created", 100
+
+    runs ->
+      expect(file.state()).toBe('rejected')


Removing unnecessary event handling on highlights control.

--- client/publication.coffee
+++ client/publication.coffee
@@ -798,20 +798,6 @@

     return # Make sure CoffeeScript does not return anything

-  'mousedown .add-access, mouseup .add-access': (e, template) ->
-    # A special case to prevent defocus after click on the input box
-    e.stopPropagation()
-    return # Make sure CoffeeScript does not return anything
-
-  'focus .add-access': (e, template) ->
-    $(template.firstNode).parents('.meta-menu').addClass('displayed')
-    return # Make sure CoffeeScript does not return anything
-
-  'blur .add-access': (e, template) ->
-    $(template.firstNode).parents('.meta-menu').removeClass('displayed')
-    $('.viewer .display-wrapper .highlights-layer .highlights-layer-highlight').trigger 'highlightControlBlur', [@_id]
-    return # Make sure CoffeeScript does not return anything
-
 resizeAnnotationsWidth = ($annotationsList) ->
   padding = parseInt($('.annotations-control').css('right'))
   displayWrapper = $('.display-wrapper')


render @props.peacemarker

render @props.peacemarker

--- client/src/sidebar.coffee
+++ client/src/sidebar.coffee
@@ -13,6 +13,8 @@
                 searchdata: null
             SidebarHeading
                 homedata: @props.homedata
+            DbListing
+                peacemarker: @props.peacemarker

@@ -214,7 +216,15 @@

-# DbListing = React.createFactory React.createClass
+DbListing = React.createFactory React.createClass
+
+    render: ->
+        console.log @props
+
+        div id: "bookmark-list", className: 'listings',
+
+            Peacemarker peacemarker: @props.peacemarker, 'text'
+
 #
 #     getInitialState: ->
 #         return placesdata: @props.placesdata
@@ -252,32 +262,29 @@
 #
 #
 #
-# # Le composant qui va définir une ligne de placesdata.
-# Placesdata = React.createFactory React.createClass
-#
-#     render: ->
-#         div id: 'sidebar-places',
-#             div id: 'sidebar-places-item', className: 'item',
-#                 p {className: "title"},
-#                     "#{@state.title}"
-#                     br null, null
-#                 span {className: "mygps"},
-#                     "Lat: #{@state.ilat} Lng: #{@state.ilng}"
-#                     br null, null
-#                 span className: 'irl',
-#                     "#{@state.address} "
-#                     "#{@state.postalCode} "
-#                     "#{@state.city}, "
-#                     "#{@state.state} "
-#                     "#{@state.zip} "
-#                     "#{@state.country}"
-#
-#                     div id: 'infow',
-#                 p {className: 'kontact'},
-#                 # coffeelint: disable=max_line_length
-#                     a href: @state.website, target: '_blank', @state.website + ' '
-#                     a href: "mailto:@state.email", target: '_top', @state.email + ' '
-#                     a href: "callto:@state.phone", target: '_top', @state.phone
-#                     # coffeelint: enable=max_line_length
-#                 span {className: 'tag'},
-#                     "TAG: #{@state.tag}"
+Peacemarker = React.createFactory React.createClass
+
+    render: -> null
+        # div id: 'sidebar-places',
+        #     div id: 'sidebar-places-item', className: 'item',
+        #         p {className: "title"},
+        #             "#{@state.title}"
+        #             br null, null
+        #         span {className: "mygps"},
+        #             "Lat: #{@state.lat} Lng: #{@state.lng}"
+        #             br null, null
+        #         span className: 'irl',
+        #             "#{@state.address} "
+        #             "#{@state.postalCode} "
+        #             "#{@state.city}, "
+        #             "#{@state.state} "
+        #             "#{@state.zip} "
+        #             "#{@state.country}"
+        #
+        #             div id: 'infow',
+        #         p {className: 'kontact'},
+        #             # coffeelint: disable=max_line_length
+        #             a href: @state.website, target: '_blank', @state.website + ' '
+        #             a href: "mailto:@state.email", target: '_top', @state.email + ' '
+        #             a href: "callto:@state.phone", target: '_top', @state.phone
+        #             # coffeelint: enable=max_line_length


WriteStream Finish

WriteStream Finish

The ending event is apparently `finish` not `end`.

--- bin/kdc-plus.coffee
+++ bin/kdc-plus.coffee
@@ -72,7 +72,7 @@
       log "Error Saving Compiled KDApp: #{err.message}"
       process.exit 1

-    destination.on 'end', ->
+    destination.on 'finish', ->
       log "KDApp Compiled Successfully!"

     loader.pipe destination


Attrs with hyphens are not special

Attrs with hyphens are not special

--- grammars/marko.cson
+++ grammars/marko.cson
@@ -78,13 +78,13 @@
     begin: "(?:\\s+|[,])(?:(ref)|([a-zA-Z][a-zA-Z]*)|(data-[a-zA-Z0-9-]*)|([_a-zA-Z0-9][a-zA-Z0-9:_.-]*))\\s*([=])\\s*"
     beginCaptures:
       "1":
-        name: "support.function.marko-attribute"
+        name: "entity.other.attribute-name.html"
       "2":
         name: "entity.other.attribute-name.html"
       "3":
         name: "entity.other.attribute-name.html"
       "4":
-        name: "support.function.marko-attribute"
+        name: "entity.other.attribute-name.html"
       "5":
         name: "punctuation.separator.key-value.html"
     end: "(?=[,;]|\\s|/>|>)"
@@ -115,7 +115,7 @@
       "2":
         name: "entity.other.attribute-name.html"
       "3":
-        name: "support.function.marko-attribute"
+        name: "entity.other.attribute-name.html"
     match: "\\s+(?:([a-zA-Z][a-zA-Z]*)|(data-[a-zA-Z0-9-]*)|([a-zA-Z0-9][a-zA-Z0-9:_.-]+))(?=\\s|/>|>|$)"
   "attr-semicolon":
     applyEndPatternLast: 1
@@ -503,7 +503,7 @@
       }
     ]
   "expression-hex":
-    match: "\\b0[xX]\\h+\\b"
+    match: "\\b0[xX][0-9A-Fa-f]+\\b"
     name: "constant.numeric.hex.js"
   "expression-no-gt":
     comment: "A JavaScript expression"
@@ -558,7 +558,7 @@
     name: "string.quoted.double.js"
     patterns: [
       {
-        match: "\\\\(x\\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)"
+        match: "\\\\(x[0-9A-Fa-f]{2}|[0-2][0-7]{0,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)"
         name: "constant.character.escape.js"
       }
       {
@@ -568,7 +568,7 @@
   "expression-string-escapes":
     patterns: [
       {
-        match: "\\\\(x\\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)"
+        match: "\\\\(x[0-9A-Fa-f]{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)"
         name: "constant.character.escape.js"
       }
     ]


edit forval: allow macros in numlist; notice illegal names assignment

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -207,23 +207,44 @@
     ]
   }
   {
-    'captures':
+    'begin': '\\b(forvalues|forvalue|forvalu|forval|forva|forv)\\s*'
+    'end': '\\s*(=)\\s*([^\\{]+)\\s*|(?=\\n)'
+    'beginCaptures':
       '1':
         'name': 'keyword.control.flow.stata'
+    'endCaptures':
+      '1':
+        'name': 'keyword.operator.assignment.stata'
       '2':
+        'patterns': [
+          {
+            'include': '#numbers'
+          }
+          {
+            'include': '#operators'
+          }
+          {
+            'include': '#macro-local'
+          }
+          {
+            'include': '#macro-global'
+          }
+        ]
+    'patterns': [
+      {
+        'include': '#reserved_names'
+      }
+      {
+        'match': '([A-Za-z_][A-Za-z0-9_]{0,31})'
         'name': 'entity.name.type.class.stata'
-      '3':
-        'name': 'keyword.operator.assignment.stata'
-      '4':
-        'patterns': [
-          {
-            'include': '#numbers'
-          }
-          {
-            'include': '#operators'
-          }
-        ]
-    'match': '\\b(forvalues)\\s+([a-zA-Z0-9_]{1,31})\\s*(=)\\s*([0-9\\(\\)/:\. ]+)\\s*'
+      }
+      {
+        'include': '#macro-local'
+      }
+      {
+        'include': '#macro-global'
+      }
+    ]
   }
   {
     'comment': 'keywords that delimit loops'