removed unused card data

removed unused card data

--- data/fetch.coffee
+++ data/fetch.coffee
@@ -35,7 +35,6 @@
   "faction",
   "factioncost", # influence
   "number",
-  "quantity",
   "setname",
   "number",
   "cyclenumber",


missed applyDelta update

missed applyDelta update

--- src/paste-manager.coffee
+++ src/paste-manager.coffee
@@ -32,7 +32,7 @@
         delta.endLength += docLength
         delta.startLength = docLength
         oldDelta = @editor.doc.toDelta()
-        @editor.applyDelta(delta)
+        @editor.applyDelta(delta, false)
         @editor.root.focus()
         lengthAdded = Math.max(0, @editor.doc.length - docLength)
         @editor.setSelection(new Scribe.Range(@editor, selection.start.index + lengthAdded, selection.start.index + lengthAdded))


Clone the container so it works on navigation

--- resources/assets/coffee/_classes/gallery.coffee
+++ resources/assets/coffee/_classes/gallery.coffee
@@ -24,7 +24,7 @@
     $(document).on 'click', '.js-gallery-thumbnail', @switchPreview

     $(document).on 'turbolinks:before-cache', ->
-      $('.pswp--open').remove()
+      $('.js-gallery--container').remove()

   initiateOpen: (e) =>
@@ -38,7 +38,11 @@

   open: ({galleryId, index}) =>
-    pswp = new PhotoSwipe @container[0],
+    container = @container[0].cloneNode(true)
+    container.classList.add 'js-gallery--container'
+    document.body.appendChild container
+
+    pswp = new PhotoSwipe container,
       PhotoSwipeUI_Default
       @data galleryId
       showHideOpacity: true
@@ -48,7 +52,7 @@
       timeToIdle: null

     if _.startsWith(galleryId, 'contest-')
-      new _exported.GalleryContest(@container[0], pswp)
+      new _exported.GalleryContest(container, pswp)

     pswp.init()


Fix indicator index test by adding them to themes

Indicators are now displayed per theme, so we need indicators with
themes to list them

--- server/test/integration/indicator_index.coffee
+++ server/test/integration/indicator_index.coffee
@@ -8,25 +8,43 @@
 suite('Indicator index')

 test("With a series of indicators, I should see their titles", (done) ->
-  helpers.createIndicatorModels([
-    {
-      title: 'indicator 1'
-    }, {
-      title: 'indicator 2'
-    }
-  ]).success((indicators)->
-    request.get {
-      url: helpers.appurl('/indicators')
-    }, (err, res, body) ->
-      assert.equal res.statusCode, 200
+  themeAttributes = [{
+    title: 'Theme 1'
+    externalId: 1
+  },{
+    title: 'Theme 2'
+    externalId: 2
+  }]

-      for indicator in indicators
-        assert.match body, new RegExp(".*#{indicator.title}.*")
+  helpers.createThemesFromAttributes(themeAttributes, (err, themes) ->
+    if err
+      console.error err
+      throw new Error(err)

-      done()
-  ).error((error) ->
-    console.error error
-    throw "Unable to create indicators"
+    indicatorAttributes = [{
+      title: "I am an indicator of theme 1"
+      theme: themes[0].externalId
+    },{
+      title: "theme 2 indicator"
+      theme: themes[1].externalId
+    }]
+
+    helpers.createIndicatorModels(indicatorAttributes).success((indicators)->
+      request.get {
+        url: helpers.appurl('/indicators')
+      }, (err, res, body) ->
+        assert.equal res.statusCode, 200
+
+        for theme in themes
+          assert.match body, new RegExp(".*#{theme.title}.*")
+
+        for indicator in indicators
+          assert.match body, new RegExp(".*#{indicator.title}.*")
+
+        done()
+      ).error((error) ->
+        console.error error
+        throw "Unable to create themes"
+      )
+    )
   )
-)
-


GEN-1938 better error handling in unsubscribe.coffee

--- servers/lib/server/handlers/unsubscribe.coffee
+++ servers/lib/server/handlers/unsubscribe.coffee
@@ -11,25 +11,28 @@
   { email }           = params
   { JAccount, JUser } = koding.models

-  JUser.one { email }, (err, user) ->
-    mailgunUnsubscribeEmail email
+  mailgunUnsubscribeEmail email

-    return res.status(500).send 'an error occured'  if err
-    return res.status(404).send 'no user found'     unless user
-    return res.status(404).send 'token not right '  if token isnt String(user._id)
+  if token == "0"
+    return res.status(200).send 'unsubscribed'
+  else
+    JUser.one { email }, (err, user) ->
+      return res.status(500).send 'an error occured'  if err
+      return res.status(404).send 'no user found'     unless user
+      return res.status(404).send 'token not right '  if token isnt String(user._id)

-    current = user.getAt('emailFrequency') or {}
-    current['global'] = false
+      current = user.getAt('emailFrequency') or {}
+      current['global'] = false

-    user.update { $set: { emailFrequency: current } }, (err) ->
-      return res.status(500).send 'an error occured'  if err
+      user.update { $set: { emailFrequency: current } }, (err) ->
+        return res.status(500).send 'an error occured'  if err

-      emailFrequency =
-        global    : current.global
+        emailFrequency =
+          global    : current.global

-      Tracker.identify user.username, { emailFrequency }
+        Tracker.identify user.username, { emailFrequency }

-    return res.status(200).send 'unsubscribed'
+      return res.status(200).send 'unsubscribed'

 mailgunUnsubscribeEmail = (email) ->


fix typoin js code

fix typoin js code

--- drivers/javascript/src/query.coffee
+++ drivers/javascript/src/query.coffee
@@ -84,7 +84,7 @@
             wrapped = {}

         for k,v of val
-            wrapped[k] = rethinkdb.jsonWrap(v)
+            wrapped[k] = rethinkdb.exprJSON(v)
         rethinkdb.expr(wrapped)

 # Is this JS value representable as JSON?


init file now is included before bundle requires

--- cli/tasks/build/parse/index.coffee
+++ cli/tasks/build/parse/index.coffee
@@ -57,8 +57,8 @@

     # create file
     file = ''
+    file += 'var init = require(\'./init\');\n'
     file += "var opts = #{config};\n"
-    file += 'var init = require(\'./init\');\n'
     file += 'opts.modules = typeof modules !== \'undefined\' ? modules : {};\n'
     file += 'module.exports = init(Neft.bind(null, opts));\n'


Terminal: WebtermView: comment-out oskite related codes, we will look them later

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -162,73 +162,72 @@
         code, serviceGenericName, machineName, reason: err?.message
       }

-      if code is 503 and serviceGenericName.indexOf("kite-os") is 0
-        @reconnectAttemptFailed serviceGenericName, vmName
-
-  reconnectAttemptFailed: (serviceGenericName, vmName) ->
-    return  if @reconnected or not serviceGenericName
-
-    kiteController = KD.getSingleton "kiteController"
-    [prefix, kiteType, kiteRegion] = serviceGenericName.split "-"
-    serviceName = "~#{kiteType}-#{kiteRegion}~#{vmName}"
-
-    @setBackoffTimeout(
-      @bound "attemptToReconnect"
-      @bound "handleConnectionFailure"
-    )
-
-    kiteController.kiteInstances[serviceName]?.cycleChannel()
-
-  attemptToReconnect: ->
-    return  if @reconnected
-    @getDelegate().notify
-      cssClass  : "error"
-      title     : "Trying to reconnect your Terminal"
-      duration  : 2 * 60 * 1000 # 2 mins
-
-    hasResponse  = no
-
-    {vm} = @getOptions()
-    {hostnameAlias, region} = vm
-    {vmController, kontrol} = KD.singletons
-
-    kite =
-      if KD.useNewKites
-      then kontrol.kites.terminal[hostnameAlias]
-      else vmController.terminalKites[hostnameAlias]
-    kite?.webtermGetSessions().then (sessions) =>
-      hasResponse = yes
-      return if @reconnected
-
-    @handleReconnect()
-
-  clearConnectionAttempts: ->
-    @clearBackoffTimeout()
-
-  handleReconnect: (force = no) ->
-    return  if not force and @reconnected
-
-    @clearConnectionAttempts()
-    options =
-      session : @sessionId
-      vm      : @getOption('vm')
-
-    @emit "WebTermNeedsToBeRecovered", options
-    @reconnected = yes
-
-  handleConnectionFailure: ->
-    title = "Sorry, something is wrong with our backend."
-
-    ErrorLog.create title
-
-    return if @failedToReconnect
-    @reconnected       = no
-    @failedToReconnect = yes
-    @clearConnectionAttempts()
-    @getDelegate().notify
-      title     : title
-      cssClass  : "error"
-      duration  : 15 * 1000 # 15 secs
+  # FIXME GG
+  #     if code is 503 and serviceGenericName.indexOf("kite-os") is 0
+  #       @reconnectAttemptFailed serviceGenericName, vmName
+
+  # reconnectAttemptFailed: (serviceGenericName, vmName) ->
+  #   return  if @reconnected or not serviceGenericName
+
+  #   kiteController = KD.getSingleton "kiteController"
+  #   [prefix, kiteType, kiteRegion] = serviceGenericName.split "-"
+  #   serviceName = "~#{kiteType}-#{kiteRegion}~#{vmName}"
+
+  #   @setBackoffTimeout(
+  #     @bound "attemptToReconnect"
+  #     @bound "handleConnectionFailure"
+  #   )
+
+  #   kiteController.kiteInstances[serviceName]?.cycleChannel()
+
+  # attemptToReconnect: ->
+  #   return  if @reconnected
+  #   @getDelegate().notify
+  #     cssClass  : "error"
+  #     title     : "Trying to reconnect your Terminal"
+  #     duration  : 2 * 60 * 1000 # 2 mins
+
+  #   hasResponse  = no
+
+  #   {vm} = @getOptions()
+  #   {hostnameAlias, region} = vm
+  #   {vmController, kontrol} = KD.singletons
+
+  #   kite =
+  #     if KD.useNewKites
+  #     then kontrol.kites.terminal[hostnameAlias]
+  #     else vmController.terminalKites[hostnameAlias]
+  #   kite?.webtermGetSessions().then (sessions) =>
+  #     hasResponse = yes
+  #     return if @reconnected
+
+  #   @handleReconnect()
+
+  # handleReconnect: (force = no) ->
+  #   return  if not force and @reconnected
+
+  #   @clearBackoffTimeout()
+  #   options =
+  #     session : @sessionId
+  #     vm      : @getOption('vm')
+
+  #   @emit "WebTermNeedsToBeRecovered", options
+  #   @reconnected = yes
+
+  # handleConnectionFailure: ->
+  #   title = "Sorry, something is wrong with our backend."
+
+  #   ErrorLog.create title
+
+  #   return if @failedToReconnect
+  #   @reconnected       = no
+  #   @failedToReconnect = yes
+  #   @clearBackoffTimeout()
+  #   @getDelegate().notify
+  #     title     : title
+  #     cssClass  : "error"
+  #     duration  : 15 * 1000 # 15 secs
+

   destroy: ->
     super


IDE: CollaborationHelpers: Refactor for sinkrow to async.

--- client/ide/lib/collaboration/helpers/environment.coffee
+++ client/ide/lib/collaboration/helpers/environment.coffee
@@ -1,7 +1,7 @@
 _                           = require 'lodash'
 kd                          = require 'kd'
+async                       = require 'async'
 remote                      = require('app/remote').getInstance()
-sinkrow                     = require 'sinkrow'
 socialHelpers               = require './social'
 userEnvironmentDataProvider = require 'app/userenvironmentdataprovider'

@@ -54,18 +54,15 @@
     method = if share then 'klientShare' else 'klientUnshare'

     queue = usernames.map (username) ->
-      ->
-        kite[method]({username}).then ->
-          queue.fin()
-
+      (fin) ->
+        kite[method]({username})
+        .then -> fin()
         .error (err) ->
-          queue.fin()
-
           return  if err.message is 'User not found' and not share

-          callback err
+          fin err

-    sinkrow.dash queue, callback
+    async.parallel queue, callback

 ###*


login: uses new username check util

login: uses new username check util

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -403,7 +403,7 @@

   usernameCheckTimer = null

-  usernameCheck:(input, event, delay=800)->
+  usernameCheck: (input, event, delay=800) ->

     return  if event?.which is 9
     return  if input.getValue().length < 4
@@ -414,43 +414,40 @@
     input.setValidationResult "usernameCheck", null
     username = input.getValue()

-    if input.valid
-      usernameCheckTimer = KD.utils.wait delay, =>
-        return  unless @signupModal?
-
-        $.ajax
-          url         : "/-/validate/username"
-          type        : 'POST'
-          data        : { username }
-          xhrFields   : withCredentials : yes
-          success     : =>
-
-            usernameCheckTimer = null
-            return  unless @signupModal?
-
-            input.setValidationResult 'usernameCheck', null
-            USERNAME_VALID = yes
-
-            @checkBeforeRegister()  if pendingSignupRequest
-
-          error       : ({responseJSON}) =>
-
-            usernameCheckTimer = null
-            return  unless @signupModal?
-
-            {forbidden, kodingUser} = responseJSON
-
-            if forbidden
-              input.setValidationResult "usernameCheck", "Sorry, \"#{username}\" is forbidden to use!"
-              USERNAME_VALID = no
-            else if kodingUser
-              input.setValidationResult "usernameCheck", "Sorry, \"#{username}\" is already taken!"
-              USERNAME_VALID = no
-            else
-              input.setValidationResult "usernameCheck", "Sorry, there is a problem with \"#{username}\"!"
-              USERNAME_VALID = no
-
-            pendingSignupRequest = no
+    return  unless input.valid
+
+    usernameCheckTimer = KD.utils.wait delay, =>
+      return  unless @signupModal?
+
+      KD.utils.usernameCheck username,
+        success : =>
+
+          usernameCheckTimer = null
+          return  unless @signupModal?
+
+          input.setValidationResult 'usernameCheck', null
+          USERNAME_VALID = yes
+
+          @checkBeforeRegister()  if pendingSignupRequest
+
+        error : ({responseJSON}) =>
+
+          usernameCheckTimer = null
+          return  unless @signupModal?
+
+          {forbidden, kodingUser} = responseJSON
+
+          if forbidden
+            input.setValidationResult "usernameCheck", "Sorry, \"#{username}\" is forbidden to use!"
+            USERNAME_VALID = no
+          else if kodingUser
+            input.setValidationResult "usernameCheck", "Sorry, \"#{username}\" is already taken!"
+            USERNAME_VALID = no
+          else
+            input.setValidationResult "usernameCheck", "Sorry, there is a problem with \"#{username}\"!"
+            USERNAME_VALID = no
+
+          pendingSignupRequest = no

   changeButtonState: (button, state) ->


switching to only after registering

switching to only after registering

--- scripts/jira-linkifier.coffee
+++ scripts/jira-linkifier.coffee
@@ -27,12 +27,10 @@
     projects.push shortcode
     robot.brain.set 'jira-projects', projects

-
+    robot.hear robot.hear /MOBILE-([0-9]*)/i, (mention) ->
+      mention.send("Issue at: https://jira.brandnetworksinc.com/browse/" + "MOBILE" + "-" + mention.match[1])

     msg.send "Watching that project for you"
-
-  robot.hear /MOBILE-([0-9]*)/i, (mention) ->
-    mention.send("Issue at: https://jira.brandnetworksinc.com/browse/" + "MOBILE" + "-" + mention.match[1])

   robot.respond /stop watching ?(.+)?/i, (msg) ->
     projects = robot.brain.get('jira-projects') or []


Update my local subject's ID

Update my local subject's ID

--- scripts/models/Subject.coffee
+++ scripts/models/Subject.coffee
@@ -3,7 +3,7 @@
 Classification = require 'models/Classification'

 class Subject extends Spine.Model
-	@configure 'Subject', 'image', 'audio', 'latitude', 'longitude', 'location', 'habitat', 'datetime'
+	@configure 'Subject', 'image', 'audio', 'latitude', 'longitude', 'location', 'habitat', 'captured'

 	@next: ->
     subjects = Subject.all()


Increase settings pane width to accommodate new keybinding option

--- assets/js/panes/options.coffee
+++ assets/js/panes/options.coffee
@@ -1,89 +1,89 @@
-#= require ./../services/panes
-#= require ./../services/settings
-
-module = angular.module("plunker.panes")
-
-module.requires.push "plunker.settings"
-
-
-module.run [ "panes", "settings", (panes, settings) ->
-
-  panes.add
-    id: "options"
-    icon: "cog"
-    size: 230
-    order: 1000
-    title: "Editor Options"
-    description: """
-      Customize the theme of the editor, key bindings, indentation options and much more.
-    """
-    template: """
-

-        <form>
-          <fieldset>
-            <legend>Editor</legend>
-            Theme:
-              <select class="input-medium" id="opts-editor-theme" ng-model="settings.editor.theme" ng-options="theme for theme in themes"></select>
-
-            Key Binding:
-              <select class="input-medium" id="opts-editor-keyboard-handler" ng-model="settings.editor.keyboard_handler" ng-options="kh for kh in keyboard_handlers"></select>
-
-            Tab size:
-              <input class="input-mini" id="opts-editor-tabSize" ng-model="settings.editor.tab_size" type="number">
-
-            Font size:
-              <input class="input-mini" id="opts-editor-fontSize" ng-model="settings.editor.font_size" type="number" min="8">
-
-
-              <input class="input-mini" id="opts-editor-lineWrap" ng-model="settings.editor.wrap.enabled" type="checkbox">
-              Line wrapping
-
-          </fieldset>
-          <fieldset>
-            <legend>Previewer</legend>
-            Refresh interval:
-              <input class="input-small" id="opts-previewer-delay" ng-model="settings.previewer.delay" ng-disabled="!settings.previewer.auto_refresh" type="number" />
-
-
-              <input type="checkbox" ng-model="settings.previewer.auto_refresh" />
-              Auto refresh
-
-          </fieldset>
-        </form>
-

-    """
-    link: ($scope, $el, attrs) ->
-      $scope.settings = settings
-      $scope.keyboard_handlers = [
-        "ace"
-        "emacs"
-        "vim"
-      ]
-      $scope.themes = [
-        "ambiance"
-        "chrome"
-        "clouds"
-        "clouds_midnight"
-        "crimson_editor"
-        "dawn"
-        "dreamweaver"
-        "eclipse"
-        "github"
-        "idle_fingers"
-        "kr_theme"
-        "merbivore"
-        "merbivore_soft"
-        "monokai"
-        "pastel_on_dark"
-        "solarized_dark"
-        "solarized_light"
-        "textmate"
-        "tomorrow"
-        "tomorrow_night"
-        "tomorrow_night_blue"
-        "tomorrow_night_bright"
-        "twilight"
-        "vibrant_ink"
-        "xcode"
-      ]
+#= require ./../services/panes
+#= require ./../services/settings
+
+module = angular.module("plunker.panes")
+
+module.requires.push "plunker.settings"
+
+
+module.run [ "panes", "settings", (panes, settings) ->
+
+  panes.add
+    id: "options"
+    icon: "cog"
+    size: 252
+    order: 1000
+    title: "Editor Options"
+    description: """
+      Customize the theme of the editor, key bindings, indentation options and much more.
+    """
+    template: """
+

+        <form>
+          <fieldset>
+            <legend>Editor</legend>
+            Theme:
+              <select class="input-medium" id="opts-editor-theme" ng-model="settings.editor.theme" ng-options="theme for theme in themes"></select>
+
+            Key Binding:
+              <select class="input-medium" id="opts-editor-keyboard-handler" ng-model="settings.editor.keyboard_handler" ng-options="kh for kh in keyboard_handlers"></select>
+
+            Tab size:
+              <input class="input-mini" id="opts-editor-tabSize" ng-model="settings.editor.tab_size" type="number">
+
+            Font size:
+              <input class="input-mini" id="opts-editor-fontSize" ng-model="settings.editor.font_size" type="number" min="8">
+
+
+              <input class="input-mini" id="opts-editor-lineWrap" ng-model="settings.editor.wrap.enabled" type="checkbox">
+              Line wrapping
+
+          </fieldset>
+          <fieldset>
+            <legend>Previewer</legend>
+            Refresh interval:
+              <input class="input-small" id="opts-previewer-delay" ng-model="settings.previewer.delay" ng-disabled="!settings.previewer.auto_refresh" type="number" />
+
+
+              <input type="checkbox" ng-model="settings.previewer.auto_refresh" />
+              Auto refresh
+
+          </fieldset>
+        </form>
+

+    """
+    link: ($scope, $el, attrs) ->
+      $scope.settings = settings
+      $scope.keyboard_handlers = [
+        "ace"
+        "emacs"
+        "vim"
+      ]
+      $scope.themes = [
+        "ambiance"
+        "chrome"
+        "clouds"
+        "clouds_midnight"
+        "crimson_editor"
+        "dawn"
+        "dreamweaver"
+        "eclipse"
+        "github"
+        "idle_fingers"
+        "kr_theme"
+        "merbivore"
+        "merbivore_soft"
+        "monokai"
+        "pastel_on_dark"
+        "solarized_dark"
+        "solarized_light"
+        "textmate"
+        "tomorrow"
+        "tomorrow_night"
+        "tomorrow_night_blue"
+        "tomorrow_night_bright"
+        "twilight"
+        "vibrant_ink"
+        "xcode"
+      ]
 ]


Finder:AppController: disable uploader and mountVm actions

--- client/finder/AppController.coffee
+++ client/finder/AppController.coffee
@@ -21,8 +21,7 @@
     finderView  = @controller.getView()

     finderView.addSubView @getAppTitleView()  if options.addAppTitle
-    finderView.addSubView @getUploader()
-    # finderView.addSubView @getMountVMButton()
+    # finderView.addSubView @getUploader()

     return @controller

@@ -30,13 +29,6 @@
     return new KDCustomHTMLView
       cssClass : "app-header"
       partial  : "Ace Editor"
-
-  getMountVMButton: ->
-    return new KDButtonView
-      title    : "Mount other VMs"
-      icon     : yes
-      cssClass : "finder-mountvm clean-gray"
-      callback : @bound 'showMountVMModal'

   getUploader: ->
     @uploaderPlaceholder = new KDView
@@ -75,59 +67,3 @@
     return  if @controller.treeController.internalDragging
     @uploaderPlaceholder.show()
     @uploader.unsetClass "hover"
-
-  showMountVMModal: ->
-    modal = new KDModalView
-      width         : 620
-      cssClass      : "modal-with-text mount-vm"
-      title         : "Mount VMs"
-      overlay       : yes
-
-    vmListController = new KDListViewController
-      view          : list = new KDListView
-        itemClass   : VMListItem
-        type        : "vmlist"
-
-    {vmController} = KD.singletons
-
-
-    list.on "VmStateChanged", (options)=>
-
-      vmController.fetchVmInfo options.hostnameAlias, (err, info)=>
-        return KD.showError err  if err
-        if options.state
-        then @controller.mountVm info
-        else @controller.unmountVm info.hostnameAlias
-
-    list.once 'viewAppended', =>
-
-      vmListController.showLazyLoader()
-      vmController.fetchVMs (err, vms)->
-        vmListController.hideLazyLoader()
-        return KD.showError err  if err
-        vmListController.instantiateListItems vms
-
-    modal.addSubView vmListController.getView()
-
-
-class VMListItem extends KDListItemView
-
-  constructor:(options = {}, data)->
-
-    super options, data
-
-
-  viewAppended:->
-
-    @addSubView loader = new KDLoaderView showLoader : yes
-
-    {hostnameAlias} = @getData()
-    KD.singletons.vmController.info hostnameAlias, (err, name, info)=>
-      loader.hide()
-      return KD.showError err if err
-      @addSubView vmLabel  = new KDLabelView title: hostnameAlias
-      @addSubView vmSwitch = new KodingSwitch
-        cssClass     : 'dark'
-        defaultValue : info.state is "RUNNING"
-        callback     : (state)=>
-          @getDelegate().emit "VmStateChanged", {state, hostnameAlias}


Activity: everty sidebar item keeps an unread count property

--- client/Social/Activity/sidebar/sidebaritem.coffee
+++ client/Social/Activity/sidebar/sidebaritem.coffee
@@ -12,6 +12,7 @@

     super options, data

+    @count       = 0
     @unreadCount = new KDCustomHTMLView
       tagName  : 'cite'
       cssClass : 'count hidden'
@@ -26,10 +27,12 @@
       @getDelegate().emit 'ItemShouldBeSelected', this
       @lastClickedTimestamp = Date.now()

-    @once 'viewAppended', @bound 'setUnreadCount'
+    @once 'viewAppended', => @setUnreadCount @getData().unreadCount

   setUnreadCount: (unreadCount = 0) ->
+
+    @count = unreadCount

     if unreadCount is 0
       @unreadCount.hide()


activity/flux: minor

activity/flux: minor

--- client/activity/lib/flux/test/threadsstore.coffee
+++ client/activity/lib/flux/test/threadsstore.coffee
@@ -5,8 +5,6 @@

 ThreadsStore = require '../stores/threadsstore'
 actionTypes = require '../actions/actiontypes'
-
-helper = require '../helper'

 describe 'ThreadsStore', ->

@@ -77,8 +75,6 @@
       expect(storeState.hasIn [channelId, 'messages', 'mock']).to.equal yes

-
-
 markAsFromServer = (message) ->

   if typeof message.get is 'function'


fallback for next page

fallback for next page

--- src/scripts/modules/media/body/embeddables/coach.coffee
+++ src/scripts/modules/media/body/embeddables/coach.coffee
@@ -88,11 +88,14 @@
       router.navigate(link, {trigger: true})

     getNextPage: ({moduleUUID}) =>
-      nextPageNumber = @getNextPageNumberFromUuid(moduleUUID)
+      nextPageNumber = if moduleUUID?
+        @getNextPageNumberFromUuid(moduleUUID)
+      else
+        @model.getNextPageNumber()
       nextPage = @findPageInAllPages (page) ->
         page.getPageNumber() is nextPageNumber

-      return '' unless nextPage?
+      return null unless nextPage?

       chapter = nextPage.get('chapter') or nextPage.get('_parent')?.get('chapter') or ''
       title = nextPage.get('searchTitle') or nextPage.get('title') or ''


Revert "birthday wooooo"

Revert "birthday wooooo"

This reverts commit 8c0615a164453cda633452a572abff08e27ad696.

--- scripts/birthday.coffee
+++ scripts/birthday.coffee
@@ -1,90 +0,0 @@
-# Description:
-#   Delegate your birthday greetings, celebrations and quotes to Hubot.
-#
-# Dependencies:
-#   None
-#
-# Configuration:
-#   None
-#
-# Commands:
-#   hubot birthday quote for <user> -- congratulate <user> with a random birthday quote
-#   hubot celebrate me <user> -- congratulate <user> with an inspirational greeting
-#   hubot happy birthday me <user> -- congratulate <user> with a humorous greeting
-#
-# Author:
-#   sopel
-
-module.exports = (robot) ->
-
-  robot.respond /(birthday quote)( for )?(.*)/i, (msg)->
-    name = msg.match[3].trim()
-    if name.length == 0
-      msg.send(quote())
-    else
-      msg.send(name + " - here's a quote for you: ")
-      msg.send(quote(name))
-
-  robot.respond /(happy birthday)( me )?(.*)/i, (msg)->
-    name = msg.match[3].trim()
-    if name.length == 0
-      msg.send(quote())
-    else
-      msg.send(greeting(name))
-
-  robot.respond /(celebrate)( me )?(.*)/i, (msg)->
-    name = msg.match[3].trim()
-    if name.length == 0
-      msg.send("You must be kidding.")
-    else
-      msg.send(celebrate(name))
-
-celebrate = (name) ->
-  celebrates[(Math.random() * celebrates.length) >> 0].replace(/{name}/, name);
-
-celebrates = [
-  "{name} - Hoping that your day will be as special as you are.",
-  "{name} - Count your life by smiles, not tears. Count your age by friends, not years.",
-  "May the years continue to be good to you. Happy Birthday {name}!",
-  "{name} - You're not getting older, you're getting better.",
-  "{name} - May this year bring with it all the success and fulfillment your heart desires.",
-  "{name} - Wishing you all the great things in life, hope this day will bring you an extra share of all that makes you happiest.",
-  "Happy Birthday {name}, and may all the wishes and dreams you dream today turn to reality.",
-  "May this day bring to you all things that make you smile. Happy Birthday {name}!",
-  "{name} - Your best years are still ahead of you.",
-  "{name} - Birthdays are filled with yesterday's memories, today's joys, and tomorrow's dreams.",
-  "{name} - Hoping that your day will be as special as you are.",
-  "{name} - You'll always be forever young."
-]
-greeting = (name) ->
-  greetings[(Math.random() * greetings.length) >> 0].replace(/{name}/, name);
-
-greetings = [
-  "Happy Birthday {name}, you're not getting older, you're just a little closer to death.",
-  "Birthdays are good for you {name}. Statistics show that people who have the most live the longest!",
-  "{name} - I'm so glad you were born, because you brighten my life and fill it with joy.",
-  "{name} - Always remember: growing old is mandatory, growing up is optional.",
-  "{name} - Better to be over the hill than burried under it.",
-  "You always have such fun birthdays {name}, you should have one every year.",
-  "Happy birthday to {name}, a person who is smart, good looking, and funny and reminds me a lot of myself.",
-  "{name} - We know we're getting old when the only thing we want for our birthday is not to be reminded of it.",
-  "Happy Birthday on your very special day {name}, I hope that you don't die before you eat your cake."
-]
-
-quote = () ->
-  quotes[(Math.random() * quotes.length) >> 0];
-
-quotes = [
-  "Our birthdays are feathers in the broad wing of time. - Jean Paul Richter",
-  "Inside every older person is a younger person wondering what happened. - Jennifer Yane",
-  "Wisdom doesn't necessarily come with age. Sometimes age just shows up all by itself. - Tom Wilson",
-  "May you stay forever young. - Bob Dylan",
-  "The old believe everything; the middle-aged suspect everything; the young know everything. - Oscar Wilde",
-  "Old age is like everything else. To make a success of it, you've got to start young. - Fred Astaire",
-  "Age is a number and mine is unlisted. - Unknown",
-  "When I was younger, I could remember anything, whether it happened or not.- Mark Twain",
-  "Whatever with the past has gone, The best is always yet to come. - Lucy Larcom",
-  "It takes a long time to grow young. - Pablo Picasso",
-  "Few women admit their age. Few men act theirs. - Unkown",
-  "The best way to remember your wife's birthday is to forget it once. - H. V. Prochnow"
-]


Updated to accept question id [RED-41]

Updated to accept question id [RED-41]

--- src/core/javascripts/services/unwarp.js.coffee
+++ src/core/javascripts/services/unwarp.js.coffee
@@ -178,7 +178,7 @@

       getAnswer: (question) ->
         for a in @answers
-          return a.value if a.question_text is question
+          return a.value if a.question_text is question or a.question_id is question

     return answers


Reset variables when constraints were removed

--- src/concepts/Domain.coffee
+++ src/concepts/Domain.coffee
@@ -300,8 +300,8 @@
   reconstrain: (other, constraint) ->
     return unless other.operation && constraint.operation
     if compared = @compare(other.operation, constraint.operation)
-      if compared != true || !@resuggest(other.operation, constraint.operation)
-        @unconstrain(other)
+      if compared != true || !(suggested = @resuggest(other.operation, constraint.operation))
+        @unconstrain(other, undefined, true)
         return
       else
         return true
@@ -368,7 +368,7 @@
     #  for constraint in stack
     #    @addConstraint constraint

-  unconstrain: (constraint, continuation) ->
+  unconstrain: (constraint, continuation, moving) ->
     for path in constraint.paths
       if typeof path == 'string'
         if group = @paths[path]
@@ -387,7 +387,7 @@
         if index > -1
           path.constraints.splice(index, 1)
           unless path.constraints.length
-            @undeclare(path)
+            @undeclare(path, moving)
         if path.operations
           for op, index in path.operations by -1
             while op
@@ -417,10 +417,17 @@
       variable = @variable('suggested_' + Math.random())
     return variable

-  undeclare: (variable) ->
+  undeclare: (variable, moving) ->
     (@nullified ||= {})[variable.name] = variable
     if @added?[variable.name]
       delete @added[variable.name]
+    if !moving && @values[variable.name] != undefined
+      delete @variables[variable.name]
+    delete @values[variable.name]
+    @nullify(variable)
+
+
+
     #if variable.editing
     #  if variable.value
     #    @removeConstraint variable.editing
@@ -467,7 +474,7 @@
         for group, index in separated
           ops = []
           for constraint, index in group
-            @unconstrain constraint
+            @unconstrain constraint, undefined, true
             if constraint.operation
               ops.push constraint.operation
           if ops.length


added trigger 'focus' for accessibility

added trigger 'focus' for accessibility

--- src/components/task/details.cjsx
+++ src/components/task/details.cjsx
@@ -16,7 +16,7 @@

   getDefaultProps: ->
     title: 'Instructions'
-    trigger: 'hover'
+    trigger: ['hover', 'focus']
     placement: 'top'
     popverClassName: 'task-details-popover'


Tests: Helpers: Environments: Fixed openVmSettingsModal.

--- client/test/lib/helpers/environmenthelpers.coffee
+++ client/test/lib/helpers/environmenthelpers.coffee
@@ -5,14 +5,18 @@

 module.exports =

-  openVmSettingsModal: (browser, vmName) ->

-    modalSelector = '.activity-modal.vm-settings'
+  openVmSettingsModal: (browser, vmName, cssSelector) ->

     if not vmName
       vmName = 'koding-vm-0'

-    vmSelector = '.activity-sidebar a[href="/IDE/' + vmName + '"].running'
+    if not cssSelector
+      cssSelector = '.general'
+
+    modalSelector = '.machine-settings-modal.AppModal'
+    itemSelector  = modalSelector + ' .AppModal-navItem' + cssSelector
+    vmSelector    = '.activity-sidebar a[href="/IDE/' + vmName + '"].running'

     browser
       .waitForElementVisible   vmSelector, 20000
@@ -21,14 +25,24 @@
       .click                   vmSelector + ' span'
       .waitForElementVisible   modalSelector, 20000 # Assertion
       .pause                   2500
+      .waitForElementVisible   itemSelector, 20000
+      .click                   itemSelector

-  clickMoreButtonInVMSettingsModal: (browser) ->
+  openDiskUsageSettings: (browser, vmName) ->

-    browser
-      .waitForElementVisible  '.settings form.with-fields .moreview', 20000
-      .click                  '.settings form.with-fields .moreview'
-      .pause                  2000
+    @openVmSettingsModal browser, vmName, '.disk-usage'
+
+
+  openSpecsSettings: (browser, vmName) ->
+
+    @openVmSettingsModal browser, vmName, '.specs'
+
+
+  openGeneralSettings: (browser,vmName) ->
+
+    @openVmSettingsModal browser, vmName
+

   openDomainSettings: (browser, vmName) ->

@@ -47,12 +61,14 @@

     @clickAddKodingVMButton browser

+
   clickAddKodingVMButton: (browser) ->

     sidebarTitle = '[testpath=main-sidebar] .activity-sidebar .vms .sidebar-title'
     browser
       .waitForElementVisible   '.more-modal.more-vms', 20000 # Assertion
       .click                   '.kdbutton.create-koding-vm'
+

   seeUpgradeModal: (browser) ->


Fix showSpies and editSpies resetting in tests

--- cms/static/coffee/spec/views/textbook_spec.coffee
+++ cms/static/coffee/spec/views/textbook_spec.coffee
@@ -132,12 +132,12 @@
         @collection = new CMS.Collections.TextbookSet
         @view = new CMS.Views.ListTextbooks({collection: @collection})
         @view.render()
-        @showSpies.constructor.reset()
-        @editSpies.constructor.reset()

     it "should render the empty template if there are no textbooks", ->
         expect(@view.$el).toContainText("You haven't added any textbooks to this course yet")
         expect(@view.$el).toContain(".new-button")
+        expect(@showSpies.constructor).not.toHaveBeenCalled()
+        expect(@editSpies.constructor).not.toHaveBeenCalled()

     it "should render TextbookShow views by default if no textbook is being edited", ->
         # add three empty textbooks to the collection
@@ -175,6 +175,10 @@
         expect(@editSpies.constructor).toHaveBeenCalledWith({model: editing})

     it "should add a new textbook when the new-button is clicked", ->
+        # reset spies
+        @showSpies.constructor.reset()
+        @editSpies.constructor.reset()
+        # test
         @view.$(".new-button").click()

         expect(@collection.length).toEqual(1)


Use Redux Devtools Chrome extension.

Use Redux Devtools Chrome extension.

--- src/scripts/store.coffee
+++ src/scripts/store.coffee
@@ -6,6 +6,7 @@
 ReduxRouter = require 'redux-simple-router'

 routes = require './views/routes.coffee'
+utils = require './utils.coffee'

 reducer = Redux.combineReducers
 	articleState: require './reducers/article-reducer.coffee'
@@ -13,10 +14,14 @@
 	loginState: require './reducers/login-reducer.coffee'
 	routing: ReduxRouter.routeReducer

+hasDevTools = (
+	typeof window == 'object' && typeof window.devToolsExtension != 'undefined'
+)
+
 module.exports = ->
-	store = Redux.applyMiddleware(
-		promiseMiddleware()
-		thunkMiddleware
+	store = Redux.compose(
+		Redux.applyMiddleware promiseMiddleware(), thunkMiddleware
+		if hasDevTools then window.devToolsExtension() else utils.identity
 	)(Redux.createStore)(reducer)
 	history = createHistory()
 	ReduxRouter.syncReduxAndRouter history, store


Moved props to alphabetize module.exports.

--- src/scripts/utils.coffee
+++ src/scripts/utils.coffee
@@ -148,7 +148,6 @@
 	array: array
 	createFormatMapper: createFormatMapper
 	createPropertyMapper: createPropertyMapper
-	prop: prop
 	getProps: getProps
 	getUserId: (userId) ->
 		getUserId(userId).cuid || userId
@@ -158,6 +157,7 @@
 	mapObjectRecursively: mapObjectRecursively
 	mapValues: mapValues
 	maybe: maybe
+	prop: prop
 	stripDbFields: (obj) ->
 		gotMap = Immutable.Map.isMap(obj)
 		m = if gotMap then obj else Immutable.Map(obj)


changes to build process

changes to build process

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -73,31 +73,28 @@

 gulp.task 'test', ['buildJS'], (done) ->
-  buildTests(false)
-  .on 'end', ->
-    config =
-      configFile: __dirname + '/test/karma.config.coffee'
-      singleRun: true
-    karma.server.start(config, done)
-
+  buildAndTest(false, done)
   return # Since this is async

 gulp.task 'tdd', (done) ->
-  buildTests(true)
-  .on 'end', ->
-    config =
-      configFile: __dirname + '/test/karma.config.coffee'
-    karma.server.start(config, done)
-
+  buildAndTest(true, done)
   return # Since this is async

+buildAndTest = (shouldWatch, done) ->
+  build(shouldWatch).on 'end', ->
+    buildTests(shouldWatch).on 'end', ->
+      config =
+        configFile: __dirname + '/test/karma.config.coffee'
+        singleRun: not shouldWatch
+
+      if shouldWatch
+        karma.server.start(config)
+        done()
+      else
+        karma.server.start(config, done)
+
+
 gulp.task 'buildJS', ['cleanJS'], -> build(false)
-gulp.task 'watchJS', ['cleanJS'], (done)->
-  build(true)
-  buildTests(true).on 'end', ->
-    config =
-      configFile: __dirname + '/test/karma.config.coffee'
-    karma.server.start(config, done)

 gulp.task 'cleanJS', (done) ->
   del([
@@ -169,7 +166,8 @@

 gulp.task 'dist', ['build']
 gulp.task 'prod', ['archive']
-gulp.task 'watch', ['watchJS', 'styles', 'copyResources', 'copyFonts'], () ->
+#gulp.task 'watch', ['styles', 'copyResources', 'copyFonts', 'tdd'], () ->
+gulp.task 'watch', ['tdd'], () ->
   gulp.watch 'style/**/{*.less, *.css}', ['styles']
   gulp.watch ['test/**/*.coffee'], ['tdd']


enhance: track after api filter form validation

--- app/components/api-filter.coffee
+++ app/components/api-filter.coffee
@@ -28,7 +28,6 @@
       @project.addNewAPIURL()

     addApiUrlFilter: (callback) ->
-      analytics.feature(ENV.csb.feature.addAPIEndpoints, ENV.csb.module.security, ENV.csb.product.appknox)
       allFilters = @$('.input')
       urls = ""
       uniqueArrays = ""
@@ -58,6 +57,7 @@
       apiScanOptions = [ENV.host,ENV.namespace, ENV.endpoints.apiScanOptions, project_id].join '/'
       data =
         apiUrlFilters: urlString
+      analytics.feature(ENV.csb.feature.addAPIEndpoints, ENV.csb.module.security, ENV.csb.product.appknox)
       @set "isSavingFilter", true
       @get("ajax").post apiScanOptions, data: data
       .then (data)->


Removing a rogue comment

Removing a rogue comment

--- app/components/gcs-upload.coffee
+++ app/components/gcs-upload.coffee
@@ -24,7 +24,6 @@
       Ember.$.post uploadedUrl, data

     uploader.on 'progress', (e) ->
-      # Use `e.percent` to get percentage
       delegate.set "percent", e.percent

     if !Ember.isEmpty files


fix: minor fix

fix: minor fix

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -68,10 +68,10 @@
     if platform is ENUMS.PLATFORM.ANDROID
       "nexus5"
     else if platform is ENUMS.PLATFORM.IOS
-      if deviceType is ENUMS.DEVICE_TYPE.PHONE_REQUIRED
+      if deviceType is ENUMS.DEVICE_TYPE.TABLET_REQUIRED
+        "ipad silver"
+      else
         "iphone5s silver"
-      else if deviceType is ENUMS.DEVICE_TYPE.TABLET_REQUIRED
-        "ipad silver"
   ).property "file.project.platform", "file.project.deviceType"

   isNotTablet: (->


Esc key fix for enketo iframe exiting #1309

--- jsapp/xlform_model_view/view.utils.coffee
+++ jsapp/xlform_model_view/view.utils.coffee
@@ -153,6 +153,7 @@

     launch.close = ()->
       $(".iframe-bg-shade").remove()
+      $(".enketo-holder").remove()

     launch.fromCsv = (surveyCsv, options={})->
       previewServer = options.previewServer or ""


remove unused message emissions

remove unused message emissions


Loading script files (asynchronously) works now


:repeat: add config parameters description in config-example.coffee comments

--- app/config-example.coffee
+++ app/config-example.coffee
@@ -4,13 +4,25 @@
   debug: false
   db: 'mongodb://localhost:27017/twibot'
   accounts: [
+    # Bot class
+    # currently available: `Bot` and `DaytimeBot`
+    # but you can create yours
     bot_class: Bot
+    # Project name. It is used for logging and unique constraints by users.
     name: 'example'
+    # keywords to search by
     keywords: []
+    # if not empty - filter all tweets with languages not in list
     langs: []
+    # if ward was found in tweet it would be skipped
     stopwords: []
+    # skip (or not) retweets
     filter_retweets: yes
+    # twitter filter_lave, can has values 'none', 'low' or 'medium'
+    # see https://blog.twitter.com/2013/introducing-new-metadata-for-tweets
     filter_level: 'low'
+    # automatic unfavorite after specified interval
+    # you should periodically run bin/unfavorite, with crontab for example
     unfavorite_interval: [10, 'hours']
     # Twitter app credentials https://apps.twitter.com/app/new
     consumer_key: ''


Check projection state before accepting mode change

--- source/server/infrastructure/projection.coffee
+++ source/server/infrastructure/projection.coffee
@@ -20,9 +20,14 @@
     else
       @_queuedEvents.push event

-  enterRebuildMode: -> @_state = 'rebuilding'
+  enterRebuildMode: ->
+    if @_is('rebuilding')
+      throw new Error "Invalid state: Cannot enterRebuildMode as #{@constructor.toString()} is already rebuilding"
+    @_state = 'rebuilding'

   exitRebuildMode: ->
+    if !@_is('rebuilding')
+      throw new Error "Invalid state: Cannot exitRebuildMode as #{@constructor.toString()} is not rebuilding"
     @_state = 'projecting'
     @on(event) for event in @_queuedEvents


クラス名から append する周りを修正

クラス名から append する周りを修正

--- src/coffee/kazitori.coffee
+++ src/coffee/kazitori.coffee
@@ -56,18 +56,20 @@

 ## Kazitori クラス
 class Kazitori
-  VERSION:"0.9.9"
-  history:null
-  location:null
-  handlers:[]
-  beforeHandlers:[]
-  afterhandlers:[]
+  VERSION: "0.9.9"
+  history: null
+  location: null
+  handlers: []
+  beforeHandlers: []
+  afterhandlers: []
   rootFiles: ['index.html', 'index.htm', 'index.php', 'unko.html']
-  root:null
-  notFound:null
-  beforeAnytimeHandler:null
-  direct:null
-  isIE:false
+  root: null
+  notFound: null
+  beforeAnytimeHandler: null
+  direct: null
+  isIE: false
+  silent: false
+  started: false
   #URL パラメーター
   _params:
     params:[]
@@ -83,32 +85,32 @@

   ###isBeforeForce###
-  isBeforeForce:false
+  isBeforeForce: false
   #befores の処理を URL 変更前にするかどうか
-  isTemae:false
-  _changeOptions:null
-
-  isNotFoundForce:false
-  _notFoudn:null
-
-  breaker:{}
-
-  _dispatcher:null
-  _beforeDeffer:null
-
-  fragment:null
-  lastFragment:null
-  isUserAction:false
-
-  _isFirstRequest:true
+  isTemae: false
+  _changeOptions: null
+
+  isNotFoundForce: false
+  _notFoudn: null
+
+  breaker: {}
+
+  _dispatcher: null
+  _beforeDeffer: null
+
+  fragment: null
+  lastFragment: null
+  isUserAction: false
+
+  _isFirstRequest: true

   #一時停止しているかどうか
-  isSuspend:false
+  isSuspend: false
   _processStep:
-    'status':'null'
-    'args':[]
+    'status': 'null'
+    'args': []

   constructor:(options)->
     @._processStep.status = 'constructor'
@@ -118,8 +120,10 @@
     if options.routes
       @.routes = options.routes

-    @.root = if options.root then options.root else '/'
+    @.root = if options.root then options.root else if @.root is null then '/' else @.root
     @.isTemae = if options.isTemae then options.isTemae else false
+    @.silent = if options.silent then options.silent else false
+

     @._params = {
       params:[]
@@ -159,7 +163,6 @@
         })
     catch e
       #throw new Error(e)
-      # console.log e
       #IEだと defineProperty がアレらしいので
       #@.__defineGetter__ したほうがいいかなー

@@ -172,9 +175,9 @@
   start:(options)->
     @._processStep.status = 'start'
     @._processStep.args = [options]
-    if Kazitori.started
+    if @.started
       throw new Error('mou hazim matteru')
-    Kazitori.started = true
+    @.started = true
     win = window
     @.options = @_extend({}, {root:'/'}, @.options, options)
     @._hasPushState = !!(@.history and @.history.pushState)
@@ -205,7 +208,7 @@
     @._dispatcher.dispatchEvent( new KazitoriEvent( KazitoriEvent.START, @.fragment ))

     override = @.root
-    if !@.options.silent
+    if !@.silent
       if not @._hasPushState and atRoot
         override = @.root + @.fragment.replace(routeStripper, '')
       else if not atRoot
@@ -218,7 +221,7 @@
     win = window
     win.removeEventListener 'popstate', @observeURLHandler
     win.removeEventListener 'hashchange', @observeURLHandler
-    Kazitori.started = false
+    @.started = false
     #ストップイベントをディスパッチ
     @._dispatcher.dispatchEvent(new KazitoriEvent(KazitoriEvent.STOP, @.fragment))

@@ -233,7 +236,7 @@
     return @direction(options, "prev")

   direction:(option, direction)->
-    if not Kazitori.started
+    if not @.started
       return false

     tmpFrag = @.lastFragment
@@ -255,7 +258,7 @@

   #url を変更する
   change:(fragment, options)->
-    if not Kazitori.started
+    if not @.started
       return false
     @._processStep.status = 'change'
     @._processStep.args = [fragment, options]
@@ -276,7 +279,6 @@
     #frag が undefined になってしまう
     # console.debug frag
     url = @.root + @_replace.apply(frag,[routeStripper, ''])
-
     matched = @_matchCheck(@.fragment, @.handlers)
     if matched is false and @.isNotFoundForce is false
       if @.notFound isnt null
@@ -312,7 +314,7 @@
     if not options
       options = @._changeOptions
     url = @.root + @.fragment.replace(routeStripper, '')
-    if not @.options.silent
+    if not @.silent
       if @._hasPushState
         @.history[ if options.replace then 'replaceState' else 'pushState' ]({}, document.title, url)
       else if @._wantChangeHash
@@ -334,7 +336,7 @@
   #中断する
   #メソッド名 intercept のほうがいいかな
   reject:()->
-    @dispatchEvent(new KazitoriEvent(KazitoriEvent.REJECT, @.fragment))
+    @dispatchEvent(new KazitoriEvent(KazitoriEvent.REJECT, @.fragment ))
     if @._beforeDeffer
       @._beforeDeffer.removeEventListener KazitoriEvent.TASK_QUEUE_COMPLETE, @beforeComplete
       @._beforeDeffer.removeEventListener KazitoriEvent.TASK_QUEUE_FAILED, @beforeFailed
@@ -345,19 +347,19 @@
   suspend:()->
     if @._beforeDeffer?
       @._beforeDeffer.suspend()
-    Kazitori.started = false
+    @.started = false
     @.isSuspend = true
-    @._dispatcher.dispatchEvent( new KazitoriEvent(KazitoriEvent.SUSPEND, @.fragment, @.lastFragment))
+    @._dispatcher.dispatchEvent( new KazitoriEvent( KazitoriEvent.SUSPEND, @.fragment, @.lastFragment ))
     return

   #処理を再開する
   resume:()->
     if @._beforeDeffer?
       @._beforeDeffer.resume()
-    Kazitori.started = true
+    @.started = true
     @.isSuspend = false
     @[@._processStep.status](@._processStep.args)
-    @._dispatcher.dispatchEvent( new KazitoriEvent(KazitoriEvent.RESUME, @.fragment, @.lastFragment))
+    @._dispatcher.dispatchEvent( new KazitoriEvent( KazitoriEvent.RESUME, @.fragment, @.lastFragment ))
     return

   registerHandler:(rule, name, isBefore, callback )->
@@ -405,6 +407,8 @@
       childBefore.update(rule)
     @.beforeHandlers = childBefores.concat @.beforeHandlers

+    #before anytime bind するのどうしようかなー
+

   #ルールを後から追加する
   appendRouter:(child, childRoot)->
@@ -417,23 +421,26 @@
       @_bindChild(rule, child)
       return @
     else
-      if name.hasOwnProperty('__super__')
+      if child.hasOwnProperty('__super__')
         try
-          child = new name({'isAutoStart':false})
-          rule = @_getChildRule(child, childRoot)
-          @_bindChild(rule, child)
+          _instance = new child({'isAutoStart':false})
+          rule = @_getChildRule(_instance, childRoot)
+          @_bindChild(rule, _instance)
           return @
         catch e
           throw new Error("引数の値が不正です。 引数として与えられるオブジェクトは Kazitori を継承している必要があります。")
+    @._dispatcher.dispatchEvent( new KazitoriEvent( KazitoriEvent.ADDED, @.fragment, @.lastFragment ))
     return @

   _getChildRule:(child, childRoot)->
     rule = child.root
     if childRoot
-      lne = childRoot.length
-      if childRoot.match(trailingSlash)
-        childRoot = childRoot.replace(trailingSlash, '')
+      # lne = childRoot.length
+      # if childRoot.match(trailingSlash)
+      #   childRoot = childRoot.replace(trailingSlash, '')
       rule = childRoot
+    if rule.match(trailingSlash)
+      rule = rule.replace(trailingSlash, '')
     if rule is @.root
       throw new Error("かぶってる")
     return rule
@@ -446,13 +453,15 @@
     if child instanceof Kazitori
       @_unbindChild(child, childRoot)
     else
-      if name.hasOwnProperty('__super__')
+      if child.hasOwnProperty('__super__')
         try
-          child = new name({'isAutoStart':false})
-          @_unbindChild(child, childRoot)
+          _instance = new child({'isAutoStart':false})
+          @_unbindChild(_instance, childRoot)
           return @
         catch e
           throw new Error("引数の値が不正です。 引数として与えられるオブジェクトは Kazitori を継承している必要があります。")
+    @._dispatcher.dispatchEvent( new KazitoriEvent(KazitoriEvent.REMOVED, @.fragment, @.lastFragment))
+    return @

   _unbindChild:(child, childRoot)->
     rule = @_getChildRule(child, childRoot)
@@ -481,7 +490,6 @@
   loadURL:(fragmentOverride, options)->
     @._processStep.status = 'loadURL'
     @._processStep.args = [fragmentOverride, options]
-
     if @.isSuspend
       return
     fragment = @.fragment = @getFragment(fragmentOverride)
@@ -550,7 +558,11 @@
       @._dispatcher.dispatchEvent(new KazitoriEvent(KazitoriEvent.NOT_FOUND))

     else if matched.length > 1
-      console.log "too many matched..."
+      for match in matched
+        if @.fragment.indexOf(match.rule) > -1
+            match.callback(@.fragment)
+
+        # if @.fragment.indexOf match.rule
     else
       for handler in matched
         handler.callback(@.fragment)
@@ -774,8 +786,6 @@
           v = if kv[1] then kv[1] else ""
           if v.indexOf('|') > -1
             v = v.split("|")
-          # obj = {}
-          # obj[k] = v
           newQueries[k] = v
         newParam.pop()
         newParam.push last.split('?')[0]
@@ -945,8 +955,8 @@
 # ちょっと大げさな気もするけど外部的には変わらんし
 # 今後を見据えてクラス化しておく
 class Rule
-  rule:""
-  _regexp:null
+  rule: ""
+  _regexp: null
   callback:null
   name:""
   router:null
@@ -973,6 +983,9 @@

   update:(path)=>
     @.rule = path + @.rule
+    #trailing last slash
+    if @.rule isnt '/'
+      @.rule = @.rule.replace(trailingSlash, '')
     @._regexp = @_ruleToRegExp(@.rule)
     re = new RegExp(namedParam)
     matched = path.match(re)
@@ -1155,4 +1168,6 @@
 #一番最初のアクセスがあった
 KazitoriEvent.FIRST_REQUEST = 'first_request'

-Kazitori.started = false
+KazitoriEvent.ADDED = 'added'
+
+KazitoriEvent.REMOVED = 'removed'


Dont put parens into types, use a new type variable name instead

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1156,7 +1156,8 @@
         # throw new Error "type name #{atom.symbol} was not defined" unless kind
         malformed ctx, atom, "This type name has not been defined" if not isFake atom
         kindOfType = star
-      withOrigin (atomicType atom.symbol, kindOfType), atom
+      name = if isFake atom then ctx.freshTypeVariable({}).name else atom.symbol
+      withOrigin (atomicType name, kindOfType), atom
     else
       expanded
   finalKind = kind type


Added icon name and icon color to data in MapInput. See #43158

--- src/elements/Map/MapInput.coffee
+++ src/elements/Map/MapInput.coffee
@@ -130,7 +130,7 @@
 			name: "iconColor"
 			disabled: CUI.util.isNull(@__selectedMarkerOptions.iconName)
 			onDataChanged: =>
-				@__map.updateSelectedMarkerOptions(@__selectedMarkerOptions)
+				@__onIconChanged()
 			options: =>
 				options = []
 				for color in CUI.MapInput.defaults.iconColors
@@ -144,7 +144,7 @@
 			data: @__selectedMarkerOptions
 			name: "iconName"
 			onDataChanged: =>
-				@__map.updateSelectedMarkerOptions(@__selectedMarkerOptions)
+				@__onIconChanged()
 				if not CUI.util.isNull(@__selectedMarkerOptions.iconName)
 					iconColorSelect.enable()
 				else
@@ -164,6 +164,11 @@
 		popoverTemplate.append(@__map, "center")
 		popoverTemplate

+	__onIconChanged: ->
+		@__map.updateSelectedMarkerOptions(@__selectedMarkerOptions)
+		@__data.iconName = @__selectedMarkerOptions.iconName
+		@__data.iconColor = @__selectedMarkerOptions.iconColor
+
 	__initMap: ->
 		@_mapOptions.onMarkerSelected = (marker) =>
 			formattedPosition = @__getFormattedPosition(


Spelling

Spelling

--- app/lib/views/application.coffee
+++ app/lib/views/application.coffee
@@ -128,7 +128,7 @@
         template: Ember.Handlebars.compile """
         n0 = {{view.n0}} cm-3

         p0 = {{view.p0}} cm-3

-        EF = {{view.E_f}} eV1

+        EF = {{view.E_f}} eV

         """


Move concat to the set section

Move concat to the set section

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1216,6 +1216,14 @@
     addToMap initialized, k, args[i + 1]
   initialized

+concatSets =
+concatMaps = (maps...) ->
+  concated = newMap()
+  for map in maps
+    for k, v of map.values
+      addToMap concated, k, v
+  concated
+
 # end of Set

 # Type inference and checker
@@ -1231,7 +1239,7 @@
     for [name, def] in pairs
       [s1, nameIndex, def] = infer context, def, nameIndex
       s2 = unify (subExp s1, (inSet context, name.token)), def.tea
-      context = subContext (concat s2, s1), context
+      context = subContext (concatMaps s2, s1), context
     for [name, def] in pairs
       name.tea = def.tea = (inSet context, name.token)
       [name, def]
@@ -1262,7 +1270,7 @@
         # assume if definedParams.length > 0
         argName = freshName nameIndex
         param = definedParams[0]
-        context = concat context, newMapWith param.token, argName
+        context = concatMaps context, newMapWith param.token, argName
         [s1, nextIndex, body] = infer context, body, nameIndex + 1
         expression.tea = (subExp s1, [argName, body.tea])
         [s1, nextIndex, expression]
@@ -1286,7 +1294,7 @@
     opTea = op.tea
   [s2, nextIndex, arg] = infer (subContext s1, context), arg, nextIndex
   s3 = unify (subExp s2, opTea), [arg.tea, returnName]
-  [(concat s3, s2, s1), nextIndex, (subExp s3, returnName)]
+  [(concatMaps s3, s2, s1), nextIndex, (subExp s3, returnName)]

@@ -1348,7 +1356,7 @@

 findFrees = (type) ->
   if Array.isArray type
-    concat (for subType in type
+    concatSets (for subType in type
       findFrees subType)...
   else
     if isTypeVariable type
@@ -1362,13 +1370,6 @@
 freshName = (nameIndex) ->
   # TODO: handle more than 27 variables
   String.fromCharCode 97 + nameIndex
-
-concat = (maps...) ->
-  concated = newMap()
-  for map in maps
-    for k, v of map.values
-      addToMap concated, k, v
-  concated

 # mycroft = (context, node, environment) ->
 #   if node.label is 'numerical'


Tweak particles to display better in mobile browser

--- source/assets/javascripts/controllers.js.coffee
+++ source/assets/javascripts/controllers.js.coffee
@@ -111,7 +111,7 @@
     progressCircle('.number-4', '.progress-circle-outer-4', 92)
     return

-  if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.platform))
+  if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
     triggerCycle()
   else
     $('#skills').one 'mouseenter', ->
@@ -168,7 +168,7 @@
     $('div#particles-js canvas').css("height", h)
     return

-  if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.platform))
-    triggerParticles(0.5, 50, 0.2, 2)
+  if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
+    triggerParticles(1, 100, 0.3, 2)
   else
     triggerParticles(2, 250, 0.4, 4)


Use radio buttons for export type selection as there is no bulk export resource yet.

--- coffee/cilantro/ui/exporter.coffee
+++ coffee/cilantro/ui/exporter.coffee
@@ -5,12 +5,12 @@
     class ExportType extends c.Backbone.View
         tagName: 'label'

-        className: 'checkbox'
+        className: 'radio'

         render: ->
             title = @model.get('title') or 'untitled'

-            @$el.html("<input type=checkbox name=export-type-check id=export-type-check-#{ title } checked /> #{ title }")
+            @$el.html("<input type=radio name=export-type-radio id=export-type-radio-#{ title } /> #{ title }")
             return @

     class EmptyExportType extends c.Backbone.View


Change toString of Monitor

Change toString of Monitor

--- scripts/monitors.coffee
+++ scripts/monitors.coffee
@@ -77,8 +77,11 @@
           callback? true

     toString: () ->
-      return "#{@url}:#{@data.status} (updated at: #{@data.checkedAt})"
-
+      buffer = "#{@url}: *#{@data.status}* (";
+      buffer += "status code: #{@data.statusCode}, " if @data.statusCode
+      buffer += "updated at: #{@data.checkedAt}"
+      buffer += ", downed at: #{@data.downedAt} " if @data.downedAt
+      buffer += ")"

   robot.respond /(.+)を監視(?:して)?/i, (msg) ->
     url = Monitor.adjustUrl msg.match[1]


run offshore in browser

run offshore in browser

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -19,6 +19,7 @@
 #   hubot Chris excuse
 #   hubot goodbye Prem
 #   hubot who is the real Sachin
+#   hubot ship CBRIS
 #
 # Author:
 #   Mateusz Kopij <sagasu>
@@ -58,6 +59,10 @@
   robot.respond /Chris excuse/i, (msg)->
     msg.send msg.random chrisExcuse

+  robot.respond /ship CBRIS/i, (msg)->
+    msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/offshoreZillaRunningInChrome.jpg"
+    msg.send "stay tuned..."
+
   robot.respond /who is the real Sachin/i, (msg)->
     msg.send "he is :sachin:"


Add "all" and "other" options for toggleMuteTab.

This adds advanced options for toggleMuteTab.

Examples:

   map X toggleMuteTab all
   map Y toggleMuteTab other

In the first case, all audible, unmuted tabs are muted; otherwise all
muted tabs are unmuted.

The second case is the same, except that the current tab is excluded
from consideration.

Follow on from #2269.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -154,6 +154,23 @@
       windowId: request.tab.windowId
       openerTabId: request.tab.id
     chrome.tabs.create tabConfig, callback
+
+toggleMuteTab = do ->
+  muteTab = (tab) -> chrome.tabs.update tab.id, {muted: !tab.mutedInfo.muted}
+
+  ({tab: currentTab, registryEntry}) ->
+    if registryEntry.options.all? or registryEntry.options.other?
+      # If there are any audible, unmuted tabs, then we mute them; otherwise we unmute any muted tabs.
+      chrome.tabs.query {audible: true}, (tabs) ->
+        if registryEntry.options.other?
+          tabs = (tab for tab in tabs when tab.id != currentTab.id)
+        audibleUnmutedTabs = (tab for tab in tabs when tab.audible and not tab.mutedInfo.muted)
+        if 0 < audibleUnmutedTabs.length
+          muteTab tab for tab in audibleUnmutedTabs
+        else
+          muteTab tab for tab in tabs when tab.mutedInfo.muted
+    else
+      muteTab currentTab

 #
 # Selects the tab with the ID specified in request.id
@@ -210,7 +227,7 @@
   openCopiedUrlInCurrentTab: (request) -> TabOperations.openUrlInCurrentTab extend request, url: Clipboard.paste()
   openCopiedUrlInNewTab: (request) -> @createTab extend request, url: Clipboard.paste()
   togglePinTab: ({tab}) -> chrome.tabs.update tab.id, {pinned: !tab.pinned}
-  toggleMuteTab: ({tab}) -> chrome.tabs.update tab.id, {muted: !tab.mutedInfo.muted}
+  toggleMuteTab: toggleMuteTab
   moveTabLeft: moveTab
   moveTabRight: moveTab
   nextFrame: ({count, frameId, tabId}) ->


Change welcome email

Change welcome email

--- src/smc-hub/email.coffee
+++ src/smc-hub/email.coffee
@@ -246,7 +246,7 @@

Welcome to #{SITE_NAME}

-#{SITE_NAME} is sophisticated web service for collaborative computation.
+#{SITE_NAME} is a sophisticated web service for collaborative computation.

@@ -258,11 +258,11 @@

-
Explore #{SITE_NAME}

+
Exploring #{SITE_NAME}

-Your work on #{SITE_NAME} happens inside private projects.
-They form your personal workspaces containing your files, computational worksheets, and data.
-You run your computations through the web interface, via interactive worksheets and notebooks, or by executing a program in a terminal.
+In #{SITE_NAME} your work happens inside private projects.
+These are personal workspaces which contain your files, computational worksheets, and data.
+You can run your computations through the web interface, via interactive worksheets and notebooks, or by executing a program in a terminal.
 #{SITE_NAME} supports online editing of
     Jupyter Notebooks,
     Sage Worksheets,


some better error handling

some better error handling

--- scripts/snomedct.coffee
+++ scripts/snomedct.coffee
@@ -45,7 +45,10 @@
     .get() (err, response, body) ->

       data = JSON.parse body
-      res.send "For SCTID #{sctid}, the FSN is #{data.fsn} and the URL/URI is http://snomed.info/id/#{sctid}"
+      try
+        res.send "For SCTID #{sctid}, the FSN is #{data.fsn} and the URL/URI is http://snomed.info/id/#{sctid}"
+      catch
+        res.send "Sorry, I cannot find any matching concepts for #{sctid}. Please try again."

@@ -56,4 +59,7 @@
     .get() (err, response, body) ->

       data = JSON.parse body
-      res.send "For concept #{concept}, there are #{data.details.total} matching descriptions and the first one has an SCTID of #{data.matches[0].conceptId} and the FSN, #{data.matches[0].fsn} with a URL/URI of http://snomed.info/id/#{data.matches[0].conceptId}"
+      try
+        res.send "For concept #{concept}, there are #{data.details.total} matching descriptions and the first one has an SCTID of #{data.matches[0].conceptId} and the FSN, #{data.matches[0].fsn} with a URL/URI of http://snomed.info/id/#{data.matches[0].conceptId}"
+      catch error
+        res.send "Sorry, I cannot find any matching descriptions for #{concept}. Please try again."


delete some stuff we don't use

delete some stuff we don't use

--- server/code/model/user.coffee
+++ server/code/model/user.coffee
@@ -27,12 +27,6 @@

 class User extends ModelBase
   @dbClass: zDbUser
-
-  constructor: (obj) ->
-    super obj
-    if not ('apikey' of obj)
-      @apikey = fresh_apikey()
-    @

   validate: ->
     # TODO: proper regex, share validation across server & client
@@ -136,15 +130,6 @@
         else
           callback "Can't find user", null

-rand32 = ->
-  # 32 bits of lovely randomness.
-  # It so happens that Math.random() only generates 32 random
-  # bits on V8 (on node.js and Chrome).
-  Math.floor(Math.random() * Math.pow(2, 32))
-
-fresh_apikey = ->
-  [rand32(), rand32()].join('-')
-
 exports.User = User

 exports.dbInject = (dbObj) ->


Make an https get to github using env auth token

--- src/streak-podium.coffee
+++ src/streak-podium.coffee
@@ -25,3 +25,9 @@
       return
     res.send "Found the access token in the environment! #{access_token}"

+  robot.hear /http test/i, (res) ->
+    access_token = process.env.HUBOT_ORG_ACCESS_TOKEN
+    robot.http("https://api.github.com/user?access_token=#{access_token}")
+      .get() (err, response, body) ->
+        res.send "Got a response! #{body}"
+


Update comment now I know what this is actually for

--- assets/front.coffee
+++ assets/front.coffee
@@ -1,4 +1,4 @@
-# This is apparently needed. Don't delete it.
+# Link to the Flash files that are used for the visualizations
 soundManager.setup
 	url: '/static/flash/'


Remove dependance of forever-monitor (which causes an npm audit issue) for nodemon

--- src/tasks/forever.coffee
+++ src/tasks/forever.coffee
@@ -2,8 +2,8 @@
 path = require "path"

 # NPM
-log         = require "de-logger"
-{ Monitor } = require "forever-monitor"
+log     = require "de-logger"
+nodemon = require "nodemon"

@@ -57,24 +57,23 @@
 		# Create file path
 		entry = src+path.sep+@server.config.forever.entry

-		# Kill previous child if it exists
+		# Ensure no previous instance is runnning
 		@terminate()

-		# Create a monitor for starting a child process
-		@child = new Monitor entry, max: 1, killTree: true
+		# Ensure we work with a clean slate of nodemon
+		nodemon.reset()

-		# Handle exit
-		@child.on "exit:code", (code) =>
-
-			log.warn "#{@server.config.title} - Forever stopped with code: #{code}" if code
-
-		# Start child process
-		@child.start()
+		# Start running the application
+		nodemon
+			script: entry,
+			ext: "js"
+			ignore: ["*"]

 	terminate: =>

-		@child.kill() if @child and @child.running
+		# Close the currently running app in case it is running
+		nodemon.emit "quit"


Fixed placement costs

Fixed placement costs

--- app/assets/javascripts/placements.coffee
+++ app/assets/javascripts/placements.coffee
@@ -14,6 +14,7 @@
     if $homeSelect.find(':selected').attr('data-use-placement-cost') is 'true'
       $placementCost.show()
     else
+      $('#placement_cost').val('')
       $placementCost.hide()

   $homeSelect.change ->


:fire: $.fn.fire require

:fire: $.fn.fire require

--- app/assets/javascripts/task_lists.coffee
+++ app/assets/javascripts/task_lists.coffee
@@ -5,7 +5,6 @@
 #= provides tasklist:change
 #= provides tasklist:changed
 #
-#= require crema/events/fire
 #= require crema/events/pageupdate
 #
 # Enables Task List update behavior.


update dropdown search match only at start

--- app/classifier/tasks/dropdown/index.cjsx
+++ app/classifier/tasks/dropdown/index.cjsx
@@ -159,6 +159,7 @@
               allowCreate={selects[i].allowCreate}
               noResultsText={if not options?.length then null}
               addLabelText="Press enter for {label}..."
+              matchPos="start"
               matchProp="label"
               ref="select-#{i}"
             />


Remove exception throws from submit handler

The exceptions are not necessary and @dan-nyanko believes they will be mistaken for bugs
https://github.com/ecohealthalliance/mobile-survey/pull/18#issuecomment-213121485

--- app/client/questions/add_question.coffee
+++ app/client/questions/add_question.coffee
@@ -71,7 +71,7 @@

     unless form.checkValidity()
       toastr.error('Please fill out all required fields')
-      throw new Meteor.Error('Invalid form')
+      return

     formData = _.object $(form).serializeArray().map(
       ({name, value})-> [name, value]
@@ -80,18 +80,18 @@

     if not instance.type.get()
       toastr.error('Please select a type')
-      throw new Meteor.Error('Invalid form')
+      return

     if instance.type.get() == 'multipleChoice' or instance.type.get() == 'checkboxes'
       console.log instance.choices
       if instance.choices.find().count() == 0
         toastr.error('Please add some choices')
-        throw new Meteor.Error('Invalid form')
+        return
       else
         choiceStrings = instance.choices.find().map(({name})-> name)
         if _.any(choiceStrings, _.isEmpty)
           toastr.error('Please fill in all the choices')
-          throw new Meteor.Error('Invalid form')
+          return
         questionProperties.choices = choiceStrings

     questionProperties.required = questionProperties.required is 'on'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -60,7 +60,7 @@
   @returns {Array} An array of the objects found
   ###
   each: (selector, fn) ->
-    self.f
+    self.find(selector)

   find: (selector) ->
     results = []


engine_selector

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -4,7 +4,6 @@
 @name Selector
 @fieldOf Engine
 @module
-
 @param {Object} I Instance variables
 @param {Object} self Reference to the engine
 ###


test-esm: Update to import coffee file

test-esm: Update to import coffee file

--- src/test/test-esm.coffee
+++ src/test/test-esm.coffee
@@ -4,13 +4,13 @@
 Do not work in Rollup or Babel.
 ###

-###* @type {Data} See data structure in all-data.coffee ###
-import allData from './data/all-data.mjs'
 import Numbo from '../esm/index.mjs'
 import enUS from '../esm/en-us.mjs'
 import zhTW from '../esm/zh-tw.mjs'
 import zhCN from '../esm/zh-cn.mjs'

+###* @type {Data} See data structure in all-data.coffee ###
+import allData from './data/all-data.coffee'
 import langs from './shared/langs.coffee'
 import types from './shared/types.coffee'
 import createResult from './shared/create-result.coffee'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -110,6 +110,8 @@
   equals 0.5.turns, Math.TAU / 2

 test "#circularPoints", ->
-
+  4.circularPoints (p) ->
+
+# p gets Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1)

 module undefined


Fix typo

Fix typo

--- src/fs_utils/common.coffee
+++ src/fs_utils/common.coffee
@@ -67,7 +67,7 @@
     input.on  'error', (err) -> fsStreamErrHandler err, 'input'
     output.on 'error', (err) -> fsStreamErrHandler err, 'output'
     output.on 'close', callback
-  retryCopy -> copy null, true
+  retryCopy = -> copy null, true
   parentDir = sysPath.dirname(destination)
   exports.exists parentDir, (exists) ->
     if exists


Improve Spectralines

Improve Spectralines

--- src/ubret/spectra.coffee
+++ src/ubret/spectra.coffee
@@ -32,10 +32,20 @@
     request.open("GET", url, true)
     request.onload = (e) =>
       lines = new Object
-      rawLines = JSON.parse(request.response)[subject.spectrumID]
-      lines[line.name] = line.wavelength for line in rawLines
+      rawLines = JSON.parse(request.response)[subject.spectrumID]
+      lines[line.name] = line.wavelength * (1 + line.linez) for line in rawLines
       @plot(subject.wavelengths, subject.flux, subject.best_fit, lines)
     request.send()
+
+  legend: =>
+    list = @opts.selector.append('ul')
+      .attr('class', 'spectra-legend')
+
+    list.selectAll('li')
+      .data(['Spectra', 'Best Fit', 'Emission Lines'])
+        .enter().append('li')
+        .attr('class', (d) -> "#{d}-legend")
+        .text((d) -> d)

   plot: (wavelengths, fluxes, bestFit, spectralLines) =>
     margin =
@@ -45,12 +55,12 @@
       left: 80
     width = @opts.width - margin.left - margin.right
     height = @opts.height - margin.top - margin.bottom
-
+
     x = d3.scale.linear()
       .range([0, width])
       .domain(d3.extent(wavelengths))
     y = d3.scale.linear()
-      .range([0, height])
+      .range([height, 0])
       .domain(d3.extent(fluxes))
     x.ticks(8)

@@ -124,5 +134,6 @@
         .style("stroke", "rgb(255,0,0)")
         .style("stroke-width", 0.5)

+    @legend()

 window.Ubret.Spectra = Spectra


Added safety check to the donations form

--- static/src/scripts/app/views/DonationFormView.coffee
+++ static/src/scripts/app/views/DonationFormView.coffee
@@ -18,6 +18,7 @@
       @name = options.name or "JailbreakHQ"
       @teamId = options.teamId or 0
       @parentView = options.parent
+      @submitted = false

     render: =>
       data =
@@ -106,7 +107,9 @@

       @data = _.extend stripeData, formData

-      Stripe.card.createToken stripeData, @stripeResponseHandler
+      if not @submitted # ensure each view can only send one donation charge request
+        @submitted = true
+        Stripe.card.createToken stripeData, @stripeResponseHandler

     stripeResponseHandler: (status, response) =>
       @l.setProgress 0.7
@@ -131,6 +134,7 @@
         ).done (data) =>
           @donationThankYou()
         .fail (err) =>
+          @submitted = false
           @donateFormResponse("alert", "Donation failed. Try again.")

     donationThankYou: =>


fix(iscroll): evented refresh and delay detection

To refresh IScroll instances on a scope or its child scopes:
```javascript
scope.$broadcast 'bp-refreshIScrollInstances'
```

Instantiation delay is now 500ms only is a real transition that takes time is
going to happen. Before it used 500ms delay whenever a parent element had
`ng-animate` attribute.

--- modules/directives/iscroll/index.coffee
+++ modules/directives/iscroll/index.coffee
@@ -14,9 +14,20 @@

     scope.getIScroll = -> iscroll

+    # only use a delay if an animation will be played during transition
+    delay =
+      if element.parents('[ng-animate]').length
+        transition = scope.getFullTransition?()
+        if not transition or transition.split('-')[0] is ''
+          0
+        else
+          500
+      else
+        0
+
     # merge defaults with global user options
     options = angular.extend
-      delay: if element.parents('[ng-animate]').length then 500 else 0
+      delay: delay
       stickyHeadersSelector: 'bp-table-header'
       scrollbarsEnabled: yes
     , bpConfig.iscroll or {}
@@ -42,14 +53,15 @@
     # schedule IScroll instantication
     $timeout instanciateIScroll, options.delay

-    scope.$on '$destroy', ->
-      iscroll.destroy()
+    scope.$on 'bpRefreshIScrollInstances', ->
+      scope.getIScroll()?.refresh?()
+
+    scope.$on '$destroy', -> iscroll.destroy()

     scope.$on '$stateChangeStart', ->
+      iscroll.destroy()
       element.removeAttr 'bp-iscroll'
       element.find('bp-iscroll-wrapper').css
         position: 'static'
         transform: ''
         transition: ''
-
-      iscroll.destroy()


JMachine: rename options from user, owner to asUser, asOwner.

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -175,10 +175,10 @@
     return newUsers

-  addUser = (users, user, owner)->
-
-    newUsers.push { id: user.getId(), owner }
+  addUser = (users, user, asOwner)->
+
     newUsers = excludeUser users, user
+    newUsers.push { id: user.getId(), owner: asOwner }

     return newUsers

@@ -247,12 +247,12 @@

   # Instance Methods

-  addUsers: (usersToAdd, owner, callback)->
+  addUsers: (usersToAdd, asOwner, callback)->

     users = @users.splice 0

     for user in usersToAdd
-      users = addUser users, user, owner
+      users = addUser users, user, asOwner

     if users.length > 10
       callback new KodingError \
@@ -273,9 +273,9 @@

   shareWith: (options, callback)->

-    { target, user, owner } = options
-    user  ?= yes
-    owner ?= no
+    { target, asUser, asOwner } = options
+    asUser  ?= yes
+    asOwner ?= no

     unless target?
       return callback new KodingError "Target required."
@@ -294,8 +294,8 @@

       if target instanceof JUser

-        if user
-        then @addUsers targets, owner, callback
+        if asUser
+        then @addUsers targets, asOwner, callback
         else @removeUsers targets, callback

       else
@@ -443,9 +443,9 @@
   # .shareWith can be used like this:
   #
   # JMachineInstance.shareWith {
-  #   user: yes, owner: no, target: ["gokmen", "dicle"]}, cb
+  #   asUser: yes, asOwner: no, target: ["gokmen", "dicle"]}, cb
   # }
-  #                    user slugs ->  ^^^^^^ ,  ^^^^^
+  #                        user slugs ->  ^^^^^^ ,  ^^^^^

   shareWith$: permit 'populate users',

@@ -481,17 +481,17 @@

   share: secure (client, users, callback)->
-    options = target: users, user: yes
+    options = target: users, asUser: yes
     JMachine::shareWith$.call this, client, options, callback

   unshare: secure (client, users, callback)->
-    options = target: users, user: no
+    options = target: users, asUser: no
     JMachine::shareWith$.call this, client, options, callback

   setAsOwner: secure (client, users, callback)->
-    options = target: users, user: yes, owner: yes
+    options = target: users, asUser: yes, asOwner: yes
     JMachine::shareWith$.call this, client, options, callback

   unsetAsOwner: secure (client, users, callback)->
-    options = target: users, user: yes, owner: no
+    options = target: users, asUser: yes, asOwner: no
     JMachine::shareWith$.call this, client, options, callback


fix human text

fix human text

--- app/lib/field-guide-content/human.coffee
+++ app/lib/field-guide-content/human.coffee
@@ -1,15 +1,5 @@
 module.exports = {
   description: '''
-    Fire is a natural part of the ecology of the African savanna. Fires often occur naturally in places where there is a distinct wet season followed by an extreme dry season. During the dry season, the vegetation dries out and can catch fire naturally from lightening or from a human source, such as a campfire. Fire, along with grazing by large animals, maintains the savanna biome in a state of open grassland with or savanna, which is grassland with sparse trees.
-
-    Gorongosa’s grasslands and savannas depend on naturally occurring fires and heavy grazing by herbivores. If fire is not allowed to sweep occasionally through these open habitats, or if grazing by animals falls dramatically, grasslands are invaded shrubby and woody vegetation, depriving many of Gorongosa’s grazers of prime feeding areas. Fire is a natural part of the ecosystem in Gorongosa, and humans have used fire in this region for more than 150,000 years. Fires release nutrients and remove old, dry grasses above ground, allowing new growth to sprout from the roots when the rains arrive.  These areas of lush, green re-growth provide prime feeding grounds for many of Gorongosa’s grazers, including waterbuck, wildebeest, and African buffalo.
+    Humans coexist with nature and sometimes these humans, or their vehicles, are also captured in trail camera photos. As a national park, Gorongosa attracts tourists from around the world who go on safari on the roads within the park. This tourism industry is supported by staff who guide tourists, provide hospitality, and maintain infrastructure, such as roads, buildings, electricity, and water lines. Other staff that are critical to the future of the park are the rangers, scientists, and conservationists who regularly do work in remote areas of the park. In Gorongosa, local people live in villages near the park where they grow crops and depend on the land for their livelihood. They also have to adapt to wildlife that moves between the park and their villages.
   '''
-
-  information: [{
-    label: 'Frequency'
-    value: 'Several times every dry season'
-  }, {
-    label: 'Causes'
-    value: 'lightning or accidental human causes'
-  }]
 }


Add camera panning

Add camera panning

--- app/scripts/components/ScrollWall.coffee
+++ app/scripts/components/ScrollWall.coffee
@@ -2,6 +2,7 @@
   init: ->
     @requires('2D, Edge, Collision')
     @shakes = []
+    @motions = []
     @attr
       x: 0
       y: 0
@@ -58,6 +59,21 @@
         xShift += coords[0]
         yShift += coords[1]
         @shakes.splice(index, 1) if shake.easing.complete
+
+      cameraPan =
+        x: 0
+        y: 0
+      for motion, index in @motions by -1
+        coordsBefore = motion.coords(motion.easing.value())
+        motion.easing.tick(fd.dt)
+        coordsAfter = motion.coords(motion.easing.value())
+        deltaX = coordsAfter[0] - coordsBefore[0]
+        deltaY = coordsAfter[1] - coordsBefore[1]
+        cameraPan.x += deltaX
+        cameraPan.y += deltaY
+        @x += deltaX
+        @y += deltaY
+        @motions.splice(index, 1) if motion.easing.complete
       x = @x + xShift
       y = @y + yShift

@@ -69,6 +85,7 @@
       Crafty.trigger('CameraMove',
         x: Math.round(@x)
         y: Math.round(@y)
+        panning: cameraPan
       )
       Crafty.trigger('ViewportMove',
         x: Math.round(x)
@@ -112,20 +129,13 @@
     )

   cameraPan: (options) ->
-    #@shakes.push(
-      #amount: amount
-      #duration: duration
-      #shakes: Math.ceil(duration / 100)
-      #easing: new Crafty.easing(duration, 'linear')
-      #startX: if Math.random() > 0.5 then -1 else 1
-      #startY: if Math.random() > 0.5 then -1 else 1
-      #coords: (v) ->
-        #shake = Math.cos((Math.PI / 2) + (v * @shakes * (Math.PI / 2)))
-        #[
-          #shake * amount * @startX
-          #shake * amount * @startY
-        #]
-    #)
+    @motions.push(
+      easing: new Crafty.easing(options.duration, 'easeInOutQuad')
+      x: options.x ? 0
+      y: options.y ? 0
+      coords: (v) ->
+        [@x * v, @y * v]
+    )

   scrollWall: (speed) ->
     if speed.x? && speed.y?


update test route

update test route

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -541,7 +541,7 @@
           # console.log JSON.stringify(formData_formatted)
           formData_formatted_JSON = JSON.stringify(formData_formatted)

-          url = "https://139.59.145.226:8000/order/form_handler"
+          url = "http://p-u-p-i-l.com/order/form_handler"

           $.ajax
             type: 'POST'


debugging permalink

debugging permalink

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -516,6 +516,7 @@
   eventOrderLinkSuccessPage: =>
     if $("#Success").length > 0
       data = @_getOrderPermalink()
+      console.log data
       url = window.location.origin + "/cart?" + $.param(data)
       link = "permalink"
       html = "
You can always revisit your order with this #{link}.
"


update for legacy cart permalink

update for legacy cart permalink

todo fix link generation on server

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -626,12 +626,14 @@
     query = window.location.search.substring(1)
     if query.length > 0
       # ?0_order=world_none%2Ceye_120hz_binocular%2Clicense_commercial&0_qty=3&1_order=world_hr%2Ceye_120hz_binocular%2Clicense_commercial&1_qty=2&2_order=product_support_6&2_qty=1
+      # https://pupil-labs.com/cart/?0_order=world_hr%252Ceye_120hz%252Clicense_academic&0_qty=1
       LocalStorage.clear()
       pairs = query.split('&')
       # while pairs.length > 0
       j = 0
       for p,i in pairs by 2
         product_id = decodeURIComponent(decodeURIComponent(pairs.shift().split("=").pop())).split(',')
+        product_id = updateLegacyProductIds_(product_id)
         qty = parseInt(pairs.shift().split("=").pop())
         item = {
           "product" : product_id


Update DiplomatView.coffee

Update DiplomatView.coffee

--- app/views/contribute/DiplomatView.coffee
+++ app/views/contribute/DiplomatView.coffee
@@ -100,7 +100,7 @@
     fr: ['Anon', 'Armaldio', 'ChrisLightman', 'Elfisen', 'Feugy', 'MartinDelille', 'Oaugereau', 'Xeonarno', 'dc55028', 'jaybi', 'pstweb', 'veritable', 'xavismeh']             # français, French
     ja: ['Coderaulic', 'g1itch', 'kengos', 'treby']             # 日本語, Japanese
     ar: ['5y', 'ahmed80dz']             # العربية, Arabic
-    'pt-BR': ['Bia41', 'Gutenberg Barros', 'Kieizroe', 'Matthew Burt', 'brunoporto', 'cassiocardoso', 'jklemm']        # português do Brasil, Portuguese (Brazil)
+    'pt-BR': ['Bia41', 'Gutenberg Barros', 'Kieizroe', 'Matthew Burt', 'brunoporto', 'cassiocardoso', 'jklemm', 'Arkhad']        # português do Brasil, Portuguese (Brazil)
     'pt-PT': ['Imperadeiro98', 'Matthew Burt', 'ProgramadorLucas', 'ReiDuKuduro', 'batista', 'gutierri']        # Português (Portugal), Portuguese (Portugal)
     pl: ['Anon', 'Kacper Ciepielewski', 'TigroTigro', 'kvasnyk']             # język polski, Polish
     it: ['AlessioPaternoster', 'flauta', 'Atomk']              # italiano, Italian


Only remove properties that may reference a dom element

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -5,8 +5,11 @@
   settings = require('settings')

   dispose = (obj) ->
+    delete obj.parent
+    delete obj.el
+    delete obj.$el
+    delete obj.regions
     obj?.__proto__ = Function
-    delete obj[key] for key of obj

   class Region
     constructor: (@el, @parent) ->


Ignore case on file extensions when uploading images [#106768564]

--- src/code/utils/has-valid-image-extension.coffee
+++ src/code/utils/has-valid-image-extension.coffee
@@ -4,7 +4,7 @@
   link = document.createElement 'a'
   link.setAttribute 'href', imageName
   [..., extension] = link.pathname.split '.'
-  valid = (['gif', 'png', 'jpg', 'jpeg'].indexOf extension) isnt -1
+  valid = (['gif', 'png', 'jpg', 'jpeg'].indexOf extension.toLowerCase()) isnt -1
   if not valid
     alert tr "~DROP.ONLY_IMAGES_ALLOWED"
   valid


Update Chrome and Chromedriver versions in the docs

--- resources/assets/js/outer/versions.coffee
+++ resources/assets/js/outer/versions.coffee
@@ -3,8 +3,8 @@
     "#{v} #{CI.Versions[v]}"

   Firefox: "38.0"
-  Chrome: "38.0.2125.122"
-  chromedriver: "2.12"
+  Chrome: "43.0.2357.130"
+  chromedriver: "2.16"

   default_ruby: "1.9.3-p448"


Block Ctrl-S while playing

Block Ctrl-S while playing

--- app/views/play/level_view.coffee
+++ app/views/play/level_view.coffee
@@ -64,6 +64,9 @@
   events:
     'click #level-done-button': 'onDonePressed'

+  shortcuts:
+    'ctrl+s': 'onCtrlS'
+
   constructor: (options, @levelID) ->
     console.profile?() if PROFILE_ME
     super options
@@ -188,6 +191,9 @@
 #    @showWizardSettingsModal() if not me.get('name')

   # callbacks
+
+  onCtrlS: (e) ->
+    e.preventDefault()

   onLevelReloadFromData: (e) ->
     isReload = Boolean @world


Template.render should ALWAYS return a Promise

--- src/liquid/template.coffee
+++ src/liquid/template.coffee
@@ -70,10 +70,8 @@

     try
       # render the nodelist.
-      # for performance reasons we get a array back here.
-      # join will make a string out of it
-      result = @root.render(context)
-      result.join?() or result
+      # TODO: original implementation used arrays up until here (for performance reasons)
+      Liquid.async.when(@root.render(context))
     finally
       @errors = context.errors


added more tests to improve coverage

added more tests to improve coverage

--- spec/parser.spec.coffee
+++ spec/parser.spec.coffee
@@ -145,11 +145,18 @@
     beforeEach ->
       workout = parse(twoSets)

-    it "returns workout", ->
-      expect(workout).not.to.eq null
-
-    it "returns workout with 2 sets", ->
-      expect(workout.sets.length).to.eq 2
+    describe "the workout", ->
+      it "is valid", ->
+        expect(workout).not.to.eq null
+
+      it "has 2 sets", ->
+        expect(workout.sets.length).to.eq 2
+
+      it "has a total distance of 1900 yards", ->
+        expect(workout.totalDistance()).to.eq 1900
+
+      # it "has a total time of 40 minutes", ->
+      #   expect(workout.totalTime()).to.eq "40:00"

     describe "the first set", ->
       warmUp = null


updated test names

updated test names

--- spec/router_test.coffee
+++ spec/router_test.coffee
@@ -84,16 +84,15 @@
         throw new Error("should have raised an exception!")
       catch
       eq(["uups!"], errors)
-  describe "in browser", ->
+  describe "browser integration", ->
     beforeEach ->
       window.open("about:blank", "test_window")
-    describe "should handle url rendering", ->
-      it "should render correct view", zhain().
-        window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
-        window_open("/index.html", (w) -> w.router.initDone; eq("/index.html", text(w, "#txt"))).
-        test()
-      it "should handle click to /foo without reloading page", zhain().
-        window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
-        click("#pageSame", (w) -> eq("Ok!", text(w, "#pageSame"))).
-        click("#link_foo", (w) -> eq(["Ok!", "/foo"], [text(w, "#pageSame"), text(w, "#txt")])).
-        test()
+    it "should render correct view", zhain().
+      window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
+      window_open("/index.html", (w) -> w.router.initDone; eq("/index.html", text(w, "#txt"))).
+      test()
+    it "should handle click to /foo without reloading page", zhain().
+      window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
+      click("#pageSame", (w) -> eq("Ok!", text(w, "#pageSame"))).
+      click("#link_foo", (w) -> eq(["Ok!", "/foo"], [text(w, "#pageSame"), text(w, "#txt")])).
+      test()


Increase defaultTimeoutInterval for Travis

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -51,7 +51,7 @@

 jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions

-if process.env.JANKY_SHA1 and process.platform is 'win32'
+if (process.env.JANKY_SHA1 and process.platform is 'win32') or process.env.CI
   jasmine.getEnv().defaultTimeoutInterval = 60000
 else
   jasmine.getEnv().defaultTimeoutInterval = 5000


update examples

update examples

--- src/elements/st-app.coffee
+++ src/elements/st-app.coffee
@@ -30,7 +30,8 @@

     # add some dummy data
     service.addSentence('This is a pen.', 'これはペンです。')
-    service.addSentence('That is two bananas.', 'あれは2本のバナナです。')
+    service.addSentence('He is bouzuya.', '彼はぼうずやです。')
+    service.addSentence('Cute is justice', 'かわいいは正義')

   add: ->
     service = SentenceService.getInstance()


ADDED window.onhashchange defaults to null

--- src/zombie/window.coffee
+++ src/zombie/window.coffee
@@ -146,6 +146,10 @@
     window.outerHeight = window.innerHeight = height
   window.resizeBy = (width, height)->
     window.resizeTo(window.outerWidth + width,  window.outerHeight + height)
+
+  # Some libraries (e.g. Backbone) check that this property exists before
+  # deciding to use onhashchange, so we need to set it to null.
+  window.onhashchange = null

   # Help iframes talking with each other
   window.postMessage = (data, targetOrigin)->


fixed conflict

fixed conflict

--- workers/social/lib/social/models/user/index.test.coffee
+++ workers/social/lib/social/models/user/index.test.coffee
@@ -67,9 +67,13 @@

   describe '#createUser', ->

+    # variables needed in the login method scope
+    userInfo = {}
+
+    # setting necessary variables before each test case
     beforeEach ->

-      userInfo = TestHelper.getUserInfo()
+      userInfo = generateUserInfo()

     it 'should pass error passwordStatus type is not valid', (done) ->
@@ -100,7 +104,7 @@

         ->
           # setting a different email, username will be duplicate
-          userInfo.email  = "kodingtestuser+#{TestHelper.getRandomString()}@gmail.com"
+          userInfo.email  = "kodingtestuser+#{generateRandomString()}@gmail.com"
           expectedError   = "The slug #{userInfo.username} is not available."

           JUser.createUser userInfo, (err) ->
@@ -125,7 +129,7 @@

         ->
           # setting a different username, email will be duplicate
-          userInfo.username = TestHelper.getRandomString()
+          userInfo.username = generateRandomString()
           expectedError     = "Sorry, \"#{userInfo.email}\" is already in use!"

           JUser.createUser userInfo, (err) ->
@@ -208,6 +212,7 @@

     it 'should be to able login user when data is valid', (done) ->

+      # variables needed in the login method scope
       session                = null
       lastLoginDate          = null
       # generating user info with random username and password
@@ -594,6 +599,150 @@
         done()

+  describe '#convert()', ->
+
+    # variables that will be used in the convert test suite scope
+    client       = {}
+    userFormData = {}
+
+    # this function will be called everytime before each test case under this test suite
+    beforeEach ->
+
+      # cloning the client and userFormData from dummy datas each time.
+      # used pure nodejs instead of a library bcs we need deep cloning here.
+      client                = JSON.parse JSON.stringify dummyClient
+      userFormData          = JSON.parse JSON.stringify dummyUserFormData
+
+      randomString          = generateRandomString()
+      userFormData.email    = "kodingtestuser+#{randomString}@gmail.com"
+      userFormData.username = randomString
+
+
+    it 'should pass error if account is already registered', (done) ->
+
+      client.connection.delegate.type = 'registered'
+
+      JUser.convert client, userFormData, (err) ->
+        expect(err)         .to.exist
+        expect(err.message) .to.be.equal "This account is already registered."
+        done()
+
+
+    it 'should pass error if username is a reserved one', (done) ->
+
+      queue             = []
+      reservedUsernames = ['guestuser', 'guest-']
+
+      for username in reservedUsernames
+        userFormData.username = username
+
+        queue.push ->
+          JUser.convert client, userFormData, (err) ->
+            expect(err.message).to.exist
+            queue.next()
+
+      # done callback will be called after all usernames checked
+      queue.push -> done()
+
+      daisy queue
+
+
+    it 'should pass error if passwords do not match', (done) ->
+
+      userFormData.password         = 'somePassword'
+      userFormData.passwordConfirm  = 'anotherPassword'
+
+      JUser.convert client, userFormData, (err) ->
+        expect(err)         .to.exist
+        expect(err.message) .to.be.equal "Passwords must match!"
+        done()
+
+
+    it 'should pass error if username is in use', (done) ->
+
+      queue = [
+
+        ->
+          JUser.convert client, userFormData, (err) ->
+            console.log err
+            expect(err).to.not.exist
+            queue.next()
+
+        ->
+          # sending a different email address, username will remain same(duplicate)
+          userFormData.email = 'kodingtestuser@koding.com'
+
+          JUser.convert client, userFormData, (err) ->
+            expect(err.message).to.exist
+            queue.next()
+
+        -> done()
+
+      ]
+
+      daisy queue
+
+
+    it 'should pass error if email is in use', (done) ->
+
+      queue = [
+
+        ->
+          JUser.convert client, userFormData, (err) ->
+            expect(err).to.not.exist
+            queue.next()
+
+        ->
+          # sending a different username, email address will remain same(duplicate)
+          userFormData.username = 'kodingtestuser'
+
+          JUser.convert client, userFormData, (err) ->
+            expect(err.message).to.exist
+            queue.next()
+
+        -> done()
+
+      ]
+
+      daisy queue
+
+
+    it.skip 'should set a random password when signed up with github', (done) ->
+
+
+    it 'should register user and create account when valid data passed to convert method', (done) ->
+
+      queue = [
+
+        ->
+          JUser.convert client, userFormData, (err) ->
+            expect(err).to.not.exist
+            queue.next()
+
+        ->
+          params = { username : userFormData.username }
+
+          JUser.one params, (err, { data : {email, registeredFrom} }) ->
+            expect(err)               .to.not.exist
+            expect(email)             .to.be.equal userFormData.email
+            expect(registeredFrom.ip) .to.be.equal client.clientIP
+            queue.next()
+
+        ->
+          params = { 'profile.nickname' : userFormData.username }
+
+          JAccount.one params, (err, { data : {profile} }) ->
+            expect(err)               .to.not.exist
+            expect(profile.nickname)  .to.be.equal userFormData.username
+            queue.next()
+
+        -> done()
+
+      ]
+
+      daisy queue
+
+
 beforeTests()

 runTests()


Allow element to have multiple views

Allow element to have multiple views

--- src/marbles/view.js.coffee
+++ src/marbles/view.js.coffee
@@ -4,7 +4,7 @@
     all: {}
   }
   @_id_counter: 0
-  @view_name: '_default'
+  @view_name: '_default' # chould be snake_case version of Marbles.Views key (e.g. Marbles.Views.MainView should be main_view)

   @find: (cid) ->
     @instances.all[cid]
@@ -60,23 +60,25 @@
   bindViews: =>
     @_child_views ?= {}
     _.each Marbles.DOM.querySelectorAll('[data-view]', (@container?.el || @el)), (el) =>
-      view_class_name = Marbles.DOM.attr(el, 'data-view')
+      view_class_names = Marbles.DOM.attr(el, 'data-view').split(/\s+/)

-      if viewClass = Marbles.Views[view_class_name]
-        _init = false
-        unless el.view_cid && (view = viewClass.instances.all[el.view_cid])
-          view = new viewClass el: el, parent_view: @
-          _init = true
-        @_child_views[view_class_name] ?= []
-        @_child_views[view_class_name].push view.cid
-        el.view_cid = view.cid
+      for view_class_name in view_class_names
+        if viewClass = Marbles.Views[view_class_name]
+          _init = false
+          el.view_cids ?= {}
+          unless (_view_cid = el.view_cids[view_class_name]) && (view = viewClass.instances.all[_view_cid])
+            view = new viewClass el: el, parent_view: @
+            _init = true
+          @_child_views[view_class_name] ?= []
+          @_child_views[view_class_name].push view.cid
+          el.view_cids[view_class_name] = view.cid

-        view.bindViews?()
+          view.bindViews?()

-        @trigger("init:#{view_class_name}", view) if _init
-        @trigger("init-view", view_class_name, view) if _init
-      else
-        console.warn "Marbles.Views.#{view_class_name} is not defined!"
+          @trigger("init:#{view_class_name}", view) if _init
+          @trigger("init-view", view_class_name, view) if _init
+        else
+          console.warn "Marbles.Views.#{view_class_name} is not defined!"

   detachChildViews: =>
     for class_name, cids of (@_child_views || {})
@@ -121,7 +123,8 @@
       else
         @el.innerHTML = html

-    @el.view_cid = @cid
+    @el.view_cids ?= {}
+    @el.view_cids[@constructor.view_name.replace(/_([a-z])/ig, (match, char) -> char.toUpperCase()).replace(/^([a-z])/, (match, char) -> char.toUpperCase())] = @cid

     @detachChildViews()