test(core): group registry get tests

test(core): group registry get tests

--- packages/core/test/registry/get.coffee
+++ packages/core/test/registry/get.coffee
@@ -5,94 +5,98 @@

 describe 'registry.get', ->
   return unless tags.api
+
+  describe 'get all', ->

-  it 'return null when not registered', ->
-    reg = registry.create()
-    action = await reg.get ['get', 'an', 'action']
-    should(action).be.exactly(null)
+    it 'get all', ->
+      reg = registry.create()
+      await reg.register ['get', 'first', 'action'], handler: (->)
+      await reg.register ['get', 'second', 'action'], handler: (->)
+      reg.get().then (actions) ->
+        Object.keys(
+          actions.get
+        ).should.eql [ 'first', 'second' ]

-  it 'a registered function', ->
-    reg = await registry
-    .create()
-    .register ['get', 'an', 'action'],
-      config: key: 'value',
-      handler: (->)
-    {config} = await reg.get ['get', 'an', 'action']
-    config.key.should.eql 'value'
+    it 'get all with flatten config', ->
+      reg = registry.create()
+      reg.register ['my', 'function'], handler: (->)
+      actions = await reg.get flatten: true
+      actions.some( (action) -> action.action.join('.') is 'my.function').should.be.true()

-  it 'get all', ->
-    reg = registry.create()
-    await reg.register ['get', 'first', 'action'], handler: (->)
-    await reg.register ['get', 'second', 'action'], handler: (->)
-    reg.get().then (actions) ->
-      Object.keys(
-        actions.get
-      ).should.eql [ 'first', 'second' ]
+    it 'get all with flatten config and deprecate', ->
+      reg = registry.create()
+      reg.register ['new', 'function'], handler: (->)
+      reg.deprecate ['old', 'function'], ['new', 'function'], handler: (->)
+      actions = await reg.get flatten: true, deprecate: false
+      actions.some( (action) -> action.action.join('.') is 'new.function').should.be.true()
+      actions.some( (action) -> action.action.join('.') is 'old.function').should.be.false()
+      actions = await reg.get flatten: true, deprecate: true
+      actions.some( (action) -> action.action.join('.') is 'new.function').should.be.true()
+      actions.some( (action) -> action.action.join('.') is 'old.function').should.be.true()
+
+  describe 'get action', ->

-  it 'option `deprecated`', ->
-    reg = registry.create()
-    reg.register ['new', 'function'], handler: (->)
-    reg.deprecate ['old', 'function'], ['new', 'function'], handler: (->)
-    actions = await reg.get deprecate: false
-    actions['new']['function'][''].handler.should.be.type 'function'
-    (actions['old'] is undefined).should.be.true()
-    actions = await reg.get deprecate: true
-    actions['new']['function'][''].handler.should.be.type 'function'
-    actions['old']['function'][''].handler.should.be.type 'function'
+    it 'return null when not registered', ->
+      reg = registry.create()
+      action = await reg.get ['get', 'an', 'action']
+      should(action).be.exactly(null)

-  it 'get all with flatten config', ->
-    reg = registry.create()
-    reg.register ['my', 'function'], handler: (->)
-    actions = await reg.get flatten: true
-    actions.some( (action) -> action.action.join('.') is 'my.function').should.be.true()
+    it 'a registered function', ->
+      reg = await registry
+      .create()
+      .register ['get', 'an', 'action'],
+        config: key: 'value',
+        handler: (->)
+      {config} = await reg.get ['get', 'an', 'action']
+      config.key.should.eql 'value'

-  it 'get all with flatten config and deprecate', ->
-    reg = registry.create()
-    reg.register ['new', 'function'], handler: (->)
-    reg.deprecate ['old', 'function'], ['new', 'function'], handler: (->)
-    actions = await reg.get flatten: true, deprecate: false
-    actions.some( (action) -> action.action.join('.') is 'new.function').should.be.true()
-    actions.some( (action) -> action.action.join('.') is 'old.function').should.be.false()
-    actions = await reg.get flatten: true, deprecate: true
-    actions.some( (action) -> action.action.join('.') is 'new.function').should.be.true()
-    actions.some( (action) -> action.action.join('.') is 'old.function').should.be.true()
+    it 'option `deprecated`', ->
+      reg = registry.create()
+      reg.register ['new', 'function'], handler: (->)
+      reg.deprecate ['old', 'function'], ['new', 'function'], handler: (->)
+      actions = await reg.get deprecate: false
+      actions['new']['function'][''].handler.should.be.type 'function'
+      (actions['old'] is undefined).should.be.true()
+      actions = await reg.get deprecate: true
+      actions['new']['function'][''].handler.should.be.type 'function'
+      actions['old']['function'][''].handler.should.be.type 'function'

-  it 'return an immutable copy of the action', ->
-    reg = registry.create()
-    reg.register ['new', 'function'],
-      metadata:
+    it 'return an immutable copy of the action', ->
+      reg = registry.create()
+      reg.register ['new', 'function'],
+        metadata:
+          test_1: 'value 1'
+        handler: (->)
+      # Retrieve the actkon
+      action = await reg.get ['new', 'function']
+      # Attempt to alter the action
+      action.metadata.test_2 = 'value 2'
+      # Ensure the attempt failed
+      action = await reg.get ['new', 'function']
+      action.metadata.should.eql
         test_1: 'value 1'
-      handler: (->)
-    # Retrieve the actkon
-    action = await reg.get ['new', 'function']
-    # Attempt to alter the action
-    action.metadata.test_2 = 'value 2'
-    # Ensure the attempt failed
-    action = await reg.get ['new', 'function']
-    action.metadata.should.eql
-      test_1: 'value 1'
-
-  it 'hook `nikita:registry:normalize` doesnt mutate the action', ->
-    reg = registry.create
-      plugins: plugandplay
-        plugins: [
-          ->
-            hooks:
-              'nikita:registry:normalize': (action) ->
-                action.config?.key = 'new value'
-                action.config?.new_key = 'new value'
-        ]
-    reg.register ['action'],
-      config: key: 'value'
-      handler: (->)
-    action = await reg.get 'action'
-    # Ensure the returned action is altered
-    action.should.match
-      config:
-        key: 'new value', new_key: 'new value'
-      handler: (val) -> val.should.be.a.Function()
-    # Now, make sure the stored action is not altered
-    action = await reg.get 'action', normalize: false
-    action.should.match
-      config: key: 'value'
-      handler: (val) -> val.should.be.a.Function()
+
+    it 'hook `nikita:registry:normalize` doesnt mutate the action', ->
+      reg = registry.create
+        plugins: plugandplay
+          plugins: [
+            ->
+              hooks:
+                'nikita:registry:normalize': (action) ->
+                  action.config?.key = 'new value'
+                  action.config?.new_key = 'new value'
+          ]
+      reg.register ['action'],
+        config: key: 'value'
+        handler: (->)
+      action = await reg.get 'action'
+      # Ensure the returned action is altered
+      action.should.match
+        config:
+          key: 'new value', new_key: 'new value'
+        handler: (val) -> val.should.be.a.Function()
+      # Now, make sure the stored action is not altered
+      action = await reg.get 'action', normalize: false
+      action.should.match
+        config: key: 'value'
+        handler: (val) -> val.should.be.a.Function()


Refresh after adding image to populate metadata.

--- dit4c-highcommand/app/assets/javascripts/controllers/compute-nodes-ctrl.coffee
+++ dit4c-highcommand/app/assets/javascripts/controllers/compute-nodes-ctrl.coffee
@@ -122,6 +122,8 @@
         .success (token) ->
           $scope.images[node.id] ||= []
           $scope.images[node.id].push(token)
+          # Add response doesn't include metadata, kick off refresh
+          refreshImages(node)

     $scope.pullImage = (node, image) ->
       $http


fixed coffeelint errors on workers/social/config.email.coffee

--- workers/social/lib/social/config.email.coffee
+++ workers/social/lib/social/config.email.coffee
@@ -1,9 +1,11 @@
-{argv} = require 'optimist'
+{ argv } = require 'optimist'
 KONFIG = require('koding-config-manager').load("main.#{argv.c}")
-{email, protocol} = KONFIG
+{ email, protocol } = KONFIG

 host = email.host
 email.protocol = protocol
-email.protocol = email.protocol.split(':').shift()+':'
+email.protocol = email.protocol.split(':').shift() + ':'

-module.exports = email+module.exports = email
+
+


need to create those guests… temp

need to create those guests… temp

--- workers/social/lib/social/models/guest.coffee
+++ workers/social/lib/social/models/guest.coffee
@@ -15,7 +15,7 @@

   @set
     sharedMethods   :
-      static        : ['obtain', 'resetAllGuests']
+      static        : ['obtain', '_resetAllGuests']
       instance      : ['on', 'getDefaultEnvironment', 'fetchStorage']
     indexes         :
       guestId       : ['unique', 'descending']


JGuest checkFlag always returns no

JGuest checkFlag always returns no

--- workers/social/lib/social/models/guest.coffee
+++ workers/social/lib/social/models/guest.coffee
@@ -118,6 +118,8 @@
                     delete obtaining[clientId]
                     callback null, guest

+  checkFlag:-> no
+
   fetchStorage: secure (client, options, callback)->
     JAppStorage = require './appstorage'
     callback null, new JAppStorage options


pricingappview: checkSlug on keyup

pricingappview: checkSlug on keyup

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -41,6 +41,17 @@
       </ul>
       """

+  checkSlug: ->
+    slug      = @groupForm.inputs.GroupUrl
+    slugView  = @groupForm.inputs.Slug
+    tmpSlug   = slug.getValue()
+
+    if tmpSlug.length > 2
+      slugy = KD.utils.slugify tmpSlug
+      KD.remote.api.JGroup.suggestUniqueSlug slugy, (err, newSlug)->
+        slugView.updatePartial "#{location.protocol}//#{location.host}/#{newSlug}"
+        slug.setValue newSlug
+
   showCancellation: ->
     @hideWorkflow()
     return  if @cancellation


Workspace: Removed headerIcon and headerStyling from Panel class.

--- client/Workspace/panel.coffee
+++ client/Workspace/panel.coffee
@@ -31,18 +31,9 @@
       cssClass     : "title"
       partial      : """ #{title} """

-    @headerIcon    = new KDCustomHTMLView
-      tagName      : "span"
-      cssClass     : "icon"
-
-    @headerTitle.addSubView @headerIcon, null, yes
-
     @header.addSubView @headerTitle
     @header.addSubView @headerButtonsContainer = new KDCustomHTMLView
       cssClass     : "tw-header-buttons"
-
-    {headerStyling} = @getOptions()
-    @applyHeaderStyling headerStyling if headerStyling

   createHeaderButtons: ->
     # TODO: fatihacet - DRY
@@ -133,20 +124,6 @@
           cssClass : "modal-cancel"
           callback : -> modal.destroy()

-  applyHeaderStyling: (options) ->
-    if options.custom
-      return @header.getElement().setAttribute "style", options.custom
-
-    {bgColor, bgGradient, bgImage, textColor, textShadowColor, borderColor} = options
-
-    @header.setCss      "color"             , textColor                     if textColor
-    @header.setCss      "borderBottomColor" , "#{borderColor}"              if borderColor
-    @header.setCss      "background"        , "#{bgColor}"                  if bgColor
-    @headerIcon.setCss  "backgroundImage"   , "url(#{bgImage})"             if bgImage
-    @header.setCss      "textShadowColor"   , "0 1px 0 #{textShadowColor}"  if textShadowColor
-
-    KD.utils.applyGradient @header, bgGradient.first, bgGradient.last       if bgGradient
-
   viewAppended: ->
     super
     @getDelegate().emit "NewPanelAdded", this


FSFolder: remove debugger

FSFolder: remove debugger

--- client/ide/fs/fsfolder.coffee
+++ client/ide/fs/fsfolder.coffee
@@ -1,8 +1,4 @@
 class FSFolder extends FSFile
-
-  constructor: ->
-    { @stack } = new Error
-    super

   fetchContents:(dontWatch, callback)->
     [callback, dontWatch] = [dontWatch, callback]  unless callback?


debug

debug

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -45,7 +45,7 @@
             select: (event, ui) =>
                 if (@_userInteractionEnabled == false)
                     return
-                JSLog(ui)
+                JSLog(ui.item[0])
                 item = ui.item.context.textContent
                 @selectMenuItem(item)
                 @closeMenu()


change server URLs to 1.133

change server URLs to 1.133

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -36,10 +36,10 @@
 			THREAD_BUTTON_BAR: 	'.iH > div'

 		# production URL
-		prodUrl: "https://script.google.com/macros/s/AKfycbzWm8LljtgQreQt2DoGe7g-fiZeXwjgy4rZQXw8CY7aFnucKtk/exec"
+		prodUrl: "https://script.google.com/macros/s/AKfycbyF_NpRI6dc_xOyOwV5xIIOCgRwsyou2QHSPNcBXO5CM8kIoKA/exec"

 		# dev URL
-		devUrl: "https://script.google.com/a/macros/feth.com/s/AKfycbwriDyhVS8RTcPDEvd1p_OZSc2w9r2pUd1ZtDhp6xo/dev"
+		devUrl: "https://script.google.com/a/macros/feth.com/s/AKfycbxDfbpvbasFE4IEFnMYwQd49MS7dMrB4efGYCMSTrI/dev"

 		# Current GMail address of the logged in user
 		currentGmail: null


Clean up code. Use brackets for jQuery functions for familiarity, rather than fancy CoffeeScript syntax. Flatten test structure a bit.

--- tests/specs/SuperfishTest.coffee
+++ tests/specs/SuperfishTest.coffee
@@ -3,46 +3,42 @@
 describe "Superfish", ->

   $menu = null
+  $liHasUl = null

   beforeEach ->
     $menu = $('ul.sf-menu')
+    $liHasUl = $menu.find('li:has(ul):first')
     $menu.superfish()

   afterEach ->
-    $menu.superfish 'destroy'
-
+    $menu.superfish('destroy')

   it "should exist", ->
     expect($.fn.superfish).toBeDefined()

   it "should be chainable", ->
-    $menu.superfish 'destroy'
+    $menu.superfish('destroy')
     expect($menu.superfish()).toBe 'ul'

   it "should store options", ->
-    expect($menu.data 'sf-options' ).toBeDefined()
+    expect($menu.data('sf-options') ).toBeDefined()

   describe "options", ->

     it "should have default options", ->
-      options = $menu.data 'sf-options'
+      options = $menu.data('sf-options')
       expect(options.speed).toMatch 'normal'

     it "should allow default options to be overridden", ->
-      $menu.superfish 'destroy'
+      $menu.superfish('destroy')
       $menu.superfish
         speed: 1000
-      options = $menu.data 'sf-options'
+      options = $menu.data('sf-options')
       expect(options.speed).toEqual 1000

   describe "method access", ->
-
-    $liHasUl = null
-
-    beforeEach ->
-      $liHasUl = $menu.find('li:has(ul):first')

     it "'show' method should exist", ->
       expect($liHasUl.superfish('show')).toBeDefined()
@@ -65,41 +61,41 @@
       ).not.toThrow new Error('Method show does not exist on jQuery.fn.superfish')

-    describe "'destroy' method", ->
+  describe "'destroy' method", ->

-      it "should handle multiple calls gracefully", ->
+    it "should handle multiple calls gracefully", ->
+      $menu.superfish('destroy')
+      expect( ->
         $menu.superfish('destroy')
-        expect( ->
-          $menu.superfish('destroy')
-        ).not.toThrow new Error("Uncaught TypeError: Cannot read property 'sfTimer' of null")
-        expect($menu.superfish('destroy')).toBe 'ul'
+      ).not.toThrow new Error("Uncaught TypeError: Cannot read property 'sfTimer' of null")
+      expect($menu.superfish('destroy')).toBe 'ul'

-    describe "'show' method", ->
-      it "should fail silently if Superfish is uninitialised", ->
-        $menu.superfish('destroy')
-        expect( $liHasUl.superfish('show') ).toBe 'li'
+  describe "'show' method", ->
+    it "should fail silently if Superfish is uninitialised", ->
+      $menu.superfish('destroy')
+      expect( $liHasUl.superfish('show') ).toBe 'li'

-      it "should cause child ul to be visible", ->
-        $submenu = $liHasUl.children('ul')
-        expect($submenu).toBeHidden()
-        expect($liHasUl).not.toBe '.sfHover'
-        $liHasUl.superfish 'show'
-        expect($submenu).toBeVisible()
-        expect($liHasUl).toBe '.sfHover'
+    it "should cause child ul to be visible", ->
+      $submenu = $liHasUl.children('ul')
+      expect($submenu).toBeHidden()
+      expect($liHasUl).not.toBe '.sfHover'
+      $liHasUl.superfish('show')
+      expect($submenu).toBeVisible()
+      expect($liHasUl).toBe '.sfHover'

-    describe "'hide' method", ->
-      it "should fail silently if Superfish is uninitialised", ->
-        $menu.superfish('destroy')
-        expect( $liHasUl.superfish('hide') ).toBe 'li'
-
-      it "should cause child ul to be hidden", ->
-        $submenu = $liHasUl.children('ul')
-        $liHasUl.superfish 'show'
-        expect($liHasUl).toBe '.sfHover'
-        expect($submenu).toBeVisible()
-        # do an instant hide for now until I figure out why .toBeHidden fails when animated
-        $liHasUl.superfish 'hide', true
-        expect($submenu).toBeHidden()
-        expect($liHasUl).not.toBe '.sfHover'
+  describe "'hide' method", ->
+    it "should fail silently if Superfish is uninitialised", ->
+      $menu.superfish('destroy')
+      expect( $liHasUl.superfish('hide') ).toBe 'li'
+
+    it "should cause child ul to be hidden", ->
+      $submenu = $liHasUl.children('ul')
+      $liHasUl.superfish('show')
+      expect($liHasUl).toBe '.sfHover'
+      expect($submenu).toBeVisible()
+      # do an instant hide for now until I figure out why .toBeHidden fails when animated
+      $liHasUl.superfish('hide', true)
+      expect($submenu).toBeHidden()
+      expect($liHasUl).not.toBe '.sfHover'


Fix 'show' test.

Fix 'show' test.

--- tests/specs/SuperfishTest.coffee
+++ tests/specs/SuperfishTest.coffee
@@ -5,8 +5,9 @@
   $menu = null

   beforeEach ->
-    $menu = $('ul.sf-menu').superfish()
-
+    $menu = $('ul.sf-menu')
+    $menu.superfish()
+
   afterEach ->
     $menu.superfish 'destroy'

@@ -15,7 +16,8 @@
     expect($.fn.superfish).toBeDefined()

   it "should be chainable", ->
-    expect($menu).toBe 'ul'
+    $menu.superfish 'destroy'
+    expect($menu.superfish()).toBe 'ul'

   it "should store options", ->
     expect($menu.data 'sf-options' ).toBeDefined()
@@ -49,7 +51,8 @@
       expect($liHasUl.superfish('hide')).toBeDefined()

     it "'destroy' method should exist", ->
-      expect($liHasUl.superfish('destroy')).toBeDefined()
+      expect($menu.superfish('destroy')).toBeDefined()
+      $menu.superfish()

     it "should not allow access to private functions", ->
       expect( ->
@@ -65,7 +68,15 @@
     describe "'destroy' method", ->

       it "should handle multiple calls gracefully", ->
+        $menu.superfish('destroy')
         expect( ->
           $menu.superfish('destroy')
-          $menu.superfish('destroy')
         ).not.toThrow new Error("Uncaught TypeError: Cannot read property 'sfTimer' of null")
+        expect($menu.superfish('destroy')).toBe 'ul'
+
+
+    describe "'show' method", ->
+      it "should fail silently if Superfish is uninitialised", ->
+        $menu.superfish('destroy')
+        expect( $liHasUl.superfish('show') ).toBe 'li'
+


Add cmd-p to match sublime text's bindings

--- keymaps/fuzzy-finder.cson
+++ keymaps/fuzzy-finder.cson
@@ -1,5 +1,6 @@
 'body':
   'meta-t': 'fuzzy-finder:toggle-file-finder'
+  'meta-p': 'fuzzy-finder:toggle-file-finder'
   'meta-b': 'fuzzy-finder:toggle-buffer-finder'
   'ctrl-.': 'fuzzy-finder:find-under-cursor'
   'meta-B': 'fuzzy-finder:toggle-git-status-finder'


HUB-150: HttpOutbound Adapter emits a native hMessage on POST

--- lib/adapters/http_out.coffee
+++ lib/adapters/http_out.coffee
@@ -68,7 +68,7 @@
           url: url.format {protocol: 'http:', hostname: @server_url, port: @port, pathname: @path}
           method: 'POST'
           encoding: "UTF-8"
-          json: hMessage.payload
+          json: hMessage

         @reqst post_options, (err, res) =>
           if err


Tests: Activity: likeunlike: Added checkSharePopup test.

--- client/test/lib/activity/likeunlike.coffee
+++ client/test/lib/activity/likeunlike.coffee
@@ -64,3 +64,17 @@
       .pause                    8000 # wait for latency to make sure really unliked on server
       .waitForElementVisible    afterUnlikeSelector, 25000
       .end()
+
+
+  checkSharePopup: (browser) ->
+
+    helpers.postActivity(browser)
+
+    selector           = activitySelector + ' .activity-actions span.optional'
+    sharePopupSelector = '.activity-share-popup'
+
+    browser
+      .waitForElementVisible  selector, 25000
+      .click                  selector
+      .waitForElementVisible  sharePopupSelector, 20000
+      .waitForElementVisible  sharePopupSelector + ' input.share-input', 20000 # Assertion


changed change event to keyup event

changed change event to keyup event

--- src/main.coffee
+++ src/main.coffee
@@ -175,8 +175,9 @@
             $("#run").removeAttr("disabled")
             $(".status-text").text("Paused")

-        $("#clockSpeed").change =>
-            @mic.setSpeed(parseInt($("#clockSpeed").val()))
+        $("#clockSpeed").keyup =>
+            value = parseInt($("#clockSpeed").val())
+            @mic.setSpeed(if value then value else 1)

         @registerVisible = true


Export Directory class

Export Directory class

--- src/main.coffee
+++ src/main.coffee
@@ -114,3 +114,4 @@
   paths

 exports.File = require './file'
+exports.Directory = require './directory'


SCREEN 2275 - Delete jwtToken if submission time expired. We cannot verify identity.

--- src/main.coffee
+++ src/main.coffee
@@ -76,6 +76,11 @@

     @ # explicitly return self

+  maybe_delete_jwt_token: (xhr) ->
+    # We can't verify anonymous responses.
+    if xhr.responseJSON?.template == 'Submission time has expired.'
+      delete window.localStorage['jwtToken']
+
   corsSupported: ->
     'withCredentials' of new XMLHttpRequest()

@@ -130,6 +135,7 @@
             'href': xhr.responseJSON?.verify_api_endpoint
             'button': xhr.responseJSON?.verify_email_button
           }))
+          @maybe_delete_jwt_token(xhr)
         else
           @$el.find('.fr_loading').text(
             "#{FormRenderer.t.error_loading}: \"#{xhr.responseJSON?.error || 'Unknown'}\""
@@ -317,6 +323,7 @@
             'href': xhr.responseJSON?.verify_api_endpoint
             'button': xhr.responseJSON?.verify_email_button
           }))
+          @maybe_delete_jwt_token(xhr)

   waitForRequests: (cb) ->
     if @requests > 0


fix for null callbacks

fix for null callbacks

--- lib/connection/client.coffee
+++ lib/connection/client.coffee
@@ -24,7 +24,7 @@
   activate: ->
     @handle 'cb', (id, result) =>
       try
-        @callbacks[id].resolve result
+        @callbacks[id]?.resolve result
       finally
         delete @callbacks[id]
         @loading.done()


Rename _check_for_duplicate_signature to _check_if_duplicate_signature

--- src/mock.coffee
+++ src/mock.coffee
@@ -36,7 +36,7 @@
   # TODO: add description
   args: (args...) ->
     @_check_args_usage(args...)
-    @_check_for_duplicate_signature(@current_signature.method_name, args...)
+    @_check_if_duplicate_signature(@current_signature.method_name, args...)
     @current_signature.args = args
     @_set_state("args")
     @
@@ -82,8 +82,7 @@
     @_throw_returns_must_be_after_expects_or_args() unless @_state_in("expects", "args")
     @_throw_returns_usage() unless value?

-  # TODO: rename to _check_if_duplicate_signature
-  _check_for_duplicate_signature: (method_name, args...) ->
+  _check_if_duplicate_signature: (method_name, args...) ->
     @_throw_duplicate_expectation("#{method_name}(#{args})") if @_find_signature(method_name, args...)

   _check_for_uncalled_signatures: ->


A factory must be defined

A factory must be defined

--- src/molo.coffee
+++ src/molo.coffee
@@ -64,6 +64,9 @@
         skipFunc = true
       else
         [name, defines] = [undefined, name]
+
+    # Skip if no factory has been defined
+    return undefined unless defines

     # Get properties from defines if it's an object, else they are no dependencies and take the function as the factory
     if typeof defines is 'function' then define = defines else {define, require: deps, context} = defines
@@ -125,7 +128,7 @@
     return undefined if skipFunc

     # Execute factory function and store it in cache
-
+
     # Special case: Factory is an object and has no dependencies ()
     if typeof define is 'object' and deps.length is 0
       cache[name] = define


fix tests

fix tests

--- lib/notifications-log.coffee
+++ lib/notifications-log.coffee
@@ -50,7 +50,7 @@

   destroy: ->
     @element.remove()
-    item.destroy() for item in logItems
+    item.destroy() for item in @logItems
     @emitter.emit 'did-destroy'

   getElement: -> @element


修改字节控制为255

修改字节控制为255

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -49,9 +49,9 @@
             alert = note.alert

             oldAlert_bytesize = alert.replace(/[^\x00-\xff]/gi, "--").length
-
-            if note_bytesize > 265
-                difference = note_bytesize - 265
+
+            if note_bytesize > 255
+                difference = note_bytesize - 255
                 alert_bytesize =  oldAlert_bytesize - difference - 3

                 alertNew = ''


updated handlebars

updated handlebars

--- lib/web/views/ki-flow.coffee
+++ lib/web/views/ki-flow.coffee
@@ -81,6 +81,7 @@
       throw "Selector '#{selector}' returned #{nodeCount} elements. There were #{assertCount} asserts. #{numberOfCompared} elements were ok, but you need to #{verb} #{Math.abs(diff)} asserts!"
   null # cleaner javascript

+hb = Handlebars.create
 handlebarsCache = {}

 getCompiledTemplate = (templateId) ->
@@ -90,7 +91,7 @@
   template = $(templateId)
   if template.size() == 0
     throw "Could not locate template '#{templateId}'"
-  handlebarsCache[templateId] = Handlebars.compile(template.html())
+  handlebarsCache[templateId] = hb.compile(template.html())

 this.renderElements = (destId, templateId, data) ->
   dest = $(destId)


Fix #413 for real.

Fix #413 for real.

Don't just hide the error message.

--- lib/kernel-manager.coffee
+++ lib/kernel-manager.coffee
@@ -57,7 +57,7 @@

     startKernelFor: (grammar, onStarted) ->
         try
-            rootDirectory = atom.project.rootDirectories[0].path or
+            rootDirectory = atom.project.rootDirectories[0]?.path or
                 path.dirname atom.workspace.getActiveTextEditor().getPath()
             connectionFile = path.join(
                 rootDirectory, 'hydrogen', 'connection.json'
@@ -69,7 +69,7 @@

         catch e
             unless e.code is 'ENOENT'
-                console.log 'KernelManager: Cannot start existing kernel:\n', e
+                console.error 'KernelManager: Cannot start existing kernel:\n', e

         language = @getLanguageFor grammar
         @getKernelSpecFor language, (kernelSpec) =>


Tweak descriptions & orders

Tweak descriptions & orders

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -33,7 +33,7 @@
       deprecationWarnings:
         title: 'Deprecation Warnings'
         type: 'boolean'
-        description: 'Hides deprecation warnings if disabled.'
+        description: 'If disabled, hides deprecation warnings.'
         default: true
         order: 3
       numberOfThreads:
@@ -54,7 +54,7 @@
           type: 'string'
         order: 5
       externalProcessPort:
-        title: 'Port for communicating with the Julia process'
+        title: 'Port for Communicating with the Julia Process'
         type: 'string'
         description: '`random` will use a new port each time, or enter an integer to set the port statically.'
         default: 'random'
@@ -137,29 +137,29 @@
         type: 'boolean'
         description: 'Show plots in Atom.'
         default: true
-        order: 6.5
+        order: 7
       openNewEditorWhenDebugging:
         title: 'Open New Editor When Debugging'
         type: 'boolean'
         default: false
         description: 'Opens a new editor tab when stepping into a new file instead
                       of reusing the current one (requires restart).'
-        order: 7
+        order: 8
       cellDelimiter:
         title: 'Cell Delimiter'
         type: 'array'
         default: ['##', '#---', '#%%', '# %%']
         description: 'Regular expressions for determining cell delimiters.'
-        order: 8
+        order: 9
       useStandardLayout:
         title: 'Restore Standard Layout on Start'
         type: 'boolean'
         default: false
-        order: 9
+        order: 10
       customLayoutOptions:
         title: 'Custom Layout Options'
         type: 'object'
-        order: 10
+        order: 11
         collapsed: true
         properties:
           console:
@@ -346,7 +346,7 @@
         title: 'Shell'
         type: 'string'
         default: terminal.defaultShell()
-        description: 'Shell. Defaults to `$SHELL`.'
+        description: 'The location of an executable shell. Set to `$SHELL` by default, and if `$SHELL` isn\'t set then fallback to `bash` or `powershell.exe` (on Windows).'
         order: 4
       terminal:
         title: 'Terminal'
@@ -371,6 +371,7 @@
         type: 'string'
         enum: ['block', 'underline', 'bar']
         default: 'block'
+        radio: true
         order: 8
       cursorBlink:
         title: 'Cursor Blink'
@@ -405,7 +406,7 @@
         order: 3
       agentAuth:
         title: 'Use SSH agent'
-        description: 'Requires `$SSH_AUTH_SOCKET` to be set. Defaults to putty\'s pageant on Windows'
+        description: 'Requires `$SSH_AUTH_SOCKET` to be set. Defaults to putty\'s pageant on Windows.'
         type: 'boolean'
         default: true
         order: 4
@@ -431,7 +432,7 @@
     order: 5.5

 if process.platform == 'win32'
-  config.juliaOptions.enablePowershellWrapper =
+  config.juliaOptions.properties.enablePowershellWrapper =
     title: 'Enable Powershell Wrapper'
     type: 'boolean'
     default: true


fix little mistake with PATCH

fix little mistake with PATCH

--- library/nucleus/stubs.coffee
+++ library/nucleus/stubs.coffee
@@ -58,7 +58,7 @@
     # usually means partial replacing contents with the new contents.
     # This HTTP method nicely maps to UPDATE method of the storages.
     # Use this method to partially replace the contents of resources.
-    PATCH: @prototype.unsupported
+    @PATCH: @prototype.unsupported

     # Delete the contents of the resources at the establushed path. It
     # generally should destroy the contents of the resource for good.


Update: check for ProductSync.actions available.

--- lib/product-import.coffee
+++ lib/product-import.coffee
@@ -11,7 +11,7 @@

   constructor: (@logger, options = {}) ->
     @sync = new ProductSync
-    if options.blackList
+    if options.blackList and ProductSync.actions
       @sync.config @_configureSync(options.blackList)
     @client = new SphereClient options.clientConfig
     @_configErrorHandling(options)


Optimized join table delete

Optimized join table delete

--- lib/relations/many.coffee
+++ lib/relations/many.coffee
@@ -118,14 +118,12 @@

         # destroy old
         if changes.removed
-          for model_json in changes.removed
-            # TODO: optimize
-            do (model_json) => queue.defer (callback) =>
-              # console.log "Destroying join for: #{@model_type.model_name} join: #{util.inspect(model_json)}"
-              @join_table.destroy model_json.id, callback
+          do (model_json) => queue.defer (callback) =>
+            @join_table.destroy {id: {$in: (model_json.id for model_json in changes.removed)}}, callback

         # create new
         for related_id in added
+          # TODO: optimize through batch create
           do (related_id) => queue.defer (callback) =>
             attributes = {}
             attributes[@foreign_key] = model.id


use fsevents library on OS X, greatly improves performance

--- lib/watcher/folder.coffee
+++ lib/watcher/folder.coffee
@@ -86,6 +86,8 @@
     @watcher = chokidar.watch(@path,
       ignoreInitial : true
       persistent    : true
+      usePolling    : false
+      useFSEvents    : true
     )

     @watcher


corrected reference to gnu emacs

corrected reference to gnu emacs

--- lib/langs/fortran-beautify.coffee
+++ lib/langs/fortran-beautify.coffee
@@ -1,5 +1,5 @@
 ###
-// Requires [f90ppr](http://www.ifremer.fr/ditigo/molagnon/fortran90/contenu.html)
+// Requires [emacs](https://www.gnu.org/software/emacs/)
 ###

 "use strict"
@@ -12,7 +12,7 @@
       " Please set this in the Atom Beautify package settings.")

   path = options.emacs_path
-  console.debug "[fortran-beautify] options: " + JSON.stringify(options)
+  #console.debug "[fortran-beautify] options: " + JSON.stringify(options)

   args = [
     '--batch'
@@ -26,10 +26,8 @@
   else
     cmd = "emacs " + args.join(' ')

-  console.debug "[fortran-beautify] cmd: " + cmd
-
+  #console.debug "[fortran-beautify] cmd: " + cmd
   fs.symlinkSync(inputPath, outputPath)
-
   return cmd

 cliBeautify = require("./cli-beautify")


refact

refact

--- node_modules/hubot-twitter-egosearch/scripts/egosearch.coffee
+++ node_modules/hubot-twitter-egosearch/scripts/egosearch.coffee
@@ -64,9 +64,7 @@
     queries[id] = query

 loopSearching = (robot) ->
-  setInterval ->
-    searchQueries(robot)
-  , eval(process.env.HUBOT_TWITTER_EGOSEARCH_SEARCH_INTERVAL) or 1000 * 60
+  searchQueries(robot)

 searchQueries = (robot) ->
   robot.logger.debug 'searchLoopQuery'


reindenting tests.

reindenting tests.

--- test/get-last-tweetTest.coffee
+++ test/get-last-tweetTest.coffee
@@ -5,15 +5,15 @@
 app = require('./../app/server').app

 describe 'GET /joshfinnie/status/last?withReplies=true', ->
-    it 'returns get-last-tweet page', (done) ->
-        request(app)
-            .get('/joshfinnie/status/last?withReplies=true')
+  it 'returns get-last-tweet page', (done) ->
+    request(app)
+      .get('/joshfinnie/status/last?withReplies=true')
             .expect("Content-Type", "text/html; charset=utf-8")
             .expect 200, done

 describe 'GET /joshfinnie/status/last?withReplies=false', ->
-    it 'returns get-last-tweet page', (done) ->
-        request(app)
-            .get('/joshfinnie/status/last?withReplies=false')
+  it 'returns get-last-tweet page', (done) ->
+    request(app)
+      .get('/joshfinnie/status/last?withReplies=false')
             .expect("Content-Type", "text/html; charset=utf-8")
             .expect 200, done


fix excess argument pass into UpdateMerger.p.processFile

--- services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee
+++ services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee
@@ -44,7 +44,7 @@

 		processFile: (project_id, fsPath, path, source, user_id, callback)->
 			logger.log project_id:project_id, "processing file update from tpds"
-			EditorController.upsertFileWithPath project_id, path, fsPath, source, user_id, callback, (err) ->
+			EditorController.upsertFileWithPath project_id, path, fsPath, source, user_id, (err) ->
 				logger.log project_id:project_id, "completed processing file update from tpds"
 				callback(err)


test: No more fibers.

test: No more fibers.

--- test/index.coffee
+++ test/index.coffee
@@ -832,127 +832,6 @@
     json.should.eql _id: 'arrays', embed: [{attr: 1}, {attr: 2}]

-# This is the best way that I can think of to check whether fibers tests
-# should work, since describe isn't run itself in a fiber
-try
-  require.resolve 'mocha-fibers'
-  require.resolve 'fibrousity'
-  describeFibers = describe
-catch err
-  describeFibers = describe.skip
-
-describeFibers "Fibers", ->
-  # This test suite really should cover everything...
-  before (done) ->
-    MyDoc.save _id: 'fibers', done
-
-  describe "Document", ->
-    describe "#findOne()", ->
-      it "should work synchronously", ->
-        doc = MyDoc.findOne _id: 'fibers'
-        doc.should.eql _id: 'fibers'
-
-    describe "#find()", ->
-      it "should work synchronously", ->
-        docs = MyDoc.find(_id: 'fibers').toArray()
-        docs.should.eql [_id: 'fibers']
-
-      it "should respect projections", ->
-        i = 'projections_fibers'
-        doc = new MyDoc()
-        doc._id = i
-        doc.attr = i
-        doc.save()
-        docs = MyDoc.find {_id: i}, {_id: 0, a: 1}
-            .toArray()
-        docs.should.eql [{a: i}]
-
-      it "should auto map projections", ->
-        i = 'projections_auto_fiber'
-        doc = new MyDoc()
-        doc.my_id = i
-        doc.attr = i
-        doc.save()
-        docs = MyDoc.find {my_id: i}, {my_id: 0, attr: 1}
-            .toArray()
-        docs.should.eql [a: i]
-
-      it "should auto map projections with defaults", ->
-        DocWithDefaults = Db.document 'my_doc_with_defaults',
-          my_id: '_id'
-          attr: ['attr', 'no']
-
-        doc = new DocWithDefaults()
-        doc.my_id = 'hello'
-        doc.attr = 'yes'
-        doc.save()
-
-        docs = DocWithDefaults.find {my_id: 'hello'}, {attr: 1}
-            .toArray()
-        docs[0].attr.should.eql 'yes'
-
-    describe "#findOne()", ->
-      it "should work synchronously", ->
-        doc = MyDoc.findOne _id: 'fibers'
-        doc.should.eql _id: 'fibers'
-
-      it "should respect projections", ->
-        i = 'projections_fiber_findOne'
-        doc = new MyDoc()
-        doc._id = i
-        doc.attr = i
-        doc.save()
-        doc = MyDoc.findOne {_id: i}, {_id: 0}
-        doc.should.eql a: i
-
-      it "should auto map projections", ->
-        i = 'projections_auto_fiber_findOne'
-        doc = new MyDoc()
-        doc.my_id = i
-        doc.attr = i
-        doc.save()
-        doc = MyDoc.findOne {my_id: i}, {my_id: 0, attr: 1}
-        doc.should.eql a: i
-
-      it "should auto map projections with defaults", ->
-        DocWithDefaults = Db.document 'my_doc_with_defaults',
-          my_id: '_id'
-          attr: ['attr', 'no']
-
-        doc = new DocWithDefaults()
-        doc.my_id = 'hello'
-        doc.attr = 'yes'
-        doc.save()
-
-        doc = DocWithDefaults.findOne {my_id: 'hello'}, {attr: 1}
-        doc.attr.should.eql 'yes'
-
-    describe "#count()", ->
-      it "should work synchronously", ->
-        count = MyDoc.find().count()
-        count.should.be.gte 1
-
-      it "should work without find", ->
-        count = MyDoc.count()
-        count.should.be.gte 1
-
-    describe "#update()", ->
-      it "should create a new doc with upsert", ->
-        starting_count = MyDoc.count()
-        res = MyDoc.update {_id: 'Fibers.update'}, {$inc: {test: 10}},
-          {upsert: true}
-        res.n.should.equal 1
-        MyDoc.count().should.equal starting_count + 1
-
-      it "should auto map updates", ->
-        i = 'update_fiber_auto'
-        doc = new MyDoc()
-        doc.my_id = i
-        doc.update {$inc: attr: 2}, {upsert: true}
-        doc = MyDoc.findOne _id: i
-        doc.should.eql _id: i, a: 2
-
-
 describe "SparseReport", ->
   compound_collection = new Db 'compound_index'


clampable

clampable

--- test/clampable.coffee
+++ test/clampable.coffee
@@ -8,8 +8,9 @@
       min: 0
       max: 100

-  # Test for equality of two objects
-  equals one, 1
+  o.update()
+
+  o

 # Clear out the module
 module()


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -58,8 +58,6 @@
   direction = Point(1, 0)
   bounds = rect.bounds()

-  debugger
-
   p1 = Collision.rayRectangle start, direction, bounds

   equals p1.y, 0


Adding assertions for the webserver

Adding assertions for the webserver

--- test/webserver.coffee
+++ test/webserver.coffee
@@ -161,16 +161,19 @@
       url: url_href
       template: template
     , (err, res, data) =>
+      assert.equal err, null
       rest.post "#{url}/clients", json: true,
       body:
         name: "joe"
         url: url_href
         template: template
-      , (err, res, data) =>
+      , (err, res, data) =>
+        assert.equal err, null
         rest.get "#{url}/clients", json: true,
         qs:
           info: true
         , (err, res, data) =>
+          assert.equal err, null
           client_list = Object.keys(data.clients)
           client_list.length.should.equal 2
           client_list.should.include 'bob'


tests for visual mode toggling

tests for visual mode toggling

--- test/ace/visual_mode.coffee
+++ test/ace/visual_mode.coffee
@@ -92,3 +92,19 @@
   @press 'lllVjgJ'
   eq @adaptor.lineText(), '_.sortBy = function(obj, iterator, context) {  return _.pluck(_.map(obj, function(value, index, list) {'
   eq @adaptor.lastRow(), lastRow - 1
+
+test 'toggle visual mode', ->
+  @press 'vW'
+  eq @jim.modeName, 'visual:characterwise'
+
+  @press 'Vj'
+  eq @jim.modeName, 'visual:linewise'
+
+  @press 'vy'
+  eq @jim.registers['"'], '_.sortBy = function(obj, iterator, context) {\n  return _'
+
+  @press 'vv'
+  eq @jim.modeName, 'normal'
+
+  @press 'VV'
+  eq @jim.modeName, 'normal'


test that we can't accidentally forget the eldest_key param

--- test/files/31_sigchain.iced
+++ test/files/31_sigchain.iced
@@ -17,6 +17,22 @@
 example_revokes_chain = require '../data/example_revokes_chain.json'

 #====================================================
+
+exports.test_eldest_key_required = (T, cb) ->
+  # Make sure that if we forget to pass eldest key to SigChain.replay, that's
+  # an error. Otherwise we could confisingly empty results.
+  esc = make_esc cb, "test_eldest_key_required"
+  {chain, keys, username, uid} = ralph_chain
+  await node_sigchain.ParsedKeys.parse {bundles_list: keys}, esc defer parsed_keys
+  await node_sigchain.SigChain.replay {
+    sig_blobs: chain
+    parsed_keys
+    uid
+    username
+    # OOPS! Forgot the eldest_kid!
+  }, defer err, sigchain
+  T.assert err, "Forgetting to pass the eldest_kid should fail the replay!"
+  cb()

 do_sigchain_test = ({T, input, err_type, len, sibkeys, eldest_index}, cb) ->
   esc = make_esc cb, "do_sigchain_test"


fix graphic-test with correct spec

fix graphic-test with correct spec

--- test/gg/graphic-test.coffee
+++ test/gg/graphic-test.coffee
@@ -101,9 +101,16 @@
   layers: [
     {
       geom: "boxplot"
+      aes:
+        x: 'd'
+        y: 'r'
+      stat: "boxplot"
     },
     {
       geom: "point"
+      aes:
+        x: 'd'
+        y: 'r'
     }
   ]


thigthen expectation for rendered html

thigthen expectation for rendered html


test: serialize collection

test: serialize collection

--- test/spec/model_spec.coffee
+++ test/spec/model_spec.coffee
@@ -47,8 +47,16 @@
       model3 = new Model
         id: 3
         qux: 'qux'
+      model4 = new Model
+        id: 4
+        foo: 'foo'
+      model5 = new Model
+        id: 5
+        baz: 'baz'
+      collection = new Backbone.Collection [model4, model5]
       model1.set model2: model2
       model2.set model3: model3
+      model2.set collection: collection
       model2.set model2: model2 # Circular fun!
       model3.set model2: model2 # Even more fun!

@@ -65,6 +73,10 @@
             qux: 'qux'
             # Circular references are nullified
             model2: null
+          collection: [
+            {foo: 'foo'},
+            {baz: 'baz'}
+          ]

       #console.debug 'passedTemplateData', d

@@ -74,6 +86,10 @@
       expect(typeof d.model2).toBe 'object'
       expect(d.model2.bar).toBe e.model2.bar
       expect(d.model2.model2).toBe e.model2.model2
+
+      expect(typeof d.model2.collection).toBe 'object'
+      expect(d.model2.collection[0].foo).toBe e.model2.collection[0].foo
+      expect(d.model2.collection[1].baz).toBe e.model2.collection[1].baz

       expect(typeof d.model2.model3).toBe 'object'
       expect(d.model2.model3.qux).toBe e.model2.model3.qux


Aded exception to refund policy

Aded exception to refund policy


Fix uncompiled pdf view

Fix uncompiled pdf view

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -41,7 +41,6 @@
 			$scope.pdf.error      = false
 			$scope.pdf.timedout   = false
 			$scope.pdf.failure    = false
-			$scope.pdf.uncompiled = false
 			$scope.pdf.projectTooLarge = false
 			$scope.pdf.url        = null
 			$scope.pdf.clsiMaintenance = false
@@ -52,8 +51,7 @@
 				$scope.pdf.view = 'errors'
 				$scope.pdf.timedout = true
 			else if response.status == "autocompile-backoff"
-				$scope.pdf.view = 'errors'
-				$scope.pdf.uncompiled = true
+				$scope.pdf.view = 'uncompiled'
 			else if response.status == "project-too-large"
 				$scope.pdf.view = 'errors'
 				$scope.pdf.projectTooLarge = true


fixing delete button

fixing delete button

--- sli/admin-tools/admin-rails/app/assets/javascripts/realms.js.coffee
+++ sli/admin-tools/admin-rails/app/assets/javascripts/realms.js.coffee
@@ -61,7 +61,7 @@
 		table.append(tr);
 		tr.show();
 		if (editable) {
-        		$(tr).select("td > .deleteButton").each(function() {
+        		$(tr).find(".deleteButton").each(function() {
                 		$(this).click(function() {
                        		//$(this).parent().parent().remove()
 					$(this).trigger("deleteRow");


be more conservative here...

be more conservative here...

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -68,7 +68,7 @@
         assert cache = @constructor.compiledTranslations
         assert session = @session, "no session detected"
         selector = language or @session.language or null
-        selector = selector or "English" # coded default
+        selector = selector or "en" # hardcoded default
         assert messages = cache[selector] or new Object
         amount = _.keys(messages).length # of messages
         banner = "Loaded %s messages for %s in %s".grey


Fix minor typo

Fix minor typo

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -113,7 +113,7 @@

     # This server side method is called on the context prior to the
     # context being compiled and flushed down to the client site. The
-    # method is wired in an synchronous way for greater functionality.
+    # method is wired in an asynchronous way for greater functionality.
     # This is the place where you would be importing the dependencies.
     prelude: (context, request, next) ->
         list = bower.commands.list


リファクタリング：ユーザ関連のAPIを他にも呼び出すため、エンドポイントのURLを組み立てるメソッドと、xhrの処理とをわけて実装

--- coffee/model/qiitaUser.coffee
+++ coffee/model/qiitaUser.coffee
@@ -1,8 +1,28 @@
 class qiitaUser
   constructor:(url_name)->
     @url_name = url_name
+  getfollowingUserList:(callback) ->
+    param =
+      method:'GET'
+      url:"https://qiita.com/api/v1/users/#{@url_name}/following_users"
+
+    return @_request(param,callback)
+
+  getUserInfo:(callback) ->
+    param =
+      method:'GET'
+      url:"https://qiita.com/api/v1/users/#{@url_name}"
+
+    return @_request(param,callback)
+
+  _cached:(userInfo)->
+    # result = Titanium.App.Properties.getString "qiitaUserList"
+    # localUserInfo = JSON.parse(result)
+
+    Titanium.App.Properties.setString("qiitaUserList",userInfo)
+    return true

-  getUserInfo:(callback) ->
+  _request:(param,callback) ->
     self = @
     xhr = Ti.Network.createHTTPClient()
     xhr.open('GET',"https://qiita.com/api/v1/users/#{@url_name}")
@@ -22,13 +42,9 @@
     xhr.send()
     return

-  _cached:(userInfo)->
-    # result = Titanium.App.Properties.getString "qiitaUserList"
-    # localUserInfo = JSON.parse(result)
-
-    Titanium.App.Properties.setString("qiitaUserList",userInfo)

-    return true
+
+
 module.exports = qiitaUser


course: add error reporting for assigning and collecting.

--- salvus/page/course.coffee
+++ salvus/page/course.coffee
@@ -955,7 +955,10 @@
             student_name = @student_name(student, true)
             a = assignment.last_assignment?[student.student_id]
             if a?.time?
-                elt = $("#{student_name} -- assigned ")
+                if a.error?
+                    elt = $("#{student_name} -- ERROR assigning at  (#{a.error})")
+                else
+                    elt = $("#{student_name} -- assigned ")
                 elt.find("span").attr('title', (new Date(a.time)).toISOString()).timeago()
             else
                 if student.project_id?
@@ -988,7 +991,10 @@
             student_name = @student_name(student, true)
             a = assignment.last_collect?[student.student_id]
             if a?.time?
-                elt = $("#{student_name} -- collected ")
+                if a.error?
+                    elt = $("#{student_name} -- ERROR collecting at  (#{a.error})")
+                else
+                    elt = $("#{student_name} -- collected ")
                 elt.find("span").attr('title', (new Date(a.time)).toISOString()).timeago()
                 collected = true
             else


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -47,7 +47,7 @@
   Collision.collide player, [enemy, enemy2], (p, e) ->
     ok(true)

-test "Collision.rayRectangle with ", ->
+test "Collision.rayRectangle with both types of", ->
   rect = GameObject
     x: 25
     y: 0


Update test to respond to '/me'

Update test to respond to '/me'

--- test/demo-mock.coffee
+++ test/demo-mock.coffee
@@ -4,7 +4,7 @@
 ], ($) ->

   $.mockjax
-    url: '/api/me'
+    url: '/me'
     proxy: 'data/me.json'

   $.mockjax


fixed

fixed

--- test/init.test.coffee
+++ test/init.test.coffee
@@ -1,14 +1,13 @@
 ## -- Dependencies -----------------------------------------------------------------------

+fs        = require 'fs'
 pkg       = require '../package.json'
-fs        = require 'fs'
 scripts   = require 'sailor-scripts'

 ## -- Setup ------------------------------------------------------------------------------

 opts =
   log: level: "silent"
-  plugins: [pkg.name]

 SCOPE =
   MODULE       : process.cwd()
@@ -21,7 +20,7 @@
     scripts.newBase ->
       scripts.linkDependency dependency for dependency in SCOPE.DEPENDENCIES
       scripts.link SCOPE.MODULE, SCOPE.LINK
-      scripts.writePluginFile pkg.name
+      scripts.writeModuleFile pkg.name
       scripts.lift SCOPE.TEST, opts, done
   else
     scripts.clean "#{SCOPE.TEST}/.tmp/"


Added current_commit function to repo

Added current_commit function to repo

--- src/repo.coffee
+++ src/repo.coffee
@@ -84,6 +84,27 @@
     skip  ?= 0

     Commit.find_all this, start, {"max-count": limit, skip}, callback
+
+
+  # Internal: Returns current commit id
+  #
+  # callback - Receives `(err, id)`.
+  #
+  current_commit_id: (callback) ->
+    @git "rev-parse HEAD", {}, []
+    , (err, stdout, stderr) =>
+      return callback err if err
+      return callback null, _.first stdout.split "\n"
+
+
+  # Public:
+  #
+  # callback - Receives `(err, commit)`
+  #
+  current_commit: (callback) ->
+    @current_commit_id (err, commit_id) =>
+      return callback err if err
+      Commit.find this, commit_id, callback

   # Public: The tree object for the treeish or master.


Fix rule matching for patterns containing newlines

--- src/rule.coffee
+++ src/rule.coffee
@@ -75,11 +75,11 @@

     if results.length > 1
       _.min results, (result) =>
-        @normalizeCaptureIndices(line, result.captureIndices)
+        @normalizeCaptureIndices(lineWithNewline, result.captureIndices)
         result.captureIndices[0].start
     else if results.length is 1
       [result] = results
-      @normalizeCaptureIndices(line, result.captureIndices)
+      @normalizeCaptureIndices(lineWithNewline, result.captureIndices)
       result

   getNextTokens: (ruleStack, line, position, firstLine) ->


Allow beforeClose to prevent close by returning false

--- coffee/vex.coffee
+++ coffee/vex.coffee
@@ -154,14 +154,14 @@
                 options.afterClose $vexContent, options if options.afterClose

             if animationEndSupport
-                beforeClose()
-                $vex
-                    .unbind(vex.animationEndEvent).bind(vex.animationEndEvent, -> close())
-                    .addClass(vex.baseClassNames.closing)
+                unless beforeClose() is false
+                    $vex
+                        .unbind(vex.animationEndEvent).bind(vex.animationEndEvent, -> close())
+                        .addClass(vex.baseClassNames.closing)

             else
-                beforeClose()
-                close()
+                unless beforeClose() is false
+                    close()

             return true


Enable caremail for testing

Enable caremail for testing

--- modacity.cson
+++ modacity.cson
@@ -23,7 +23,7 @@
     "hi"
     "title"
     "wolfram"
-    #"caremail"
+    "caremail"
     "mp3"
   ]


code cleaning

code cleaning

--- models.coffee
+++ models.coffee
@@ -37,11 +37,7 @@
             else if res.statusCode != 200
                 callback new Error(users)
             else
-                adapter.updateKeys req.body.password, (err) =>
-                    if err
-                        callback new Error("Keys are not updated")
-                    else
-                        callback null
+                callback null

     deleteAll: (callback) ->
         path = "request/#{@type.toLowerCase()}/all/destroy/"


Combined plot.

Combined plot.

--- ruler.coffee
+++ ruler.coffee
@@ -1,29 +1,21 @@
 # Measurements from N trials
-fig1 = figure
+
+fig3 = figure
     xlabel: "probabilty"
     ylabel: "inverse erf"
+    yaxis: {min:-4, max:4}
     height: 200
+    colors: ["green", "red"]
     series:
-        color: "green"
         shadowSize: 0
         lines: {lineWidth: 1, show:true}
         points: {show: false}

-fig2 = figure
-    xlabel: "probabilty"
-    ylabel: "inverse erf"
-    height: 200
-    series:
-        color: "green"
-        shadowSize: 0
-        lines: {lineWidth: 1, show:true}
-        points: {show: false}
+p = linspace -0.9999, 0.9999, 1000 #;

-p = linspace -0.9999, 0.9999, 1000
-
-c0 = 1.758
-c1 = -2.257
-c2 = 0.1661
+c0 = 1.758 #;
+c1 = -2.257 #;
+c2 = 0.1661 #;

 sign = (u) -> (u<=0)-(u>0)

@@ -32,11 +24,9 @@
     t = sqrt(-log(0.5*(1+s*u)))
     (c0 + c1*t + c2*t*t)*s

-plot p, erfinv(p), fig: fig1
+erfinv2 = (x) ->
+    a = 8*(pi-3)/3/pi/(4-pi)
+    b = log(1-x*x)
+    -sign(x)*sqrt(sqrt((2/pi/a+b/2).pow(2)-b/a)-(2/pi/a+b/2))

-erfinv2 = (x) ->
-     a = 8*(pi-3)/3/pi/(4-pi)
-     b = log(1-x*x)
-     -sign(x)*sqrt(sqrt((2/pi/a+b/2).pow(2)-b/a)-(2/pi/a+b/2))
-
-plot p, erfinv2(p), fig: fig2
+plot p, [erfinv(p), erfinv2(p)], fig: fig3


Halted VMs are now available from their pool.

--- src/spec.coffee
+++ src/spec.coffee
@@ -135,13 +135,14 @@
               update: (VM) ->
                 # Unless this VM belongs to this pool, there is no need
                 # to continue.
-                return unless VM.$connectionId is @value.UUID
+                return unless VM.$pool is @value.UUID
+
+                remove @field, VM.UUID

                 # If this VM is running or paused, it is necessarily on
                 # a host.
-                if state is 'Paused' or state is 'Runnning'
-                  remove @field, VM.UUID
-                  return
+                state = VM.power_state
+                return if state is 'Paused' or state is 'Running'

                 # TODO: Check whether this VM belong to a local SR.
                 local = false
@@ -298,6 +299,9 @@
           # VM is not on a host.
           $container: get('resident_on')

+          # TODO: removes it when hooks have access to the generator.
+          $pool: get '$pool'
+
           $VBDs: @dynamic [], {
             VBD: {
               update: (VBD, UUID) ->


if csv has than 100 invites or there are more than 100 members, directly invite

--- servers/lib/server/handlers/invitetoteambycsvAnalyze.coffee
+++ servers/lib/server/handlers/invitetoteambycsvAnalyze.coffee
@@ -42,6 +42,10 @@
     parser content.toString('utf8'), csvParseOpts, (err, data) ->
       return callback err, null if err

+      invitationCount = data.length
+
+      return callback null, 'Over 100 Invitation'  if invitationCount > 100
+
       { connection: { delegate: account } } = client
       { _id } = account
       myEmail = null
@@ -56,6 +60,7 @@

           group.fetchMembersWithEmail client, {}, (err, users) ->
             return callback err, null  if err
+            return callback null, 'Over 100 Invitation'  if users.length > 100

             adminEmails = []
             alreadyMemberEmails = []


Print error when unable to scroll using scrollto

--- js-library/app/js/util/scrollto.coffee
+++ js-library/app/js/util/scrollto.coffee
@@ -1,4 +1,8 @@
 Factlink.scrollTo = (fact_id) ->
+  if $("span[data-factid=#{fact_id}]").length <= 0
+    console?.error? "Factlink: Could not scroll to non-existing fact_id: #{fact_id}"
+    return
+
   $('body')
     ._scrollable()
     .scrollTo "span[data-factid=#{fact_id}]", 800,


Fixed incorrect merge again

Fixed incorrect merge again

--- core/app/backbone/models/discussion.coffee
+++ core/app/backbone/models/discussion.coffee
@@ -15,7 +15,7 @@
     new OpinionatersEvidence({type: type}, fact: @_fact)

   getInteractorsEvidence: ->
-    switch @type()
+    @_interactors ?= switch @type()
       when 'supporting' then @_interactorsPage('believes')
       when 'weakening' then @_interactorsPage('disbelieves')
       when 'doubting' then @_interactorsPage('doubts')


Use sinon's calledOnce

Use sinon's calledOnce

--- spec/dummy/spec/javascripts/validatable_form_spec.js.coffee
+++ spec/dummy/spec/javascripts/validatable_form_spec.js.coffee
@@ -29,7 +29,7 @@

     it 'only binds the handlers to the form once', ->
       @submitButton.click()
-      expect(@validateInputsSpy.callCount).toBe(1)
+      expect(@validateInputsSpy.calledOnce).toBe(true)

     describe 'when Comply.enginePath has been set', ->
       beforeEach ->


collaboration: controller add workspace helper

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -15,6 +15,7 @@
 doXhrRequest                  = require 'app/util/doXhrRequest'
 realtimeHelpers               = require './collaboration/helpers/realtime'
 socialHelpers                 = require './collaboration/helpers/social'
+workspaceHelpers              = require './collaboration/helpers/workspace'

 {warn} = kd


Remove fat arrow

Remove fat arrow

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -137,7 +137,7 @@

     @removeWorkspaceSnapshot target

-    socialHelpers.kickParticipants @socialChannel, [account], (err, result) =>
+    socialHelpers.kickParticipants @socialChannel, [account], (err, result) ->
       return callbacks.error err  if err
       callbacks.success()


Collaboration: Don't use unneeded fat arrows.

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -857,16 +857,16 @@

       fileName = @getRealtimeFileName()

-      realtimeHelpers.deleteCollaborationFile @rtm, fileName, (err) =>
+      realtimeHelpers.deleteCollaborationFile @rtm, fileName, (err) ->
         throwError err  if err

-      @setMachineSharingStatus off, (err) =>
+      @setMachineSharingStatus off, (err) ->
         throwError err  if err

-      socialHelpers.destroyChannel @socialChannel, (err) =>
+      socialHelpers.destroyChannel @socialChannel, (err) ->
         throwError err  if err

-      envHelpers.detachSocialChannel @workspaceData, (err) =>
+      envHelpers.detachSocialChannel @workspaceData, (err) ->
         throwError err  if err

       callback()


Add health-check command

Add health-check command

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -514,6 +514,36 @@
       runCountlyDocker
     }

+    function health_check () {
+      declare interval=${1:-10}
+      declare timeout=${2:-60}
+      declare duration=0
+
+      declare response_code=$(curl --silent --output /dev/null \
+        --write-out "%{http_code}\\n" \
+        $KONFIG_PUBLICHOSTNAME/-/healthCheck)
+
+      echo -n 'health-check: '
+
+      until [[ $response_code -eq 200 ]]; do
+        if [ $duration -eq $timeout ]; then
+          echo ' timed out!'
+          exit 255
+        fi
+
+        echo -n '.'
+
+        sleep $interval
+        duration=$((duration + interval))
+
+        response_code=$(curl --silent --output /dev/null \
+          --write-out "%{http_code}\\n" \
+          $KONFIG_PUBLICHOSTNAME/-/healthCheck)
+      done
+
+      echo ' succeeded!'
+    }
+
     if [ "$#" == "0" ]; then
       checkrunfile
       run $1
@@ -625,6 +655,10 @@

     elif [ "$1" == "migrations" ]; then
       migrations $2 $3
+
+    elif [ "$1" == "health-check" ]; then
+      shift
+      health_check "$@"

     else
       echo "Unknown command: $1"


config: update mongo to 3.0.2

config: update mongo to 3.0.2

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -399,7 +399,7 @@
     }

     function runMongoDocker () {
-        docker run -d -p $KONFIG_SERVICEHOST:27017:27017 --name=mongo mongo:2.6 --nojournal --noprealloc --smallfiles
+        docker run -d -p $KONFIG_SERVICEHOST:27017:27017 --name=mongo mongo:3.0.2 --nojournal --noprealloc --smallfiles
         check_connectivity mongo
     }


Made the currently printing job highlighted

--- src/tegh.coffee
+++ src/tegh.coffee
@@ -143,10 +143,15 @@
     for job, i in jobs
       name = job.file_name
       id = job.id.toString()
-      prefix = "  #{i}) #{name} "
-      suffix = "job ##{job.id.pad(5)}  "
+      prefix = "  #{i}) #{name} ";
+      if job.printing
+        suffix = "PRINTING    "
+      else
+        suffix = "job ##{job.id.pad(5)}  "
       padding = @cli.width - suffix.length - prefix.length - 1
-      console.log "#{prefix.padRight(".", padding)} #{suffix}"
+      line = "#{prefix.padRight(".", padding)} #{suffix}"
+      line = line.green if job.printing
+      console.log line
     console.log ""
     @cli.rl.prompt()
     @cli.rl.resume()


Prevent accessing the DOM when its not ready

--- lib/behaviours/InfiniteScrolling.coffee
+++ lib/behaviours/InfiniteScrolling.coffee
@@ -28,17 +28,20 @@
   # Ev, handler executed when the collection starts syncing with the server
   syncStart: ->
     @loading = true
-    @ui.scrollableContainer.addClass 'loading'
+    if @ready
+      @ui.scrollableContainer.addClass 'loading'

   # Ev, handler executed when the collection finishes syncing with the server
   syncStop: ->
     @loading = false
-    @ui.scrollableContainer.removeClass 'loading'
+    if @ready
+      @ui.scrollableContainer.removeClass 'loading'

   # Init it when the view DOM is ready
   onRender: ->
+    @ready = true

     # the ui events hash uses $.delegate, but the scroll event does not bubble
     # so


Fixed some wrong variable names

Fixed some wrong variable names

--- pimatic-smartmeter.coffee
+++ pimatic-smartmeter.coffee
@@ -52,12 +52,12 @@
       p1datastream.on 'data', (data) =>
         @emit "actualusage", Number data.currentUsage
         @emit "activetariff", Number data.currentTariff
-        @emit "tariff1totalusage", Number data.tariffOneTotalUsage
-        @emit "tariff2totalusage", Number data.tariffTwoTotalUsage
+        @emit "rate1totalusage", Number data.tariffOneTotalUsage
+        @emit "rate2totalusage", Number data.tariffTwoTotalUsage

     getActualusage: -> Promise.resolve @actualusage
-    getActiveRate: -> Promis.resolve @activetariff
+    getActiveTariff: -> Promis.resolve @activetariff
     getRate1TotalUsage: -> Promise.resolve @rate1totalusage
     getRate2TotalUsage: -> Promise.resolve @rate2totalusage


remove deprecated createJob call

remove deprecated createJob call

--- sample.coffee
+++ sample.coffee
@@ -458,13 +458,12 @@
                filename: "tn_#{file.filename}.png"
                contentType: 'image/png'
                metadata: file.metadata
-            job = myJobs.createJob('makeThumb',
+            job = new Job myJobs, 'makeThumb',
                owner: file.metadata._auth.owner
                inputFileURL: Meteor.absoluteUrl("#{myData.baseURL[1..]}/#{file._id}")
                outputFileURL: Meteor.absoluteUrl("#{myData.baseURL[1..]}/put/#{outputFileId}")
                inputFileId: file._id
                outputFileId: outputFileId
-            )
             if jobId = job.delay(5000).retry({ wait: 20000, retries: 5 }).save()
                myData.update({ _id: file._id }, { $set: { 'metadata._Job': jobId }})
                myData.update({ _id: outputFileId }, { $set: { 'metadata._Job': jobId, 'metadata.thumbOf': file._id }})


Fix coffee script lint

Fix coffee script lint

--- js/umobi.page.coffee
+++ js/umobi.page.coffee
@@ -90,7 +90,7 @@
               $contentContainer.css('paddingBottom', f.height() + 'px') if f.get(0)
             upage.on 'pagereveal', AdjustContentPadding
           else
-            # use absolute position and fixed header/footer for desktop
+            # use absolute position and fixed header/footer for desktop
             AdjustContentHeight = (e) ->
               console.log "pagereveal" , h.height(), f.height()
               contentHeight = $(window).height()


using coffeescript '=>' to lock render_view context to At currect typeos

--- coffee/jquery.atwho.coffee
+++ coffee/jquery.atwho.coffee
@@ -25,512 +25,514 @@

 (($) ->

-    # At.js 使用这个类克隆输入框, 插入标记后获得该标记的位置.
-    #
-    # @example
-    #   mirror = new Mirror($("textarea#inputor"))
-    #   html = "
We will get the rect of @icho
"
-    #   mirror.create(html).get_flag_rect()
-    class Mirror
-      css_attr: [
-        "overflowY", "height", "width", "paddingTop", "paddingLeft",
-        "paddingRight", "paddingBottom", "marginTop", "marginLeft",
-        "marginRight", "marginBottom",'fontFamily', 'borderStyle',
-        'borderWidth','wordWrap', 'fontSize', 'lineHeight', 'overflowX'
-      ]
-
-      # @param $inputor [Object] 输入框的 jQuery 对象
-      constructor: (@$inputor) ->
-
-      # 克隆输入框的样式
-      #
-      # @return [Object] 返回克隆得到样式
-      copy_inputor_css: ->
-        css =
-          position: 'absolute'
-          left: -9999
-          top:0
-          zIndex: -20000
-          'white-space': 'pre-wrap'
-        $.each @css_attr, (i,p) =>
-          css[p] = @$inputor.css p
-        css
-
-      # 在页面中创建克隆后的镜像.
-      #
-      # @param html [String] 将输入框内容转换成 html 后的内容.
-      #   主要是为了给 `flag` (@, etc.) 打上标记
-      #
-      # @return [Object] 返回当前对象
-      create: (html) ->
-        @$mirror = $('

')
-        @$mirror.css this.copy_inputor_css()
-        @$mirror.html(html)
-        @$inputor.after(@$mirror)
-        this
-
-      # 获得标记的位置
-      #
-      # @return [Object] 标记的坐标
-      #   {left: 0, top: 0, bottom: 0}
-      get_flag_rect: ->
-        $flag = @$mirror.find "span#flag"
-        pos = $flag.position()
-        rect = {left: pos.left, top: pos.top, bottom: $flag.height() + pos.top}
-        @$mirror.remove()
-        rect
-
-
-    KEY_CODE =
-      DOWN: 40
-      UP: 38
-      ESC: 27
-      TAB: 9
-      ENTER: 13
-
-
-    DEFAULT_CALLBACKS =
-
-      # 匹配当前标记后面字符的匹配规则
-      #
-      # @param flag [String] 当前标记 ("@", etc)
-      # @param subtext [String] 输入框从开始到插入符号前的字符串
-      #
-      # @return [String] 匹配后得到的字符串
-      matcher: (flag, subtext) ->
-        regexp = new RegExp flag+'([A-Za-z0-9_\+\-]*)$|'+flag+'([^\\x00-\\xff]*)$','gi'
-        match = regexp.exec subtext
-        matched = null
-        if match
-          matched = if match[2] then match[2] else match[1]
-        matched
-
-      # 根据匹配的的字符串搜索数据
-      #
-      # @param query [String] 匹配得到的字符串
-      # @param data [Array] 数据列表
-      # @param search_key [String] 用于搜索的关键字
-      #
-      # @return [Array] 过滤后的数据
-      filter: (query, data, search_key) ->
-        $.map data, (item,i) =>
-          name = if $.isPlainObject(item) then item[search_key] else item
-          item if name.toLowerCase().indexOf(query) >= 0
-
-      # 当 `data` 设置为 url 的时候, 我们使用这个 filter 来发起 ajax 请求
-      #
-      # @param params [Hash] ajax 请求参数. {q: query, limit: 5}
-      # @param url [String] 开发者自己设置的 url 地址
-      # @param render_view [Function] 将数据渲染到下拉列表的回调
-      remote_filter: (params, url, render_view) ->
-        $.ajax url, params, (data) ->
-          names = $.parseJSON(data)
-          render_view(names)
-
-      # 重构数据结构, 以便于渲染模板
-      #
-      # @param data [Array] 开发者自己在配置中设置的数据列表
-      #
-      # @return [Array] 重构后的数据列表
-      data_refactor: (data) ->
-        $.map data, (item, k) ->
-          if not $.isPlainObject item
-            item = {name:item}
-          return item
-
-      # 对重构后的数据进行排序
-      #
-      # @param query [String] 匹配后的关键字
-      # @param items [Array] 重构后的数据列表
-      # @param search_key [String] 用于搜索的关键字
-      #
-      # @return [Array] 排序后的数据列表
-      sorter: (query, items, search_key) ->
-        results = []
-
-        for item in items
-          text = item[search_key]
-          item.order = text.toLowerCase().indexOf query
-          results.push(item)
-
-        results.sort (a,b) ->
-          a.order - b.order
-        return results
-
-      # 解析并渲染下拉列表中单个项的模板
-      #
-      # @param tpl [String] 模板字符串
-      # @param map [Hash] 数据的键值对.
-      tpl_eval: (tpl, map) ->
-        try
-          el = tpl.replace /\$\{([^\}]*)\}/g, (tag,key,pos) ->
-            map[key]
-        catch error
-          ""
-
-      # 高亮关键字
-      #
-      # @param li [String] HTML String. 经过渲染后的模板
-      # @param query [String] 匹配得到的关键字
-      #
-      # @return [String] 高亮处理后的 HTML 字符串
-      highlighter: (li, query) ->
-        return li if not query
-        li.replace new RegExp(">\\s*(\\w*)(" + query.replace("+","\\+") + ")(\\w*)\\s*<", 'ig'), (str,$1, $2, $3) ->
-            '> '+$1+'' + $2 + ''+$3+' <'
-
-      # 选择某列表项的动作
-      #
-      # @param $li [jQuery Object] 选中的列表项目
-      selector: ($li) ->
-        @controller.replace_str($li.data("value") || "") if $li.length > 0
-
-
-    # At.js 对数据操作(搜索, 匹配, 渲染) 的主控中心
-    class Controller
-
-      # @param inputor [HTML DOM Object] 输入框
-      constructor: (inputor) ->
-        @settings     = {}
-        @common_settings       = {}
-        @pos          = 0
-        @flags        = null
-        @current_flag = null
-        @query        = null
-
-        @$inputor = $(inputor)
-        @mirror = new Mirror(@$inputor)
-        @common_settings = $.extend {}, $.fn.atWho.default
-        @view = new View(this, @$el)
-        this.listen()
-
-      # 绑定对输入框的各种监听事件
-      listen: ->
-        @$inputor
-          .on "keyup.atWho", (e) =>
-            stop = e.keyCode is KEY_CODE.DOWN or e.keyCode is KEY_CODE.UP
-            can_lookup = not (stop and @view.visible())
-            this.look_up() if can_lookup
-          .on 'keyup.atWho', (e) =>
-            this.on_keyup(e)
-          .on 'keydown.atWho', (e) =>
-            this.on_keydown(e)
-          .on 'scroll.atWho', (e) =>
-            @view.hide()
-          .on 'blur.atWho', (e) =>
-            @view.hide(1000)
-
-      # At.js 可以对每个输入框绑定不同的监听标记. 比如同时监听 "@", ":" 字符
-      # 并且通过不同的 `settings` 给予不同的表现行为, 比如插入不同的内容(即不同的渲染模板)
-      #
-      # 控制器初始化的时候会将默认配置当作一个所有标记共有的配置. 而每个标记只存放针对自己的特定配置.
-      # 搜索配置的时候, 将先寻找标记里的配置. 如果找不到则去公用的配置里找.
-      #
-      # 当输入框已经注册了某个字符后, 再对该字符进行注册将会更新其配置, 比如改变 `data`, 其它的配置不变.
-      #
-      # @param flag [String] 要监听的字符
-      # @param settings [Hash] 配置哈希值
-      reg: (flag, settings) ->
-        if not flag
-          console.log settings
-          @common_settings = $.extend {}, @common_settings, settings
-        else if not @settings[flag]
-          @settings[flag] = $.extend {}, settings
+  # At.js 使用这个类克隆输入框, 插入标记后获得该标记的位置.
+  #
+  # @example
+  #   mirror = new Mirror($("textarea#inputor"))
+  #   html = "
We will get the rect of @icho
"
+  #   mirror.create(html).get_flag_rect()
+  class Mirror
+    css_attr: [
+      "overflowY", "height", "width", "paddingTop", "paddingLeft",
+      "paddingRight", "paddingBottom", "marginTop", "marginLeft",
+      "marginRight", "marginBottom",'fontFamily', 'borderStyle',
+      'borderWidth','wordWrap', 'fontSize', 'lineHeight', 'overflowX'
+    ]
+
+    # @param $inputor [Object] 输入框的 jQuery 对象
+    constructor: (@$inputor) ->
+
+    # 克隆输入框的样式
+    #
+    # @return [Object] 返回克隆得到样式
+    copy_inputor_css: ->
+      css =
+        position: 'absolute'
+        left: -9999
+        top:0
+        zIndex: -20000
+        'white-space': 'pre-wrap'
+      $.each @css_attr, (i,p) =>
+        css[p] = @$inputor.css p
+      css
+
+    # 在页面中创建克隆后的镜像.
+    #
+    # @param html [String] 将输入框内容转换成 html 后的内容.
+    #   主要是为了给 `flag` (@, etc.) 打上标记
+    #
+    # @return [Object] 返回当前对象
+    create: (html) ->
+      @$mirror = $('

')
+      @$mirror.css this.copy_inputor_css()
+      @$mirror.html(html)
+      @$inputor.after(@$mirror)
+      this
+
+    # 获得标记的位置
+    #
+    # @return [Object] 标记的坐标
+    #   {left: 0, top: 0, bottom: 0}
+    get_flag_rect: ->
+      $flag = @$mirror.find "span#flag"
+      pos = $flag.position()
+      rect = {left: pos.left, top: pos.top, bottom: $flag.height() + pos.top}
+      @$mirror.remove()
+      rect
+
+
+  KEY_CODE =
+    DOWN: 40
+    UP: 38
+    ESC: 27
+    TAB: 9
+    ENTER: 13
+
+
+  # @mixin
+  DEFAULT_CALLBACKS =
+
+    # 匹配当前标记后面字符的匹配规则
+    #
+    # @param flag [String] 当前标记 ("@", etc)
+    # @param subtext [String] 输入框从开始到插入符号前的字符串
+    #
+    # @return [String] 匹配后得到的字符串
+    matcher: (flag, subtext) ->
+      regexp = new RegExp flag+'([A-Za-z0-9_\+\-]*)$|'+flag+'([^\\x00-\\xff]*)$','gi'
+      match = regexp.exec subtext
+      matched = null
+      if match
+        matched = if match[2] then match[2] else match[1]
+      matched
+
+    # 根据匹配的的字符串搜索数据
+    #
+    # @param query [String] 匹配得到的字符串
+    # @param data [Array] 数据列表
+    # @param search_key [String] 用于搜索的关键字
+    #
+    # @return [Array] 过滤后的数据
+    filter: (query, data, search_key) ->
+      $.map data, (item,i) =>
+        name = if $.isPlainObject(item) then item[search_key] else item
+        item if name.toLowerCase().indexOf(query) >= 0
+
+    # 当 `data` 设置为 url 的时候, 我们使用这个 filter 来发起 ajax 请求
+    #
+    # @param params [Hash] ajax 请求参数. {q: query, limit: 5}
+    # @param url [String] 开发者自己设置的 url 地址
+    # @param render_view [Function] 将数据渲染到下拉列表的回调
+    remote_filter: (params, url, render_view) ->
+      $.ajax url, params, (data) ->
+        names = $.parseJSON(data)
+        render_view(names)
+
+    # 重构数据结构, 以便于渲染模板
+    #
+    # @param data [Array] 开发者自己在配置中设置的数据列表
+    #
+    # @return [Array] 重构后的数据列表
+    data_refactor: (data) ->
+      $.map data, (item, k) ->
+        if not $.isPlainObject item
+          item = {name:item}
+        return item
+
+    # 对重构后的数据进行排序
+    #
+    # @param query [String] 匹配后的关键字
+    # @param items [Array] 重构后的数据列表
+    # @param search_key [String] 用于搜索的关键字
+    #
+    # @return [Array] 排序后的数据列表
+    sorter: (query, items, search_key) ->
+      results = []
+
+      for item in items
+        text = item[search_key]
+        item.order = text.toLowerCase().indexOf query
+        results.push(item)
+
+      results.sort (a,b) ->
+        a.order - b.order
+      return results
+
+    # 解析并渲染下拉列表中单个项的模板
+    #
+    # @param tpl [String] 模板字符串
+    # @param map [Hash] 数据的键值对.
+    tpl_eval: (tpl, map) ->
+      try
+        el = tpl.replace /\$\{([^\}]*)\}/g, (tag,key,pos) ->
+          map[key]
+      catch error
+        ""
+
+    # 高亮关键字
+    #
+    # @param li [String] HTML String. 经过渲染后的模板
+    # @param query [String] 匹配得到的关键字
+    #
+    # @return [String] 高亮处理后的 HTML 字符串
+    highlighter: (li, query) ->
+      return li if not query
+      li.replace new RegExp(">\\s*(\\w*)(" + query.replace("+","\\+") + ")(\\w*)\\s*<", 'ig'), (str,$1, $2, $3) ->
+          '> '+$1+'' + $2 + ''+$3+' <'
+
+    # 选择某列表项的动作
+    #
+    # @param $li [jQuery Object] 选中的列表项目
+    selector: ($li) ->
+      @controller.replace_str($li.data("value") || "") if $li.length > 0
+
+
+  # At.js 对数据操作(搜索, 匹配, 渲染) 的主控中心
+  class Controller
+
+    # @param inputor [HTML DOM Object] 输入框
+    constructor: (inputor) ->
+      @settings     = {}
+      @common_settings       = {}
+      @pos          = 0
+      @flags        = null
+      @current_flag = null
+      @query        = null
+
+      @$inputor = $(inputor)
+      @mirror = new Mirror(@$inputor)
+      @common_settings = $.extend {}, $.fn.atWho.default
+      @view = new View(this, @$el)
+      this.listen()
+
+    # 绑定对输入框的各种监听事件
+    listen: ->
+      @$inputor
+        .on "keyup.atWho", (e) =>
+          stop = e.keyCode is KEY_CODE.DOWN or e.keyCode is KEY_CODE.UP
+          can_lookup = not (stop and @view.visible())
+          this.look_up() if can_lookup
+        .on 'keyup.atWho', (e) =>
+          this.on_keyup(e)
+        .on 'keydown.atWho', (e) =>
+          this.on_keydown(e)
+        .on 'scroll.atWho', (e) =>
+          @view.hide()
+        .on 'blur.atWho', (e) =>
+          @view.hide(1000)
+
+    # At.js 可以对每个输入框绑定不同的监听标记. 比如同时监听 "@", ":" 字符
+    # 并且通过不同的 `settings` 给予不同的表现行为, 比如插入不同的内容(即不同的渲染模板)
+    #
+    # 控制器初始化的时候会将默认配置当作一个所有标记共有的配置. 而每个标记只存放针对自己的特定配置.
+    # 搜索配置的时候, 将先寻找标记里的配置. 如果找不到则去公用的配置里找.
+    #
+    # 当输入框已经注册了某个字符后, 再对该字符进行注册将会更新其配置, 比如改变 `data`, 其它的配置不变.
+    #
+    # @param flag [String] 要监听的字符
+    # @param settings [Hash] 配置哈希值
+    reg: (flag, settings) ->
+      if not flag
+        console.log settings
+        @common_settings = $.extend {}, @common_settings, settings
+      else if not @settings[flag]
+        @settings[flag] = $.extend {}, settings
+      else
+        @settings[flag] = $.extend {}, @settings[flag], settings
+
+      this
+
+    # At.js 允许开发者自定义控制器使用的一些功能函数
+    #
+    # @param func_name [String] 回调的函数名
+    # @return [Function] 该回调函数
+    callbacks: (func_name)->
+      # this.get_opt("callbacks", {})[func_name]
+      if not (func = this.get_opt("callbacks",{})[func_name])
+        func = @common_settings["callbacks"][func_name]
+      return func
+
+    # 由于可以绑定多字符, 但配置缺不相同, 而且有公用配置.所以会根据当前标记获得对应的配置
+    #
+    # @param key [String] 某配置项的键名
+    # @param default_value [?] 没有找到任何值后自定义的默认值
+    # @return [?] 配置项的值
+    get_opt: (key, default_value) ->
+      try
+        value = @settings[@current_flag][key] if @current_flag
+        value = @common_settings[key] if value is undefined
+        value = if value is undefined then default_value else value
+      catch e
+        value = if default_value is undefined then null else default_value
+
+    # 获得标记字符在输入框中的位置
+    #
+    # @return [Hash] 位置信息. {top: y, left: x, bottom: bottom}
+    rect: ->
+      $inputor = @$inputor
+      if document.selection # for IE full
+        Sel = document.selection.createRange()
+        x = Sel.boundingLeft + $inputor.scrollLeft()
+        y = Sel.boundingTop + $(window).scrollTop() + $inputor.scrollTop()
+        bottom = y + Sel.boundingHeight
+          # -2 : for some font style problem.
+        return {top:y-2, left:x-2, bottom:bottom-2}
+
+      format = (value) ->
+        value.replace(/</g, '&lt')
+        .replace(/>/g, '&gt')
+        .replace(/`/g,'&#96')
+        .replace(/"/g,'&quot')
+        .replace(/\r\n|\r|\n/g,"
")
+
+      ### 克隆完inputor后将原来的文本内容根据
+        @的位置进行分块,以获取@块在inputor(输入框)里的position
+      ###
+      start_range = $inputor.val().slice(0,@pos - 1)
+      html = ""+format(start_range)+""
+      html += "?"
+
+      ###
+        将inputor的 offset(相对于document)
+        和@在inputor里的position相加
+        就得到了@相对于document的offset.
+        当然,还要加上行高和滚动条的偏移量.
+      ###
+      offset = $inputor.offset()
+      at_rect = @mirror.create(html).get_flag_rect()
+
+      x = offset.left + at_rect.left - $inputor.scrollLeft()
+      y = offset.top - $inputor.scrollTop()
+      bottom = y + at_rect.bottom
+      y += at_rect.top
+
+      # bottom + 2: for some font style problem
+      return {top:y,left:x,bottom:bottom + 2}
+
+    # 捕获标记字符后的字符串
+    #
+    # @return [Hash] 该字符串的信息, 包括在输入框中的位置. {'text': "hello", 'head_pos': 0, 'end_pos': 0}
+    catch_query: ->
+      content = @$inputor.val()
+      ##获得inputor中插入符的position.
+      caret_pos = @$inputor.caretPos()
+      ### 向在插入符前的的文本进行正则匹配
+       * 考虑会有多个 @ 的存在, 匹配离插入符最近的一个###
+      subtext = content.slice(0,caret_pos)
+
+      query = null
+      $.each @settings, (flag, settings) =>
+        query = this.callbacks("matcher").call(this, flag, subtext)
+        if query?
+          @current_flag = flag
+          return false
+
+      if typeof query is "string" and query.length <= 20
+        start = caret_pos - query.length
+        end = start + query.length
+        @pos = start
+        query = {'text': query.toLowerCase(), 'head_pos': start, 'end_pos': end}
+      else
+        @view.hide()
+
+      @query = query
+
+    # 将选中的项的`data-value` 内容插入到输入框中
+    #
+    # @param str [String] 要插入的字符串, 一般为 `data-value` 的值.
+    replace_str: (str) ->
+      $inputor = @$inputor
+      source = $inputor.val()
+      flag_len = if this.get_opt("display_flag") then 0 else @current_flag.length
+      start_str = source.slice 0, (@query['head_pos'] || 0) - flag_len
+      text = "#{start_str}#{str} #{source.slice @query['end_pos'] || 0}"
+
+      $inputor.val text
+      $inputor.caretPos start_str.length + str.length + 1
+      $inputor.change()
+
+    on_keyup: (e) ->
+      return unless @view.visible()
+      switch e.keyCode
+        when KEY_CODE.ESC
+          e.preventDefault()
+          @view.hide()
         else
-          @settings[flag] = $.extend {}, @settings[flag], settings
-
-        this
-
-      # At.js 允许开发者自定义控制器使用的一些功能函数
-      #
-      # @param func_name [String] 回调的函数名
-      # @return [Function] 该回调函数
-      callbacks: (func_name)->
-        # this.get_opt("callbacks", {})[func_name]
-        if not (func = this.get_opt("callbacks",{})[func_name])
-          func = @common_settings["callbacks"][func_name]
-        return func
-
-      # 由于可以绑定多字符, 但配置缺不相同, 而且有公用配置.所以会根据当前标记获得对应的配置
-      #
-      # @param key [String] 某配置项的键名
-      # @param default_value [?] 没有找到任何值后自定义的默认值
-      # @return [?] 配置项的值
-      get_opt: (key, default_value) ->
-        try
-          value = @settings[@current_flag][key] if @current_flag
-          value = @common_settings[key] if value is undefined
-          value = if value is undefined then default_value else value
-        catch e
-          value = if default_value is undefined then null else default_value
-
-      # 获得标记字符在输入框中的位置
-      #
-      # @return [Hash] 位置信息. {top: y, left: x, bottom: bottom}
-      rect: ->
-        $inputor = @$inputor
-        if document.selection # for IE full
-          Sel = document.selection.createRange()
-          x = Sel.boundingLeft + $inputor.scrollLeft()
-          y = Sel.boundingTop + $(window).scrollTop() + $inputor.scrollTop()
-          bottom = y + Sel.boundingHeight
-            # -2 : for some font style problem.
-          return {top:y-2, left:x-2, bottom:bottom-2}
-
-        format = (value) ->
-          value.replace(/</g, '&lt')
-          .replace(/>/g, '&gt')
-          .replace(/`/g,'&#96')
-          .replace(/"/g,'&quot')
-          .replace(/\r\n|\r|\n/g,"
")
-
-        ### 克隆完inputor后将原来的文本内容根据
-          @的位置进行分块,以获取@块在inputor(输入框)里的position
-        ###
-        start_range = $inputor.val().slice(0,@pos - 1)
-        html = ""+format(start_range)+""
-        html += "?"
-
-        ###
-          将inputor的 offset(相对于document)
-          和@在inputor里的position相加
-          就得到了@相对于document的offset.
-          当然,还要加上行高和滚动条的偏移量.
-        ###
-        offset = $inputor.offset()
-        at_rect = @mirror.create(html).get_flag_rect()
-
-        x = offset.left + at_rect.left - $inputor.scrollLeft()
-        y = offset.top - $inputor.scrollTop()
-        bottom = y + at_rect.bottom
-        y += at_rect.top
-
-        # bottom + 2: for some font style problem
-        return {top:y,left:x,bottom:bottom + 2}
-
-      # 捕获标记字符后的字符串
-      #
-      # @return [Hash] 该字符串的信息, 包括在输入框中的位置. {'text': "hello", 'head_pos': 0, 'end_pos': 0}
-      catch_query: ->
-        content = @$inputor.val()
-        ##获得inputor中插入符的position.
-        caret_pos = @$inputor.caretPos()
-        ### 向在插入符前的的文本进行正则匹配
-         * 考虑会有多个 @ 的存在, 匹配离插入符最近的一个###
-        subtext = content.slice(0,caret_pos)
-
-        query = null
-        $.each @settings, (flag, settings) =>
-          query = this.callbacks("matcher").call(this, flag, subtext)
-          if query?
-            @current_flag = flag
-            return false
-
-        if typeof query is "string" and query.length <= 20
-          start = caret_pos - query.length
-          end = start + query.length
-          @pos = start
-          query = {'text': query.toLowerCase(), 'head_pos': start, 'end_pos': end}
+          $.noop()
+          e.stopPropagation()
+
+    on_keydown: (e) ->
+      return if not @view.visible()
+      switch e.keyCode
+        when KEY_CODE.ESC
+          e.preventDefault()
+          @view.hide()
+        when KEY_CODE.UP
+          e.preventDefault()
+          @view.prev()
+        when KEY_CODE.DOWN
+          e.preventDefault()
+          @view.next()
+        when KEY_CODE.TAB, KEY_CODE.ENTER
+          return if not @view.visible()
+          e.preventDefault()
+          @view.choose()
         else
+          $.noop()
+      e.stopPropagation()
+
+    # 将处理完的数据显示到下拉列表中
+    #
+    # @param data [Array] 处理过后的数据列表
+    render_view: (data) =>
+      search_key = this.get_opt("search_key")
+
+      data = this.callbacks("data_refactor").call(this, data)
+      data = this.callbacks("sorter").call(this, @query.text, data, search_key)
+      data = data.splice(0, this.get_opt('limit'))
+
+      @view.render data
+
+    # 根据关键字搜索数据
+    look_up: ->
+      query = this.catch_query()
+      return no if not query
+
+      origin_data = this.get_opt("data")
+      search_key = this.get_opt("search_key")
+      if typeof data is "string"
+        params =
+          q: query.text
+          limit: this.get_opt("limit")
+        # $.proxy(this.render_view, this)
+        this.callbacks('remote_filter').call(this, params, this.render_view)
+      else if (data = this.callbacks('filter').call(this, query.text, origin_data, search_key))
+          this.render_view data
+      else
           @view.hide()
-
-        @query = query
-
-      # 将选中的项的`data-value` 内容插入到输入框中
-      #
-      # @param str [String] 要插入的字符串, 一般为 `data-value` 的值.
-      replace_str: (str) ->
-        $inputor = @$inputor
-        source = $inputor.val()
-        flag_len = if this.get_opt("display_flag") then 0 else @current_flag.length
-        start_str = source.slice 0, (@query['head_pos'] || 0) - flag_len
-        text = "#{start_str}#{str} #{source.slice @query['end_pos'] || 0}"
-
-        $inputor.val text
-        $inputor.caretPos start_str.length + str.length + 1
-        $inputor.change()
-
-      on_keyup: (e) ->
-        return unless @view.visible()
-        switch e.keyCode
-          when KEY_CODE.ESC
-            e.preventDefault()
-            @view.hide()
-          else
-            $.noop()
-            e.stopPropagation()
-
-      on_keydown: (e) ->
-        return if not @view.visible()
-        switch e.keyCode
-          when KEY_CODE.ESC
-            e.preventDefault()
-            @view.hide()
-          when KEY_CODE.UP
-            e.preventDefault()
-            @view.prev()
-          when KEY_CODE.DOWN
-            e.preventDefault()
-            @view.next()
-          when KEY_CODE.TAB, KEY_CODE.ENTER
-            return if not @view.visible()
-            e.preventDefault()
-            @view.choose()
-          else
-            $.noop()
+      $.noop()
+
+
+  # 操作下拉列表所有表现行为的类
+  # 所有的这个类的对象都只操作一个视图.
+  class View
+
+    # @param controller [Object] 控制器对象.
+    constructor: (@controller) ->
+      @id = @controller.get_opt("view_id", "at-view")
+      @timeout_id = null
+      @$el = $("##{@id}")
+      this.create_view()
+
+    # 如果试图还不存在,则创建一个新的视图
+    create_view: ->
+      return if this.exist()
+      tpl = "

"
+      $("body").append(tpl)
+      @$el = $("##{@id}")
+
+      $menu = @$el.find('ul')
+      $menu.on 'mouseenter.view','li', (e) ->
+        $menu.find('.cur').removeClass 'cur'
+        $(e.currentTarget).addClass 'cur'
+      .on 'click', (e) =>
         e.stopPropagation()
-
-      # 将处理完的数据显示到下拉列表中
-      #
-      # @param data [Array] 处理过后的数据列表
-      render_view: (data) ->
-        search_key = this.get_opt("search_key")
-
-        data = this.callbacks("data_refactor").call(this, data)
-        data = this.callbacks("sorter").call(this, @query.text, data, search_key)
-        data = data.splice(0, this.get_opt('limit'))
-
-        @view.render data
-
-      # 根据关键字搜索数据
-      look_up: ->
-        query = this.catch_query()
-        return no if not query
-
-        origin_data = this.get_opt("data")
-        search_key = this.get_opt("search_key")
-        if typeof data is "string"
-          params =
-            q: query.text
-            limit: this.get_opt("limit")
-          callback = $.proxy(this.render_view, this.callbacks('remote_filter').call(this, params, callback))
-        else if (data = this.callbacks('filter').call(this, query.text, origin_data, search_key))
-            this.render_view data
-        else
-            @view.hide()
-        $.noop()
-
-
-    # 操作下拉列表所有表现行为的类
-    # 所有的这个类的对象都只操作一个视图.
-    class View
-
-      # @param controller [Object] 控制器对象.
-      constructor: (@controller) ->
-        @id = @controller.get_opt("view_id", "at-view")
-        @timeout_id = null
-        @$el = $("##{@id}")
-        this.create_view()
-
-      # 如果试图还不存在,则创建一个新的视图
-      create_view: ->
-        return if this.exist()
-        tpl = "

"
-        $("body").append(tpl)
-        @$el = $("##{@id}")
-
-        $menu = @$el.find('ul')
-        $menu.on 'mouseenter.view','li', (e) ->
-          $menu.find('.cur').removeClass 'cur'
-          $(e.currentTarget).addClass 'cur'
-        .on 'click', (e) =>
-          e.stopPropagation()
-          e.preventDefault()
-          this.choose()
-
-      # 判断视图是否存在
-      #
-      # @return [Boolean]
-      exist: ->
-        $("##{@id}").length > 0
-
-      # 判断视图是否显示中
-      #
-      # @return [Boolean]
-      visible: ->
-        @$el.is(":visible")
-
-      # 选择某项的操作
-      choose: ->
-        $li = @$el.find ".cur"
-        @controller.callbacks("selector").call(this, $li)
+        e.preventDefault()
+        this.choose()
+
+    # 判断视图是否存在
+    #
+    # @return [Boolean]
+    exist: ->
+      $("##{@id}").length > 0
+
+    # 判断视图是否显示中
+    #
+    # @return [Boolean]
+    visible: ->
+      @$el.is(":visible")
+
+    # 选择某项的操作
+    choose: ->
+      $li = @$el.find ".cur"
+      @controller.callbacks("selector").call(this, $li)
+      this.hide()
+
+    # 重置视图在页面中的位置.
+    reposition: ->
+      rect = @controller.rect()
+      if rect.bottom + @$el.height() - $(window).scrollTop() > $(window).height()
+          rect.bottom = rect.top - @$el.height()
+      @$el.offset {left:rect.left, top:rect.bottom}
+
+    next: ->
+      cur = @$el.find('.cur').removeClass('cur')
+      next = cur.next()
+      next = $(@$el.find('li')[0]) if not next.length
+      next.addClass 'cur'
+
+    prev: ->
+      cur = @$el.find('.cur').removeClass('cur')
+      prev = cur.prev()
+      prev = @$el.find('li').last() if not prev.length
+      prev.addClass('cur')
+
+    show: ->
+      @$el.show() if not this.visible()
+      this.reposition()
+
+    hide: (time) ->
+      if isNaN time
+        @$el.hide() if this.visible()
+      else
+        callback = => this.hide()
+        clearTimeout @timeout_id
+        @timeout_id = setTimeout callback, @controller.get_opt("display_timeout", 300)
+
+    clear: ->
+      @$el.find('ul').empty()
+
+    render: (list) ->
+      return no if not $.isArray(list)
+      if list.length <= 0
         this.hide()
-
-      # 重置视图在页面中的位置.
-      reposition: ->
-        rect = @controller.rect()
-        if rect.bottom + @$el.height() - $(window).scrollTop() > $(window).height()
-            rect.bottom = rect.top - @$el.height()
-        @$el.offset {left:rect.left, top:rect.bottom}
-
-      next: ->
-        cur = @$el.find('.cur').removeClass('cur')
-        next = cur.next()
-        next = $(@$el.find('li')[0]) if not next.length
-        next.addClass 'cur'
-
-      prev: ->
-        cur = @$el.find('.cur').removeClass('cur')
-        prev = cur.prev()
-        prev = @$el.find('li').last() if not prev.length
-        prev.addClass('cur')
-
-      show: ->
-        @$el.show() if not this.visible()
-        this.reposition()
-
-      hide: (time) ->
-        if isNaN time
-          @$el.hide() if this.visible()
-        else
-          callback = => this.hide()
-          clearTimeout @timeout_id
-          @timeout_id = setTimeout callback, @controller.get_opt("display_timeout", 300)
-
-      clear: ->
-        @$el.find('ul').empty()
-
-      render: (list) ->
-        return no if not $.isArray(list)
-        if list.length <= 0
-          this.hide()
-          return yes
-
-        # holder.cache(list)
-        this.clear()
-
-        $ul = @$el.find('ul')
-        tpl = @controller.get_opt('tpl', DEFAULT_TPL)
-
-        $.each list, (i, item) =>
-          li = @controller.callbacks("tpl_eval").call(this, tpl, item)
-          $ul.append @controller.callbacks("highlighter").call(this, li, @controller.query.text)
-
-        this.show()
-        $ul.find("li:eq(0)").addClass "cur"
-
-
-    DEFAULT_TPL = "
${name}
"
-
-    $.fn.atWho = (flag, options) ->
-      @.filter('textarea, input').each () ->
-        $this = $(this)
-        data = $this.data "AtWho"
-
-        $this.data 'AtWho', (data = new Controller(this)) if not data
-        data.reg flag, options
-
-    $.fn.atWho.Controller = Controller
-    $.fn.atWho.View = View
-    $.fn.atWho.Mirror = Mirror
-    $.fn.atWho.default =
-        data: null
-        search_key: "name"
-        callbacks: DEFAULT_CALLBACKS
-        cache: yes
-        limit: 5
-        display_flag: yes
-        display_timeout: 300
-        tpl: DEFAULT_TPL
+        return yes
+
+      # holder.cache(list)
+      this.clear()
+
+      $ul = @$el.find('ul')
+      tpl = @controller.get_opt('tpl', DEFAULT_TPL)
+
+      $.each list, (i, item) =>
+        li = @controller.callbacks("tpl_eval").call(this, tpl, item)
+        $ul.append @controller.callbacks("highlighter").call(this, li, @controller.query.text)
+
+      this.show()
+      $ul.find("li:eq(0)").addClass "cur"
+
+
+  DEFAULT_TPL = "
${name}
"
+
+  $.fn.atWho = (flag, options) ->
+    @.filter('textarea, input').each () ->
+      $this = $(this)
+      data = $this.data "AtWho"
+
+      $this.data 'AtWho', (data = new Controller(this)) if not data
+      data.reg flag, options
+
+  $.fn.atWho.Controller = Controller
+  $.fn.atWho.View = View
+  $.fn.atWho.Mirror = Mirror
+  $.fn.atWho.default =
+      data: null
+      search_key: "name"
+      callbacks: DEFAULT_CALLBACKS
+      cache: yes
+      limit: 5
+      display_flag: yes
+      display_timeout: 300
+      tpl: DEFAULT_TPL

 )(window.jQuery)


Time checking fix (and hello from Windows)

--- tests/user.coffee
+++ tests/user.coffee
@@ -277,8 +277,8 @@
 	test.ok user.salt.length > 0, 'should generate salt'
 	test.ok user.hash.length > 0, 'should generate hash'
 	test.ok user.sessid.length > 0, 'should generate sessid'
-	test.ok user.reg_time <= new Date(), 'should not put registration time into future'
-	test.ok user.sess_time <= new Date(), 'should not put session timestamp into future'
+	test.ok Math.abs(user.reg_time - new Date()) < 1000, 'should set registration time to (almost) current time'
+	test.ok Math.abs(user.sess_time - new Date()) < 1000, 'should set session timestamp to (almost) current time'
 	test.strictEqual user.permissions, 'admin', 'should set specified permissions'
 	test.strictEqual user.character_id, null, 'should not assign character'


fix game removal issue

fix game removal issue

--- server.coffee
+++ server.coffee
@@ -99,8 +99,9 @@

       when "leave-game"
         msg.action = "quit"
-        requester.send(JSON.stringify(msg)) if games[socket.gameid].players.length > 1
         removePlayer(socket)
+        game = games[socket.gameid]
+        requester.send(JSON.stringify(msg)) if game and game.players.length > 1

       when "join"
         joinGame(socket, msg.gameid)


Send game list updates at most 1 per second.

--- server.coffee
+++ server.coffee
@@ -25,9 +25,13 @@
 # Game lobby
 gameid = 0
 games = {}
+lobbyUpdate = false

 swapSide = (side) ->
   if side is "Corp" then "Runner" else "Corp"
+
+refreshLobby = () ->
+  lobbyUpdate = true

 removePlayer = (socket) ->
   game = games[socket.gameid]
@@ -45,7 +49,7 @@
       requester.send(JSON.stringify({action: "remove", gameid: socket.gameid}))
     socket.leave(socket.gameid)
     socket.gameid = false
-    lobby.emit('netrunner', {type: "games", games: games})
+    refreshLobby()
   for k, v of games
     delete games[k] if v.players.length < 2 and (new Date() - v.date) > 3600000

@@ -57,7 +61,7 @@
     socket.join(gameid)
     socket.gameid = gameid
     socket.emit("netrunner", {type: "game", gameid: gameid})
-    lobby.emit('netrunner', {type: "games", games: games})
+    refreshLobby()

 getUsername = (socket) ->
   ((socket.request || {}).user || {}).username
@@ -86,7 +90,7 @@
     db.collection('messages').insert msg, (err, result) ->

 lobby = io.of('/lobby').on 'connection', (socket) ->
-  lobby.emit('netrunner', {type: "games", games: games})
+  refreshLobby()

   socket.on 'disconnect', () ->
     gid = socket.gameid
@@ -105,7 +109,7 @@
         socket.join(gameid)
         socket.gameid = gameid
         socket.emit("netrunner", {type: "game", gameid: gameid})
-        lobby.emit('netrunner', {type: "games", games: games})
+        refreshLobby()

       when "leave-lobby"
         gid = socket.gameid
@@ -136,7 +140,7 @@
           socket.join(msg.gameid)
           socket.gameid = msg.gameid
           socket.emit("netrunner", {type: "game", gameid: msg.gameid, started: game.started})
-          lobby.emit('netrunner', {type: "games", games: games})
+          refreshLobby()
           if game.started
             requester.send(JSON.stringify({action: "notification", gameid: msg.gameid, text: "#{getUsername(socket)} joined the game as a spectator."}))
           else
@@ -177,13 +181,20 @@
           requester.send(JSON.stringify(msg))
           for player in game.players
             delete player["deck"]
-          lobby.emit('netrunner', {type: "games", games: games})
+          refreshLobby()

       when "do"
         try
           requester.send(JSON.stringify(msg))
         catch err
           console.log(err)
+
+sendLobby = () ->
+  if lobby and lobbyUpdate
+    lobby.emit('netrunner', {type: "games", games: games})
+    lobbyUpdate = false
+
+setInterval(sendLobby, 1000)

 # Express config
 app.configure ->