more storage docs

more storage docs

--- lib/argumenta/storage.coffee
+++ lib/argumenta/storage.coffee
@@ -52,6 +52,7 @@
   #
   # @param [User] user A valid user with a unique username.
   # @param [Function] cb(err) Called on completion or error.
+  # @param [StorageError|ValidationError|StorageConflictError] err Any error.
   addUser: (user, cb) ->
     unless user instanceof User
       return cb new @Error "User instance required to add user."
@@ -65,7 +66,8 @@

   # Delete *all* entities from the store.
   #
-  # @param [Function] cb(err) Called on completion or error.
+  # @param [Function]     cb(err) Called on completion or error.
+  # @param [StorageError] err Any error.
   clearAll: (cb) ->
     @store.clearAll (err) ->
       return cb err
@@ -73,8 +75,10 @@
   # Get a user by username, omitting sensitive fields.
   #
   # @see getPasswordHash()
-  # @param [String]   username Of the user to retrieve.
-  # @param [Function] cb Called on completion or error.
+  # @param [String]       username Of the user to retrieve.
+  # @param [Function]     cb(err, user) Called on completion or error.
+  # @param [StorageError] err Any error.
+  # @param [Object]       user The retrieved user's public fields.
   getUserByName: (username, cb) ->
     @store.getUserByName username, (err, user) ->
       return cb new @Error("Failed getting user: " + username, err) if err
@@ -82,16 +86,20 @@

   # Get a user's password hash.
   #
-  # @param [String]   username Of the user whose hash to retrieve.
-  # @param [Function] cb Called on completion or error.
+  # @param [String]       username Of the user whose hash to retrieve.
+  # @param [Function]     cb(err, hash) Called on completion or error.
+  # @param [StorageError] err Any error.
+  # @param [String]       hash The retrieved password hash.
   getPasswordHash: (username, cb) ->
-    @store.getPasswordHash username, (err, password_hash) ->
+    @store.getPasswordHash username, (err, hash) ->
       return cb new @Error("Failed getting user: "  + username, err) if err
-      return cb null, password_hash
+      return cb null, hash

   # Get an array of all users, omitting sensitive fields.
   #
-  # @param [Function] cb Called on completion or error.
+  # @param [Function]      cb(err, users) Called on completion or error.
+  # @param [StorageError]  err Any error.
+  # @param [Array<Object>] users An object array of users' public fields.
   getAllUsers: (cb) ->
     @store.getAllUsers (err, users) ->
       return cb new @Error "Failed getting all users from store." if err


Removed Mongo.coffee module

Removed Mongo.coffee module


Move to 2d transform

Move to 2d transform

--- src/paper.coffee
+++ src/paper.coffee
@@ -767,8 +767,8 @@

       # css-transform the drag canvas to that area
       dragCanvas.style.webkitTransform = "translate(#{scrollDest.x}px, #{scrollDest.y}px)"
-      dragCanvas.style.mozTransform = "translate3d(#{scrollDest.x}px, #{scrollDest.y}px, 0)"
-      dragCanvas.style.transform = "translate3d(#{scrollDest.x}px, #{scrollDest.y}px, 0)"
+      dragCanvas.style.mozTransform = "translate(#{scrollDest.x}px, #{scrollDest.y}px)"
+      dragCanvas.style.transform = "translate(#{scrollDest.x}px, #{scrollDest.y}px)"

       event.preventDefault()


raspberrypi4-64.coffee: Clean-up device pretty name

We only support 64 bits OS for the pi4 so let's not
explicitly mention it.

Changelog-entry: Remove the 64 bits OS reference from the rpi4 device pretty name
Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@balena.io>

--- raspberrypi4-64.coffee
+++ raspberrypi4-64.coffee
@@ -5,7 +5,7 @@
 	version: 1
 	slug: 'raspberrypi4-64'
 	aliases: [ 'raspberrypi4-64' ]
-	name: 'Raspberry Pi 4 (using 64bit OS)'
+	name: 'Raspberry Pi 4'
 	arch: 'aarch64'
 	state: 'experimental'


LEV-12 recolor with colorMap fixed

LEV-12 recolor with colorMap fixed

--- modules/levelcreator/app/assets/javascripts/level_creator/plugins/recolor.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator/plugins/recolor.coffee
@@ -60,15 +60,19 @@
     colorMap = @assetHandler.getPixelArray(colorMapName)

     for i in [0...rgba.length] by 4
+
+      if rgba[i + 3] is 0
+        continue
+
       r = rgba[i + 0]
       g = rgba[i + 1]
       b = rgba[i + 2]
-      a = rgba[i + 3]
+      #a = rgba[i + 3]
       luminance = Math.floor((0.2126 * r) + (0.7152 * g) + (0.0722 * b)) * 4
       rgba[i + 0] = colorMap[luminance + 0]
       rgba[i + 1] = colorMap[luminance + 1]
       rgba[i + 2] = colorMap[luminance + 2]
-      rgba[i + 3] = colorMap[luminance + 3]
+      #rgba[i + 3] = colorMap[luminance + 3]

     rgba


koding-bash-user-glue: accidentally saved this file

formatting is slightly improved and whitespace is removed, even though we don't really use this module anymore.

--- node_modules_koding/koding-bash-user-glue/lib/koding-bash-user-glue/index.coffee
+++ node_modules_koding/koding-bash-user-glue/lib/koding-bash-user-glue/index.coffee
@@ -1,4 +1,4 @@
-exports.bash =do->
+exports.bash = do ->

   glueEsc =(userInput)-> userInput.replace /'/g, "'\"'\"'"

@@ -8,7 +8,7 @@
   	#
   	# we're ATTEMPTING to ensure that, no matter what, arbitrary bash
   	# commands can't be executed as root.  we are making sure that all user
-  	# input is encased inside single quotes, regardless of its context, and
+  	# input is encased inside single quotes, regardless of its context, and
   	# that any single-quotes interior to the user input are "escaped" in a
   	# bash-specific syntax (i.e. string gluing).  The idea is, no matter
   	# what the user input, treat every character literally.
@@ -17,11 +17,11 @@
   	#
   	# BUT IT MAY NOT WORK!
   	#
-  	# Use of other, better, more appropriate and/or specific filters is
+  	# Use of other, better, more appropriate and/or specific filters is
   	# strongly encouraged!
   	#
-  	# Better yet, never do an insane thing like running commands as root,
-  	# which are interpolated with raw user input, if it can be avoided.
+  	# Better yet, never do an insane thing like running commands as root,
+  	# which are interpolated with raw user input, if it can be avoided.
   	# This is meant as a safety net for victims of some obivious exploits.
   	#
   	# AGAIN: IT MAY NOT WORK!


Made some minor tweaks to the executor module

--- lib/debugger/executor.coffee
+++ lib/debugger/executor.coffee
@@ -10,19 +10,19 @@
     command = "java"
     args = ["-jar", debuggerPath]
     stdout = (output) => @handleOutput(output)
-    exit = (code) => @handleExit()
+    exit = (code) => @handleExit(code)
     @process = new BufferedProcess({command, args, stdout, exit})
     @emitter = new Emitter()

-  handleExit: ->
+  handleExit: (code) ->
     @emitStop()
     @resetFlags()

   handleOutput: (output) ->
-    if (output.indexOf("!!!VIEWCHANNELREADY!!!") > -1)
+    if (output.indexOf('!!!VIEWCHANNELREADY!!!') > -1)
       @viewChannelReady = true

-    if (output.indexOf("!!!RUNTIMECHANNELREADY!!!") > -1)
+    if (output.indexOf('!!!RUNTIMECHANNELREADY!!!') > -1)
       @runtimeChannelReady = true

     if (@viewChannelReady && @runtimeChannelReady)
@@ -52,5 +52,6 @@
 module.exports =
 class ExecutorProvider
   instance = null
+
   @getInstance: ->
     instance ?= new Executor()


Fix error on reload if database panel is closed

--- lib/existdb-tree-view.coffee
+++ lib/existdb-tree-view.coffee
@@ -537,7 +537,7 @@

         remove: ->
           super
-          @panel.destroy()
+          @panel?.destroy()

         # Toggle the visibility of this view
         toggle: ->


More 404.

More 404.

--- src/proxy.coffee
+++ src/proxy.coffee
@@ -114,7 +114,8 @@
 KITTY_INDEX = "#{__dirname}/../kitties.txt"

 BLACKLIST = [
-    "atelierzen.canalblog.com"
+    "atelierzen.canalblog.com",
+    "www.shipleyschool.org"
 ]

 BLACKLIST_CONTENT = FS.readFileSync "#{__dirname}/../static/404.html"


Remove TreeWalker

Remove TreeWalker

Should be IE8 compatible now?

--- src/range.coffee
+++ src/range.coffee
@@ -198,12 +198,20 @@
     document = bounds.ownerDocument

     if not Util.contains(bounds, @start)
-      w = document.createTreeWalker(bounds, NodeFilter.SHOW_TEXT, null, false)
-      @start = w.firstChild()
+      node = bounds
+      while node? and node = firstLeaf(node)
+        if node.nodeType is Util.NodeTypes.TEXT_NODE
+          break
+        node = node.nextBranch(bounds, node)
+      @start = node

     if not Util.contains(bounds, @end)
-      w = document.createTreeWalker(bounds, NodeFilter.SHOW_TEXT, null, false)
-      @end = w.lastChild()
+      node = bounds
+      while node? and node = lastLeaf(node)
+        if node.nodeType is Util.NodeTypes.TEXT_NODE
+          break
+        node = node.previousBranch(bounds, node)
+      @end = node

     return null unless @start and @end


Use valid biscotto link

Use valid biscotto link

--- src/range.coffee
+++ src/range.coffee
@@ -13,7 +13,7 @@
 # ```
 module.exports =
 class Range
-  # Public: Call this with the result of {::serialize} to construct a new Range.
+  # Public: Call this with the result of {Range.serialize} to construct a new Range.
   @deserialize: (array) ->
     new this(array...)


unless (!==)

unless (!==)

--- js/main.coffee
+++ js/main.coffee
@@ -1,4 +1,3 @@
 do ->
-  arr = [1, 2, 3]
-  for number in arr
-    console.log(number)
+  answer = 20
+  console.log six = (one = 1) + (two = 2) + (three = 3) unless answer is 10


:bug: Fix git commit --amend

:bug: Fix git commit --amend

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -37,7 +37,7 @@

 showFile = ->
   split = ''
-  split = 'right'  if atom.config.get 'git-plus.openInPane'
+  split = 'right' if atom.config.get 'git-plus.openInPane'
   atom.workspace
     .open(commitFilePath(), split: split, activatePane: true)
     # ::open returns a promise resolving to the editor
@@ -49,7 +49,7 @@
   watcher.close()
   cleanFile()
   args = ['commit', '--cleanup=strip', "--file=#{commitFilePath()}"]
-  args.push '--amend' if amendMsg is not ''
+  args.push '--amend' if amendMsg isnt ''
   git.cmd(
     args: args,
     stdout: (data) ->


Handle keyup events in `handleKeyboardEvent`

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -416,9 +416,13 @@
       event.preventDefault()
       return

-    @queuedKeyboardEvents.push(event)
-    @queuedKeystrokes.push(keystroke)
-    keystrokes = @queuedKeystrokes.join(' ')
+    if event.type is 'keyup'
+      keystrokes = "#{@lastKeydownKeystrokes}^#{keystroke}"
+    else
+      @queuedKeyboardEvents.push(event)
+      @queuedKeystrokes.push(keystroke)
+      keystrokes = @queuedKeystrokes.join(' ')
+      @lastKeydownKeystrokes = keystrokes

     # If the event's target is document.body, assign it to defaultTarget instead
     # to provide a catch-all element when nothing is focused.


added try catch in yaml

added try catch in yaml

--- src/lib/PortFinder.coffee
+++ src/lib/PortFinder.coffee
@@ -34,7 +34,11 @@

   ## -- Private ----------------------------------------------------------------

-  _readYAMLFile: (filePath) -> yaml.safeLoad(fs.readFileSync(filePath, 'utf8'))
+  _readYAMLFile: (filePath) ->
+    try
+      yaml.safeLoad(fs.readFileSync(filePath, 'utf8'))
+    catch e
+      throw new Error e

   _getPort: (serviceName, protocol) ->
     service = @services[serviceName]


Find selections always open when either find view is open

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -109,6 +109,8 @@
   attach: ->
     atom.workspaceView.prependToBottom(this) unless @hasParent()

+    atom.workspaceView.addClass('find-visible')
+
     selectedText = atom.workspace.getActiveEditor()?.getSelectedText?()
     if selectedText and selectedText.indexOf('\n') < 0
       @findEditor.setText(selectedText)
@@ -118,6 +120,8 @@

   detach: ->
     return unless @hasParent()
+
+    atom.workspaceView.removeClass('find-visible')

     @hideAllTooltips()
     atom.workspaceView.focus()


Add tooltips for project find

Add tooltips for project find

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -47,6 +47,12 @@
     @caseOptionButton.addClass('selected') if @model.caseSensitive

     @clearMessages()
+    @initializeTooltips()
+
+  initializeTooltips: ->
+    @regexOptionButton.setTooltip("Use Regex", command: 'project-find:toggle-regex-option', commandElement: @findEditor)
+    @caseOptionButton.setTooltip("Match Case", command: 'project-find:toggle-case-option', commandElement: @findEditor)
+    @replaceAllButton.setTooltip("Replace All", command: 'project-find:replace-all', commandElement: @replaceEditor)

   serialize: ->
     attached: @hasParent()


Update text buffer to use telepath markers

--- lib/snippet-expansion.coffee
+++ lib/snippet-expansion.coffee
@@ -23,8 +23,8 @@
         @editSession.normalizeTabsInBufferRange(newRange)
       @indentSubsequentLines(startPosition.row, snippet) if snippet.lineCount > 1

-  cursorMoved: ({oldBufferPosition, newBufferPosition, bufferChanged}) ->
-    return if @settingTabStop or bufferChanged
+  cursorMoved: ({oldBufferPosition, newBufferPosition, textChanged}) ->
+    return if @settingTabStop or textChanged
     oldTabStops = @tabStopsForBufferPosition(oldBufferPosition)
     newTabStops = @tabStopsForBufferPosition(newBufferPosition)
     @destroy() unless _.intersection(oldTabStops, newTabStops).length


Preserve truthy semantics.

Preserve truthy semantics.

Also fix another renaming bug.

--- js2py.coffee
+++ js2py.coffee
@@ -22,8 +22,12 @@

 estraverse.Syntax.PySlice = 'PySlice'
 estraverse.Syntax.PyClass = 'PyClass'
-estraverse.VisitorKeys.PySlice = ['arguments']
-estraverse.VisitorKeys.PyClass = ['methods']
+estraverse.VisitorKeys.PySlice = ['callee', 'arguments']
+estraverse.VisitorKeys.PyClass = ['id', 'methods']
+
+
+tempNameCount = 0
+getTempName = -> "__temp__#{tempNameCount++}"

 extractSideEffects = (c) ->
   statements = []
@@ -33,6 +37,13 @@
         when 'AssignmentExpression'
           statements.push c
           return c.left
+        when 'CallExpression'
+          left = { type: 'Identifier', name: getTempName() }
+          statements.push {
+              type: 'AssignmentExpression',
+              operator: '=', left: left, right: c
+          }
+          return left
   return { expr, statements }

 transform = (c) ->
@@ -79,8 +90,6 @@

   currentFunction = null
   visibleFunctions = null
-  tempNameCount = 0
-  getTempName = -> "__temp__#{tempNameCount++}"
   tryGetType = (c) ->
     if not c then return true
     if c.type is 'Literal'
@@ -419,7 +428,8 @@
             }
         when 'ThrowStatement'
           if (lastCatch = catchClauses[catchClauses.length - 1]) and
-             JSON.stringify(lastCatch.param) == JSON.stringify(c.argument)
+             c.argument.type is 'Identifier' and
+             c.argument.name is lastCatch.param.name
             c.argument = null
           return
         when 'CatchClause'
@@ -439,7 +449,7 @@
   p = new LinePrinter
   isElse = false

-  SIDE_EFFECT_FREE = ['Literal', 'Identifier', 'AssignmentExpression']
+  SIDE_EFFECT_FREE = ['Literal', 'Identifier', 'AssignmentExpression', 'UpdateExpression']

   maybeParens = (expr) ->
     s = walk expr
@@ -447,6 +457,13 @@
       s
     else
       "(#{s})"
+
+  maybeTruthy = (expr) ->
+    s = walk expr
+    if expr.type in ['LogicalExpression', 'BinaryExpression', 'UnaryExpression', 'Literal']
+      s
+    else
+      "#{s} not in [None, False]"

   walk = (c) ->
     return if c is null
@@ -475,7 +492,7 @@
         walk c.body
         p.addLine ''
       when 'IfStatement'
-        p.addLine "#{if c.isElse then 'el' else ''}if #{walk c.test}:"
+        p.addLine "#{if c.isElse then 'el' else ''}if #{maybeTruthy c.test}:"
         walk ensure_block c.consequent
         if c.alternate
           if c.alternate.type is 'IfStatement'
@@ -488,7 +505,7 @@
       when 'WhileStatement'
         if c.prelude
           c.prelude.map(walk)
-        p.addLine "while #{walk c.test}:"
+        p.addLine "while #{maybeTruthy c.test}:"
         walk c.body
       when 'VariableDeclaration'
         for d in c.declarations
@@ -532,7 +549,7 @@
       when 'PySlice'
         "#{walk c.callee}[#{maybeParens c.arguments[0]}:#{maybeParens c.arguments[1]}]"
       when 'ConditionalExpression'
-        "(#{walk c.consequent} if #{walk c.test} else #{walk c.alternate})"
+        "(#{walk c.consequent} if #{maybeTruthy c.test} else #{walk c.alternate})"
       when 'MemberExpression'
         if c.computed
           "#{walk c.object}[#{walk c.property}]"


Disable voxel-debris by default

Disable voxel-debris by default

--- index.coffee
+++ index.coffee
@@ -173,17 +173,21 @@
   # block interaction: left/right-click to break/place blocks, uses raytracing
   game.currentMaterial = 1

-  debris = game.plugins.load 'debris', {power: 1.5}
+  debris = plugins.load 'debris', {power: 1.5}
+  plugins.disable 'debris' # lag :(
+
   debris.on 'collect', (item) ->
     console.log 'collect', item

   mine.on 'break', (pos) =>
-    debris(pos, 2) # TODO: pass material
-    #game.setBlock pos, 0
+    if plugins.isEnabled('debris') # TODO: refactor into module itself (event listener)
+      debris(pos, 2) # TODO: pass material
+    else
+      game.setBlock pos, 0

   gui = new datgui.GUI()
   console.log 'gui',gui
-  debug = game.plugins.load 'debug', {gui: gui}
+  debug = plugins.load 'debug', {gui: gui}
   debug.axis [0, 0, 0], 10

   pluginsUI = createPluginsUI game, {gui: gui}


Use statuses class for all status-related methods.

--- index.coffee
+++ index.coffee
@@ -58,9 +58,42 @@

   class Statuses
     constructor: (@statuses) ->
-      @modified = @_filterByState 'modified'
-      @added = @_filterByState 'added'
-      @deleted = @_filterByState 'deleted'
+      # Create status arrays.
+      @added = []
+      @modified = []
+      @deleted = []
+      @staged = []
+      @unstaged = []
+
+      # Bind array formatters.
+      for key, formatter of Statuses.formatters
+        @[key].toString = formatter if @[key]
+
+      # Populate status arrays.
+      for status in @statuses
+        # Each status has a code that maps to how the file has changed and
+        # whether they are staged.
+        #
+        # For example, when `code` is 1, the file is added and staged.
+        switch status.code
+          when 1
+            @added.push status
+            @staged.push status
+          when 2
+            @modified.push status
+            @staged.push status
+          when 4
+            @deleted.push status
+            @staged.push status
+          when 128
+            @added.push status
+            @unstaged.push status
+          when 256
+            @modified.push status
+            @unstaged.push status
+          when 512
+            @deleted.push status
+            @unstaged.push status

     toString: ->
       results = []
@@ -68,35 +101,6 @@
       results.push @added if @added.length
       results.push @deleted if @deleted.length
       results.join ' '
-
-    _filterByState: (@state) ->
-      filtered = _.filter @statuses, (status) ->
-        Statuses.CODE_MAP[status.code]?.state is state
-
-      if Statuses.formatters[state]
-        filtered.toString = Statuses.formatters[state]
-
-      filtered
-
-    @CODE_MAP:
-      1:
-        state: 'added'
-        staged: true
-      2:
-        state: 'modified'
-        staged: true
-      4:
-        state: 'deleted'
-        staged: true
-      128:
-        state: 'added'
-        staged: false
-      256:
-        state: 'modified'
-        staged: false
-      512:
-        state: 'deleted'
-        staged: false

     @formatters:
       added: ->
@@ -124,51 +128,49 @@

         path: path
         code: code
-        staged: Statuses.CODE_MAP[code]?.staged
-        state: Statuses.CODE_MAP[code]?.state

-      done null, statuses
+      done null, new Statuses statuses

+  # Get an object that represents the status of staged files.
+  register '_staged',
+    update: (done) ->
+      git._status (err, statuses) ->
+        done err, new Statuses statuses.staged
+
+  # Get a string that represents the status of staged files.
+  # Format: "∆2 +1 -3"
   register 'staged',
     update: (done) ->
+      git._staged (err, statuses) ->
+        done err, statuses.toString()
+
+  # Get an object that represents the status of unstaged files.
+  register '_unstaged',
+    update: (done) ->
       git._status (err, statuses) ->
-        done err, new Statuses _.where(statuses, {staged: true})
+        done err, new Statuses statuses.unstaged

+  # Get a string that represents the status of unstaged files.
+  # Format: "∆2 +1 -3"
   register 'unstaged',
     update: (done) ->
-      git._status (err, statuses) ->
-        done err, new Statuses _.where(statuses, {staged: false})
+      git._unstaged (err, statuses) ->
+        done err, statuses.toString()

-  # Get the number of "untracked" files
-  # Untracked is defined as new files that are not staged
-  register 'untracked',
+  # Get the number of added files.
+  register 'added',
     update: (done) ->
       git._status (err, statuses) ->
-        statuses = _filter_statuses_by_state(statuses, 'added')
-        count = _.where(statuses, {staged: false}).length
-        done err, count
+        done err, statuses.added.toString()

-  # Get the number of modified files
-  # Does not matter whether or not they are staged
+  # Get the number of modified files.
   register 'modified',
     update: (done) ->
       git._status (err, statuses) ->
-        count = _filter_statuses_by_state(statuses, 'modified').length
-        done err, count
+        done err, statuses.modified.toString()

-  # Get the number of deleted files
-  # Does not matter whether or not they are staged
+  # Get the number of deleted files.
   register 'deleted',
     update: (done) ->
       git._status (err, statuses) ->
-        count = _filter_statuses_by_state(statuses, 'deleted').length
-        done err, count
-
-  # Get the number of "added" files
-  # Added is defined as new files that are staged
-  register 'added',
-    update: (done) ->
-      git._status (err, statuses) ->
-        statuses = _filter_statuses_by_state statuses, 'added'
-        count = _.where(statuses, {staged: true}).length
-        done err, count
+        done err, statuses.deleted.toString()


Lower cache rates to once per minute.

Lower cache rates to once per minute.

--- index.coffee
+++ index.coffee
@@ -59,7 +59,7 @@

   github.register 'ci',
     cache: 'repository'
-    expire: 300
+    expire: 60
     update: (done) ->
       git.remoteBranch (err, branch) ->
         return done err, branch if err or not branch
@@ -75,7 +75,7 @@

   github.register 'pullRequest',
     cache: 'repository'
-    expire: 300
+    expire: 60
     update: (done) ->
       git.remoteBranch (err, branch) ->
         return done err, branch if err or not branch


Use sass instead of less

Use sass instead of less

--- src/octicons/sizes.coffee
+++ src/octicons/sizes.coffee
@@ -1,6 +1,6 @@
 fs    = require 'fs'
 css   = require 'css'
-less  = require 'less'
+sass  = require 'node-sass'
 jsdom = require 'jsdom'
 JSDOM = jsdom.JSDOM

@@ -27,16 +27,15 @@
           size = sizes[selector]
           break

-        "#{selectors.join ", "} { width: unit(( #{size.width} / #{size.height} ), em); }"
+        "#{selectors.join ", "} { width: ( #{size.width} / #{size.height} ) * 1em; }"
       .join "\n"

-    less.render stylesheet, (_, output) ->
-      ast = css.parse output.css
-      ast.stylesheet.rules = ast.stylesheet.rules.filter (rule, index, array) ->
-        _index = array.findIndex (_rule) -> _rule.declarations[0].value is rule.declarations[0].value
-        array[_index].selectors = array[_index].selectors.concat rule.selectors if index isnt _index
-        index is _index
-      callback css.stringify ast
+    ast = css.parse sass.renderSync(data: stylesheet).css.toString()
+    ast.stylesheet.rules = ast.stylesheet.rules.filter (rule, index, array) ->
+      _index = array.findIndex (_rule) -> _rule.declarations[0].value is rule.declarations[0].value
+      array[_index].selectors = array[_index].selectors.concat rule.selectors if index isnt _index
+      index is _index
+    callback css.stringify ast

   round = (length) ->
     if Math.abs(length - Math.round length) < 0.01


Convert JS ternary expression to coffescript style ternary.

(Whyyyyyyyyyyy)

--- Web/js/rohbot.coffee
+++ Web/js/rohbot.coffee
@@ -69,7 +69,7 @@
 			user.AvatarFolder = user.Avatar.substring(0, 2)
 		# Explicit falsy to false
 		user.Playing = false unless user.Playing
-		user.Color = user.Playing ? "ingame" : ( user.Web ? "web" : "" )
+		user.Color = if user.Playing then "ingame" else if user.Web then "web" else ""
 		return user

 	rohbot.on 'userlist', (users) ->


download by stream

download by stream

--- index.coffee
+++ index.coffee
@@ -6,7 +6,6 @@
 fs      = require 'fs'
 wfall   = require 'water-fall'
 mime    = require 'mime-types'
-{exec}  = require 'child_process'
 log4js  = require 'log4js'

 helper  = require './helper'
@@ -268,13 +267,18 @@
     request par, (err, resp) ->
       logger.error err if err
       logger.debug resp.statusCode, resp.headers if resp
-      logger.info hooks.cmd = "curl -o '#{hooks.info.name}' '#{resp.headers['location']}'"
-      callback()
-  wf.push (hooks, callback) ->
-    return callback() if not hooks.cmd
-    exec hooks.cmd, (err, out) ->
-      logger.debug out
-      callback()
+      logger.info hooks.location = resp.headers['location']
+      callback()
+  wf.push (hooks, callback) ->
+    return callback() if not hooks.location
+    name = if _.endsWith options.output, '/' then options.output + hooks.info.name else option.output
+    w = fs.createWriteStream name
+    w.on 'pipe', () ->
+      logger.info 'downloading...'
+    w.on 'finish', () ->
+      logger.info 'downloading...finish'
+      callback()
+    request(hooks.location).pipe(w)
   wf.exec (err) ->

@@ -283,7 +287,7 @@
   logger: 'INFO'
   user: '/v1.0/me'
   dirve: '/drive'
-  authinfo: '.odAuthInfo'
+  authinfo: process.env['HOME'] + '/.odAuthInfo'
   scope: 'offline_access files.readwrite.all'
   redirect_uri: 'http://localhost'

@@ -317,10 +321,11 @@

   program.command 'get <id>'
     .description 'get item content'
-    .option '-p --path [name]', 'item id/name'
+    .option '-o --output [localpath]', 'item save'
     .action (id, options) ->
-      par = _.extend {}, defaultOptions, pickParent(options), _.pick options, ['path'].concat _.keys defaultOptions
+      par = _.extend {}, defaultOptions, pickParent(options), _.pick options, ['output'].concat _.keys defaultOptions
       par.id = id
+      par.output = './' if not par.output
       logger.setLevel par.logger
       cmdGetContent par


Added arguments into destructure

Added arguments into destructure

--- index.coffee
+++ index.coffee
@@ -1,7 +1,8 @@
 #!/usr/bin/env coffee

 {UI, Client, Server, codes} = require './lib'
-{stdin, stdout} = process
+{stdin, stdout, argv} = process
+argv = argv.slice 2

 ui = new UI process
 stdin.resume();
@@ -26,7 +27,6 @@
       share.typed = alter.join('')
       share.right--

-
 ui.on 'resize', ->
   ui.prompt


Updated index.coffee (_generateWriteStream)

--- index.coffee
+++ index.coffee
@@ -28,11 +28,21 @@
       @writeStream.end()
       @emit 'end'

+  _generateWriteStream: ->
+    @writeStream = fslib.createWriteStream @outputFilePath
+
+    @writeStream.on 'error', (err)=>
+      @emit 'error', err
+
+    @writeStream.on 'end', =>
+      @emit 'write-end'
+
   copy: ->
     @_gatherInputFileStats =>
       @_calculateUpperLimits()

       @_generateReadStream()
+      @_generateWriteStream()

 @Slowcopy = Slowcopy


refactor errors

refactor errors

--- index.coffee
+++ index.coffee
@@ -59,6 +59,11 @@
     return localForage.getItem retro.md5
   catch error
     console.log error
+
+error = (e) ->
+  loading.classList.add 'hidden'
+  document.getElementById('error').classList.remove 'hidden'
+  console.error e

 play = (rom, extension) ->
   Promise.resolve()
@@ -106,11 +111,7 @@
   else if settings.extensions[extension]
     rom = buffer
   play rom, extension
-  .catch (e) ->
-    loading.classList.add 'hidden'
-    document.getElementById('error').classList.remove 'hidden'
-    localForage.setItem retro.md5, new Uint8Array() if retro.md5
-    console.error e
+  .catch error

 load = (file) ->
   return if not file instanceof Blob
@@ -215,3 +216,4 @@
 window.addEventListener 'touchstart', (e) ->
   e.preventDefault()

+window.addEventListener 'error', error


make sure devices isn't an empty object

make sure devices isn't an empty object

--- index.coffee
+++ index.coffee
@@ -71,7 +71,11 @@

       return @restartDevice(device, callback) if _.findWhere(gateblu.devices, uuid: device.uuid)

+      gateblu.devices = null if _.isEmpty(gateblu.devices)
+
       gateblu.devices ?= []
+
+      debug 'devices', gateblu.devices

       gateblu.devices.push {uuid: device.uuid, token: device.token, connector: 'flow-runner'}
       @saveGateblu gateblu, callback


refactored to class and corrected

refactored to class and corrected

--- index.coffee
+++ index.coffee
@@ -1,50 +1,41 @@
 extractValues = require 'extract-values'
-
-When  = require 'when'
-
-fs = require 'fs-extra'
-
 path = require 'path'
-
-Q = require 'q'
-
-request = require 'superagent'
+request = require 'request'

+class Say
+  constructor: (@phrase) ->
+    @link = ''
+    @formData =
+      MyLanguages: 'sonid9'
+      MySelectedVoice: 'Graham'
+      MyTextForTTS: "#{@phrase}"
+      SendToVaaS: ''

-makeLink = phrase (phraseLink) ->
-  if phrase is null then console.log 'we need phrase'
-  else
-  getPhraseLink phrase
+    @options =
+      headers:
+        ' content-type': 'Content-type: application/x-www-form-urlencoded\r\n'
+
+
+
+    do getLink = (@options, @formData, @bodyLink) ->
+      request.post
+        url: 'http://www.acapela-group.com/demo-tts/DemoHTML5Form_V2.php'
+        headers: @options.headers
+        form: @formData
+      , (error, response, body) ->
+        body = extractValues JSON.stringify(response), "var myPhpVar = '{link}\';"
+        @bodyLink = body.link
+
+
+        @link = @bodyLink
+        console.log link:@link

-
-getPhraseLink = (err, phrase, phraseLink) ->
-  request
-  .post 'http://www.acapela-group.com/demo-tts/DemoHTML5Form_V2.php'
-  .set 'Content-Type', 'application/x-www-form-urlencoded'
-  .send 'MyLanguages=sonid8'
-  .send 'MySelectedVoice=Graham'
-  .send 'MyTextForTTS=' + "#{phrase}".split(" ").join("+")
-  .send 'SendToVaaS='
-  .end (res) ->
-    if err then console.error err
-    else
-    resText = res.text
-    foobar = extractValues resText, "myPhpVar = '{fileSource}';"
-
-    resLink = foobar
-
-    phraseLink = resLink
-    console.log phraseLink
-  if err then throw console.error err
-  else
-  phraseLink
+module.exports = Say

-exports.makeLink = makeLink
-


More efficient of getting changed lines

More efficient of getting changed lines

--- index.coffee
+++ index.coffee
@@ -101,27 +101,18 @@
         original = editor.getText();

         try
+          console.log("here1");
           result = jsxformat.format(original)
-          selection.clear();
+          selection.clear()

-          lastChangedLine = 0
+          console.log("here2");
+
+          originalLineCount = editor.getLineCount()
+          editor.setText(result)
+          newLineCount = editor.getLineCount()
+
           firstChangedLine = range[0][0] - 1
-
-          editor.setText(result);
-
-          originalLines = original.split('\n')
-          resultLines = result.split('\n')
-
-          i = 0
-
-          # TODO: use sourcemaps for this
-          _.findLast resultLines, (newline, index) ->
-            orgline = originalLines[originalLines.length - 1 - i]
-            i++
-
-            if newline != orgline
-              lastChangedLine = index
-              return true
+          lastChangedLine = range[1][0] - 1 + (newLineCount - originalLineCount)

           if lastChangedLine > firstChangedLine
             for row in [firstChangedLine...(lastChangedLine + 1)]
@@ -129,6 +120,8 @@

           # return back
           editor.setCursorBufferPosition([firstChangedLine, range[0][1]])
+        catch err
+          console.log(err)

   processEditor: (editor) ->


set current match of world

set current match of world

--- index.coffee
+++ index.coffee
@@ -23,10 +23,13 @@

 newMatch = ( match ) ->
     console.log 'new match ' + match.id
-    db.query 'INSERT INTO matches (match_id, red_world_id, blue_world_id, green_world_id, start_time, end_time) VALUES (?,?,?,?,?,?)',
-        [ match.id, match.worlds.red, match.worlds.blue, match.worlds.green, match.start_time, match.end_time ], (err, result) ->
+    db.query 'INSERT INTO matches (match_id, red_world_id, blue_world_id, green_world_id, start_time, end_time, data) VALUES (?,?,?,?,?,?,?)',
+        [ match.id, match.worlds.red, match.worlds.blue, match.worlds.green, match.start_time, match.end_time, JSON.stringify(match) ], (err, result) ->
             throw err if err
             ids[ "#{match.id}_#{match.start_time}" ] = result.insertId
+            worlds = match.all_worlds.red.concat(match.all_worlds.blue, match.all_worlds.green)
+            db.query 'UPDATE worlds SET match_id = ? WHERE id IN (?)', [ result.insertId, worlds ], (err, result) ->
+                throw err if err

 crawler = new Crawler config.matchUrl, config.detailUrl


Move logo toggling to voxel-oculus; remove rift hash

Requires deathcap/voxel-oculus@ac4782759739f348cc553d959cf92c9bfdef0230

--- index.coffee
+++ index.coffee
@@ -127,11 +127,6 @@

   plugins.loadAll()

-  if window.location.href.indexOf('rift') != -1 ||  window.location.hash.indexOf('rift') != -1
-    # Oculus Rift support
-    plugins.enable 'oculus'
-    document.getElementById('logo').style.visibility = 'hidden'
-
   # the game view element itself
   game = plugins.get('voxel-engine')
   container = document.body


Properly wire up option aliases

Properly wire up option aliases

--- lib/utils/cli_helpers.coffee
+++ lib/utils/cli_helpers.coffee
@@ -10,7 +10,7 @@
     for optName, optConfig of config
       opts.options optName,
         describe: optConfig.description
-        alias:    optConfig.alias
+        alias:    optConfig.aliases

     # * Optimist's `options()` method doesn't honor aliased values when specifying boolean values,
     #   so we are forced to set those up separately.


Fix password regex

Fix password regex


Make vigil API a bit more friendly.

Make vigil API a bit more friendly.

--- src/utils.coffee
+++ src/utils.coffee
@@ -66,8 +66,10 @@
     excluded = (filename) ->
       relname = relative filename

-      return true unless includeRe.test relname
-      return true if     excludeRe.test relname
+      if includeRe?
+        return true unless includeRe.test relname
+      if excludeRe?
+        return true if excludeRe.test relname

     opts.relative = relative
     opts.excluded = excluded


Remove unused require

Remove unused require

--- lib/window-panel-view.coffee
+++ lib/window-panel-view.coffee
@@ -1,4 +1,4 @@
-{$$, View} = require 'atom'
+{View} = require 'atom'

 module.exports =
 class PackagePanelView extends View


Fix #1316: Interpolate interpolations safely

Instead of compiling to `"" + + (+"-");`, `"#{+}-"'` now gives an appropriate
error message:

    [stdin]:1:5: error: unexpected end of interpolation
    "#{+}-"
        ^

This is done by _always_ (instead of just sometimes) wrapping the interpolations
in parentheses in the lexer. Unnecessary parentheses won't be output anyway.

I got tired of updating the tests in test/location.coffee (which I had enough of
in #3770), which relies on implementation details (the exact amount of tokens
generated for a given string of code) to do their testing, so I refactored them
to be less fragile.

--- lexer.coffee
+++ lexer.coffee
@@ -499,16 +499,16 @@
       # Skip the trailing `}`.
       index += 1

-      # Remove leading and trailing `{` and `}`.
-      nested.shift()
-      nested.pop()
+      # Turn the leading and trailing `{` and `}` into parentheses. Unnecessary
+      # parentheses will be removed later.
+      [open, ..., close] = nested
+      open[0]  = open[1]  = '('
+      close[0] = close[1] = ')'
+      close.origin = ['', 'end of interpolation', close[2]]

       # Remove leading 'TERMINATOR' (if any).
-      nested.shift() if nested[0]?[0] is 'TERMINATOR'
-
-      if nested.length > 1
-        nested.unshift @makeToken '(', '(', offsetInChunk + 1, 0
-        nested.push    @makeToken ')', ')', offsetInChunk + 1 + index, 0
+      nested.splice 1, 1 if nested[1]?[0] is 'TERMINATOR'
+
       # Push a fake 'TOKENS' token, which will get turned into real tokens later.
       tokens.push ['TOKENS', nested]

@@ -535,8 +535,8 @@
       [tag, value] = token
       switch tag
         when 'TOKENS'
-          # Optimize out empty interpolations.
-          continue if value.length is 0
+          # Optimize out empty interpolations (an empty pair of parentheses).
+          continue if value.length is 2
           # Push all the tokens in the fake 'TOKENS' token. These already have
           # sane location data.
           locationToken = value[0]


switching interpolateString's method signature to take on options hash.

--- lexer.coffee
+++ lexer.coffee
@@ -132,7 +132,7 @@
     return false unless match: @chunk.match(HEREDOC)
     quote: match[1].substr 0, 1
     doc: @sanitizeHeredoc match[2] or match[4], {quote}
-    @interpolateString "$quote$doc$quote", no, yes
+    @interpolateString "$quote$doc$quote", {heredoc: yes}
     @line: + count match[1], "\n"
     @i: + match[1].length
     true
@@ -170,7 +170,7 @@
       str: regex.substring(1).split('/')[0]
       str: str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
       @tokens: @tokens.concat [['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]
-      @interpolateString "\"$str\"", yes
+      @interpolateString "\"$str\"", {escapeQuotes: yes}
       @tokens.splice @tokens.length, 0, [',', ','], ['STRING', "\"$flags\""] if flags
       @tokens.splice @tokens.length, 0, [')', ')'], [')', ')']
     else
@@ -389,7 +389,8 @@
   # If it encounters an interpolation, this method will recursively create a
   # new Lexer, tokenize the interpolated contents, and merge them into the
   # token stream.
-  interpolateString: (str, escapeQuotes, heredoc) ->
+  interpolateString: (str, options) ->
+    options: or {}
     if str.length < 3 or not starts str, '"'
       @token 'STRING', str
     else
@@ -411,7 +412,7 @@
           tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
           inner: expr.substring(2, expr.length - 1)
           if inner.length
-            inner: inner.replace new RegExp('\\\\' + quote, 'g'), quote if heredoc
+            inner: inner.replace new RegExp('\\\\' + quote, 'g'), quote if options.heredoc
             nested: lexer.tokenize "($inner)", {line: @line}
             (tok[0]: ')') for tok, idx in nested when tok[0] is 'CALL_END'
             nested.pop()
@@ -429,7 +430,7 @@
         [tag, value]: token
         if tag is 'TOKENS'
           @tokens: @tokens.concat value
-        else if tag is 'STRING' and escapeQuotes
+        else if tag is 'STRING' and options.escapeQuotes
           escaped: value.substring(1, value.length - 1).replace(/"/g, '\\"')
           @token tag, "\"$escaped\""
         else


activity: remove obselete componentDidMount

After merge sideeffects

--- client/activity/lib/components/publicchatpane/index.coffee
+++ client/activity/lib/components/publicchatpane/index.coffee
@@ -24,19 +24,6 @@
     document.body.appendChild ModalContainer

-  componentDidUpdate: ->
-
-    PublicChatList           = React.findDOMNode(@refs.PublicChatList)
-    PublicChatPaneBody       = React.findDOMNode(@refs.PublicChatPaneBody)
-    chatList                 = PublicChatList.firstElementChild
-    chatListHeight           = chatList.offsetHeight
-    publicChatPaneBodyHeight = PublicChatPaneBody.offsetHeight
-
-    if (chatListHeight > publicChatPaneBodyHeight)
-    then chatList.classList.remove 'padded'
-    else chatList.classList.add 'padded'
-
-
   channel: (key) -> @props.thread?.getIn ['channel', key]


SidebarStackSection: Don't set any links to plus icon and title for team

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -84,8 +84,8 @@
       ref='sidebarSection'
       className={kd.utils.curry 'SidebarStackSection', @props.className}
       title={@props.stack.get 'title'}
-      titleLink='/Stacks'
-      secondaryLink='/Stacks'
+      titleLink='' # Set empty string
+      secondaryLink='' # Set empty string
       unreadCount={@getStackUnreadCount()}
       unreadCountClickHandler={@unreadCountClickHandler}
       >


Clean debit method, wip.

Clean debit method, wip.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -69,8 +69,13 @@
     if not currentGroup or currentGroup is 'koding' then "koding~#{KD.nick()}"
     else currentGroup

-  createGroupVM:(type='personal', callback)->
-    defaultVMOptions = {cpu : 1, disk : 1, ram : 1}
+  createGroupVM:(type='personal', hostname, cpuSize, callback)->
+    defaultVMOptions =
+      usage          :
+        cpu          : cpuSize
+        disk         : 1
+        ram          : 1
+      hostname       : hostname
     group = KD.singletons.groupsController.getCurrentGroup()

     group.fetchBundle (err, bundle)->
@@ -115,7 +120,9 @@

     # Take this to a better place, possibly to payment controller.
-    makePayment = (type, planCode, callback)->
+    makePayment = (type, hostname, plan, callback)->
+      planCode = plan.code
+      cpuSize  = plan.item.meta.cpu
       if type is 'shared'
         group = KD.singletons.groupsController.getCurrentGroup()
         group.checkPayment (err, payments)->
@@ -139,20 +146,20 @@
                   if err
                     onError err
                   else
-                    vmController.createGroupVM type, vmCreateCallback
+                    vmController.createGroupVM type, hostname, cpuSize, vmCreateCallback
                     onSuccess()
                     callback()
               paymentModal.on "KDModalViewDestroyed", -> vmController.emit "PaymentModalDestroyed"
           else
             group.updatePayment {plan: planCode}, (err, subscription)->
-              vmController.createGroupVM type, vmCreateCallback
+              vmController.createGroupVM type, hostname, cpuSize, vmCreateCallback
               callback()
       else
         _createUserVM = (cb)->
           KD.remote.api.JRecurlyPlan.getPlanWithCode planCode, (err, plan)->
             plan.subscribe {}, (err, subscription)->
               return cb err  if cb and err
-              vmController.createGroupVM type, vmCreateCallback
+              vmController.createGroupVM type, hostname, cpuSize, vmCreateCallback
               cb?()
         KD.remote.api.JRecurlyPlan.getUserAccount (err, account)->
           if err
@@ -176,7 +183,7 @@
       else
         KD.singletons.finderController.mountVm vm.name
         vmController.emit 'VMListChanged'
-      paymentModal.destroy()
+      paymentModal?.destroy()

     group = KD.singletons.groupsController.getGroupSlug()

@@ -221,7 +228,8 @@
                 form                = modal.modalTabs.forms["Create VM"]
                 {personal, shared}  = form.buttons

-                makePayment formData.type, @paymentPlans[formData.host].code, ->
+                # Gökmen buraya!
+                makePayment formData.type, "HOSTNAME", @paymentPlans[formData.host], ->
                   modal.destroy()
               buttons               :
                 personal            :


suspend and resume lifecycle added

suspend and resume lifecycle added

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -1,7 +1,7 @@

 events = require 'events'

-ACTIONS = ['configured', 'booted', 'started', 'running', 'shutdown', 'stopped']
+ACTIONS = ['configured', 'booted', 'started', 'running', 'suspend',  'resume', 'shutdown', 'stopped']

 module.exports =

@@ -115,8 +115,8 @@
                             else
                                 done null, ctx

-                    else
-                        done null, ctx
+                        else
+                            done null, ctx

             else
                 done new Error 'Unknown Context-Type: '+config.type


Refactor: 不要なロギング除去

Refactor: 不要なロギング除去

--- src/progress/index.coffee
+++ src/progress/index.coffee
@@ -49,7 +49,6 @@
         if player.id == playerId
           player.isAnswer = true
         return player
-      console.log players_
       @update (state) =>
         state.players= players_
         return state


Fix to configurations

Fix to configurations

--- src/vtree.coffee
+++ src/vtree.coffee
@@ -22,7 +22,7 @@
     _.extend(@config(), options)

   @config: ->
-    @_config = new Configuration
+    @_config ?= new Configuration

   @hooks: ->
     return @_hooks if @_hooks?


0.0.3

--- fake.service.coffee
+++ fake.service.coffee
@@ -29,6 +29,15 @@
 #  console.log('urlll',params);
   delayTime=1000
   res.setHeader('Access-Control-Allow-Origin', '*');
+  res.setHeader('Access-Control-Request-Method', '*');
+  res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST');
+  res.setHeader('Access-Control-Allow-Headers', '*');
+  if  req.method == 'OPTIONS'
+    res.writeHead(200);
+    res.end();
+
+    return
+
   res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
 #  res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -186,10 +186,8 @@

 @name each
 @methodOf Array#
-@param {Function} iterator Function to be called once for
-each element in the array.
-@param {Object} [context] Optional context parameter to be
-used as `this` when calling the iterator function.
+@param {Function} iterator Function to be called once for each element in the array.
+@param {Object} [context] Optional context parameter to be used as `this` when calling the iterator function.
 @returns {Array} this to enable method chaining.
 ###
 Array::each = (iterator, context) ->


Cosmetic

Cosmetic

--- src/webjs.coffee
+++ src/webjs.coffee
@@ -179,7 +179,6 @@
             switch (req.headers['content-type'] or '').split(';')[0]
                 when 'application/x-www-form-urlencoded'
                     q = querystring.parse(data)
-                    break
                 when 'text/plain', ''
                     q = data
                 else


Zoom to location with indication arrow

Zoom to location with indication arrow

--- client.coffee
+++ client.coffee
@@ -1402,7 +1402,17 @@
 												backgroundColor: Db.shared.peek('colors', getTeamOfUser(Plugin.userId()), 'hex')
 										Dom.div !->
 											Dom.cls 'arrowDivText'
-											Dom.text "You're " + distanceToPlayfield + " away"
+											Dom.text "You're " + distanceToPlayfield + " away"
+										Dom.div !->
+											Dom.onTap !->
+												map.setView(latLngObj, 16)
+											Dom.style
+												position: 'absolute'
+												bottom: '0px'
+												left: '0px'
+												width: '160px'
+												height: '45px'
+												zIndex: '11'
 							Obs.onClean ->
 								# Deregister move/zoom listeners to update indication arrow
 								if mapReady()


Don't rebind feeds on Aggregator#combine

--- feeds/models.coffee
+++ feeds/models.coffee
@@ -53,7 +53,7 @@
   parse: (blob) -> blob

   indexKey: =>
-    "#{@prefix}/#{@name}"
+    "#{@prefix ? ''}/#{@name}"

   dataKey: (id) =>
     "#{@key}/#{id}"
@@ -207,6 +207,10 @@

 # Combine multiple feeds in to a single one
 class Aggregator extends Feed
+  constructor: ->
+    super
+    @others ?= {}
+
   # No need for entry key as it is already stored by another feed
   dataKey: identity

@@ -220,6 +224,10 @@
   # Whenever an entry is added to the other feed, create a reference to it
   # with the data key and timestamp. Then reemit the data.
   combine: (other) =>
+    return if @others[other.key]
+
+    @others[other.key] = other
+
     other.on 'data', ({id, data, timestamp}) =>
       id = other.dataKey(id)
       @save {id, timestamp}


Signal an error on unknown middleware (fixes #82)

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -339,6 +339,8 @@
         app.use zappa_middleware[name](arg)
       else if typeof express[name] is 'function'
         app.use express[name](arg)
+      else
+        throw "Unknown middleware #{name}"

     for a in arguments
       switch typeof a


fix scope

fix scope

--- lib/atomizr.coffee
+++ lib/atomizr.coffee
@@ -168,7 +168,7 @@

       # Write back to editor and change scope
       editor.setText(output)
-      editor.setGrammar(atom.grammars.grammarForScopeName('source.json.'))
+      editor.setGrammar(atom.grammars.grammarForScopeName('source.json'))
       @renameFile(editor, "json")
     else
       atom.notifications.addWarning("Atomizr", detail: "This doesn't seem to be a valid Atom file. Aborting.", dismissable: false)


add support for loading the presenter's current slide upon initial load

[[id:e98028ee-32ce-4741-9563-c21b6ced5ac2][endpoint to track current presentor state]]

--- lib/app.coffee
+++ lib/app.coffee
@@ -69,17 +69,18 @@
     console?.error?(err)
     prevState                   # leave it at the old state after erro

-loadPresentationState = () ->
+loadPresentationState = (initSlide) ->
   window.mainSlideDeck = deck = revealjsDomToSlideDeck()
-  initSlide = deck.get(Reveal.getIndices())
+  if not initSlide
+    initSlide = deck.get(Reveal.getIndices())
   initState = new core.PresentationState(deck, initSlide)
   setSlideState(initState)
   initState

-initEventstreamSubscriptions = ->
+initEventstreamSubscriptions = (initSlide) ->
   ui.uiSlideEventstream().subscribe(streams.localSlideEventstream)
   streams.localSlideEventstream.aggregate(
-    loadPresentationState(), aggregateStateOnSlideEvent).
+    loadPresentationState(initSlide), aggregateStateOnSlideEvent).
     subscribe((finalState)->)
     # NOTE: live stream aggregation so final callback never called, but required

@@ -99,7 +100,9 @@
 main = ->
   $ ->
     ui.init()
-    setTimeout(initEventstreamSubscriptions, 300) # delay to ensure reveal dom elems are live
+    $.ajax('/current_slide/', {timeout: 700}).
+      done(initEventstreamSubscriptions).
+      fail((req, err) -> console?.log?(err); initEventstreamSubscriptions())

 ################################################################################
 exports.main = main


window.navigator.javaEnabled = false

window.navigator.javaEnabled = false

--- src/server/browser.coffee
+++ src/server/browser.coffee
@@ -134,6 +134,7 @@
             img.height = height
             img

+        window.navigator.javaEnabled = false
         window.navigator.language = 'en-US'
         # Taken from Chrome 16 request headers
         window.navigator.userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7'


do not overwrite service name label

do not overwrite service name label

--- lib/compose.coffee
+++ lib/compose.coffee
@@ -15,7 +15,6 @@
   augmentCompose: (instance, options, doc) ->
     addNetworkContainer = (serviceName, service) ->
       labels = _.extend {}, service.labels
-      labels['bigboat/service/name'] = "net-#{serviceName}"
       labels['bigboat/container/type'] = 'net'
       subDomain = "#{instance}.#{config.domain}.#{config.tld}"
       netcontainer =


Unique_ID

--- lib/esl.coffee
+++ lib/esl.coffee
@@ -406,8 +406,9 @@
         when 'disconnect'  then @end()

   start_call_server: ->
+    Unique_ID = 'Unique-ID'
     @connect (req,res) =>
-      unique_id = req.body['Unique-ID']
+      unique_id = req.body[Unique_ID]
       @auto_cleanup
       # Handle the incoming connection
       @linger (req,res) =>


Add console.logs

Add console.logs

--- lib/editors.coffee
+++ lib/editors.coffee
@@ -54,11 +54,18 @@
     @editor.on "text-change", (deltas, source) ->
       # call the backend with deltas
       backend deltas
+      console.log deltas

   observeLocalCursor: (backend) ->
-    @editor.on "selection-change", (range) ->
-      # only when there's a cursor (range start === range end)
-      if range and range.start == range.end then backend range.start
+    @editor.on "selection-change", (range, source) ->
+      if source == 'api'
+        # only when there's a cursor (range start === range end)
+        console.log "changed by backend"
+      else
+        console.log "changed by user"
+      if range and range.start == range.end
+        backend range.start
+        console.log range.start

   updateContents: (delta) ->
     @editor.updateContents delta


Use isFileSync instead of existsSync for checking file existence

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -8,7 +8,7 @@

   userKeymapPath = atom.keymaps.getUserKeymapPath()

-  if fs.existsSync(userKeymapPath)
+  if fs.isFileSync(userKeymapPath)
     linter.checkKeymap(CSON.readFileSync(atom.keymaps.getUserKeymapPath()), {
       packageName: "your local #{path.basename(userKeymapPath)} file",
       packagePath: "",
@@ -17,7 +17,7 @@

   userStyleSheetPath = atom.styles.getUserStyleSheetPath()

-  if fs.existsSync(userStyleSheetPath)
+  if fs.isFileSync(userStyleSheetPath)
     userStyleSheet = fs.readFileSync(atom.styles.getUserStyleSheetPath(), 'utf8')
     linter.checkSyntaxStylesheet(userStyleSheet, {
       packageName: "your local #{path.basename(userStyleSheetPath)} file",


:memo: Clarify a comment

:memo: Clarify a comment

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -6,6 +6,8 @@
 Helpers = module.exports =
   shouldTriggerLinter: (linter, wasTriggeredOnChange, scopes)->
     # Trigger fly linters on save, but not save linters on fly
+    # Because we want to trigger onFly linters on save when the
+    # user has disabled lintOnFly from config
     return false if wasTriggeredOnChange and not linter.lintOnFly
     return false unless scopes.some (entry) -> entry in linter.grammarScopes
     return true


fix(options.coffee): Make "--reporter" alias match that for Mocha

The application's "--reporter" alias has been '-r'. It has been changed to match Mocha's alias '-R'
for the same option (passthrough).

BREAKING CHANGE: Alias "-r" will no longer be used for "--reporter"; use '-R' instead.

--- lib/options.coffee
+++ lib/options.coffee
@@ -39,7 +39,7 @@
     type: 'boolean'

   reporter:
-    alias: 'r'
+    alias: 'R'
     description: 'Specify reporter to use'
     type: 'string'
     default: 'spec'


full url support using backbone's Router


Fixes console.og typo

Fixes console.og typo

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -425,7 +425,7 @@
   index = commandstream.toCommandStreamBuffer message, runtime.library, g.nodeMap, g.componentMap, temp, 0
   data = temp.slice(0, index)
   runtime.device.sendCommands data, (err) ->
-    console.og 'sendMessage error', err if err
+    console.log 'sendMessage error', err if err

 handleNetworkCommand = (command, payload, connection, runtime, transport, debugLevel) ->
     if command is "start"


streamDestroyed handler had an undefined event.

--- scripts/OpenTok.coffee
+++ scripts/OpenTok.coffee
@@ -327,6 +327,11 @@
   streamDestroyedHandler: (streamId) =>
     pdebug "streamDestroyedHandler", streamId
     element = streamElements[ streamId ]
+    stream = {streamId: streamId}
+    event = {
+      stream: stream,
+      streams: [stream]
+    }
     if(element)
       element.parentNode.removeChild(element)
       delete( streamElements[ streamId ] )


Clojure overeager bugfix

Clojure overeager bugfix

--- scripts/clojure.coffee
+++ scripts/clojure.coffee
@@ -16,7 +16,7 @@
 ringSessionID = ''

 module.exports = (robot) ->
-  robot.hear /(\(.*\))/i, (msg)->
+  robot.hear /^(\(.*\))/i, (msg)->
     script = msg.match[1]

     msg.http("http://www.tryclj.com/eval.json")


naming yo

naming yo

--- js/src/crypto/RsaKeyGenerator.coffee
+++ js/src/crypto/RsaKeyGenerator.coffee
@@ -68,14 +68,14 @@

     numToUnsignedUint8Array: (num) ->
       hexString = num.toString(2)
-      array = new Uint8Array(hexString.length)
+      binary    = new Uint8Array(hexString.length)
       for i in [0..hexString.length]
-        array[i] = hexString[i]
-      arr2size = Math.ceil(array.length/8)
-      array2 = new Uint8Array(arr2size)
-      for i in [0..arr2size]
-        uint8 = array.slice(i*8, i*8 + 7)
-        array2[i] = Number.parseInt(uint8, 2)
-      return array2
+        binary[i] = hexString[i]
+      uLength     = Math.ceil(binary.length/8)
+      unsignedInt = new Uint8Array(uLength)
+      for i in [0..uLength]
+        uint8          = binary.slice(i*8, i*8 + 7)
+        unsignedInt[i] = Number.parseInt(uint8, 2)
+      return unsignedInt

   return RsaKeyGenerator


use options to call their corresponding functions

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -163,11 +163,23 @@
       # get options for Browserify
       browserifyOptions = @getBrowserifyOptions file, option

+      # extract special options
+      specialOptions = {}
+      for which in ['ignore', 'external', 'exclude', 'plugin']
+        specialOptions[which] = browserifyOptions[which]
+        delete browserifyOptions[which]
+
       # create a browserify instance passing our readable stream as input,
       # and options object for debug and the basedir
       browserify = Browserify [@getReadable(file)], browserifyOptions

+      # convert object "options" into their corresponding specific function calls
+      @applySpecialOptions browserify, browserifyOptions, specialOptions
+
       # apply browserify tranforms specified in options file, and envify
+      # the transforms can be in the browserify options object without problems
+      # because we named it 'transforms' instead of 'transform'. Had we done that,
+      # I'd have to extract it along with the other "special" options...
       @applyTransforms browserify, browserifyOptions

       # process bundle with exorcist to get source map
@@ -181,9 +193,33 @@

     catch e
       file.error message:e.message + '\n' +
-        @buildErrorMessage e, option, browserifyOptions
+        @buildErrorMessage e, option, browserifyOptions, specialOptions

     return
+
+
+  applySpecialOptions: (browserify, browserifyOptions, options) ->
+    # check for these types of options groups
+    for which in [ 'ignore', 'external', 'exclude', 'plugin' ]
+
+      # if it exists in options then we'll process it
+      if options[which]?
+
+        # they're in an array to allow order control
+        for it in options[which]
+
+          # when it's a string just pass that to the function
+          if typeof(it) is 'string'
+            browserify[which] it, basedir:browserifyOptions.basedir
+
+          # to allow them to have options, look for an object element
+          else if typeof(it) is 'object'
+
+            # iterate thru its keys which are strings to pass to the function
+            # and its value is the options, which may be empty..
+            for name,whichOptions of it
+              whichOptions.basedir ?= browserifyOptions.basedir
+              browserify[which] name, whichOptions

   applyTransforms: (browserify, browserifyOptions) ->
@@ -201,7 +237,7 @@

     return

-  buildErrorMessage: (error, option, browserifyOptions) ->
+  buildErrorMessage: (error, option, browserifyOptions, specialOptions) ->
     # let's look for the cannot find module error's parts
     regex = /Cannot find module '(.*)' from '(.*)'/
     match = regex.exec error.toString()
@@ -244,6 +280,9 @@
     #{moduleCheckMessage}
     Browserify options:
     >  #{stringify browserifyOptions, null, '>  '}
+
+    Special options:
+    >  #{stringify specialOptions, null, '>  '}
     """


move browserify options to the creating function call

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -29,18 +29,12 @@
   # add the buffer into the stream and end the stream with one call to end()
   readable.end step.read()

-  # must tell Browserify where to find npm modules.
-  # CompileStep has the absolute path to the file in `fullInputPath`
-  # CompileStep has the name of the file in `inputPath`
-  # basedir is fullInputPath with inputPath replaced with '.npm/package'
-  basedir = getBasedir(step)
-
-  # debug is true unless we are doing a bundle or build, excpet with --debug set
-  debug = getDebug(step)
-
   # create a browserify instance passing our readable stream as input,
   # and options with debug set to true for a dev build, and the basedir
-  browserify = Browserify [readable], debug:debug, basedir:basedir
+  browserify = Browserify [readable],
+    # browserify options
+    basedir = getBasedir(step) # Browserify looks here for npm modules
+    debug = getDebug(step)     # Browserify creates internal source map

   # have browserify process the file and include all required modules.
   # we receive a readable stream as the result
@@ -81,7 +75,7 @@

 getDebug = (step) ->
   debug = true
-
+
   # check args used
   for key in process.argv
     # if 'meteor bundle file' or 'meteor build file'


so, tabs work

so, tabs work

--- plugins/tabs/tabs.coffee
+++ plugins/tabs/tabs.coffee
@@ -9,20 +9,37 @@
   position: 'top'
   html: require 'tabs/tabs.html'

+  # The Editor pane we're managing.
+  editor: null
+
   keymap:
     'Command-Ctrl-T': 'toggle'

   initialize: ->
+    @editor = activeWindow.document
+
+    @editor.ace.on 'open', ({filename}) =>
+      # Only care about files, not directories
+      return if not /\.\w+$/.test filename
+      @addTab filename
+
     tab = this
     # click tab
     $(document).delegate '#tabs ul a', 'click', ->
       tab.switchToTab this
       false

-  addTab: ->
-    $('#tabs ul .add').before '
untitled
'
+  addTab: (path) ->
+    existing = $("#tabs [data-path='#{path}']")
+    if existing.length
+      return @switchToTab existing
+
+    name = _.last path.split '/'
     $('#tabs ul .active').removeClass()
-    $('#tabs ul .add').prev().addClass 'active'
+    $('#tabs ul li:last').after """
+
#{name}

+    """
+    $('#tabs ul li:last').addClass 'active'

   hideTabs: ->
     $('#tabs').parents('.pane').remove()
@@ -37,6 +54,7 @@
   switchToTab: (tab) ->
     $('#tabs ul .active').removeClass()
     $(tab).parents('li').addClass 'active'
+    @editor.switchToSession $(tab).data 'path'

   toggle: ->
     if $('#tabs').length


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,4 +1,5 @@
 ClampBounds = (I={}, self) ->
+  Obje

   self.bind "update", ->
     I.x = I.x.clamp()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -7,4 +7,4 @@

   self.bind "update", ->
     I.x = I.x.clamp(I.width / 2, App.width - I.width / 2)
-    I.y = I.y.clamp(I.height / 2, App.h)
+    I.y = I.y.clamp(I.height / 2, App.height - I.h)


Actuallu compare transformed values

Actuallu compare transformed values

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -32,13 +32,11 @@

 			# console.log "Layer.#{name}.set #{value} current:#{@[name]}"

+			if transformer and (value or value is null)
+				value = transformer(value)
+
 			# Return unless we get a new value
 			return if value is @_properties[name]
-
-			if value and transformer
-				value = transformer(value)
-			else if value == null and transformer
-				value = transformer(value)

 			if value and validator and not validator(value)
 				layerValueTypeError(name, value)


improve converting point from context function

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -779,7 +779,6 @@
 Utils.convertPointFromContext = (point = {}, layer, rootContext=false, includeLayer = true) ->
 	point = _.defaults(point, {x:0, y:0, z:0})
 	ancestors = layer.ancestors(rootContext)
-	point = ancestors.pop().matrix3d.inverse().point(point) if ancestors.length
 	ancestors.reverse()
 	ancestors.push(layer) if includeLayer
 	for ancestor in ancestors


Still allow touch events if touch is not supported

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -433,6 +433,7 @@

 Utils.domValidEvent = (element, eventName) ->
 	return if not eventName
+	return true if eventName in ["touchstart", "touchmove", "touchend"]
 	return typeof(element["on#{eventName.toLowerCase()}"]) isnt "undefined"

 Utils.domLoadScript = (url, callback) ->


Hide categories with no installed games

Hide categories with no installed games

Fixes #127

--- src/client/games.coffee
+++ src/client/games.coffee
@@ -145,6 +145,9 @@
 updateCategorySelect = ->
   $('.category select').html('')
   categories = _.keys(Categories[$('.user select').val()]).sort()
+  categories = categories.filter (cat) ->
+    Categories[$('.user select').val()][cat].some (appID) ->
+      _.some global.Games, appID: appID
   # Move favorites to front if they're at the back
   if categories.indexOf('favorite') > 0
     categories = ['favorite'].concat _.without categories, 'favorite'


Don't deactivate a comment if dialog is open.

--- client/publication.coffee
+++ client/publication.coffee
@@ -1259,7 +1259,7 @@
   $wrapper = $(@findAll '.comment-editor')
   $editor = $(@findAll '.comment-content-editor')

-  if $editor.text().trim() or $editor.is ':focus'
+  if $editor.text().trim() or $editor.is(':focus') or @_destroyDialog
     $wrapper.addClass 'active'
   else
     $wrapper.removeClass 'active'
@@ -1280,7 +1280,7 @@
   'blur .comment-content-editor': (e, template) ->
     $editor = $(e.currentTarget)
     $wrapper = $(template.findAll '.comment-editor')
-    $wrapper.removeClass 'active' unless $editor.text().trim()
+    $wrapper.removeClass 'active' unless $editor.text().trim() or template._destroyDialog

     return # Make sure CoffeeScript does not return anything


モバイル検出用のメソッドを追加

モバイル検出用のメソッドを追加

--- src/coffee/Utils.coffee
+++ src/coffee/Utils.coffee
@@ -1,6 +1,23 @@
 class Utils
 	constructor: () ->
 		return
+
+	@detectMobile: () ->
+		_ua = navigator.userAgent.toLowerCase()
+		_mobileUa = ["android", "ipod", "iphone", "ipad"]
+		_isMobile = false
+		for i in [0.._mobileUa.length - 1]
+			if _ua.indexOf(_mobileUa[i]) != -1
+				_isMobile = true
+		return _isMobile
+
+	@detectIOs: () ->
+		@detectIOs()
+		_ua = navigator.userAgent.toLowerCase()
+		_isIOs = false
+		if _ua.indexOf("ipod") != -1 || _ua.indexOf("iphone") != -1 || _ua.indexOf("ipad") != -1
+			_isIOs = true
+		return _isIOs

 	@getUrlParams: () ->
 		pl     = /\+/g


spacing typo

spacing typo

--- src/coffee/tools.coffee
+++ src/coffee/tools.coffee
@@ -56,7 +56,7 @@
     @plotview.main_can_wrapper.bind('mouseup', (e) =>
       if @button_activated
         @_stop_drag()
-       return false
+        return false
     )
     @pan_button = $("<button> #{@options.buttonText} </button>")
     @plotview.$el.find('.button_bar').append(@pan_button)


solved error in if for Turbolinks

solved error in if for Turbolinks

--- lib/assets/javascript/init.js.coffee
+++ lib/assets/javascript/init.js.coffee
@@ -31,7 +31,7 @@
 jQuery ->
   window.Initjs.initialize()

-  unless _.isUndefined(Turbolinks)
+  unless typeof(Turbolinks) == 'undefined'
     document.addEventListener "page:change", ->
       window.Initjs.initialize()


Guard against posts/comments where the user no longer exists

--- lib/calculate_member_activity.coffee
+++ lib/calculate_member_activity.coffee
@@ -35,7 +35,7 @@
           decay = 1 / Math.pow(2, (days_old / SIX_MONTHS))

           # Add points to the poster.
-          poster = _.find members, (member) -> member.id is post.from.id
+          poster = _.find members, (member) -> member.id is post.from?.id
           if poster?
             poster.points += POST * decay

@@ -49,7 +49,7 @@
           # Add points for comments.
           if post.comments?
             for comment in post.comments.data
-              commenter = _.find members, (member) -> member.id is comment.from.id
+              commenter = _.find members, (member) -> member.id is comment.from?.id
               if commenter?
                 commenter.points += COMMENT * decay
         )


Fix scope issue when iterating through a loop. Additionally, this avoid generating a function for each loop step.

--- lib/bot.coffee
+++ lib/bot.coffee
@@ -52,16 +52,19 @@

     # Build up parser callbacks.
     for own key, config of pluginOptions
-      parser.on "#{pluginId}-#{key}", (opt, value) =>
-        parsed = if config.parse
-          config.parse.apply @, arguments
-        else
-          value
-        @logEvent 'bot', 'loadPlugin', "Setting #{pluginId}.#{key} = #{parsed}"
-        targetOptions[key] = parsed
-        return
+      parser.on "#{pluginId}-#{key}", @onPluginOption pluginId, key, config, targetOptions

     return parser
+
+  onPluginOption : (pluginId, key, config, targetOptions) ->
+    (opt, value) =>
+      parsed = if config.parse
+        config.parse.apply @, arguments
+      else
+        value
+      @logEvent 'bot', 'loadPlugin', "Setting #{pluginId}.#{key} = #{parsed}"
+      targetOptions[key] = parsed
+      return

   loadPlugin : (pluginId) ->
     pluginPath = "../plugins/#{pluginId}"


add case-insensitive-sort command

add case-insensitive-sort command

--- lib/operator-transform-string.coffee
+++ lib/operator-transform-string.coffee
@@ -655,6 +655,18 @@
   getNewRows: (rows) ->
     rows.sort()

+class CaseInsensitiveSort extends ChangeOrder
+  @extend()
+  @registerToSelectList()
+  @description: "Sort lines alphabetically (case insensitive)"
+  getNewRows: (rows) ->
+    mapped = rows.map (el, i) ->
+      {index: i, value: el.toLowerCase()}
+    mapped.sort (a, b) ->
+      +(a.value > b.value) or +(a.value is b.value) - 1
+    mapped.map (el) ->
+      rows[el.index]
+
 class SortByNumber extends ChangeOrder
   @extend()
   @registerToSelectList()


Sixth batch of Swedish translation. (#6132)

--- app/locale/sv.coffee
+++ app/locale/sv.coffee
@@ -1107,86 +1107,86 @@
     editor_config_behaviors_description: "Avsluta automatiskt hakparenteser, parenteser, och citat."

   about:
-#    title: "About CodeCombat - Engaging Students, Empowering Teachers, Inspiring Creation"
-#    meta_description: "Our mission is to level computer science through game-based learning and make coding accessible to every learner. We believe programming is magic and want learners to be empowered to to create things from pure imagination."
-#    learn_more: "Learn More"
+    title: "Om CodeCombat - Engagerande studenter, stärka lärare, inspirerande skapelse"
+    meta_description: "Vårt uppdrag är att planera datavetenskap genom spelbaserat lärande och göra kodning tillgänglig för alla elever. Vi tror att programmering är magiskt och vill att eleverna ska kunna skapa saker från fantasi."
+    learn_more: "Läs mer"
     main_title: "Om du vill lära dig programmera behlver du skriva (en hel del) kod."
     main_description: "PÅ CodeCombat, vårt jobb är att se till att du gör det med ett leende på läpparna."
     mission_link: "Uppdrag"
-#    team_link: "Team"
-#    story_link: "Story"
-#    press_link: "Press"
+    team_link: "Team"
+    story_link: "Berättelse"
+    press_link: "Press"
     mission_title: "Vårt uppdrag: göra programmering tillgängligt för varje elev på Jorden."
-#    mission_description_1: "Programming is magic. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using typed code."
-#    mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to every student, because everyone should have the chance to learn the magic of programming."
-#    team_title: "Meet the CodeCombat team"
-#    team_values: "We value open and respectful dialogue, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our GitHub contributors, because we value growth and learning in our team."
+    mission_description_1: "Programmering är magiskt. Det är förmågan att skapa saker från ren fantasi. Vi startade CodeCombat för att ge eleverna en känsla av trolldomskraft vid sina fingertoppar genom att använda kod."
+    mission_description_2: "Som vi ser, kan de också lära sig snabbare. MYCKET snabbare. Det är som att ha en konversation istället för att läsa en manual. Vi vill föra den konversationen till varje skola och till varje elev för att alla borde ha chansen att lära sig magin i programmeringen."
+    team_title: "Möt CodeCombat-teamet"
+    team_values: "Vi värdesätter en öppen och respektfull dialog där den bästa idén vinner. Våra beslut bygger på kundundersökningar och vår process är inriktad på att leverera konkreta resultat för dem. Alla medverkar, från vår VD till våra GitHub-bidragsgivare, eftersom vi värdesätter tillväxt och lärande i vårt team."
     nick_title: "Medgrundare" # {change}
-#    csm_title: "Customer Success Manager"
-#    ae_title: "Account Executive"
-#    sae_title: "Senior Account Executive"
-#    sism_title: "Senior Inside Sales Manager"
-#    shan_title: "Head of Marketing, CodeCombat Greater China"
-#    run_title: "Head of Operations, CodeCombat Greater China"
-#    lance_title: "Head of Technology, CodeCombat Greater China"
-#    zhiran_title: "Head of Curriculum, CodeCombat Greater China"
-#    yuqiang_title: "Head of Innovation, CodeCombat Greater China"
-#    swe_title: "Software Engineer"
-#    sswe_title: "Senior Software Engineer"
-#    css_title: "Customer Support Specialist"
-#    css_qa_title: "Customer Support / QA Specialist"
-#    maya_title: "Senior Curriculum Developer"
-#    bill_title: "General Manager, CodeCombat Greater China"
-#    pvd_title: "Product and Visual Designer"
-#    spvd_title: "Senior Product and Visual Designer"
-#    daniela_title: "Marketing Manager"
-#    bobby_title: "Game Designer"
-#    brian_title: "Senior Game Design Manager"
-#    stephanie_title: "Customer Support Specialist"
-#    sdr_title: "Sales Development Representative"
+    csm_title: "Kundframgångschef"
+    ae_title: "Kontoansvarig"
+    sae_title: "Senior kontoansvarig"
+    sism_title: "Senior försäljningschef"
+    shan_title: "Chef för marknadsföring, CodeCombat Kina"
+    run_title: "Verksamhetschef, CodeCombat Kina"
+    lance_title: "Teknikchef, CodeCombat Kina"
+    zhiran_title: "Chef för läroplanen, CodeCombat Kina"
+    yuqiang_title: "Innovationschef, CodeCombat Kina"
+    swe_title: "Mjukvaruingenjör"
+    sswe_title: "Senior mjukvaruingenjör"
+    css_title: "Kundtjänst specialist"
+    css_qa_title: "Kundtjänst / QA-specialist"
+    maya_title: "Senior kursplanutvecklare"
+    bill_title: "General Manager, CodeCombat Kina"
+    pvd_title: "Produkt- och visuell formgivare"
+    spvd_title: "Senior produkt- och visuell formgivare"
+    daniela_title: "Marknadsansvarig"
+    bobby_title: "Spel designer"
+    brian_title: "Senior spel designerchef"
+    stephanie_title: "Kundtjänst specialist"
+    sdr_title: "Försäljningsutvecklingsrepresentant"
     retrostyle_title: "Illustration"
     retrostyle_blurb: "RetroStyle Games"
-#    community_title: "...and our open-source community"
-#    bryukh_title: "Senior Gameplay Developer"
-#    oa_title: "Operations Associate"
-#    ac_title: "Administrative Coordinator"
-#    ea_title: "Executive Assistant"
-#    om_title: "Operations Manager"
-#    mo_title: "Manager, Operations"
-#    scd_title: "Senior Curriculum Developer"
-#    lcd_title: "Lead Curriculum Developer"
-#    vpm_title: "VP, Marketing"
-#    community_subtitle: "Over 500 contributors have helped build CodeCombat, with more joining every week!"
-#    community_description_3: "CodeCombat is a"
-#    community_description_link_2: "community project"
-#    community_description_1: "with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our"
-#    community_description_link: "contribute page"
-#    community_description_2: "for more info."
-#    number_contributors: "Over 450 contributors have lent their support and time to this project."
-#    story_title: "Our story so far"
-#    story_subtitle: "Since 2013, CodeCombat has grown from a mere set of sketches to a living, thriving game."
-#    story_statistic_1a: "20,000,000+"
-#    story_statistic_1b: "total players"
+    community_title: "... och vårt community med öppen källkod"
+    bryukh_title: "Senior spelutvecklare"
+    oa_title: "Operations Associate"
+    ac_title: "Administrativ koordinator"
+    ea_title: "Verkställande assistent"
+    om_title: "Verksamhetschef"
+    mo_title: "Chef, Verksamhet"
+    scd_title: "Senior kursplanutvecklare"
+    lcd_title: "Huvud kursplanutvecklare"
+    vpm_title: "VP, marknadsföring"
+    community_subtitle: "Över 500 bidragsgivare har hjälpt till att bygga CodeCombat, med fler som ansluter varje vecka!"
+    community_description_3: "CodeCombat är ett"
+    community_description_link_2: "community projekt"
+    community_description_1: "med hundratals spelare som frivilligt skapar nivåer, bidrar till vår kod för att lägga till funktioner, fixa buggar, test spela och till och med översätta spelet till 50 språk hittills. Anställda, bidragsgivare och webbplatsen får genom att dela idéer och samla insatser, liksom öppen källkod i allmänhet. Webbplatsen bygger på många open source-projekt, och vi är öppna för att ge tillbaka till samhället och ge kodnyfikna spelare ett välbekant projekt att utforska och experimentera med. Vem som helst kan gå med i CodeCombat-communityn! Kolla in vår"
+    community_description_link: "bidrag sida"
+    community_description_2: "för mer information."
+    number_contributors: "Över 450 bidragsgivare har gett sitt stöd och tid till detta projekt."
+    story_title: "Vår historia hittills"
+    story_subtitle: "Sedan 2013 har CodeCombat vuxit från bara en uppsättning skisser till ett levande, blomstrande spel."
+    story_statistic_1a: "20.000.000+"
+    story_statistic_1b: "totalt antal spelare"
     story_statistic_1c: "Har börjat sin programmeringsresa genom CodeCombat"
     story_statistic_2a: "Vi har översatt till över 50 languages — våra spelare kommer från"
     story_statistic_2b: "190+ länder"
-#    story_statistic_3a: "Together, they have written"
-#    story_statistic_3b: "1 billion lines of code and counting"
-#    story_statistic_3c: "across many different programming languages"
-#    story_long_way_1: "Though we've come a long way..."
-#    story_sketch_caption: "Nick's very first sketch depicting a programming game in action."
-#    story_long_way_2: "we still have much to do before we complete our quest, so..."
-#    jobs_title: "Come work with us and help write CodeCombat history!"
-#    jobs_subtitle: "Don't see a good fit but interested in keeping in touch? See our \"Create Your Own\" listing."
-#    jobs_benefits: "Employee Benefits"
-#    jobs_benefit_4: "Unlimited vacation"
-#    jobs_benefit_5: "Professional development and continuing education support – free books and games!"
-#    jobs_benefit_6: "Medical (gold), dental, vision, commuter, 401K"
-#    jobs_benefit_7: "Sit-stand desks for all"
-#    jobs_benefit_9: "10-year option exercise window"
-#    jobs_benefit_10: "Maternity leave: 12 weeks paid, next 6 @ 55% salary"
-#    jobs_benefit_11: "Paternity leave: 12 weeks paid"
-#    jobs_custom_title: "Create Your Own"
+    story_statistic_3a: "Tillsammans har de skrivit"
+    story_statistic_3b: "1 miljard rader kod, och mer blir det,"
+    story_statistic_3c: "på många olika programmeringsspråk"
+    story_long_way_1: "Även om vi har kommit långt..."
+    story_sketch_caption: "Nicks allra första skiss som visar ett programmeringsspel."
+    story_long_way_2: "vi har fortfarande mycket att göra innan vi slutför vårt uppdrag, så..."
+    jobs_title: "Kom och arbeta med oss och hjälp med att skriva CodeCombat-historia!"
+    jobs_subtitle: "Passar detta inte dig men är intresserad av att hålla kontakten? Se vår lista \"Skapa din egen\"."
+    jobs_benefits: "Anställningsförmåner"
+    jobs_benefit_4: "Obegränsad semester"
+    jobs_benefit_5: "Professionell utveckling och fortbildningsstöd - gratis böcker och spel!"
+    jobs_benefit_6: "Medicinsk (guld), tandvård, syn, pendlare, 401K"
+    jobs_benefit_7: "Höj och säkbart skrivbord för alla"
+    jobs_benefit_9: "10-årigt övningsfönster för optioner"
+    jobs_benefit_10: "Mammaledighet: 12 veckor betald, nästa 6 @ 55% lön"
+    jobs_benefit_11: "Pappaledighet: 12 veckor betald"
+    jobs_custom_title: "Skapa din egen"
 #    jobs_custom_description: "Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!"
 #    jobs_custom_contact_1: "Send us a note at"
 #    jobs_custom_contact_2: "introducing yourself and we might get in touch in the future!"


delete commented portion

delete commented portion

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -127,86 +127,6 @@
       '10':
         'name': 'punctuation.definition.parameters.end.stata'
   }
-  # {
-  #   'comment': 'Add Regex support for regexr() too'
-  #   'begin': '(regexr)(\\()(.*)(,)\\s*(\"|`\")(.+)(\"|\"\')\\s*'
-  #   'beginCaptures':
-  #     '1':
-  #       'name': 'support.function.builtin.stata'
-  #     '2':
-  #       'name': 'punctuation.definition.parameters.begin.stata'
-  #     '3':
-  #       'patterns': [
-  #         {
-  #           'include': '#string-compound'
-  #         }
-  #         {
-  #           'include': '#string-regular'
-  #         }
-  #         {
-  #           'include': '#macro-local'
-  #         }
-  #         {
-  #           'include': '#macro-global'
-  #         }
-  #         {
-  #           'include': '#builtin_functions'
-  #         }
-  #         {
-  #           'match': '[A-Za-z_][A-Za-z0-9_]{0,31}'
-  #           'name': 'variable.parameter.function.stata'
-  #         }
-  #         {
-  #           'include': '#comments'
-  #         }
-  #       ]
-  #     '4':
-  #       'name': 'punctuation.definition.variable.begin.stata' # the comma
-  #   'contentName':
-  #     'patterns': [
-  #
-  #     ]
-  #     '5':
-  #       'name': 'punctuation.definition.string.begin.stata'
-  #     '6':
-  #       'patterns': [
-  #         {'include': 'source.regexp.stata'}
-  #       ]
-  #     '7':
-  #       'name': 'punctuation.definition.string.end.stata'
-  #     '8':
-  #       'name': 'punctuation.definition.variable.begin.stata' # the comma
-  #     '9':
-  #       'patterns': [
-  #         {
-  #           'include': '#string-compound'
-  #         }
-  #         {
-  #           'include': '#string-regular'
-  #         }
-  #         {
-  #           'include': '#macro-local'
-  #         }
-  #         {
-  #           'include': '#macro-global'
-  #         }
-  #         {
-  #           'include': '#builtin_functions'
-  #         }
-  #         {
-  #           'match': '[A-Za-z_][A-Za-z0-9_]{0,31}'
-  #           'name': 'variable.parameter.function.stata'
-  #         }
-  #         {
-  #           'include': '#comments'
-  #         }
-  #       ]
-  #   'end': '(,)(.*)(\\))'
-  #   'endCaptures':
-  #     '1':
-  #       'name': 'punctuation.definition.parameters.end.stata'
-  # }
-
   {
     'include': '#builtin_functions'
   }


apply CDN url to pages context which is separate.

BOOO HISSS

--- config/application.coffee
+++ config/application.coffee
@@ -42,6 +42,11 @@
         js: "../js/app.js"
         css: "../css/app.css"

+  pages:
+    dist:
+      context:
+        cdn: "http://cdn4.testdouble.com/"
+
   # Use grunt-markdown-blog in lieu of Lineman's built-in pages task
   prependTasks:
     common: "markdown:dev"


修正事件无法通过初始化JSON绑定的BUG

修正事件无法通过初始化JSON绑定的BUG

--- src/core/element.coffee
+++ src/core/element.coffee
@@ -57,8 +57,8 @@
 			for name, definition of events
 				realName = name.toLowerCase()
 				if name isnt realName
-					attributes[realName] = definition
-					delete attributes[name]
+					events[realName] = definition
+					delete events[name]

 			events.$has = hasDefinition
 			events.$get = getDefinition


fix small things introduced in #166

fix small things introduced in #166

--- app/assets/javascripts/stacks.js.coffee
+++ app/assets/javascripts/stacks.js.coffee
@@ -13,8 +13,8 @@

   displayConfigureCiMessage = ->
     commits = $('.commit')
-    ciConfigured = !commits.length || commits.length != commits.filter('.unknown').length
-    $('.configure-ci').toggleClass('hidden', ciConfigured)
+    ciConfigured = !commits.length || commits.length != commits.find('a.unknown').length
+    $('.configure-ci').parent().toggleClass('hidden', ciConfigured)
     return

   displayConfigureCiMessage()


Added ss version to images

Added ss version to images

--- snippets/template.cson
+++ snippets/template.cson
@@ -53,36 +53,36 @@
     'prefix': 'include '
     'body': '<% include ${1} %>${0}'
   # Image manipulation
-  'ScaleWidth Image':
+  'ScaleWidth Image 3.2+':
     'prefix': 'scalewidth '
     'body': 'ScaleWidth(${1:width})${0}'
-  'ScaleMaxWidth Image':
+  'ScaleMaxWidth Image 3.2+':
     'prefix': 'scalemaxwidth '
     'body': 'ScaleMaxWidth(${1:width})${0}'
-  'ScaleHeight Image':
+  'ScaleHeight Image 3.2+':
     'prefix': 'scaleheight '
     'body': 'ScaleHeight(${1:height})${0}'
-  'ScaleMaxHeight Image':
+  'ScaleMaxHeight Image 3.2+':
     'prefix': 'scalemaxheight '
     'body': 'ScaleMaxHeight(${1:height})${0}'
-  'Fit Image':
+  'Fit Image 3.2+':
     'prefix': 'fit '
     'body': 'Fit(${1:width},${1:height})${0}'
-  'FitMax Image':
+  'FitMax Image 3.2+':
     'prefix': 'fitmax '
     'body': 'FitMax(${1:width},${1:height})${0}'
-  'Fill Image':
+  'Fill Image 3.2+':
     'prefix': 'fill '
     'body': 'Fill(${1:width},${1:height})${0}'
-  'FillMax Image':
+  'FillMax Image 3.2+':
     'prefix': 'fillmax '
     'body': 'FillMax(${1:width},${1:height})${0}'
-  'CropWidth Image':
+  'CropWidth Image 3.2+':
     'prefix': 'cropwidth '
     'body': 'CropWidth(${1:width})${0}'
-  'CropHeight Image':
+  'CropHeight Image 3.2+':
     'prefix': 'cropheight '
     'body': 'CropHeight(${1:width})${0}'
-  'Pad Image':
+  'Pad Image 3.2+':
     'prefix': 'pad '
     'body': 'Pad(${1:width},${1:height},${3:hex})${0}'


Fix coding style

Fix coding style

--- test/hookable_test.coffee
+++ test/hookable_test.coffee
@@ -91,3 +91,5 @@
     User.create (err, user) ->
         user.destroy()

+it 'allows me to modify attributes before saving', (test) ->
+    test.done()


Try a different query

Try a different query

--- controllers/queues.coffee
+++ controllers/queues.coffee
@@ -8,9 +8,9 @@
       model: models.User
       as: 'Holder'
     ]
-  ,
-    model: models.User
-    as: 'Owners'
+  #  ,
+  #    model: models.User
+  #    as: 'Owners'
 ]

 exports.index = (req, res, next) ->


[mailbox] better json rpc inbound command handline

--- test/mail/rpc_json.coffee
+++ test/mail/rpc_json.coffee
@@ -5,30 +5,42 @@
     net = require 'net'

     constructor: (@debug = off, port = 3000, host = "localhost") ->
+        # @payload may temporarily hold a partial result (incomplete json response)
         @payload = ""
         @defer_request = []
         @defer_connection = q.defer()
         @json_rpc_request_counter = 0
         @connection = net.createConnection port, host
         @connection.on 'connect', () =>
-            console.log "Connection opened" if @debug
+            console.log "Connection opened #{host}:#{port}" if @debug
             @defer_connection.resolve()

-        @connection.on 'data', (payload) =>
-            @payload += payload.toString()
-            if @payload.charAt(@payload.length - 1) == '\n'
-                payload = @payload.trim()
+        @connection.on 'data', (_payload) =>
+
+            _payload = _payload.toString() unless typeof _payload is 'string'
+            @payload += _payload
+
+            # @payload may be holding more than one command
+            payload_complete = @payload.charAt(@payload.length - 1) is '\n'
+            cmds = @payload.trim().split '\n'
+            if not payload_complete
+                # save incomplete command for the next on 'data' event
+                [..., @payload] = cmds
+                # keep just the complete commands
+                cmds = cmds[0...-1]
+            else
                 @payload = ""
-                for str in payload.split '\n'
-                    console.log "<<< #{str}" if @debug
-                    response = JSON.parse(str)
-                    if response.error
-                        @defer_request[response.id].reject(response.error)
-                    else
-                        @defer_request[response.id].resolve(response.result)

-                    delete @defer_request[response.id]
-
+            for cmd in cmds
+                console.log "<<< #{cmd}" if @debug
+                response = JSON.parse(cmd)
+                if response.error
+                    @defer_request[response.id].reject(response.error)
+                else
+                    @defer_request[response.id].resolve(response.result)
+
+                delete @defer_request[response.id]
+
         @connection.on 'end', (response) =>
             console.log "Connection closed" if @debug
             @defer_connection = null
@@ -58,6 +70,7 @@
         @defer_connection.promise.then (p) =>
             data = JSON.stringify rpc_data
             console.log ">>> #{data}" if @debug
+            #console.log ">>> #{data.id}: #{data.method} #{data.params.join(" ")}" if @debug
             @connection.write data

         @defer_request[@json_rpc_request_counter].promise


Physics: Added update_rotation and set_friction methods to body.

--- engine/physics/bullet.coffee
+++ engine/physics/bullet.coffee
@@ -393,6 +393,23 @@
         tmp_Transform.setRotation(tmp_Quaternion)
         @btbody.setWorldTransform(tmp_Transform)

+    update_rotation: ->
+        return if not @btbody?
+        ob = @owner
+        {tmp_Vector3, tmp_Quaternion, tmp_Transform} = @world
+        if @btbody.getMotionState
+            transform = tmp_Transform
+            @btbody.getMotionState().getWorldTransform(transform)
+        else
+            transform = @btbody.getWorldTransform()
+        if ob.parent or ob.rotation_order != 'Q'
+            {position, rotation} = ob.get_world_position_rotation()
+        else
+            {position, rotation} = ob
+        tmp_Quaternion.setValue(rotation.x, rotation.y, rotation.z, rotation.w)
+        transform.setRotation(tmp_Quaternion)
+        @btbody.setWorldTransform(tmp_Transform)
+
     get_linear_velocity: (local=false)->
         v = @btbody.getLinearVelocity()
         new_v = vec3.new v.x(), v.y(), v.z()
@@ -431,6 +448,12 @@
         {tmp_Vector3} = @world
         tmp_Vector3.setValue(force.x, force.y, force.z)
         @btbody.applyCentralImpulse(tmp_Vector3)
+
+    set_friction: (friction) ->
+        if Math.abs(@friction-friction) < 0.01
+            return
+        @friction = friction
+        @instance()

     # Character


renaming tests

renaming tests

--- test/test.coffee
+++ test/test.coffee
@@ -14,7 +14,7 @@
   after deleteAssets
   before deleteAssets

-  describe 'basics', ->
+  describe 'unit', ->

     it "require a dependency that exist locally", ->
       dep = forceRequire 'mocha'
@@ -28,7 +28,7 @@
       dep = forceRequire 'force-require-test'
       (typeof dep.hello).should.be.equal 'function'

-  describe 'advanced', ->
+  describe 'integration', ->

     it 'async mode', (done) ->
       forceRequire 'mocha', done


Add test

Add test

--- test/test.coffee
+++ test/test.coffee
@@ -105,3 +105,11 @@
 			sorted = anysort.grouped sortable, [before]
 			{matched} = anysort.splice sortable, before
 			assert.deepEqual matched, sorted[0...matched.length]
+
+		it 'should allow nested arrays and non-arrays in groupedMatchers', ->
+			sorted = anysort.grouped sortable, [before, after]
+			matchedBefore = (anysort.splice sortable, before).matched
+			matchedAfter  = (anysort.splice sortable, after ).matched
+			start = matchedBefore.length
+			end = start + matchedAfter.length
+			assert.deepEqual matchedAfter, sorted[start...end]


Fix match-pair tests

Fix match-pair tests

--- spec/emmet-spec.coffee
+++ spec/emmet-spec.coffee
@@ -86,14 +86,27 @@

     describe "for match-pair-inward", ->
       beforeEach ->
-        editSession.setCursorBufferPosition([3, 23])
+        editSession.setCursorBufferPosition([1, 4])

       it "matches pairs inwards via commands", ->
-        editSession.getSelection().setBufferRange([[1, 29], [4, 4]])
+        editor.trigger "emmet:match-pair-inward"
+        expect(editor.getSelection().getBufferRange()).toEqual [[0, 15], [5, 0]]
+        editor.trigger "emmet:match-pair-inward"
+        expect(editor.getSelection().getBufferRange()).toEqual [[1, 4], [4, 14]]
+        editor.trigger "emmet:match-pair-inward"
+        expect(editor.getSelection().getBufferRange()).toEqual [[1, 29], [4, 4]]
         editor.trigger "emmet:match-pair-inward"
         expect(editor.getSelection().getBufferRange()).toEqual [[2, 8], [2, 33]]
+
+      it "matches pairs inwards via keybindings", ->
         editor.trigger keydownEvent('d', altKey: true, target: editor[0])
-        expect(editor.getSelection().getBufferRange()).toEqual [[2, 12], [2, 28]]
+        expect(editor.getSelection().getBufferRange()).toEqual [[0, 15], [5, 0]]
+        editor.trigger keydownEvent('d', altKey: true, target: editor[0])
+        expect(editor.getSelection().getBufferRange()).toEqual [[1, 4], [4, 14]]
+        editor.trigger keydownEvent('d', altKey: true, target: editor[0])
+        expect(editor.getSelection().getBufferRange()).toEqual [[1, 29], [4, 4]]
+        editor.trigger keydownEvent('d', altKey: true, target: editor[0])
+        expect(editor.getSelection().getBufferRange()).toEqual [[2, 8], [2, 33]]

     describe "for go-to match-pair", ->
       it "goes to the match-pair via commands", ->


fixes back button

fixes back button

--- app/js/app.coffee
+++ app/js/app.coffee
@@ -43,7 +43,7 @@
     editableThemes['default'].cancelTpl = '<button type="button" ng-click="$form.$cancel()" class="btn btn-sm btn-warning"><i class="fa fa-times fa-lg"></i></button>'

     $rootScope.$on "$stateChangeSuccess", (event, toState, toParams, fromState, fromParams) ->
-        app_history.push {state: fromState.name, params: fromState} if fromState.name
+        app_history.push {state: fromState.name, params: fromParams} if fromState.name

     $rootScope.history_back = ->
         return false if app_history.length == 0 or window.history.length == 0
@@ -53,9 +53,8 @@
             prev_page = app_history.pop()
             break unless prev_page
             break unless prev_page.state == "createwallet" or prev_page.state == "unlockwallet"
-        return false if window.history.length < history_counter
-        $window.history.go(0 - history_counter)
-        return true
+        $state.go(prev_page.state, prev_page.params) if prev_page
+        return !!prev_page

     $rootScope.history_forward = ->
         $window.history.forward()


smarter loop range syntax

smarter loop range syntax

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -202,27 +202,27 @@
     hPanel.appendChild hMask

     for anchor in ['top', 'bottom']
-      for i in [1..@hPanels]
+      for i in [0...@hPanels]
         panel = hPanel.cloneNode true
         content = panel.getElementsByClassName('oridomi-content')[0]
-        panel.style[css.transform] = @_transform @_getXy i - 1, anchor
+        panel.style[css.transform] = @_transform @_getXy i, anchor

         if anchor is 'top'
-          yOffset = -((i - 1) * @panelHeight)
+          yOffset = -(i * @panelHeight)
         else
           panel.style[css.transformOrigin] = 'bottom'
-          yOffset = -((@hPanels * @panelHeight) - (@panelHeight * i))
+          yOffset = -((@hPanels * @panelHeight) - (@panelHeight * (i + 1)))

         content.style[css.transform] = @_transform [0, yOffset]

         if @shading
-          @shaders[anchor].top[i - 1] = panel.getElementsByClassName('oridomi-shader-top')[0]
-          @shaders[anchor].bottom[i - 1] = panel.getElementsByClassName('oridomi-shader-bottom')[0]
-
-        @panels[anchor][i - 1] = panel
-
-        unless i is 1
-          @panels[anchor][i - 2].appendChild panel
+          @shaders[anchor].top[i] = panel.getElementsByClassName('oridomi-shader-top')[0]
+          @shaders[anchor].bottom[i] = panel.getElementsByClassName('oridomi-shader-bottom')[0]
+
+        @panels[anchor][i] = panel
+
+        unless i is 0
+          @panels[anchor][i - 1].appendChild panel

       @stages[anchor].appendChild @panels[anchor][0]

@@ -248,27 +248,27 @@
     vPanel.appendChild vMask

     for anchor in ['left', 'right']
-      for i in [1..@vPanels]
+      for i in [0...@vPanels]
         panel = vPanel.cloneNode true
         content = panel.getElementsByClassName('oridomi-content')[0]
-        panel.style[css.transform] = @_transform @_getXy i - 1, anchor
+        panel.style[css.transform] = @_transform @_getXy i, anchor

         if anchor is 'left'
-          xOffset = -((i - 1) * @panelWidth)
+          xOffset = -(i * @panelWidth)
         else
           panel.style[css.transformOrigin] = 'right'
-          xOffset = -((@vPanels * @panelWidth) - (@panelWidth * i))
+          xOffset = -((@vPanels * @panelWidth) - (@panelWidth * (i + 1)))

         content.style[css.transform] = @_transform [xOffset, 0]

         if @shading
-          @shaders[anchor].left[i - 1]  = panel.getElementsByClassName('oridomi-shader-left')[0]
-          @shaders[anchor].right[i - 1] = panel.getElementsByClassName('oridomi-shader-right')[0]
-
-        @panels[anchor][i - 1] = panel
-
-        unless i is 1
-          @panels[anchor][i - 2].appendChild panel
+          @shaders[anchor].left[i]  = panel.getElementsByClassName('oridomi-shader-left')[0]
+          @shaders[anchor].right[i] = panel.getElementsByClassName('oridomi-shader-right')[0]
+
+        @panels[anchor][i] = panel
+
+        unless i is 0
+          @panels[anchor][i - 1].appendChild panel

       @stages[anchor].appendChild @panels[anchor][0]