adding csdoc style comments for doc generation to base test element helpers

--- test-integration/helpers/ui/test-element.coffee
+++ test-integration/helpers/ui/test-element.coffee
@@ -9,7 +9,15 @@
   loadingState:
     css: '.is-loading'

+###
+Wraps the locator of an element item accessible by specs and exposes convenient functions on the matching element(s)
+###
 class TestItemHelper
+  ###
+  @param {Object} test The full context of the test element is being instantiated in
+  @param {Object|Function|String} testElementLocator Selenium locator object, or a function that returns a locator object based on parameters. If a string is provided, the locator is assumed to be a css locator.
+  @param {Object} options Extra options, `onBeforeMethodCall` can be set.
+  ###
   constructor: (test, testElementLocator, options = {}) ->
     throw new Error('BUG: Missing the current test!') unless test
     throw new Error('BUG: Missing locator') unless testElementLocator
@@ -29,6 +37,14 @@

     @

+  ###
+  Takes the internally set `locator` and returns the corresponding valid Selenium selector.
+
+  Can take arguments to pass into `locator` if `locator` is parameter dependent.
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @returns {Selenium.Locator}
+  ###
   getLocator: (args...) =>
     locator = if _.isFunction(@locator)
       @locator(args...)
@@ -37,6 +53,15 @@
     else
       @locator

+  ###
+  Helps pass through custom wait time to `get`, `getAll`, and `waitClick` if provided.
+  *Intended for internal use*, but exposed for extensibility if need.
+
+  @param {Function} waitFunction The wait function to wrap
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @param {Number} waitMs The last parameter, if provided and is a number.
+  @returns {Function}
+  ###
   aliasWait: (waitFunction, args...) =>
     locator = @getLocator(args...)

@@ -45,40 +70,98 @@

     waitFunction(locator, waitTime)

-  # methods that wait until elements are available before running
+  ###
+  Waits until element is visible before trying to return the Selenium.Promise of the matching Selenium.WebElement
+  This will only wait until waitMs (default `60000`).
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @param {Number} waitMs The last parameter, if provided and is a number.
+  @returns {Selenium.Promise}
+  ###
   get: (args...) =>
     @aliasWait(@test.utils.wait.for, args...)

+  ###
+  Waits until an element is visible before trying to return the Selenium.Promise of Array of matching Selenium.WebElements
+  This will only wait until waitMs (default `60000`).
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @param {Number} waitMs The last parameter, if provided and is a number.
+  @returns {Selenium.Promise}
+  ###
   getAll: (args...) =>
     @aliasWait(@test.utils.wait.forMultiple, args...)

-  # Helper for the common case of `wait.for(...).click()`.
-  # Plus, it allows a place to add logging since this is one of the most
-  # common places for Selenium to time out (trying to click on an element)
+  ###
+  Helper for the common case of `wait.for(...).click()`.
+
+  Plus, it allows a place to add logging since this is one of the most common places for Selenium to time out (trying to click on an element)
+
+  This will only wait until waitMs (default `60000`).
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @param {Number} waitMs The last parameter, if provided and is a number.
+  @returns {Selenium.Promise}
+  ###
   waitClick: (args...) =>
     @aliasWait(@test.utils.wait.click, args...)

-  # methods that will try to run as soon as you call them
+  ###
+  Tries to find and return Selenium.Promise of Selenium.WebElement immediately
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @returns {Selenium.Promise}
+  ###
   findElement: (args...) =>
     locator = @getLocator(args...)
     @test.driver.findElement(locator)

+  ###
+  Tries to find and return Selenium.Promise of array of matching Selenium.WebElement immediately
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @returns {Selenium.Promise}
+  ###
   findElements: (args...) =>
     locator = @getLocator(args...)
     @test.driver.findElements(locator)

-  # Helper for the common case of `findElement(...).click()`.
+  ###
+  Tries to find and click immediately
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @returns {Selenium.Promise}
+  ###
   click: (args...) =>
     @findElement(args...).click()

+  ###
+  Checks if element matching locator is immediately present, returns Selenium.Promise that resolves to a boolean
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @param {Function} forEachFunction The last parameter, if provided and is a function, will be run on each WebElement
+  @param {Function} forEachFunction2 optional, If an additional function is available, this will run once before.
+  ###
   forEach: (args..., forEachFunction, forEachFunction2) =>
     locator = @getLocator(args...)
     @test.utils.forEach(locator, forEachFunction, forEachFunction2)

+  ###
+  Checks if element matching locator is currently present, returns Selenium.Promise that resolves to a boolean
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @returns {Selenium.Promise}
+  ###
   isPresent: (args...) =>
     locator = @getLocator(args...)
     @test.driver.isElementPresent(locator)

+  ###
+  Checks if element matching locator is currently displayed, returns Selenium.Promise that resolves to a boolean
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @returns {Selenium.Promise}
+  ###
   isDisplayed: (args...) =>
     @isPresent(args...).then (isPresent) =>
       if isPresent
@@ -88,19 +171,34 @@
       else
         false

+  ###
+  Tries to find the immediate parent of the element matching the locator, returns Selenium.Promise of Selenium.WebElement
+
+  @param {Any} args... Any number of parameters for `locator` to use if `locator` is a function dependent on parameters
+  @returns {Selenium.Promise}
+  ###
   getParent: (args...) =>
     locator = @getLocator(args...)
     @test.utils.dom.getParent(locator)

-
+###
+A collection of convenience methods for a test component.  Usually extended with additional helper methods before instantiation.
+
+@property {Object} el Exposes instances of `TestItemHelper` of common test elements on this component.
+###
 class TestHelper
+  ###
+  @param {Object} test The full context of the test element is being instantiated in
+  @param {Object|Function|String} testElementLocator Selenium locator object, or a function that returns a locator object based on parameters. If a string is provided, the locator is assumed to be a css locator.
+  @param {Object} commonElements An Object where the `name` of `locator`s are the keys, and the locators are the values.
+  @param {Object} options Extra options, `defaultWaitTime` can be set.
+  ###
   constructor: (test, testElementLocator, commonElements, options) ->
     commonElements ||= _.result(@, 'elementRefs', {})
     commonElements = _.extend({}, DEFAULT_ELEMENTS, commonElements)

     defaultOptions =
       defaultWaitTime: 40 * 1000 # TODO: Letting tests define their own wait time is dangerous. tutor-dev takes > 10sec to delete a task-plan
-      # defaultWaitTime: 20 * 1000 # 20sec seems to be enough for deployed code but not local

     @test = test
     @_options = _.assign {}, defaultOptions, options
@@ -111,15 +209,33 @@
     _.each commonElements, @setCommonElement
     @

+  ###
+  Wait until component is loaded before beginning tests.  Defaults to waiting for `css: '.is-loading'` to be not present.
+
+  The locator to wait for can be set in `commonElements` during instantiation as `loadingState`.
+
+  @returns {Selenium.Promise}
+  ###
   waitUntilLoaded: =>
-    # Adjust the test timeout *and* tell selenium to wait up to the same amount of time. Maybe this is redundant?
     @test.utils.wait.giveTime @options.defaultWaitTime, =>
       @test.utils.wait.until "Waiting until Loadable #{JSON.stringify(@el.loadingState().getLocator())} is gone", =>
         @el.loadingState().isPresent().then (isPresent) -> not isPresent

+  ###
+  Set item helper as a function that takes arguments for the locator on `el`.
+
+  @param {String} name The name the `el` will be accessed by.
+  @param {TestItemHelper|TestHelper} helper Usually a `TestItemHelper` that is exposed on `el`.
+  ###
   setCommonHelper: (name, helper) =>
     @el[name] = helper.fn or helper

+  ###
+  Takes `commonElements` from instantiation and wraps the `locator` with `TestItemHelper` methods
+
+  @param {Object|Function|String} locator Selenium locator object, or a function that returns a locator object based on parameters. If a string is provided, the locator is assumed to be a css locator.
+  @param {String} name The name the `el` will be accessed by.
+  ###
   setCommonElement: (locator, name) =>
     @setCommonHelper(name, new TestItemHelper(@test, locator,
       onBeforeMethodCall: (methodName, args...) ->


Moved init function into superclass to be used by all its subclasses

--- app/assets/javascripts/admin/admin_in_place_editor.js.coffee
+++ app/assets/javascripts/admin/admin_in_place_editor.js.coffee
@@ -46,6 +46,46 @@
     alert.append(txt)

     $(alert).insertBefore($('h1'))
+
+  initTaxonConceptTypeaheads: () ->
+
+    $('input.typeahead').each (idx) ->
+      formId = $(@).closest('form').attr('id')
+
+      if formId?
+        matches = formId.match('^(.+_)?(new|edit)_(.+)$')
+        prefix = matches[3]
+        prefix = matches[1] + prefix unless matches[1] == undefined
+
+        taxonomyEl = $('#' + prefix + '_taxonomy_id')
+        rankEl = $('#' + prefix + '_rank_id')
+
+        #initialize this typeahead
+        $(@).typeahead
+          source: (query, process) =>
+            $.get('/admin/taxon_concepts/autocomplete',
+            {
+              search_params: {
+                scientific_name: query,
+                taxonomy: {
+                  id: taxonomyEl && taxonomyEl.val() || $(@).attr('data-taxonomy-id')
+                },
+                rank: {
+                  id: rankEl && rankEl.val() || $(@).attr('data-rank-id'),
+                  scope: $(@).attr('data-rank-scope')
+                }
+              }
+              limit: 25
+            }, (data) =>
+              labels = []
+              $.each(data, (i, item) =>
+                label = item.full_name + ' ' + item.rank_name
+                labels.push(label)
+              )
+              return process(labels)
+            )
+          $().add(taxonomyEl).add(rankEl).change () =>
+            $(@).val(null)

   initSearchTypeahead: () ->
     $('.search-typeahead').typeahead
@@ -167,46 +207,6 @@
     @saveAndReopen = false
     @initTaxonConceptTypeaheads()
     $('.distributions-list > a').popover({});
-
-  initTaxonConceptTypeaheads: () ->
-
-    $('input.typeahead').each (idx) ->
-      formId = $(@).closest('form').attr('id')
-
-      if formId?
-        matches = formId.match('^(.+_)?(new|edit)_(.+)$')
-        prefix = matches[3]
-        prefix = matches[1] + prefix unless matches[1] == undefined
-
-        taxonomyEl = $('#' + prefix + '_taxonomy_id')
-        rankEl = $('#' + prefix + '_rank_id')
-
-        #initialize this typeahead
-        $(@).typeahead
-          source: (query, process) =>
-            $.get('/admin/taxon_concepts/autocomplete',
-            {
-              search_params: {
-                scientific_name: query,
-                taxonomy: {
-                  id: taxonomyEl && taxonomyEl.val() || $(@).attr('data-taxonomy-id')
-                },
-                rank: {
-                  id: rankEl && rankEl.val() || $(@).attr('data-rank-id'),
-                  scope: $(@).attr('data-rank-scope')
-                }
-              }
-              limit: 25
-            }, (data) =>
-              labels = []
-              $.each(data, (i, item) =>
-                label = item.full_name + ' ' + item.rank_name
-                labels.push(label)
-              )
-              return process(labels)
-            )
-          $().add(taxonomyEl).add(rankEl).change () =>
-            $(@).val(null)

   alertSuccess: (txt) ->
     $('.alert').remove()


FIXED bugs in conversation

FIXED bugs in conversation

--- app/assets/javascripts/angular/controllers/SearchCtrl.coffee
+++ app/assets/javascripts/angular/controllers/SearchCtrl.coffee
@@ -1,6 +1,4 @@
 Grasshopper.controller "SearchCtrl", ['$scope', '$location', 'User', '$http', ($scope, $location, User, $http) ->
-  User.loadAll().then (result) ->
-    $scope.users = result.users

   reloadUser = () ->
     User.loadCurrentUser().then (data) ->
@@ -17,8 +15,6 @@

   $scope.search = () ->
     $location.url '/search'
-
-  $scope.searchText = ''

   $scope.filterByNameOrSkill = (users,searchText) ->
     filteredUsers = []
@@ -42,6 +38,7 @@
     if _.indexOf($scope.usersCommunicatedWith, user.id) == -1 and user.id != $scope.currentUser.id
       User.createConversationWithMessage($scope.currentUser, user, messageText).success (response) ->
         noty { text: 'Successfully sent a message!', type: 'success'}
+        console.log '$scope.messageText: ', $scope.messageText = ''
         $scope.usersCommunicatedWith.push user.id
         reloadUser()
       .error (response) ->
@@ -50,11 +47,20 @@
       conversation = _.find($scope.conversations, (conversation) -> conversation.created_by == user.id || conversation.created_for == user.id)
       User.createMessageTo($scope.currentUser, user, messageText, conversation.id).success (response) ->
         noty { text: 'Successfully sent a message!', type: 'success'}
+        console.log '$scope.messageText: ', $scope.messageText = ''
       .error (response) ->
         noty { text: 'Message cannot be sent! Please try again', type: 'error'}
     $(".modal").modal('hide')
     return

+  User.loadAll().then (result) ->
+    $scope.users = result.users
+
   reloadUser()
+
+  $scope.messageText = ''
+
+  $scope.searchText = ''
+
 ]


Maintenance: Improve ajax requests for object manager attribute overview.

--- app/assets/javascripts/app/controllers/object_manager.coffee
+++ app/assets/javascripts/app/controllers/object_manager.coffee
@@ -92,17 +92,19 @@
     )

   build: (objects) =>
-    @tabs = []
-    for object in objects
-      item =
-        name:       object
-        target:     "c-#{object}"
-        controller: Items
-        params:
-          object: object
-      @tabs.push item
-
-    @render()
+    App.ObjectManagerAttribute.fetchFull(=>
+      @tabs = []
+      for object in objects
+        item =
+          name:       object
+          target:     "c-#{object}"
+          controller: Items
+          params:
+            object: object
+        @tabs.push item
+
+      @render()
+    )

 class Items extends App.ControllerSubContent
   header: __('Object Manager')
@@ -116,7 +118,7 @@
   constructor: ->
     super
     @subscribeId = App.ObjectManagerAttribute.subscribe(@render)
-    App.ObjectManagerAttribute.fetch()
+    @render()

   release: =>
     if @subscribeId


Add notes to TaskSerializer

Add notes to TaskSerializer

--- app/assets/javascripts/serializers/task_serializer.js.coffee
+++ app/assets/javascripts/serializers/task_serializer.js.coffee
@@ -9,6 +9,8 @@
     json[key] = Em.get(record, key).mapBy("id")  if relationshipType is "manyToNone" or relationshipType is "manyToMany"
     return

+  # This is overridden because finding a 'task' and getting back a root key of 'author_task' will
+  # break the isPrimary check.
   extractSingle: (store, primaryType, payload, recordId, requestType) ->
     payload = @normalizePayload(primaryType, payload)
     primaryTypeName = primaryType.typeKey
@@ -17,12 +19,14 @@
       typeName = @typeForRoot(prop)
       type = store.modelFor(typeName)
       isPrimary = type.typeKey is primaryTypeName
+      # =======Custom check for primary type
       if payload[prop].parent_type == 'task'
         isPrimary = true
         primaryType = type
         primaryTypeName = type.typeKey
       else
         isPrimary = type.typeKey is primaryTypeName
+      # =======Custom check for primary type

       # legacy support for singular resources
       if isPrimary and Ember.typeOf(payload[prop]) isnt "array"


Navigate to search page when enter key is pressed

--- src/scripts/modules/find-content/find-content.coffee
+++ src/scripts/modules/find-content/find-content.coffee
@@ -1,4 +1,6 @@
 define (require) ->
+  $ = require('jquery')
+  router = require('cs!router')
   BaseView = require('cs!helpers/backbone/views/base')
   template = require('hbs!./find-content-template')
   require('less!./find-content')
@@ -9,6 +11,14 @@

     events:
       'click .dropdown-toggle': 'clickToggle'
+      'keyup input': 'triggerSearch'

     clickToggle: (e) ->
       e.preventDefault()
+
+    triggerSearch: (e) ->
+      if e.keyCode is 13
+        @search($(e.currentTarget).val())
+
+    search: (query) ->
+      router.navigate("search?q=#{query}", {trigger: true})


Handle compositions in Chrome and Safari

--- src/trix/controllers/level_2_input_controller.coffee
+++ src/trix/controllers/level_2_input_controller.coffee
@@ -3,6 +3,12 @@
 {objectsAreEqual, compact, summarizeStringChange} = Trix

 class Trix.Level2InputController extends Trix.AbstractInputController
+  elementDidMutate: (mutationSummary) ->
+    if @composition?
+      @delegate?.inputControllerDidAllowUnhandledInput?()
+    else
+      super
+
   mutationIsExpected: (mutationSummary) ->
     result = objectsAreEqual(mutationSummary, @inputSummary)
     console.log("[mutation] [#{if result then "expected" else "unexpected"}] #{JSON.stringify({mutationSummary})}")
@@ -19,6 +25,15 @@
       console.log("[#{event.type}] #{JSON.stringify(event.data)}")
       Promise.resolve().then(console.groupEnd)

+    compositionend: (event) ->
+      if @composition?
+        string = @composition
+        delete @composition
+        @delegate?.inputControllerWillPerformTyping()
+        @responder?.expandSelectionInDirection("backward", length: string.length)
+        @responder?.insertString(string)
+        @requestRender()
+
   # https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes

   deleteByComposition: (event) ->
@@ -28,6 +43,7 @@
   deleteByDrag: (event) ->

   deleteCompositionText: (event) ->
+    @composition = ""

   deleteContent: (event) ->

@@ -111,8 +127,11 @@
   historyUndo: (event) ->

   insertCompositionText: (event) ->
+    @composition = event.data

   insertFromComposition: (event) ->
+    delete @composition
+    @insertReplacementText(event)

   insertFromDrop: (event) ->

@@ -141,7 +160,7 @@
     {textAdded, textDeleted}

   insertReplacementText: (event) ->
-    newText = event.dataTransfer.getData("text/plain")
+    newText = event.data ? event.dataTransfer.getData("text/plain")
     oldText = getTargetText(event)
     {length} = oldText
     @delegate?.inputControllerWillPerformTyping()


set default scrollback to 1000

set default scrollback to 1000

--- client/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -10,6 +10,7 @@
       @appStorage.setValue 'fontSize', 14 if not @appStorage.getValue('fontSize')?
       @appStorage.setValue 'theme', 'green-on-black' if not @appStorage.getValue('theme')?
       @appStorage.setValue 'visualBell', false if not @appStorage.getValue('visualBell')?
+      @appStorage.setValue 'scrollback', 1000 if not @appStorage.getValue('scrollback')?
       @updateSettings()

     super


:fire: Remove old coffee file

:fire: Remove old coffee file

--- Source/SSH.coffee
+++ Source/SSH.coffee
@@ -1,122 +0,0 @@
-# @Compiler-Output "../Dist/SSH.js"
-
-FS = require 'fs'
-Driver = require 'ssh2'
-Path = require 'path'
-Promise = require 'a-promise'
-
-class SSH
-  constructor:(Config)->
-    @Config = Config
-    if typeof Config.username is 'undefined' then throw new Error "Please specify a valid Username"
-    if typeof Config.host is 'undefined' then throw new Error "Please specify a valid Host"
-    if Config.privateKey
-      if Path.isAbsolute(Config.privateKey)
-        try
-          Config.privateKey = FS.readFileSync Config.privateKey
-        catch
-          throw new Error "Unable to read the Private Key File"
-    else if typeof Config.password is 'undefined'
-      throw new Error "Please specify a valid password or Private Key"
-    Config.port = Config.port || 22
-    @Connected = false
-  connect:->
-    @Connection = new Driver()
-    return new Promise (Resolve, Reject)=>
-      try
-        @Connection.on 'error', Reject
-        @Connection.on 'ready', =>
-          @Connected = true
-          Resolve()
-        @Connection.connect(@Config)
-      catch error
-        Reject error
-  mkdir:(Path)->
-    return @exec("mkdir -p #{Path}")
-  exec:(Command, Opts)->
-    throw new Error("Please connect before doing anything else") unless @Connected
-    Opts = Opts || {}
-    return new Promise (Resolve, Reject)=>
-      Command = "cd #{Opts.cwd}; #{Command}" if Opts.cwd
-      @Connection.exec Command, (Error, Stream)=>
-        return Reject(Error) if Error
-        ToReturn = stdout: [], stderr: []
-        Stream.on('close', ->
-          Resolve stderr: ToReturn.stderr.join(""), stdout: ToReturn.stdout.join("")
-        ).on('data', (Data)->
-          ToReturn.stdout.push Data.toString()
-        ).stderr.on('data', (Data)->
-          ToReturn.stderr.push Data.toString()
-        )
-  put:(LocalFile, RemoteFile, SFTP, Retry = true)->
-    throw new Error("Please connect before doing anything else") unless @Connected
-    try
-      FS.accessSync(LocalFile, FS.R_OK)
-    catch
-      throw new Error("Local File '#{LocalFile}' doesn't exist")
-    if SFTP
-      ThePromise = Promise.resolve(SFTP)
-    else
-      ThePromise = @requestSFTP()
-    return ThePromise.then (SFTP)=>
-      Deferred = Promise.defer()
-      SFTP.fastPut LocalFile, RemoteFile, (Error)=>
-        if Error
-          if Error.message is 'No such file' and not Retry
-            Deferred.reject(Error)
-          else
-            Deferred.resolve(@mkdir(RemoteFile.split("/").slice(0,-1).join('/')).then =>
-              @put(LocalFile, RemoteFile, SFTP, false)
-            )
-        else
-          Deferred.resolve()
-      return Deferred.promise
-  putMulti:(Files, SFTP)->
-    throw new Error("Please connect before doing anything else") unless @Connected
-    if SFTP
-      ThePromise = Promise.resolve(SFTP)
-    else
-      ThePromise = @requestSFTP()
-    Promises = []
-    return ThePromise.then (SFTP)=>
-      Files.forEach (File)=>
-        return unless File.Local or File.Remote
-        return unless FS.existsSync File.Local
-        Promises.push @put(File.Local, File.Remote, SFTP)
-      return Promise.all(Promises)
-  get:(RemoteFile, LocalFile, SFTP)->
-    throw new Error("Please connect before doing anything else") unless @Connected
-    if SFTP
-      ThePromise = Promise.resolve(SFTP)
-    else
-      ThePromise = @requestSFTP()
-    return ThePromise.then (SFTP)=>
-      Deferred = Promise.defer()
-      Stream = SFTP.createReadStream(RemoteFile)
-      Contents = []
-      Stream.on('data', (Data)->
-        Contents.push Data.toString()
-      ).on('close', ->
-        Contents = Contents.join('')
-        return Deferred.resolve(Contents) unless LocalFile
-        FS.writeFile LocalFile, Contents, (error)->
-          return Reject(error) if error
-          Deferred.resolve()
-      )
-      return Deferred.promise
-  requestSFTP:->
-    throw new Error("Please connect before doing anything else") unless @Connected
-    return new Promise (Resolve, Reject)=>
-      @Connection.sftp (Error, SFTP)=>
-        return Reject(Error) if Error
-        Resolve(SFTP)
-  requestShell:->
-    throw new Error("Please connect before doing anything else") unless @Connected
-    return new Promise (Resolve, Reject)=>
-      @Connection.shell (Error, Shell)=>
-        return Reject(Error) if Error
-        Resolve(Shell)
-  end:->
-    @Connection.end()
-    @Connected = false
-module.exports = SSH


switch from gulp to make

switch from gulp to make

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -1,132 +0,0 @@
-gulp       = require 'gulp'
-gutil      = require 'gulp-util'
-mocha      = require 'gulp-mocha'
-coveralls  = require 'gulp-coveralls'
-coffee     = require 'gulp-coffee'
-istanbul   = require 'gulp-coffee-istanbul'
-browserify = require 'browserify'
-coffeeify  = require 'coffeeify'
-source     = require 'vinyl-source-stream'
-rename     = require 'gulp-rename'
-uglify     = require 'gulp-uglify'
-streamify  = require 'gulp-streamify'
-coffeelint = require 'gulp-coffeelint'
-
-stat = require 'node-static'
-spawn = require('child_process').spawn
-glob = require 'glob'
-
-# application entry point to generate standalone library
-ENTRY = './src/gerber-to-svg.coffee'
-DIST = 'gerber-to-svg'
-DISTDIR = './dist'
-NAME = 'gerberToSvg'
-
-# source code and destination code locations
-SRC = './src/*.coffee'
-TEST = './test/*_test.coffee'
-LIBDIR = './lib'
-
-# plugin options
-MOCHA_OPTS = {
-  reporter: 'spec'
-  globals: {
-    coffee: require 'coffee-script/register'
-    stack: Error.stackTraceLimit = 3
-  }
-}
-
-ZUUL_OPTS = [ '--concurrency', 2 ]
-
-handleCompileError = (e) ->
-  if e.name is 'SyntaxError'
-    gutil.log e.stack
-  else
-    gutil.log e.message
-  @emit 'end'
-
-gulp.task 'default', ->
-  gulp.src SRC
-    .pipe coffeelint()
-    .pipe coffeelint.reporter()
-    .pipe coffee()
-      .on 'error', handleCompileError
-    .pipe gulp.dest LIBDIR
-
-gulp.task 'standalone', ->
-  browserify ENTRY, { extensions: [ '.coffee' ], standalone: NAME }
-    .transform 'coffeeify'
-    .bundle().on 'error', gutil.log
-    .pipe source "#{DIST}.js"
-    .pipe gulp.dest DISTDIR
-    .pipe rename "#{DIST}.min.js"
-    .pipe streamify uglify {
-      output: {
-        preamble: '/* copyright 2015 by mike cousins and contributors; shared
-        under the terms of
-        the MIT license. Source code available at
-        github.com/mcous/gerber-to-svg */'
-      }
-      compress: { drop_console: true }
-      mangle: true
-    }
-    .pipe gulp.dest DISTDIR
-
-gulp.task 'build', [ 'default', 'standalone' ]
-
-gulp.task 'watch', [ 'build' ], ->
-  gulp.watch [ './src/*' ] , [ 'build' ]
-
-# this is also ugly but it works
-gulp.task 'test', (cb) ->
-  gulp.src SRC
-    .pipe istanbul { includeUntested: true }
-    .pipe istanbul.hookRequire()
-    .on 'finish', ->
-      gulp.src TEST
-        .pipe coffeelint()
-        .pipe coffeelint.reporter()
-        .pipe mocha MOCHA_OPTS
-        .on 'error', handleCompileError
-        .pipe istanbul.writeReports { reporters: [ 'lcov', 'text-summary' ] }
-        .on 'end', cb
-    # return null so that cb fires correctly
-    null
-
-gulp.task 'coveralls', [ 'test' ], ->
-  gulp.src './coverage/lcov.info'
-    .pipe coveralls()
-
-gulp.task 'browsers', (cb) ->
-  glob TEST, (err, files) ->
-    if err? then cb err
-    else if files?
-      ZUUL_OPTS.push files...
-      child = spawn 'zuul', ZUUL_OPTS, { stdio: 'inherit' }
-      child.on 'close', (code) ->
-        gutil.log 'browser tests completed'
-        cb()
-  null
-
-gulp.task 'travis', [ 'test', 'coveralls' ], ->
-
-gulp.task 'testwatch', ['test' ], ->
-  gulp.watch ['./src/*', './test/*'], ['test', 'default']
-
-gulp.task 'testvisual', [ 'watch' ], ->
-  server = new stat.Server '.'
-  require('http').createServer( (request, response) ->
-    request.addListener( 'end', ->
-      if request.url is '/'
-        server.serveFile '/test/index.html', 200, {}, request, response
-        gutil.log "served #{request.url}"
-      else
-        server.serve(request, response, (error, result) ->
-          if error
-            gutil.log "error serving #{request.url}"
-          else
-            gutil.log "served #{request.url}"
-        )
-    ).resume()
-  ).listen 4242
-  gutil.log 'test server started at http://localhost:4242\n'


coverage is broken; trying to fix

coverage is broken; trying to fix

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -62,7 +62,7 @@
       globals: {
         should: require 'should'
         coffee: require 'coffee-script/register'
-        stack: Error.stackTraceLimit = 3
+      #  stack: Error.stackTraceLimit = 3
       }
     }
     .on 'error', (e) ->
@@ -74,7 +74,7 @@
     -r test/register-coffee-coverage -r should
     -R mocha-lcov-reporter', { silent: true }
     .exec()
-    .pipe rename 'lcov.info'
+    #.pipe rename 'lcov.info'
     .pipe streamify coveralls()

 gulp.task 'testwatch', ['test' ], ->


Check if request is of a path before looking up silo for extension.

--- lib/action.coffee
+++ lib/action.coffee
@@ -61,14 +61,10 @@
       actions= site_meta._actions
       extLookup= actions[if req.method is 'HEAD' then 'GET' else req.method]
       return nextSite() unless extLookup?
-      extSilo = extLookup[urlExtension] ? extLookup['*']

-      if extLookup['/']?
-        fs.stat pathobj.full, (err,stats)=>
-          if !err && stats.isDirectory()
-            traverseActionList(extLookup['/'])
-          else
-            traverseActionList(extSilo)
-      else
-        traverseActionList(extSilo)
+      fs.stat pathobj.full, (err,stats)=>
+        if !err && stats.isDirectory()
+          traverseActionList(extLookup['/'])
+        else
+          traverseActionList(extLookup[urlExtension] ? extLookup['*'])
   nextSite()


Only call onModalReady when it is set

Only call onModalReady when it is set

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -58,7 +58,7 @@
   else
     showFrame.src = url

-showFrame.onload = -> window.onModalReady()
+showFrame.onload = -> window.onModalReady?()

 # initialize the page, so we are ready to render new pages fast
 loadUrl '/client/blank'


minor code re-formatting, nvm

minor code re-formatting, nvm

--- library/nucleus/watch.coffee
+++ library/nucleus/watch.coffee
@@ -235,12 +235,12 @@
         hasProto = (s) -> _.isObject(s) and s.prototype
         isService = (s) -> try s.derives service.Service
         isTyped = (s) -> hasProto(s) and isService(s)
-        isFinal = (s) -> try not s.abstract()
-        unscoped = _.filter globals, isTyped
-        services = _.filter exports, isTyped
-        services = _.merge services, unscoped
-        services = _.filter services, isFinal
-        return _.unique services or Array()
+        isFinal = (serv) -> try not serv.abstract()
+        assert unscoped = _.filter globals, isTyped
+        assert services = _.filter exports, isTyped
+        assert services = _.merge services, unscoped
+        assertservices = _.filter services, isFinal
+        return _.unique services or new Array()

     # Watch the specified directory for addition and changing of
     # the files, looking for modules with services there and then


Classroom: Implemented thumb views for enrolled and related classes.

--- client/app/Applications/Classroom.kdapplication/Views/classroomclassesview.coffee
+++ client/app/Applications/Classroom.kdapplication/Views/classroomclassesview.coffee
@@ -10,8 +10,21 @@
     @createClasses()

   createClasses: ->
-    for enrolled in @getData().enrolled
-      @enrolledContainer.addSubView new ClassroomClassThumbView {}, enrolled
+    @thumbViews = []
+
+    if @getData().enrolled
+      for enrolled in @getData().enrolled
+        @createThumbView @enrolledContainer, "enrolled", enrolled
+
+    if @getData().related
+      for related in @getData().related
+        @createThumbView @relatedContainer, "related", related
+
+  createThumbView: (container, type, data) ->
+    {cdnRoot} = @getDelegate()
+    thumbView = new ClassroomClassThumbView { cdnRoot, type }, data
+    container.addSubView thumbView
+    @thumbViews.push thumbView

   createElements: ->
     @enrolledContainer = new KDCustomHTMLView


group logo settings added to dashboard

group logo settings added to dashboard

--- client/Dashboard/views/generalsettingsview.coffee
+++ client/Dashboard/views/generalsettingsview.coffee
@@ -47,6 +47,10 @@
                   location.replace('/')
             , data
       fields:
+        Logo                :
+          label             : "Logo"
+          itemClass         : GroupLogoSettings
+
         Title               :
           label             : "Group Name"
           name              : "title"


removed uuid

removed uuid

--- server/publish.coffee
+++ server/publish.coffee
@@ -8,7 +8,6 @@

 Meteor.publish("my_posts", ->
   user_id = Meteor.call('get_user_id')
-  uuid = Meteor.uuid
   self = this
   if user_id?
     # gather ids of my posts and posts i've replied to
@@ -40,7 +39,8 @@
     )
     self.onStop( ->
       handle.stop()
-      self.unset("client_posts", uuid, [
+      #self.unset("client_posts", uuid, [
+      self.unset("client_posts", [
         'author_id', 'doc.author_id', 'content',
         'parent_id', 'tags', 'my_tags', 'my_vote', 'votes'
       ])


editor: cleanup before rtm dispose

editor: cleanup before rtm dispose

--- client/ide/lib/workspace/panes/ideeditorpane.coffee
+++ client/ide/lib/workspace/panes/ideeditorpane.coffee
@@ -322,6 +322,9 @@
     @rtm
       .on 'TextInsertedIntoString', @bound 'handleCollaborativeStringEvent'
       .on 'TextDeletedFromString',  @bound 'handleCollaborativeStringEvent'
+      .on 'RealtimeManagerWillDispose', =>
+        @rtm.unbindRealtimeListeners string, 'string'
+        @removeAllCursorWidgets()

   handleCollaborativeStringEvent: (changedString, change) ->


FinderPane: Minor review fix.

FinderPane: Minor review fix.

--- client/ide/lib/workspace/panes/idefinderpane.coffee
+++ client/ide/lib/workspace/panes/idefinderpane.coffee
@@ -34,11 +34,12 @@

   getOpenFolders: (filterRootFolder = yes) ->

-    fc              = @finderController
-    machineNodePath = fc.getMachineNode(@mountedMachine.uid).getData().path
-    folderPaths     = fc.treeController.getOpenFolders()
+    fc          = @finderController
+    folderPaths = fc.treeController.getOpenFolders()

     return folderPaths  unless filterRootFolder
+
+    machineNodePath = fc.getMachineNode(@mountedMachine.uid).getData().path
     return folderPaths.filter (path) -> path isnt machineNodePath


Catch billing errors

Catch billing errors

--- client/landing/app/MainApp/paymentcontroller.coffee
+++ client/landing/app/MainApp/paymentcontroller.coffee
@@ -37,7 +37,7 @@
       val.input.giveValidationFeedback no

     for e in err
-      if e.field == 'account.base'
+      if e.field in ['account.base', 'billing_info.base']
         val.input.showValidationError e.message
         if e.message.indexOf('card') > -1
           inputs.cardNumber.giveValidationFeedback yes


Stack credential: remove interpolation characters replacement of __rawContent

--- client/stacks/lib/views/stacks/variablesview.coffee
+++ client/stacks/lib/views/stacks/variablesview.coffee
@@ -116,7 +116,6 @@
       @_providedData = {}
       @setState 'INVALID'
     else
-      content = helper.encodeRawContent content
       @_providedData = _.extend converted.contentObject, { __rawContent : content }
       @handleDataChange()

@@ -155,27 +154,7 @@
         { meta } = data
         if (Object.keys meta).length
           content = if rawContent = meta.__rawContent
-          then helper.decodeRawContent rawContent
+          then rawContent
           else (jsonToYaml meta).content

-          @getAce().setContent content
-
-
-  helper =
-
-    encodeRawContent: (content) ->
-
-      content
-        .replace '$', encodeURIComponent('$'), 'g'
-        .replace '{', encodeURIComponent('{'), 'g'
-        .replace '}', encodeURIComponent('}'), 'g'
-
-
-    decodeRawContent: (content) ->
-
-      _.unescape(
-        content
-          .replace encodeURIComponent('$'), '$', 'g'
-          .replace encodeURIComponent('{'), '{', 'g'
-          .replace encodeURIComponent('}'), '}', 'g'
-      )
+          @getAce().setContent _.unescape content


Fix header response on file upload

Fix header response on file upload

--- src/main/coffeescript/view/OperationView.coffee
+++ src/main/coffeescript/view/OperationView.coffee
@@ -173,9 +173,11 @@

   wrap: (data) ->
     headers = {}
-    headerArray = data.getAllResponseHeaders().split(":")
-    for i in [0..headerArray.length/2] by (2)
-      headers[headerArray[i]] = headerArray[i+1]
+    headerArray = data.getAllResponseHeaders().split("\r")
+    for i in headerArray
+      h = i.split(':')
+      if (h[0] != undefined && h[1] != undefined)
+        headers[h[0].trim()] = h[1].trim()

     o = {}
     o.content = {}


Fix for zoom

Fix for zoom

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -32,7 +32,7 @@
   enableMarking: ->
     # console.log 'CANVAS GRAPH: enableMarking()'
     @markingDisabled = false
-
+
     # TODO: should this be in the constructor?
     @marks = new Marks
     window.marks = @marks
@@ -44,7 +44,7 @@
     # debugger
     # console.log 'onMouseDown()'
     xClick = e.pageX - e.target.getBoundingClientRect().left - window.scrollX
-    return if xClick < 80 # display line instead
+    return if xClick < 80 # display line instead
     @addMarkToGraph(e)

   onMouseMove: (e) =>
@@ -64,12 +64,12 @@
       # draw triangle
       w = 10
       s = 2*w*Math.tan(@leftPadding)
-
+
       @ctx.beginPath()
       @ctx.moveTo(w,yClick)
       @ctx.lineTo(0,yClick+s)
       @ctx.lineTo(0,yClick-s)
-      @ctx.fillStyle = '#FC4542'
+      @ctx.fillStyle = '#FC4542'
       @ctx.fill()

       @ctx.beginPath()
@@ -85,7 +85,7 @@
       @ctx.font = '10pt Arial'
       @ctx.textAlign = 'left'
       @ctx.fillText( @toDataYCoord((-yClick+@canvas.height)).toFixed(4), 15, yClick+5 ) # don't forget to flip y-axis values
-
+
   processLightcurve: (removeOutliers=true) ->

     @removeOutliers = removeOutliers
@@ -115,7 +115,7 @@
     @largestY = Math.max  @data.y...

     @plotPoints()
-
+
     return

   toggleOutliers: ->
@@ -131,16 +131,16 @@
     for y, i in [ data... ]
       y_new[i] = (y-1)/(std)
       y_new[i] = y/(mean)
-
+
     return y_new

-  processOutliers: (data, nsigma) ->
+  processOutliers: (data, nsigma) ->
     data_new = {}
     data_new.x = []
     data_new.y = []
     mean = @mean(data.y)
     std = @std(data.y)
-
+
     for y, i in [data.y...]
       continue if (y - mean) > (nsigma * std) # skip outlier
       data_new.x.push data.x[i]
@@ -165,7 +165,7 @@
     # console.log 'showFakePrevMarks()'
     @zoomOut()
     maxMarks = 5
-    minMarks = 1
+    minMarks = 1
     howMany = Math.floor( Math.random() * (maxMarks-minMarks) + minMarks )
     # console.log 'randomly generating ', howMany, ' (fake) marks' # DEBUG
     @generateFakePrevMarks( howMany )
@@ -217,7 +217,7 @@

     # get necessary values from classifier
     @sliderValue = +classifier.el.find('#ui-slider').val()
-
+
     # draw points
     for i in [0...@dataLength]
       x = ((+@data.x[i]-xMin)/(xMax-xMin)) * (@canvas.width-@leftPadding)
@@ -248,12 +248,12 @@
       @sliderValue = 0
     else
       @sliderValue = +classifier.el.find('#ui-slider').val()
-
+
     # draw marks
     if @marks
       for mark in @marks.all
-        scaledMin = ((parseFloat(mark.dataXMinRel) + parseFloat(@toDays(@leftPadding)) - parseFloat(xMin) - parseFloat(@sliderValue) ) / (parseFloat(xMax) - parseFloat(xMin)) ) * parseFloat(@canvas.width-@leftPadding)
-        scaledMax = ((parseFloat(mark.dataXMaxRel) + parseFloat(@toDays(@leftPadding)) - parseFloat(xMin) - parseFloat(@sliderValue) ) / (parseFloat(xMax) - parseFloat(xMin)) ) * parseFloat(@canvas.width-@leftPadding)
+        scaledMin = ( mark.dataXMinRel - xMin ) / (xMax - xMin) * (@canvas.width-@leftPadding) + @leftPadding
+        scaledMax = ( mark.dataXMaxRel - xMin ) / (xMax - xMin) * (@canvas.width-@leftPadding) + @leftPadding
         mark.element.style.width = (parseFloat(scaledMax)-parseFloat(scaledMin)) + "px"
         mark.element.style.left = parseFloat(scaledMin) + "px"
         mark.save(scaledMin, scaledMax)
@@ -268,7 +268,7 @@

     # update slider position
     classifier.el.find('#ui-slider').val(@graphCenter-@zoomRanges[@zoomLevel]/2)
-
+
     @plotPoints(@smallestX, @largestX)

     [cMin, cMax] = [@xMin, @xMax]
@@ -282,13 +282,13 @@
     # # TODO: broken (a major pain in my ass)
     # step = 1.5
     # zoom = setInterval (=>
-
+
     #   # gradually expand zooming bounds
     #   cMin -= step
     #   if cMin < @smallestX then cMin = @smallestX
     #   cMax += step
     #   if cMax > @largestX then cMax = @largestX
-
+
     #   # console.log "[cMin,cMax] = [#{cMin},#{cMax}]"
     #   @plotPoints(cMin,cMax)

@@ -296,7 +296,7 @@
     #     clearInterval zoom
     #     classifier.el.find('#graph').removeClass('is-zooming')
     #     @plotPoints(@smallestX, @largestX)
-    #     unless callback is undefined
+    #     unless callback is undefined
     #       callback.apply()
     #       classifier.el.find("#zoom-button").removeClass("zoomed")
     #       classifier.el.find("#zoom-button").removeClass("allowZoomOut") # for last zoom level
@@ -400,7 +400,7 @@
       continue if i is 0 # skip first value

       # draw ticks (bottom)
-      @ctx.beginPath()
+      @ctx.beginPath()
       @ctx.moveTo( @toPixels(tick)+@leftPadding, @canvas.height )
       if i % majorTickInterval is 0
         @ctx.lineTo( @toPixels(tick)+@leftPadding, @canvas.height - tickMajorLength ) # major tick
@@ -426,7 +426,7 @@
       @ctx.fillText( 'DAYS', textSpacing+10+@leftPadding, @canvas.height - textSpacing )

       # draw ticks (top)
-      @ctx.beginPath()
+      @ctx.beginPath()
       @ctx.moveTo( @toPixels(tick)+@leftPadding, 0 )
       if i % majorTickInterval is 0
         @ctx.lineTo( @toPixels(tick)+@leftPadding, 0 + tickMajorLength ) # major tick
@@ -439,7 +439,7 @@
       # top numbers
       if (i % 4) is 0 # zoomed out
         @ctx.fillText( (tick + @originalMin).toFixed(2), @toPixels(tick)+@leftPadding, 0 + textSpacing+10 )
-
+
   drawYTickMarks: (yMin, yMax) ->
     # generate intervals
     yTicks = []
@@ -453,7 +453,7 @@
     for stepFactor in [-numStepsDown..numStepsUp]
       tickValue = 1+stepFactor*yStep
       unless tickValue >= yMax or tickValue <=yMin
-        if tickValue is 1.0
+        if tickValue is 1.0
           if (tickIdx%2 is 0)
             meanTickIndexIsEven = true
         yTicks.push tickValue
@@ -467,7 +467,7 @@
     tickMinorLength = 5
     tickMajorLength = 10
     tickWidth = 1
-    tickColor = '#323232' #'rgba(200,20,20,1)'
+    tickColor = '#323232' #'rgba(200,20,20,1)'
     textColor = '#323232'
     textSpacing = 15
     majorTickInterval = 2
@@ -488,7 +488,7 @@
       @ctx.font = '10pt Arial'
       @ctx.textAlign = 'left'
       @ctx.fillStyle = textColor
-      @ctx.beginPath()
+      @ctx.beginPath()
       @ctx.moveTo( 0, tickPos )

       # make sure 1.00 (mean) tickmark is labeled
@@ -513,7 +513,7 @@

   clearCanvas: -> @ctx.clearRect(0,0,@canvas.width, @canvas.height)

-  toPixels: (dataPoint, printValue) ->
+  toPixels: (dataPoint, printValue) ->
     pixel = ( (parseFloat(dataPoint) - parseFloat(@xMin)) / (parseFloat(@xMax) - parseFloat(@xMin)) ) * \
       ( parseFloat(@canvas.width) - parseFloat(@leftPadding) )
     if printValue
@@ -546,7 +546,7 @@
   shakeGraph: ->
     graph = $('#graph-container')
     return if graph.hasClass('shaking')
-    graph.addClass('shaking')
+    graph.addClass('shaking')
     graph.effect( "shake", {times:4, distance: 2}, 700, =>
         graph.removeClass('shaking')
       ) # eventually remove jquery ui dependency?


#16 - Start big database refactor by generating new tables

--- app/controllers/database.coffee
+++ app/controllers/database.coffee
@@ -32,59 +32,90 @@
 # Initialise Nitro database
 setup = ->

-  log = console.log.bind(console)
-
-  # Create 'user' table
-  query("""
+  query """
     CREATE TABLE IF NOT EXISTS `user` (
-     `id`            int(11)        NOT NULL    AUTO_INCREMENT,
-     `name`          varchar(100)   NOT NULL,
-     `email`         varchar(100)   NOT NULL,
-     `password`      char(60)       NOT NULL,
-     `pro`           tinyint(1)     NOT NULL,
-     `data_task`     mediumblob     NOT NULL,
-     `data_list`     mediumblob     NOT NULL,
-     `data_pref`     mediumblob     NOT NULL,
-     `data_time`     mediumblob     NOT NULL,
-     `index_task`    int(11)        NOT NULL    DEFAULT '0',
-     `index_list`    int(11)        NOT NULL    DEFAULT '0',
-     `created_at`    timestamp      NOT NULL    DEFAULT '0000-00-00 00:00:00',
-     `updated_at`    timestamp      NOT NULL    DEFAULT CURRENT_TIMESTAMP       ON UPDATE CURRENT_TIMESTAMP,
+      `id`           int(11)        unsigned   NOT NULL    AUTO_INCREMENT,
+      `name`         varchar(100)              NOT NULL,
+      `email`        varchar(100)              NOT NULL,
+      `password`     char(60)                  NOT NULL,
+      `pro`          tinyint(1)     unsigned   NOT NULL,
+      `created_at`   timestamp                 NOT NULL     DEFAULT CURRENT_TIMESTAMP,
       PRIMARY KEY (`id`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;
-  """).fail(log)
-
-  query("""
-    CREATE TABLE IF NOT EXISTS `user_register` (
-     `id`         int(11)      NOT NULL  AUTO_INCREMENT,
-     `token`      char(22)     NOT NULL,
-     `name`       varchar(100) NOT NULL,
-     `email`      varchar(100) NOT NULL,
-     `password`   char(60)     NOT NULL,
-     `created_at` timestamp    NOT NULL  DEFAULT CURRENT_TIMESTAMP,
-      PRIMARY KEY (`id`, `token`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;
-  """).fail(log)
-
-  query("""
-    CREATE TABLE IF NOT EXISTS `user_login` (
-     `user_id`    int(11)   NOT NULL,
-     `token`      char(64)  NOT NULL,
-     `created_at` timestamp NOT NULL  DEFAULT CURRENT_TIMESTAMP,
-      PRIMARY KEY (`user_id`, `token`),
-      FOREIGN KEY (`user_id`) REFERENCES user(`id`) ON DELETE CASCADE
-    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-  """).fail(log)
-
-  query("""
-    CREATE TABLE IF NOT EXISTS `user_reset` (
-     `user_id`    int(11)      NOT NULL,
-     `token`      char(22)     NOT NULL,
-     `created_at` timestamp    NOT NULL  DEFAULT CURRENT_TIMESTAMP,
-      PRIMARY KEY (`user_id`, `token`),
-      FOREIGN KEY (`user_id`) REFERENCES user(`id`) ON DELETE CASCADE
-    ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;
-  """).fail(log)
+    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+  """
+
+  query """
+    CREATE TABLE IF NOT EXISTS `task` (
+      `id`          int(11)         unsigned   NOT NULL    AUTO_INCREMENT,
+      `user_id`     int(11)         unsigned   NOT NULL,
+      `list_id`     int(11)         unsigned   NOT NULL,
+      `name`        varchar(150)               NOT NULL    DEFAULT '',
+      `notes`       varchar(400)               NOT NULL    DEFAULT '',
+      `priority`    tinyint(4)      unsigned   NOT NULL,
+      `completed`   bigint(20)      unsigned   NOT NULL,
+      `date`        bigint(20)      unsigned   NOT NULL,
+      PRIMARY KEY (`id`,`user_id`),
+      CONSTRAINT `task_user_id` FOREIGN KEY (`user_id`)
+      REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+  """
+
+  query """
+    CREATE TABLE IF NOT EXISTS `list` (
+      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+      `user_id` int(11) unsigned NOT NULL,
+      `name` varchar(150) NOT NULL DEFAULT '',
+      PRIMARY KEY (`id`),
+      CONSTRAINT `list_user_id` FOREIGN KEY (`user_id`)
+      REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+  """
+
+  query """
+    CREATE TABLE IF NOT EXISTS `list_tasks` (
+      `list_id` int(11) unsigned NOT NULL,
+      `task_id` int(11) unsigned NOT NULL,
+      PRIMARY KEY (`list_id`,`task_id`),
+      CONSTRAINT `list_tasks_task_id` FOREIGN KEY (`task_id`)
+      REFERENCES `task` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+      CONSTRAINT `list_tasks_list_id` FOREIGN KEY (`list_id`)
+      REFERENCES `list` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+  """
+
+  query """
+    CREATE TABLE IF NOT EXISTS `register` (
+      `id`           int(11)        unsigned   NOT NULL    AUTO_INCREMENT,
+      `token`        char(22)                  NOT NULL,
+      `name`         varchar(100)              NOT NULL,
+      `email`        varchar(100)              NOT NULL,
+      `password`     char(60)                  NOT NULL,
+      `created_at`   timestamp                 NOT NULL    DEFAULT CURRENT_TIMESTAMP,
+      PRIMARY KEY (`id`,`token`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+  """
+
+  query """
+    CREATE TABLE IF NOT EXISTS `login` (
+      `user_id`      int(11)        unsigned   NOT NULL,
+      `token`        char(64)                  NOT NULL,
+      `created_at`   timestamp                 NOT NULL    DEFAULT CURRENT_TIMESTAMP,
+      PRIMARY KEY (`user_id`,`token`),
+      CONSTRAINT `login_user_id` FOREIGN KEY (`user_id`)
+      REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+  """
+
+  query """
+    CREATE TABLE IF NOT EXISTS `reset` (
+      `user_id`      int(11)        unsigned   NOT NULL,
+      `token`        char(22)                  NOT NULL,
+      `created_at`   timestamp                 NOT NULL    DEFAULT CURRENT_TIMESTAMP,
+      PRIMARY KEY (`user_id`,`token`),
+      CONSTRAINT `reset_user_id` FOREIGN KEY (`user_id`)
+      REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+  """

 close = ->
   db.end()
@@ -170,25 +201,25 @@
 # -----------------------------------------------------------------------------

 login_add = (user, token) ->
-  sql = 'INSERT INTO user_login (user_id, token) VALUES (?, ?)'
+  sql = 'INSERT INTO login (user_id, token) VALUES (?, ?)'
   query(sql, [user, token])

 login_exists = (user, token) ->
-  sql = 'SELECT user_id FROM user_login WHERE user_id=? AND token=?'
+  sql = 'SELECT user_id FROM login WHERE user_id=? AND token=?'
   query(sql, [user, token]).then (row) ->
     return !! row.length

 login_expire = ->
   date = Date.now() - 1209600 # 60 * 60 * 24 * 14
-  sql = 'DELETE FROM user_login WHERE created_at < ?'
+  sql = 'DELETE FROM login WHERE created_at < ?'
   query(sql, date)

 login_remove = (user, token) ->
-  sql = 'DELETE FROM user_login WHERE user_id=? AND token=?'
+  sql = 'DELETE FROM login WHERE user_id=? AND token=?'
   query(sql, [user, token])

 login_remove_all = (user) ->
-  sql = 'DELETE FROM user_login WHERE user_id=?'
+  sql = 'DELETE FROM login WHERE user_id=?'
   query(sql, user)

 # -----------------------------------------------------------------------------
@@ -216,7 +247,7 @@
 ###

 register_add = (info) ->
-  sql = 'INSERT INTO user_register SET ?'
+  sql = 'INSERT INTO register SET ?'
   query(sql, info).then (data) ->
     return data.insertId + '_' + info.token

@@ -237,7 +268,7 @@
   return Q.resolve() unless match
   [id, token] = match

-  sql = 'SELECT id, name, email, password FROM user_register WHERE id=? AND token=?'
+  sql = 'SELECT id, name, email, password FROM register WHERE id=? AND token=?'
   query(sql, [id, token]).then (rows) ->
     return unless rows.length
     return rows[0]
@@ -250,7 +281,7 @@
 ###

 register_remove = (id) ->
-  sql = 'DELETE FROM user_register WHERE id=?'
+  sql = 'DELETE FROM register WHERE id=?'
   query(sql, id)

@@ -259,7 +290,7 @@
 # -----------------------------------------------------------------------------

 reset_add = (id, token) ->
-  sql = 'INSERT INTO user_reset (user_id, token) VALUES (?, ?)'
+  sql = 'INSERT INTO reset (user_id, token) VALUES (?, ?)'
   query(sql, [id, token]).then ->
     return id + '_' + token

@@ -267,7 +298,7 @@
   token = parseToken(token)
   return Q.reject(new Error) unless token

-  sql = 'SELECT user_id FROM user_reset WHERE user_id=? AND token=?'
+  sql = 'SELECT user_id FROM reset WHERE user_id=? AND token=?'
   query(sql, token).then (rows) ->
     throw new Error() unless rows.length
     return rows[0].user_id
@@ -277,7 +308,7 @@
   token = parseToken(token)
   return Q.reject(new Error) unless token

-  sql = 'DELETE FROM user_reset WHERE user_id=? AND token=?'
+  sql = 'DELETE FROM reset WHERE user_id=? AND token=?'
   query(sql, token)


Add a function that corrects old spread values

--- app/controllers/fan_tool.coffee
+++ app/controllers/fan_tool.coffee
@@ -171,3 +171,19 @@
     super

 module.exports = FanTool
+
+# This converts spreads made by the old fan tool into the value the user meant.
+window.correctSpread = do ->
+  {PI, cos, atan} = Math
+  toRad = (deg) -> deg / (180 / PI)
+  toDeg = (rad) -> rad * (180 / PI)
+  (spread) ->
+    adjacent = 100 # Doesn't actually matter
+    hypotenuse = adjacent / cos(toRad(spread))
+    opposite = hypotenuse * sin(toRad(spread))
+    inradius = (adjacent * opposite) / (adjacent + opposite + hypotenuse)
+    renderedOpposite = inradius * 2
+    renderedAdjacent = adjacent - renderedOpposite
+    renderedAngle = atan(renderedOpposite / renderedAdjacent)
+    renderedSpread = renderedAngle * 2
+    return toDeg(renderedSpread)


steward: if browser does not support history or hashchange disable all listeners and just render the first page

--- lib/web/views/Steward.coffee
+++ lib/web/views/Steward.coffee
@@ -20,13 +20,13 @@

 # Missing features:
 # - support for browsers without history & onhashchange -> disable
-# - querystring parameters as part of params
 # - copy build configs from bacon.js
 # - split to own repository
 # - more complete sinatra path parsing, JavascriptRouteParser
 # - test suite
 # - documentation
-# Planned features
+# Possible features
+# - querystring parameters as part of params
 # - form support, catch form submits (how would this work?) get / post?
 # - chrome fails when converting plain url to hashbang url: %23
 # - clarify when fallbackRoute is used
@@ -81,22 +81,23 @@
       @init = false

   attachClickListener: =>
-    @addListener document, "click", (event) =>
-      target = event.target
-      if( target && target.tagName == "A")
-        target.href = target.href # fix for IE
-        if !@metakeyPressed(event) && @targetAttributeIsCurrentWindow(target) && @targetHostSame(target)
-          href = target.attributes.href.nodeValue
-          @log("Processing click", href)
-          ret = @exec(href)
-          if !ret
-            href = target.pathname # try with full path name
+    if @pushStateSupport || @hashchangeSupport
+      @addListener document, "click", (event) =>
+        target = event.target
+        if( target && target.tagName == "A")
+          target.href = target.href # fix for IE
+          if !@metakeyPressed(event) && @targetAttributeIsCurrentWindow(target) && @targetHostSame(target)
+            href = target.attributes.href.nodeValue
+            @log("Processing click", href)
             ret = @exec(href)
-          if ret
-            @log("New url", href)
-            event.preventDefault();
-            @previousView = href
-            @updateUrl(href)
+            if !ret
+              href = target.pathname # try with full path name
+              ret = @exec(href)
+            if ret
+              @log("New url", href)
+              event.preventDefault();
+              @previousView = href
+              @updateUrl(href)

   metakeyPressed: (event) =>
     (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)
@@ -140,13 +141,14 @@
       if window.location.hash.substring(0, 2) == "#!" && @find(window.location.hash.substring(2))
         forceUrlUpdate = initialUrl = window.location.hash.substring(2)
     else
-      if window.location.hash == "" && @find(initialUrl)
-        if @hashBaseUrl && @hashBaseUrl != initialUrl
-          window.location.pathname = @hashBaseUrl + "#!" + initialUrl
-        else
-          window.location.hash = "!" + initialUrl
-      if window.location.hash.substring(0, 2) == "#!"
-        initialUrl = window.location.hash.substring(2)
+      if @hashchangeSupport
+        if window.location.hash == "" && @find(initialUrl)
+          if @hashBaseUrl && @hashBaseUrl != initialUrl
+            window.location.pathname = @hashBaseUrl + "#!" + initialUrl
+          else
+            window.location.hash = "!" + initialUrl
+        if window.location.hash.substring(0, 2) == "#!"
+          initialUrl = window.location.hash.substring(2)
     @renderUrl(initialUrl)
     if forceUrlUpdate
       @updateUrl(forceUrlUpdate)
@@ -168,7 +170,8 @@
       if @pushStateSupport
         history.pushState({ }, document.title, href)
       else
-        window.location.hash = "!" + href
+        if @hashchangeSupport
+          window.location.hash = "!" + href

   addListener: (element, event, fn) =>
     if element.addEventListener  # W3C DOM


Fix execution error catching

Fix execution error catching

--- lib/workspace-manager.coffee
+++ lib/workspace-manager.coffee
@@ -276,6 +276,8 @@
             message =
               'The text editor\'s buffer is not saved yet.\nSave your program in
               order to be able to execute it.'
+          else
+            message = error.message
         notificationUtils.addError message,
           head: 'Oh no! Execution failed!'
           important: true


Add comments for getETA doc

Add comments for getETA doc

--- lib/getETA.coffee
+++ lib/getETA.coffee
@@ -1,18 +1,18 @@
 Meteor.methods {
   getETA: (stop,route,direction)->
-    check stop,String
-    check route,String
-    check direction,String
+    check stop,String # This should be the stop_id
+    check route,String # This should be the route_id
+    check direction,String # This should be the direction, as specified in the feed

-    # Get data from backend
-    #HTTP.get 'http://localhost:
+    # Get data from backend (check http://docs.meteor.com/#http for usage)
+    #HTTP.get 'http://localhost/get...'

     # For now, we just produce dummy data
     return {
-      eta: do -> Random.choice [0..10]
+      eta: do -> Random.choice [0..10] # The nearest arrival ETA
       next: [
-        do -> 15 + Random.choice [0..10]
-        do -> 30 + Random.choice [0..10]
+        do -> 15 + Random.choice [0..10] # The next arrival ETA
+        do -> 30 + Random.choice [0..10] # etc...
       ]
     }
 }


Language Speciefic error messages

Language Speciefic error messages

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -62,7 +62,7 @@
             #     console.log 'stderr: ', stderr
             #     if error != null
             #         console.log 'exec error: ', error
-     stderr: (data, caption = "kernel answer") ->
+     stderr: (data, caption) ->
         detail = data.toString()
         method = 'addInfo'
         if /warning/gi.test detail
@@ -71,7 +71,7 @@
             method = 'addError'

         console.error "kernel process received on stderr:", detail
-        atom.notifications[method] "Hydrogen: #{caption}",
+        atom.notifications[method] "#{@language} Kernel: #{caption}",
          detail: detail, dismissable: /error/i.test method

     connect: ->


Add root directions compatible

Add root directions compatible

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -28,7 +28,7 @@
     response = new VMAPResponse()
     response.adbreaks = [] # Hack

-    unless xml?.documentElement? and xml.documentElement.nodeName is "VMAP"
+    unless xml?.documentElement? and xml.documentElement.nodeName is ("VMAP" or "vmap:VMAP")
       return cb()

     for node in xml.documentElement.childNodes


ComputeController.UI: ask dialog wip

ComputeController.UI: ask dialog wip

--- client/Main/providers/computecontroller.ui.coffee
+++ client/Main/providers/computecontroller.ui.coffee
@@ -103,3 +103,14 @@

       callback          : (data)->
         form.emit "Submit", data
+
+  @askFor: (action, target, callback)->
+
+    modal = KDModalView.confirm
+      title       : "Remove machine"
+      description : "Do you want to remove ?"
+      ok          :
+        title     : "Yes, remove"
+        callback  : ->
+          modal.destroy()
+          callback()


login: loginview is hidden when out of viewport

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -7,6 +7,7 @@
   constructor:(options = {}, data)->

     {entryPoint} = KD.config
+    options.cssClass = 'hidden'

     super options, data

@@ -282,6 +283,7 @@

       @emit "LoginViewHidden"
       @hidden = yes
+      @hideTimer = @utils.wait 2000, => @setClass 'hidden'
       callback?()

     unless @hidden then do cb
@@ -289,15 +291,17 @@

   show:(callback)->

-    @setY 0
-
+    @utils.killWait @hideTimer
     cb = =>
       @emit "LoginViewShown"
       @hidden = no
       callback?()

-    unless @hidden then do cb
-    else @once "transitionend", cb
+    @unsetClass 'hidden'
+    @utils.defer =>
+      @setY 0
+      unless @hidden then do cb
+      else @once "transitionend", cb

   click:(event)->
     if $(event.target).is('.login-screen')


Stub the repo in ConflictedEditor specs.

--- spec/conflicted-editor-spec.coffee
+++ spec/conflicted-editor-spec.coffee
@@ -52,6 +52,9 @@
         editor = editorView.getModel()
         state =
           isRebase: false
+          repo:
+            getWorkingDirectory: -> ""
+            relativize: (filepath) -> filepath

         m = new ConflictedEditor(state, pkg, editor)
         m.mark()
@@ -209,6 +212,9 @@
         editor = editorView.getModel()
         state =
           isRebase: true
+          repo:
+            getWorkingDirectory: -> ""
+            relativize: (filepath) -> filepath

         m = new ConflictedEditor(state, pkg, editor)
         m.mark()


add throw/catch test

add throw/catch test

--- tests/batman/model/record_lifecycle_test.coffee
+++ tests/batman/model/record_lifecycle_test.coffee
@@ -124,3 +124,20 @@
         product._push(13)
         deepEqual(product.callOrder, @Product.loadingCallOrder.concat(@Product.expectedCallOrder))
         QUnit.start()
+
+asyncTest "throwing an error stops the storage operation", ->
+
+  @Product.find 10, (err, product) =>
+    product._push(105)
+    product.set('foo', 'bar')
+    product.on 'before saving', ->
+      throw "Stop saving!"
+
+    try
+      product.save (err) =>
+        throw err if err
+        product._push(7)
+    catch err
+      equal "#{err}", "Stop saving!"
+      deepEqual product.callOrder, @Product.loadingCallOrder.concat([0,1])
+      QUnit.start()


Use config to get page size

Use config to get page size

--- app/talk/board-preview.cjsx
+++ app/talk/board-preview.cjsx
@@ -9,6 +9,9 @@
 merge = require 'lodash.merge'
 Avatar = require '../partials/avatar'
 getPageOfComment = require './lib/get-page-of-comment'
+config = require './config'
+
+PAGE_SIZE = config.boardPageSize

 module?.exports = React.createClass
   displayName: 'TalkBoardDisplay'
@@ -43,7 +46,7 @@
                   </Link>
                 }</PromiseRenderer>{' '}

-                  <Link to="#{@projectPrefix()}talk-discussion" params={merge({}, {board: discussion.board_id, discussion: discussion.id}, @props.params)} query={page: getPageOfComment(comment, discussion, 10)}>{discussion.title}</Link>{' '}
+                <Link to="#{@projectPrefix()}talk-discussion" params={merge({}, {board: discussion.board_id, discussion: discussion.id}, @props.params)} query={page: getPageOfComment(comment, discussion, PAGE_SIZE)}>{discussion.title}</Link>{' '}
                 <span>{timeAgo(discussion.updated_at)}</span>

               </div>


Add failing spec

Add failing spec

--- spec/package-generator-spec.coffee
+++ spec/package-generator-spec.coffee
@@ -96,6 +96,23 @@
         expect(apmExecute.mostRecentCall.args[0]).toBe atom.packages.getApmPath()
         expect(apmExecute.mostRecentCall.args[1]).toEqual ['init', '--package', "#{path.join(path.dirname(packagePath), "camel-case-is-for-the-birds")}"]

+    it "normalizes the package's path", ->
+      packagePath = path.join("~", "the-package")
+      atom.commands.dispatch(getWorkspaceView(), "package-generator:generate-package")
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        packageGeneratorView = $(getWorkspaceView()).find(".package-generator").view()
+        packageGeneratorView.miniEditor.setText(packagePath)
+        apmExecute = spyOn(packageGeneratorView, 'runCommand')
+        atom.commands.dispatch(packageGeneratorView.element, "core:confirm")
+
+        expect(apmExecute).toHaveBeenCalled()
+        expect(apmExecute.mostRecentCall.args[0]).toBe atom.packages.getApmPath()
+        expect(apmExecute.mostRecentCall.args[1]).toEqual ['init', '--package', "#{fs.normalize(packagePath)}"]
+
     describe 'when creating a package', ->
       beforeEach ->
         atom.commands.dispatch(getWorkspaceView(), "package-generator:generate-package")


client: welcome: hide welcome modal if route changes

--- client/home/lib/welcome/welcomemodal.coffee
+++ client/home/lib/welcome/welcomemodal.coffee
@@ -10,6 +10,7 @@

     super options, data

+    kd.singletons.router.once 'RouteInfoHandled', @bound 'destroy'

   viewAppended: ->


fix(spec): fix failing draft specs

fix(spec): fix failing draft specs

--- spec/stores/task-queue-spec.coffee
+++ spec/stores/task-queue-spec.coffee
@@ -81,7 +81,7 @@
     it "only returns a task that matches the criteria", ->
       expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B1'})).toEqual(@subclassB1)
       expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B2'})).toEqual(@subclassB2)
-      expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B3'})).toEqual(null)
+      expect(TaskQueue.findTask(TaskSubclassB, {bProp: 'B3'})).toEqual(undefined)

   describe "enqueue", ->
     beforeEach ->


Use ... instead of .. range operator to exclude final value (our ranges are 0-based); properly pair x and y with width and height, respectively

--- src-common/commons/geometry.coffee
+++ src-common/commons/geometry.coffee
@@ -46,8 +46,8 @@
 # Returns all the whole-number points within the provided rect.
 root.rect.surfacePoints = (rect, predicate) ->
   pts = []
-  for y in [0..(Math.floor(rect.size.width))]
-    for x in [0..(Math.floor(rect.size.height))]
+  for y in [0...(Math.floor(rect.size.height))]
+    for x in [0...(Math.floor(rect.size.width))]
       do (x,y) ->
         if predicate?
           if predicate(rect.point.x + x, rect.point.y + y)


Fix spec initialization ordering

Fix spec initialization ordering

--- cms/static/coffee/spec/views/upload_spec.coffee
+++ cms/static/coffee/spec/views/upload_spec.coffee
@@ -7,72 +7,78 @@
             modal_helpers.installModalTemplates()
             appendSetFixtures($("<script>", {id: "upload-dialog-tpl", type: "text/template"}).text(tpl))
             CMS.URL.UPLOAD_ASSET = "/upload"
-
             @model = new FileUpload(
               mimeTypes: ['application/pdf']
             )
             @dialogResponse = dialogResponse = []
-            @view = new UploadDialog(
-              model: @model,
+            @mockFiles = []
+
+        afterEach ->
+            delete CMS.URL.UPLOAD_ASSET
+            if (@lastView && modal_helpers.isShowingModal(@lastView))
+                @lastView.hide()
+
+        createTestView = (test) ->
+            view = new UploadDialog(
+              model: test.model,
               url:  CMS.URL.UPLOAD_ASSET,
               onSuccess: (response) =>
-                dialogResponse.push(response.response)
+                test.dialogResponse.push(response.response)
             )
-            spyOn(@view, 'remove').and.callThrough()
+            spyOn(view, 'remove').and.callThrough()

             # create mock file input, so that we aren't subject to browser restrictions
-            @mockFiles = []
             mockFileInput = jasmine.createSpy('mockFileInput')
-            mockFileInput.files = @mockFiles
+            mockFileInput.files = test.mockFiles
             jqMockFileInput = jasmine.createSpyObj('jqMockFileInput', ['get', 'replaceWith'])
             jqMockFileInput.get.and.returnValue(mockFileInput)
-            realMethod = @view.$
-            spyOn(@view, "$").and.callFake (selector) ->
+            originalView$ = view.$
+            spyOn(view, "$").and.callFake (selector) ->
                 if selector == "input[type=file]"
                     jqMockFileInput
                 else
-                    realMethod.apply(this, arguments)
-
-        afterEach ->
-            delete CMS.URL.UPLOAD_ASSET
-            if (@view && modal_helpers.isShowingModal(@view))
-                @view.hide()
+                    originalView$.apply(this, arguments)
+            @lastView = view

         describe "Basic", ->
             it "should render without a file selected", ->
-                @view.render()
-                expect(@view.$el).toContainElement("input[type=file]")
-                expect(@view.$(".action-upload")).toHaveClass("disabled")
+                view = createTestView(this)
+                view.render()
+                expect(view.$el).toContainElement("input[type=file]")
+                expect(view.$(".action-upload")).toHaveClass("disabled")

             it "should render with a PDF selected", ->
+                view = createTestView(this)
                 file = {name: "fake.pdf", "type": "application/pdf"}
                 @mockFiles.push(file)
                 @model.set("selectedFile", file)
-                @view.render()
-                expect(@view.$el).toContainElement("input[type=file]")
-                expect(@view.$el).not.toContainElement("#upload_error")
-                expect(@view.$(".action-upload")).not.toHaveClass("disabled")
+                view.render()
+                expect(view.$el).toContainElement("input[type=file]")
+                expect(view.$el).not.toContainElement("#upload_error")
+                expect(view.$(".action-upload")).not.toHaveClass("disabled")

             it "should render an error with an invalid file type selected", ->
+                view = createTestView(this)
                 file = {name: "fake.png", "type": "image/png"}
                 @mockFiles.push(file)
                 @model.set("selectedFile", file)
-                @view.render()
-                expect(@view.$el).toContainElement("input[type=file]")
-                expect(@view.$el).toContainElement("#upload_error")
-                expect(@view.$(".action-upload")).toHaveClass("disabled")
+                view.render()
+                expect(view.$el).toContainElement("input[type=file]")
+                expect(view.$el).toContainElement("#upload_error")
+                expect(view.$(".action-upload")).toHaveClass("disabled")

             it "should render an error with an invalid file type after a correct file type selected", ->
+                view = createTestView(this)
                 correctFile = {name: "fake.pdf", "type": "application/pdf"}
                 inCorrectFile = {name: "fake.png", "type": "image/png"}
                 event = {}
-                @view.render()
+                view.render()

                 event.target = {"files": [correctFile]}
-                @view.selectFile(event)
-                expect(@view.$el).toContainElement("input[type=file]")
-                expect(@view.$el).not.toContainElement("#upload_error")
-                expect(@view.$(".action-upload")).not.toHaveClass("disabled")
+                view.selectFile(event)
+                expect(view.$el).toContainElement("input[type=file]")
+                expect(view.$el).not.toContainElement("#upload_error")
+                expect(view.$(".action-upload")).not.toHaveClass("disabled")

                 realMethod = @model.set
                 spyOn(@model, "set").and.callFake (data) ->
@@ -83,10 +89,10 @@
                     realMethod.apply(this, arguments)

                 event.target = {"files": [inCorrectFile]}
-                @view.selectFile(event)
-                expect(@view.$el).toContainElement("input[type=file]")
-                expect(@view.$el).toContainElement("#upload_error")
-                expect(@view.$(".action-upload")).toHaveClass("disabled")
+                view.selectFile(event)
+                expect(view.$el).toContainElement("input[type=file]")
+                expect(view.$el).toContainElement("#upload_error")
+                expect(view.$(".action-upload")).toHaveClass("disabled")

         describe "Uploads", ->
             beforeEach ->
@@ -96,38 +102,32 @@
                 @clock.restore()

             it "can upload correctly", ->
-                requests = AjaxHelpers["requests"](this)
-
-                @view.render()
-                @view.upload()
+                requests = AjaxHelpers.requests(this);
+                view = createTestView(this)
+                view.render()
+                view.upload()
                 expect(@model.get("uploading")).toBeTruthy()
-                expect(requests.length).toEqual(1)
-                request = requests[0]
-                expect(request.url).toEqual("/upload")
-                expect(request.method).toEqual("POST")
-
-                request.respond(200, {"Content-Type": "application/json"},
-                        '{"response": "dummy_response"}')
+                AjaxHelpers.expectRequest(requests, "POST", "/upload")
+                AjaxHelpers.respondWithJson(requests, { response: "dummy_response"})
                 expect(@model.get("uploading")).toBeFalsy()
                 expect(@model.get("finished")).toBeTruthy()
                 expect(@dialogResponse.pop()).toEqual("dummy_response")

             it "can handle upload errors", ->
-                requests = AjaxHelpers["requests"](this)
-
-                @view.render()
-                @view.upload()
-                requests[0].respond(500)
+                requests = AjaxHelpers.requests(this);
+                view = createTestView(this)
+                view.render()
+                view.upload()
+                AjaxHelpers.respondWithError(requests)
                 expect(@model.get("title")).toMatch(/error/)
-                expect(@view.remove).not.toHaveBeenCalled()
+                expect(view.remove).not.toHaveBeenCalled()

             it "removes itself after two seconds on successful upload", ->
-                requests = AjaxHelpers["requests"](this)
-
-                @view.render()
-                @view.upload()
-                requests[0].respond(200, {"Content-Type": "application/json"},
-                        '{"response": "dummy_response"}')
-                expect(modal_helpers.isShowingModal(@view)).toBeTruthy();
+                requests = AjaxHelpers.requests(this);
+                view = createTestView(this)
+                view.render()
+                view.upload()
+                AjaxHelpers.respondWithJson(requests, { response: "dummy_response"})
+                expect(modal_helpers.isShowingModal(view)).toBeTruthy();
                 @clock.tick(2001)
-                expect(modal_helpers.isShowingModal(@view)).toBeFalsy();
+                expect(modal_helpers.isShowingModal(view)).toBeFalsy();


Added assertions and increased wait time

--- client/test/lib/helpers/environmenthelpers.coffee
+++ client/test/lib/helpers/environmenthelpers.coffee
@@ -303,6 +303,9 @@
       .waitForElementVisible  vmSelector + ' .vm.terminating', 20000
       .waitForElementVisible  vmSelector + ' .vm.building', 300000
       .waitForElementVisible  vmSelector + ' .vm.running', 600000
+      .waitForElementVisible  vmSelector, 20000
+      .assert.containsText    vmSelector, 'koding-vm-0'
+

   terminateVM: (browser) ->
@@ -310,26 +313,31 @@
     terminateSelector       = '.kdmodal.AppModal .advanced .advanced.terminate'
     proceedSelector         = '.kdmodal.with-buttons .kdbutton.red'
     terminatedLabelSelector = '.kdmodal.env-modal .state-label.terminated'
+    vmSelector              = '.sidebar-machine-box'

     browser
       .waitForElementVisible  terminateSelector, 20000
       .click                  terminateSelector
       .waitForElementVisible  proceedSelector, 20000
       .click                  proceedSelector
-      .waitForElementVisible  terminatedLabelSelector, 100000
+      .waitForElementVisible  vmSelector + ' .vm.terminating', 200000
+      .waitForElementVisible  terminatedLabelSelector, 200000
       .assert.containsText    terminatedLabelSelector, "successfully deleted" #Assertion

   createAnewVMforNonPayingUsers: (browser) ->

-  	createVMbutton    = '.content-container .kdbutton'
-  	addKodingVmButton = '.environments-modal .kdbutton.add-vm-button'
-  	vmSelector        = '.sidebar-machine-box .notinitialized'
-
-  	browser
-  	  .waitForElementVisible  createVMbutton, 20000
-  	  .click                  createVMbutton
-  	  .waitForElementVisible  addKodingVmButton, 20000
-  	  .click                  addKodingVmButton
-  	  .waitForElementVisible  vmSelector, 20000
-  	  .assert.containsText    vmSelector, 'koding-vm-0'+    createVMbutton    = '.content-container .kdbutton'
+    addKodingVmButton = '.environments-modal .kdbutton.add-vm-button'
+    vmSelector        = '.sidebar-machine-box .notinitialized'
+    vmStateModal      = '.env-machine-state .kdmodal-content'
+
+    browser
+      .waitForElementVisible  createVMbutton, 20000
+      .click                  createVMbutton
+      .waitForElementVisible  addKodingVmButton, 20000
+      .click                  addKodingVmButton
+      .waitForElementVisible  vmSelector, 20000
+      .assert.containsText    vmSelector, 'koding-vm-0'
+      .waitForElementVisible  vmStateModal + ' .turn-on', 20000
+      .assert.containsText    vmStateModal + ' .turn-on', 'TURN IT ON'


Added new methods, fixed some issues.

Added new methods, fixed some issues.

--- components/inputs/KDInputViewWithPreview.coffee
+++ components/inputs/KDInputViewWithPreview.coffee
@@ -1,8 +1,7 @@
 class KDInputViewWithPreview extends KDInputView

-  constructor:(options={},data)->
-
-    options.preview               ?= {}
+  constructor:(options = {}, data)->
+    options.preview               or= {}
     options.preview.autoUpdate    ?= yes
     options.preview.language      or= "markdown"
     options.preview.showInitially ?= yes
@@ -21,14 +20,14 @@
         @generatePreview()
       yes

-    super options,data
+    super options, data

     @setClass "kdinputwithpreview"

     @showPreview = options.preview.showInitially or no

     @previewOnOffLabel = new KDLabelView
-      cssClass      : "preview_switch_label"
+      cssClass      : "preview_switch_label unselectable"
       title         : "Preview"
       tooltip       :
         title       : "Show/hide the Text Preview Box"
@@ -58,167 +57,28 @@
       cssClass : "preview_switch"

     if @options.showHelperModal
-
-      @markdownLink = new KDCustomHTMLView
-        tagName     : 'a'
-        name        : "markdownLink"
-        value       : "markdown is enabled"
-        attributes  :
-          href      : '#'
-          value     : "markdown syntax is enabled"
-        cssClass    : 'markdown-link'
-        partial     : "What is Markdown?"
-        tooltip     :
-          title     : "Show available Markdown formatting syntax"
-        click       : (event)=>
-          if $(event.target).is 'span'
-            link.hide()
-          else
-            markdownText = new KDMarkdownModalText
-            modal = new KDModalView
-              title       : "How to use the Markdown syntax."
-              cssClass    : "what-you-should-know-modal markdown-cheatsheet"
-              height      : "auto"
-              overlay     : yes
-              width       : 500
-              content     : markdownText.markdownText()
-              buttons     :
-                Close     :
-                  title   : 'Close'
-                  style   : 'modal-clean-gray'
-                  callback: -> modal.destroy()
-
+      @markdownLink = @getMarkdownLink()
       @previewOnOffContainer.addSubView @markdownLink

-    # if @options.openMaximized
-    #   log 'opening maximized'
-    #   @hide()
-    #   @textContainer = new KDView
-    #     cssClass:"modal-fullscreen-text"
-
-    #   @text = new KDInputViewWithPreview
-    #     type : "textarea"
-    #     cssClass : "fullscreen-data kdinput text"
-    #     allowMaximized : no
-    #     defaultValue : @getValue()
-
-    #   @textContainer.addSubView @text
-
-    #   modal = new KDModalView
-    #     title       : "Please enter your content here."
-    #     cssClass    : "modal-fullscreen"
-    #     width       : window.innerWidth-100
-    #     height      : window.innerHeight-100
-    #     # width       : $(window).width()-100
-    #     # height      : $(window).height()-100
-    #     view        : @textContainer
-    #     position:
-    #       top       : 35
-    #       left      : 35
-    #     overlay     : yes
-    #     buttons     :
-    #       Cancel    :
-    #         title   : "Discard changes"
-    #         style   : "modal-clean-gray"
-    #         callback:=>
-    #           modal.destroy()
-    #       Apply     :
-    #         title   : "Apply changes"
-    #         style   : "modal-clean-gray"
-    #         callback:=>
-    #           @setValue @text.getValue()
-    #           @generatePreview()
-    #           modal.destroy()
-
-
-    #   @utils.defer =>
-
-    #     modal.$(".kdmodal-content").height modal.$(".kdmodal-inner").height()-modal.$(".kdmodal-buttons").height()-modal.$(".kdmodal-title").height() # minus the margin, border pixels too..
-    #     modal.$(".fullscreen-data").height modal.$(".kdmodal-content").height()-30-23+10
-    #     modal.$(".input_preview").height   modal.$(".kdmodal-content").height()-0-21+10
-    #     modal.$(".input_preview").css maxHeight:  modal.$(".kdmodal-content").height()-0-21+10
-    #     modal.$(".input_preview div.preview_content").css maxHeight:  modal.$(".kdmodal-content").height()-0-21
-
-    #     contentWidth = modal.$(".kdmodal-content").width()-40
-    #     halfWidth  = contentWidth / 2
-
-    #     @text.on "PreviewHidden", =>
-    #       modal.$(".fullscreen-data").width contentWidth #-(modal.$("div.preview_switch").width()+20)-10
-    #       # modal.$(".input_preview").width (modal.$("div.preview_switch").width()+20)
-
-    #     @text.on "PreviewShown", =>
-    #       modal.$(".fullscreen-data").width contentWidth-halfWidth-5
-    #       # modal.$(".input_preview").width halfWidth-5
-
-    #     modal.$(".fullscreen-data").width contentWidth-halfWidth-5
-    #     modal.$(".input_preview").width halfWidth-5
-
     if @options.allowMaximized
-      @fullScreenBtn = new KDButtonView
-        style           : "clean-gray small"
-        cssClass        : "fullscreen-button"
-        title           : "Fullscreen Edit"
-        icon            : no
-        tooltip         :
-          title         : "Open a Fullscreen Editor"
-        callback: =>
-          @textContainer = new KDView
-            cssClass:"modal-fullscreen-text"
-
-          @text = new KDInputViewWithPreview
-            type : "textarea"
-            cssClass : "fullscreen-data kdinput text"
-            allowMaximized : no
-            defaultValue : @getValue()
-
-          @textContainer.addSubView @text
-
-          @modal = new KDModalView
-            title       : "Please enter your content here."
-            cssClass    : "modal-fullscreen"
-            width       : window.innerWidth-100
-            height      : window.innerHeight-100
-            # width       : $(window).width()-100
-            # height      : $(window).height()-100
-            view        : @textContainer
-            position:
-              top       : 35
-              left      : 35
-            overlay     : yes
-            buttons     :
-              Apply     :
-                title   : "Apply changes"
-                style   : "modal-clean-gray"
-                callback:=>
-                  @setValue @text.getValue()
-                  @generatePreview()
-                  @modal.destroy()
-              Cancel    :
-                title   : "cancel"
-                style   : "modal-cancel"
-                callback:=> @modal.destroy()
-
-          @utils.defer =>
-            @setPaneSizes()
-
-          $(window).on "resize", =>
-            # (do/dont) Set width as "half" if preview is on
-            willSetWidths = @previewOnOffSwitch.defaultValue is on
-            @setPaneSizes(willSetWidths)
-
-      # @addSubView @fullScreenBtn
-      @previewOnOffContainer.addSubView @fullScreenBtn
+      @fullscreenEditButton = @getFullscreenEditButton()
+      @previewOnOffContainer.addSubView @fullscreenEditButton

     @previewOnOffContainer.addSubView @previewOnOffLabel
     @previewOnOffContainer.addSubView @previewOnOffSwitch

     @addSubView @previewOnOffContainer

-  setPaneSizes:(willSetWidths = yes)->
-    # selector performance ?
+    @listenWindowResize()
+
+  setPaneSizes:(opt_setWidths = yes)->
+    # For: "Uncaught TypeError: Cannot call method '$' of undefined"
+    return  unless @modal
+
+    # selector performance?
+    kdmodalContent = @modal.$(".kdmodal-content")
     fullscreenData = @modal.$(".fullscreen-data")
     inputPreview   = @modal.$(".input_preview")
-    kdmodalContent = @modal.$(".kdmodal-content")

     # minus the margin, border pixels too..
     kdmodalContent.height @modal.$(".kdmodal-inner").height() - @modal.$(".kdmodal-buttons").height() - @modal.$(".kdmodal-title").height()
@@ -227,29 +87,79 @@

     fullscreenData.height kdmodalContentHeight - 30 - 23 + 10
     inputPreview.height kdmodalContentHeight - 0 - 21 + 10
-    inputPreview.css
-      maxHeight :  kdmodalContentHeight - 0 - 21 + 10
-    @modal.$(".input_preview div.preview_content").css
-      maxHeight :  kdmodalContentHeight - 0 - 21
+    @modal.$(".input_preview div.preview_content").css "maxHeight",  kdmodalContentHeight - 0 - 21

     contentWidth = kdmodalContent.width() - 40
     halfWidth    = contentWidth / 2

     @text.on "PreviewHidden", =>
-      fullscreenData.width contentWidth # -(@modal.$("div.preview_switch").width() + 20) - 10
-      # inputPreview.width (@modal.$("div.preview_switch").width() + 20)
+      fullscreenData.width contentWidth

     @text.on "PreviewShown", =>
       fullscreenData.width contentWidth - halfWidth - 5
-      # inputPreview.width halfWidth - 5
-
-    if willSetWidths
+
+    if opt_setWidths
       fullscreenData.width contentWidth - halfWidth - 5
       inputPreview.width halfWidth - 5

+    # quick fix for "window.on resize"
+    @modal.$().height window.innerHeight - 55
+
+  getMarkdownLink:->
+    new KDCustomHTMLView
+      tagName     : "a"
+      cssClass    : "markdown-link unselectable"
+      partial     : "What is Markdown?"
+      tooltip     :
+        title     : "Show available Markdown formatting syntax"
+      click       : -> new MarkdownModal
+
+  getFullscreenEditButton:->
+    new KDButtonView
+      style           : "clean-gray small"
+      cssClass        : "fullscreen-button"
+      title           : "Fullscreen Edit"
+      icon            : no
+      tooltip         :
+        title         : "Open a Fullscreen Editor"
+      callback        : =>
+        @textContainer = new KDView
+          cssClass:"modal-fullscreen-text"
+
+        @text = new KDInputViewWithPreview
+          type           : "textarea"
+          cssClass       : "fullscreen-data kdinput text"
+          allowMaximized : no
+          defaultValue   : @getValue()
+
+        @textContainer.addSubView @text
+
+        @modal = new KDModalView
+          title        : "Please enter your content here."
+          cssClass     : "modal-fullscreen"
+          width        : window.innerWidth - 100
+          height       : window.innerHeight - 55
+          overlay      : yes
+          view         : @textContainer
+          buttons      :
+            Apply      :
+              title    : "Apply changes"
+              style    : "modal-clean-gray"
+              callback :=>
+                @setValue @text.getValue()
+                @generatePreview()
+                @modal.destroy()
+            Cancel     :
+              title    : "cancel"
+              style    : "modal-cancel"
+              callback :=> @modal.destroy()
+
+        @utils.defer =>
+          @setPaneSizes()
+
   getEditScrollPercentage:->
-      scrollPosition = @$().scrollTop()
-      scrollHeight = @$().height()
+      scrollPosition  = @$().scrollTop()
+      scrollHeight    = @$().height()
       scrollMaxheight = @getDomElement()[0].scrollHeight

       scrollPosition / (scrollMaxheight - scrollHeight) * 100
@@ -257,7 +167,7 @@
   setPreviewScrollPercentage:(percentage)->
     s = @$("div.preview_content")
     s.animate
-     scrollTop : ((s[0].scrollHeight - s.height())*percentage/100)
+     scrollTop : ((s[0].scrollHeight - s.height()) * percentage / 100)
     , 50, "linear"

   setDomElement:(cssClass="")->
@@ -303,4 +213,9 @@
       if @options.preview.language is "markdown"
         @$("div.preview_content span.data").html @utils.applyMarkdown @getValue()
         @$("div.preview_content span.data pre").each (i,element)=>
-          hljs.highlightBlock element+          hljs.highlightBlock element
+
+  _windowDidResize:->
+    @utils.defer =>
+      opt_setWidths = @previewOnOffSwitch.defaultValue is on
+      @setPaneSizes(opt_setWidths)


Activity: make the glance call when it is really needed to be glanced

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -19,7 +19,6 @@
     @on 'LazyLoadThresholdReached', @bound 'lazyLoad'  if typeConstant in ['group', 'topic']

     {windowController} = KD.singletons
-

     windowController.addFocusListener (focused) =>

@@ -99,21 +98,16 @@

     data = @getData()
     {id, typeConstant} = data
-    {socialapi} = KD.singletons
+    {socialapi, appManager} = KD.singletons

-    # fixme: don't fire unnecessary last seen time requests
-    # find a consisten way here./
+    app  = appManager.get 'Activity'
+    item = app.getView().sidebar.selectedItem

-    # item = @getDelegate().parent.sidebar.selectedItem
-    # return  unless item.count
+    return  unless item.count

     if typeConstant is 'post'
-    then socialapi.channel.glancePinnedPost messageId : id, log
-    else socialapi.channel.updateLastSeenTime channelId : id, log
-
-
-
-
+    then socialapi.channel.glancePinnedPost   messageId : id, ->
+    else socialapi.channel.updateLastSeenTime channelId : id, ->

   populate: ->


render template name in CanonicalPage

render template name in CanonicalPage

--- services/web/app/coffee/Features/Templates/TemplatesWebController.coffee
+++ services/web/app/coffee/Features/Templates/TemplatesWebController.coffee
@@ -50,6 +50,7 @@
 			if err?
 				logger.err err:err, user_id:user_id, template_id:template_id, "something went wrong in _renderCanonicalPage"
 				return res.send 500
+			data.title = data?.template?.name
 			data.tag = null
 			res.render "templates/template", data


callback should only be once

callback should only be once

--- lib/server.coffee
+++ lib/server.coffee
@@ -492,7 +492,7 @@
 										console.log "invalid doc",item.id
 										index = findplace(item.id)
 										workque[index].status = "dropped"
-								callback()
+							callback()

 			), config.readBatchsize)
 			syncJobs = (host, jobs) ->


フォロワーの一覧取得→フォロワー毎に最新投稿情報取得して配列に格納→配列ソートまでの処理が非同期で実行されるため、 mainTable.setDataする処理はsetTimeoutで10秒送らせてからじっしする形で仮実装

--- coffee/model/getFollowerItemsCommand.coffee
+++ coffee/model/getFollowerItemsCommand.coffee
@@ -1,12 +1,12 @@
 class getFollowerItemsCommand extends baseCommand
   constructor:() ->
-
+
   execute:()  ->
     result = []
     items = JSON.parse(Ti.App.Properties.getString("followerItems"))
     if items? is false or items is ""
-      @_test()
-      # @getFollowerItems()
+
+      @getFollowerItems()
     else
       items.sort( (a, b) ->

@@ -17,65 +17,47 @@
       result.push(mainTableView.createRowForLoadOldEntry(storedTo))
       mainTable.setData result
       @_hideStatusView()
-  _test:() ->
-
-    array ='[{"url_name":"mochiz@github","profile_image_url":"https://secure.gravatar.com/avatar/3ff4f5ddb4cf6ae28b0c27c37adbc30e?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png"},{"url_name":"ofl@github","profile_image_url":"https://secure.gravatar.com/avatar/4a4f28aeda559d0a8f569e7069b12964?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png"},{"url_name":"torshinor","profile_image_url":"https://si0.twimg.com/profile_images/1715071898/bakeneko_normal.png"},{"url_name":"sora_h","profile_image_url":"https://secure.gravatar.com/avatar/626ca235e8dab778c5bad6fc10e94ad8?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png"},{"url_name":"donayama","profile_image_url":"https://si0.twimg.com/profile_images/896848221/donayama_normal.jpg"},{"url_name":"atsusy","profile_image_url":"https://si0.twimg.com/profile_images/2306591484/DA074407-C036-4F58-AE56-0FBC5FBF0EB0_normal"},{"url_name":"masuidrive","profile_image_url":"https://secure.gravatar.com/avatar/0ec58a040e1e4e959c8566484b4bba19?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png"},{"url_name":"hmsk","profile_image_url":"https://secure.gravatar.com/avatar/8358fe546d1b082b163f18a02eec145d?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png"},{"url_name":"kyanny","profile_image_url":"https://secure.gravatar.com/avatar/fffd8bf62b842a46803fb07792029fa4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png"}]'
-    result = JSON.parse(array)
-    resetRows = []
-    mainTable.setData resetRows
-    for item in result
-      rows = []
-      _url = "https://qiita.com/api/v1/users/#{item.url_name}/items?per_page=5"
-      Ti.API.info _url
-      xhr = Ti.Network.createHTTPClient()
-      xhr.open("GET",_url)
-      xhr.onload = ->
-        if @.status is 200
-          items = JSON.parse(@.responseText)
-          if items? is true
-            for item in items
-              row = mainTableView.createRow(item)
-              mainTable.appendRow(row,{animated:true})
-
-      xhr.onerror = (e) ->
-        error = JSON.parse(@.responseText)
-        Ti.API.info error
-      xhr.timeout = 5000
-      xhr.send()
-
-
-    return

   getFollowerItems:() ->
-    rows = []
-    # @_showStatusView()
+
+    qiitaUser.getfollowingUserList( (result) =>
+
+      for item in result
+        _url = "https://qiita.com/api/v1/users/#{item.url_name}/items?per_page=5"
+        _items = []
+        xhr = Ti.Network.createHTTPClient()
+        xhr.open("GET",_url)
+        xhr.onload = ->
+          if @.status is 200
+            items = JSON.parse(@.responseText)
+            if items isnt null
+              Ti.API.info typeof items
+              for item in items
+                _items.push item
+
+        xhr.onerror = (e) ->
+          error = JSON.parse(@.responseText)
+          Ti.API.info error
+        xhr.timeout = 5000
+        xhr.send()
+
+
+      setTimeout (=>
+        result = []
+        _items.sort( (a, b) ->
+
+          (if moment(a.created_at).format("YYYYMMDDHHmm") > moment(b.created_at).format("YYYYMMDDHHmm") then -1 else 1)
+        )
+
+        for _item in _items
+          result.push(mainTableView.createRow(_item))
+
+        mainTable.setData result
+
+      ),10000
+
+    )

-    qiitaUser.getfollowingUserList( (userList) ->
-      rows = []
-      # reset row data
-      mainTable.setData rows
-      for user in userList
-        Ti.API.info user
-        # Ti.API.info "https://qiita.com/api/v1/users/#{user.url_name}/items"
-        # xhr = Ti.Network.createHTTPClient()
-        # xhr.open("GET","https://qiita.com/api/v1/users/#{user.url_name}/items")
-
-        # xhr.onload = ->
-        #   if @.status is 200
-        #     items = JSON.parse(@.responseText)
-        #     # if items? is true
-        #     #   Ti.API.info items
-
-
-        # xhr.onerror = (e) ->
-        #   error = JSON.parse(@.responseText)
-        #   Ti.API.info error
-        # xhr.timeout = 5000
-        # xhr.send()
-        @_hideStatusView()
-        return
-    )
-

   _currentSlideState:() ->


Event is not used anymore

Event is not used anymore

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -61,4 +61,4 @@
 showFrame.onload = -> window.onModalReady()

 # initialize the page, so we are ready to render new pages fast
-showUrl '/client/blank', -> window.remote.trigger 'intermediateFrameReady'
+loadUrl '/client/blank'


* minor changed.

* minor changed.

--- src/nosql-encoding.coffee
+++ src/nosql-encoding.coffee
@@ -75,7 +75,6 @@
   mGetSync: (keys, options) ->
     keyEncoding = @keyEncoding options
     valueEncoding = @valueEncoding options
-    valueDecode = valueEncoding.decode.bind(valueEncoding) if valueEncoding
     keys = keys.map keyEncoding.encode.bind(keyEncoding) if keyEncoding
     result = super(keys, options)
     options ||= {}
@@ -83,9 +82,9 @@
       if valueEncoding or keyEncoding
         result.map (item)->
           item.key = keyEncoding.decode item.key if keyEncoding
-          item.value = valueDecode item.value if valueEncoding
+          item.value = valueEncoding.decode item.value if valueEncoding
     else
-      result = result.map valueDecode if valueEncoding
+      result = result.map valueEncoding.decode.bind(valueEncoding) if valueEncoding
     result
   putSync: (key, value, options) ->
     keyEncoding = @keyEncoding options
@@ -131,7 +130,6 @@
   mGetAsync: (keys, options, callback) ->
     keyEncoding = @keyEncoding options
     valueEncoding = @valueEncoding options
-    valueDecode = valueEncoding.decode.bind(valueEncoding) if valueEncoding
     keys = keys.map keyEncoding.encode.bind(keyEncoding) if keyEncoding
     that = @
     super keys, options, (err, result)->
@@ -143,7 +141,7 @@
             item.key = keyEncoding.decode item.key if keyEncoding
             item.value = valueEncoding.decode item.value if valueEncoding
       else
-        result = result.map valueDecode if valueEncoding
+        result = result.map valueEncoding.decode.bind(valueEncoding) if valueEncoding
       callback null, result
   putAsync: (key, value, options, callback) ->
     keyEncoding = @keyEncoding options