Use raw.githubusercontent.com to reduce API calls

--- src/githubrepofilesystem.coffee
+++ src/githubrepofilesystem.coffee
@@ -4,6 +4,7 @@

 class GitHubRepoFileSystem
   API_URL = "https://api.github.com"
+  RAW_URL = "https://raw.githubusercontent.com"

   ###*
   @class
@@ -50,7 +51,10 @@
     Get last modification date of the entry
     ###
     getMetadata: (successCallback, errorCallback) ->
-      successCallback(@_mdate)
+      @_fs._getCommitInfo((result) =>
+        return errorCallback?({code: FileError.NOT_READABLE_ERR}) unless result
+        successCallback(new Date(@_fs._commit.committer.date))
+      )
       undefined

     ###*
@@ -58,7 +62,7 @@
     Move an entry to a different location on the file system (not supported)
     ###
     moveTo: (parent, newName, successCallback, errorCallback) ->
-      errorCallback?()
+      errorCallback?({code: FileError.NO_MODIFICATION_ALLOWED_ERR})
       undefined

     ###*
@@ -66,7 +70,7 @@
     Copy an entry to a different location on the file system (not supported)
     ###
     copyTo: (parent, newName, successCallback, errorCallback) ->
-      errorCallback?()
+      errorCallback?({code: FileError.NO_MODIFICATION_ALLOWED_ERR})
       undefined

     ###*
@@ -80,7 +84,7 @@
     Deletes a file or directory
     ###
     remove: (successCallback, errorCallback) ->
-      errorCallback?()
+      errorCallback?({code: FileError.NO_MODIFICATION_ALLOWED_ERR})
       undefined

     ###*
@@ -94,11 +98,13 @@
     ###*
     @protected
     Constructor of GitHubRepoEntry
-    ###
-    constructor: (@_fs, @_parent, @_url, @_name, @_mdate) ->
-      @_mdate or= (@_parent or this)._mdate
-      @_isFile = true
-      @_fullPath = "#{(@_parent?.fullPath or "").replace(/\/$/, '')}/#{@_name}"
+    @param {GitHubRepoFileSystem}     _fs     File system object
+    @param {GitHubRepoDirectoryEntry} parent  Parent directory @nullable
+    @param {String}                   path    Relative path from parent
+    ###
+    constructor: (@_fs, parent, path) ->
+      @_fullPath = "#{(parent?.fullPath or "").replace(/\/$/, '')}/#{path}"
+      @_name = @_fullPath.split("/").pop()

   ###*
   @class
@@ -117,7 +123,11 @@
     Opens an existing file
     ###
     getFile: (path, options, successCallback, errorCallback) ->
-      @_getEntry("blob", GitHubRepoFileEntry, path, options, successCallback, errorCallback)
+      options or= {}
+      if options.create
+        errorCallback?({code: FileError.NO_MODIFICATION_ALLOWED_ERR})
+        return undefined
+      successCallback(new GitHubRepoFileEntry(@_fs, this, path.replace(/\/$/, '')))
       undefined

     ###*
@@ -125,7 +135,11 @@
     Opens an existing directory
     ###
     getDirectory: (path, options, successCallback, errorCallback) ->
-      @_getEntry("tree", GitHubDirectoryEntry, path, options, successCallback, errorCallback)
+      options or= {}
+      if options.create
+        errorCallback?({code: FileError.NO_MODIFICATION_ALLOWED_ERR})
+        return undefined
+      successCallback(new GitHubRepoDirectoryEntry(@_fs, this, path.replace(/\/$/, '')))
       undefined

     ###*
@@ -133,48 +147,20 @@
     Deletes a directory and all of its contents (not supported)
     ###
     removeRecursively: (successCallback, errorCallback) ->
-      errorCallback?()  # Because read-only
+      errorCallback?({code: FileError.NO_MODIFICATION_ALLOWED_ERR})
       undefined

     ###*
     @private
     Constructor of GitHubRepoDirectoryEntry
-    ###
-    constructor: (fs, parent, url, name, mdate) ->
+    @param {GitHubRepoFileSystem}     fs      File system object
+    @param {GitHubRepoDirectoryEntry} parent  Parent directory @nullable
+    @param {String}                   path    Relative path from parent
+    ###
+    constructor: (fs, parent, path) ->
       super
       @_isFile = false
-
-    ###*
-    @private
-    Get a tree information from GitHub
-    ###
-    _getTree: (successCallback, errorCallback) ->
-      req = new XMLHttpRequest
-      req.open("GET", @_url)
-      req.responseType = "json"
-      req.onreadystatechange = ->
-        return unless req.readyState == @DONE
-        return errorCallback?() unless req.status == 200
-        successCallback(req.response.tree or [])
-      req.send()
-
-    ###*
-    @private
-    Get a new entry (file or directory)
-    ###
-    _getEntry: (type, eclass, path, options, successCallback, errorCallback) ->
-      options or= {}
-      return errorCallback?() if options.create # Because read-only
-      @_getTree(
-        ((tree) =>
-          for f in tree
-            continue unless f.path == path
-            return errorCallback?() unless f.type == type and f.url
-            return successCallback(new eclass(@_fs, this, f.url, f.path))
-          return errorCallback?() # Entry not found
-        ),
-        errorCallback
-      ) # @_getTree
+      @_url = undefined

   ###*
   @class
@@ -183,35 +169,21 @@
   class GitHubRepoDirectoryReader
     ###*
     @method
-    Returns a list of entries from a specific directory
+    (NOT SUPPORTED) Returns a list of entries from a specific directory
+    @param {Function} successCallback   Callback ({Entry[]} entries)
+    @param {Function} errorCallback     Callback ({FileError} err) @nullable
     ###
     readEntries: (successCallback, errorCallback) ->
-      if @_done
-        successCallback([])
-        return undefined
-      @_dirEntry._getTree(
-        ((tree) =>
-          entries = []
-          for f in tree
-            if f.type == "tree"
-              eclass = GitHubRepoDirectoryEntry
-            else
-              eclass = GitHubRepoFileEntry
-            entries.push(new eclass(@_dirEntry._fs, @_dirEntry, f.url, f.path))
-          @_done = true
-          successCallback(entries)
-        ),
-        errorCallback
-      ) # @_entry._getTree
+      errorCallback?({code: FileError.NOT_READABLE_ERR})
       undefined

     ###*
     @private
     Constructor of GitHubRepoDirectoryReader
-    ###
-    constructor: (@_dirEntry) ->
-      @_tree = null
-      @_done = false
+    @param {GitHubRepoDirectoryEntry} _dir  Entry of target directory
+    ###
+    constructor: (@_dir) ->
+      null

   ###*
   @class
@@ -222,7 +194,7 @@
     Creates a new FileWriter compatible object associated with this file
     ###
     createWriter: (successCallback, errorCallback) ->
-      errorCallback?() # Because read-only
+      errorCallback?({code: FileError.NO_MODIFICATION_ALLOWED_ERR})
       undefined

     ###*
@@ -231,93 +203,105 @@
     file: (successCallback, errorCallback) ->
       req = new XMLHttpRequest
       req.open("GET", @_url)
+      req.responseType = "arraybuffer"
+      req.onreadystatechange = =>
+        return unless req.readyState == XMLHttpRequest.DONE
+        unless req.status == 200
+          return errorCallback?({code: FileError.NOT_FOUND_ERR})
+        array = new Uint8Array(req.response.size)
+        successCallback(new File([req.response], @_name))
+      req.send()
+      undefined
+
+    ###*
+    @private
+    Constructor of GitHubRepoFileEntry
+    @param {GitHubRepoFileSystem}     fs      File system object
+    @param {GitHubRepoDirectoryEntry} parent  Parent directory @nullable
+    @param {String}                   path    Relative path from parent
+    ###
+    constructor: (fs, parent, path) ->
+      super
+      @_isFile = true
+      ref = @_fs._ref
+      @_url = "#{RAW_URL}/#{@_fs._owner}/#{@_fs._repo}/" +
+        "#{ref.commit or ref.tag or ref.branch}#{@_fullPath}"
+
+  ###*
+  @property {String} name
+  @readonly
+  Name of the file system
+  ###
+  @property "name", get: -> "GitHub"
+
+  ###*
+  @property {DirectoryEntry} root
+  @readonly
+  The root directory of the file system
+  ###
+  @property "root", get: -> @_root
+
+  ###*
+  @static
+  Request filesystem for GitHub repository storage
+  @param {String}   owner             Owner name
+  @param {String}   repo              Repository name
+  @param {String}   ref.branch        Branch name (default: "master") @nullable
+  @param {String}   ref.tag           Tag name @nullable
+  @param {String}   ref.commit        Commit hash @nullable
+  @param {Function} successCallback   Callback ({FileSystem} fs)
+  @param {Function} errorCallback     Callback ({FileError} err) @nullable
+  ###
+  @requestFileSystem: (owner, repo, ref, successCallback, errorCallback) ->
+    successCallback(new GitHubRepoFileSystem(owner, repo, ref))
+    undefined
+
+  ###*
+  @private
+  Get commit information from GitHub API
+  @param {String}   commit      Commit hash @nullable
+  @param {Function} callback    Callback ({Boolean} result)
+  ###
+  _getCommitInfo: (commit, callback) ->
+    req = new XMLHttpRequest
+    base = "#{API_URL}/repos/#{@_owner}/#{@_repo}/git"
+    commit or= @_ref.commit
+    if commit
+      req.open("GET", "#{base}/commits/#{commit}")
       req.responseType = "json"
       req.onreadystatechange = =>
         return unless req.readyState == XMLHttpRequest.DONE
-        return errorCallback?() unless req.status == 200
-        array = new Uint8Array(req.response.size)
-        switch req.response.encoding
-          when "base64"
-            bin = atob(req.response.content)
-            (array[i] = bin.charCodeAt(i)) for i in [0...array.length]
-          else
-            return errorCallback?()
-        successCallback(new File([array], @_name, {lastModified: @_mdate}))
+        return callback(false) unless req.status == 200
+        @_commit = req.response
+        callback(true)
       req.send()
-      undefined
-
-    ###*
-    @private
-    Constructor of GitHubRepoFileEntry
-    ###
-    constructor: (fs, parent, url, name, mdate) ->
-      super
-
-  ###*
-  @property {String} name
-  @readonly
-  Name of the file system
-  ###
-  @property "name", get: -> "GitHub"
-
-  ###*
-  @property {DirectoryEntry} root
-  @readonly
-  The root directory of the file system
-  ###
-  @property "root", get: -> @_root
-
-  ###*
-  @static
-  Request filesystem for GitHub repository storage
-  @param {String}   owner             Owner of repository
-  @param {String}   repo              Name of repository
-  @param {String}   ref.branch        Name of branch (default: master) @nullable
-  @param {String}   ref.tag           Name of tag @nullable
-  @param {Function} successCallback   Callback ({FileSystem} fs)
-  @param {Function} errorCallback     Callback () @nullable
-  ###
-  @requestFileSystem: (owner, repo, ref, successCallback, errorCallback) ->
-    if ref?.tag
-      path = "tags/#{ref.tag}"
     else
-      path = "heads/#{ref?.branch or "master"}"
-    fs = new GitHubRepoFileSystem(owner, repo, path)
-    fs._request(successCallback, errorCallback)
-
-  ###*
-  @private
-  Request filesystem (body)
-  @param {Function} successCallback   Callback ({FileSystem} fs)
-  @param {Function} errorCallback     Callback () @nullable
-  ###
-  _request: (successCallback, errorCallback) ->
-    url = "#{API_URL}/repos/#{@_owner}/#{@_repo}/git"
-    req = new XMLHttpRequest
-    req.open("GET", "#{url}/refs/#{@_ref}")
-    req.responseType = "json"
-    req.onreadystatechange = =>
-      return unless req.readyState == XMLHttpRequest.DONE
-      return errorCallback?() unless req.status == 200
-      sha = req.response.object?.sha
-      return errorCallback?() unless sha
-      req = new XMLHttpRequest
-      req.open("GET", "#{url}/commits/#{sha}")
+      base += "/refs/"
+      if @_ref.tag
+        base += "tags/#{@_ref.tag}"
+      else
+        base += "heads/#{@_ref.branch}"
+      req.open("GET", base)
       req.responseType = "json"
       req.onreadystatechange = =>
         return unless req.readyState == XMLHttpRequest.DONE
-        return errorCallback?() unless req.status == 200
-        date = new Date(req.response.author.date)
-        @_root = new GitHubRepoDirectoryEntry(this, null, "#{url}/trees/#{sha}", "", date)
-        successCallback(this)
+        return callback(false) unless req.status == 200
+        @_getCommitInfo(req.response.object.sha, callback)
       req.send()
-    req.send()
     undefined

   ###*
   @private
   Constructor of GitHubRepoFileSystem
+  @param {String} _owner        Owner name
+  @param {String} _repo         Repository name
+  @param {String} _ref.branch   Branch name (default: "master") @nullable
+  @param {String} _ref.tag      Tag name @nullable
+  @param {String} _ref.commit   Commit hash @nullable
   ###
   constructor: (@_owner, @_repo, @_ref) ->
-    null
-
+    @_root = new GitHubRepoDirectoryEntry(this, null, "/")
+    @_ref or= {}
+    @_ref.branch or= "master" unless @_ref.tag or @_ref.commit
+    @_commit = null
+


Make all detection values configurables

Make all detection values configurables

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -1,4 +1,10 @@
 Utils = require "./Utils"
+
+GestureInputLongPressTime = 0.25
+GestureInputDoubleTapTime = 0.25
+GestureInputSwipeThreshold = 30
+GestureInputEdgeSwipeDistance = 30
+GestureInputVelocityTime = 0.1

 {DOMEventManager} = require "./DOMEventManager"

@@ -25,7 +31,7 @@
 			startEvent: @_getGestureEvent(event)
 			startMultiEvent: null
 			startTime: Date.now()
-			pressTimer: window.setTimeout(@longpressstart, 250)
+			pressTimer: window.setTimeout(@longpressstart, GestureInputLongPressTime * 1000)
 			started: {}
 			events: []

@@ -33,7 +39,7 @@

 		@tapstart(event)

-		if Date.now() - @doubleTapTime < 250
+		if Date.now() - @doubleTapTime < (GestureInputDoubleTapTime * 1000)
 			@doubletap(event)
 		else
 			@doubleTapTime = Date.now()
@@ -153,13 +159,13 @@
 		maxX = Utils.frameGetMaxX(Screen.canvasFrame)
 		maxY = Utils.frameGetMaxY(Screen.canvasFrame)

-		if swipeEdge is "top" and 0 < event.start.y - Screen.canvasFrame.y < 30
+		if swipeEdge is "top" and 0 < event.start.y - Screen.canvasFrame.y < GestureInputEdgeSwipeDistance
 			@edgeswipedirectionstart(event)
-		if swipeEdge is "right" and maxX - 30 < event.start.x < maxX
+		if swipeEdge is "right" and maxX - GestureInputEdgeSwipeDistance < event.start.x < maxX
 			@edgeswipedirectionstart(event)
-		if swipeEdge is "bottom" and maxY - 30 < event.start.y < maxY
+		if swipeEdge is "bottom" and maxY - GestureInputEdgeSwipeDistance < event.start.y < maxY
 			@edgeswipedirectionstart(event)
-		if swipeEdge is "left" and 0 < event.start.x - Screen.canvasFrame.x < 30
+		if swipeEdge is "left" and 0 < event.start.x - Screen.canvasFrame.x < GestureInputEdgeSwipeDistance
 			@edgeswipedirectionstart(event)

 	swipedirection: (event) =>
@@ -224,7 +230,9 @@
 		# Detect swipe events

 		# If we did not start but moved more then the swipe threshold, start
-		if not @session.started.swipe and (Math.abs(event.offset.x) > 30 or Math.abs(event.offset.y) > 30)
+		if not @session.started.swipe and (
+			Math.abs(event.offset.x) > GestureInputSwipeThreshold or
+			Math.abs(event.offset.y) > GestureInputSwipeThreshold)
 				@swipestart(event)
 		# If we did start send swipe events
 		else if @session.started.swipe
@@ -279,7 +287,7 @@
 				event.delta = Utils.pointSubtract(event.touchCenter, @session.lastEvent.touchCenter)

 		if @session?.events
-			events = _.filter @session.events, (e) -> e.time > (event.time - 100)
+			events = _.filter @session.events, (e) -> e.time > (event.time - GestureInputVelocityTime)
 			event.velocity = @_getVelocity(events)

 		if event.touches.length > 0


<debug> Proper export and access to removable flag.

--- tests/fixtures/basicDriver.coffee
+++ tests/fixtures/basicDriver.coffee
@@ -8,6 +8,9 @@
 sensors = []

 module.exports.isSensor = true
+
+areDevicesRemovable = true
+module.exports.setRemovableFlag = (flag) -> areDevicesRemovable = flag

 module.exports.init = (callback) ->
 	return callback null
@@ -23,8 +26,11 @@
 			callback "Invalid CustomId"

 module.exports.remove = (customId, id, callback) ->
-	delete sensors[customId]
-	return callback null
+	if areDevicesRemovable
+		delete sensors[customId]
+		callback null
+	else
+		callback "Device not removable"

 module.exports.update = (oldCustomId, newCustomId, callback) ->
 	if sensors[newCustomId]


[grunt] add links to github issues related to `_n()` Nunjucks' filter

There are few issues with current lib:
* https://github.com/foretagsplatsen/numbro/issues/111
* https://github.com/foretagsplatsen/numbro/issues/112

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -379,6 +379,9 @@
            * Format number based on given pattern
            * @todo Use global function instead of filter. It's more flexible. For now it's filter
            *       just because it's faster to use and easier replacement for old filter
+           * @todo There are few issues with current lib:
+           *       * https://github.com/foretagsplatsen/numbro/issues/111
+           *       * https://github.com/foretagsplatsen/numbro/issues/112
            * @param {number} value                  Number which should be formatted
            * @param {string} format = '0,0[.]00'    Pattern as per http://numbrojs.com/format.html
            * @param {string} locale = currentLocale Locale name as per https://github.com/foretagsplatsen/numbro/tree/master/languages


[grunt] add documentation for `humanReadableUrl()`

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -52,6 +52,13 @@
       placeholders.unshift(string)
       return sprintf.apply null, placeholders

+  ###*
+   * Rename pagepath (except if it's matching `exclude` pattern) to `index.html` and move
+   * to directory named after basename of the file
+   * @example `/posts/2015-10-12-article.nj` -> `/posts/2015-10-12-article.nj/index.html`
+   * @param  {string} pagepath Path to page
+   * @return {string} Renamed path
+  ###
   humanReadableUrl = (pagepath) ->
     exclude  = /^(index|\d{3})$/

@@ -429,9 +436,6 @@
         src: ['{,**/}*.{nj,html}', '!{,**/}_*.{nj,html}']
         dest: '<%= path.build.root %>/' + localeDir
         ext: '.html'
-        # Rename all except matching `exclude` pattern files to `index.html`
-        # and move them to directory named after basename of the file
-        # @example `/posts/2015-10-12-article.nj` -> `/posts/2015-10-12-article.nj/index.html`
         rename: (dest, src) =>
           src = humanReadableUrl(src)
           path.join(dest, src);


implemented delete mirror operation

implemented delete mirror operation

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -145,6 +145,17 @@
           then (n) ->
             mirror.saved = true
           , fetchError('update mirror')
+
+    $scope.deleteMirror = (mirror)->
+      idx = $scope.mirrors.indexOf(mirror)
+      if( idx < 0)
+        console.log("EE can't find mirror to delete")
+        console.log(mirror)
+      else
+        Restangular.one('mirrors').remove(mirror.id).
+          then (n) ->
+            $scope.mirrors.splice(idx,1)
+          , fetchError('delete mirror')

     $scope.fetchNode = (id)->
       Restangular.one('node', id).get().


use bsStyle properly

use bsStyle properly

--- src/components/task-plan/homework.cjsx
+++ src/components/task-plan/homework.cjsx
@@ -53,13 +53,13 @@
   renderHeader: ->
     unless @props.index is 0
       moveUp =
-        <BS.Button bsStyle="xs" onClick={@moveExerciseUp} className="-move-exercise-up">
+        <BS.Button onClick={@moveExerciseUp} className="btn-xs -move-exercise-up">
           <i className="fa fa-arrow-up"/>
         </BS.Button>

     # TODO: Add conditional logic for displaying this button
     moveDown =
-      <BS.Button bsStyle="xs" onClick={@moveExerciseDown} className="-move-exercise-down">
+      <BS.Button onClick={@moveExerciseDown} className="btn-xs -move-exercise-down">
         <i className="fa fa-arrow-down"/>
       </BS.Button>

@@ -68,7 +68,7 @@
       <span className="pull-right card-actions">
         {moveUp}
         {moveDown}
-        <BS.Button bsStyle="xs" onClick={@removeExercise} className="-remove-exercise"><i className="fa fa-close"/></BS.Button>
+        <BS.Button onClick={@removeExercise} className="btn-xs -remove-exercise"><i className="fa fa-close"/></BS.Button>
       </span>
     </span>


add auth to ipinfo for pings

add auth to ipinfo for pings

--- server/ping.coffee
+++ server/ping.coffee
@@ -18,7 +18,7 @@
       data.ip = this.request.headers['x-real-ip']
       data.forwarded = this.request.headers['x-forwarded-for']
       try
-        res = HTTP.call 'GET', 'http://ipinfo.io/' + data.ip
+        res = HTTP.call 'GET', 'http://ipinfo.io/' + data.ip + (if API.settings?.use?.ipinfo?.token? then '?token=' + API.settings.use.ipinfo.token else '')
         info = JSON.parse res.content
         data[k] = info[k] for k of info
         if data.loc


First pass at workflow for authorization cookie expiration during editing.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -167,7 +167,7 @@
   $('html, body').animate({scrollTop: $(document).height()-$(window).height()},600,'linear')

 # save collection form values to localStorage
-save_collection_form = ->
+@save_collection_form = save_collection_form = ->
   collection = $('#collection_select').val()
   localStorage[collection] = true
   for child in $('#collection_form').children()
@@ -281,7 +281,7 @@
   properties = $(collection).find('citeProperty')
   add_property_to_form(property,form) for property in properties

-  submit_button = $('<input>').attr('type','button').attr('value','Submit').attr('class','btn btn-primary')
+  submit_button = $('<input>').attr('type','button').attr('value','Submit').attr('class','btn btn-primary').attr('id','submit_button')
   submit_button.bind 'click', (event) =>
     submit_collection_form()
   save_button = $('<input>').attr('type','button').attr('value','Save').attr('class','btn')
@@ -325,6 +325,14 @@

   # set textareas to autosize
   $('textarea').autosize()
+
+  # set an OAuth expiration callback
+  if get_cookie 'access_token_expires_at'
+    authorization_expires_in = parseInt(get_cookie('access_token_expires_at')) - Date.now()
+    console.log "Disabling submit in #{authorization_expires_in}ms"
+    setTimeout ->
+      disable_submit()
+    , authorization_expires_in

 # set the author name using Google profile information
 set_author_name = (callback) ->
@@ -371,11 +379,14 @@
   history.replaceState(null,'',window.location.href.replace("#{location.hash}",hash_string))
   return params

-set_cookie = (key, value, expires_in) ->
+expires_in_to_date = (expires_in) ->
   cookie_expires = new Date
   cookie_expires.setTime(cookie_expires.getTime() + expires_in * 1000)
+  return cookie_expires
+
+set_cookie = (key, value, expires_in) ->
   cookie = "#{key}=#{value}; "
-  cookie += "expires=#{cookie_expires.toGMTString()}; "
+  cookie += "expires=#{expires_in_to_date(expires_in).toUTCString()}; "
   cookie += "path=#{window.location.pathname.substring(0,window.location.pathname.lastIndexOf('/')+1)}"
   document.cookie = cookie

@@ -404,6 +415,7 @@
         console.log "Access Token Validation Error: #{textStatus}"
       success: (data) ->
         set_cookie('access_token',params['access_token'],params['expires_in'])
+        set_cookie('access_token_expires_at',expires_in_to_date(params['expires_in']).getTime(),params['expires_in'])
         $('#collection_select').change()
       complete: (jqXHR, textStatus) ->
         callback() if callback?
@@ -438,6 +450,13 @@
   else
     window.location.href + new_hash
   history.pushState(null,$(selected).text(),new_url)
+
+# disable the submit button with an informative dialogue if the cookie expires while editing
+disable_submit = ->
+  $('#submit_button').prop('disabled',true)
+  $('#submit_button').before $('<div>').attr('class','alert alert-warning').attr('id','oauth_expiration_warning').append('Your Google Fusion Tables authorization has expired. ')
+  $('#oauth_expiration_warning').append $('<a>').attr('href',google_oauth_url()).append('Click here to save your work and re-authorize.').attr('onclick','save_collection_form()')
+  $('#oauth_expiration_warning').append(' You will be able to submit your work upon return.')

 build_collection_editor_from_capabilities = (capabilities_url) ->
   $.ajax capabilities_url,
@@ -484,7 +503,7 @@
   cite_collection_editor_config = $.extend({}, default_cite_collection_editor_config, window.cite_collection_editor_config)
   google_oauth_parameters_for_fusion_tables['client_id'] = cite_collection_editor_config['google_client_id']

-  if location.hash.substring(1).length
+  if location.hash.substring(1).length && !(parse_query_string()['state'])
      console.log "Setting OAuth URL parameter state: #{location.hash.substring(1)}"
      google_oauth_parameters_for_fusion_tables['state'] = location.hash.substring(1)


Start building nodes via document.createElement

--- src/lines-tile-component.coffee
+++ src/lines-tile-component.coffee
@@ -118,24 +118,30 @@
     {width} = @newState
     {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]

-    classes = ''
+    lineNode = document.createElement("div")
+    lineNode.classList.add("line")
     if decorationClasses?
       for decorationClass in decorationClasses
-        classes += decorationClass + ' '
-    classes += 'line'
-
-    lineHTML = "
"
+        lineNode.classList.add(decorationClass)
+
+    lineNode.style.position = "absolute"
+    lineNode.style.top = top + "px"
+    lineNode.style.width = width + "px"
+    lineNode.dataset.screenRow = screenRow

     if text is ""
-      lineHTML += @buildEmptyLineInnerHTML(id)
+      @appendEmptyLineInnerNodes(id, lineNode)
     else
-      lineHTML += @buildLineInnerHTML(id)
-
-    lineHTML += '' if fold
-    lineHTML += "
"
-    lineHTML
-
-  buildEmptyLineInnerHTML: (id) ->
+      @appendLineInnerNodes(id, lineNode)
+
+    if fold
+      foldMarker = document.createElement("span")
+      foldMarker.classList.add("fold-marker")
+      lineNode.appendChild(foldMarker)
+
+    lineNode.outerHTML
+
+  appendEmptyLineInnerNodes: (id, lineNode) ->
     {indentGuidesVisible} = @newState
     {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]

@@ -143,35 +149,46 @@
       invisibleIndex = 0
       lineHTML = ''
       for i in [0...indentLevel]
-        lineHTML += ""
+        indentGuide = document.createElement("span")
+        indentGuide.classList.add("indent-guide")
         for j in [0...tabLength]
           if invisible = endOfLineInvisibles?[invisibleIndex++]
-            lineHTML += "#{invisible}"
+            invisibleCharacter = document.createElement("span")
+            invisibleCharacter.classList.add("invisible-character")
+            invisibleCharacter.textContent = invisible
+            indentGuide.appendChild(invisibleCharacter)
           else
-            lineHTML += ' '
-        lineHTML += ""
+            indentGuide.insertAdjacentText("beforeend", " ")
+        lineNode.appendChild(indentGuide)

       while invisibleIndex < endOfLineInvisibles?.length
-        lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}"
-
-      lineHTML
+        invisibleCharacter = document.createElement("span")
+        invisibleCharacter.classList.add("invisible-character")
+        invisibleCharacter.textContent = endOfLineInvisibles[invisibleIndex++]
+        lineNode.appendChild(invisibleCharacter)
     else
-      @buildEndOfLineHTML(id) or ' '
-
-  buildLineInnerHTML: (id) ->
+      unless @appendEndOfLineNodes(id, lineNode)
+        lineNode.insertAdjacentHTML("beforeend", " ")
+
+  appendLineInnerNodes: (id, lineNode) ->
     lineState = @newTileState.lines[id]
     {firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
     lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0

     innerHTML = ""
     @tokenIterator.reset(lineState)
+    openScopeNode = lineNode

     while @tokenIterator.next()
       for scope in @tokenIterator.getScopeEnds()
-        innerHTML += ""
+        openScopeNode = openScopeNode.parentElement

       for scope in @tokenIterator.getScopeStarts()
-        innerHTML += ""
+        newScopeNode = document.createElement("span")
+        newScopeNode.className = scope.replace(/\.+/g, ' ')
+        openScopeNode.appendChild(newScopeNode)
+
+        openScopeNode = newScopeNode

       tokenStart = @tokenIterator.getScreenStart()
       tokenEnd = @tokenIterator.getScreenEnd()
@@ -196,87 +213,80 @@
         (invisibles?.tab and isHardTab) or
           (invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))

-      innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
-
-    for scope in @tokenIterator.getScopeEnds()
-      innerHTML += ""
-
-    for scope in @tokenIterator.getScopes()
-      innerHTML += ""
-
-    innerHTML += @buildEndOfLineHTML(id)
-    innerHTML
-
-  buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
+      @appendToken(openScopeNode, tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
+
+    @appendEndOfLineNodes(id, lineNode)
+
+  appendToken: (scopeNode, tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
     if isHardTab
-      classes = 'hard-tab'
-      classes += ' leading-whitespace' if firstNonWhitespaceIndex?
-      classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
-      classes += ' indent-guide' if hasIndentGuide
-      classes += ' invisible-character' if hasInvisibleCharacters
-      return "#{@escapeTokenText(tokenText)}"
+      hardTab = document.createElement("span")
+      hardTab.classList.add("hard-tab")
+      hardTab.classList.add("leading-whitespace") if firstNonWhitespaceIndex?
+      hardTab.classList.add("trailing-whitespace") if firstTrailingWhitespaceIndex?
+      hardTab.classList.add("indent-guide") if hasIndentGuide
+      hardTab.classList.add("invisible-character") if hasInvisibleCharacters
+      hardTab.textContent = tokenText
+
+      scopeNode.appendChild(hardTab)
     else
       startIndex = 0
       endIndex = tokenText.length

-      leadingHtml = ''
-      trailingHtml = ''
+      leadingWhitespaceNode = null
+      trailingWhitespaceNode = null

       if firstNonWhitespaceIndex?
-        leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)
-
-        classes = 'leading-whitespace'
-        classes += ' indent-guide' if hasIndentGuide
-        classes += ' invisible-character' if hasInvisibleCharacters
-
-        leadingHtml = "#{leadingWhitespace}"
+        leadingWhitespaceNode = document.createElement("span")
+        leadingWhitespaceNode.classList.add("leading-whitespace")
+        leadingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide
+        leadingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
+        leadingWhitespaceNode.textContent = tokenText.substring(0, firstNonWhitespaceIndex)
+
         startIndex = firstNonWhitespaceIndex

       if firstTrailingWhitespaceIndex?
         tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
-        trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)
-
-        classes = 'trailing-whitespace'
-        classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
-        classes += ' invisible-character' if hasInvisibleCharacters
-
-        trailingHtml = "#{trailingWhitespace}"
+
+        trailingWhitespaceNode = document.createElement("span")
+        trailingWhitespaceNode.classList.add("trailing-whitespace")
+        trailingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
+        trailingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
+        trailingWhitespaceNode.textContent = tokenText.substring(firstTrailingWhitespaceIndex)

         endIndex = firstTrailingWhitespaceIndex

-      html = leadingHtml
+      scopeNode.appendChild(leadingWhitespaceNode) if leadingWhitespaceNode?
+
       if tokenText.length > MaxTokenLength
         while startIndex < endIndex
-          html += "" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + ""
+          tokenNode = document.createElement("span")
+          tokenNode.textContent = @sliceText(tokenText, startIndex, startIndex + MaxTokenLength)
+          scopeNode.appendChild(tokenNode)
           startIndex += MaxTokenLength
       else
-        html += @escapeTokenText(tokenText, startIndex, endIndex)
-
-      html += trailingHtml
-    html
-
-  escapeTokenText: (tokenText, startIndex, endIndex) ->
+        scopeNode.insertAdjacentText("beforeend", @sliceText(tokenText, startIndex, endIndex))
+
+      scopeNode.appendChild(trailingWhitespaceNode) if trailingWhitespaceNode?
+
+  sliceText: (tokenText, startIndex, endIndex) ->
     if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
       tokenText = tokenText.slice(startIndex, endIndex)
-    tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)
-
-  escapeTokenTextReplace: (match) ->
-    switch match
-      when '&' then '&'
-      when '"' then '"'
-      when "'" then '''
-      when '<' then '<'
-      when '>' then '>'
-      else match
-
-  buildEndOfLineHTML: (id) ->
+    tokenText
+
+  appendEndOfLineNodes: (id, lineNode) ->
     {endOfLineInvisibles} = @newTileState.lines[id]

-    html = ''
+    hasInvisibles = false
     if endOfLineInvisibles?
       for invisible in endOfLineInvisibles
-        html += "#{invisible}"
-    html
+        hasInvisibles = true
+
+        invisibleCharacter = document.createElement("span")
+        invisibleCharacter.classList.add("invisible-character")
+        invisibleCharacter.textContent = invisible
+        lineNode.appendChild(invisibleCharacter)
+
+    hasInvisibles

   updateLineNode: (id) ->
     oldLineState = @oldTileState.lines[id]


shutdown stops camera

shutdown stops camera

--- components/GetUserMedia.coffee
+++ components/GetUserMedia.coffee
@@ -78,4 +78,8 @@
       return
     throw new Error msg

+  shutdown: ->
+    @stopStream()
+
+
 exports.getComponent = -> new GetUserMedia


Set default value for fieldset type option

--- components/fieldset/index.cjsx
+++ components/fieldset/index.cjsx
@@ -39,6 +39,12 @@
     if attributes = next_props.attributes
       @setState attributes: attributes, type: _determineType attributes
       @setValue next_props.value or @props.value
+
+  componentDidUpdate: ->
+    if @state.type is TYPE.RADIO
+      no_active = true
+      no_active = false for key, ref of @refs when ref.getValue?() is true
+      @refs[default].setValue true if no_active and default = Object.keys(@refs)?[0]

   # -- Events
   onChange: (event) ->


Update Spellbook.headingLinks.slugify

Update Spellbook.headingLinks.slugify


test position

test position

--- src/layout/test_layout.coffee
+++ src/layout/test_layout.coffee
@@ -55,7 +55,7 @@
     c = size / 2
     transform_result = "translate(#{size},#{size}) rotate(0 #{c} #{c})"

-    test_position(t, [1,1])
+    test_position t, [1,1]
     t.ok sprite.get('x') > 0, 'non-zero x'
     t.ok sprite.get('y') > 0, 'non-zero y'
     t.equal sprite.get('transform'), transform_result , 'transform'
@@ -63,7 +63,10 @@
     t.equal sprite.get('fill'), 'blue', 'fill'

     t.notOk sprite.call('apply', {target: grid}), "only apply to self"
-    t.ok sprite.call('apply', {target: sprite}), "apply self"
+    forward = sprite.get('language')['forward']
+    message = {target: sprite, action: forward}
+    t.ok sprite.call('apply', message), "apply message"
+    test_position t, [1,1]

     t.end()


Render all model outputs as grids.

Render all model outputs as grids.

--- src/ext/components/model-output.coffee
+++ src/ext/components/model-output.coffee
@@ -34,13 +34,6 @@

   switch _model.algo
     when 'glm'
-      if table = _.inspect 'output', _model
-        renderPlot 'Output', _.plot (g) ->
-          g(
-            g.select()
-            g.from table
-          )
-
       if table = _.inspect 'Normalized Coefficient Magnitudes', _model
         renderPlot 'Normalized Coefficient Magnitudes', _.plot (g) ->
           g(
@@ -52,41 +45,6 @@
           )

     when 'deeplearning'
-      if table = _.inspect 'Status of Neuron Layers', _model
-        renderPlot 'Status of Neuron Layers', _.plot (g) ->
-          g(
-            g.select()
-            g.from table
-          )
-
-      if table = _.inspect 'Training Metrics', _model
-        renderPlot 'Training Metrics', _.plot (g) ->
-          g(
-            g.select()
-            g.from table
-          )
-
-      if table = _.inspect 'Training Confusion Matrix', _model
-        renderPlot 'Training Confusion Matrix', _.plot (g) ->
-          g(
-            g.select()
-            g.from table
-          )
-
-      if table = _.inspect 'Validation Metrics', _model
-        renderPlot 'Validation Metrics', _.plot (g) ->
-          g(
-            g.select()
-            g.from table
-          )
-
-      if table = _.inspect 'Validation Confusion Matrix', _model
-        renderPlot 'Validation Confusion Matrix', _.plot (g) ->
-          g(
-            g.select()
-            g.from table
-          )
-
       if table = _.inspect 'Variable Importances', _model
         renderPlot 'Variable Importances', _.plot (g) ->
           g(
@@ -97,45 +55,38 @@
             g.limit 25
           )

+    when 'gbm', 'drf'
       if table = _.inspect 'Scoring History', _model
-        renderPlot 'Scoring History', _.plot (g) ->
-          g(
-            g.select()
-            g.from table
-          )
-
-    when 'gbm', 'drf'
-      if table = _.inspect 'output', _model
-        if table.schema.mse_valid
-          renderPlot 'Output', _.plot (g) ->
+        if table.schema['Validation MSE']
+          renderPlot 'Scoring History', _.plot (g) ->
             g(
               g.path(
-                g.position 'tree', 'mse_train'
+                g.position 'Number of Trees', 'Training MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.path(
-                g.position 'tree', 'mse_valid'
+                g.position 'Number of Trees', 'Validation MSE'
                 g.strokeColor g.value '#ff7f0e'
               )
               g.point(
-                g.position 'tree', 'mse_train'
+                g.position 'Number of Trees', 'Training MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.point(
-                g.position 'tree', 'mse_valid'
+                g.position 'Number of Trees', 'Validation MSE'
                 g.strokeColor g.value '#ff7f0e'
               )
               g.from table
             )
         else
-          renderPlot 'Output', _.plot (g) ->
+          renderPlot 'Scoring History', _.plot (g) ->
             g(
               g.path(
-                g.position 'tree', 'mse_train'
+                g.position 'Number of Trees', 'Training MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.point(
-                g.position 'tree', 'mse_train'
+                g.position 'Number of Trees', 'Training MSE'
                 g.strokeColor g.value '#1f77b4'
               )
               g.from table
@@ -150,6 +101,21 @@
             )
             g.from table
             g.limit 25
+          )
+
+  for tableName in _.ls _model when tableName isnt 'parameters'
+    if table = _.inspect tableName, _model
+      if table.indices.length > 1
+        renderPlot tableName, _.plot (g) ->
+          g(
+            g.select()
+            g.from table
+          )
+      else
+        renderPlot tableName, _.plot (g) ->
+          g(
+            g.select 0
+            g.from table
           )

   toggle = ->
@@ -173,7 +139,7 @@
   defer _go

   key: _model.key
-  algo: _model.algo
+  algo: _model.algo_full_name
   plots: _plots
   inputParameters: _inputParameters
   isExpanded: _isExpanded


FIX: Correct scope access in task update

--- src/app/tasks/task-definition-editor/task-definition-editor.coffee
+++ src/app/tasks/task-definition-editor/task-definition-editor.coffee
@@ -210,7 +210,7 @@
                 alertService.add("danger", "Error: " + response.data.error, 6000)
           )
         else
-          TaskDefinition.update( { unit_id: @scope.unit.id, id: task.id, task_def: task } ).$promise.then (
+          TaskDefinition.update( { unit_id: $scope.unit.id, id: task.id, task_def: task } ).$promise.then (
             (response) ->
               populate_task($scope.task, response)
               alertService.add("success", "#{response.name} Updated", 2000)


Refactoring and prepare to created and updated.

--- app/client/js/users/store.coffee
+++ app/client/js/users/store.coffee
@@ -13,6 +13,18 @@
   constructor: ->
     super 'user'
     @setEmpty()
+
+  # ###*
+  #   TODO(steida): If ok, move to este.labs.Store.
+  #   @type {?number}
+  # ###
+  # created: null
+  #
+  # ###*
+  #   TODO(steida): If ok, move to este.labs.Store.
+  #   @type {?number}
+  # ###
+  # updated: null

   ###*
     @type {Array.<app.songs.Song>}
@@ -92,18 +104,22 @@
     @override
   ###
   toJson: ->
+    # created: @created
     newSong: @newSong
     songs: @asObject @songs
+    # updated: @updated
     user: @getJsonUser @user

   ###*
     @override
   ###
   fromJson: (json) ->
+    # @created = json.created
     @newSong = @instanceFromJson app.songs.Song, json.newSong
-    @user = @getJsonUser json.user
     # NOTE(steida): '|| []'' because JSON stringify and parse ignores empty array.
     @songs = @asArray(json.songs || []).map @instanceFromJson app.songs.Song
+    @user = @getJsonUser json.user
+    # @updated = json.updated

   # PATTERN(steida): Use only server unique props, because user is going to be
   # saved into localStorage, which is shared across browser tabs/windows.
@@ -124,22 +140,22 @@
     localUserStoreJson = @toJson()
     @mergeSongs localUserStoreJson, serverUserStoreJson
     @fromJson localUserStoreJson
-    console.log 'updateFromServer notify'
-    @notify()
+    console.log 'store serverNotify'
+    @serverNotify()

   ###*
     @param {Object} localUserStoreJson
     @param {Object} serverUserStoreJson
   ###
   mergeSongs: (localUserStoreJson, serverUserStoreJson) ->
-    # serverUserStoreJson can be null.
+    # serverUserStoreJson can be null for new user.
     return if !serverUserStoreJson
     serverSongs = serverUserStoreJson.songs
     return if !serverSongs
-    for id, serverSong of serverSongs
-      localSong = localUserStoreJson.songs[id]
+    for serverSongId, serverSong of serverSongs
+      localSong = localUserStoreJson.songs[serverSongId]
       if !localSong
-        localUserStoreJson.songs[id] = serverSong
+        localUserStoreJson.songs[serverSongId] = serverSong
         continue
       @mergeSong localSong, serverSong
     return
@@ -149,9 +165,9 @@
     @param {Object} serverSong
   ###
   mergeSong: (localSong, serverSong) ->
-    # localSong.name = serverSong.name
-    # localSong.artist = serverSong.artist
-    # localSong.lyrics = serverSong.lyrics
+    localSong.name = serverSong.name
+    localSong.artist = serverSong.artist
+    localSong.lyrics = serverSong.lyrics

   ###*
     PATTERN(steida): Logout deletes all user data in memory and in local storage,


pan by drag boundaries

pan by drag boundaries

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -33,11 +33,11 @@
       height: 0
     }
     panEnabled: false
-    keyPanZoomEnabled: false
+    zooming: false

   componentDidMount: ->
     @refs.videoScrubber?.value = 0
-    addEventListener "keydown", @frameKeyPan
+    addEventListener "keypress", @frameKeyPan

   componentDidUpdate: ->
     @refs.videoPlayer?.playbackRate = @state.playbackRate
@@ -50,7 +50,7 @@
     frameDisplay = switch type
       when 'image'

-
+

           {if @state.loading

@@ -103,17 +103,17 @@

-                <button title={"draw"} className={"fa fa-mouse-pointer"} title={"annotate"} onClick={@togglePan}/>
+                <button title={"draw"} className={"fa fa-mouse-pointer"} title={"annotate"} onClick={@togglePanOff}/>

-                <button title={"pan"} className={"fa fa-arrows"} title={"pan"} onClick={@togglePan}/>
+                <button title={"pan"} className={"fa fa-arrows"} title={"pan"} onClick={@togglePanOn}/>

               <button title={"zoom out"} className="zoom-out fa fa-minus" onClick={ @zoom.bind(this, 1.1 ) } />

-              <button title={"zoom in"} className="zoom-in fa fa-plus" onClick={ @zoom.bind(this, .9) } />
+              <button title={"zoom in"} className="zoom-in fa fa-plus" onMouseDown={@zoom.bind(this, .9)} onMouseUp={@toggleZoom} />

               <button title={"rest zoom levels"} className="reset fa fa-refresh" onClick={ this.zoomReset } ></button>
@@ -195,6 +195,7 @@

     if (newNaturalWidth > @state.frameDimensions.width) || (newNaturalHeight * change > @state.frameDimensions.height)
       @zoomReset()
+
     else
       @setState
         viewBoxDimensions:
@@ -202,6 +203,12 @@
           height: newNaturalHeight,
           x: newNaturalX,
           y: newNaturalY
+        , =>
+          console.log "Pressed Zoom"
+          # setTimeout(function(){ debugger; }, 3000);
+          # setTimeout console.log "time", 0000
+          # debugger
+

   zoomReset: ->
     @setState
@@ -211,35 +218,40 @@
         x: 0,
         y: 0

-  enableKeyPan: ->
-    @setState
-      keyPanZoomEnabled: true
-
-  disableKeyPan: ->
-    @setState
-      keyPanZoomEnabled: false
-
-
-  togglePan: ->
-    @setState panEnabled: !@state.panEnabled, =>
-      @toggleKeyPanZoom()
-
+
+  toggleZoom: ->
+    console.log "toggleZoom"
+    @setState zooming: !@state.zooming
+
+  togglePanOn: ->
+    unless @state.panEnabled == true
+      @setState panEnabled: true, =>
+        if @state.panEnabled then this.refs.subjectImage.focus()
+
+  togglePanOff: ->
+    @setState panEnabled: false
+
   toggleKeyPanZoom: ->
     @setState keyPanZoomEnabled: !@state.keyPanZoomEnabled, =>
       if @state.panEnabled then this.refs.subjectImage.focus()

   panByDrag: (e, d) ->
     return if @state.panEnabled == false
-
-    @setState
-      viewBoxDimensions:
-        x: @state.viewBoxDimensions.x -= d.x
-        y: @state.viewBoxDimensions.y -= d.y
+    minX = @state.frameDimensions.width - @state.viewBoxDimensions.width
+    minY = @state.frameDimensions.height - @state.viewBoxDimensions.height
+
+    @setState
+      viewBoxDimensions:
+        x: Math.max(0, Math.min(@state.viewBoxDimensions.x -= d.x, minX))
+        y: Math.max(0, Math.min(@state.viewBoxDimensions.y -= d.y, minY))
         width: @state.viewBoxDimensions.width
         height: @state.viewBoxDimensions.height

   frameKeyPan: (e)->
-    return if @state.keyPanZoomEnabled == false
+    console.log "frameKeyPan()"
+    console.log "---> keypress", e.which
+    console.log "---> @state.panEnabled", @state.panEnabled
+    return if @state.panEnabled == false
     keypress = e.which
     switch keypress
       # left
@@ -259,11 +271,12 @@
         e.preventDefault()
         @panVertical(20)
       # zoom out
-      when 187
+      when 61 #187
         e.preventDefault()
         @zoom(.9)
       # zoom in
-      when 189
+      when 45 #189
+        @setState zooming: true
         e.preventDefault()
         @zoom(1.1)

@@ -272,7 +285,7 @@
     minX = @state.frameDimensions.width - @state.viewBoxDimensions.width
     @setState
       viewBoxDimensions:
-        x: Math.max(0, Math.min @state.viewBoxDimensions.x + direction, minX)
+        x: Math.max(0, Math.min(@state.viewBoxDimensions.x + direction, minX))
         y: @state.viewBoxDimensions.y
         width: @state.viewBoxDimensions.width
         height: @state.viewBoxDimensions.height
@@ -282,6 +295,6 @@
     @setState
       viewBoxDimensions:
         x: @state.viewBoxDimensions.x
-        y: Math.max 0, Math.min(@state.viewBoxDimensions.y + direction, minY)
+        y: Math.max(0, Math.min(@state.viewBoxDimensions.y + direction, minY))
         width: @state.viewBoxDimensions.width
         height: @state.viewBoxDimensions.height


file remaked

file remaked

--- scripts/coffee/field-view.coffee
+++ scripts/coffee/field-view.coffee
@@ -1,29 +0,0 @@
-# Class for DOM manipulations
-class FieldView
-	constructor: (@size) ->
-		do @initCells
-
-	initCells: ->
-		#generate cells
-		@cells = []
-		for y in [0...@size.y]
-			for x in [0...@size.x]
-				@cells.push new Cell({y: y, x: x, status: 0})
-
-	getCell: ->
-		#work with one or two arguments
-		return if arguments.length > 2 || arguments.length == 0
-		position = switch arguments.length
-			when 1 then arguments[0]
-			when 2 then { x: arguments[0], y: arguments[1] }
-
-		return if position.x > @size.x || position.y > @size.y
-		@cells[(position.y * @size.x) + position.x]
-
-	setCellStatus: (cell, status) ->
-		cell = @getCell(cell)
-		cell.status = status
-		do cell.refreshCellView
-
-	refreshField: ->
-		cell.refreshCellView() for cell in @cells


Revert "remove isnumeric"

Revert "remove isnumeric"

This reverts commit c7a9a8996135aa180c3f10413fe3ac5a4a07c411.

--- framer/Color.coffee
+++ framer/Color.coffee
@@ -184,14 +184,14 @@

 			else if color.hasOwnProperty("h") or color.hasOwnProperty("s")

-				h = if _.isNumber(color.h) then color.h else 0
-				s = if _.isNumber(color.s) then convertToPercentage(color.s) else 1
+				h = if isNumeric(color.h) then color.h else 0
+				s = if isNumeric(color.s) then convertToPercentage(color.s) else 1

 				if color.hasOwnProperty("v")
-					v = if _.isNumber(color.v) then convertToPercentage(color.v) else 0
+					v = if isNumeric(color.v) then convertToPercentage(color.v) else 0
 					rgb = hsvToRgb(h, s, v)
 				else
-					l = if _.isNumber(color.l) then convertToPercentage(color.l) else 50
+					l = if isNumeric(color.l) then convertToPercentage(color.l) else 50
 					rgb = hslToRgb(h, s, l)

 				ok = true
@@ -214,9 +214,9 @@
 # Conversion Functions
 # RGB to RGB
 rgbToRgb = (r, g, b) ->
-	r: if _.isNumber(r) then bound01(r, 255) * 255 else 0
-	g: if _.isNumber(g) then bound01(g, 255) * 255 else 0
-	b: if _.isNumber(b) then bound01(b, 255) * 255 else 0
+	r: if isNumeric(r) then bound01(r, 255) * 255 else 0
+	g: if isNumeric(g) then bound01(g, 255) * 255 else 0
+	b: if isNumeric(b) then bound01(b, 255) * 255 else 0

 # RGB to HEX
 rgbToHex = (r, g, b, allow3Char) ->
@@ -389,6 +389,9 @@
 	hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
 	}

+isNumeric = (value) ->
+	return !isNaN(value) && isFinite(value)
+
 stringToObject = (color) ->
 	trimLeft = /^[\s,#]+/
 	trimRight = /\s+$/


Reword invalid event name error

Reword invalid event name error

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -956,7 +956,7 @@
 		@_addListener(eventName, listener)

 	addListener: (eventName, listener) =>
-		throw Error("Layer.on needs an event name") unless eventName
+		throw Error("Layer.on needs a valid event name") unless eventName
 		throw Error("Layer.on needs an event listener") unless listener
 		super(eventName, listener)
 		@_addListener(eventName, listener)


[COVERAGE] Make assign.render() use tag.render()

--- src/liquid/tags/assign.coffee
+++ src/liquid/tags/assign.coffee
@@ -20,4 +20,4 @@

   render: (context) ->
     context.lastScope()[@to] = context.get(@from)
-    ''
+    super context


start pixi

start pixi

--- scrolling/game-controller.coffee
+++ scrolling/game-controller.coffee
@@ -1,4 +1,7 @@
 window.GameController = class GameController
     constructor: (@viewport) ->
+        @stage = new PIXI.Stage 0xf5f5f5
+        @renderer = new PIXI.WebGLRenderer 1024, 576

     start: ->
+        @viewport.append @renderer.view


limit autocompletion suggestions to 5

limit autocompletion suggestions to 5

--- search_index/search_index.coffee
+++ search_index/search_index.coffee
@@ -59,7 +59,7 @@

 # Make yourself available via the event bus
 eb.registerHandler 'reitti.searchIndex.find', ({query}, replier) ->
-  replier {results: trie.find(query.toLowerCase(), 10)}
+  replier {results: trie.find(query.toLowerCase(), 5)}

 # Index all the resource files into the trie
 files =


fix for #472: usd supply on the market page shows wrong number (not updated)

--- app/js/controllers/market.coffee
+++ app/js/controllers/market.coffee
@@ -9,6 +9,7 @@
     $scope.account = account = {name: account_name, base_balance: 0.0, quantity_balance: 0.0}
     $scope.avg_price = 0
     $scope.advanced = false
+    $scope.asset_total_supply = 0.0
     current_market = null
     price_decimals = 4

@@ -41,6 +42,8 @@
         frequency: "each_block"
         update: (data, deferred) ->
             changed = false
+            Blockchain.get_asset($scope.actual_market.base_asset.id).then (asset) ->
+                $scope.asset_total_supply = asset.current_share_supply / asset.precision
             promise = WalletAPI.account_balance(account_name)
             promise.then (result) =>
                 #console.log "------ account_balances_observer result ------>", result
@@ -115,9 +118,6 @@
             account.quantity_balance = data[market.asset_quantity_symbol] / market.quantity_precision
             account.short_balance = if market.inverted then account.base_balance else account.quantity_balance
         Observer.registerObserver(account_balances_observer)
-
-        Blockchain.get_asset($scope.actual_market.base_asset.id).then (asset) ->
-            $scope.asset_total_supply = asset.current_share_supply / asset.precision
         Blockchain.get_info().then (config) ->
             $scope.blockchain_symbol = config.symbol #XTS or BTS
             WalletAPI.get_transaction_fee($scope.blockchain_symbol).then (blockchain_tx_fee) ->


update tester discription (#4215)

update tester discription (#4215)

add in a line about testers having access to inactive workflows.

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -32,7 +32,7 @@
     description: 'Moderators have extra privileges in the community discussion area to moderate discussions. They will also be marked as moderators on "Talk".'
   tester:
     label: 'Tester'
-    description: 'Testers can view and classify on your project to give feedback while it’s still private. They cannot access the project builder.'
+    description: 'Testers can view and classify on your project to give feedback while it’s still private. If given the direct url, they can also view and classify on inactive workflows; this is useful for already launched projects that are planning on building a new workflow and woud like volunteer feedback. Testers cannot access the project builder.'
   translator:
     label: 'Translator'
     description: 'Translators will have access to the translation site.'


test now running on chrome+firefox

test now running on chrome+firefox

--- test/unit/wd-by-method-test.coffee
+++ test/unit/wd-by-method-test.coffee
@@ -2,309 +2,311 @@
 should = require 'should'
 CoffeeScript = require 'coffee-script'

-sleep = (ms) ->
-  fiber = Fiber.current
-  setTimeout (->
-    fiber.run()
-  ), ms
-  `yield()`
+test = (browserName) ->
+
+  browser = null;
+  WdWrap = WdWrap with: (-> browser)
+  capabilities = null
+
+  it "wd.remote", (done) ->
+    browser = wd.remote(mode:'sync')
+    Wd = Wd with:browser
+    done()
+
+  it "init", WdWrap ->
+    @init browserName: browserName
+
+  it "capabilities", WdWrap ->
+    capabilities = @capabilities()
+    should.exist capabilities
+    should.exist capabilities.browserName
+    should.exist capabilities.platform
+
+  it "get", WdWrap ->
+    @get "file://#{__dirname}/assets/test-page.html"
+
+  it "refresh", WdWrap ->
+    @refresh()
+
+  it "eval", WdWrap ->
+    (@eval "1+2").should.equal 3
+    (@eval "document.title").should.equal "TEST PAGE"
+    (@eval "$('#eval').length").should.equal 1
+    (@eval "$('#eval li').length").should.equal 2
+
+  it "execute", WdWrap ->
+    @execute "window.wd_sync_execute_test = 'It worked!'"
+    (@eval "window.wd_sync_execute_test").should.equal 'It worked!'
+
+  # @todo implement async script timeout in wd
+  it "executeAsync (async mode)", (done) ->
+    scriptAsCoffee =
+      """
+        [args...,done] = arguments
+        done "OK"
+      """
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    browser.executeAsync scriptAsJs, (err,res) ->
+      res.should.equal "OK"
+      done()
+
+  it "executeAsync (sync mode)", WdWrap ->
+    scriptAsCoffee =
+      """
+        [args...,done] = arguments
+        done "OK"
+      """
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    res = @executeAsync scriptAsJs
+    res.should.equal "OK"
+
+  it "setWaitTimeout", WdWrap ->
+    @setWaitTimeout 0
+    scriptAsCoffee =
+      '''
+        jQuery ->
+          setTimeout ->
+            $('#setWaitTimeout').html '
a child
'
+          , 1000
+      '''
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    @execute scriptAsJs
+    should.not.exist (@elementByCss "#setWaitTimeout .child")
+    @setWaitTimeout 2000
+    should.exist (@elementByCss "#setWaitTimeout .child")
+    @setWaitTimeout 0
+
+  it "element", WdWrap ->
+    should.exist (@element "name", "elementByName")
+    should.not.exist (@element "name", "elementByName2")
+
+  it "elementByLinkText", WdWrap ->
+    should.exist (@elementByLinkText "click helloByLinkText")
+    should.not.exist (@elementByLinkText "click helloByLinkText2")
+
+  it "elementById", WdWrap ->
+    should.exist (@elementById "elementById")
+    should.not.exist (@elementById "elementById2")
+
+  it "elementByName", WdWrap ->
+    should.exist (@elementByName "elementByName")
+    should.not.exist (@elementByName "elementByName2")
+
+  it "elementByCss", WdWrap ->
+    should.exist (@elementByCss "#elementByCss")
+    should.not.exist (@elementByCss "#elementByCss2")
+
+  it "getAttribute", WdWrap ->
+    testDiv = @elementById "getAttribute"
+    should.exist testDiv
+    (@getAttribute testDiv, "weather").should.equal "sunny"
+    should.not.exist @getAttribute testDiv, "timezone"
+
+  it "getValue (input)", WdWrap ->
+    inputField = @elementByCss "#getValue input"
+    should.exist (inputField)
+    (@getValue inputField).should.equal "Hello getValueTest!"
+
+  it "getValue (textarea)", WdWrap ->
+    inputField = @elementByCss "#getValue textarea"
+    should.exist (inputField)
+    (@getValue inputField).should.equal "Hello getValueTest2!"
+
+  it "clickElement", WdWrap ->
+    anchor = @elementByCss "#clickElement a"
+    (@text anchor).should.equal "not clicked"
+    scriptAsCoffee =
+      '''
+        jQuery ->
+          a = $('#clickElement a')
+          a.click ->
+            a.html 'clicked'
+      '''
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    @execute scriptAsJs
+    (@text anchor).should.equal "not clicked"
+    @clickElement anchor
+    (@text anchor).should.equal "clicked"
+
+  it "moveTo", WdWrap ->
+    a1 = @elementByCss "#moveTo .a1"
+    a2 = @elementByCss "#moveTo .a2"
+    current = @elementByCss "#moveTo .current"
+    should.exist a1
+    should.exist a2
+    should.exist current
+    (@text current).should.equal ""
+    scriptAsCoffee =
+      '''
+        jQuery ->
+          a1 = $('#moveTo .a1')
+          a2 = $('#moveTo .a2')
+          current = $('#moveTo .current')
+          a1.hover ->
+            current.html 'a1'
+          a2.hover ->
+            current.html 'a2'
+      '''
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    @execute scriptAsJs
+    (@text current).should.equal ""
+    @moveTo a1
+    (@text current).should.equal "a1"
+    @moveTo a2
+    (@text current).should.equal "a2"
+
+  ###
+  @todo waiting for implementation
+  it "scroll", WdWrap ->
+  ###
+
+  it "buttonDown / buttonUp", WdWrap ->
+    a = @elementByCss "#mouseButton a"
+    resDiv = @elementByCss "#mouseButton div"
+    should.exist a
+    should.exist resDiv
+    scriptAsCoffee =
+      '''
+        jQuery ->
+          a = $('#mouseButton a')
+          resDiv = $('#mouseButton div')
+          a.mousedown ->
+            resDiv.html 'button down'
+          a.mouseup ->
+            resDiv.html 'button up'
+      '''
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    @execute scriptAsJs
+    (@text resDiv).should.equal ''
+    @moveTo a
+    @buttonDown()
+    (@text resDiv).should.equal 'button down'
+    @buttonUp()
+    (@text resDiv).should.equal 'button up'
+
+  it "click", WdWrap ->
+    anchor = @elementByCss "#click a"
+    (@text anchor).should.equal "not clicked"
+    scriptAsCoffee =
+      '''
+        jQuery ->
+          a = $('#click a')
+          a.click ->
+            a.html 'clicked'
+      '''
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    @execute scriptAsJs
+    (@text anchor).should.equal "not clicked"
+    @moveTo anchor
+    @click 0
+    (@text anchor).should.equal "clicked"
+    @click()
+
+  it "doubleclick", WdWrap ->
+    anchor = @elementByCss "#doubleclick a"
+    (@text anchor).should.equal "not clicked"
+    scriptAsCoffee =
+      '''
+        jQuery ->
+          a = $('#doubleclick a')
+          a.click ->
+            a.html 'clicked'
+      '''
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    @execute scriptAsJs
+    (@text anchor).should.equal "not clicked"
+    @moveTo anchor
+    @doubleclick 0
+    (@text anchor).should.equal "clicked"
+    @doubleclick()
+
+  it "type", WdWrap ->
+    inputField = @elementByCss "#type input"
+    should.exist (inputField)
+    @type inputField, "Hello World"
+    (@getValue inputField).should.equal "Hello World"
+    @type inputField, "\n" # no effect
+    (@getValue inputField).should.equal "Hello World"
+
+  it "clear", WdWrap ->
+    inputField = @elementByCss "#clear input"
+    should.exist (inputField)
+    (@getValue inputField).should.equal "not cleared"
+    @clear inputField
+    (@getValue inputField).should.equal ""
+
+  it "title", WdWrap ->
+    @title().should.equal "TEST PAGE"
+
+  it "text", WdWrap ->
+    textDiv = @elementByCss "#text"
+    should.exist (textDiv)
+    (@text textDiv).should.include "text content"
+    (@text textDiv).should.not.include "div"
+
+  it "textPresent", WdWrap ->
+    textDiv = @elementByCss "#textPresent"
+    should.exist (textDiv)
+    (@textPresent 'sunny', textDiv).should.be.true
+    (@textPresent 'raining', textDiv).should.be.false
+
+  it "acceptAlert", WdWrap ->
+    a = @elementByCss "#acceptAlert a"
+    should.exist (a)
+    scriptAsCoffee =
+      """
+        a = $('#acceptAlert a')
+        a.click ->
+          alert "coffee is running out"
+      """
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    res = @execute scriptAsJs
+    @clickElement a
+    @acceptAlert()
+
+  it "dismissAlert", WdWrap ->
+    a = @elementByCss "#dismissAlert a"
+    should.exist (a)
+    scriptAsCoffee =
+      """
+        a = $('#dismissAlert a')
+        a.click ->
+          alert "coffee is running out"
+      """
+    scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
+    res = @execute scriptAsJs
+    @clickElement a
+    # known bug on chrome/mac, need to use acceptAlert instead
+    unless (capabilities.platform is 'MAC' and
+      capabilities.browserName is 'chrome')
+        @dismissAlert()
+    else
+      @acceptAlert()
+
+
+  it "active", WdWrap ->
+    i1 = @elementByCss "#active .i1"
+    i2 = @elementByCss "#active .i2"
+    @clickElement i1
+    @active().should.equal i1
+    @clickElement i2
+    @active().should.equal i2
+
+  it "url", WdWrap ->
+    @url().should.include "/test/unit/assets/test-page.html"
+
+  it "close", WdWrap ->
+    @close()
+
+  it "quit", WdWrap ->
+    @quit()

 describe "wd-sync", ->

   describe "method by method tests", ->

-    browser = null;
-    WdWrap = WdWrap with: (-> browser)
-    capabilities = null
-    it "wd.remote", (done) ->
-      browser = wd.remote(mode:'sync')
-      Wd = Wd with:browser
-      done()
-
-    it "init", WdWrap ->
-      @init browserName: "chrome"
-
-    it "capabilities", WdWrap ->
-      capabilities = @capabilities()
-      should.exist capabilities
-      should.exist capabilities.browserName
-      should.exist capabilities.platform
-
-    it "get", WdWrap ->
-      @get "file://#{__dirname}/assets/test-page.html"
-
-    it "refresh", WdWrap ->
-      @refresh()
-
-    it "eval", WdWrap ->
-      (@eval "1+2").should.equal 3
-      (@eval "document.title").should.equal "TEST PAGE"
-      (@eval "$('#eval').length").should.equal 1
-      (@eval "$('#eval li').length").should.equal 2
-
-    it "execute", WdWrap ->
-      @execute "window.wd_sync_execute_test = 'It worked!'"
-      (@eval "window.wd_sync_execute_test").should.equal 'It worked!'
-
-    # @todo implement async script timeout in wd
-    it "executeAsync (async mode)", (done) ->
-      scriptAsCoffee =
-        """
-          [args...,done] = arguments
-          done "OK"
-        """
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      browser.executeAsync scriptAsJs, (err,res) ->
-        res.should.equal "OK"
-        done()
-
-    it "executeAsync (sync mode)", WdWrap ->
-      scriptAsCoffee =
-        """
-          [args...,done] = arguments
-          done "OK"
-        """
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      res = @executeAsync scriptAsJs
-      res.should.equal "OK"
-
-    it "setWaitTimeout", WdWrap ->
-      @setWaitTimeout 0
-      scriptAsCoffee =
-        '''
-          jQuery ->
-            setTimeout ->
-              $('#setWaitTimeout').html '
a child
'
-            , 1000
-        '''
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      @execute scriptAsJs
-      should.not.exist (@elementByCss "#setWaitTimeout .child")
-      @setWaitTimeout 2000
-      should.exist (@elementByCss "#setWaitTimeout .child")
-      @setWaitTimeout 0
-
-    it "element", WdWrap ->
-      should.exist (@element "name", "elementByName")
-      should.not.exist (@element "name", "elementByName2")
-
-    it "elementByLinkText", WdWrap ->
-      should.exist (@elementByLinkText "click helloByLinkText")
-      should.not.exist (@elementByLinkText "click helloByLinkText2")
-
-    it "elementById", WdWrap ->
-      should.exist (@elementById "elementById")
-      should.not.exist (@elementById "elementById2")
-
-    it "elementByName", WdWrap ->
-      should.exist (@elementByName "elementByName")
-      should.not.exist (@elementByName "elementByName2")
-
-    it "elementByCss", WdWrap ->
-      should.exist (@elementByCss "#elementByCss")
-      should.not.exist (@elementByCss "#elementByCss2")
-
-    it "getAttribute", WdWrap ->
-      testDiv = @elementById "getAttribute"
-      should.exist testDiv
-      (@getAttribute testDiv, "weather").should.equal "sunny"
-      should.not.exist @getAttribute testDiv, "timezone"
-
-    it "getValue (input)", WdWrap ->
-      inputField = @elementByCss "#getValue input"
-      should.exist (inputField)
-      (@getValue inputField).should.equal "Hello getValueTest!"
-
-    it "getValue (textarea)", WdWrap ->
-      inputField = @elementByCss "#getValue textarea"
-      should.exist (inputField)
-      (@getValue inputField).should.equal "Hello getValueTest2!"
-
-    it "clickElement", WdWrap ->
-      anchor = @elementByCss "#clickElement a"
-      (@text anchor).should.equal "not clicked"
-      scriptAsCoffee =
-        '''
-          jQuery ->
-            a = $('#clickElement a')
-            a.click ->
-              a.html 'clicked'
-        '''
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      @execute scriptAsJs
-      (@text anchor).should.equal "not clicked"
-      @clickElement anchor
-      (@text anchor).should.equal "clicked"
-
-    it "moveTo", WdWrap ->
-      a1 = @elementByCss "#moveTo .a1"
-      a2 = @elementByCss "#moveTo .a2"
-      current = @elementByCss "#moveTo .current"
-      should.exist a1
-      should.exist a2
-      should.exist current
-      (@text current).should.equal ""
-      scriptAsCoffee =
-        '''
-          jQuery ->
-            a1 = $('#moveTo .a1')
-            a2 = $('#moveTo .a2')
-            current = $('#moveTo .current')
-            a1.hover ->
-              current.html 'a1'
-            a2.hover ->
-              current.html 'a2'
-        '''
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      @execute scriptAsJs
-      (@text current).should.equal ""
-      @moveTo a1
-      (@text current).should.equal "a1"
-      @moveTo a2
-      (@text current).should.equal "a2"
-
-    ###
-    @todo waiting for implementation
-    it "scroll", WdWrap ->
-    ###
-
-    it "buttonDown / buttonUp", WdWrap ->
-      a = @elementByCss "#mouseButton a"
-      resDiv = @elementByCss "#mouseButton div"
-      should.exist a
-      should.exist resDiv
-      scriptAsCoffee =
-        '''
-          jQuery ->
-            a = $('#mouseButton a')
-            resDiv = $('#mouseButton div')
-            a.mousedown ->
-              resDiv.html 'button down'
-            a.mouseup ->
-              resDiv.html 'button up'
-        '''
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      @execute scriptAsJs
-      (@text resDiv).should.equal ''
-      @moveTo a
-      @buttonDown()
-      (@text resDiv).should.equal 'button down'
-      @buttonUp()
-      (@text resDiv).should.equal 'button up'
-
-    it "click", WdWrap ->
-      anchor = @elementByCss "#click a"
-      (@text anchor).should.equal "not clicked"
-      scriptAsCoffee =
-        '''
-          jQuery ->
-            a = $('#click a')
-            a.click ->
-              a.html 'clicked'
-        '''
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      @execute scriptAsJs
-      (@text anchor).should.equal "not clicked"
-      @moveTo anchor
-      @click 0
-      (@text anchor).should.equal "clicked"
-      @click()
-
-    it "doubleclick", WdWrap ->
-      anchor = @elementByCss "#doubleclick a"
-      (@text anchor).should.equal "not clicked"
-      scriptAsCoffee =
-        '''
-          jQuery ->
-            a = $('#doubleclick a')
-            a.click ->
-              a.html 'clicked'
-        '''
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      @execute scriptAsJs
-      (@text anchor).should.equal "not clicked"
-      @moveTo anchor
-      @doubleclick 0
-      (@text anchor).should.equal "clicked"
-      @doubleclick()
-
-    it "type", WdWrap ->
-      inputField = @elementByCss "#type input"
-      should.exist (inputField)
-      @type inputField, "Hello World"
-      (@getValue inputField).should.equal "Hello World"
-      @type inputField, "\n" # no effect
-      (@getValue inputField).should.equal "Hello World"
-
-    it "clear", WdWrap ->
-      inputField = @elementByCss "#clear input"
-      should.exist (inputField)
-      (@getValue inputField).should.equal "not cleared"
-      @clear inputField
-      (@getValue inputField).should.equal ""
-
-    it "title", WdWrap ->
-      @title().should.equal "TEST PAGE"
-
-    it "text", WdWrap ->
-      textDiv = @elementByCss "#text"
-      should.exist (textDiv)
-      (@text textDiv).should.include "text content"
-      (@text textDiv).should.not.include "div"
-
-    it "textPresent", WdWrap ->
-      textDiv = @elementByCss "#textPresent"
-      should.exist (textDiv)
-      (@textPresent 'sunny', textDiv).should.be.true
-      (@textPresent 'raining', textDiv).should.be.false
-
-    it "acceptAlert", WdWrap ->
-      a = @elementByCss "#acceptAlert a"
-      should.exist (a)
-      scriptAsCoffee =
-        """
-          a = $('#acceptAlert a')
-          a.click ->
-            alert "coffee is running out"
-        """
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      res = @execute scriptAsJs
-      @clickElement a
-      @acceptAlert()
-
-    it "dismissAlert", WdWrap ->
-      a = @elementByCss "#dismissAlert a"
-      should.exist (a)
-      scriptAsCoffee =
-        """
-          a = $('#dismissAlert a')
-          a.click ->
-            alert "coffee is running out"
-        """
-      scriptAsJs = CoffeeScript.compile scriptAsCoffee, bare:'on'
-      res = @execute scriptAsJs
-      @clickElement a
-      # known bug on chrome/mac, need to use acceptAlert instead
-      unless (capabilities.platform is 'MAC' and
-        capabilities.browserName is 'chrome')
-          @dismissAlert()
-      else
-        @acceptAlert()
-
-
-    it "active", WdWrap ->
-      i1 = @elementByCss "#active .i1"
-      i2 = @elementByCss "#active .i2"
-      @clickElement i1
-      @active().should.equal i1
-      @clickElement i2
-      @active().should.equal i2
-
-    it "url", WdWrap ->
-      @url().should.include "/test/unit/assets/test-page.html"
-
-    it "close", WdWrap ->
-      @close()
-    ###
-    it "quit", WdWrap ->
-      @quit()
+    describe "using chrome", ->
+      test 'chrome'
+
+    describe "using firefox", ->
+      test 'firefox'


Fixes typo in bond snippet

Fixes typo in bond snippet

--- snippets/bond.cson
+++ snippets/bond.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
-  'Desscribe bond':
+  'Describe bond':
     'prefix': 'bond'
     'body': "# http://inspec.io/docs/reference/resources/bond/\ndescribe bond('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"


Fix Collections.get(cursor)

Fix Collections.get(cursor)

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -32,7 +32,11 @@
       # Collection name.
       global[arg]
     else if @isCursor(arg)
-      arg.collection
+      # Attempt to find Meteor.Collection from LocalCollection.
+      collection = arg.collection
+      if collection
+        collection = @get(Collections.getTitle(collection)) ? collection
+      collection
     else if @isCollection(arg)
       arg
     else


activity: show koding channel on logged out site

--- servers/lib/crawler/index.coffee
+++ servers/lib/crawler/index.coffee
@@ -83,10 +83,29 @@
     feed.createFeed models, options, callback

+fetchAnnouncementContent = (models, options, callback) ->
+
+  {JGroup} = models
+
+  options.channelName = "koding"
+
+  # TODO change this slug after groups are implemented
+  JGroup.one slug: "koding", (err, group) ->
+    return callback err  if err
+    return callback notFoundError "group"  unless group
+
+    options.channelId = group.socialApiAnnouncementChannelId
+    options.route = "Announcement"
+    options.contentType = "post"
+    feed.createFeed models, options, callback
+
+
 fetchContent = (models, options, callback) ->
   {section, entrySlug, client} = options

   switch section
+    when "Announcement"
+      fetchAnnouncementContent models, options, callback
     when "Public"
       fetchGroupContent models, options, callback
     when "Topic"


add initial CLI skeleton

add initial CLI skeleton

--- bin/command.coffee
+++ bin/command.coffee
@@ -1,4 +1,22 @@
-console.log 'nuc! I don\'t do anything, yet.'
+
+# let's tell the system who we are
+process.title = 'nuc'

 # `nuc` uses itself for its own configuration values
-values = require('../lib') id:'nu'#, defaults:require './defaults'
+values = require('../lib') id:'nuc', defaults:require './defaults.json'
+
+program = require 'commander'
+
+program
+  .version (require '../package.json').version
+  # .usage '<command> [options]'
+  .command 'set <key> <value> [scope]', 'Set key/value pair into scope'
+  # .option '' # option to set it only if it doesn't exist yet
+
+  .command 'get <key> [scope]', 'Get value for key in first scope, or specified scope'
+
+  .command 'delete <key> [all]', 'Delete key/value pair'
+
+  .command 'list [scope]', 'List key/value pairs from all scopes, or specified scope' # scope: local, user, system, global
+
+  .parse process.argv


removed test duplication

removed test duplication

--- test/transaction-stream-test.coffee
+++ test/transaction-stream-test.coffee
@@ -4,109 +4,102 @@

 # TODO: start listening *after* subscribing

-it 'subscribes to account and streams it', (done) ->

-  clock = sinon.useFakeTimers();

-  fakeWebSocketInstance =
-    handlers: {}
-    on: sinon.spy (event, callback) ->
-      this.handlers[event] = callback
-    fakeEmit: (event, obj) ->
-      this.handlers[event](obj)
-    send: sinon.stub()
-  fakeWebSocketConstructor = sinon.stub().returns(fakeWebSocketInstance)
+describe 'transaction-stream', ->
+  world = null
+  clock = null

-  testSubject = transactionStream(fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')
+  beforeEach ->
+    world =
+      given:
+        fakeWebSocketConstructor: ->

-  assert(fakeWebSocketConstructor.calledWithNew())
-  assert(fakeWebSocketConstructor.calledWith('ws://live.stellar.org:9001'))
+          world.fakeWebSocketInstance =
+            handlers: {}
+            on: sinon.spy (event, callback) ->
+              this.handlers[event] = callback
+            fakeEmit: (event, obj) ->
+              this.handlers[event](obj)
+            send: sinon.stub()

-  clock.tick 50
+          world.fakeWebSocketConstructor =
+            sinon.stub().returns(world.fakeWebSocketInstance)
+          world

-  fakeWebSocketInstance.fakeEmit 'open'
+    clock = sinon.useFakeTimers()

-  clock.tick 100
+  afterEach -> clock.restore()

-  testSubject.on 'error', -> assert.fail('should not call error')
+  it 'subscribes to account and streams it', (done) ->
+    world.given.fakeWebSocketConstructor()

-  fakeWebSocketInstance.send.yield null
+    testSubject = transactionStream(world.fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')

-  assert(fakeWebSocketInstance.send.calledWith(
-    JSON.stringify({ "command" : "subscribe",   "accounts" : [ "gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q" ] })))
+    assert(world.fakeWebSocketConstructor.calledWithNew())
+    assert(world.fakeWebSocketConstructor.calledWith('ws://live.stellar.org:9001'))

-  testSubject.on 'data', (data) ->
-    assert.deepEqual data,
-      from: "gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q"
-      to: "gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT"
-      date: 461599670
-      amount: 50000000
-    done()
+    clock.tick 50

-  fakeWebSocketInstance.fakeEmit 'message', JSON.stringify({
-     "transaction":{
-        "Account":"gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q",
-        "Amount":"50000000",
-        "Destination":"gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT",
-        "TransactionType":"Payment",
-        "date":461599670,
-        "hash":"4FE01B5CB153EFC5825DB0BDDD8A4764D63FC55102E25277548A85B2B6277202"
-     }
-  })
+    world.fakeWebSocketInstance.fakeEmit 'open'

-  clock.restore()
+    clock.tick 100

-it 'forwards errors from on error', (done)->
+    testSubject.on 'error', -> assert.fail('should not call error')

-  clock = sinon.useFakeTimers();
+    world.fakeWebSocketInstance.send.yield null

-  fakeWebSocketInstance =
-    handlers: {}
-    on: sinon.spy (event, callback) ->
-      this.handlers[event] = callback
-    fakeEmit: (event, obj) ->
-      this.handlers[event](obj)
-    send: sinon.stub()
-  fakeWebSocketConstructor = sinon.stub().returns(fakeWebSocketInstance)
+    assert(world.fakeWebSocketInstance.send.calledWith(
+      JSON.stringify({ "command" : "subscribe",   "accounts" : [ "gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q" ] })))

-  testSubject = transactionStream(fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')
+    testSubject.on 'data', (data) ->
+      assert.deepEqual data,
+        from: "gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q"
+        to: "gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT"
+        date: 461599670
+        amount: 50000000
+      done()

-  clock.tick 50
+    world.fakeWebSocketInstance.fakeEmit 'message', JSON.stringify({
+       "transaction":{
+          "Account":"gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q",
+          "Amount":"50000000",
+          "Destination":"gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT",
+          "TransactionType":"Payment",
+          "date":461599670,
+          "hash":"4FE01B5CB153EFC5825DB0BDDD8A4764D63FC55102E25277548A85B2B6277202"
+       }
+    })

-  fakeError = new Error()

-  testSubject.on 'error', (error) ->
-    assert.equal fakeError, error
-    done()
+  it 'forwards errors from on error', (done) ->
+    world.given.fakeWebSocketConstructor()

-  fakeWebSocketInstance.fakeEmit 'error', fakeError
+    testSubject = transactionStream(world.fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')

-  clock.restore()
+    clock.tick 50

-it 'forwards errors from send callback', (done) ->
+    fakeError = new Error()

-  clock = sinon.useFakeTimers();
+    testSubject.on 'error', (error) ->
+      assert.equal fakeError, error
+      done()

-  fakeWebSocketInstance =
-    handlers: {}
-    on: sinon.spy (event, callback) ->
-      this.handlers[event] = callback
-    fakeEmit: (event, obj) ->
-      this.handlers[event](obj)
-    send: sinon.stub()
-  fakeWebSocketConstructor = sinon.stub().returns(fakeWebSocketInstance)
+    world.fakeWebSocketInstance.fakeEmit 'error', fakeError

-  testSubject = transactionStream(fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')
+  it 'forwards errors from send callback', (done) ->

-  clock.tick 50
+    world.given.fakeWebSocketConstructor()

-  fakeWebSocketInstance.fakeEmit 'open'
+    testSubject = transactionStream(world.fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')

-  fakeError = new Error()
-  testSubject.on 'error', (error) ->
-    assert.equal fakeError, error
-    done()
+    clock.tick 50

-  fakeWebSocketInstance.send.yield fakeError
+    world.fakeWebSocketInstance.fakeEmit 'open'

-  clock.restore()
+    fakeError = new Error()
+    testSubject.on 'error', (error) ->
+      assert.equal fakeError, error
+      done()
+
+    world.fakeWebSocketInstance.send.yield fakeError


Document baseURL and set it for tasks

Document baseURL and set it for tasks

--- src/api/settings.coffee
+++ src/api/settings.coffee
@@ -1,6 +1,7 @@
 settings =
-  # # set/comment in to use actual BE
-  # baseUrl: 'http://localhost:3001'
+  # to customize, set environmental var BASE_URL when building or running webpack-dev-server
+  # Currently is set on an endpoint by endpoint basis until all are implemented by BE
+  # baseUrl: process?.env?.BASE_URL

   endpoints:
     'exercise.*.send.save':
@@ -26,6 +27,7 @@
     'task.*.send.fetchByModule':
       url: 'api/cc/tasks/{collectionUUID}/{moduleUUID}'
       method: 'GET'
+      baseUrl: process?.env?.BASE_URL
       completedEvent: 'task.{collectionUUID}/{moduleUUID}.receive.fetchByModule'

 module.exports = settings


Disable export button if export is already requested

--- app/talk/data-export-button.cjsx
+++ app/talk/data-export-button.cjsx
@@ -7,11 +7,14 @@
   displayName: 'DataExportButton'

   getInitialState: ->
+    isAvailable: null
     exportRequested: false
     exportError: null

   exportGet: ->
-    @_exportsGet or= talkClient.type('data_requests').get section: @section(), kind: @props.exportType
+    @_exportsGet or= talkClient.type('data_requests').get(section: @section(), kind: @props.exportType).then (requests) =>
+      @setState(isAvailable: true) if requests.length > 0
+      requests

   section: ->
     "project-#{ @props.project.id }"
@@ -27,7 +30,7 @@

   render: ->
     <div>
-      <button type="button" disabled={@state.exportRequested} onClick={@requestDataExport}>
+      <button type="button" disabled={@state.exportRequested or @state.isAvailable} onClick={@requestDataExport}>
         {@props.label}
       </button> {' '}
       <small className="form-help">


fix: selectors for product action buttons

--- test/integration/helpers/page_objects/products/index_page/row_view.coffee
+++ test/integration/helpers/page_objects/products/index_page/row_view.coffee
@@ -14,16 +14,16 @@

     # action buttons
     actionButton: get: ->
-      row.findElement protractor.By.xpath("//button[contains(text(), 'Action')]")
+      row.findElement protractor.By.xpath(".//button[contains(text(), 'Action')]")

     showButton: get: ->
       @actionButton.click()
-      row.findElement protractor.By.xpath("//a[contains(text(), 'Show')]")
+      row.findElement protractor.By.xpath(".//a[contains(text(), 'Show')]")

     editButton: get: ->
       @actionButton.click()
-      row.findElement protractor.By.xpath("//a[contains(text(), 'Edit')]")
+      row.findElement protractor.By.xpath(".//a[contains(text(), 'Edit')]")

     deleteButton: get: ->
       @actionButton.click()
-      row.findElement protractor.By.xpath("//a[contains(text(), 'Delete')]")
+      row.findElement protractor.By.xpath(".//a[contains(text(), 'Delete')]")


Added player recoil

Added player recoil

--- src/lib/Character/Player/Player.coffee
+++ src/lib/Character/Player/Player.coffee
@@ -74,7 +74,7 @@
         @i = @i + 1
         dx = @sprite.body.x + @sprite.body.width * @sprite.anchor.x - game.input.mousePointer.x
         dy = @sprite.body.y + @sprite.body.height * @sprite.anchor.y - game.input.mousePointer.y
-        @sprite.angle = -100 + 180 * Math.atan2(dy, dx) / Math.PI + 15 * Math.sin(@i / 100.0)
+        @sprite.angle = -100 + 180 * Math.atan2(dy, dx) / Math.PI + 15 * Math.sin(@i / 100.0) + Math.max(170, @gunCountdown) - 170

         # move sprite forward so footstep circles don't obscure player
         @sprite.bringToTop()


Little feedback test refactoring

Little feedback test refactoring

--- spec/itemStorage.spec.coffee
+++ spec/itemStorage.spec.coffee
@@ -41,6 +41,13 @@
     id: DOMAIN_ID
   target:
     id: itemId
+
+saveFeedbackWithSource = (storage) ->
+  feedbackWithSource = exampleFeedbackMessage()
+  feedbackWithSource.source = { id: OTHER_ITEM_ID }
+  # other item has to be present
+  storage.save(exampleMessage(OTHER_ITEM_ID))
+  storage.feedback feedbackWithSource

 describe 'ItemStorage class', ->

@@ -96,14 +103,13 @@
         expect(@storage[DOMAIN_ID][ITEM_ID].hitcount).toEqual(2)

       it 'adjusts the recommends hash of the source to show how often it has been recommended', ->
-        feedbackWithSource = exampleFeedbackMessage()
-        feedbackWithSource.source = { id: OTHER_ITEM_ID }
-        # other item has to be present
-        @storage.save(exampleMessage(OTHER_ITEM_ID))
+        saveFeedbackWithSource(@storage)
+        expect(@storage[DOMAIN_ID][OTHER_ITEM_ID].recommends[ITEM_ID]).toEqual 1

-        @storage.feedback feedbackWithSource
-
-        expect(@storage[DOMAIN_ID][OTHER_ITEM_ID].recommends[ITEM_ID]).toEqual 1
+      it 'increases the recommends score if further feedback messages come along', ->
+        saveFeedbackWithSource(@storage)
+        saveFeedbackWithSource(@storage)
+        expect(@storage[DOMAIN_ID][OTHER_ITEM_ID].recommends[ITEM_ID]).toEqual 2

     describe 'persists the storage', ->


:art: Fix description test

:art: Fix description test

Actually it doesn’t test multiple selections moves.

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -2004,7 +2004,7 @@
             expect(editor.lineTextForBufferRow(3)).toBe "    if (items.length <= 1) return items;"
             expect(editor.lineTextForBufferRow(4)).toBe "    while(items.length > 0) {"

-        describe "when there are multiple selections and the following row is a folded row", ->
+        describe "when the following row is a folded row", ->
           it "moves the lines spanned by the selection to the following row, but preserves the folded code", ->
             expect(editor.lineTextForBufferRow(2)).toBe "    if (items.length <= 1) return items;"
             expect(editor.lineTextForBufferRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"


Update theme specs for new layout

Update theme specs for new layout

--- spec/theme-panel-spec.coffee
+++ spec/theme-panel-spec.coffee
@@ -1,52 +1,53 @@
 {$} = require 'atom'
+PackageManager = require '../lib/package-manager'
 ThemePanel = require '../lib/theme-panel'

 describe "ThemePanel", ->
-  panel = null
+  [panel, packageManager] = []

   beforeEach ->
-    atom.packages.activatePackage('atom-light-ui')
-    atom.packages.activatePackage('atom-dark-ui')
-    atom.packages.activatePackage('atom-dark-syntax')
+    atom.packages.loadPackage('atom-light-ui')
+    atom.packages.loadPackage('atom-dark-ui')
+    atom.packages.loadPackage('atom-light-syntax')
+    atom.packages.loadPackage('atom-dark-syntax')
+    atom.themes.activatePackages()
     atom.config.set('core.themes', ['atom-dark-ui', 'atom-dark-syntax'])
-    panel = new ThemePanel
+    packageManager = new PackageManager
+    spyOn(packageManager, 'getAvailable').andReturn []
+    spyOn(atom.themes, 'setEnabledThemes').andCallThrough()
+    panel = new ThemePanel(packageManager)

-  describe "when an enabled theme is reloced in the themes list", ->
-    it "updates the 'core.themes' config key to reflect the new order", ->
-      li = panel.enabledThemes.children(':first').detach()
-      panel.enabledThemes.append(li)
-      panel.enabledThemes.sortable('option', 'update')()
-      expect(atom.config.get('core.themes')).toEqual ['atom-dark-syntax', 'atom-dark-ui']
+  afterEach ->
+    atom.themes.deactivateThemes()

-  describe "when a theme is dragged into the enabled themes list", ->
-    it "updates the 'core.themes' config key to reflect the themes in the enabled list", ->
-      dragHelper = panel.availableThemes.find("li[name='atom-light-ui']").clone()
-      panel.enabledThemes.prepend(dragHelper)
-      panel.enabledThemes.sortable('option', 'receive')(null, helper: dragHelper[0])
-      panel.enabledThemes.sortable('option', 'update')()
-      expect(atom.config.get('core.themes')).toEqual ['atom-light-ui', 'atom-dark-ui', 'atom-dark-syntax']
+  it "selects the active syntax and UI themes", ->
+    expect(panel.uiMenu.val()).toBe 'atom-dark-ui'
+    expect(panel.syntaxMenu.val()).toBe 'atom-dark-syntax'

-    describe "when the theme is already present in the enabled list", ->
-      it "removes the previous instance of the theme, updating the order based on the location of drag", ->
-        dragHelper = panel.availableThemes.find("li[name='atom-dark-ui']").clone()
-        panel.enabledThemes.append(dragHelper)
-        panel.enabledThemes.sortable('option', 'receive')(null, helper: dragHelper[0])
-        panel.enabledThemes.sortable('option', 'update')()
-        expect(atom.config.get('core.themes')).toEqual ['atom-dark-syntax', 'atom-dark-ui']
+  describe "when a UI theme is selected", ->
+    it "updates the 'core.themes' config key with the selected UI theme", ->
+      jasmine.unspy(window, 'setTimeout')
+      panel.uiMenu.val('atom-light-ui').trigger('change')

-        dragHelper = panel.availableThemes.find("li[name='atom-dark-ui']").clone()
-        panel.enabledThemes.prepend(dragHelper)
-        panel.enabledThemes.sortable('option', 'receive')(null, helper: dragHelper[0])
-        panel.enabledThemes.sortable('option', 'update')()
-        expect(atom.config.get('core.themes')).toEqual ['atom-dark-ui', 'atom-dark-syntax']
+      waitsFor ->
+        atom.themes.setEnabledThemes.callCount > 0

-  describe "when the disable icon is clicked on a theme li", ->
-    it "removes the theme from the list and the 'core.themes' array", ->
-      panel.enabledThemes.find('li:first .disable-theme').click()
-      expect(panel.getEnabledThemeNames()).toEqual ['atom-dark-syntax']
-      expect(atom.config.get('core.themes')).toEqual ['atom-dark-syntax']
+      runs ->
+        expect(atom.config.get('core.themes')).toEqual ['atom-light-ui', 'atom-dark-syntax']

-  describe "when the 'core.config' key is updated", ->
-    it "refreshes the enabled themes list", ->
+  describe "when a syntax theme is selected", ->
+    it "updates the 'core.themes' config key with the selected syntax theme", ->
+      jasmine.unspy(window, 'setTimeout')
+      panel.syntaxMenu.val('atom-light-syntax').trigger('change')
+
+      waitsFor ->
+        atom.themes.setEnabledThemes.callCount > 0
+
+      runs ->
+        expect(atom.config.get('core.themes')).toEqual ['atom-dark-ui', 'atom-light-syntax']
+
+  describe "when the 'core.config' key is changes", ->
+    it "refreshes the theme menus", ->
       atom.config.set('core.themes', ['atom-light-ui', 'atom-light-syntax'])
-      expect(panel.getEnabledThemeNames()).toEqual ['atom-light-ui', 'atom-light-syntax']
+      expect(panel.uiMenu.val()).toBe 'atom-light-ui'
+      expect(panel.syntaxMenu.val()).toBe 'atom-light-syntax'


fix(mail-important): Fix broken specs when getStandardCategory('important') returns null

--- src/components/mail-important-icon.cjsx
+++ src/components/mail-important-icon.cjsx
@@ -32,7 +32,9 @@
   render: =>
     return false unless @state.showing

-    importantId = CategoryStore.getStandardCategory('important').id
+    importantId = CategoryStore.getStandardCategory('important')?.id
+    return false unless importantId
+
     isImportant = _.findWhere(@props.thread.labels, {id: importantId})?

     activeClassname = if isImportant then "active" else ""


failing spec for continue wrapped branching

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -226,6 +226,36 @@
               btree.deliver data
               btree.then 'btreethen', (c, data) ->
                 data * 3
+        t = Root()
+        t.deliver 5
+        .contest "contest-multiply", [
+          multiply.bind @, 2
+          multiply.bind @, 3
+        ], (c, results) ->
+          paths = results.map (r) -> r.choice.namedPath().join '-'
+          idx = paths.indexOf 'a-tripled-btreethen'
+          idx = 0 if idx is -1
+          results[idx]
+        .finally 'end',   (c, res) ->
+          chai.expect(res).to.eql [15]
+          done()
+
+    describe 'promise chain within branching within continue', ->
+      it 'should execute the promise chain', (done) ->
+        multiply = (multiplier, c, data) ->
+          tree = c.tree 'a'
+          tree.deliver data
+          tree.then "#{multiplier}", (c, d) ->
+            c.continue()
+            .deliver()
+            .then (choice,d) ->
+              choice.branch 'doubled', (b, data) ->
+                data * 2
+              choice.branch 'tripled', (b, data) ->
+                btree = b.continue()
+                btree.deliver data
+                btree.then 'btreethen', (c, data) ->
+                  data * 3
         t = Root()
         t.deliver 5
         .contest "contest-multiply", [


Add tests for explicitly scoped variables.


template google analytics

template google analytics

--- templates/page.coffee
+++ templates/page.coffee
@@ -29,15 +29,6 @@
    @link href: "lib/skeleton/css/skeleton.css", rel: "stylesheet"
    @link href: "lib/highlightjs/styles/default.css", rel: "stylesheet"
    @link href: "css/style.css", rel: "stylesheet"
-   @script '''
-     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-     m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-     })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
-     ga('create', 'UA-37809145-1', 'auto');
-     ga('send', 'pageview');
-   '''

   @body ->
    @div ".container.wallapatta-container", ->


Remove errant file.

Remove errant file.

--- tmp-Sakefile.coffee
+++ tmp-Sakefile.coffee
@@ -1,10 +0,0 @@
-
-Path  = require "path"
-futil = require "sake/file-utils"
-
-require "sake/clean"
-require "sake/clobber"
-
-task "one", ["two"], (t)->
-  # task actions
-  t.done()


Elaborate a couple tests

Elaborate a couple tests

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -6,13 +6,13 @@

 describe "Git-Plus git module", ->
   describe "git.getRepo", ->
-    it "returns a promise", ->
+    it "returns a promise resolving to repository", ->
       waitsForPromise ->
         git.getRepo().then (repo) ->
           expect(repo.getWorkingDirectory()).toContain 'git-plus'

   describe "git.dir", ->
-    it "returns a promise", ->
+    it "returns a promise resolving to absolute path of repo", ->
       waitsForPromise ->
         git.dir().then (dir) ->
           expect(dir).toContain 'git-plus'