Regrouped queryparam serializer list tests as flattened and non-flat.

--- test/unit/service_interface/query_param_serializer.spec.coffee
+++ test/unit/service_interface/query_param_serializer.spec.coffee
@@ -68,71 +68,66 @@

   describe 'lists', ->

-    it 'numbers list members starting at 1', ->
-      rules = {Name:{t:'a',m:{}}} # array of strings
-      params = serialize({Name:['a','b','c']}, rules)
-      expect(params).toEqual([
-        ['Name.1', 'a'],
-        ['Name.2', 'b'],
-        ['Name.3', 'c'],
-      ])
+    describe 'flattened', ->

-    it 'accepts nested arrays', ->
-      rules = {Person:{t:'o',m:{Name:{t:'a',m:{}}}}} # nested list of strings
-      params = serialize({Person:{Name:['a','b','c']}}, rules)
-      expect(params).toEqual([
-        ['Person.Name.1', 'a'],
-        ['Person.Name.2', 'b'],
-        ['Person.Name.3', 'c'],
-      ])
+      it 'numbers list members starting at 1', ->
+        rules = {Name:{t:'a',m:{}}} # array of strings
+        params = serialize({Name:['a','b','c']}, rules)
+        expect(params).toEqual([
+          ['Name.1', 'a'],
+          ['Name.2', 'b'],
+          ['Name.3', 'c'],
+        ])

-    it 'supports lists of complex types', ->
-      rules = {Root:{t:'a',m:{t:'o',m:{Aa:{},Bb:{}}}}} # nested list of structures
-      params = serialize({Root:[{Aa:'a1',Bb:'b1'},{Aa:'a2',Bb:'b2'}]},rules)
-      expect(params).toEqual([
-        ['Root.1.Aa', 'a1'],
-        ['Root.1.Bb', 'b1'],
-        ['Root.2.Aa', 'a2'],
-        ['Root.2.Bb', 'b2'],
-      ])
+      it 'accepts nested arrays', ->
+        rules = {Person:{t:'o',m:{Name:{t:'a',m:{}}}}} # nested list of strings
+        params = serialize({Person:{Name:['a','b','c']}}, rules)
+        expect(params).toEqual([
+          ['Person.Name.1', 'a'],
+          ['Person.Name.2', 'b'],
+          ['Person.Name.3', 'c'],
+        ])

-    it 'serialzes list members as strings when member rule not present', ->
-      rules = {Root:{t:'a'}} # omitting m
-      params = serialize({Root:['a', 'b', 'c']}, rules)
-      expect(params).toEqual([
-        ['Root.1', 'a'],
-        ['Root.2', 'b'],
-        ['Root.3', 'c'],
-      ])
+      it 'supports lists of complex types', ->
+        rules = {Root:{t:'a',m:{t:'o',m:{Aa:{},Bb:{}}}}} # nested list of structures
+        params = serialize({Root:[{Aa:'a1',Bb:'b1'},{Aa:'a2',Bb:'b2'}]},rules)
+        expect(params).toEqual([
+          ['Root.1.Aa', 'a1'],
+          ['Root.1.Bb', 'b1'],
+          ['Root.2.Aa', 'a2'],
+          ['Root.2.Bb', 'b2'],
+        ])

-    it 'uses the member name when present', ->
-      rules = {Root:{t:'a',m:{n:'Entry'}}}
-      params = serialize({Root:['a', 'b', 'c']}, rules)
-      expect(params).toEqual([
-        ['Entry.1', 'a'],
-        ['Entry.2', 'b'],
-        ['Entry.3', 'c'],
-      ])
+      it 'serialzes list members as strings when member rule not present', ->
+        rules = {Root:{t:'a'}} # omitting m
+        params = serialize({Root:['a', 'b', 'c']}, rules)
+        expect(params).toEqual([
+          ['Root.1', 'a'],
+          ['Root.2', 'b'],
+          ['Root.3', 'c'],
+        ])

-  describe 'membered lists', -> # member lists name their list members

-    it 'numbers list members starting at 1', ->
-      rules = {Person:{t:'a',m:{}}} # array of strings
-      params = serialize({Person:['a','b','c']}, rules, true)
-      expect(params).toEqual([
-        ['Person.member.1', 'a'],
-        ['Person.member.2', 'b'],
-        ['Person.member.3', 'c'],
-      ])

-    it 'applies member name traits', ->
-      rules = {Person:{t:'a',m:{n:'Name'}}} # array of strings
-      params = serialize({Person:['a','b','c']}, rules, true)
-      expect(params).toEqual([
-        ['Person.Name.1', 'a'],
-        ['Person.Name.2', 'b'],
-        ['Person.Name.3', 'c'],
-      ])
+    describe 'non-flat', ->
+
+      it 'adds a `.member` prefix to each list member', ->
+        rules = {Person:{t:'a',m:{}}} # array of strings
+        params = serialize({Person:['a','b','c']}, rules, true)
+        expect(params).toEqual([
+          ['Person.member.1', 'a'],
+          ['Person.member.2', 'b'],
+          ['Person.member.3', 'c'],
+        ])
+
+      it 'replaces `.member` with the member name trait', ->
+        rules = {Person:{t:'a',m:{n:'Name'}}}
+        params = serialize({Person:['a','b','c']}, rules, true)
+        expect(params).toEqual([
+          ['Person.Name.1', 'a'],
+          ['Person.Name.2', 'b'],
+          ['Person.Name.3', 'c'],
+        ])

   describe 'maps', -> # maps are hashes with user defined keys

@@ -163,4 +158,3 @@
         ['Attributes.3.Name', 'Value'],
         ['Attributes.3.Value', 'low'],
       ])
-


simplified command execution

simplified command execution

--- main.coffee
+++ main.coffee
@@ -33,6 +33,13 @@
   process.exit 1

+exec = (cmd, keepOutput) ->
+  if keepOutput
+    execSync cmd
+  else
+    execSync cmd, stdio: 'ignore'
+
+
 readFile = (path) ->
   fs.readFileSync path, encoding: 'ascii'

@@ -103,11 +110,11 @@
     if fs.existsSync projNameHyph
       throw new Error "Directory #{projNameHyph} already exists"

-    execSync 'type lein'
-    execSync 'type pod'
-    execSync 'type watchman'
-
-    podVersion = execSync('pod --version').toString().trim()
+    exec 'type lein'
+    exec 'type pod'
+    exec 'type watchman'
+
+    podVersion = exec('pod --version', true).toString().trim()
     unless semver.satisfies podVersion, ">=#{podMinVersion}"
       throw new Error """
                       Natal requires CocoaPods #{podMinVersion} or higher (you have #{podVersion}).
@@ -115,22 +122,22 @@
                       """

     log 'Creating Leiningen project'
-    execSync "lein new #{projNameHyph}", stdio: 'ignore'
+    exec "lein new #{projNameHyph}"

     log 'Updating Leiningen project'
     process.chdir projNameHyph
-    execSync "cp #{resources}project.clj project.clj"
+    exec "cp #{resources}project.clj project.clj"
     editSync 'project.clj', [[projNameHyphRx, projNameHyph]]
     corePath = "src/#{projNameUs}/core.clj"
     fs.unlinkSync corePath
     corePath += 's'
-    execSync "cp #{resources}core.cljs #{corePath}"
+    exec "cp #{resources}core.cljs #{corePath}"
     editSync corePath, [[projNameHyphRx, projNameHyph], [projNameRx, projName]]
-    execSync "cp #{resources}ambly.sh start.sh"
+    exec "cp #{resources}ambly.sh start.sh"
     editSync 'start.sh', [[projNameUnderRx, projNameUs]]

     log 'Compiling ClojureScript'
-    execSync 'lein cljsbuild once dev', stdio: 'ignore'
+    exec 'lein cljsbuild once dev'

     log 'Creating React Native skeleton'
     fs.mkdirSync 'iOS'
@@ -148,17 +155,17 @@
       dependencies:
         'react-native': rnVersion
     , null, 2
-    execSync 'npm i', stdio: 'ignore'
+    exec 'npm i'

     log 'Installing Pod dependencies'
     process.chdir 'iOS'
-    execSync "cp #{resources}Podfile ."
-    execSync 'pod install', stdio: 'ignore'
+    exec "cp #{resources}Podfile ."
+    exec 'pod install'

     log 'Updating Xcode project'
     for ext in ['m', 'h']
       path = "#{projName}/AppDelegate.#{ext}"
-      execSync "cp #{resources}AppDelegate.#{ext} #{path}"
+      exec "cp #{resources}AppDelegate.#{ext} #{path}"
       editSync path, [[projNameRx, projName], [projNameHyphRx, projNameHyph]]

     uuid1 = crypto
@@ -264,16 +271,16 @@
 launch = ({name, device}) ->
   log 'Building Xcode project'
   try
-    execSync "
-             xcodebuild
-             -workspace iOS/iOS/#{name}.xcworkspace
-             -scheme #{name}
-             -destination platform='iOS Simulator',OS=latest,id='#{device}'
-             clean test
-             ", stdio: 'ignore'
+    exec "
+         xcodebuild
+         -workspace iOS/iOS/#{name}.xcworkspace
+         -scheme #{name}
+         -destination platform='iOS Simulator',OS=latest,id='#{device}'
+         clean test
+         "

     log 'Launching simulator'
-    execSync "xcrun simctl launch #{device} #{getBundleId name}", stdio: 'ignore'
+    exec "xcrun simctl launch #{device} #{getBundleId name}"

   catch {message}
     logErr message
@@ -281,7 +288,7 @@

 openXcode = (name) ->
   try
-    execSync "open iOS/iOS/#{name}.xcworkspace", stdio: 'ignore'
+    exec "open iOS/iOS/#{name}.xcworkspace"
   catch {message}
     logErr \
       if message.match /ENOENT/i
@@ -297,7 +304,7 @@

 getDeviceList = ->
   try
-    execSync 'xcrun instruments -s devices'
+    exec 'xcrun instruments -s devices', true
       .toString()
       .split '\n'
       .filter (line) -> /^i/.test line


:first_quarter_moon::strawberry: Updated in browser at strd6.github.io/editor

--- main.coffee
+++ main.coffee
@@ -147,22 +147,19 @@
   MidiFile = require "./lib/midifile"
   MidiPlayer = require "./midi_player"

-  micrcosecondsPerBeat = 500000
-
   badApple = "http://whimsy.space/danielx/data/clOXhtZz4VcunDJZdCM8T5pjBPKQaLCYCzbDod39Vbg"
   waltz = "http://whimsy.space/danielx/data/qxIFNrVVEqhwmwUO5wWyZKk1IwGgQIxqvLQ9WX0X20E"
+  jordan = "http://whimsy.space/danielx/data/FhSh0qeVTMu9Xwd4vihF6shaPJsD_rM8t1OSKGl-ir4"
   # Bad Apple 36MB MIDI

   require("./sample")().then (buffer) ->
-    console.log "SAMPLE:", buffer
     context.decodeAudioData buffer, (audioBuffer) ->
-      console.log audioBuffer
       global.sample = audioBuffer
     , (err) ->
       console.error 'Iam error'

   Ajax = require "./lib/ajax"
-  Ajax.getBuffer(waltz)
+  Ajax.getBuffer(jordan)
   .then (buffer) ->
     array = new Uint8Array(buffer)
     midiFile = MidiFile(array)
@@ -186,7 +183,7 @@
         when "channel:noteOff"
           releaseNote noteNumber, time + timeOffset
         when "channel:programChange"
-          ; # TODO
+          ;# console.log "PROG CH", event
         when "meta:copyrightNotice"
           if meta.copyrightNotice
             meta.copyrightNotice += "/n#{event.text}"


Replaced showEmptyListing with suggestCurrentValue

A listing with the current display value will appear as an option if this is `true`.

If set to a value less than 0, the display value will be appended to the end of the suggestions listing. Otherwise, the display value will appear as the first suggestion in the list.

--- packages/emberella/lib/views/autocomplete.coffee
+++ packages/emberella/lib/views/autocomplete.coffee
@@ -320,17 +320,21 @@
   displayValueBinding: 'inputView.value'

   ###
-    When no suggestions, a listing with the current display value will appear
-    if this is `true`.
+    A listing with the current display value will appear as an option if this
+    is `true`.
+
+    If set to a value less than 0, the display value will be appended to the
+    end of the suggestions listing. Otherwise, the display value will appear as
+    the first suggestion in the list.

     TODO: adjust this behavior to do something more useful like display
     a message/error.

-    @property showEmptyListing
+    @property suggestCurrentValue
     @type Boolean
     @default false
   ###
-  showEmptyListing: false
+  suggestCurrentValue: false

   ###
     When `true`, the selected autocomplete suggestion is set as the value when
@@ -443,7 +447,7 @@
     allSuggestions = get(@, 'allSuggestions').slice()
     _suggestions = allSuggestions.slice(0, items)

-    return _suggestions if _suggestions.length > 0 or !get(@,'showEmptyListing') or displayValue is ''
+    return _suggestions unless (suggestCurrentValue = get(@,'suggestCurrentValue'))

     contentPath = get @, 'contentPath'
     inputObject = displayValue
@@ -455,7 +459,9 @@
         part = parts.shift()
         inputObject[part] = if parts.length > 0 then {} else displayValue

-    _suggestions.pushObject(inputObject)
+    method = if +suggestCurrentValue < 0 then "pushObject" else "unshiftObject"
+
+    _suggestions[method](inputObject)

     _suggestions
   .property('allSuggestions', 'items', 'sorter').readOnly()


moved xcode task

moved xcode task

--- main.coffee
+++ main.coffee
@@ -319,15 +319,12 @@

     init name

+
 cli.command 'launch'
   .description 'Run project in simulator and start REPL'
   .action ->
     launch readConfig()

-cli.command 'xcode'
-  .description 'Open Xcode project'
-  .action ->
-    openXcode readConfig().name

 cli.command 'listdevices'
   .description 'List available simulator devices by index'
@@ -336,6 +333,7 @@
       .map (line, i) -> "#{i}\t#{line.replace /\[.+\]/, ''}"
       .join '\n')

+
 cli.command 'setdevice <index>'
   .description 'Choose simulator device by index'
   .action (index) ->
@@ -346,6 +344,13 @@
     config.device = pluckUuid device
     writeConfig config

+
+cli.command 'xcode'
+  .description 'Open Xcode project'
+  .action ->
+    openXcode readConfig().name
+
+
 cli.on '*', (command) ->
   logErr "Unknown command #{command[0]}. See natal --help for valid commands"


symbol -> sheet

symbol -> sheet

--- main.coffee
+++ main.coffee
@@ -1,5 +1,12 @@

-class BlabSymbol
+# A BlabSheet is an array of data augmented with row and column names
+# (i.e., how spreadsheets look. Better name?). If row/col names aren't
+# given, defaults are applied. Data can be extracted by various
+# methods: for instance, JSON data with the row headings as keys
+# (rowJson). The data provided by the methods is suitable for
+# digestion by other table/plotting classes.
+
+class BlabSheet

     constructor: (@spec) ->

@@ -15,26 +22,42 @@
         else
             @rowHeaders = ('r'+k for k in [0...@data.length])

-    rowData: ->
+    rowJson: ->
         x = {}
-        y = $blab.sym['y']
         x[rh] = @data[k] for rh, k in @rowHeaders
         return x

+# BlabC3 is a convenience wrapper for c3.js; it takes a BlabSheet as
+# its argument. There are various other charting libraries (nvd3 for
+# d3.js) and these can be simularly wrapped.

-class BlabTable
+class BlabC3
+
+    constructor: (@sheet) ->
+
+        @chart = c3.generate(
+          bindto: '#chart'
+          data: json: @sheet.rowJson()
+          )
+
+    load: ->
+        @chart.load(json: @sheet.rowJson())
+
+# BlabHandsontable = wrapper for handsontable.js
+
+class BlabHandsontable

     constructor: (@spec) ->

-        @sym = @spec.sym
-        @data = @sym.data
+        @sheet = @spec.sheet
+        @data = @sheet.data

         @table = new Handsontable $("#"+@spec.id)[0],
             data: @data
             startRows: @data.length
             startCols: @data[0].length
-            rowHeaders: @sym.rowHeaders
-            colHeaders: @sym.colHeaders
+            rowHeaders: @sheet.rowHeaders
+            colHeaders: @sheet.colHeaders
             contextMenu: true
             columns: ({type: 'numeric'} for k in [1..@data[0].length])

@@ -47,64 +70,44 @@
     afterChange: (@callback) ->
         @table.addHook 'afterChange', => @callback()

-
-class BlabFigure
-
-    constructor: (@sym) ->
-
-        @chart = c3.generate(
-          bindto: '#chart'
-          data: json: @sym.rowData()
-          )
-
-    load: ->
-        @chart.load(json: @sym.rowData())
-
-
+
 ## From GUI

-# symbols
+# sheets

-symbol = (id, data) -> new BlabSymbol {id:id, data:data}
-$blab.sym =
-    A: symbol "A", [[1,2],[3,4]]
-    x: symbol "x", [[5],[6]]
-    b: symbol "b", [[]]
-    y: symbol "y", [[30, 200, 100, 400, 150, 250],[50,  20,  10,  40,  15,  25]]
+sh = (id, data) -> new BlabSheet {id:id, data:data}
+$blab.sheet =
+    A: sh "A", [[1,2],[3,4]]
+    x: sh "x", [[5],[6]]
+    b: sh "b", [[0],[0]]
+    y: sh "y", [[30, 200, 100, 400, 150, 250],[50,  20,  10,  40,  15,  25]]

-$blab.sym['y'].rowHeaders = ['dA','dB']
-$blab.sym['y'].colHeaders = ['i','ii','iii','iv','v','vi']
+$blab.sheet['y'].rowHeaders = ['dA','dB']
+$blab.sheet['y'].colHeaders = ['i','ii','iii','iv','v','vi']

 # tables

-table = (id) -> new BlabTable {sym:$blab.sym[id], id:id}
-$blab.tab =
-    A: table "A"
-    x: table "x"
-    b: table "b"
-    y: table "y"
+tab = (id) -> new BlabHandsontable {sheet:$blab.sheet[id], id:id}
+$blab.table =
+    A: tab "A"
+    x: tab "x"
+    b: tab "b"
+    y: tab "y"

 # figures

-$blab.fig = []
-$blab.fig['fig1'] = new BlabFigure $blab.sym['y']
-$blab.sym['y'].data = [[30, 200, 100, 0, 150, 250],[50,  20,  10,  40,  15,  25]]
-$blab.fig['fig1'].load()
+$blab.figure = []
+$blab.figure['y'] = new BlabC3 $blab.sheet['y']

 ## user coffeescript

 # For convenience

-sym = $blab.sym
-tab = $blab.tab
-
-#A = sym['A'].data
-#x = sym['x'].data
-#b = sym['b'].data
-
-#console.log sym
-for s, d of sym
-    eval(s + " = sym['" + s + "'].data")
+sheet = $blab.sheet
+table = $blab.table
+figure = $blab.figure
+for s, d of sheet
+    eval(s + " = sheet['" + s + "'].data") # !!! local vars !!!

 # User code
 fn = (A, x) ->
@@ -112,15 +115,11 @@

 compute = ->
     b = fn(A, x)
-    tab['b'].setData b
-
-# Auto-compute after changing A and x
-tab['A'].afterChange (-> compute())
-tab['x'].afterChange (-> compute())
+    table['b'].setData b

 compute()

-
-
-
-
+# table updates
+table['A'].afterChange (-> compute())
+table['x'].afterChange (-> compute())
+table['y'].afterChange (-> figure['y'].load())


change string operator and some fix

change string operator and some fix

--- src/tools/Gruntfile.coffee
+++ src/tools/Gruntfile.coffee
@@ -1,47 +1,54 @@
 module.exports = (grunt) ->
-  "use strict"
+  'use strict'

   # Project configuration.
   grunt.initConfig
     # Metadata.
-    pkg: grunt.file.readJSON("package.json")
+    pkg: grunt.file.readJSON 'package.json'

     # Parse CSS and add vendor-prefixed CSS properties using the Can I Use database.
     autoprefixer:
       options:
-        browsers: ["ios >= 5", "android >= 2.3"]
+        browsers: ['ios >= 5', 'android >= 2.3']
       dist:
-        src: "../files/css/maple.css"
+        src: '../files/css/maple.css'

-    # Start a static web server.
+    # Start a static web server.
     # Reload assets live in the browser
     connect:
       dist:
         options:
           port: 8080
-          base: "../"
-          open: "http://localhost:8080/components/"
+          base: '../'
+          open: 'http://localhost:8080/components/'

     # Sort CSS properties in specific order.
     csscomb:
       dist:
         files:
-          "../files/css/maple.resort.css": ["../files/css/maple.css"]
+          '../files/css/maple.resort.css': ['../files/css/maple.css']

     # Lint CSS files.
     csslint:
       dist:
         options:
-          csslintrc: ".csslintrc"
-        src: ["../files/css/maple.css"]
+          csslintrc: '.csslintrc'
+        src: ['../files/css/maple.css']

     # Minify CSS files with CSSO.
     csso:
       dist:
         options:
-          banner: "/*\n * Maple.css\n * Copyright 2013 Koji Ishimoto\n * Licensed under the MIT License\n */\n"
+          banner: """
+          /*
+           * Maple.css
+           * Copyright 2013 Koji Ishimoto
+           * Licensed under the MIT License
+           */
+
+          """
         files:
-          "../files/css/maple.min.css": ["../files/css/maple.css"]
+          '../files/css/maple.min.css': ['../files/css/maple.css']

     # Make ImageOptim, ImageAlpha and JPEGmini part of your automated build process
     imageoptim:
@@ -50,79 +57,89 @@
         jpegMini: false
         quitAfter: true
       dist:
-        src: ["../files/img/sprite"]
-
+        src: ['../files/img/sprite']
+
     # KSS styleguide generator for grunt.
     kss:
       options:
-        includeType: "css"
-        includePath: "../files/css/maple.css"
-        template: "../docs/template"
+        includeType: 'css'
+        includePath: '../files/css/maple.css'
+        template: '../docs/template'
       dist:
         files:
           # dest : src
-          "../docs/": ["../files/css/sass"]
-
+          '../docs/': ['../files/css/sass']
+
     # Grunt task to compile Sass SCSS to CSS
     sass:
       dist:
         files:
-          "../files/css/maple.css": "../files/css/sass/maple.scss"
-
+          '../files/css/maple.css': '../files/css/sass/maple.scss'
+
     # Grunt task for creating spritesheets and their coordinates
     sprite:
       dist:
-        dt: "<%= Date.now() %>"
-        src: "../files/img/sprite/tabs/*.png"
-        destImg: "../files/img/sprite/tabs.<%= sprite.dist.dt %>.png"
-        imgPath: "/files/img/sprite/tabs.<%= sprite.dist.dt %>.png"
-        destCSS: "../files/css/sass/libs/_sprite.scss"
-        algorithm: "binary-tree"
+        dt: '<%= Date.now() %>'
+        src: '../files/img/sprite/tabs/*.png'
+        destImg: '../files/img/sprite/tabs.<%= sprite.dist.dt %>.png'
+        imgPath: '/files/img/sprite/tabs.<%= sprite.dist.dt %>.png'
+        destCSS: '../files/css/sass/libs/_sprite.scss'
+        algorithm: 'binary-tree'
         padding: 2
-        cssTemplate: "spritesmith.mustache"
+        cssTemplate: 'spritesmith.mustache'
         # cssOpts: { functions: false }
-
+
     # Run tasks whenever watched files change.
     watch:
       options:
         livereload: true
       css:
-        files: ["../files/css/**/*.scss", "../components/*.html"]
-        tasks: ["sass", "autoprefixer", "csso"]
+        files: ['../files/css/**/*.scss', '../components/*.html']
+        tasks: ['sass', 'autoprefixer', 'csso']
       sprite:
-        files: ["../files/img/sprite/*/*.png"]
-        tasks: ["sprite"]
-
+        files: ['../files/img/sprite/*/*.png']
+        tasks: ['sprite']
+
     # SVG to webfont converter for Grunt.
     webfont:
       dist:
-        src: "../files/font/svg/*.svg"
-        dest: "../files/font/"
-        destCss: "../files/css/sass/libs/"
+        src: '../files/font/svg/*.svg'
+        dest: '../files/font/'
+        destCss: '../files/css/sass/libs/'
         options:
-          font: "myfont"
-          types: ["woff", "ttf"]
-          stylesheet: "scss"
+          font: 'myfont'
+          types: ['woff', 'ttf']
+          stylesheet: 'scss'
           htmlDemo: false
-          syntax: "bootstrap"
-          relativeFontPath: "/files/font/"
+          syntax: 'bootstrap'
+          relativeFontPath: '/files/font/'

   # Load the plugins.
-  grunt.loadNpmTasks "grunt-kss"
-  grunt.loadNpmTasks "grunt-sass"
-  grunt.loadNpmTasks "grunt-csso"
-  grunt.loadNpmTasks "grunt-csscomb"
-  grunt.loadNpmTasks "grunt-webfont"
-  grunt.loadNpmTasks "grunt-imageoptim"
-  grunt.loadNpmTasks "grunt-spritesmith"
-  grunt.loadNpmTasks "grunt-autoprefixer"
-  grunt.loadNpmTasks "grunt-contrib-connect"
-  grunt.loadNpmTasks "grunt-contrib-csslint"
-  grunt.loadNpmTasks "grunt-contrib-watch"
-
+  grunt.loadNpmTasks 'grunt-kss'
+  grunt.loadNpmTasks 'grunt-sass'
+  grunt.loadNpmTasks 'grunt-csso'
+  grunt.loadNpmTasks 'grunt-csscomb'
+  grunt.loadNpmTasks 'grunt-webfont'
+  grunt.loadNpmTasks 'grunt-imageoptim'
+  grunt.loadNpmTasks 'grunt-spritesmith'
+  grunt.loadNpmTasks 'grunt-autoprefixer'
+  grunt.loadNpmTasks 'grunt-contrib-connect'
+  grunt.loadNpmTasks 'grunt-contrib-csslint'
+  grunt.loadNpmTasks 'grunt-contrib-watch'
+
   # Tasks.
-  grunt.registerTask "default", ["develop"]
-  grunt.registerTask "develop", ["connect", "watch"]
-  grunt.registerTask "typeset", ["webfont", "sass", "autoprefixer"]
-  grunt.registerTask "publish", ["sass", "autoprefixer","csslint", "kss"]
-  grunt.registerTask "build", ["csslint", "sass", "autoprefixer", "csso", "imageoptim"]+  grunt.registerTask 'default', ['develop']
+  grunt.registerTask 'develop', ['connect', 'watch']
+  grunt.registerTask 'typeset', [
+    'webfont', 'sass'
+    'autoprefixer'
+  ]
+  grunt.registerTask 'publish', [
+    'sass', 'autoprefixer'
+    'csslint', 'kss'
+  ]
+  grunt.registerTask 'build', [
+    'csslint', 'sass'
+    'autoprefixer', 'csso'
+    'imageoptim'
+  ]


Tune scroller hiding

Tune scroller hiding

--- source/assets/javascripts/app.js.coffee
+++ source/assets/javascripts/app.js.coffee
@@ -177,7 +177,7 @@
     scroller.attr { fill: '#b3b3b3', cursor: 'pointer' }
     $(scroller.node).one 'click', ->
       $.scrollTo '.fonts', { duration: defaultSpeed }
-      scroller.remove()
+      scroller.attr { opacity: 0 }
     $(scroller.node).on 'mouseenter', ->
       scroller.attr { fill: '#000' }
     $(scroller.node).on 'mouseleave', ->
@@ -185,7 +185,9 @@
     $(window).on 'scroll', ->
       topBound = $('header.main').height()
       if $(window).scrollTop() > topBound
-        scroller.remove()
+        scroller.attr { opacity: 0 }
+      if $(window).scrollTop() < topBound
+        scroller.attr { opacity: 1 }
   moveButton = (item, index) ->
     index = 0 if index > 11
     elementToMove = buttons.selectAll('g#'+item.id+' > g')[index]


Show vmMountSwitch on machines' context menu

--- client/landing/app/Applications/Environments.kdapplication/views/scene/environmentmachineitem.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/scene/environmentmachineitem.coffee
@@ -23,9 +23,10 @@

     vmName = @getData().title
     vmStateSwitch = new NVMToggleButtonView {}, {vmName}
-
+    vmMountSwitch = new NMountToggleButtonView {}, {vmName}
     items =
-      customView1 : vmStateSwitch
+      customView1        : vmStateSwitch
+      customView2        : vmMountSwitch
       'Re-initialize VM' :
         disabled         : KD.isGuest()
         callback         : ->
@@ -43,7 +44,7 @@
       'Unfocus'          :
         separator        : yes
         action           : 'unfocus'
-      customView2        : colorSelection
+      customView3        : colorSelection

     return items


:memo: Add documentation for findTooltips

--- src/tooltip-manager.coffee
+++ src/tooltip-manager.coffee
@@ -155,6 +155,11 @@

     disposable

+  # Extended: Find the tooltips that have been applied to the given element.
+  #
+  # * `target` The `HTMLElement` to find tooltips on.
+  #
+  # Returns an {Array} of `Tooltip` objects that match the `target`.
   findTooltips: (target) ->
     if @tooltips.has(target)
       @tooltips.get(target).slice()


Correct version of 73c3e3.

Correct version of 73c3e3.

--- src/trans-websocket.coffee
+++ src/trans-websocket.coffee
@@ -102,12 +102,10 @@

 # Inheritance only for decorateConnection.
 class RawWebsocketSessionReceiver extends transport.Session
-    protocol: "websocket-raw"
-
     constructor: (req, conn, server, @ws) ->
         @prefix = server.options.prefix
         @readyState = Transport.OPEN
-        @recv = {connection: conn}
+        @recv = {connection: conn, protocol: "websocket-raw"}

         @connection = new transport.SockJSConnection(@)
         @decorateConnection(req)


missing param

missing param

--- scripts/pagerv2_hook.coffee
+++ scripts/pagerv2_hook.coffee
@@ -36,7 +36,7 @@
         if req.body? and req.body.messages? and req.body.messages[0].type?
           robot.logger.debug req.body
           if /^incident.*$/.test(req.body.messages[0].type)
-            pagerv2.parseWebhook(req.body.messages)
+            pagerv2.parseWebhook(robot.adapter.name, req.body.messages)
             .then (messages) ->
               for message in messages
                 robot.messageRoom pagerRoom, message


Resolve references in return value options

--- src/util/referencer.coffee
+++ src/util/referencer.coffee
@@ -236,6 +236,9 @@

       for returnValue in data.doc.returnValue ? []
         returnValue.desc = @resolveTextReferences(returnValue.desc, entity, path)
+
+        for options in returnValue.options ? []
+          options.desc = @resolveTextReferences(options.desc, entity, path)

       if data.doc.throwValue
         for throws, index in data.doc.throwValue


[state] test for leaveChannel on current state

--- spec/plugins/scaleApp.state.spec.coffee
+++ spec/plugins/scaleApp.state.spec.coffee
@@ -146,6 +146,14 @@
         (expect @machine.current).toEqual "a"
         done()

+    it "emits 'leaveChannel' for current state", (done) ->
+      @machine.current = "a"
+      @machine.addState ["a", "b", "c"]
+      @machine.addTransition "x", from: ["a", "b"], to: "c"
+      @machine.onLeave "a", ->
+        done()
+      (expect @machine.fire "x").toEqual true
+
   describe "can method", ->
     it "returns true if transition can be fired", ->
       @machine.addState ["a", "b"]


Remove Change Encoding from mini editors

--- menus/encoding-selector.cson
+++ menus/encoding-selector.cson
@@ -7,7 +7,7 @@
 ]

 'context-menu':
-  'atom-text-editor': [
+  'atom-text-editor:not([mini])': [
     'label': 'Change Encoding'
     'command': 'encoding-selector:show'
   ]


Moved all Button logic to subclassess

Moved all Button logic to subclassess

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -1,26 +1,4 @@
-class Button
-  constructor: ->
-    @frame = new FactlinkJailRoot.ControlIframe()
-    @frame.setContent($.parseHTML(@content.trim())[0])
-    @$el = $(@frame.frameBody.firstChild)
-
-  _bindCallbacks: (callbacks) ->
-    @_robustHover = new FactlinkJailRoot.RobustHover @$el, callbacks
-    @$el.on 'click', -> callbacks.click?()
-
-  startHovering: => @frame.addClass 'hovered'
-  stopHovering: => @frame.removeClass 'hovered'
-
-  # TODO: do all updates of different buttons in one pass
-  _updatePositionRegularly: ->
-    $(window).on 'resize', @_updatePosition
-    @_interval = setInterval @_updatePosition, 1000
-    @_updatePosition()
-
-  _updatePosition: =>
-
-
-class FactlinkJailRoot.CreateButton extends Button
+class FactlinkJailRoot.CreateButton
   content: """

@@ -35,11 +13,13 @@
   """

   constructor: ->
-    super
+    @frame = new FactlinkJailRoot.ControlIframe()
+    @frame.setContent($.parseHTML(@content.trim())[0])
+    @$el = $(@frame.frameBody.firstChild)

     @_robustHover = new FactlinkJailRoot.RobustHover @$el,
-      mouseenter: => @startHovering()
-      mouseleave: => @stopHovering()
+      mouseenter: => @frame.addClass 'hovered'
+      mouseleave: => @frame.removeClass 'hovered'

     @$el.on 'mousedown', (event) -> event.preventDefault() # To prevent de-selecting text
     @$el.on 'click', @_onClick
@@ -80,8 +60,12 @@
     @_visible = false

-
-class IconButton extends Button
+class IconButton
+
+  constructor: ->
+    @frame = new FactlinkJailRoot.ControlIframe()
+    @frame.setContent($.parseHTML(@content.trim())[0])
+    @$el = $(@frame.frameBody.firstChild)

   destroy: =>
     @frame.destroy()
@@ -90,6 +74,22 @@
     clearInterval @_interval
     @$boundingBox?.remove()

+  _bindCallbacks: (callbacks) ->
+    @_robustHover = new FactlinkJailRoot.RobustHover @$el, callbacks
+    @$el.on 'click', -> callbacks.click?()
+
+  startHovering: => @frame.addClass 'hovered'
+  stopHovering: => @frame.removeClass 'hovered'
+
+  # TODO: do all updates of different buttons in one pass
+  _updatePositionRegularly: ->
+    $(window).on 'resize', @_updatePosition
+    @_interval = setInterval @_updatePosition, 1000
+    @_updatePosition()
+
+  _updatePosition: =>
+
+

 class FactlinkJailRoot.ShowButton extends IconButton
   content: '

'


Moved start/stopLoading to CreateButton

Moved start/stopLoading to CreateButton

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -7,9 +7,6 @@
   _bindCallbacks: (callbacks) ->
     @_robustHover = new FactlinkJailRoot.RobustHover @$el, callbacks
     @$el.on 'click', -> callbacks.click?()
-
-  startLoading: => @_addClass 'loading'
-  stopLoading: => @_removeClass 'loading hovered'

   startHovering: => @_addClass 'hovered'
   stopHovering: => @_removeClass 'hovered'
@@ -82,6 +79,9 @@
     @$el.on 'mousedown', (event) -> event.preventDefault() # To prevent de-selecting text
     @$el.on 'click', @_onClick

+  startLoading: => @_addClass 'loading'
+  stopLoading: => @_removeClass 'loading hovered'
+
   _onClick: (event) =>
     @startLoading()
     current_user_opinion = $(event.target).closest('[data-opinion]').data('opinion')


Request: rename verb port to method

Request: rename verb port to method

--- components/Request.coffee
+++ components/Request.coffee
@@ -6,7 +6,7 @@
   constructor: ->
     @inPorts =
       server: new noflo.Port 'number'
-      verb: new noflo.Port 'string'
+      method: new noflo.Port 'string'
       path: new noflo.Port 'string'
     @outPorts =
       request: new noflo.Port 'object'
@@ -25,7 +25,7 @@
       @removeRoute()
       @path = path
       @createRoute()
-    @inPorts.verb.on 'data', (verb) =>
+    @inPorts.method.on 'data', (verb) =>
       @removeRoute()
       @verb = verb
       @createRoute()
@@ -37,9 +37,9 @@
   removeRoute: ->
     return unless @server and @verb and @path
     routes = @server.routes[@verb]
-    for route in routes
-      if route.path == @path
-        route.slice()
+    for route, i in routes
+      if route.path == @path and route.method == @verb
+        routes.slice(i, -1)

   shutdown: ->
     @removeRoute()


compute the list of sounds from sound_index

--- source/assets/javascripts/konsole.js.coffee
+++ source/assets/javascripts/konsole.js.coffee
@@ -85,73 +85,12 @@
   $('.level').find('i[data-level=' + level + ']').addClass 'is-active'

 ion.sound
-  sounds: [
-    {name:'WorkIt1'}
-    {name:'MakeIt1'}
-    {name:'DoIt1'}
-    {name:'MakesUs1'}
-    {name:'Harder1'}
-    {name:'Better1'}
-    {name:'Faster1'}
-    {name:'Stronger1'}
-    {name:'MoreThan1'}
-    {name:'Hour1'}
-    {name:'Our1'}
-    {name:'Never1'}
-    {name:'Ever1'}
-    {name:'After1'}
-    {name:'WorkIs1'}
-    {name:'Over1'}
-    {name:'WorkIt2'}
-    {name:'MakeIt2'}
-    {name:'DoIt2'}
-    {name:'MakesUs2'}
-    {name:'Harder2'}
-    {name:'Better2'}
-    {name:'Faster2'}
-    {name:'Stronger2'}
-    {name:'MoreThan2'}
-    {name:'Hour2'}
-    {name:'Our2'}
-    {name:'Never2'}
-    {name:'Ever2'}
-    {name:'After2'}
-    {name:'WorkIs2'}
-    {name:'Over2'}
-    {name:'MoreThan3'}
-    {name:'Hour3'}
-    {name:'Our3'}
-    {name:'Never3'}
-    {name:'Ever3'}
-    {name:'After3'}
-    {name:'WorkIs3'}
-    {name:'Over3'}
-    {name:'WorkIt4'}
-    {name:'MakeIt4'}
-    {name:'DoIt4'}
-    {name:'MakesUs4'}
-    {name:'Harder4'}
-    {name:'Better4'}
-    {name:'Faster4'}
-    {name:'Stronger4'}
-    {name:'MoreThan4'}
-    {name:'Hour4'}
-    {name:'Our4'}
-    {name:'Never4'}
-    {name:'Ever4'}
-    {name:'After4'}
-    {name:'WorkIs4'}
-    {name:'Over4'}
-    {name:'MoreThan5'}
-    {name:'Hour5'}
-    {name:'Our5'}
-    {name:'Never5'}
-    {name:'Ever5'}
-    {name:'After5'}
-    {name:'WorkIs5'}
-    {name:'Over5'}
-
-  ]
+  sounds: do ->
+    sounds = []
+    for i in [1..5]
+      for s in sound_index
+        sounds.push {name: (s[2] + i)}
+    return sounds
   path: 'http://s.cdpn.io/190177/'
   preload: true


Fix: couldn't edit label or hint if they were empty

--- src/resources/forms/orbeon/newbuilder/resources/grid/placeholders.coffee
+++ src/resources/forms/orbeon/newbuilder/resources/grid/placeholders.coffee
@@ -34,7 +34,7 @@
     hidePlaceholder = (labelHint) ->
         if YD.hasClass labelHint, 'fb-label-hint-placeholder'
             YD.removeClass labelHint, 'fb-label-hint-placeholder'
-            OD.setStringValue labelHint, ''
+            $(labelHint).contents().filter(-> @nodeType == @TEXT_NODE).detach()
     doOnLabelHint = (gridTd, f) ->
         for labelHintClass in ['xforms-label', 'xforms-hint']
             labelHint = (YD.getElementsByClassName labelHintClass, null, gridTd)[0]


Maintain portsForTab mapping tabIds to frame ports.

This maintains a mapping from tab Ids to a mapping from frame Ids to
their ports.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -20,6 +20,7 @@

 currentVersion = Utils.getCurrentVersion()
 frameIdsForTab = {}
+portsForTab = {}
 root.urlForTab = {}
 topFramePortForTab = {}

@@ -293,6 +294,8 @@
     topFramePortForTab[tabId] = port if frameId == 0
     frameIdsForTab[tabId] ?= []
     frameIdsForTab[tabId].push frameId unless frameId in frameIdsForTab[tabId]
+    portsForTab[tabId] ?= {}
+    portsForTab[tabId][frameId] = port
     port.postMessage handler: "registerFrameId", chromeFrameId: frameId

     port.onDisconnect.addListener listener = ->
@@ -302,6 +305,8 @@
       # events happen (e.g. on navigation, a new top frame registers before the old one unregisters).
       if tabId of frameIdsForTab and frameId != 0
         frameIdsForTab[tabId] = (fId for fId in frameIdsForTab[tabId] when fId != frameId)
+      if tabId of portsForTab
+        delete portsForTab[tabId][frameId]

     # Return our onMessage handler for this port.
     (request, port) =>
@@ -406,7 +411,7 @@
 # there are no remaining incognito-mode windows.  Since the common case is that there are none to begin with,
 # we first check whether the key is set at all.
 chrome.tabs.onRemoved.addListener (tabId) ->
-  delete cache[tabId] for cache in [frameIdsForTab, urlForTab, topFramePortForTab]
+  delete cache[tabId] for cache in [frameIdsForTab, urlForTab, topFramePortForTab, portsForTab]
   chrome.storage.local.get "findModeRawQueryListIncognito", (items) ->
     if items.findModeRawQueryListIncognito
       chrome.windows.getAll null, (windows) ->


String.contains is not a standard method in Chrome 41

--- spec/default-directory-provider-spec.coffee
+++ spec/default-directory-provider-spec.coffee
@@ -15,8 +15,8 @@
       provider = new DefaultDirectoryProvider()
       tmp = temp.mkdirSync()
       nonNormalizedPath = tmp + path.sep +  ".." + path.sep + path.basename(tmp)
-      expect(tmp.contains("..")).toBe false
-      expect(nonNormalizedPath.contains("..")).toBe true
+      expect(tmp.includes("..")).toBe false
+      expect(nonNormalizedPath.includes("..")).toBe true

       directory = provider.directoryForURISync(nonNormalizedPath)
       expect(directory.getPath()).toEqual tmp


simplify the PaneResizeHandleElement specs

--- spec/pane-resize-handle-element-spec.coffee
+++ spec/pane-resize-handle-element-spec.coffee
@@ -3,7 +3,7 @@
 PaneAxis = require '../src/pane-axis'

 describe "PaneResizeHandleElement", ->
-  describe "as children of PaneAxisElement", ->
+  describe "when add or remove panes in PaneAxisElement", ->
     [paneAxisElement, paneAxis] = []

     beforeEach ->
@@ -12,11 +12,7 @@
       paneAxisElement.initialize(paneAxis)
       document.querySelector('#jasmine-content').appendChild(paneAxisElement)

-    it "inserts or remove resize elements when pane axis added or removed", ->
-      modelChildren = (new PaneAxis({}) for i in [1..5])
-      paneAxis.addChild(modelChildren[0])
-      paneAxis.addChild(modelChildren[1])
-
+    it "inserts or remove resize elements", ->
       expectPaneAxisElement = (index) ->
         child = paneAxisElement.children[index]
         expect(child.nodeName.toLowerCase()).toBe('atom-pane-axis')
@@ -24,23 +20,23 @@
       expectResizeElement = (index) ->
         child = paneAxisElement.children[index]
         expect(paneAxisElement.isPaneResizeHandleElement(child)).toBe(true)
+
+      models = (new PaneAxis({}) for i in [0..2])
+      paneAxis.addChild(models[0])
+      paneAxis.addChild(models[1])
       expectResizeElement(1)

-      paneAxis.addChild(modelChildren[2])
-      paneAxis.addChild(modelChildren[3])
-      expectPaneAxisElement(i) for i in [0, 2, 4, 6]
-      expectResizeElement(i) for i in [1, 3, 5]
+      paneAxis.addChild(models[2])
+      expectPaneAxisElement(i) for i in [0, 2, 4]
+      expectResizeElement(i) for i in [1, 3]

       # test removeChild
-      paneAxis.removeChild(modelChildren[2])
+      paneAxis.removeChild(models[2])
       expectResizeElement(i) for i in [1, 3]

-      # test replaceChild
-      paneAxis.replaceChild(modelChildren[0], modelChildren[4])
-      expectResizeElement(i) for i in [1, 3]
-
-  describe "when mouse drag the resize element", ->
-    [container, containerElement, resizeElementMove, getElementWidth] = []
+  describe "when user drag the resize element", ->
+    [container, containerElement] = []
+    [resizeElementMove, getElementWidth, expectFlexScale] = []

     beforeEach ->
       container = new PaneContainer
@@ -63,104 +59,47 @@
       getElementWidth = (element) ->
         element.getBoundingClientRect().width

-    it "drag the pane resize handle element, then the panes around it will resize", ->
-      activePane = container.getActivePane()
-      rightPane = activePane.splitRight()
+      # assert the pane's flex scale. arguments is list of pane-scale pair
+      expectFlexScale = ->
+        args = Array::slice.apply(arguments, 0)
+        for paneScale in args
+          expect(paneScale[0].getFlexScale()).toBeCloseTo(paneScale[1], 0.1)

-      resizeElement = containerElement.querySelector('atom-pane-resize-handle')
-      expect(resizeElement).toBeTruthy()
-
-      leftWidth = getElementWidth(resizeElement.previousSibling)
-      resizeElementMove(resizeElement, leftWidth/2, 0)
-      expect(activePane.getFlexScale()).toBeCloseTo(0.5, 0.1)
-      expect(rightPane.getFlexScale()).toBeCloseTo(1.5, 0.1)
-
-    it "drag the resize element, the size of other panes in the same direction will not change", ->
+    it "add and remove panes in the same direction", ->
       leftPane = container.getActivePane()
       middlePane = leftPane.splitRight()
-      rightPane = middlePane.splitRight()

       resizeElements = containerElement.querySelectorAll('atom-pane-resize-handle')
       paneElements = containerElement.querySelectorAll('atom-pane')
       expect(resizeElements.length).toBe(2)

-      expectPaneScale = (leftScale, middleScale, rightScale) ->
-        expect(leftPane.getFlexScale()).toBeCloseTo(leftScale, 0.1)
-        expect(middlePane.getFlexScale()).toBeCloseTo(middleScale, 0.1)
-        expect(rightPane.getFlexScale()).toBeCloseTo(rightScale, 0.1)
+      resizeElementMove(resizeElements[0], getElementWidth(paneElements[0]) / 2)
+      expectPaneScale [leftPane, 0.5], [middlePane, 1.5]

-      resizeElementMove(resizeElements[0], getElementWidth(paneElements[0]) / 2)
-      expectPaneScale(0.5, 1.5, 1)
-
+      rightPane = middlePane.splitRight()
       clientX = getElementWidth(paneElements[0]) + getElementWidth(paneElements[1]) / 2
       resizeElementMove(resizeElements[1], clientX)
-      expectPaneScale(0.5, 0.75, 1.75)
+      expectPaneScale [leftPane, 0.5], [middlePane, 0.75], [rightPane, 1.75]

-    it "drag the horizontal element, the size of other vertical pane will not change", ->
-      upperPane = container.getActivePane()
-      lowerPane = upperPane.splitDown()
-
-      [upperRightPane, upperLeftPane] = [upperPane.splitRight(), upperPane]
-      upperPane = upperLeftPane.getParent()
-
-      [lowerRightPane, lowerLeftPane] = [lowerPane.splitRight(), lowerPane]
-      lowerPane = lowerLeftPane.getParent()
-
-      horizontalResizeElements = containerElement.querySelectorAll('atom-pane-resize-handle.horizontal')
-      expect(horizontalResizeElements.length).toBe(2)
-
-      expectCloseTo = (element, scale) ->
-        expect(element.getFlexScale()).toBeCloseTo(scale, 0.1)
-
-      expectPaneScale = (upper, lower, upperLeft, upperRight, lowerLeft, lowerRight) ->
-        paneScales = [
-          [upperPane, upper], [lowerPane, lower], [upperLeftPane, upperLeft],
-          [upperRightPane, upperRight], [lowerLeftPane, lowerLeft],
-          [lowerRightPane, lowerRight]
-        ]
-        expectCloseTo(e[0], e[1]) for e in paneScales
-
-      newWidth = getElementWidth(horizontalResizeElements[0].previousSibling) / 2
-      resizeElementMove(horizontalResizeElements[0], newWidth)
-      expectPaneScale(1, 1, 0.5, 1.5, 1, 1)
-
-      newWidth = getElementWidth(horizontalResizeElements[1].previousSibling) / 2
-      resizeElementMove(horizontalResizeElements[1], newWidth)
-      expectPaneScale(1, 1, 0.5, 1.5, 0.5, 1.5)
-
-    it "transform the flex scale when dynamically split or close panes in the same direction", ->
-      leftPane = container.getActivePane()
-      middlePane = leftPane.splitRight()
-      rightPane = middlePane.splitRight()
-
-      expectPaneScale = (leftScale, middleScale, rightScale) ->
-        paneScales = [[leftPane, leftScale], [middlePane, middleScale], [rightPane, rightScale]];
-        expect(e[0].getFlexScale()).toBeCloseTo(e[1], 0.1) for e in paneScales
-
-      resizeElement = containerElement.querySelector('atom-pane-resize-handle')
-      resizeElementMove(resizeElement, getElementWidth(atom.views.getView(leftPane)) / 2)
-      expectPaneScale(0.5, 1.5, 1)
+      middlePane.close()
+      expectPaneScale [leftPane, 0.44], [rightPane, 1.55]

       leftPane.close()
-      expect(middlePane.getFlexScale()).toBeCloseTo(1.2, 0.1)
-      expect(rightPane.getFlexScale()).toBeCloseTo(0.8, 0.1)
+      expectPaneScale [rightPane, 1]

-      rightPane.close() # when close the same direction pane, the flexScale will recorver
-      expect(middlePane.getFlexScale()).toBeCloseTo(1, 0.1)
-
-    it "retain the flex scale when dynamically split or close panes in orthogonal direction", ->
+    it "split or close panes in orthogonal direction", ->
       leftPane = container.getActivePane()
       rightPane = leftPane.splitRight()

       resizeElement = containerElement.querySelector('atom-pane-resize-handle')
       resizeElementMove(resizeElement, getElementWidth(resizeElement.previousSibling) / 2)
-      expect(leftPane.getFlexScale()).toBeCloseTo(0.5, 0.1)

-      lowerPane = leftPane.splitDown()   # dynamically split pane, pane's flexScale will become to 1
-      expect(leftPane.getFlexScale()).toBeCloseTo(1, 0.1)
-      expect(leftPane.getParent().getFlexScale()).toBeCloseTo(0.5, 0.1)
+      # dynamically split pane, pane's flexScale will become to 1
+      lowerPane = leftPane.splitDown()
+      expectPaneScale [lowerPane, 1], [leftPane, 1], [leftPane.getParent(), 0.5]

-      lowerPane.close() # dynamically close pane, the pane's flexscale will recorver to origin value
-      expect(leftPane.getFlexScale()).toBeCloseTo(0.5, 0.1)
+      # dynamically close pane, the pane's flexscale will recorver to origin value
+      lowerPane.close()
+      expectPaneScale [leftPane, 0.5], [rightPane, 1.5]


Fix bug to use correct constructor

Fix bug to use correct constructor

--- src/tub.coffee
+++ src/tub.coffee
@@ -22,7 +22,7 @@

 genTubConfig = (tub, appdir, yml) ->
   # body...
-  tc = new DockerFileDefault(tub, appdir, yml)
+  tc = new TubConfig(tub, appdir, yml)
   tc.create()
   tc.write()


add Quadrilateral judgement to Quadrilateral constructor.

--- unitary.coffee
+++ unitary.coffee
@@ -210,6 +210,10 @@
   constructor: (A, B, C, D) ->
     if (not (@ instanceof Quadrilateral))
       throw new Error('Constructor cannot be called as a function.')
+    if (Segment(A, B).intersects(Segment(C, D)))
+      throw new Error('ABCD is not a quadrilateral.')
+    if (equals(A, B) || equals(A, C) || equals(A, D) || equals(B, C) || equals(B, D) || equals(C, D))
+      throw new Error('ABCD is not a quadrilateral.')
     super(A, B, C, D)
   name: () -> 'Quadrilateral'
 # }}}


チャンネルのpurposeはバグがよく発生するのでなかったことに。

チャンネルのpurposeはバグがよく発生するのでなかったことに。

--- scripts/notification.coffee
+++ scripts/notification.coffee
@@ -9,13 +9,8 @@
     message = JSON.parse msg
     switch message.type
       when 'channel_created'
-        setTimeout ->
-          creator = robot.adapter.client.rtm.dataStore.getUserById(message.channel.creator)
-          channel = robot.adapter.client.rtm.dataStore.getChannelById(message.channel.id)
-          purpose = ""
-          purpose = channel.purpose.value if channel.purpose.value?
-          robot.send {room: process.env.ROOM_GENERAL}, "新しいチャンネルができたよ！ ( ##{message.channel.name} ) By #{creator.real_name}\nどんなところ? -> #{purpose}"
-        ,10000
+        creator = robot.adapter.client.rtm.dataStore.getUserById(message.channel.creator)
+        robot.send {room: process.env.ROOM_GENERAL}, "新しいチャンネルができたよ！ ( ##{message.channel.name} ) By #{creator.real_name}"
       when 'emoji_changed'
         break unless message.subtype is 'add'
         robot.send {room: process.env.ROOM_GENERAL}, "新しい絵文字か追加されたよ!\n どんなの? -> :#{message.name}: [#{message.name}]\nちなみに追加は<https://c-a-c.slack.com/customize/emoji|ここ>からできるよ"


Cleaner syntax

Cleaner syntax

--- src/client/messenger/note_messenger.coffee
+++ src/client/messenger/note_messenger.coffee
@@ -1,4 +1,4 @@
-@NoteMessenger = class NoteMessenger
+class @NoteMessenger
   @MESSAGE_SENT = 'message-sent'
   @NOTE_START = 'start'
   @NOTE_CONTINUE = 'continue'


prepare for broker architecture change

prepare for broker architecture change

--- node_modules_koding/bongo-client/src/eventbus.coffee
+++ node_modules_koding/bongo-client/src/eventbus.coffee
@@ -13,11 +13,13 @@

   getRoutingKey =(oid, event)-> "oid.#{oid}.event.#{event}"

+  getGenericRoutingKey =(oid)-> "oid.#{oid}.event"
+
   on:(inst, event, listener)->
     oid = inst.getId()
     unless @oids[oid]?
       @oids[oid] = 0
-      @channel.on getRoutingKey(oid, '#'), @dispatch.bind(@)
+      @channel.on getRoutingKey(oid), @dispatch.bind(@)
     @oids[oid]++
     @tree.on getRoutingKey(oid, event), listener


Use simpler method for retrieving the requested query ID

--- src/coffee/cilantro/ui/query/loader.coffee
+++ src/coffee/cilantro/ui/query/loader.coffee
@@ -30,10 +30,8 @@

             @data.queries.on 'sync', @loadRequestedQuery

-        onRouterLoad: (router, fragment, args...) =>
-            @requestedQueryId = null
-            if args? and args[0]?
-                @requestedQueryId = parseInt(args[0])
+        onRouterLoad: (router, fragment, id) =>
+            @requestedQueryId = parseInt(id) or null

         loadRequestedQuery: =>
             if @requestedQueryId?


Increase default width/height

Increase default width/height

--- src/index.cjsx
+++ src/index.cjsx
@@ -5,8 +5,8 @@

   getDefaultProps: ->
     return {
-      width: 50
-      height: 10
+      width: 100
+      height: 16
       strokeColor: 'black'
       strokeWidth: '0.5px'
       interpolate: 'basis'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -21,7 +21,7 @@
       width: 30
       height: 20
       activeBounds: Rectangle
-
+        x:

 ActiveBounds module
 @name ActiveBounds


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -26,7 +26,8 @@
         width: 100
         height 100

-    # => bullet2 will n
+    # => bullet2 will be removed when it
+

 ActiveBounds module
 @name ActiveBounds


Considerations for private bots

Considerations for private bots

If I run my bot in a public place, like freenode, I want to be able to control exactly what rooms it joins.

Just walking into any room someone invites him into and talking with strangers is bad :)

--- src/irc.coffee
+++ src/irc.coffee
@@ -187,6 +187,9 @@

     bot.addListener 'pm', (nick, message) ->
       console.log('Got private message from %s: %s', nick, message)
+
+      if process.env.HUBOT_IRC_PRIVATE
+        return

       nameLength = options.nick.length
       if message.slice(0, nameLength).toLowerCase() != options.nick.toLowerCase()
@@ -209,7 +212,9 @@

     bot.addListener 'invite', (channel, from) ->
       console.log('%s invite you to join %s', from, channel)
-      bot.join channel
+
+      if not process.env.HUBOT_IRC_PRIVATE
+        bot.join channel

     @bot = bot


This is a known constant

This is a known constant

--- src/lib.coffee
+++ src/lib.coffee
@@ -1,7 +1,7 @@
 ###
 TODO: description

-exposed through window.TminusLib or require(<this file>):
+exposed through window.TminusLib or require('lib-tminus'):
     createCountdownWithEpoch: (Number, DOMElement | Array(DOMElement) [, Options]) -> countdown.StateWindow
         @param 1 Target date in seconds since epoch
         @param 2 Either a single or list of DOMElements which will be parsed for tminus attributes


Oghma/Ext/Table.coffee: Fix occasional mouse up bug by resetting focus after processing event.

--- Oghma/Ext/Table.coffee
+++ Oghma/Ext/Table.coffee
@@ -45,8 +45,8 @@
         null
       )
       @getEl().on( 'mouseup', ( e ) =>
+        @up_dropper( @t( [ e.getX(), e.getY() ] )..., e )
         O.reset_focus()
-        @up_dropper( @t( [ e.getX(), e.getY() ] )..., e )
         null
       )
       @getEl().on( 'mousemove', ( e ) =>


ComputeController: do not assign local variable in function definition

[completes #87346764]

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -230,7 +230,7 @@
       return err

-  destroy: (machine, force = @_force)->
+  destroy: (machine, force)->

     destroy = (machine)=>

@@ -257,7 +257,7 @@

         (@errorHandler call, 'destroy', machine) err

-    return destroy machine  if force?
+    return destroy machine  if force or @_force

     ComputeController
       .UI.askFor 'destroy', machine, force, =>


clicking New Address button slides menu open

--- app/assets/javascripts/backbone/apps/addresses/index/index_view.js.coffee
+++ app/assets/javascripts/backbone/apps/addresses/index/index_view.js.coffee
@@ -22,6 +22,16 @@
   class Index.Header extends App.Views.ItemView
     template: 'addresses/index/header'
     id: 'address-header'
+
+    ui:
+      newAddressBtn: '.add-new a'
+
+    events:
+      'click @ui.newAddressBtn': '_clickNewAddress'
+
+    _clickNewAddress: (event) ->
+      event.preventDefault()
+      App.vent.trigger 'toggle:addresses:form'

   ##############################################################################
@@ -218,6 +228,7 @@
       'click @ui.btnCancel': '_clickCancel'

     initialize: ->
+      @listenTo App.vent, 'toggle:addresses:form', @_toggle
       @listenTo App.vent, 'scan:qr', @_scanQr

     _keydownInput:
@@ -263,6 +274,9 @@
         if @ui.inputAddress.val().length is 0
           @model.clear()
           @_clearErrors()
+
+    _toggle: ->
+      @$el.slideToggle()

     _scanQr: ->
       public_address = @ui.inputAddress.val()


Rename MockFunction.check_mock_usage() to check_usage()

--- src/TinyMock.coffee
+++ src/TinyMock.coffee
@@ -34,7 +34,7 @@
     @_mock_methods = undefined

   load_args: (args) ->
-    @check_mock_usage(args)
+    @check_usage(args)
     @parse_args(args)
     @check_expects_method_name()

@@ -54,7 +54,7 @@
     @_mock_methods.restore_original_methods()
     @uninstall_expects_method()

-  check_mock_usage: (args) ->
+  check_usage: (args) ->
     switch args.length
       when 1
         fail(messages.MockUsage) unless is_function(args[0])


compute/storage: automatically emit change events

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/providers/computestorage.coffee
+++ client/app/lib/providers/computestorage.coffee
@@ -23,10 +23,6 @@
         machines.map (machine) ->
           if machine.ಠ_ಠ then machine else remote.revive machine

-      postPush: -> @emit 'change'
-      postPop: -> @emit 'change'
-
-
     stacks       :
       collection : 'JComputeStack'
       payload    : 'userStacks'
@@ -35,7 +31,6 @@
           @pop 'machines', '_id', machine._id  for machine in cached.machines

       postPop    : (stackId) ->
-        @emit 'change'
         { reactor } = kd.singletons
         reactor.dispatch actions.REMOVE_STACK, stackId

@@ -43,7 +38,6 @@
         @push 'machines', machine  for machine in stack.machines

       postPush   : (stack) ->
-        @emit 'change'
         { reactor } = kd.singletons
         reactor.dispatch actions.LOAD_USER_ENVIRONMENT_SUCCESS, stack.machines
         reactor.dispatch actions.LOAD_USER_STACKS_SUCCESS, [ stack ]
@@ -68,14 +62,8 @@
       prePush    : (template) ->
         template.on 'update', kd.noop

-      postPush: -> @emit 'change'
-      postPop: -> @emit 'change'
-
     credentials  :
       collection : 'JCredential'
-
-      postPush: -> @emit 'change'
-      postPop: -> @emit 'change'

   constructor: ->
@@ -185,6 +173,10 @@

     postPush?.call this, value

+    @emit 'change', @storage
+    @emit "change:#{type}", { operation: 'push', value }
+
+
     return value

@@ -209,6 +201,9 @@
       item[key] isnt value

     postPop?.call this, value
+
+    @emit 'change', @storage
+    @emit "change:#{type}", { operation: 'pop', value }

     debug 'after pop', @storage

@@ -257,5 +252,4 @@
     for item, index in (@get type)
       if item._id is key
         _.extend @storage[type][index], value
-        @emit 'change'
         return @storage[type][index]


video: start using signals for start/end

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -150,6 +150,18 @@

       @emit eventName

+    session.on 'signal:end', =>
+      @stopPublishing
+        success : @bound 'handleStopSuccess'
+        error   : (err) =>
+
+    session.on 'signal:start', =>
+      options = { publishAudio: @isMySession(), publishVideo: @isMySession() }
+      @startPublishing options,
+        success : @bound 'handlePublishSuccess'
+        error   : (err) =>
+
+
   subscribeToStream: (session, stream) ->

     helper.subscribeToStream session, stream, @getView().getContainer(),
@@ -278,9 +290,8 @@
   ###
   start: (options) ->

-    @startPublishing options,
-      success : @bound 'handlePublishSuccess'
-      error   : (err) =>
+    @_service.sendSessionStartSignal @channel, (err) =>
+      return console.error err  if err

   ###*
@@ -289,9 +300,8 @@
   ###
   end: ->

-    @stopSession
-      success : @bound 'handleStopSuccess'
-      error   : (err) =>
+    @_service.sendSessionEndSignal @channel, (err) =>
+      return console.error err  if err

   ###*


IDE:TerminalPane: support session option

--- client/ide/workspace/panes/terminalpane.coffee
+++ client/ide/workspace/panes/terminalpane.coffee
@@ -8,9 +8,10 @@

     super options, data

-    {@machine} = @getOptions()
+    {@machine, @session} = @getOptions()

     @createTerminal()
+

   createTerminal: ->
     @webtermView       = new WebTermView
@@ -18,6 +19,7 @@
       readOnly         : @getOption 'readOnly'
       machine          : @machine
       mode             : @getMode()
+      session          : @session
       cssClass         : 'webterm'
       advancedSettings : no

@@ -34,13 +36,17 @@
     @webtermView.connectToTerminal()

     @webtermView.once "WebTerm.terminated", =>
+
       paneView = @parent
       tabView  = paneView.parent

       tabView.removePane paneView

+      @machine.getBaseKite().fetchTerminalSessions()
+
+
   getMode: ->
-    return 'create'
+    return  if @session? then 'resume' else 'create'

   runCommand: (command, callback) ->
     return unless command


Fix break in tests caused by empty formula fragment for custom relationship type

--- src/code/models/relation-factory.coffee
+++ src/code/models/relation-factory.coffee
@@ -91,7 +91,7 @@
     id: 5
     text: tr "~NODE-RELATION-EDIT.CUSTOM"
     postfixIco: "cus"
-    formulaFrag: ""
+    formulaFrag: "~"
     magnitude: 2
     gradual: 0
     className: "option-custom"
@@ -129,11 +129,12 @@
     scalar = _.find @scalars, (s) ->
       _.endsWith relation.formula, s.formulaFrag
     useCustomData = false
-    if @customRelation vector
-      scalar = @custom
-      useCustomData = true
-    else if scalar == @custom
-      scalar = @aboutTheSame
+    if vector?
+      if @customRelation vector
+        scalar = @custom
+        useCustomData = true
+      else if scalar == @custom
+        scalar = @aboutTheSame
     magnitude = 0
     gradual = 0
     if vector && scalar


Compression of map data

Compression of map data

--- src/map.coffee
+++ src/map.coffee
@@ -1,5 +1,6 @@
 _ = require 'lodash'
-{arrayRemove} = require './util'
+{arrayRemove, repeat} = require './util'
+log = require './log'

 filter = (e, filter) ->
 	switch
@@ -48,11 +49,64 @@

 		@data[y]?[x]?.seeThrough ? yes

-	loadFromJSON: ({@id, @w, @h, @data, @positions, entities}) ->
+	@compressData: (data) ->
+		tileTable = []
+		tileData = []
+
+		findInTable = (tile) =>
+			_.findIndex tileTable, ((t) -> _.isEqual t, tile), @
+
+		currentTile = -1
+		currentCount = 0
+
+		for row, y in data
+			for tile, x in row
+				tti = findInTable tile
+
+				if tti < 0
+					tti = tileTable.length
+					tileTable.push tile
+
+				if tti isnt currentTile
+					tileData.push [currentTile, currentCount] if currentTile >= 0
+
+					currentTile = tti
+					currentCount = 1
+
+				else currentCount++
+
+		tileData.push [currentTile, currentCount]
+
+		{tileTable, tileData, rleWidth: data[0].length}
+
+	@decompressData: ({tileTable, tileData, rleWidth}) ->
+		if rleWidth?
+			data = _.flatten(
+				for [tti, count] in tileData
+					repeat count, tileTable[tti]
+			)
+
+			tileData = for i in [0...data.length] by rleWidth
+				data[i...i+rleWidth]
+
+			tileData
+
+		else
+			for row, y in tileData
+				for tti, x in row
+					tileTable[tti]
+
+	loadFromJSON: ({@id, @w, @h, data, @positions, entities}) ->
+		if data.tileTable? and data.tileData?
+			@data = Map.decompressData data
+
+		else @data = data
+
 		@addEntity entities...

 	toJSON: ->
 		{
-			@id, @w, @h, @data, @positions
+			@id, @w, @h, @positions
+			data: Map.compressData @data
 			entities: @entities.filter (e) -> not e.isPlayer()
 		}


Use ForGroup to render display fields

Use ForGroup to render display fields

--- client/lanes/components/shared/FieldMixin.cjsx
+++ client/lanes/components/shared/FieldMixin.cjsx
@@ -47,28 +47,21 @@
         {value}

     formGroupClassNames: ->
-        _.classnames('form-group',
+        _.classnames(
             _.result(this, 'formGroupClass')
-            editing: @isEditingRecord()
         )

     _mixinRenderValue: (label, className) ->
         value = (@renderDisplayValue || @renderMixinDisplayValue)?()
+        className = _.classnames(@props.className, className)
         if @props.unstyled
             value
         else
-            className = _.classnames(className, "value", changeset: @state.changeset)
-            colProps = _.omit(@props, 'name', 'label')
-            <BS.Col {...colProps}>
-

-
-
{label}

-

-                            {value}
-

-
-

-            </BS.Col>
+            <LC.FormGroup display {...@props}
+                className={className} label={label}
+            >
+                {value}
+            </LC.FormGroup>

     render: ->
         unless @props.unlabeled


use list reporter on CI to check test run times

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -16,7 +16,7 @@
       ci:
         options:
           cmd: "./bin/barrier"
-          reporter: "spec"
+          reporter: "list"

         src: ["test/helper.coffee", "test/**/*_test.coffee"]


ext-ariagrid will eventually be part of standard dist

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -57,6 +57,7 @@
                 "<%= meta.banner %>"
                 # "lib/intro.js"
                 "src/jquery.fancytree.js"
+#                "build/jquery.fancytree.ariagrid.js"
                 "src/jquery.fancytree.childcounter.js"
                 "src/jquery.fancytree.clones.js"
 #                "src/jquery.fancytree.columnview.js"
@@ -92,6 +93,7 @@
             src: [
                 "lib/intro.js"
                 "build/jquery.fancytree.min.js"
+#                "build/jquery.fancytree.ariagrid.min.js"
                 "build/jquery.fancytree.childcounter.min.js"
                 "build/jquery.fancytree.clones.min.js"
 #                "build/jquery.fancytree.columnview.min.js"


update esteDeps option, output_file to outputFile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -71,7 +71,7 @@
         options:
           depsWriterPath: 'bower_components/closure-library/closure/bin/build/depswriter.py'
           # consider rename to outputFile
-          output_file: depsPath
+          outputFile: depsPath
           prefix: depsPrefix
           root: appDirs


chore(Gruntfile): Updated tasks to copy files to bower repositories

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,5 +1,4 @@
 server = require "./server"
-path   = require "path"

 module.exports = (grunt) ->
   require('matchdep').filterDev('grunt-*').forEach grunt.loadNpmTasks
@@ -107,7 +106,11 @@
       moduleCss:
         files: [
           {
-            dest: "build/bower-macgyver/<%= pkg.name.toLowerCase() %>-core.css"
+            dest: "build/bower-macgyver/<%= pkg.name.toLowerCase() %>.css"
+            src: "<%= buildConf.css.example %>"
+          }
+          {
+            dest: "build/bower-macgyver-core/<%= pkg.name.toLowerCase() %>-core.css"
             src: "<%= buildConf.css.core %>"
           }
           {
@@ -187,8 +190,12 @@
       bower:
         files: [
           {
+            src: "lib/<%= pkg.name.toLowerCase() %>.js"
+            dest: "build/bower-macgyver/<%= pkg.name.toLowerCase() %>.js"
+          }
+          {
             src: "lib/<%= pkg.name.toLowerCase() %>-core.js"
-            dest: "build/bower-macgyver/macgyver-core.js"
+            dest: "build/bower-macgyver-core/macgyver-core.js"
           }
           {
             src: "lib/<%= pkg.name.toLowerCase() %>-datepicker.js"
@@ -216,7 +223,7 @@

     uglify:
       options:
-        report:           "gzip"
+        report:           "min"
         preserveComments: false
       dist:
         files: [
@@ -224,9 +231,16 @@
             src: "lib/<%= pkg.name.toLowerCase() %>.js"
             dest: "lib/<%= pkg.name.toLowerCase() %>.min.js"
           }
+        ]
+      bower:
+        files: [
+          {
+            src: "lib/<%= pkg.name.toLowerCase() %>.js"
+            dest: "build/bower-macgyver/macgyver.min.js"
+          }
           {
             src: "lib/<%= pkg.name.toLowerCase() %>-core.js"
-            dest: "build/bower-macgyver/macgyver-core.min.js"
+            dest: "build/bower-macgyver-core/macgyver-core.min.js"
           }
           {
             src: "lib/<%= pkg.name.toLowerCase() %>-datepicker.js"
@@ -314,7 +328,7 @@
         options:
           pattern: /templateUrl: "([^"]+)"/g
           replace: (match) ->
-            filePath     = path.join "example/", match[1]
+            filePath     = require("path").join "example/", match[1]
             compiledHtml = grunt.file.read filePath
             compiledHtml = compiledHtml.replace /"/g, "\\\""
             "template: \"#{compiledHtml}\""
@@ -359,6 +373,16 @@
           src:     "**/*.md"
           dest:    "docs/html"
           ext:     ".html"
+        ]
+
+    updatebuild:
+      app:
+        files: [
+          expand:  true
+          flatten: false
+          cwd:     "build"
+          src:     "**/bower.json"
+          dest:    "build"
         ]

     "gh-pages":
@@ -369,12 +393,19 @@
       src: ["**"]

   grunt.registerTask "prepare", "Prepare for deploying", ["bump", "changelog"]
+  grunt.registerTask "concatDeploy", [
+    "concat:jqueryui"
+    "concat:appJs"
+    "concat:deployAppJs"
+    "concat:modulesJs"
+    "concat:css"
+  ]

   grunt.registerTask "deploy", "Build and copy to lib/", [
       "coffee"
       "stylus"
       "jade"
-      "concat"
+      "concatDeploy"
       "clean"
       "replace:src"
       "chalkboard"
@@ -383,8 +414,9 @@
       "replace:version"
       "karma:build"
       "update:component"
-      "copy"
-      "uglify"
+      "copy:images"
+      "copy:public"
+      "uglify:dist"
       "tag"
     ]

@@ -401,6 +433,15 @@
     "replace:docs"
   ]

+  grunt.registerTask "deploy-bower", "Updated all bower repositories", [
+    "copy:bower"
+    "uglify:bower"
+    "stylus:compile"
+    "concat:moduleCss"
+    "clean"
+    "updatebuild"
+  ]
+
   grunt.registerTask "run", "Watch src and run test server", ->
     @async()


Sauce Labs integration

Sauce Labs integration

Sauce Labs crendentials should be provided as environment variables.
Please see Sauce Labs docs for instructions: https://docs.saucelabs.com/tutorials/node-js/

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -144,6 +144,21 @@
                     browsers: [
                         { browserName: 'firefox' }
                     ]
+            sauce:
+                src: ['static/test/promises-test.js']
+                options:
+                    testName: 'sauce usage test'
+                    usePromises: true
+                    reporter: 'spec'
+                    concurrency: 1
+                    secureCommands : true
+                    tunneled: true
+                    username: process.env.SAUCE_USERNAME
+                    key: process.env.SAUCE_ACCESS_KEY
+                    browsers: [
+                        {browserName: 'chrome', platform: 'Windows 7', version: '44'}
+                    ]
+
         less:
             main:
                 options:
@@ -252,3 +267,4 @@
     grunt.registerTask 'test', ['coffee:test', 'express', 'mochaWebdriver:phantom-test']
     grunt.registerTask 'chrometest', ['coffee:test', 'express', 'mochaWebdriver:chrome-test']
     grunt.registerTask 'firefoxtest', ['coffee:test', 'express', 'mochaWebdriver:firefox-test']
+    grunt.registerTask 'saucelabs', ['coffee:test', 'express', 'mochaWebdriver:sauce']


clean up gruntfile, update cssmin/sizereport task files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -40,6 +40,9 @@
       "Core":
         "Compressed stylesheet": [ rootFile + ".min.css" ]
         "Compressed javascript": [ rootFile + ".min.js" ]
+      "Stylesheets":
+        "structure": [structureFile  + ".min.css"]
+        "theme": [themeFile + ".min.css"]
       "Custom font-awesome":
         "Stylesheet": [ "compiled/customfont/fontawesome.css" ]
         "TTF fonts": [ "compiled/customfont/fontawesome-webfont.ttf" ]
@@ -95,18 +98,12 @@
       all:
         src: rootFile + ".css"
         dest: rootFile  + ".min.css"
-
-    # cssmin: cssmin
-  # CSSMIN configuration
-#    cssmin = {
-#      "compiled/umobi.min.css": "compiled/umobi.css"
-#      all: {
-#        # src: ["<%= global.ver.min %>", rootFile + ".css"]
-#      }
-#    }
-  # cssmin.all[ "#{rootFile}.min.css" ]      = ["<%= global.ver.min %>", rootFile + ".css"]
-  # cssmin.all[ "#{structureFile}.min.css" ] = ["<%= global.ver.min %>", structureFile + ".css"]
-  # cssmin.all[ "#{themeFile}.min.css" ]     = ["<%= global.ver.min %>", themeFile + ".css"]
+      structure:
+        src: structureFile + ".css"
+        dest: structureFile  + ".min.css"
+      theme:
+        src: themeFile + ".css"
+        dest: themeFile + ".min.css"
     compress: readCoffee "build/compress.coffee"

     # JS config, mostly the requirejs configuration


Find all unit tests in test/ dir

Find all unit tests in test/ dir

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -283,7 +283,7 @@
       options:
         template: 'test/index.template.html'
         runner: 'test/index.html'
-        files: 'test/spec/**/*.js'
+        files: 'test/**/*.js'

     # Mocha for testing
     mocha:


Client: update -> with noop

Client: update -> with noop

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -515,7 +515,7 @@
       fnName             : 'fetchChannels'
       mapperFn           : mapChannels

-    fetchActivities      : (options = {}, callback = ->)->
+    fetchActivities      : (options = {}, callback = noop)->

       # show exempt content if only requester is admin or exempt herself
       showExempt = KD.checkFlag?("super-admin") or KD.whoami()?.isExempt


Fixup 0d6180e: pages Grunt task.

Fixup 0d6180e: pages Grunt task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -117,6 +117,7 @@

   grunt.registerTask 'pages', [
     'dist'
+    'docs'
     'clean:gh-pages'
     'copy:gh-pages'
     'markdown:gh-pages'


trailing space

trailing space

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,7 +22,7 @@
 				jshintrc:'.jshintrc'
 			}
 		}
-		compass: {
+		compass: {
 			config: 'config.rb',
 			dev: {
 				options: {
@@ -33,7 +33,7 @@
 				options: {
 					environment: 'production'
 					cssDir: 'app/dist/css'
-				}
+				}
 			}
 		},
 		sass: {
@@ -139,8 +139,8 @@
 				options: {
 					curlyTags: {
 			        	rlsdate: '<%= grunt.template.today("yyyymmdd") %>'
-				    }
-				}
+				    }
+				}
 			}
 		},
 		autoprefixer: {
@@ -180,5 +180,5 @@
 	grunt.registerTask 'server', 'launch the server', (n) ->
 		if grunt.option('dist')
 			grunt.task.run ['build', 'concurrent:server']
-		else
+		else
 			grunt.task.run ['targethtml:dev', 'concurrent:server']


chore(build): Create a tmp dir with a fake index.html on travis

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,13 +10,13 @@
   #   "appPublicDir" : "path_to_the_frontEndApp_public_dir"
   # }
   #
-  if grunt.file.exists('env.json')
+  if grunt.file.exists 'env.json'
     frontendAppPublicDir = grunt.file.readJSON('env.json').appPublicDir

   # travis fixup: on travis, the previous path obviously does not exist
   # so some tests will make explode the app. So:
-  unless grunt.file.exists
-    runt.file.mkdir 'tmpPublic'
+  unless grunt.file.exists frontendAppPublicDir
+    grunt.file.mkdir 'tmpPublic'
     grunt.file.write 'tmpPublic/index.html', '...'

     frontendAppPublicDir = './tmpPublic'


Do not run browser tdd for now

Do not run browser tdd for now

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -85,7 +85,7 @@
     @task.run 'connect'
     if target is 'all' or target is 'nodejs'
       @task.run 'mochaTest'
-    if target is 'all' or target is 'browser'
+    if target is 'browser'
       @task.run 'coffee'
       @task.run 'mocha_phantomjs'


Prevent grunt-gh-pages from removing .gitattributes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -17,6 +17,7 @@
         options:
           base: 'dist'
           message: 'Update release to v<%= pkg.version %>'
+          add: true
         src: ['**']

     # Stage 1 tasks


Add Grunt task coffee

Add Grunt task coffee

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,4 +1,15 @@
 module.exports = (grunt) ->
-	grunt.initConfig()
+	grunt.initConfig
+		coffee:
+			options:
+				bare: true
+			files:
+				expand: true
+				cwd: 'src'
+				src: ['**/*.coffee']
+				dest: 'lib'
+				ext: '.js'
+
+	grunt.loadNpmTasks 'grunt-contrib-coffee'

 	grunt.registerTask 'default', []


fix(grunt): disable `leading_quotes` for list, this could help avoid unwanted list style offset with leading quotes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -247,7 +247,7 @@

     leading_quotes:
       options:
-        elements: "p, li, h1, h2, h3, h4, h5, h6"
+        elements: "p, h1, h2, h3, h4, h5, h6"
         regex: /「|『|“|‘|（/
         class: "leading-indent-fix"
         verbose: true


Handle account token legacy for webDav account. #549

--- client/app/views/settings_modal.coffee
+++ client/app/views/settings_modal.coffee
@@ -26,6 +26,10 @@
     initialize: ->
         @model = window.webDavAccount
         if @model?
+            # See https://github.com/cozy/cozy-calendar/issues/549
+            legacyToken = @model.password
+            @model.token = @model.token or legacyToken
+
             @model.placeholder = @getPlaceholder @model.token


Better task loading format

Better task loading format

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -48,6 +48,7 @@
         files: ['src/public/js/voxel/*.js']
         tasks: ['browserify:voxel']

-  require('matchdep').filterDev('grunt-*').forEach @loadNpmTasks
+  @loadNpmTasks(task) for task in require('matchdep').filterDev('grunt-*')
+
   @registerTask 'default', ['clean', 'compass', 'browserify', 'docs']
   @registerTask 'dev', ['default', 'connect', 'watch']


Gruntfile: Removed unused tasks.

Gruntfile: Removed unused tasks.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -35,11 +35,6 @@
           ext            : '.js'
         ]

-    sprites              :
-      all                :
-        files            :
-          src            : 'src/images'
-          dest           : 'build/images'

     coffee2closure       :
       all                :
@@ -77,13 +72,6 @@
             'bower_components/closure-templates'
             'build'
           ]
-
-    tests                :
-      app                :
-        options          :
-          depsPath       : '<%= deps.all.options.outputFile %>'
-          prefix         : '<%= deps.all.options.prefix %>'
-        src              : 'build/js/**/*_test.js'

     karma                :
       headless           :
@@ -205,7 +193,6 @@
     grunt.task.run [
       'clean'
       'mkdir'
-      'sprites'
       'coffee'
       'coffee2closure'
       'stylus'
@@ -221,7 +208,6 @@
     grunt.task.run [
       'clean'
       'mkdir'
-      'sprites'
       'coffee'
       'coffee2closure'
       'stylus'
@@ -235,7 +221,6 @@
     grunt.task.run [
       'clean'
       'mkdir'
-      'sprites'
       'coffee'
       'coffee2closure'
       'stylus'
@@ -251,7 +236,6 @@
     grunt.task.run [
       'clean'
       'mkdir'
-      'sprites'
       'coffee'
       'coffee2closure'
       'stylus'


Add options to custom test task

Add options to custom test task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -64,7 +64,10 @@
                 options: {}
                 src: "test/fixtures/default"
             custom:
-                options: {}
+                options:
+                    name: "Codo-codo"
+                    title: "Codo-codo Documentation"
+                    extra: [ "LICENSE-MIT" ]
                 src: [
                     "test/fixtures/default"
                     "test/fixtures/custom/**/*.coffee"


More verbose test output with PhantomJS

More verbose test output with PhantomJS

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -133,7 +133,7 @@
     mocha_phantomjs:
       all:
         options:
-          reporter: 'dot'
+          reporter: 'node_modules/mocha/lib/reporters/spec.js'
           urls: ['http://127.0.0.1:9999/spec/runner.html']

     'saucelabs-mocha':


tests: Include React also for unit tests

Needed because the-graph expects it as global, and we now require() that

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -387,6 +387,8 @@
       all:
         options:
           scripts: [
+            "../node_modules/react/dist/react-with-addons.js"
+            "../node_modules/react-dom/dist/react-dom.js"
             "../browser/<%=pkg.name%>.js"
             '../node_modules/sinon/pkg/sinon-server.js'
           ]


tidy gruntfile"

tidy gruntfile"

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,19 +1,26 @@
 # Official Grunt tasks to load
 contribs = ['stylus', 'watch', 'jst', 'connect', 'copy', 'jshint', 'uglify', 'concat']

+vendor = 'vendor/'
+js = '.js'
+
 # Library paths
-jquery = 'vendor/jquery/dist/jquery.js'
-underscore = 'vendor/underscore/underscore.js'
-bootstrap = 'vendor/bootstrap/dist/js/bootstrap.js'
-blueimp = 'vendor/blueimp-gallery/js/jquery.blueimp-gallery.min.js'
-bbs = 'vendor/blueimp-bootstrap-image-gallery/js/bootstrap-image-gallery.min.js'
-d3 = 'vendor/d3/d3.min.js'
-dropzone = 'vendor/dropzone/downloads/dropzone.js'
-penguinator = 'vendor/penguinator/index.js'
-gmaps = 'vendor/gmaps/gmaps.js'
-modernizr = 'vendor/webshim/js-webshim/minified/extras/modernizr-custom.js'
-webshims = 'vendor/webshim/js-webshim/minified/polyfiller.js'
-alert = 'vendor/alert/alert.js'
+libs =
+  jquery: 'jquery/dist/jquery'
+  underscore: 'underscore/underscore'
+  bootstrap: 'bootstrap/dist/js/bootstrap'
+  blueimp: 'blueimp-gallery/js/jquery.blueimp-gallery.min'
+  bbs: 'blueimp-bootstrap-image-gallery/js/bootstrap-image-gallery.min'
+  d3: 'd3/d3.min'
+  dropzone: 'dropzone/downloads/dropzone'
+  penguinator: 'penguinator/index'
+  gmaps: 'gmaps/gmaps'
+  modernizr: 'webshim/js-webshim/minified/extras/modernizr-custom'
+  webshims: 'webshim/js-webshim/minified/polyfiller'
+  alert: 'alert/alert'
+
+for k, v of libs
+  libs[k] = vendor + v + js

 # Module paths
 # Compiled templates file
@@ -36,18 +43,26 @@
 user_profile = 'js/user_profile.js'

 # Files used by every page
-shared = [jquery, alert, modernizr, webshims, underscore, bootstrap, common]
+shared = [
+  libs.jquery
+  libs.alert
+  libs.modernizr
+  libs.webshims
+  libs.underscore
+  libs.bootstrap
+  common
+]

 # Mapping of HTML files to the scripts they require
 dependencies =
   'site/index.html': [main]
-  'site/start-job.html': [blueimp, bbs, start_job]
+  'site/start-job.html': [libs.blueimp, libs.bbs, start_job]
   'site/define-form.html': [define_form]
   'site/view-jobs.html': [dashboard]
-  'site/upload/image.html': [dropzone, penguinator, blueimp, bbs, gmaps, image_upload, map]
-  'site/upload/executable.html': [dropzone, executable_upload]
+  'site/upload/image.html': [libs.dropzone, libs.penguinator, libs.blueimp, libs.bbs, libs.gmaps, image_upload, map]
+  'site/upload/executable.html': [libs.dropzone, executable_upload]
   'site/settings.html': [settings]
-  'site/graphs.html': [d3, bar_chart, graphs]
+  'site/graphs.html': [libs.d3, bar_chart, graphs]
   'site/user-profile.html': [user_profile]

 # Add the shared dependencies to every page


Gruntfile: Fixed open target path for coverage target.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -177,7 +177,7 @@
         path             : 'http://localhost:1111/public/debug'
         app              : 'Google Chrome'
       coverage           :
-        path             : 'http://localhost:1111/build/coverage/PhantomJS 1.9.7 (Mac OS X)/lcov-report/index.html'
+        path             : 'http://localhost:1111/build/coverage/PhantomJS 1.9.8 (Mac OS X)/lcov-report/index.html'
         app              : 'Google Chrome'
       build              :
         path             : 'http://localhost:2222/public/compiled'


Make watch monitor for svg changes too

Make watch monitor for svg changes too

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -43,7 +43,7 @@

       watch:
          less:
-            files: ["*.less"],
+            files: ["*.less", "imgs/*.svg"],
             tasks: ["default"]
    })


Compile before linting

Compile before linting

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -34,5 +34,5 @@

   grunt.registerTask 'clean', -> require('rimraf').sync('lib')
   grunt.registerTask('lint', ['coffeelint:src', 'coffeelint:test'])
-  grunt.registerTask('default', ['coffeelint:src', 'coffee'])
+  grunt.registerTask('default', ['coffee', 'coffeelint:src'])
   grunt.registerTask('test', ['default', 'coffeelint:test', 'shell:test'])


Banner in JS Files

Banner in JS Files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -14,10 +14,19 @@
   userConfig = require "#{__dirname}/client/build.config"

   grunt.initConfig
+    pkg: grunt.file.readJSON('package.json')
+
+    banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
+      '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
+      '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
+      '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>\n'+
+      '* Many thanks to all those who made this possible by contributing to open source software!'+
+      '*/\n'
+
     clean:
       all: [
-        '.tmp/**/*'
-        'public/*'
+        '.tmp'
+        'public'
       ]

     coffee:
@@ -112,12 +121,14 @@

     concat:
       precompile:
-        src: userConfig.getLocalFiles('production', userConfig.app, 'client/').concat [
+        src: userConfig.getLocalFiles('development', userConfig.app, 'client/').concat [
           '.tmp/js/app/**/*.js'
         ]
         dest: 'public/js/app.js'

     uglify:
+      options:
+        banner: '<%= banner %>'
       precompile:
         files:
           'public/js/app.js': ['public/js/app.js']


Remove the test examples

Remove the test examples

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -91,6 +91,7 @@
                       'bower_components'
                       'bower_components/reveal.js/plugin/leap'
                       'bower_components/reveal.js/test'
+                      'bower_components/reveal.js/test/examples'
                       'bower_components/reveal.js/plugin/math'
                       'bower_components/reveal.js/plugin/print-pdf'
                     ]


begin refactor for organization by module

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -148,13 +148,15 @@
 				expand: true
 			prod:
 				files: [
+					# images
 					cwd: '.temp/'
-					src: 'fonts/**'
+					src: '**/*.{eot,svg,ttf,woff}'
 					dest: 'dist/'
 					expand: true
 				,
+					# fonts
 					cwd: '.temp/'
-					src: 'images/**'
+					src: '**/*.{gif,jpeg,jpg,png,svg,webp}'
 					dest: 'dist/'
 					expand: true
 				,
@@ -181,7 +183,7 @@
 		# glyphicons-halflings.png -> glyphicons-halflings.6c8829cc6f.png
 		# scripts.min.js -> scripts.min.6c355e03ee.js
 		hash:
-			images: '.temp/images/**/*'
+			images: '.temp/**/*.{gif,jpeg,jpg,png,svg,webp}'
 			scripts:
 				cwd: '.temp/scripts/'
 				src: [
@@ -196,7 +198,7 @@
 			images:
 				files: [
 					cwd: '.temp/'
-					src: 'images/**/*.png'
+					src: '**/*.{gif,jpeg,jpg,png}'
 					dest: '.temp/'
 					expand: true
 				]
@@ -207,7 +209,7 @@
 		jade:
 			views:
 				cwd: '.temp/'
-				src: '**/views/*.jade'
+				src: '**/*.jade'
 				dest: '.temp/'
 				expand: true
 				ext: '.html'
@@ -236,8 +238,8 @@
 						'dist/scripts/libs/angular-animate.js'
 						'dist/scripts/libs/angular-route.js'
 						'bower_components/angular-mocks/angular-mocks.js'
-						'dist/scripts/**/*.js'
-						'test/scripts/**/*.{coffee,js}'
+						'dist/**/*.js'
+						'test/**/*.{coffee,js}'
 					]
 					frameworks: [
 						'jasmine'
@@ -291,7 +293,7 @@
 		ngTemplateCache:
 			views:
 				files:
-					'.temp/scripts/views.js': '.temp/views/**/*.html'
+					'.temp/scripts/views.js': '.temp/**/*.html'
 				options:
 					trim: '.temp'

@@ -434,7 +436,7 @@
 					'src/images/**'
 					'src/scripts/**/*.js'
 					'src/styles/**/*.css'
-					'src/views/**/*.html'
+					'src/**/*.html'
 				]
 				tasks: [
 					'copy:app'


refining build process... again... NPM hates alchemy...

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -66,8 +66,6 @@
         command: "git add -A && git commit -am 'commit dist files for #{pkg.version}'"
       docs:
         command: 'grunt --gruntfile site/Gruntfile.coffee'
-      loadEnvVariables:
-        command: 'source s3.sh'

     # Watches files for changes and runs tasks based on the changed files
     watch:
@@ -502,7 +500,6 @@
        "archiveDist", # create archive of files to zip for github release
        "concat:s3", # squash vendor and alchemy files for cdn
        "concat:s3Version", # apply version numbers for cdn
-       "shell:loadEnvVariables", # load aws keys for deployment
        "s3:production" # publish files to s3 for cdn
        "shell:docs", # publish docs
       ]


add build task, and changed lang task (#196)

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -4,7 +4,6 @@
   # Project configuration.
   grunt.initConfig
       pkg: grunt.file.readJSON 'package.json'
-
       jshint:
         core: ['dev/src/*.js', '!dev/src/_*.js']
         plugins: ['dev/src/*.js', '!dev/src/_*.js', 'dev/plugins/*.js']
@@ -89,14 +88,14 @@
           dest: 'dev/enchant.js'
           options:
             banner: """
-                                                            /**
-                                                             * <%= pkg.name %> v<%= pkg.version %>
-                                                             * <%= pkg.homepage %>
-                                                             *
-                                                             * Copyright Ubiquitous Entertainment Inc.
-                                                             * Released under the MIT license.
-                                                             */\n\n
-                                                            """
+                                                                        /**
+                                                                         * <%= pkg.name %> v<%= pkg.version %>
+                                                                         * <%= pkg.homepage %>
+                                                                         *
+                                                                         * Copyright Ubiquitous Entertainment Inc.
+                                                                         * Released under the MIT license.
+                                                                         */\n\n
+                                                                        """

       uglify:
         dist:
@@ -135,7 +134,8 @@

   grunt.registerTask 'doc', 'Make jsdoc', ()->
     done = this.async
-    sh = require('child_process').exec;
+    sh = require('child_process').exec
+    ;

     sh 'java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js ja/enchant.js -t=doc/template -d=doc/core/ja'
     sh 'java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js enchant.js -t=doc/template -d=doc/core/en'
@@ -144,36 +144,38 @@
     sh 'java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js enchant.js plugins/*.js -t=doc/template -d=doc/plugins/en'
     sh 'java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js de/enchant.js de/plugins/*.js -t=doc/template -d=doc/plugins/de'

+  grunt.registerTask 'build', '', ()->
+    fs = require('fs')
+    grunt.task.run('lang:en')
+    grunt.file.delete('./build', {force: true})
+
+    grunt.file.recurse('./lang/en', (abspath, rootdir, subdir, filename)->
+      path = (subdir || '') + '/' + filename
+      grunt.file.copy('./lang/en/' + path, 'build/' + path)
+    )
+
+
   grunt.registerTask 'lang', 'Make lang files.', ()->
+    lang = @args[0] || 'en'
+    path = @args[1] || 'lang/' + lang;
     grunt.log.writeln 'processing..'
     done = @async
     fs = require('fs')

+    grunt.file.mkdir('lang/' + lang + '/plugins')
+
     make_repl = (lang) ->
       new RegExp('^[\\*\\s]*\\[lang:' + lang + '\\]\\n([\\s\\S]*?)^[\\*\\s]*\\[\\/lang\\]\\n', 'mg')
-
+    repl_ja = make_repl 'ja'
     repl_en = make_repl 'en'
-    repl_ja = make_repl 'ja'
     repl_de = make_repl 'de'

     (glob.sync "dev/{plugins\/,}*.js").forEach (source)->
-      dist_en = source.toString().replace(/dev\//, './')
-      dist_ja = source.toString().replace(/dev\//, './ja/')
-      dist_de = source.toString().replace(/dev\//, './de/')
+      dist = source.toString().replace(/dev\//, './lang/' +lang+ '/')

-      fs.writeFileSync dist_en, (fs.readFileSync source).toString()
-        .replace(repl_en, '$1')
-        .replace(repl_ja, '')
-        .replace(repl_de, '')
-
-      fs.writeFileSync dist_ja, (fs.readFileSync source).toString()
-        .replace(repl_en, '')
-        .replace(repl_ja, '$1')
-        .replace(repl_de, '')
-
-      fs.writeFileSync dist_de, (fs.readFileSync source).toString()
-        .replace(repl_en, '')
-        .replace(repl_ja, '')
-        .replace(repl_de, '$1')
+      fs.writeFileSync dist, (fs.readFileSync source).toString()
+      .replace(repl_en, (if lang == 'en' then '$1' else ''))
+      .replace(repl_ja, (if lang == 'ja' then '$1' else ''))
+      .replace(repl_de, (if lang == 'de' then '$1' else ''))

       grunt.log.writeln(source)


As this target is executed in both task it should be named more generic and reflect what it does

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -87,7 +87,7 @@
       afterFontgen: [
         '<%= webAssetsPath %>/fonts/*.css'  # remove all generated css-files as they are obsolete
       ]
-      afterGenerateCssForProduction: [
+      afterGenerateCss: [
         '.sass-cache'
       ]
       beforeGenerateIconFonts: [
@@ -295,14 +295,14 @@
   grunt.registerTask 'generateCssForDev', [
     'compass'
     'autoprefixer'
-    'clean:afterGenerateCssForProduction'
+    'clean:afterGenerateCss'
   ]

   # Generate the css-files for live
   grunt.registerTask 'generateCssForProduction', [
     'compass:production'
     'autoprefixer'
-    'clean:afterGenerateCssForProduction'
+    'clean:afterGenerateCss'
   ]

   # Generate the js files


feat(grunt): disable `removeEmptyElements` by default since it causes too much troble in the past, also props @Cee, should fix https://github.com/sparanoid/almace-scaffolding/issues/56

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -179,7 +179,7 @@
           useShortDoctype: false
           removeEmptyAttributes: true
           removeOptionalTags: true
-          removeEmptyElements: true
+          removeEmptyElements: false
           lint: false
           keepClosingSlash: false
           caseSensitive: true


Update src path

Update src path

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -111,7 +111,7 @@
     requirejs:
       compile:
         options:
-          appDir: 'site'
+          appDir: 'src'
           baseUrl: 'scripts'
           dir: 'dist'
           mainConfigFile: 'src/scripts/config.js'


Add task for testing in real browsers

Add task for testing in real browsers

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -42,11 +42,15 @@
           rename: coffeeRename
         ]
     connect:
+      options:
+        port: TEST_SERVER_PORT
+        base: '.'
       tests:
         options:
-          port: TEST_SERVER_PORT
-          base: '.'
-          keepalive: grunt.option('keepalive')?
+          keepalive: false
+      testskeepalive:
+        options:
+          keepalive: true
     express:
       testserver:
         options:
@@ -101,9 +105,10 @@
   grunt.registerTask 'build:browsertests', ['coffee:browsertests']
   grunt.registerTask 'build:standalone', ['build:node', 'browserify:standalone', 'uglify:browserbuilds']
   grunt.registerTask 'default', ['build']
-  grunt.registerTask 'test:browser', ['build:browsertests', 'build:standalone', 'runtestserver', 'connect:tests', 'mocha']
+  grunt.registerTask 'test:phantom', ['build:browsertests', 'build:standalone', 'runtestserver', 'connect:tests', 'mocha']
+  grunt.registerTask 'test:browser', ['build:browsertests', 'build:standalone', 'runtestserver', 'connect:testskeepalive', 'express-keepalive']
   grunt.registerTask 'test:server', ['runtestserver', 'settestglobals', 'mochaTest']
-  grunt.registerTask 'test', ['test:browser', 'settestglobals', 'mochaTest']
+  grunt.registerTask 'test', ['test:phantom', 'settestglobals', 'mochaTest']
   grunt.registerTask 'runtestserver', ['express:testserver']
   grunt.registerTask 'settestglobals', ->
     # Sets globals for the server tests so we can use the same module for


feat(grunt): `cacheBust` only css and js

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -304,7 +304,7 @@
     cacheBust:
       options:
         algorithm: "md5"
-        assets: ["<%= amsf.user.assets %>/**/*"]
+        assets: ["**/*.css", "**/*.js"]
         baseDir: "<%= config.dist %>"
         deleteOriginals: true
         encoding: "utf8"


minor code style fix

minor code style fix

--- src/builder/index.coffee
+++ src/builder/index.coffee
@@ -239,7 +239,7 @@

     _resolve: (module, dep) ->
         for alias, prefix of @paths
-            continue if not _.startsWith(dep, alias)
+            continue unless _.startsWith(dep, alias)
             if (dep[alias.length] || "/") is "/"
                 dep = dep.replace(alias, prefix)
                 break


Monitor event loop by looking for skew

Monitor event loop by looking for skew

If we monitor with setImmediate, we miss big blocking loops. For example,
suppose we have 1000 1ms loops then a single bad 1000ms loop. setImmediate
will only be called at the right time 1/1000 of the time (it has to be the
loop just before the bad one). So this monitoring method gives a good average
if the std dev is low, but doesn't pick up spikes.

Instead, we can monitor the skew from the expected time between setIntervals.
In the case above, with a setInterval for 1000ms, we will pick up a skew
proportional to the amount of time that it overlaps the bad loop. So 50%
change of picking up skew > 500ms, and thus getting a good sense of any
spikes.

--- event_loop.coffee
+++ event_loop.coffee
@@ -1,18 +1,16 @@
-seconds = 1000
-
 module.exports = EventLoopMonitor =
-	monitor: (logger) ->
-		interval = setInterval () ->
-			EventLoopMonitor.Delay()
-		, 1 * seconds
+	monitor: (logger, interval = 1000, log_threshold = 100) ->
 		Metrics = require "./metrics"
+
+		previous = Date.now()
+		intervalId = setInterval () ->
+			now = Date.now()
+			offset = now - previous - interval
+			if offset > log_threshold
+				logger.warn {offset: offset}, "slow event loop"
+			previous = now
+			Metrics.timing("event-loop-millsec", offset)
+		, interval
+
 		Metrics.registerDestructor () ->
-			clearInterval(interval)
-
-	Delay: () ->
-		Metrics = require "./metrics"
-		t1 = process.hrtime()
-		setImmediate () ->
-			delta = process.hrtime(t1)
-			responseTime = delta[0]*1e6 + delta[1]*1e-3
-			Metrics.timing("event-loop-microsec", responseTime)
+			clearInterval(intervalId)


fixtypo

fixtypo

--- core/model/account.coffee
+++ core/model/account.coffee
@@ -25,7 +25,7 @@
     setting:
       avatar_url: crypto.createHash('md5').update(email).digest('hex')
     attribure:
-      plnas: []
+      plans: []
     tokens: []
   , {}, callback


test(e2e): Fixed e2e test not compiling jade

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -92,6 +92,7 @@
     "clean"
     "coffee"
     "stylus"
+    "jade"
     "concatDeploy"
     "clean"
     "replace:src"


Updated Gruntfile to reload HTML and PHP files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -18,7 +18,7 @@

             template:
                 files: ['<%= pkg.path %>*.<%= pkg.fileType %>']
-                tasks: ['replace:dev']
+                tasks: ['htmlmin:dev', 'replace:dev']
                 options:
                     livereload: true,

@@ -29,7 +29,7 @@
                     '<%= pkg.path %>assets/js/*.js',
                     '<%= pkg.path %>assets/css/*.css',
                     '<%= pkg.path %>assets/img/{,**/}*.{png,jpg,jpeg,gif,webp,svg}',
-                    '<%= pkg.path %>*.<%= pkg.fileType %>'
+                    '<%= pkg.devPath %>*.<%= pkg.fileType %>'
                 ]

         coffee:


Stop recursive concatenation

Stop recursive concatenation

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,12 +10,12 @@
         expand: true
         cwd: 'test'
         src: '**/*.coffee'
-        dest: 'public/test/js'
+        dest: 'public/test/js/src'
         ext: '.js'

     concat:
       tests:
-        src: [ 'public/test/js/**/*.js' ]
+        src: 'public/test/js/src/**/*.js'
         dest: 'public/test/js/tests.js'

     shell:


grunt watch 中にもテストできるように

grunt watch 中にもテストできるように

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -50,7 +50,8 @@
       files: 'dist'
     watch:
       files: ['**/*.coffee','**/*.html']
-      tasks: ['coffee']
+      # tasks: ['coffee']
+      tasks: ['coffee','clean','concat','uglify']
       options:
         livereload: true
     connect:


removed server, the page must be viewed from the actual HTML document

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -108,19 +108,6 @@
       jade:
         files: ['<%= config.dir.app.source %>/**/*.jade']
         tasks: ['newer:jade:compile']
-    connect:
-      live:
-        options:
-          hostname: '<%= config.server.hostname %>',
-          livereload: '<%= config.server.livereload %>',
-          base: '<%= config.dir.app.dest %>',
-          port: '<%= config.server.port %>'
-      alive:
-        options:
-          hostname: '<%= config.server.hostname %>',
-          base: '<%= config.dir.app.dest %>',
-          port: '<%= config.server.port %>',
-          keepalive:true

   # load all grunt tasks matching the `grunt-*` pattern
   require('load-grunt-tasks') grunt
@@ -135,7 +122,6 @@
   ]

   grunt.registerTask 'dev', [
-    'connect:live'
     'watch'
   ]