Update Atom settings

Update Atom settings

--- atom/config.cson
+++ atom/config.cson
@@ -1,6 +1,7 @@
 "*":
   "atom-beautify":
-    analytics: false
+    general:
+      analytics: false
   "autoclose-html":
     additionalGrammars: [
       "text.html.django"
@@ -31,7 +32,6 @@
       "spacedust"
     ]
   editor:
-    fontSize: 15
     invisibles: {}
     preferredLineLength: 120
     tabLength: 4
@@ -44,16 +44,12 @@
     lintOnEditorFocus: false
     lintOnFly: false
   "linter-flake8":
-    executableDir: "/Users/jonklo/Code/tienda/bin/"
-    executablePath: "/Users/jonklo/Code/tienda/bin/flake8"
-    ignoreErrorCodes: [
-      "E128"
-      "E261"
-    ]
-    maxLineLength: 160
+    executableDir: "$PROJECT/bin/"
+    executablePath: "$PROJECT/bin/flake8"
+    projectConfigFile: "config/linters/flake8.rc"
   "linter-pylint":
-    executable: "/Users/jonklo/Code/tienda/bin/pylint"
-    rcFile: "/Users/jonklo/Code/tienda/config/linters/pylint.rc"
+    executable: "%p/bin/pylint"
+    rcFile: "config/linters/pylint.rc"
   "linter-scss-lint":
     executablePath: "/usr/local/bin/scss-lint"
   "python-isort":


Atom linter update

Atom linter update

--- atom/config.cson
+++ atom/config.cson
@@ -1,19 +1,27 @@
 "*":
+  "atom-ctags":
+    GotoSymbolKey: [
+      "ctrl"
+    ]
+    autoBuildTagsWhenActive: true
   core:
+    closeDeletedFileTabs: true
     disabledPackages: [
-      "wrap-guide"
-      "activate-power-mode"
+      "symbols-view"
     ]
     telemetryConsent: "limited"
-  editor: {}
+  editor:
+    nonWordCharacters: "/\\()\"',.;<>~!@#$%^&*|+=[]{}`?-"
   "exception-reporting":
     userId: "79967b20-c933-87c9-85b8-ea6b20c3a648"
-  linter:
-    errorPanelHeight: 51
+  "go-plus": {}
+  linter: {}
   "linter-puppet-lint":
     oldVersion: false
     skip140Chars: true
     skip80Chars: true
+  "linter-ui-default":
+    showPanel: true
   minimap:
     plugins:
       cursorline: true
@@ -36,3 +44,6 @@
     when_does_it_get_light: 900
   welcome:
     showOnStartup: false
+".puppet.source":
+  editor:
+    nonWordCharacters: "/\\()\"',.;<>~!@#$%^&*|+=[]{}`?-"


Update Atom config chlorine

Update Atom config chlorine

--- atom/config.cson
+++ atom/config.cson
@@ -1,7 +1,5 @@
 "*":
-  chlorine:
-    "eval-mode": "cljs"
-    "refresh-mode": "simple"
+  chlorine: {}
   core:
     allowPendingPaneItems: false
     automaticallyUpdate: false


Add key binding for auto indent

Add key binding for auto indent

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -49,6 +49,8 @@
   'alt-shift-S': 'unset!'

 'atom-text-editor:not([mini])':
+  # Auto-indents the selection according to the file's designated grammar
+  'alt-cmd-tab': 'editor:auto-indent'
   # Toggles list of snippets that apply to current document
   'ctrl-cmd-s': 'snippets:available'
   # Advances to next Linter error in document


playercode editor stuff for Fabian

playercode editor stuff for Fabian

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -66,7 +66,6 @@
         $(@acelne).append(d)
         $(@acelne).attr({"id":"acelne"})
         $(@acelne).css({"z-index": -1})
-        $("body").append(@acelne)

         @setUpInsertButtons()
         @addEventListeners()
@@ -173,44 +172,8 @@
         return

     moveEditorButtons: =>
-        if @acelne != null
-            gtop = $(".ace_active-line").offset().top
-            gleft = $(".ace_active-line").offset().left
-            gheight = $(".ace_active-line").height()
-            gwidth = $(".ace_active-line").width()
-            $(@acelne).css(
-                {"position":"absolute","top":gtop,
-                "left":gleft,"width":gwidth,
-                "height":gheight, "z-index": 12})
-        else
-            @acelne = document.createElement("div")
-
-            x = document.createElement("img")
-            $(x).attr({"src":"#{@deleteImg}","class":"ace_xbutton"})
-            u = document.createElement("img")
-            $(u).attr({"src":"#{@switchUpImg}","class":"ace_uparrow"})
-            d = document.createElement("img")
-            $(d).attr({"src":"#{@switchDownImg}","class":"ace_downarrow"})
-
-            $(@acelne).append(x)
-            $(@acelne).append(u)
-            $(@acelne).append(d)
-
-            gtop = $(".ace_active-line").offset().top
-            gleft = $(".ace_active-line").offset().left
-            gheight = $(".ace_active-line").height()
-            gwidth = $(".ace_active-line").width()
-
-            $(@acelne).css(
-                {"position":"absolute",
-                "top":gtop, "left":gleft,
-                "width":gwidth, "height":gheight,
-                "z-index": 12})
-
-            $(@acelne).attr({"id":"acelne"})
-
-            $("body").append(@acelne)
-            return
+
+        return

     onEditorCursorMove: (cursorEvent) =>
         if @parameterPopUp == undefined
@@ -229,11 +192,20 @@
             Return true: continue event propogation
             Return false: stop event propogation
         ###
+        row = clickEvent.$pos.row
+
+        $('.ace_gutter-layer').children().eq(row).append(@acelne)
+
+        aglw = $('.ace_gutter-layer').width()
+        aglh = $('.ace_gutter-cell').height()
+        aglpl = $('.ace_gutter-cell').css("padding-left")
+
+        $(@acelne).css({"width":aglw,"height":aglh,"z-index": 20,"background-color":"white","position":"relative","right":aglpl,"bottom":aglh})
+
         if @parameterPopUp == undefined
             @parameterPopUp = jQuery('#parameter-pop-up')

         if inBounds
-            row = clickEvent.$pos.row
             line = clickEvent.editor.getSession().getLine row
             rowLength = line.length
             if rowLength == 0


timed scanner but the regex isn't perfect yet

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -50,7 +50,7 @@
                 if i != numberOfInputs
                     innerRegex += ","

-            re = "^#{command}\\((#{innerRegex})\\)"
+            re = "^#{command}\\((#{innerRegex})\\)(?:,|.|$)"
             @commands[command]['regex'] = RegExp re
         return

@@ -101,10 +101,15 @@
         ###
             This function is triggered after the text in the ace editor is changed.
         ###
-        startRow = changeEvent.data.range.start.row
-        text = @editSession.getLine startRow
-        # alert "Text Left:\n#{text}"
+        if @scanTextID
+            window.clearTimeout @scanTextID
+            delete @scanTextID
+        @scanTextID = window.setTimeout @scanTextCallback.bind(@), 1500
         return true
+
+    scanTextCallback: ->
+        @scanText()
+        @UpdateCommandsStatus()

     scanText: ->
         ###
@@ -144,28 +149,38 @@
         alert "Inner Process: \n#{innerText}"
         if typeof innerText == "undefined" or innerText == null or innerText == ""
             return
+        while innerText != ""
+            for command of @commands
+                result = @commands[command]['regex'].exec innerText
+                if result != null
+                    @commands[command]['usesRemaining']--
+                    alert "Found: \n#{result[0]}"
+                    @processMatch command, result[1]
+                    break
+
+            if result == null
+                result = /^,/.exec innerText
+
+            if result == null
+                innerText = innerText.substring 1
+            else
+                innerText = innerText.substring result[0].length
+
+        return
+
+    UpdateCommandsStatus: ->
+        ###
+            Updates the number of commands remaining for each command.
+        ###
+        buttonField = jQuery '#insertButtons'
         for command of @commands
-            result = @commands[command]['regex'].exec innerText
-            if result != null
-                @commands[command]['usesRemaining']--
-                alert "Found: \n#{result[0]}"
-                @processMatch command, result[1]
-
-            if result == null
-                result = /^,/.exec innerText
-
-        return
-
-    UpdateCommandsStatus: ->
-        ###
-            Updates the number of commands remaining for each command.
-        ###
-        buttonField = jQuery('#insertButtons')
-        for command of @commands
-            button = buttonField.find("##{command}")
-            line = @createBlankFunctionHeader(command)
+            button = buttonField.find "##{command}"
+            line = @createBlankFunctionHeader command
             usesRemaining = @commands[command]['usesRemaining']
-            button.text("#{line}: #{usesRemaining}")
+            if usesRemaining <= 0
+                button.attr 'disabled', true
+
+            button.text "#{line}: #{usesRemaining}"
         return

     switchUp: ({currentRow, currentColumn}) ->
@@ -183,32 +198,19 @@

     deleteLine: ({text, currentRow}) ->
         line = text.getLine currentRow
-        if line != ""
-            command = @getCommandFromLine line
-            @commands[command]['usesRemaining']++
-            @UpdateCommandsStatus()
         if text.getLength() == 1
             text.insertLines currentRow + 1, ["\n"]
             text.removeNewLine currentRow
         text.removeLines currentRow, currentRow
         return

-    getCommandFromLine: (line) ->
-        re = /^(.+)\(/
-        return re.exec(line)[1]
-
     insertLine: ({text, line, currentRow}) ->
-        command = @getCommandFromLine(line)
-        if @commands[command]['usesRemaining'] > 0
-            @commands[command]['usesRemaining']--
-
-            inputsDiv = jQuery('#insertButtons')
-            text.insertLines currentRow, [line]
-
-            if text.getLength() == 2 and text.getLine(currentRow + 1) == ""
-                text.removeNewLine currentRow
-
-            @UpdateCommandsStatus()
+        inputsDiv = jQuery('#insertButtons')
+        text.insertLines currentRow, [line]
+
+        if text.getLength() == 2 and text.getLine(currentRow + 1) == ""
+            text.removeNewLine currentRow
+
         return

     resetState: ->


Some tweaks to how commands are counted, fixes #22

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -47,10 +47,10 @@
         else
             @deleteImg = 'img/cx.png'

+        @interpreter = new CodeInterpreter @commands
         @editor = new PlayerCodeEditor 'ace-editor', \
             @commands, @codeConfig.initial, @codeConfig.show, @codeConfig.prefix, \
-            @codeConfig.postfix, @editorConfig.freeformEditting
-        @interpreter = new CodeInterpreter @commands
+            @codeConfig.postfix, @editorConfig.freeformEditting, @interpreter

         # Create editor buttons
         @acelne = document.createElement("div")
@@ -121,6 +121,7 @@
             setTimeout @moveEditorButtons, @moveEditorButtonDelay
             return
         ed.editSession.on 'changeScrollTop', updateMove
+
         normalResize = ed.editor.renderer.onResize.bind ed.editor.renderer
         addOurResize = (force, gutterWidth, width, height) ->
             normalResize(force, gutterWidth, width, height)
@@ -144,10 +145,13 @@
             When the student code changes, run it through the
             interpreter to figure out commands remaining.
         ###
-        if @scanTimer?
-            window.clearTimeout @scanTimer
-            @scanTimer = null
-        @scanTimer = window.setTimeout @scan, 500
+        if @editorConfig.freeformEditting
+            if @scanTimer?
+                window.clearTimeout @scanTimer
+                @scanTimer = null
+            @scanTimer = window.setTimeout @scan, 300
+        else
+        @UpdateCommandsStatus null
         if @onStudentCodeChangeCallback?
             @onStudentCodeChangeCallback changeData
         return
@@ -167,7 +171,10 @@
             button = buttonField.find "##{command}"
             line = @editor.createBlankFunctionHeader command

-            usesRemaining = remaining[command]
+            if remaining != null
+                usesRemaining = remaining[command]
+            else
+                usesRemaining = @commands[command]['usesRemaining']
             if usesRemaining <= 0
                 button.attr 'disabled', true
                 if usesRemaining < 0
@@ -320,7 +327,7 @@
     ###
         Creates and provides functionality for an Ace editor representing player's code.
     ###
-    constructor: (@editorDivId, @commands, codeText, @wrapCode, @codePrefix, @codeSuffix, @freeEdit) ->
+    constructor: (@editorDivId, @commands, codeText, @wrapCode, @codePrefix, @codeSuffix, @freeEdit, @interpreter) ->
         ###
             Sets internal variables, the default text and buttons
             and their event handlers.
@@ -401,10 +408,6 @@
         @editor.on 'changeSelection', callback
         return

-    # onMove: (cursorEvent) ->
-    #     if @onMoveCallback != null
-    #         @onMoveCallback cursorEvent
-
     switchUp: ({currentRow, currentColumn}) ->
         maxRow = @editSession.getLength()
         if currentRow - 1 < @codePrefixLength or currentRow >= maxRow - @codeSuffixLength
@@ -428,6 +431,8 @@
         if currentRow >= maxRow - @codeSuffixLength or currentRow < @codePrefixLength
             return
         line = text.getLine currentRow
+        command = @interpreter.identifyCommand line
+        @commands[command]['usesRemaining']++
         if text.getLength() == 1
             text.insertLines currentRow + 1, ["\n"]
             text.removeNewLine currentRow
@@ -439,6 +444,7 @@
         if currentRow + 1 < @codePrefixLength or currentRow + 1 >= maxRow - (@codeSuffixLength - 1)
             return

+        @commands[command]['usesRemaining']--
         printLine = (@createBlankFunctionHeader command) + ';'
         text.insertLines currentRow + 1, [printLine]

@@ -468,6 +474,8 @@
         @editor.clearSelection()
         @reIndentCode()
         @gotoLine @codePrefixLength + 1
+        for name, command of @commands
+            command['usesRemaining'] = command['maxUses']
         return

     reIndentCode: =>


added stream lookup by name

added stream lookup by name

--- twitch.coffee
+++ twitch.coffee
@@ -10,18 +10,23 @@
 # Commands:
 #   hubot ttv <category> - Perform a case-sensitive search on Twitch.tv for live streams in a game category (returns the first 5)
 #   hubot ttv featured - Return the first 5 featured live streams on Twitch.tv
+#   hubot ttv stream <name> - Returns information about stream <name>
 #
 # Author:
 #   MrSaints

 module.exports = (robot) ->
     robot.respond /ttv (.*)/i, (msg) ->
+        tempv = msg.match[1].indexOf "stream",0
         if msg.match[1] is "featured"
             getStreams msg, true
+        else if tempv is 0
+            getStreams msg, false, true
         else
             getStreams msg

-getStreams = (msg, featured) ->
+
+getStreams = (msg, featured, channel) ->
     category = msg.match[1]

     API = "/streams"
@@ -31,12 +36,30 @@
         API = "/streams/featured"
         type = "featured"

+    if channel
+        streamsplit = msg.match[1].split " "
+        API = "/streams/#{streamsplit[1]}"
+        type = "stream"
+
+
     msg.http("https://api.twitch.tv/kraken#{API}")
         .query
             game: category
             limit: 5
         .get() (err, res, body) ->
             data = JSON.parse body
+
+            if channel
+                if not data[type]
+                    msg.reply "The stream is either offline, or does not exist."
+                    return
+
+                if data[type]
+                    stream = data[type]
+                    msg.send "#{stream.channel.display_name} is streaming #{stream.channel.game} @ #{stream.channel.url}"
+                    msg.send "Stream status: \"#{stream.channel.status}\""
+                    msg.send "Viewers: #{stream.viewers}"
+                    return

             if data._total is 0
                 msg.reply "No live streams were found in '#{category}'. Try a different category or try again later."
@@ -47,4 +70,5 @@
                 msg.send "#{stream.channel.display_name} (#{stream.channel.status}) - #{stream.channel.url} [Viewers: #{stream.viewers}]"

             if !featured and data._total > 5
-                msg.reply "There are #{data._total - 5} other '#{category}' live streams."+                msg.reply "There are #{data._total - 5} other '#{category}' live streams."
+


grunt compress: Fix couple of wrong file paths

Caused tarball to miss some files, or have it place in wrong directory

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -190,10 +190,6 @@
         options:
           archive: 'noflo-<%= pkg.version %>.zip'
         files: [
-          src: ['browser/noflo-noflo-indexeddb/vendor/*']
-          expand: true
-          dest: '/'
-        ,
           src: ['browser/noflo-ui.js']
           expand: true
           dest: '/'
@@ -204,7 +200,7 @@
             'node_modules/codemirror/lib/*.css'
             'node_modules/codemirror/theme/mdn-like.css'
             'node_modules/coffee-script/extras/*.js'
-            'node_modules/coffeelint/js/coffeelint.js'
+            'node_modules/coffeelint/lib/coffeelint.js'
             'node_modules/jshint/dist/jshint.js'
             'bower_components/ease-djdeath/*.js'
             'node_modules/font-awesome/css/*.css'
@@ -225,7 +221,8 @@
             'node_modules/rtc/dist/rtc.js.map'
             'bower_components/the-graph/**/*.js'
             'bower_components/the-graph/**/*.css'
-            'bower_components/webcomponentsjs/webcomponents.js'
+            'node_modules/webcomponents.js/webcomponents.min.js'
+            'node_modules/indexeddbshim/dist/indexeddbshim.min.js'
           ]
           expand: true
           dest: '/'


add .htaccess to build

add .htaccess to build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -192,6 +192,7 @@
             '../package.json'
             '*.{png,ico,xml}'
             'index.html'
+            '.htaccess'
           ]
         ,
           '<%= app.dist %>/package.json': 'package.json'


only copy relevant icons into chrome app

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -173,10 +173,6 @@
             ]
           dest: chromeExtDevPath + 'scripts/'
         }, {
-          # expand: true, cwd: 'node_modules/freedom-for-chrome/'
-          # src: ['freedom.js']
-          # dest: 'build/chrome/extension/lib'},
-          # Libraries
           expand: true, cwd: 'third_party/lib'
           src: ['**']
           dest: chromeExtDevPath + 'lib'
@@ -207,7 +203,7 @@
             'freedom-for-chrome-for-uproxy.js'
           ]
           dest: chromeAppDevPath + 'lib/'
-        }, {  # Libraries
+        }, {  # Additional hack - TODO: remove this once social enum is gone.
           expand: true, cwd: 'third_party', flatten: true
           src: [
             'freedom-ts-hacks/social-enum.js'
@@ -215,7 +211,7 @@
           dest: chromeAppDevPath + 'scripts/'
         }, {  # uProxy Icons.
           expand: true, cwd: 'src/'
-          src: ['icons/*']
+          src: ['icons/uproxy-*.png']
           dest: chromeAppDevPath
         } ]


Remove unnecessary grunt-watchify from gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -137,7 +137,6 @@
   grunt.loadNpmTasks 'grunt-contrib-uglify'
   grunt.loadNpmTasks 'grunt-contrib-watch'
   grunt.loadNpmTasks 'grunt-browserify'
-  grunt.loadNpmTasks 'grunt-watchify'

   grunt.registerTask 'default',
     ['build']


microflo: Configure baudrates

microflo: Configure baudrates

Needs microflo/microflo#126

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -148,12 +148,26 @@
         options:
           replacements: [
             pattern: '\n'
-            replacement: '\n#include <Adafruit_WS2801.h>\n#define HAVE_ADAFRUIT_WS2801\n\n'
+            replacement: """\n
+              #include <Adafruit_WS2801.h>
+              #define HAVE_ADAFRUIT_WS2801
+              #define MICROFLO_ARDUINO_BAUDRATE 9600
+            \n"""
           ]
+      tablelights:
+        files:
+          'build/arduino/TableLights/TableLights.ino': 'build/arduino/TableLights/TableLights.ino.tmpl'
+        options:
+          replacements: [
+            pattern: '\n'
+            replacement: """\n
+              #define MICROFLO_ARDUINO_BAUDRATE 115200
+            \n"""
+          ]

     exec:
-      tablelights_arduino_gen: microflo_gen 'TableLights', { target: 'arduino' }
-      tablelights_arduino_build: arduino_build 'TableLights', { board: 'energia:tivac:EK-TM4C123GXL' }
+      tablelights_arduino_gen: microflo_gen 'TableLights', { target: 'arduino', ext: 'ino.tmpl' }
+      tablelights_arduino_build: arduino_build 'TableLights', { board: 'energia:tivac:EK-TM4C123GXL', ext: 'ino' }
       tablelights_linux_gen: microflo_gen 'TableLights'
       tablelights_linux_comp: microflo_compile 'TableLights'
       tablelights_run: './spec/microflo-linux.sh TableLights 4444 & sleep 5'
@@ -186,7 +200,7 @@
   ]
   @registerTask 'build-microflo-arduino', [
     'exec:portallights_arduino_gen', 'string-replace:portallights', 'exec:portallights_arduino_build',
-    'exec:tablelights_arduino_gen', 'exec:tablelights_arduino_build',
+    'exec:tablelights_arduino_gen', 'string-replace:tablelights', 'exec:tablelights_arduino_build',

   ]
   @registerTask 'run-microflo-linux', [


upload the minified content from dist/

upload the minified content from dist/

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -77,7 +77,7 @@
           dest: "bugsnag-<%= pkg.version.split('.')[0] %>.js"
         },
         {
-          src: "src/bugsnag.min.js"
+          src: "dist/bugsnag.min.js"
           dest: "bugsnag-<%= pkg.version.split('.')[0] %>.min.js"
         }]


update grunt setting

update grunt setting

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -75,7 +75,7 @@
     grunt.log.writeln "#{target}: #{files} has #{action}"

     # coffeelint
-    grunt.config ['coffeelint', target], src: files
+    # grunt.config ['coffeelint', target], src: files

     # coffee
     coffeeData = grunt.config ['coffee', target]
@@ -87,7 +87,6 @@

   # tasks.
   grunt.registerTask 'compile', [
-    'coffeelint'
     'coffee'
   ]


Fix grunt task name.

Fix grunt task name.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -151,7 +151,7 @@
   @loadNpmTasks 'grunt-saucelabs'

   @registerTask 'build', ['browserify', 'uglify:engine', 'usebanner']
-  @registerTask 'test', ['coffeelint', 'build:js', 'phantom']
+  @registerTask 'test', ['coffeelint', 'build', 'phantom']
   @registerTask 'phantom', ['connect', 'mocha_phantomjs']
   @registerTask 'crossbrowser', ['build', 'coffeelint', 'connect', 'mocha_phantomjs', 'saucelabs-mocha']
   @registerTask 'default', ['build']


Watch for coffee-files

Watch for coffee-files

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

     # Watch configuration
     watch:
+      # Watch the coffee files so we can (re)generate the js files
+      coffee:
+        files: [
+          '<%= assetsPath %>/coffee/**'
+        ]
+        tasks: [
+          'coffee'
+        ]
       # Watch the files to be copied
       copy:
         files: [


fix: Gruntfile task bump

fix: Gruntfile task bump

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -22,7 +22,6 @@
     bump:
       options:
         commitMessage: 'chore: release v%VERSION%'
-        pushTo: 'upstream'

     'auto-release':
       options:


Mod Grundfile.coffee

Mod Grundfile.coffee

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -43,8 +43,8 @@
     open:
       server:
         path: "http://localhost:<%= connect.options.port %>"
+#        app: 'Google Chrome'
         app: 'Google Chrome Canary'
-#        app: 'Google Chrome'
 #      file:
 #        path: '/etc/hosts'
 #        app: 'Sublime Text'


Bypassing jshint errors due to how coffeescript generates the javascript.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -14,6 +14,8 @@
         files:
           "dist/js/ahg.min.js": ["dist/js/ahg.js"]
     jshint:
+      options:
+        force: true
       all: ["src/**/*.js"]
     watch:
       coffee:


Fixed the Gruntfile to rely on compass installed with bundler

The contributing documentation asks about installing compass through
Bundler, so the Gruntfile should expect it as well.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -56,6 +56,7 @@
     compass:
       chosen_css:
         options:
+          bundleExec: true
           specify: ['sass/chosen.scss']

     cssmin:


Ignores map files

Ignores map files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -11,7 +11,8 @@
         files: [{
           expand: true
           cwd: "bower_components/fontawesome/css/"
-          src: [ "font-awesome.min.css", "font-awesome.css.map" ]
+          src: [ "font-awesome.min.css" ]
+          # src: [ "font-awesome.min.css", "font-awesome.css.map" ]
           dest: "assets/vendor/css/"
         },
         {
@@ -24,20 +25,22 @@
         files: [{
           expand: true
           cwd: "bower_components/jquery/dist/"
-          src: [ "jquery.min.js", "jquery.min.map" ]
+          src: [ "jquery.min.js" ]
+          # src: [ "jquery.min.js", "jquery.min.map" ]
           dest: "assets/vendor/js"
         }]
       bootstrap:
         files: [{
           expand: true
           cwd: "bower_components/bootstrap/dist/css/"
-          src: [ "bootstrap.min.css", "bootstrap.css.map" ]
+          src: [ "bootstrap.min.css" ]
           dest: "assets/vendor/css"
         },
         {
           expand: true
           cwd: "bower_components/bootstrap/dist/js/"
-          src: [ "bootstrap.min.js", "bootstrap.min.js" ]
+          src: [ "bootstrap.min.js" ]
+          # src: [ "bootstrap.min.css", "bootstrap.css.map" ]
           dest: "assets/vendor/js"
         }]


Copy images to dist

Copy images to dist

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,7 +29,7 @@
             jshint:
                 files: ['js/*.js']
                 tasks: ['jshint']
-
+
         connect:

             livereload:
@@ -67,7 +67,8 @@
                     src: [
                         'slides/**'
                         'bower_components/**'
-                        'js/**'
+                        'js/**',
+                        'image/**'
                     ]
                     dest: 'dist/'
                 },{
@@ -77,7 +78,7 @@
                     filter: 'isFile'
                 }]

-
+
         buildcontrol:

             options:
@@ -89,7 +90,7 @@
                 options:
                     remote: 'git@github.com:dvberkel/rails.girls.utrecht.2015.git'
                     branch: 'gh-pages'
-
+

     # Load all grunt tasks.
@@ -131,13 +132,13 @@
             'copy'
         ]

-
+
     grunt.registerTask 'deploy',
         'Deploy to Github Pages', [
             'dist'
             'buildcontrol'
         ]
-
+

     # Define default task.
     grunt.registerTask 'default', [


grunt: build jekyll with --draft

grunt: build jekyll with --draft

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -56,7 +56,7 @@
       install:
         cmd: "bundle install"
       jekyll:
-        cmd: "bundle exec jekyll build --trace"
+        cmd: "bundle exec jekyll build --trace --draft"

     bower:
       install: {}


Removing useless spaces.

Removing useless spaces.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -13,7 +13,7 @@
   grunt.initConfig
     yeoman: yeomanConfig

-
+
     coffee:
       dist:
         files: [
@@ -28,8 +28,8 @@
         src: '<%=yeoman.dist %>/featnessjs.js'
         dest: '<%=yeoman.dist %>/featnessjs.min.js'
     mochaTest:
-      test:
-        options:
+      test:
+        options:
           reporter: 'spec'
           compilers: 'coffee:coffee-script'
         src: ['test/**/*.coffee']
@@ -38,4 +38,4 @@
       'mochaTest'
       'coffee'
       'uglify'
-    ]+    ]


uglify template.js after dist-template

uglify template.js after dist-template

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -24,6 +24,12 @@
         expand: true
         cwd: 'public/assets/dist/js'
         src: ['**/*.js', '!**/*.min.js']
+        dest: 'public/assets/dist/js'
+        ext: '.min.js'
+      jst:
+        expand: true
+        cwd: 'public/assets/dist/js'
+        src: ['templates.js']
         dest: 'public/assets/dist/js'
         ext: '.min.js'

@@ -78,7 +84,7 @@
         tasks: ['dist-css']
       jst:
         files: ['public/assets/jst/*.hbs', 'public/assets/jst/*.html', 'public/assets/jst/*.jst']
-        tasks: ['dist-template']
+        tasks: ['dist-template', 'uglify:jst']

   require('load-grunt-tasks')(grunt, { scope: 'devDependencies' })
   require('time-grunt')(grunt)


Clarify npm file: dependencies comment.

Clarify npm file: dependencies comment.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -177,11 +177,8 @@
           # package.json preinstall script also installs and runs jspm,
           # npm install won't get as far as that, since npm first
           # attempts to resolve dependency references, which include
-          # jspm package locations. It is unknown why these file
-          # references are in package.json.
-          # TODO - remove the package.json file: references and try
-          #   install. If it succeeds, then clean up the process. If not,
-          #   then document why here.
+          # jspm angular package locations. The angular references are
+          # required per https://github.com/ng-bootstrap/ng-bootstrap.
           'npm install jspm && jspm install && npm install'

       qirest:


update Grunt Bump module config

update Grunt Bump module config

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -57,6 +57,7 @@
         ]
         updateConfigs: ['pkg']
         commitFiles: ['-a']
+        pushTo: '<%= pkg.repository.url %>'

   grunt.registerTask 'build', [


ceil fix

ceil fix

--- src/spatial-hash.coffee
+++ src/spatial-hash.coffee
@@ -39,9 +39,9 @@
     pixelWidth = @pixelWidth
     pixelHeight = @pixelHeight

-    for x in [position.x..FLOOR(maxX/pixelWidth)*pixelWidth] by pixelWidth
+    for x in [position.x..CEIL(maxX/pixelWidth)*pixelWidth] by pixelWidth
       if x >= position.x
-        for y in [position.y..FLOOR(maxY/pixelHeight)*pixelHeight] by pixelHeight
+        for y in [position.y..CEIL(maxY/pixelHeight)*pixelHeight] by pixelHeight
           if y >= position.y
             hash = @hash(x, y)
             @addByHash(object, hash, layer)


Allow scoping to element

Allow scoping to element

--- app/assets/javascripts/active_admin/active_admin_globalize.js.coffee
+++ app/assets/javascripts/active_admin/active_admin_globalize.js.coffee
@@ -1,8 +1,10 @@
-$ ->
+class ActiveAdminGlobalize

-  translations = ->
+  translations: (container=document) ->

-    $(".activeadmin-translations > ul").each ->
+    $container = $(container)
+
+    $(".activeadmin-translations > ul", $container).each ->
       $dom = $(this)
       if !$dom.data("ready")
         $dom.data("ready", true)
@@ -25,12 +27,16 @@
           containsErrors = $content.find(".input.error").length > 0
           $tab.toggleClass("error", containsErrors)

+
+$ ->
+  globalizer = new ActiveAdminGlobalize
+  globalizer.translations()
+
+  $(document).on 'translations:refresh', =>
+    globalizer.translations()
+
   # this is to handle elements created with has_many
-  $("a").bind "click", ->
-    setTimeout(
-      -> translations()
-      50
-    )
-
-  translations()
-
+  $(document).on 'has_many_add:after', (event, fieldset, parent) ->
+    setTimeout =>
+      globalizer.translations(fieldset)
+    , 50


on success just return token data not complete response

--- app/assets/javascripts/angular/services/registrytoken_service.coffee
+++ app/assets/javascripts/angular/services/registrytoken_service.coffee
@@ -40,7 +40,7 @@

       $http.get(bear['url'],{headers:_h,params: bear['params']}).then(
         (response) ->
-          deferred.resolve(response)
+          deferred.resolve(response.data)
         (response) ->
           deferred.reject(response)
       )


avstreams: Use only 8 chars when creating a random ID

--- avstreams.coffee
+++ avstreams.coffee
@@ -13,7 +13,7 @@

   shasum = crypto.createHash 'sha512'
   shasum.update buf
-  return shasum.digest 'hex'
+  return shasum.digest('hex')[0..7]

 class AVStream
   constructor: (id) ->


fix failing spec

fix failing spec

--- apps/user/test/client/account_form.coffee
+++ apps/user/test/client/account_form.coffee
@@ -40,6 +40,7 @@
   describe '#toggleService', ->
     describe 'link', ->
       beforeEach ->
+        @userEdit.set accessToken: 'x-foo-token'
         location.assign = sinon.stub()
         global.location.href = 'user/edit'
         @view.$('.settings-toggle-service[data-service="twitter"]').click()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -1,5 +1,5 @@
 ###*
-The TimedEvents module allows arbitrary code to be executed
+The TimedEvents module allows arbitrary code to be executed at set int

 player = Core


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -1,5 +1,5 @@
 ###*
-The Time module is used to provide basic data about the
+The TimedEvents module is used to provide basic data about the
 location and dimensions of the including object. This module is included
 by default in GameObject.


fix and enable undo change coalescing

fix and enable undo change coalescing

--- src/undo-manager.coffee
+++ src/undo-manager.coffee
@@ -61,14 +61,12 @@
     @redoStack = []
     undoDelta = oldDelta.invert(changeDelta)
     timestamp = new Date().getTime()
-    ###
     if @lastRecorded + @options.delay > timestamp and @undoStack.length > 0
       change = @undoStack.pop()
-      undoDelta = change.undo.compose(undoDelta)
+      undoDelta = undoDelta.compose(change.undo)
       changeDelta = change.redo.compose(changeDelta)
     else
       @lastRecorded = timestamp
-    ###
     @undoStack.push({
       redo: changeDelta
       undo: undoDelta


adds logging module

adds logging module

--- src/util/logging.coffee
+++ src/util/logging.coffee
@@ -1,4 +1,4 @@
-Luca.log = (args...)
+Luca.log = (args...)->
   if Luca.isComponent(@)
     args.unshift( @identifier() )


Replace magic numbers w/ constants

Replace magic numbers w/ constants

--- src/views/editor.coffee
+++ src/views/editor.coffee
@@ -129,7 +129,7 @@
     row = Math.floor((position.y - @grid.offset().top) / @cellSize)
     col = Math.floor((position.x - @grid.offset().left) / @cellSize)

-    if 0 <= row <= 9 and 0 <= col <= 9
+    if 0 <= row < @MAX_CELL_COUNT and 0 <= col < @MAX_CELL_COUNT
       @previousRow = row
       @previousCol = col
       @fillBlock(row, col)
@@ -143,7 +143,6 @@
     row = Math.floor((position.y - @grid.offset().top) / @cellSize)
     col = Math.floor((position.x - @grid.offset().left) / @cellSize)

-    # Only recognize movement if within grid bounds
     if 0 <= row < @MAX_CELL_COUNT and 0 <= col < @MAX_CELL_COUNT
       @fillBlock(row, col) if row != @previousRow or col != @previousCol


Using Ember.Array instead of deprecated Ember.Set


Social/Client: disable commenting for announcement posts

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -42,6 +42,9 @@
     {commentViewClass, activitySettings: {disableFollow}} = options

     @commentBox  = new commentViewClass options.commentSettings, data
+    if data.initialChannelId is KD.getGroup().socialApiAnnouncementChannelId
+    # if data.initialChannelId is KD.getGroup().socialApiChannelId
+      @commentBox.hide()
     @actionLinks = new ActivityActionsView delegate: @commentBox, data

     @commentBox.forwardEvent @actionLinks, "Reply"


LikeView: Regress LikeSummary from activitylistitem

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -25,7 +25,6 @@
       origin     : origin

     @author      = new ProfileLinkView { origin }
-    @likeSummary = new ActivityLikeSummaryView {}, data

     {commentViewClass} = @getOptions()

@@ -229,7 +228,6 @@
       {article{@formatContent #(body)}}
       {{> @embedBox}}
       {{> @actionLinks}}
-      {{> @likeSummary}}
     </div>
     {{> @commentBox}}
     """


Moderation: Follow buttons are removed for deleted, and child topics

--- client/Social/Topics/Views/TopicsListItemView.coffee
+++ client/Social/Topics/Views/TopicsListItemView.coffee
@@ -4,6 +4,7 @@
     options.type = "topics"
     super options, data

+    data = @getData()
     @titleLink = new KDCustomHTMLView
       tagName     : 'a'
       pistachio   : '{{#(title)}}'
@@ -23,17 +24,19 @@
     else
       @settingsButton = new KDCustomHTMLView tagName : 'span', cssClass : 'hidden'

-
-    @followButton = new FollowButton
-      cssClass       : 'solid green'
-      errorMessages  :
-        KodingError  : 'Something went wrong while follow'
-        AccessDenied : 'You are not allowed to follow topics'
-      stateOptions   :
-        unfollow     :
-          cssClass   : 'following-btn'
-      dataType       : 'JTag'
-    , data
+    unless data.status is 'synonym' or data.status is 'deleted'
+      @followButton = new FollowButton
+        cssClass       : 'solid green'
+        errorMessages  :
+          KodingError  : 'Something went wrong while follow'
+          AccessDenied : 'You are not allowed to follow topics'
+        stateOptions   :
+          unfollow     :
+            cssClass   : 'following-btn'
+        dataType       : 'JTag'
+      , data
+    else
+      @followButton = new KDCustomHTMLView tagName : 'span', cssClass : 'hidden'

   getSettingsMenu:->


MemberItemView: Make settings view toggle logic a separated method.

--- client/admin/lib/views/members/memberitemview.coffee
+++ client/admin/lib/views/members/memberitemview.coffee
@@ -34,10 +34,8 @@

     @roleLabel = new KDCustomHTMLView
       cssClass : 'role'
-      click    : =>
-        @settings.toggleClass  'hidden'
-        @roleLabel.toggleClass 'active'
       partial  : "#{@memberRole.label} "
+      click    : @bound 'toggleSettings'

     @createSettingsView()

@@ -72,10 +70,13 @@
     @settings.addSubView new KDButtonView
       cssClass : 'solid compact outline'
       title    : 'MAKE OWNER'
+  toggleSettings: ->

     @settings.addSubView new KDButtonView
       cssClass : 'solid compact outline red'
       title    : 'KICK USER'
+    @settings.toggleClass  'hidden'
+    @roleLabel.toggleClass 'active'

   pistachio: ->


Save user settings for Ace editor

Save user settings for Ace editor

This is also a very detailed example of new AppStorage Class usage.

--- client/app/Applications/Ace.kdapplication/ace.coffee
+++ client/app/Applications/Ace.kdapplication/ace.coffee
@@ -1,6 +1,6 @@
 ###
   todo:
-
+
     - put search replace
     - fix setSoftWrap it goes back to off when you reopen the settings

@@ -8,38 +8,49 @@

 class Ace extends KDView
-
+
   constructor:(options, file)->
-
+
     super options, file
     @lastSavedContents = ""
+    @appStorage = new AppStorage 'Ace', '1.0'

   setDomElement:(cssClass)->

     @domElement = $ "

"

   viewAppended:->
-
-    require ['ace/ace'], (ace)=>
-      @editor = ace.edit "editor#{@getId()}"
-      @prepareEditor()
-      @utils.wait => @emit "ace.ready"
-      @fetchContents (err, contents)=>
-        @setContents contents if contents
-        @editor.gotoLine 0
-
+
+    @appStorage.fetchStorage (storage)=>
+      log storage
+      require ['ace/ace'], (ace)=>
+        @editor = ace.edit "editor#{@getId()}"
+        @prepareEditor()
+        @utils.wait => @emit "ace.ready"
+        @fetchContents (err, contents)=>
+          @setContents contents if contents
+          @editor.gotoLine 0

   prepareEditor:->
-
+
     @setTheme()
     @setSyntax()
-    @setShowPrintMargin no
+    @setUseSoftTabs         @appStorage.getValue 'useSoftTabs',         yes
+    @setShowGutter          @appStorage.getValue 'showGutter',          yes
+    @setShowPrintMargin     @appStorage.getValue 'showPrintMargin',     no
+    @setHighlightActiveLine @appStorage.getValue 'highlightActiveLine', yes
+    @setShowInvisibles      @appStorage.getValue 'showInvisibles',      yes
+    @setFontSize            @appStorage.getValue 'fontSize',            10
+    @setTabSize             @appStorage.getValue 'tabSize',             4
+    @setUseWordWrap         @appStorage.getValue 'useWordWrap',         yes
+    @setSoftWrap            @appStorage.getValue 'softWrap',            'free'
     @setEditorListeners()
-
+
   setEditorListeners:->
+
     @editor.getSession().selection.on 'changeCursor', (cursor)=>
       @emit "ace.change.cursor", @editor.getSession().getSelection().getCursor()
-
+
     @editor.commands.addCommand
         name    : 'save'
         bindKey :
@@ -63,11 +74,11 @@

     file.on "fs.save.started", =>
       @lastContentsSentForSave = @getContents()
-
+
   ###
   FS REQUESTS
   ###
-
+
   requestSave:->

     contents = @getContents()
@@ -79,48 +90,56 @@
     contents = @getContents()
     @emit "ace.requests.saveAs", contents

-
+
   fetchContents:(callback)->
-
+
     file = @getData()
     unless /localfile:/.test file.path
       file.fetchContents callback
     else
       callback null, file.contents or ""
-
+
   ###
   GETTERS
   ###
-
+
   getContents:-> @editor.getSession().getValue()
-
+
   getTheme:-> @editor.getTheme().replace "ace/theme/", ""

   getSyntax:-> @syntaxMode

-  getUseSoftTabs:-> @editor.getSession().getUseSoftTabs()
-
-  getShowGutter:-> @editor.renderer.getShowGutter()
-
-  getShowPrintMargin:-> @editor.getShowPrintMargin()
-
-  getHighlightActiveLine:-> @editor.getHighlightActiveLine()
-
-  getShowInvisibles:-> @editor.getShowInvisibles()
-
-  getFontSize:-> parseInt @$("#editor#{@getId()}").css("font-size"), 10
-
-  getTabSize:-> @editor.getSession().getTabSize()
-
-  getUseWordWrap:-> @editor.getSession().getUseWrapMode()
-
-  getSoftWrap:->
-
-    limit = @editor.getSession().getWrapLimitRange().max
+  getUseSoftTabs:->
+    @appStorage.getValue 'useSoftTabs', @editor.getSession().getUseSoftTabs()
+
+  getShowGutter:->
+    @appStorage.getValue 'showGutter', @editor.renderer.getShowGutter()
+
+  getShowPrintMargin:->
+    @appStorage.getValue 'showPrintMargin', @editor.getShowPrintMargin()
+
+  getHighlightActiveLine:->
+    @appStorage.getValue 'highlightActiveLine', @editor.getHighlightActiveLine()
+
+  getShowInvisibles:->
+    @appStorage.getValue 'showInvisibles', @editor.getShowInvisibles()
+
+  getFontSize:->
+    @appStorage.getValue 'fontSize', parseInt(@$("#editor#{@getId()}").css("font-size"), 10)
+
+  getTabSize:->
+    @appStorage.getValue 'tabSize', @editor.getSession().getTabSize()
+
+  getUseWordWrap:->
+    @appStorage.getValue 'useWordWrap', @editor.getSession().getUseWrapMode()
+
+  getSoftWrap:->
+
+    limit = @appStorage.getValue 'softWrap', @editor.getSession().getWrapLimitRange().max
     if limit then limit
     else
       if @getUseWordWrap() then "free" else "off"
-
+
   getSettings:->
     theme               : @getTheme()
     syntax              : @getSyntax()
@@ -133,67 +152,93 @@
     fontSize            : @getFontSize()
     tabSize             : @getTabSize()
     softWrap            : @getSoftWrap()
-
+
   ###
   SETTERS
   ###

   setContents:(contents)-> @editor.getSession().setValue contents

-  setTheme:(themeName = "merbivore_soft")->
-
+  setTheme:(themeName)->
+    themeName or= @appStorage.getValue 'theme', 'merbivore_soft'
     require ["ace/theme/#{themeName}"], (callback) =>
       @editor.setTheme "ace/theme/#{themeName}"
-
+      @appStorage.setValue 'theme', themeName, =>
+        callback
+
   setSyntax:(mode)->
-
+
     file = @getData()
     mode or= file.syntax
-
+
     unless mode
       ext  = @utils.getFileExtension file.path
       for name, [language, extensions] of __aceSettings.syntaxAssociations
         if ///^(?:#{extensions})$///i.test ext
           mode = name
       mode or= "text"
-
+
     require ["ace/mode/#{mode}"], ({Mode})=>
       @editor.getSession().setMode new Mode
       @syntaxMode = mode

-  setUseSoftTabs:(value)-> @editor.getSession().setUseSoftTabs value
-
-  setShowGutter:(value)-> @editor.renderer.setShowGutter value
-
-  setShowPrintMargin:(value)-> @editor.setShowPrintMargin value
-
-  setHighlightActiveLine:(value)-> @editor.setHighlightActiveLine value
+  setUseSoftTabs:(value)->
+
+    @editor.getSession().setUseSoftTabs value
+    @appStorage.setValue 'useSoftTabs', value, =>
+
+  setShowGutter:(value)->
+
+    @editor.renderer.setShowGutter value
+    @appStorage.setValue 'showGutter', value, =>
+
+  setShowPrintMargin:(value)->
+
+    @editor.setShowPrintMargin value
+    @appStorage.setValue 'showPrintMargin', value, =>
+
+  setHighlightActiveLine:(value)->
+
+    @editor.setHighlightActiveLine value
+    @appStorage.setValue 'highlightActiveLine', value, =>

   # setHighlightSelectedWord:(value)-> @editor.setHighlightActiveLine value

-  setShowInvisibles:(value)-> @editor.setShowInvisibles value
-
-  setFontSize:(value)-> @$("editor#{@getId()}").css fontSize : "#{value}px"
-
-  setTabSize:(value)-> @editor.getSession().setTabSize value
-
-  setUseWordWrap:(value)-> @editor.getSession().setUseWrapMode value
-
-  softWrapValueMap = ->
-
-    'off'  : [ null, 80 ]
-    '40'   : [ 40,   40 ]
-    '80'   : [ 80,   80 ]
-    'free' : [ null, 80 ]
-
+  setShowInvisibles:(value)->
+
+    @editor.setShowInvisibles value
+    @appStorage.setValue 'showInvisibles', value, =>
+
+  setFontSize:(value)->
+
+    @$("editor#{@getId()}").css fontSize : "#{value}px"
+    @appStorage.setValue 'fontSize', value, =>
+
+  setTabSize:(value)->
+
+    @editor.getSession().setTabSize value
+    @appStorage.setValue 'tabSize', value, =>
+
+  setUseWordWrap:(value)->
+
+    @editor.getSession().setUseWrapMode value
+    @appStorage.setValue 'useWordWrap', value, =>
+
   setSoftWrap:(value)->
-
-    [limit, margin] = softWrapValueMap()[value]
+    softWrapValueMap =
+      'off'  : [ null, 80 ]
+      '40'   : [ 40,   40 ]
+      '80'   : [ 80,   80 ]
+      'free' : [ null, 80 ]
+
+    [limit, margin] = softWrapValueMap[value]

     @editor.getSession().setWrapLimitRange limit, limit
     @editor.renderer.setPrintMarginColumn margin
     @setUseWordWrap no if value is "off"

+    @appStorage.setValue 'softWrap', value, =>
+
   ###
   HELPERS
   ###
@@ -202,9 +247,9 @@
   notify:(msg, style, details)->

     notification.destroy() if notification
-
+
     style or= 'error' if details
-
+
     notification = new KDNotificationView
       title     : msg or "Something went wrong"
       type      : "mini"


set attribute name to display

set attribute name to display

--- client/skr/screens/customer-maint/CustomerMaint.cjsx
+++ client/skr/screens/customer-maint/CustomerMaint.cjsx
@@ -18,7 +18,7 @@
             <BS.Row>
                 <SC.CustomerFinder sm=4 editOnly ref="finder"
                     syncOptions={include: ['billing_address', 'shipping_address']}
-                    commands={@state.commands} model={@customer} />
+                    commands={@state.commands} model={@customer} name='code' />
                 <LC.Input sm=8 name="name" model={@customer} />
             </BS.Row>
             <BS.Row>


tweak text and fix progress

tweak text and fix progress

--- static/js/script.coffee
+++ static/js/script.coffee
@@ -107,29 +107,29 @@
 		if this.files.length
 			file = this.files[0]
 			if file.size > 10485760
-				return modalAlert('照片不符合要求', '照片太大了， 不要超过10MB哦')
+				return modalAlert('照片不符合要求', '照片太大了，不要超过10MB哦！')
 			if file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase() in ['jpg', 'jpeg', 'png', 'gif']
 				reader = new FileReader()
 				reader.onload = (event)->
 					$('#avatar > img.preview').attr('src', event.target.result)
 				reader.readAsDataURL(file)
 			else
-				return modalAlert('照片不符合要求', '照片格式不对， 请上传JPG / PNG / GIF格式的照片')
+				return modalAlert('照片不符合要求', '照片格式不对，请上传JPG / PNG / GIF格式的照片。')

 	$('#apply > input.select').change ->
 		if this.files.length
 			file = this.files[0]
 			if file.size > 104857600
-				return modalAlert('报名表不符合要求', '报名表太大了， 不要超过100MB哦')
+				return modalAlert('报名表不符合要求', '报名表太大了，不要超过100MB哦！')

 			if file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase() in ['pdf', 'zip', 'rar', '7z']
 				$('#apply > img.preview').attr('src', 'static/img/logo.png')
 			else
-				return modalAlert('报名表不符合要求', '报名表格式不对， 请上传PDF / ZIP / RAR / 7Z格式的报名表')
+				return modalAlert('报名表不符合要求', '报名表格式不对，请上传PDF / ZIP / RAR / 7Z格式的报名表。')

 	$('#submit').click ->
 		if uploaded
-			return modalAlert('报名表已上传', '放心吧，报名表已提交')
+			return modalAlert('报名表已提交', '放心吧，报名表已提交。')

 		for formID in ['#form1', '#form2']
 			if not $(formID).form('validate form')
@@ -137,7 +137,7 @@
 				return $('html, body').animate({scrollTop: offsetTop}, 500)

 		if not $('#form3').form('get values').workshop.length
-			return modalAlert('请选择WorkShop时段', '请至少选择一个WorkShop时段， 时间总是挤出来的嘛')
+			return modalAlert('请选择WorkShop时段', '请选择至少一个WorkShop时段，时间总是挤出来的嘛。')

 		doSubmit()

@@ -146,11 +146,15 @@
 	submitForm = ->
 		switchModalProgress('正在上传信息表')
 		form = $.extend({}, $('#form1').form('get values'), $('#form3').form('get values'))
-		jqxhr = $.post '/joinsdi/upload/form', form, (body)->
-			submitID = body
-			submitAvatar()
+		jqxhr = $.post('/joinsdi/upload/form', form)
+		jqxhr.done (body)->
+			if jqxhr.status == 200
+				submitID = body
+				submitAvatar()
+			else
+				modalAlert('信息表上传失败', '数据错误，请确认所有栏目已规范填写，并向我们反馈。')
 		jqxhr.fail ->
-			modalAlert('网络错误', '信息表上传失败。请确认网络链接正常，或向我们反馈')
+			modalAlert('信息表上传失败', '网络错误，请确认网络连接正常。')

 	submitAvatar = ->
 		switchModalProgress('正在上传照片')
@@ -164,9 +168,9 @@
 			if this.status == 200
 				submitApply()
 			else
-				modalAlert('网络错误', '照片上传失败。请确认网络链接正常，或向我们反馈')
+				modalAlert('照片上传失败', '数据错误，请确认上传的照片的大小，格式符合要求，并向我们反馈。')
 		xhr.onerror = ->
-			modalAlert('网络错误', '照片上传失败。请确认网络链接正常，或向我们反馈')
+			modalAlert('照片上传失败', '网络错误，请确认网络连接正常。')
 		xhr.upload.addEventListener('progress', updateModelProgress)
 		xhr.send(form)

@@ -181,11 +185,12 @@
 		xhr.onload = ->
 			if this.status == 200
 				uploaded = true
-				modalAlert('上传成功', '我们已经向您所填写的邮箱发送了确认邮件，请注意查收')
-			else
-				modalAlert('网络错误', '报名表上传失败。请确认网络链接正常，或向我们反馈')
+				email = $('input[name="email"]').val()
+				modalAlert('上传成功', "我们已经向您所填写的邮箱#{email}发送了确认邮件，请注意查收。")
+			else
+				modalAlert('报名表上传失败', '数据错误，请确认上传的报名表的大小，格式符合要求，并向我们反馈。')
 		xhr.onerror = ->
-			modalAlert('网络错误', '报名表上传失败。请确认网络链接正常，或向我们反馈')
+			modalAlert('报名表上传失败', '网络错误，请确认网络连接正常。')
 		xhr.upload.addEventListener('progress', updateModelProgress)
 		xhr.send(form)

@@ -211,18 +216,19 @@
 	element.children('.header').text(header)
 	element.find('.label').text(header)

-lastProgress = Number.MAX_VALUE
+lastPercent = 100
 updateModelProgress = (event)->
 	if event.lengthComputable
-		if event.loaded < lastProgress
+		percent = Math.round(100 * event.loaded / event.total)
+		if percent < lastPercent
 			$('#progress .ui.progress').progress
-				total: event.total
-				value: event.loaded
+				total: 100
+				value: percent
 		else
-			$('#progress .ui.progress').progress('increment', event.loaded - lastProgress)
-
-		$('#progress .bar .progress').text(parseInt(100 * event.loaded / event.total) + '%')
-		lastProgress = event.loaded
+			$('#progress .ui.progress').progress('increment', percent - lastPercent)
+
+		$('#progress .bar .progress').text(percent + '%')
+		lastPercent = percent

 form1Rule =
 	name:


minor simplification

minor simplification

--- dkobo/koboform/static/js/xlform/model_xlform.coffee
+++ dkobo/koboform/static/js/xlform/model_xlform.coffee
@@ -559,8 +559,7 @@
     @options.parentList = @
   summaryObj: ->
     name: @get("name")
-    options: do =>
-      @options.models.map("toJSON")
+    options: @options.models.map("toJSON")

 class XLF.ChoiceLists extends Backbone.Collection
   model: XLF.ChoiceList


new Mongo server

new Mongo server

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -5,7 +5,7 @@

 version = "0.0.1" #fs.readFileSync nodePath.join(__dirname, '../.revision'), 'utf-8'

-mongo = 'dev:GnDqQWt7iUQK4M@rose.mongohq.com:10084/koding_dev2?auto_reconnect'
+mongo = 'dev:GnDqQWt7iUQK4M@miles.mongohq.com:10057/koding_dev2?auto_reconnect'
 # mongo = 'koding_stage_user:dkslkds84ddj@web0.beta.system.aws.koding.com:38017/koding_stage?auto_reconnect'

 projectRoot = nodePath.join __dirname, '..'


update vagrant config to match latest schema

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -30,6 +30,9 @@
     clusterSize : 2
     queueName   : socialQueueName+'web'
     watch       : yes
+  sourceServer  :
+    enabled     : yes
+    port        : 1337
   mongo         : mongo
   runGoBroker   : yes
   watchGoBroker : no
@@ -141,6 +144,8 @@
     email: ""
     token: ""
     interval: 60000
+  haproxy:
+    webPort     : 3020

   # crypto :
   #   encrypt: (str,key=Math.floor(Date.now()/1000/60))->


Added status mock res.status method to allow unit test to pass

--- routes/blogRoutesTest.coffee
+++ routes/blogRoutesTest.coffee
@@ -86,6 +86,7 @@
   req.params = { topicUrl: "topic-99" }

   res = getBasicResponse()
+  res.status = (status) -> return res
   res.render = (page, viewModel) ->
     test.passIf page is "404", "viewOneInvalid"
     viewRecent() # fire next test
@@ -129,6 +130,7 @@
   req.params = {topicUrl: "topic-99"}

   res = getBasicResponse()
+  res.status = (status) -> return res
   res.render = (page, viewModel) ->
     test.passIf page is "404", "editBadUrl"
     editGoodUrl()
@@ -168,6 +170,7 @@
   req.body = {title: "t1", summary: "s1", content: "c1"}

   res = getBasicResponse()
+  res.status = (status) -> return res
   res.render = (page, viewModel) ->
     test.passIf page is "500", "saveBadId"
     saveNonExistingId()


Add gulp

Add gulp

--- gulp/tasks/lint.coffee
+++ gulp/tasks/lint.coffee
@@ -1,3 +1,4 @@
+gulp   = require("gulp")
 config = require("../config")
 $      = config.plugins


Fixes https://factlink.sirportly.com/staff/tickets/JI-595282?filter=1794 by jumping to the textarea after adding the first user.

--- core/app/backbone/views/conversations/start_conversation_view.coffee
+++ core/app/backbone/views/conversations/start_conversation_view.coffee
@@ -14,6 +14,10 @@

   onRender: ->
     @recipients_container.show @auto_complete_view
+    @recipients.on 'add', @newRecipient
+
+  newRecipient: =>
+    @$('.message-textarea').focus() if @recipients.length == 1

   submit: ->
     # Check for the length of `@recipients`, not `recipients`, to allow sending message to oneself


fix(rpm): Upload RPM builds following naming convention

--- build/tasks/publish-nylas-build-task.coffee
+++ build/tasks/publish-nylas-build-task.coffee
@@ -160,7 +160,7 @@
           if path.extname(file) is '.deb'
             uploadPromises.push uploadToS3(file, "#{fullVersion}/#{process.platform}/#{process.arch}/N1.deb")
           if path.extname(file) is '.rpm'
-            uploadPromises.push uploadToS3(file, "#{fullVersion}/#{process.platform}/#{process.arch}/#{path.basename(file)}")
+            uploadPromises.push uploadToS3(file, "#{fullVersion}/#{process.platform}/#{process.arch}/N1.rpm")

       else
         grunt.fail.fatal "Unsupported platform: '#{process.platform}'"


Add i18n support to sesame information

Add i18n support to sesame information

--- handlers/opserv.coffee
+++ handlers/opserv.coffee
@@ -60,11 +60,11 @@
 adminKey = null
 db.get "SELECT COUNT(*) AS count FROM user_to_permission", (err, row) ->
 	if err?
-		winston.error "Error while checking permissions", err
+		winston.error "Error while checking whether permissions exist", err
 	else
 		if row.count is 0
 			crypto.randomBytes 20, (ex, buf) ->
-				console.log "There are no permissions set. Use the following command to gain administrator privileges: "
+				console.log __("There are no permissions set. Use the following command to gain administrator privileges:")
 				adminKey = (buf.toString 'hex').substring 0, 20
 				console.log "	?sesame #{adminKey}"


Hotfix for mongo config

Hotfix for mongo config

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -149,8 +149,8 @@
   haproxy:
     webPort     : 3020
   kontrold        :
-    mongo         : '127.0.0.1'
-      host        :
+    mongo         :
+      host        : '127.0.0.1'
     rabbitmq      :
       host        : 'salt-master.in.koding.com'
       port        : '5672'


KDEventEmitter: code style

KDEventEmitter: code style

seems like we don't need to distinguish between "straight" and "wild" events when we are removing them.

--- core/KDEventEmitter.coffee
+++ core/KDEventEmitter.coffee
@@ -164,9 +164,9 @@
     if nextNode?
       return removeAllListeners nextNode, edges, it, i + 1

-    if it? and (straight = node[listenerKey])?
+    if it? and (listeners = node[listenerKey])?

-      node[listenerKey] = (listener for listener in straight \
+      node[listenerKey] = (listener for listener in listeners \
                                     when it isnt listener)

     else node[listenerKey] = []


Support header metadata (`{{"id": "foo", "bar": "baz"}}`).

Allow spliting markdown into multiple HTML files (experimental)

TODO: Document new features.

--- tasks/md_to_html.coffee
+++ tasks/md_to_html.coffee
@@ -1,6 +1,6 @@
 #
-# grunt-md-templator
-# https://github.com/realyze/grunt-md-templator
+# grunt-markdown-jade
+# https://github.com/realyze/grunt-markdown-jade
 #
 # Copyright (c) 2013 Tomas Brambora
 # Licensed under the MIT license.
@@ -51,6 +51,8 @@
   beautifyHTML = require('js-beautify').html
   Entities = require('html-entities').AllHtmlEntities
   {decode} = new Entities()
+  path = require 'path'
+  mkdirp = require 'mkdirp'

   # Global options hash.
@@ -73,7 +75,6 @@
   insert = (data, root) ->
     tag = data[0][0]

-    headerId = getHeaderId data
     childTag = _.last _.pluck root.children, 'tag'

     if _.isEmpty(root.children) or compareTags(tag, childTag) >= 0
@@ -81,8 +82,11 @@
       # changing data object.
       getHTML = (data) -> _.union(['html'], JSON.parse JSON.stringify data)

+      metadata = getMetadata data
+
       root.children.push
-        id: headerId
+        id: metadata.id
+        metadata: metadata
         content: markdown.renderJsonML(getHTML data)
         header: markdown.renderJsonML(getHTML [data[0]])
         body: markdown.renderJsonML(getHTML _.rest(data))
@@ -131,33 +135,38 @@
     return root

-  # Returns header id for a region (accessible in the template).
-  # Either you can specify the id in the Markdown file or it will take the
-  # heading text and slugify it.
-  getHeaderId = (data) ->
+  # Returns metadata for a region (accessible in the template).
+  getMetadata = (data) ->
+    grunt.log.debug 'data', data
+
     text = _.last data[0]
-    {id_pattern} = _options
-    match = text.match id_pattern
+    {metadata_pattern} = _options
+    match = text.match metadata_pattern
+
+    metadata = {}
+
+    grunt.log.debug 'match', text, match

     if match
-      # Strip the ID pattern.
-      data[0][data[0].length - 1] = _.trim text.replace(id_pattern, '')
-      headerId = match[1]
-
-    # Slugify the header text.
-    headerId or= _(data[0]).chain().last().slugify().value().toLowerCase()
-
-    return headerId
-
-
-  grunt.registerMultiTask 'md_to_html', 'Transmogrify your MD into HTML.', ->
+      # Strip the metadata pattern.
+      data[0][data[0].length - 1] = _.trim text.replace(metadata_pattern, '')
+      metadata = JSON.parse match[1]
+
+    metadata.id or= _(data[0]).chain().last().slugify().value().toLowerCase()
+
+    grunt.log.debug 'meatadata', metadata
+
+    return metadata
+
+
+  grunt.registerMultiTask 'markdown_jade', 'Turn markdown & template into HTML.', ->

     # Default options.
     _options = @options
-      id_pattern: /{(.+)}/
+      metadata_pattern: /{(.+)}/
       tags: ['h1', 'h2', 'h3']

-    _.each @files, ({src, template, dest}) =>
+    _.each @files, ({src, template, dest, ext}) =>

       files = _.filter src, (filepath) ->
         if not grunt.file.exists filepath
@@ -174,8 +183,62 @@
       _.each parsedFilesTuples, ([filepath, data]) ->
         tpl = template or _options.template

-        grunt.file.copy tpl, dest,
-          process: (contents, path) ->
-            html = grunt.template.process contents, data: data
-            # Pretty print the html.
-            return if _options.pretty then beautifyHTML(html) else html
+        if not _options.multiple_files
+          grunt.file.copy tpl, dest,
+            process: (contents, path) ->
+              html = grunt.template.process contents, data: _.extend(data, _options.data)
+              # Pretty print the html.
+              return if _options.pretty then beautifyHTML(html) else html
+        else
+          splitIntoMultipleFiles(data, tpl, dest, ext)
+
+
+  getDataLeaves = (data, pathToRoot=[], res) ->
+    for child in data.children
+      #parent = if data.id then [data.id] else []
+      getDataLeaves(child, _.union(pathToRoot, [data]), res)
+
+    # Don't forget the leave node.
+    pathToRoot.push(data)
+
+    # We're only interested in the leaves.
+    return unless _.isEmpty(data.children)
+
+    # Forget the first part (that's the "header").
+    res.push _.rest(pathToRoot)
+
+
+  splitIntoMultipleFiles = (data, tpl, dest, ext) ->
+    leaves = []
+    getDataLeaves data, [], leaves
+
+    tmp = []
+
+    # Add the non-leaf paths to the mix as well (so that we have links to
+    # e.g. foo/bar, i.e., the "section overview page").
+    leaves = _.union leaves, (_.initial(p) for p in leaves)
+
+    for leavePath in leaves when leavePath.length > 0
+      parts = _.union([dest], _.pluck(leavePath, 'id'))
+      leaveDest = path.join.apply @, parts
+      leaveDest = "#{leaveDest}#{ext}"
+
+      grunt.log.debug 'mkdirp', path.dirname(leaveDest)
+      grunt.log.debug 'section',_.pluck(leavePath, 'id'), _.last(leavePath)
+
+      if not _.last(leavePath).body
+        grunt.log.debug "skipping ", _.pluck(leavePath, 'id'), ": no body"
+        continue
+
+      mkdirp.sync path.dirname(leaveDest)
+
+      grunt.file.copy tpl, leaveDest,
+        process: (contents, path) ->
+          html = grunt.template.process contents, data: _.extend({
+              section: _.last(leavePath)
+              ids: _.pluck(leavePath, 'id')
+              all: data
+              root: leavePath[0]
+            }, _options.data)
+          # Pretty print the html.
+          return if _options.pretty then beautifyHTML(html) else html


remove extra take(2)

remove extra take(2)

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -629,7 +629,7 @@
       -> unsub()

   takeUntil: (stopper) =>
-    stopped = stopper.skipErrors().map(true).toProperty(false).take(2)
+    stopped = stopper.skipErrors().map(true).toProperty(false)
     withFlag = stopped.combine(this, (stopped, value) -> {value, stopped})
       .takeWhile((({stopped}) -> !stopped))
       .changes()


Print stack on compile error during watch mode

--- src/beans.coffee
+++ src/beans.coffee
@@ -170,7 +170,7 @@
   target = info.targetPath + source.replace(/.coffee$/, '.js')
   makeDir path.dirname(target)
   fs.writeFileSync target, src
-  info.onCompile?(target, src)
+  info.onCompile? target, src
   ts = (new Date()).toLocaleTimeString()
   source = info.sourcePath.substr(path.resolve('.').length + 1) + source
   console.log ts + ' - compiled ' + source
@@ -183,7 +183,10 @@
         compile info, file
       if watch
         watchFiles files, (file) ->
-          compile info, file
+          try
+            compile info, file
+          catch err
+            console.log err.stack

 # Use Stitch to create a browser bundle.
 bundle = (info) ->


Tutorial change, closes #8

Tutorial change, closes #8

--- app/lib/tutorial_steps.coffee
+++ app/lib/tutorial_steps.coffee
@@ -24,8 +24,8 @@

   new Step
     content: inline '''
-      All over the Serengeti, scientists have set up camera traps --
-      cameras that take photos whenever something walks in front of them.
+      All over the Serengeti, scientists have set up motion sensitive camera traps.
+      The camera snaps a few shots anytime something moves in front of it.
       Many of these photos come as a sequence of two or three.
       Check out other snapshots in the sequence using the buttons below the image.
     '''


refactor binary saving.

refactor binary saving.

--- client/landing/app/MainApp/fs/fsfile.coffee
+++ client/landing/app/MainApp/fs/fsfile.coffee
@@ -102,18 +102,39 @@

   saveBinary:(contents, callback)->

-    @abortRequested     = no
     info                = @getLocalFileInfo()
     chunkQueue          = FSFile.createChunkQueue contents, null, info.lastUploadedChunk
     total               = chunkQueue.length

     @setLocalFileInfo totalChunks: total

+    @on "ChunkUploaded", (response)=>
+      loaded   = total - chunkQueue.length
+      percent  = 100 * loaded / total
+      @setLocalFileInfo lastUploadedChunk: loaded
+      callback? null, response, {total, loaded, percent}
+
+    @once "AllChunksUploaded", =>
+      @off "ChunkUploaded"
+      @removeLocalFileInfo()
+      callback? null, finished: yes
+
+    @once "AbortRequested", =>
+      @abortRequested = yes
+      callback? null, abort: yes
+
     iterateChunks = =>
+
+      unless chunkQueue.length
+        @emit "AllChunksUploaded"
+
       next = chunkQueue.shift()
       return unless next
+      return if @abortRequested

-      if next.skip
+      {skip, content, append} = next
+
+      if skip
         callback null, {},
           percent: 100 * info.lastUploadedChunk / info.totalChunks
         iterateChunks()
@@ -122,34 +143,15 @@
       @vmController.run
         method    : 'fs.writeFile'
         vmName    : @vmName
-        withArgs  :
-          path    : FSHelper.plainPath @path
-          content : next.content
-          append  : next.append
+        withArgs  : {path: FSHelper.plainPath(@path), content, append}
       , (err, res) =>
-
         return callback? err  if err
-
-        @setLocalFileInfo lastUploadedChunk: total - chunkQueue.length
-
-        if @abortRequested
-          @abortRequested = no # reset the state
-          callback? null, abort: yes
-          return
-
-        loaded   = total - chunkQueue.length
-        percent  = 100 * loaded / total
-
-        callback? null, res, {total, loaded, percent}
+        @emit "ChunkUploaded", res
         iterateChunks()
-        if chunkQueue.length is 0 # finished all the chunks
-          callback? null, finished: yes
-          @removeLocalFileInfo()

     iterateChunks() if chunkQueue.length > 0

-  abort: ->
-    @abortRequested = yes
+  abort: -> @emit "AbortRequested"

   save:(contents, callback, useEncoding=yes)->


stack-editor: minor fix for edit name link in stack-editor page

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -274,13 +274,12 @@
         { changeTemplateTitle } = EnvironmentFlux.actions
         changeTemplateTitle stackTemplate?._id, e.target.value

+    @header.addSubView @inputTitle = new kd.InputView options
+
     @header.addSubView @editName = new CustomLinkView
       cssClass: 'edit-name'
       title: 'Edit Name'
-      click : =>
-        @inputTitle.setFocus()
-
-    @header.addSubView @inputTitle = new kd.InputView options
+      click : @inputTitle.bound 'setFocus'

     kd.singletons.reactor.observe valueGetter, (value) =>

@@ -294,8 +293,11 @@
       @inputTitle.prepareClone()
       @inputTitle.resize()

-    @inputTitle.on 'blur', =>
-      @editName.show()
+    @inputTitle.on 'blur', @editName.bound 'show'
+
+    @inputTitle.on 'keydown', (event) =>
+      return  unless event.keyCode is 13
+      @inputTitle.setBlur()

     @inputTitle.on 'focus', =>
       @inputTitle.resize()


Tests: Teams: Handle user is member but team not exist case.

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -77,16 +77,30 @@

   fillUsernamePasswordForm: (browser, user) ->

-    browser
-      .waitForElementVisible  teamsModalSelector, 20000
-      .waitForElementVisible  'input[name=username]', 20000
-      .clearValue             'input[name=username]'
-      .setValue               'input[name=username]', user.username
-      .setValue               'input[name=password]', user.password
-      .click                  'button.TeamsModal-button--green'
-      .pause                  2000 # wait for modal change
-
-    @loginAssertion(browser)
+    doneButton         = "#{teamsModalSelector} button.TeamsModal-button--green"
+    usernameInput      = "#{teamsModalSelector} input[name=username]"
+    passwordInput      = "#{teamsModalSelector} input[name=password]"
+    alreadyMemberModal = "#{teamsModalSelector}.alreadyMember"
+
+    browser
+      .waitForElementVisible   teamsModalSelector, 20000
+      .element 'css selector', alreadyMemberModal, (result) =>
+        if result.status is 0
+          browser
+            .waitForElementVisible  passwordInput, 20000
+            .setValue               passwordInput, user.password
+        else
+          browser
+            .waitForElementVisible  usernameInput, 20000
+            .clearValue             usernameInput
+            .setValue               usernameInput, user.username
+            .setValue               passwordInput, user.password
+
+        browser
+          .click doneButton
+          .pause 2000 # wait for modal change
+
+        @loginAssertion(browser)

   loginAssertion: (browser) ->
@@ -138,7 +152,6 @@
     teamsLogin        = '.TeamsModal--login'
     stackCatalogModal = '.StackCatalogModal'
     closeButton       = "#{stackCatalogModal} .kdmodal-inner .closeModal"
-

     browser.url url
     browser.maximizeWindow()


Tests: teamsHelper: Fixed openTeamSettingsModal helper.

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -133,11 +133,14 @@

   openTeamSettingsModal: (browser) ->

-    teamDashboard = '.AppModal--admin'
+    avatarareaPopup = '.avatararea-popup'
+    teamDashboard   = '.AppModal--admin'

     browser
-      .click                  '.avatararea-popup.active .content a[href="/Admin"'
+      .waitForElementVisible  avatarareaPopup
+      .click                  avatarareaPopup + ' .content a[href="/Admin"'
       .waitForElementVisible  teamDashboard, 20000 # Assertion
+      .pause                  200 # Wait for team dashboard
       .assert.containsText    teamDashboard, 'Team Dashboard' # Assertion


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -38,11 +38,13 @@
   window.Color = (color) ->
     color ||= "rgba(0, 0, 0, 0)"

+    parsedColor = null
+
     if arguments.length > 2
       alpha = if arguments[3]? then arguments[3] else 1
       parsedColor = [arguments[0], arguments[1], arguments[2], alpha]
-
-    parsedColor = lookup[normalizeKey(color)] || parseHex(color) || parseRGB(color)
+    else
+      parsedColor = lookup[normalizeKey(color)] || parseHex(color) || parseRGB(color)

     return unless parsedColor


adding docs for Color

adding docs for Color

--- src/color.coffee
+++ src/color.coffee
@@ -610,13 +610,13 @@
     red = Color(255, 0, 0)
     yellow = Color(255, 255, 0)

-    # With no arguments the colors are mixed evenly
+    # With no amount argument the colors are mixed evenly
     orange = red.mixWith(yellow)

     orange.toString()
     # => 'rgba(255, 128, 0, 1)'

-    # With this argument we are mixing the color 30% red and 70% yellow
+    # With an amount of 0.3 we are mixing the color 30% red and 70% yellow
     somethingCloseToOrange = red.mixWith(yellow, 0.3)

     somethingCloseToOrange.toString()
@@ -641,14 +641,14 @@
     yellow = Color(255, 255, 0)
     anotherRed = Color(255, 0, 0)

-    # With no arguments the colors are mixed evenly
+    # With no amount argument the colors are mixed evenly
     red.mixWith$(yellow)

     # We have modified red in place to be orange
     red.toString()
     # => 'rgba(255, 128, 0, 1)'

-    # With this argument we are mixing the color 30% red and 70% yellow
+    # With an amount of 0.3 we are mixing the color 30% red and 70% yellow
     anotherRed.mixWith$(yellow, 0.3)

     # We have modified `anotherRed` in place to be somethingCloseToOrange
@@ -3406,6 +3406,23 @@
   ###*
   Mix two colors. Behaves just like `#mixWith` except that you are passing two colors.

+

+  red = Color(255, 0, 0)
+  yellow = Color(255, 255, 0)
+
+  # With no amount argument the colors are mixed evenly
+  orange = Color.mix(red, yellow)
+
+  orange.toString()
+  # => 'rgba(255, 128, 0, 1)'
+
+  # With an amount of 0.3 we are mixing the color 30% red and 70% yellow
+  somethingCloseToOrange = Color.mix(red, yellow, 0.3)
+
+  somethingCloseToOrange.toString()
+  # => rgba(255, 179, 0, 1)
+

+
   @name mix
   @methodOf Color
   @see Color#mixWith


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -12,7 +12,7 @@
     ].join('').toUpperCase()

   window.Color = (color) ->
-    color ||= 'rgba(0, 0, 0, 0)'
+    color ||= "rgba(0, 0, 0, 0)"

     parsedColor = null


Remove a little bit of redundant code

Remove a little bit of redundant code

--- app/scripts/directives.coffee
+++ app/scripts/directives.coffee
@@ -420,11 +420,8 @@
             else if token.repeat[repeat_idx] > 100
                 token.repeat[repeat_idx] = 100

-            if token.repeat[1] < token.repeat[0] and repeat_idx is 0
-                token.repeat[1] = token.repeat[0]
-
-            if token.repeat[1] < token.repeat[0] and repeat_idx is 1
-                token.repeat[0] = token.repeat[1]
+            if token.repeat[1] < token.repeat[0]
+                token.repeat[(repeat_idx+1)%2] = token.repeat[repeat_idx]

             if token.repeat[1] < 1
                 token.repeat[1] = 1
@@ -457,11 +454,9 @@
             else if token.repeat[between_idx] > 100
                 token.repeat[between_idx] = 100

-            if token.repeat[1] < token.repeat[0] and between_idx is 0
-                token.repeat[1] = token.repeat[0]
-
-            if token.repeat[1] < token.repeat[0] and between_idx is 1
-                token.repeat[0] = token.repeat[1]
+            if token.repeat[1] < token.repeat[0]
+                token.repeat[(between_idx+1)%2] = token.repeat[between_idx]
+

         s.betweenBlur = (between_idx, token_idx) ->
             token = s.data[token_idx]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -10,7 +10,7 @@

   player.flicker
     duration: 20
-    frequency:
-    alpha:
+    frequency: 5
+    alpha: 0.2

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -36,7 +36,7 @@
 test "#sendOrApply", ->
   o = GameObject()

-  o.sendOrApply "pos"
+  p = o.sendOrApply "position"

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


[fix] syncs trigger 'parsed' event

[fix] syncs trigger 'parsed' event

--- app/scripts/syncs/base.coffee
+++ app/scripts/syncs/base.coffee
@@ -25,6 +25,7 @@
       xhr = options.xhr = Backbone.ajax params
       xhr.done (response, status, xhr) =>
         model.set model.parse(response)
+        model.trigger "parsed", model
       model.trigger "request", model, xhr, options
       xhr


test single function factory source

test single function factory source

--- test/integration.coffee
+++ test/integration.coffee
@@ -275,13 +275,30 @@
       test.done()

   'a factory with $nocache property is not cached': (test) ->
-    c =
+    lifetime =
       factories:
         x: -> 1

-    c.factories.x.$nocache = true
-
-    hinoki(c, 'x').then (x) ->
+    lifetime.factories.x.$nocache = true
+
+    hinoki(lifetime, 'x').then (x) ->
       test.equal x, 1
-      test.ok not c.values?
-      test.done()
+      test.ok not lifetime.values?
+      test.done()
+
+  'single factory source function': (test) ->
+    lifetime =
+      factories: (name) ->
+        switch name
+          when 'a' then -> 0
+          when 'b' then -> 1
+          when 'c' then (a, b) -> a + b
+          when 'd' then (b, c) -> b + c
+
+    hinoki(lifetime, 'd')
+      .then (d) ->
+        test.equal d, 2
+        hinoki(lifetime, 'e')
+      .catch hinoki.UnresolvableError, (error) ->
+        test.equal error.message, "unresolvable name 'e' (e)"
+        test.done()


Implicitly test that blessing Workspace descendants works.

--- test/object_test.coffee
+++ test/object_test.coffee
@@ -1,4 +1,5 @@
 redeye_suite = require './support/redeye_suite'
+Workspace = require '../lib/workspace'

 # This class is instantiated and `@bless`'d auto-magically by Redeye.
 class SomeObj
@@ -8,7 +9,7 @@

 # This class' object should be recursovely blessed,
 # giving it access to the `@get_now` method.
-class OtherObj
+class OtherObj extends Workspace
   do_stuff: -> @quux()

 module.exports = redeye_suite


Unused dependencies

Unused dependencies

--- test/plugin-test.coffee
+++ test/plugin-test.coffee
@@ -1,8 +1,3 @@
-fs   = require('fs')
-util = require('util')
-exec = require('child_process').exec
-temp = require('temp').track()
-
 describe "Plugin", ->
   beforeEach ->
     @plugin = new Plugin {}


[test] make deleteById test specific to deleted job

--- test/redis-model.coffee
+++ test/redis-model.coffee
@@ -389,8 +389,8 @@

       await fakeJob queuename, data, "active", ideally defer {queue, job}
       await instance.deleteById queuename, job.jobId, ideally defer _
-      await instance.allKeys null, ideally defer keys
-      expect(keys).to.be.empty
+      await instance.dataById queuename, job.jobId, ideally defer data
+      expect(data).to.be.empty
       done()

   describe "##deleteAll", ->


another metadata year fix

another metadata year fix

--- noddy/service/v2/find.coffee
+++ noddy/service/v2/find.coffee
@@ -432,6 +432,8 @@
       delete metadata.year if isNaN parseInt metadata.year
     catch
       delete metadata.year
+    try
+      delete metadata.year if metadata.year.length isnt 4 or metadata.year.replace(/[^0-9]/gi,'').length isnt 0
   if not metadata.year? and metadata.published?
     try
       mps = metadata.published.split('-')


Fix wrong deprecation wrappers of BrowserWindow

--- atom/browser/api/lib/browser-window.coffee
+++ atom/browser/api/lib/browser-window.coffee
@@ -90,16 +90,18 @@
 deprecate.member BrowserWindow, 'paste', 'webContents'
 deprecate.member BrowserWindow, 'selectAll', 'webContents'
 deprecate.member BrowserWindow, 'reloadIgnoringCache', 'webContents'
-deprecate.member BrowserWindow, 'getPageTitle', 'webContents'
 deprecate.member BrowserWindow, 'isLoading', 'webContents'
 deprecate.member BrowserWindow, 'isWaitingForResponse', 'webContents'
 deprecate.member BrowserWindow, 'stop', 'webContents'
 deprecate.member BrowserWindow, 'isCrashed', 'webContents'
-deprecate.member BrowserWindow, 'executeJavaScriptInDevTools', 'webContents'
 deprecate.member BrowserWindow, 'print', 'webContents'
 deprecate.member BrowserWindow, 'printToPDF', 'webContents'
 deprecate.rename BrowserWindow, 'restart', 'reload'
 deprecate.rename BrowserWindow, 'loadUrl', 'loadURL'
 deprecate.rename BrowserWindow, 'getUrl', 'getURL'
+BrowserWindow::executeJavaScriptInDevTools = deprecate 'executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', (code) ->
+  @devToolsWebContents?.executeJavaScript code
+BrowserWindow::getPageTitle = deprecate 'getPageTitle', 'webContents.getTitle', ->
+  @webContents?.getTitle()

 module.exports = BrowserWindow


cleanup variance and split data for different colors

--- dashing/javascripts/app/views/detail.coffee
+++ dashing/javascripts/app/views/detail.coffee
@@ -14,22 +14,50 @@
     initialize: ->
       @id = @model.cid

-    valueChart: ->
+    splitData: (data)->
+      pass = []
+      fail = []
+      warn = []
+
       m = @model

-      if m.get('display') == 'rate'
-        data = m.get('rates')
+      _.times data.length, (i) ->
+        val = data[i].value
+        x = m.get('values')[i].date.getTime()
+
+        if m.get('target')
+          target = m.getCompareAndVal(m.get('target'))
+        else
+          pass.push([x, val])
+          return
+
+        if m.get('warning')
+          warning = m.getCompareAndVal(m.get('warning'))
+
+        if not m.compareVal(target, val)
+          if warning and m.compareVal(warning, val)
+            warn.push([x, val])
+          else
+            fail.push([x, val])
+        else
+          pass.push([x, val])
+
+      return [pass, fail, warn]
+
+    valueChart: ->
+      if @model.get('display') == 'rate'
+        data = @model.get('rates')
         display = "Rate (%)"
       else
-        data = m.get('values')
+        data = @model.get('values')
         display = "Volume"

-      values = _.map(data, (v, i)->
-        return [v.date.getTime(), v.value])
+      dataSeries = @splitData(data)

       container = $('#val-chart')[0]
       graphOpts =
         title: display
+        colors: ['green', 'red', 'orange']
         bars:
           show: true
           barWidth: 1000 * 43000
@@ -43,45 +71,7 @@
         markers:
           show: true

-      graph = Flotr.draw(container, [values], graphOpts)
-
-    rateChart: ->
-      yLabel = 'Rate'
-      m = @model
-
-      values = []
-      sum = 0
-
-      _.times m.get('values').length, (i) ->
-        val = m.get('values')[i].value
-        total = m.get('rel_total').get('values')[i].value
-        x = m.get('values')[i].date.getTime()
-
-        rate = Math.round(val/total * 100)
-        sum += rate
-
-        values.push([x, rate])
-
-      mean = []
-
-      _.each m.get('values'), (v)->
-          mean.push([v.date.getTime(), Math.round(sum/values.length)])
-
-      container = $('#rate-chart')[0]
-      graphOpts =
-        title: "% of Total"
-        lines:
-          fill: true
-        xaxis:
-          mode: 'time'
-          labelsAngle: 45
-          timeMode: 'local'
-          noTicks: 10
-        yaxis:
-          min: 0
-          max: 100
-
-      graph = Flotr.draw(container, [values], graphOpts)
+      graph = Flotr.draw(container, dataSeries, graphOpts)

     varianceChart: ->
       yLabel = 'Variance from Target'
@@ -89,42 +79,30 @@
       max = 100
       min = -100

-      values = []
-      sum = 0
-
       if m.get('display') == 'rate'
         data = m.get('rates')
       else
         data = m.get('values')

-      _.times data.length, (i) ->
-        val = data[i].value
-        target = m.getCompareAndVal(m.get('target'))
+      dataSeries = @splitData(data)
+      target = m.get('targetVal')

-        if m.get('display') != 'rate'
-          variance = Math.abs(m.get('targetVal') - val)
-          variance = Math.round((variance / m.get('targetVal')) * 100)
-        else
-          variance = Math.round((val / m.get('targetVal')) * 100)
+      dataSeries = _.map(dataSeries, (series, i)->
+        _.map(series, (val)->
+          if i == 0
+            variance = Math.abs(target - val[1])
+          else
+            variance =  Math.abs(target - val[1]) * -1

-        if m.compareVal(target, val)
-          if target[0][0] == '<'
-            variance = 100 - variance
-        else
-          if target[0][0] == '>'
-            variance = 100 - variance
+          if variance > max
+            max = variance

-          variance = variance * -1
+          if variance < min
+            min = variance

-        x = m.get('values')[i].date.getTime()
-        values.push([x, variance])
-
-        if variance > max
-          max = variance
-
-        if variance < min
-          min = variance
-
+          return [val[0], variance]
+        )
+      )

       aMin = Math.abs(min)

@@ -135,7 +113,11 @@

       container = $('#rate-chart')[0]
       graphOpts =
-        title: "% Variance from Target"
+        title: "Variance"
+        colors: ["green", "red", "orange"]
+        bars:
+          show: true
+          barWidth: 1000 * 43000
         xaxis:
           mode: 'time'
           labelsAngle: 45
@@ -147,7 +129,7 @@
         mouse:
           track: true

-      graph = Flotr.draw(container, [values], graphOpts)
+      graph = Flotr.draw(container, dataSeries, graphOpts)

     render: ->
       @$el.attr('id', @id)
@@ -165,6 +147,8 @@

       @$el.on 'shown', =>
         @valueChart()
-        @varianceChart()
+
+        if @model.get('target')
+          @varianceChart()

       @$el.modal()


object "all"-key functions

object "all"-key functions

--- src/6.misc.coffee
+++ src/6.misc.coffee
@@ -3,8 +3,11 @@
     for key in Object.keys(x)
         y[key] = x[key]
     y
+Object.allKeys = (x) -> key for key of x
 Object.keyValues = (x) -> Object.keys(x).map((key) -> [key, x[key]])
+Object.allKeyValues = (x) -> Object.allKeys(x).map((key) -> [key, x[key]])
 Object.forEach = (x, callback) -> Object.keys(x).forEach((key) -> callback(key, x[key]))
+Object.forEachOfAll = (x, callback) -> Object.allKeys(x).forEach((key) -> callback(key, x[key]))
 JSON.clone = (x) -> JSON.parse(JSON.stringify(x))
 Date::add = (x) -> # `x` must be a number
     new Date(@ - (-x))


enUS: Change to use trimWhitespace module

--- src/en-us.coffee
+++ src/en-us.coffee
@@ -17,6 +17,7 @@
 import splitInt from './utils/split-int.coffee'
 import speakByDigit from './utils/speak-by-digit.coffee'
 import parse99 from './utils/parse-99.coffee'
+import trimWhitespace from './utils/trim-whitespace.coffee'

 defaultOptions =
 	type: 'number'
@@ -72,7 +73,7 @@
 			unit = ' ' + n1000[times]
 			item = speak999(parseInt(item, 10), addAnd)
 			output.push item + unit
-	output.join(' ').replace(/^\s+|\s+$/g,'') # trim whitespace
+	trimWhitespace(output.join(' '))

 # Main function 1 of 2
 speakNum = (str) ->


little refactoring re: token value lookup in the context

--- fizzylogo_Token.coffee
+++ fizzylogo_Token.coffee
@@ -13,6 +13,13 @@
     toBeReturned.isStatementSeparator = ->
       return @value == ";"

+
+    toBeReturned.lookup = (theContext) ->
+      console.log "evaluation " + indentation() + "looking up temp token: " + @value
+      existingLookedUpValuePlace = theContext.whichDictionaryContainsToken @
+      if existingLookedUpValuePlace?
+        console.log "evaluation " + indentation() + "found temp token: " + @value
+        return theContext.lookUpTokenValue @, existingLookedUpValuePlace

     toBeReturned.eval = (theContext, remainingMessage) ->
@@ -30,11 +37,9 @@
       # if there is, that wins all the times, so you could
       # have an exotic value for "false", or "2" that is completely
       # different from what it would naturally be.
-      console.log "evaluation " + indentation() + "looking up temp token: " + @value
-      existingLookedUpValuePlace = theContext.whichDictionaryContainsToken @
-      if existingLookedUpValuePlace?
-        console.log "evaluation " + indentation() + "found temp token: " + @value
-        return theContext.lookUpTokenValue @, existingLookedUpValuePlace
+      lookup = @lookup theContext
+      if lookup? then return lookup
+
       # you could match the leading "+" or "-", however this would
       # be uneven with the general case of handling leading + and -
       #else if /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/.test @value


new definition scheme allows for arbitrary media types

--- src/action.coffee
+++ src/action.coffee
@@ -10,13 +10,9 @@
     @status = response?.status || 200
     if request?.type
       @request_schema = @schema_manager.find mediaType: request.type
-      unless @request_schema
-        throw new Error "No schema found for request '#{request.type}'"

     if response?.type
       @response_schema = @schema_manager.find mediaType: response.type
-      unless @response_schema
-        throw new Error "No schema found for response '#{request_schema}'"

     @_base_headers = @base_headers(@definition)


Removing extra debug log

Removing extra debug log

This shouldn’t have been checked in.

--- src/aether.coffee
+++ src/aether.coffee
@@ -94,10 +94,7 @@
     return true if careAboutLineNumbers and @language.hasChangedLineNumbers a, b
     return true if careAboutLint and @hasChangedLintProblems a, b
     # If the simple tests fail, we compare abstract syntax trees for equality.
-    result = @language.hasChangedASTs a, b
-    if result
-      console.log 'Aether hasChangedSignificantly', result
-    result
+    @language.hasChangedASTs a, b

   # Determine whether two strings of code produce different lint problems.
   hasChangedLintProblems: (a, b) ->


feat(vm.snapshot): copy ACLs from VM to snapshot (#495)

See vatesfr/xo-web#1865

Also, correctly remove ACLs on VM deletion.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -1,6 +1,5 @@
 $assign = require 'lodash/assign'
 $debug = (require 'debug') 'xo:api:vm'
-$filter = require 'lodash/filter'
 $findIndex = require 'lodash/findIndex'
 $findWhere = require 'lodash/find'
 concat = require 'lodash/concat'
@@ -23,6 +22,7 @@
   formatXml: $js2xml,
   isArray: $isArray,
   map,
+  mapFilter,
   mapToArray,
   noop,
   parseSize,
@@ -301,6 +301,16 @@

   xapi = @getXapi(vm)

+  @getAllAcls().then((acls) =>
+    Promise.all(mapFilter(acls, (acl) =>
+      if (acl.object == vm.id)
+        return pCatch.call(
+          @removeAcl(acl.subject, acl.object, acl.action),
+          noop
+        )
+    ))
+  )
+
   # Update IP pools
   yield Promise.all(map(vm.VIFs, (vifId) =>
     vif = xapi.getObject(vifId)
@@ -642,6 +652,18 @@
   yield checkPermissionOnSrs.call(this, vm)

   snapshot = yield @getXapi(vm).snapshotVm(vm._xapiRef, name ? "#{vm.name_label}_#{new Date().toISOString()}")
+
+  # Copy VM's ACLs to the snapshot
+  @getAllAcls().then((acls) =>
+    Promise.all(mapFilter(acls, (acl) =>
+      if (acl.object == vm.id)
+        return pCatch.call(
+          @addAcl(acl.subject, snapshot.$id, acl.action),
+          noop
+        )
+    ))
+  )
+
   return snapshot.$id

 snapshot.params = {


Fail if parsing of Liquid-template fails

--- src/index.coffee
+++ src/index.coffee
@@ -45,7 +45,7 @@
           grunt.file.write fp.dest, output
           grunt.log.writeln "File \"#{fp.dest}\" created."
         .catch (e) ->
-          grunt.log.warn e
+          grunt.fail.warn e
         .finally done

       parsePromise.catch (e) ->


testing

testing

--- src/index.coffee
+++ src/index.coffee
@@ -36,6 +36,7 @@
         @endMeeting meetingId

   endMeeting: (meetingId) =>
+    console.log "====================================="
     console.log 'Ending meeting with meetingId:', meetingId
     message = {
       devices: [ "*" ]


Fixed hostmapping override assignment not working

--- src/index.coffee
+++ src/index.coffee
@@ -66,7 +66,7 @@
             # Non browser environments or developer builds will want to override
             # detected settings
             #
-            overrideMapping settings.get( "overrideMapping" )
+            overrideMapping = settings.get( "overrideMapping" )
             if overrideMapping?
                 settings.set( "currentHostMapping", overrideMapping )


Removed trailing console log

Removed trailing console log

--- src/index.coffee
+++ src/index.coffee
@@ -5,7 +5,6 @@
 CliParser = require './lib/cli-parser'
 cliParser = new CliParser()
 options = cliParser.initCommander()
-console.log options
 if options?
 	svgAssets = new SvgAssets options
 	svgAssets.process()


Fix Jonathan's mistakes

Fix Jonathan's mistakes

--- src/index.coffee
+++ src/index.coffee
@@ -86,7 +86,7 @@

     @robot.on "GithubPullRequestReviewed", (pr, sender) =>
       @robot.logger.debug "Sending PR reviewed notice to #{pr.user.login}, sender is #{sender?.name}"
-      @adapter.dm pr.requested_reviewer,
+      @adapter.dm pr.creator,
         text: """
           Your pull request has been reviewed by #{if sender then "by #{sender.name}" else ""}
         """


Fix Uncaught ReferenceError: cnt is not defined

--- src/index.coffee
+++ src/index.coffee
@@ -21,7 +21,7 @@

   _cnt = 0
   done = (err) ->
-    return next err if ++cnt is 1
+    return next err if ++_cnt is 1

   new Form()


Remove use of (deprecated) `transferPropsTo`

--- src/index.coffee
+++ src/index.coffee
@@ -17,7 +17,9 @@
     else 'Component'
   React.createClass
     displayName: "Pressable#{ wrappedDisplayName }"
-    render: -> @transferPropsTo (Pressable component: componentClass, @props.children)
+    render: ->
+      newProps = extend @props, component: componentClass
+      (Pressable newProps, @props.children)

 combineHandlers = (handlers...) ->
   handlers = (handler for handler in handlers when handler) # Filter out nulls


removed a dubious file

removed a dubious file

--- src/input.coffee
+++ src/input.coffee
@@ -1,25 +0,0 @@
-class Input
-  constructor: (@str) ->
-    @len = str.length
-    @pos = 0
-
-  hasMore: () -> @pos < @len
-
-  peek: (n) ->
-    i = @pos + n
-    @str.charAt i if i < @len
-
-  inc: () -> @pos++
-
-  advance: (f) ->
-    while @pos < @len and f @str.charAt @pos
-      @pos++
-
-  get: () -> @str.substring @start, @pos
-
-  save: () ->  @start = @pos
-
-  restore: () -> @pos = @start
-
-module.exports = Input
-


Add `Batman.App.currentRoute` property.

Add `Batman.App.currentRoute` property.

--- src/batman.coffee
+++ src/batman.coffee
@@ -1489,6 +1489,7 @@
       $redirect('/404')

     @app.set 'currentURL', url
+    @app.set 'currentRoute', route

 # History Manager
 # ---------------


Add a way to make comparators, made sortItems work

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- src/items.coffee
+++ src/items.coffee
@@ -64,7 +64,17 @@
 ##
 # Sorts all items by the given comparator.
 #
-lol.items.sortItems = (comparator) -> lol.items.list.clone().sort comparator
+lol.items.sortItems = (comparator) ->
+  list = []
+  for itemName, item of lol.items.list
+    list.push item
+  list.sort comparator
+  return list
+
+##
+# Makes a comparator to sort by.
+#
+lol.items.makeComparator = (property, asc = true) -> (a, b) -> if asc then (a[property] || 0) - (b[property] || 0) else (b[property] || 0) - (a[property] || 0)

 ##
 # Defines a new item.


fixed broken test

fixed broken test

--- jsapp/test/unit/Factory/RestApi.Factory.Tests.coffee
+++ jsapp/test/unit/Factory/RestApi.Factory.Tests.coffee
@@ -6,7 +6,7 @@
       factory.createSurveyDraftApi()
       expect(resourceSpy).toHaveBeenCalledWith "/api/survey_drafts"

-    it "should invoke $resource with a custom save object when an id is provided", ->
+    iit "should invoke $resource with a custom save object when an id is provided", ->
       resourceSpy = sinon.spy()
       factory = restApiFactory(resourceSpy)
       factory.createSurveyDraftApi 1
@@ -15,3 +15,6 @@
       ,
         save:
           method: "PATCH"
+        publish:
+          method: "POST"
+          url: "/api/survey_drafts/:id/publish"


add space between words in placeholder

add space between words in placeholder

--- lib/assets/javascripts/directives/ngupdate.js.coffee
+++ lib/assets/javascripts/directives/ngupdate.js.coffee
@@ -14,7 +14,7 @@
       else
         placeholder = ''
         for word in callModel.split('.').pop().split('_')
-          placeholder += word[0].toUpperCase() + word[1..-1].toLowerCase()
+          placeholder += word[0].toUpperCase() + word[1..-1].toLowerCase() + ' '
       callModel += ',' + attributes.ngTrackBy if attributes.ngTrackBy
       callFunction = 'update("' + callModel + '")'
       callFunction += ';' + attributes.ngCallback if attributes.ngCallback


Prevent duplicate socket connections. Fixes loafoe/hubot-matteruser#52

--- src/client.coffee
+++ src/client.coffee
@@ -177,6 +177,9 @@
         options.agent = new HttpsProxyAgent(@httpProxy) if @httpProxy

         # Set up websocket connection to server
+        if @ws
+            @ws.close()
+            @ws = null
         @ws = new WebSocket @socketUrl, options

         @ws.on 'error', (error) =>


完成所有步骤

完成所有步骤

--- lib/gitCreatePackage/gitCreatePackage-manager.coffee
+++ lib/gitCreatePackage/gitCreatePackage-manager.coffee
@@ -80,14 +80,39 @@

         cp.on 'exit', (code, signal)->
           console.log "code:#{code}   signal： #{signal}"
-          return reject new Error "SIGTERM" if signal is 'SIGTERM' and code == null
+          if signal is 'SIGTERM' and code == null
+            reject new Error "SIGTERM"
+          else
+            resolve(repoUrl)

         cp.stdout.on 'data', (data) -> notify stdout: data.toString()
         cp.stderr.on 'data', (data) -> notify stderr: data.toString()
-    .then ->
+    .then (repoUrl)->
       # 开始发布到chameleon packagesManager
-
-
+      server = atom.config.get('atom-butterfly.puzzleServerAddress')
+      r = request.post {url:"#{server}/api/packages", timeout: 1000*60*10}, (err, httpResponse, body) ->
+        reject(err) if err
+        if httpResponse.statusCode is 201
+          resolve
+            result: true
+            statusCode: 201
+            body: body
+        else if httpResponse.statusCode is 403
+          resolve
+            result: false
+            statusCode: 403
+            body: body
+      form = r.form()
+      form.append "access_token","#{atom.config.get('atom-butterfly.puzzleAccessToken')}"
+      form.append "name", info.packageName
+      form.append "repository_url", repoUrl
+      form.append "description", info.describe || info.packageName
+      form.append "previews", info.previews if info.previews
+      # form.append "tags", info.tags if info.tags
+    .then (obj) ->
+      # TODO 是否更新此package
+      if obj.statusCode is 403
+        console.log 'update this package...'
     .progress (notify) ->
       console.log notify.stdout if notify.stdout
       console.error notify.stderr if notify.stderr
@@ -96,4 +121,4 @@
       done(error)
       alert("#{error}")
     .finally ->
-      console.log "发布package finally."
+      console.log "publish package finally."


remove a console.log

remove a console.log

--- gameViewDefault.coffee
+++ gameViewDefault.coffee
@@ -116,7 +116,6 @@
     $(event.currentTarget.parentElement).toggleClass 'collapsed'

   'click .game-panel.zoomable, click .game-panel.panel-visibility-stack': (event) ->
-    console.log @, event.currentTarget
     event.preventDefault()
     event.stopImmediatePropagation()
     @toggleZoom()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -1,5 +1,6 @@
 ( ->
   rgbParser = /^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),?\s*(\d?\.?\d*)?\)$/
+  hslParser = /^hsla?\((\d{1,3}),\s*(\d?\.?\d*),\s*(\d?\.?\d*),?\s*(\d?\.?\d*)?\)$/

   parseRGB = (colorString) ->
     return undefined unless channels = rgbParser.exec(colorString)
@@ -8,6 +9,14 @@
     parsedColor[3] ||= 1.0

     return parsedColor
+
+  parseHSL = (colorString) ->
+    return undefined unless channels = hslParser.exec(colorString)
+
+    hslMap = bits.splice(1, 3).map (channel) ->
+      parseFloat channel
+
+    return hslToRgb(hslMap.concat(if bits[1]? then parseFloat(bits[1]) else 1.0))

   Color2 = (args...) ->
     __proto__: Color::