Teambuilder: Add new team button should work.

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -8,7 +8,7 @@

   events:
     # Team view
-    'click .add-new-team': 'addNewTeam'
+    'click .add-new-team': 'addNewTeamEvent'
     'click .export-team': 'exportTeam'
     'click .clone-team': 'cloneTeam'
     'click .delete-team': 'deleteTeam'
@@ -94,6 +94,9 @@

   addEmptyPokemon: (team) =>
     team.add(new Pokemon(teambuilder: true))
+
+addNewTeamEvent: (e) =>
+  @addNewTeam()

   addNewTeam: (team) =>
     team ||= new Team()


fix ReferenceError in event handler :facepalm:

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -609,7 +609,7 @@
   splitLeft = handle.getX()

   notifier = new ProximityNotifier
-    handler: ->
+    handler: (event) ->
       { pageX, pageY } = event

       distX = Math.pow splitLeft - pageX, 2


test: remove landing cc step test

test: remove landing cc step test

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -185,10 +185,6 @@
         .pause                 2500

       @enterTeamURL(browser)
-      helpers.fillPaymentForm(browser)
-      browser
-        .waitForElementVisible  '[testpath=payment-button]', 20000
-        .click                  '[testpath=payment-button]'
       @fillUsernamePasswordForm(browser, user)

@@ -328,7 +324,7 @@
       browser
         .waitForElementVisible  purposeSelector, 20000
         .setValue               purposeSelector, purpose
-
+
     browser
       .waitForElementVisible  createChannelModalButtonSelector, 20000
       .moveToElement          createChannelModalButtonSelector, 32, 12
@@ -349,7 +345,7 @@
         .pause                  2000 # wait for side bar channel list
         .waitForElementVisible  channelLinkOnSidebarSelector, 20000
         .assert.containsText    sidebarSectionsSelector, channelName
-
+
     if purpose
       browser.assert.containsText '.ChannelThreadPane-purposeWrapper .ChannelThreadPane-purpose',purpose

@@ -637,4 +633,4 @@
       .setValue               inputTextSelector, editedPurposeText + browser.Keys.ENTER
       .pause                  3000 #waiting for the text to change
       .waitForElementVisible  editedTextSelector, 20000
-      .assert.containsText    editedTextSelector, editedPurposeText+      .assert.containsText    editedTextSelector, editedPurposeText


since we're being opionated

since we're being opionated

--- src/editor.coffee
+++ src/editor.coffee
@@ -42,6 +42,7 @@
     @ace.getSession().setTabSize 2
     @ace.pane = this
     @ace.setShowInvisibles(true)
+    @ace.setPrintMarginColumn 78

     @ace.getSession().on 'change', ->
       activeWindow.setDirty true
@@ -56,7 +57,7 @@
     return @saveAs() if not @filename

     @removeTrailingWhitespace()
-    File.write @filename, @code()
+    File.write @filename, @code()
     @sessions[@filename] = @ace.getSession()
     activeWindow.setDirty false
     @ace._emit 'save', { @filename }
@@ -109,7 +110,7 @@

   deleteSession: (path) ->
     if path is @filename
-      @filename = null
+      @filename = null
     delete @sessions[path]
     @ace.setSession @newSession()


Don't allow NaNs

Don't allow NaNs

--- src/editor.coffee
+++ src/editor.coffee
@@ -1400,14 +1400,18 @@
   #
   # Returns a {Number}.
   getFirstVisibleScreenRow: ->
-    Math.floor(@scrollTop() / @lineHeight)
+    screenRow = Math.floor(@scrollTop() / @lineHeight)
+    screenRow = 0 if isNaN(screenRow)
+    screenRow

   # Retrieves the number of the row that is visible and currently at the bottom of the editor.
   #
   # Returns a {Number}.
   getLastVisibleScreenRow: ->
     calculatedRow = Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1
-    Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
+    screenRow = Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
+    screenRow = 0 if isNaN(screenRow)
+    screenRow

   # Given a row number, identifies if it is currently visible.
   #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -53,7 +53,7 @@

   enemy.follow(player)

-  # =>
+  # => The enemy no


Favourite user topics when following a user.

--- core/app/backbone/views/tour/tour_users_list_view.coffee
+++ core/app/backbone/views/tour/tour_users_list_view.coffee
@@ -24,8 +24,8 @@

   primaryAction: ->
     @model.follow()
-    # TODO: favourite
     @$el.removeClass 'hover'
+    @model.user_topics().invoke 'favourite'
     @updateButton()

   secondaryAction: ->


add help command to print out emotions

add help command to print out emotions

--- src/gifgif.coffee
+++ src/gifgif.coffee
@@ -73,3 +73,6 @@
               msg.send "GifGif #{res.statusCode} error:\n#{body}"
     else
       msg.send "\u261B Valid emotions include: #{emotions}"
+
+  robot.respond /gifgif\?/i, (msg) ->
+    msg.send "\u261B Valid emotions include: #{emotions}"


moar documentation: finished documenting Repository.

--- src/gitteh.coffee
+++ src/gitteh.coffee
@@ -442,8 +442,8 @@
  * @property {String} path location of the Git metadata directory
  * @property {String} workingDirectory location of the working directory, if
  * applicable (non-bare repository)
- * @property {Remote[]} remotes List of remotes configured for this repository.
- * @property {Reference[]} references List of references contained in this
+ * @property {String[]} remotes  names of remotes configured for this repository
+ * @property {String[]} references names of references contained in this
  * repository.
  * @property {Index} index The Git index for this repository.
 ###
@@ -566,6 +566,11 @@

 ###*
  * Creates a new reference, which can either by direct or symbolic.
+ * @param {String} name
+ * @param {String} target reference/oid targetted by the new reference.
+ * @param {Boolean} [force=false] force creation of this reference, destroying
+ * the reference with same name, if it exists.
+ * @param {Function} cb called when reference has been created.
  * @see Reference
 ###
 Repository.prototype.createReference = ->
@@ -580,6 +585,12 @@
 		fn = "createOidReference"
 	_priv.native[fn] name, target, force, wrapCallback cb, (ref) =>
 		cb null, new Reference @, ref
+
+###*
+ * Loads a remote with given name.
+ * @param {String} name
+ * @param {Function} cb called when remote has been loaded.
+###
 Repository.prototype.remote = ->
 	_priv = getPrivate @
 	[name, cb] = args
@@ -587,6 +598,14 @@
 		cb: type: "function"
 	_priv.native.remote name, wrapCallback cb, (remote) =>
 		return cb null, new Remote @, remote
+
+###*
+ * Create a new {@link Remote} for this repository.
+ * @param {String} name
+ * @param {String} url
+ * @param {Function} cb called when Remote has been created.
+ * @see Remote
+###
 Repository.prototype.createRemote = ->
 	_priv = getPrivate @
 	[name, url, cb] = args


add adroll tracking

add adroll tracking

--- js/inner/billing.coffee
+++ js/inner/billing.coffee
@@ -252,6 +252,7 @@
         mixpanel.track('Paid')
         window._pq = window._pq or []
         _pq.push ["track" "payer"]
+        __adroll.record_user({"adroll_segments": "payer"})
         @loadPlanData(data)
         @loadInvoices()
         VM.org().subpage('containers')


Debug draw: Fix point when not supplying position.

--- engine/debug_draw.coffee
+++ engine/debug_draw.coffee
@@ -273,7 +273,7 @@
     constructor: (debug_draw, options={}) ->
         super debug_draw
         {
-            @position=[vec3.create(),vec3.create()]
+            @position=vec3.create()
             @color=color4.new 1,1,1,1
             @size=.1
             @ttl_frames=0


only wrap if there is content to wrap

only wrap if there is content to wrap

--- js/libs/terminal.coffee
+++ js/libs/terminal.coffee
@@ -52,7 +52,10 @@
           ""

     wrapDefaultColor: (content) ->
-      "#{content or ""}"
+      if (not content?) or (content is "")
+        ""
+      else
+        "#{content}"

     get_trailing: () ->
       @wrapDefaultColor(trailing_out)


added count

added count

--- caboose-model/lib/model.coffee
+++ caboose-model/lib/model.coffee
@@ -26,6 +26,9 @@

   @first: (callback) ->
     new Query(this).first callback
+
+  @count: (callback) ->
+    new Query(this).count callback

   @where: (query) ->
     new Query this, query


Update event arguments

Update event arguments

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -66,18 +66,20 @@
 #
 # * `matched` -
 #      Emitted when keystrokes match a binding.
-#      * usedBinding - The {KeyBinding} that was used
-#      * unusedBindings - Other {KeyBinding}s that matched, but weren't used
+#      * keystrokes - The keystroke {String} that matched the binding
+#      * binding - The {KeyBinding} that was used
+#      * keyboardEventTarget - The target element of the keyboard event
 #
 # * `matched-partially` -
 #      Emitted when keystrokes partially match one or more bindings.
-#      * keystrokes - The keystroke {String} that triggered the partial
-#        binding match
-#      * keyBindings - The {KeyBinding}s that partially matched
+#      * keystrokes - The keystroke {String} that partially match some bindings
+#      * partiallyMatchedBindings - The {KeyBinding}s that partially matched
+#      * keyboardEventTarget - The target element of the keyboard event
 #
 # * `match-failed` -
 #      Emitted when keystrokes don't match any bindings.
 #      * keystrokes - The keystroke {String} that matched no bindings
+#      * keyboardEventTarget - The target element of the keyboard event

 module.exports =
 class Keymap
@@ -243,7 +245,7 @@
           @clearQueuedKeystrokes()
           @cancelPendingState()
           if @dispatchCommandEvent(exactMatch.command, target, event)
-            @emit 'matched', exactMatch, _.without(exactMatches, exactMatch)
+            @emit 'matched', {keystrokes, binding:exactMatch , keyboardEventTarget: target}
             return
         currentTarget = currentTarget.parentElement

@@ -256,9 +258,9 @@
       event.preventDefault()
       enableTimeout = foundMatch ? @pendingStateTimeoutHandle?
       @enterPendingState(partialMatches, enableTimeout)
-      @emit 'matched-partially', keystrokes, partialMatches
+      @emit 'matched-partially', {keystrokes, partiallyMatchedBindings: partialMatches, keyboardEventTarget: target}
     else
-      @emit 'match-failed', keystrokes
+      @emit 'match-failed', {keystrokes, keyboardEventTarget: target}
       @terminatePendingState()

   # Public: Get the key bindings for a given command and optional target.


handle some attributes in aspecial way

handle some attributes in aspecial way

--- src/linker.coffee
+++ src/linker.coffee
@@ -1,4 +1,9 @@
 { isArray } = Array
+
+##
+# check if a string contains a substring
+is_sub = (string, sub) ->
+    (string?.indexOf?(sub) ? -1) isnt -1

 ##
 # copy many objects into one
@@ -34,9 +39,17 @@
     return yes unless el? # nothing to test against
     return no if tag.name isnt el.name
     for key, value of tag.attrs
-        if el.attrs[key] isnt value
-            return no if typeof value isnt 'string' or
-                        (el.attrs[key]?.indexOf(value) ? -1) is -1
+        elvalue = el.attrs[key]
+        # handle some attributes in a special way
+        switch key.toLowerCase()
+            when 'class'
+                # ignore order
+                for cls in value.split(' ')
+                    return no unless is_sub(elvalue, cls)
+            else
+                if value isnt elvalue
+                    unless typeof value is 'string' and is_sub(elvalue, value)
+                        return no
     return yes

 ##


note on does.get(..) errors

note on does.get(..) errors

--- src/loader.coffee
+++ src/loader.coffee
@@ -53,7 +53,16 @@

             does.get query: tag: name, (error, object) ->

+                #
+                # * does.get() returns error on not found tag or bad args
+                #       * for now those errors can be safely ignored
+                #           * not found is valid reason to fall through to local injection below
+                #           * bad args will not occur becase no pathway exists that leads through
+                #             here to a call to does.get with bad args.
+                #
+
                 return action.resolve object if object?
+                # return action.reject error if # TODO: network/db errors later

                 if path = (try local.modules[name].require)
                     if path[0] is '.' then path = normalize local.dir + sep + path


smoothing disabled?

smoothing disabled?

--- scripts/coffee/app.coffee
+++ scripts/coffee/app.coffee
@@ -17,6 +17,7 @@
   console.log ':create'

   game.stage.backgroundColor = config.backgroundColor
+  game.stage.smoothed = false

   game.physics.startSystem(Phaser.Physics.ARCADE)


Follow symlinks for vendors.

Follow symlinks for vendors.

--- src/toaster/toast.coffee
+++ src/toaster/toast.coffee
@@ -109,15 +109,17 @@
       # ...: vendors - optional
       if config.optimize.vendors?
         for vname, vurl of config.optimize.vendors
-
+
           continue if /^http/m.test vurl

           vpath = path.join @basepath, vurl

           if fs.existsSync vpath
+            if (fs.lstatSync vpath).isSymbolicLink()
+              vpath = fs.readlinkSync vendor
             config.optimize.vendors[vname] = vpath
+
           else
-
             # error "Local vendor not found. #{dir}\nCheck your config."
             msg = 'Check your `toaster.coffee` config file, local vendor was '
             msg += 'not found:\n\t' + vpath


fixing IE again

fixing IE again

--- client/routes/roomRoute.coffee
+++ client/routes/roomRoute.coffee
@@ -18,7 +18,8 @@

 				mainNode = document.querySelector('.main-content')
 				if mainNode?
-					child?.remove() for child in mainNode.children
+					for child in mainNode.children
+						mainNode.removeChild child if child?
 					room = RoomManager.getDomOfRoom(params._id)
 					mainNode.appendChild room
 					if room.classList.contains('room-container')
@@ -42,5 +43,5 @@
 					if child?
 						if child.classList.contains('room-container')
 							child.oldScrollTop = child.querySelector('.messages-box > .wrapper').scrollTop
-						child.remove()
+						mainNode.removeChild child
 	]


WebTermview: after getting disconnected, wait for connection first.

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -139,8 +139,9 @@

     kite.on 'close', =>

-      unless kite.isDisconnected
-        @webtermConnect if remote? then 'resume' else 'create'
+      if not kite.isDisconnected and not kite.invalid
+        kite.once 'connected', =>
+          @webtermConnect if remote? then 'resume' else 'create'

   connectToTerminal: ->


fix overwritten code from clean-up

fix overwritten code from clean-up

--- client/views/login/services.coffee
+++ client/views/login/services.coffee
@@ -11,7 +11,7 @@
 					serviceName = 'Meteor'
 					icon = 'meteor'
 				when 'github'
-					serviceName = 'Github'
+					serviceName = 'GitHub'
 					icon = 'github-circled'
 				when 'gitlab'
 					serviceName = 'Gitlab'


STL/ajax Close #9 making it extract JSON data on 'complete' instad of 'success'

--- stl/ajax/src/ajax.coffee
+++ stl/ajax/src/ajax.coffee
@@ -61,8 +61,8 @@
     @options = ext(@options, options)

     # attaching the standard listeners
+    @on 'complete', 'evalScripts'
     @on
-      success:  'evalScripts'
       create:   'showSpinner'
       complete: 'hideSpinner'
       cancel:   'hideSpinner'
@@ -204,7 +204,7 @@
     else if options.evalJSON and /json/i.test(content_type)
       @responseJSON = JSON.parse(@responseText)

-    if (options = @header('X-JSON'))
+    if options = @header('X-JSON')
       this.headerJSON = JSON.parse(options)

     return;


Adjusting pathing for robot.coffee

Adjusting pathing for robot.coffee

--- src/scripts/team-city-listener.coffee
+++ src/scripts/team-city-listener.coffee
@@ -3,6 +3,10 @@
 # environment variable and Bob's your uncle you'll get build status messages from Hubot in your chat rooms.
 # All the properties aviable on the build object can be found at the properties list at the top of this file:
 # http://sourceforge.net/apps/trac/tcplugins/browser/tcWebHooks/trunk/src/main/java/webhook/teamcity/payload/format/WebHookPayloadJsonContent.java
+# THIS SCRIPT ASSUMES IT IS RUNNING FROM THE SCRIPTS DIRECTORY AT THE ROOT OF YOUR HUBOT INSTALL
+# If it's not, you'll need to adjust the path to the robot.coffee file right below
+
+Robot = require '../node_modules/hubot/src/robot.coffee'

 room = process.env.HUBOT_ROOM_TO_RECEIVE_TEAM_CITY_BUILD_RESULTS
 unless room


navigate to new instance page on 'start app' event

--- client/main.cjsx
+++ client/main.cjsx
@@ -55,7 +55,7 @@
   onEvent 'app removed', (app) ->
     eventEmitter.emit 'show info message', "App #{app.name}:#{app.version} is deleted."
     browserHistory.push '/apps'
-  onEvent 'start app', (app) -> console.log 'start app', app
+  onEvent 'start app', (app) -> browserHistory.push("/instance/new/#{app.name}/#{app.version}")

   onEvent 'show error message', (message) -> Session.set 'globalErrorMessage', ErrorMapper message
   onEvent 'clear error message', -> Session.set 'globalErrorMessage', null


ReceiveFiles: update styling

ReceiveFiles: update styling

--- components/ReceiveFiles.coffee
+++ components/ReceiveFiles.coffee
@@ -9,7 +9,7 @@
     @inPorts =
       in: new noflo.Port 'object'
     @outPorts =
-      out: new noflo.Port 'object'
+      file: new noflo.Port 'object'
       error: new noflo.Port 'object'

     @inPorts.in.on 'data', (request) =>
@@ -18,16 +18,14 @@
       else
         @sendError("No files attached")
     @inPorts.in.on 'disconnect', () =>
-      @outPorts.out.disconnect() if @outPorts.out.isConnected()
+      @outPorts.file.disconnect() if @outPorts.file.isConnected()

-  sendError: (err) ->
-    return unless @outPorts.error.isAttached()
-    @outPorts.error.send(new Error(err))
-    @outPorts.error.disconnect()
+  sendError: (msg) ->
+    @error new Error msg

   sendFiles: (request) ->
-    return unless @outPorts.out.isAttached()
+    return unless @outPorts.file.isAttached()
     for name, obj of request.files
-      @outPorts.out.send(obj)
+      @outPorts.file.send(obj)

 exports.getComponent = -> new ReceiveFiles


:lipstick: Combine specs

:lipstick: Combine specs

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -57,7 +57,7 @@
         expect(atom.config.settings.foo).toEqual {}

   describe ".getDefault(keyPath)", ->
-    it "returns the default value", ->
+    it "returns a clone of the default value", ->
       atom.config.setDefaults("foo", same: 1, changes: 1)
       expect(atom.config.getDefault('foo.same')).toBe 1
       expect(atom.config.getDefault('foo.changes')).toBe 1
@@ -67,7 +67,6 @@
       expect(atom.config.getDefault('foo.same')).toBe 1
       expect(atom.config.getDefault('foo.changes')).toBe 1

-    it "returns a clone of the default value", ->
       initialDefaultValue = [1, 2, 3]
       atom.config.setDefaults("foo", bar: initialDefaultValue)
       expect(atom.config.getDefault('foo.bar')).toEqual initialDefaultValue


Add spec for update event on load

Add spec for update event on load

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -359,10 +359,18 @@
     describe "when the config file contains valid cson", ->
       beforeEach ->
         fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'baz'")
+
+      it "updates the config data based on the file contents", ->
         atom.config.loadUserConfig()
-
-      it "updates the config data based on the file contents", ->
         expect(atom.config.get("foo.bar")).toBe 'baz'
+
+      it "notifies observers for updated keypaths on load", ->
+        observeHandler = jasmine.createSpy("observeHandler")
+        observeSubscription = atom.config.observe "foo.bar", observeHandler
+
+        atom.config.loadUserConfig()
+
+        expect(observeHandler).toHaveBeenCalledWith 'baz'

     describe "when the config file contains invalid cson", ->
       beforeEach ->


remove a debug line.

remove a debug line.

--- kites/pinger/api.coffee
+++ kites/pinger/api.coffee
@@ -30,7 +30,7 @@
           .digest('hex')
         request.get {
           uri: apiUri
-          qs: {env: 'vpn', kiteName, uri, token}
+          qs: {kiteName, uri, token}
         }, -> callback()
       else
         console.log "#{kiteName} has responded to ping"


sync in local hub: add locking so that filesystem and client sync don't corrupt each other; I observed a situation when a patch got multiply applied, studying the log -- the error was 100% in the local hub during the sync, so not the fault of clients.  Right in the middle, a filesystem sync happened, and the local_hub code had no locking against filesystem sync and client sync.  It's easy to imagine them conflicting and causing this bug.  Now there is a lock.

--- salvus/local_hub.coffee
+++ salvus/local_hub.coffee
@@ -710,11 +710,13 @@
         @connect(@server)
         @server.connect(@)

-# The CodeMirrorDiffSyncHub class represents a global hub viewed as a
-# remote client for this local hub.  There may be dozens of global
-# hubs connected to this single local hub, and these are the only
-# clients a local hub can have.  The local hub has no upstream server,
-# except the on-disk file itself.
+# The CodeMirrorDiffSyncHub class represents a
+# downstream remote client for this local hub.  There may be dozens of thes.
+# The local hub has no upstream server, except the on-disk file itself.
+#
+# NOTE: These have *nothing* a priori to do with CodeMirror -- the name is
+# historical and should be changed. TODO.
+#
 class CodeMirrorDiffSyncHub
     constructor : (@socket, @session_uuid, @client_id) ->

@@ -737,6 +739,7 @@
         @write_mesg('diffsync_ready')

+# TODO:
 class CodeMirrorSession
     constructor: (mesg, cb) ->
         @path = mesg.path
@@ -1271,7 +1274,7 @@
             write_mesg('error', {error:"client #{mesg.client_id} not registered for synchronization"})
             return

-        if @_client_sync_lock # or Math.random() <= .5 # (for testing)
+        if @_client_sync_lock or @_filesystem_sync_lock # or Math.random() <= .5 # (for testing)
             winston.debug("client_diffsync hit a sync lock -- send retry message back")
             write_mesg('error', {error:"retry"})

@@ -1299,10 +1302,23 @@

     sync_filesystem: (cb) =>
         @is_active = true
+
+        if @_client_sync_lock # or Math.random() <= .5 # (for testing)
+            winston.debug("sync_filesystem -- hit client sync lock")
+            cb?("cannot sync with filesystem while syncing with clients")
+            return
+        if @_filesystem_sync_lock
+            winston.debug("sync_filesystem -- hit filesystem sync lock")
+            cb?("cannot sync with filesystem; already syncing")
+            return
+
+
         before = @content
         if not @diffsync_fileclient?
             cb?("filesystem sync object (@diffsync_fileclient) no longer defined")
             return
+
+        @_filesystem_sync_lock = true
         @diffsync_fileclient.sync (err) =>
             if err
                 # Example error: 'reset -- checksum mismatch (29089 != 28959)'
@@ -1312,14 +1328,17 @@
                 @diffsync_fileserver = new DiffSyncFile_server @, (err, ignore_content) =>
                     if err
                         winston.debug("@diffsync_fileclient.sync -- making new server failed: #{err}")
-                        cb(err); return
+                        @_filesystem_sync_lock = false
+                        cb?(err); return
                     @diffsync_fileclient = new DiffSyncFile_client(@diffsync_fileserver)
                     @diffsync_fileclient.live = @content
                     @diffsync_fileclient.sync (err) =>
                         if err
                             winston.debug("@diffsync_fileclient.sync -- making server worked but re-sync failed -- #{err}")
+                            @_filesystem_sync_lock = false
                             cb?("codemirror fileclient sync error -- '#{err}'")
                         else
+                            @_filesystem_sync_lock = false
                             cb?()
                 return

@@ -1328,6 +1347,7 @@
                 # recommend all clients sync
                 for id, ds_client of @diffsync_clients
                     ds_client.remote.sync_ready()
+            @_filesystem_sync_lock = false
             cb?()

     add_client: (socket, client_id) =>
@@ -1352,7 +1372,6 @@
             else
                 resp = message.success(id:mesg.id)
             socket.write_mesg('json', resp)
-

     read_from_disk: (socket, mesg) =>
         fs.readFile @path, (err, data) =>


Fix typo in misc_node.coffee that breaks (until now unused) backup code.

--- salvus/misc_node.coffee
+++ salvus/misc_node.coffee
@@ -260,7 +260,7 @@
     env = misc.copy(process.env)

     if opts.env?
-        for k, v of opt.env
+        for k, v of opts.env
             env[k] = v

     if opts.uid?


help -- use Flux instead of FluxComponent to clarify what props are injected; also add when stats last updated and fix a serious bug in which stats are displayed

--- salvus/page/r_help.cjsx
+++ salvus/page/r_help.cjsx
@@ -24,11 +24,11 @@
 ###

-{React, Actions, Store, Table, flux, rtypes, rclass, FluxComponent} = require('flux')
+{React, Actions, Store, Table, flux, rtypes, rclass, Flux} = require('flux')

 {Well, Col, Row, Accordion, Panel, ProgressBar} = require('react-bootstrap')

-{Icon, Loading} = require('r_misc')
+{Icon, Loading, TimeAgo} = require('r_misc')

 {HelpEmailLink, SiteName, SiteDescription} = require('customize')

@@ -59,6 +59,18 @@

 flux.getActions('server_stats').setTo(loading : true)

+stats_connect =
+    loading            : 'server_stats'
+    time               : 'server_stats'
+    hub_servers        : 'server_stats'
+    accounts           : 'server_stats'
+    projects           : 'server_stats'
+    active_projects    : 'server_stats'
+    last_day_projects  : 'server_stats'
+    last_week_projects : 'server_stats'
+    last_month_projects: 'server_stats'
+
+
 # The stats table

 class StatsTable extends Table
@@ -68,7 +80,7 @@
     _change: (table, keys) =>
         newest = undefined
         for obj in table.get(keys).toArray()
-            if obj? and (not newest? or obj.time > newest.time)
+            if obj? and (not newest? or obj.get('time') > newest.get('time'))
                 newest = obj
         if newest
             newest = newest.toJS()
@@ -130,6 +142,9 @@

                 <Icon name='dashboard' /> Current usage
+
+                    updated <TimeAgo date={new Date(@props.time)} />
+

                 {@render_signed_in_stats()}
@@ -522,9 +537,9 @@

                     <HelpPageSupportSection support_links={SUPPORT_LINKS} />

-                    <FluxComponent flux={flux} connectToStores={'server_stats'}>
+                    <Flux flux={flux} connect_to={stats_connect}>
                         <HelpPageUsageSection />
-                    </FluxComponent>
+                    </Flux>

                     <HelpPageAboutSection />


Removed dummy "bots" and made text in the chatbox selectable (bugged)

--- lib/atox-chatbox.coffee
+++ lib/atox-chatbox.coffee
@@ -16,7 +16,7 @@
         @subview "inputfield", new TextEditorView(mini: true, placeholderText: "Type here to write something.")

   addToHistory: (color, user, message) ->
-      @chathistory.append "
#{user}: #{message}"
+      @chathistory.append "
#{user}: #{message}
"

   initialize: (chatname) ->
     atom.views.getView atom.workspace
@@ -25,9 +25,6 @@
     jQuery("#aTox-chatbox-#{chatname}-textfield").keydown( (event) =>
       if event.which == 13 and @inputfield.getText() != ""
         @addToHistory("white", "You", @inputfield.getText());
-        @addToHistory("red", "Taiterio", "Heyo");
-        @addToHistory("red", "Arvius", "Neyo");
-        @addToHistory("red", "Mister Mense", "Meyo");
         jQuery("#aTox-chatbox-#{chatname}-chathistory").scrollTop(jQuery("#aTox-chatbox-#{chatname}-chathistory")[0].scrollHeight);
         @inputfield.setText("");
     )


modify: remove leake ;p

modify: remove leake ;p

--- tasks/gulp.config.coffee
+++ tasks/gulp.config.coffee
@@ -291,7 +291,6 @@

 # coffee compile process pc
 g.task 'coffee-pc', ->
-  pathArray = []
   g.src([paths.pc.js.plugin, paths.pc.js.javascript, paths.pc.js.coffee])
   .pipe $.plumber()
   .pipe $.webpack require './webpack.config.pc.coffee'


fixing bug with teamcity script

fixing bug with teamcity script

--- src/scripts/teamcity.coffee
+++ src/scripts/teamcity.coffee
@@ -47,18 +47,18 @@
         err = body unless res.statusCode == 200
         callback err, body, msg

-getCurrentBuild = (msg, type, callback) ->
-  if (arguments.length == 2)
-    if (Object.prototype.toString.call(type) == "[object Function]")
-      callback = type
-      url = "http://#{hostname}/httpAuth/app/rest/builds/?locator=running:true"
-  else
-    url = "http://#{hostname}/httpAuth/app/rest/builds/?locator=buildType:#{type},running:true"
-  msg.http(url)
-    .headers(getAuthHeader())
-    .get() (err, res, body) ->
-    err = body unless res.statusCode == 200
-    callback err, body, msg
+  getCurrentBuild = (msg, type, callback) ->
+    if (arguments.length == 2)
+      if (Object.prototype.toString.call(type) == "[object Function]")
+        callback = type
+        url = "http://#{hostname}/httpAuth/app/rest/builds/?locator=running:true"
+    else
+      url = "http://#{hostname}/httpAuth/app/rest/builds/?locator=buildType:#{type},running:true"
+    msg.http(url)
+      .headers(getAuthHeader())
+      .get() (err, res, body) ->
+        err = body unless res.statusCode == 200
+        callback err, body, msg

   getProjects = (msg, callback) ->


announce options for custom plans

announce options for custom plans

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -1389,6 +1389,19 @@

                 However, we find that many data science and computational science courses
                 run much smoother with the additional RAM and CPU found in the standard plan.
+
+
Custom Course Plans

+                In addition to the plans listed on this page, we can offer the following on a custom basis:
+

+
start on a specified date after payment

+
customized duration

+
customized number of students

+
bundle several courses with different start dates

+
transfer upgrades from purchasing account to course administrator account

+

+                To learn more about these options, email us at <HelpEmailLink/> with a description
+                of your specific requirements.
+


:art: Rename marker's destroyed flag to match other classes

--- lib/color-marker.coffee
+++ lib/color-marker.coffee
@@ -4,8 +4,9 @@
 module.exports =
 class ColorMarker
   constructor: ({@marker, @color, @text, @invalid, @colorBuffer}) ->
+    @id = @marker.id
     @subscriptions = new CompositeDisposable
-    @subscriptions.add @marker.onDidDestroy => @destroyed()
+    @subscriptions.add @marker.onDidDestroy => @markerWasDestroyed()
     @subscriptions.add @marker.onDidChange =>
       if @marker.isValid()
         @checkMarkerScope()
@@ -15,17 +16,17 @@
     @checkMarkerScope()

   destroy: ->
-    return if @wasDestroyed
+    return if @destroyed
     @marker.destroy()

-  destroyed: ->
-    return if @wasDestroyed
+  markerWasDestroyed: ->
+    return if @destroyed
     @subscriptions.dispose()
     {@marker, @color, @text, @colorBuffer} = {}
-    @wasDestroyed = true
+    @destroyed = true

   match: (properties) ->
-    return false if @wasDestroyed
+    return false if @destroyed

     bool = true

@@ -38,7 +39,7 @@
     bool

   serialize: ->
-    return if @wasDestroyed
+    return if @destroyed
     out = {
       markerId: String(@marker.id)
       bufferRange: @marker.getBufferRange().serialize()
@@ -50,6 +51,7 @@
     out

   checkMarkerScope: (forceEvaluation=false) ->
+    return if @destroyed
     range = @marker.getBufferRange()

     try


run: handle err without stack trace

run: handle err without stack trace

--- lib/commands/run.coffee
+++ lib/commands/run.coffee
@@ -115,9 +115,9 @@
       rl.write '\n'
       rl.write "#{err.message}\n"
       for err in err.errors
-        rl.write "#{styles.final_status_error err.stack.trim() or err.message}\n"
+        rl.write "#{styles.final_status_error err.stack?.trim() or err.message}\n"
     else
-      rl.write "#{styles.final_status_error err.stack.trim() or err.message}\n"
+      rl.write "#{styles.final_status_error err.stack?.trim() or err.message}\n"
     rl.close()


temp disable test

temp disable test

--- spec/filter-spec.coffee
+++ spec/filter-spec.coffee
@@ -770,14 +770,14 @@
       expect(bestMatch(candidates, 'appcon')).toBe candidates[1]

-    it "prefer path together to shorter path", ->
-
-      candidates = [
-        path.join('app', 'controllers', 'shipments_controller.rb'),
-        path.join('app', 'controllers', 'core', 'shipments_controller.rb')
-      ]
-
-      expect(bestMatch(candidates, 'core shipments controller')).toBe candidates[1]
+  #    it "prefer path together to shorter path", ->
+  #
+  #      candidates = [
+  #        path.join('app', 'controllers', 'shipments_controller.rb'),
+  #        path.join('app', 'controllers', 'core', 'shipments_controller.rb')
+  #      ]
+  #
+  #      expect(bestMatch(candidates, 'core shipments controller')).toBe candidates[1]

   #
   #  Optional Characters


Converted some tests for number and deleted some.

--- spec/number_spec.coffee
+++ spec/number_spec.coffee
@@ -14,6 +14,14 @@
         unless equal seen, expected
           throw new Error "Expected #{seen} to be #{expected}."
         expect(classof seen).toEqual classof expected
+
+  check 0, -> num(0).sgn()
+  check 0, -> num(0).cmp(0)
+
+  check 1,      -> num(1).sgn()
+  check true,   -> num(1).cmp(0) > 0
+  check num(1), -> num(1).gcd(1)
+  check num(0), -> num(1).times(0)

   check num(7), -> num(98).gcd 21
   check num(7), -> num(77777).gcd 21
@@ -59,109 +67,7 @@
   check num('123456789000006789'), -> a.plus -123450000
   check num(0),                    -> a.minus a

-
 describe "A number", ->
-  describe "with value 0", ->
-    a = num 0
-
-    it "should have sign zero", ->
-      expect(a.sgn()).toEqual 0
-
-    it "can be compared with 0", ->
-      expect(a.cmp(0)).toBe 0
-
-
-  describe "with value 1", ->
-    a = num 1
-
-    it "should have a positive sign", ->
-      expect(a.sgn()).toEqual 1
-
-    it "can be compared with 0", ->
-      expect(a.cmp(0)).toBeGreaterThan 0
-
-    it "can have the gcd taken with itself", ->
-      expect(a.gcd(a).toString()).toEqual '1'
-
-    it "can be multiplied by 0", ->
-      expect(a.times(0).toString()).toEqual '0'
-
-
-  describe "with value 12345679", ->
-    a = num 12345679
-
-    it "can have its sign changed", ->
-      expect(a.neg().toString()).toEqual '-12345679'
-
-    it "can have its absolute value taken", ->
-      expect(a.abs().toString()).toEqual '12345679'
-      expect(a.neg().abs().toString()).toEqual '12345679'
-
-    it "can have its sign determined", ->
-      expect(a.sgn()).toBe 1
-      expect(a.neg().sgn()).toBe -1
-
-    it "can be compared with an integer", ->
-      expect(a.cmp(12345680)).toBeLessThan 0
-
-    it "can be compared with 0", ->
-      expect(a.cmp(0)).toBeGreaterThan 0
-
-    it "can be compared with another number", ->
-      expect(a.cmp(num 12345680)).toBeLessThan 0
-
-    it "can have an integer added to it", ->
-      expect(a.plus(87654320).toString()).toEqual '99999999'
-
-    it "can have another number added to it", ->
-      expect(a.plus(num 87654320).toString()).toEqual '99999999'
-
-    it "can have an integer subtracted from it", ->
-      expect(a.minus(4445).toString()).toEqual '12341234'
-
-    it "can have another number subtracted from it", ->
-      expect(a.minus(num 4445).toString()).toEqual '12341234'
-
-    it "can be multiplied with an integer", ->
-      expect(a.times(9000000009).toString()).toEqual '111111111111111111'
-
-    it "can be multiplied with another number", ->
-      expect(a.times(num 9000000009).toString())
-        .toEqual '111111111111111111'
-
-    it "can be multiplied by 0", ->
-      expect(a.times(0).toString()).toEqual '0'
-
-    it "can be negated and then multiplied by 0", ->
-      expect(a.neg().times(0).toString()).toEqual '0'
-
-    it "can be divided by an integer", ->
-      expect(a.idiv(343).toString()).toEqual '35993'
-
-    it "can be divided by another number", ->
-      expect(a.idiv(num 343).toString()).toEqual '35993'
-
-    it "can be taken modulo an integer", ->
-      expect(a.mod(343).toString()).toEqual '80'
-
-    it "can be taken modulo another number", ->
-      expect(a.mod(num 343).toString()).toEqual '80'
-
-    it "can be taken to an integer power", ->
-      expect(a.pow(5).toString()).toEqual '286797197645258138803977054387424399'
-      expect(a.pow(4).toString()).toEqual '23230573032496482275618623681'
-
-    it "can be taken to a number power", ->
-      expect(a.pow(num 5).toString())
-        .toEqual '286797197645258138803977054387424399'
-      expect(a.pow(num 4).toString())
-        .toEqual '23230573032496482275618623681'
-
-    it "can be taken the square root of", ->
-      expect(a.isqrt().toString()).toEqual '3513'
-      expect(a.pow(4).isqrt().toString()).toEqual a.pow(2).toString()
-
-
   describe """If a, b and c are long integers with
               a = -1e12 + 5001, b = 5001 and c = 1e12 - 5000""", ->
     a = num -1e12 + 5001


Much better to pass it with options, add hitenterview for about edit

--- client/app/CommonViews/FormViews.coffee
+++ client/app/CommonViews/FormViews.coffee
@@ -146,18 +146,19 @@
     defaultPlaceHolder = "You haven't entered anything in your bio yet. Why not add something now?"

     @aboutInput = new KDInputView
-      cssClass      : 'about editable'
+      cssClass      : 'about editable hitenterview active'
       type          : 'textarea'
       defaultValue  : if profile.about is defaultPlaceHolder then '' else Encoder.htmlDecode profile.about
       placeholder   : if profile.about isnt defaultPlaceHolder then null else Encoder.htmlDecode profile.about
       name          : 'about'

     @aboutInfo = new PersonalAboutView
-      tooltip     :
-        title     : "Click to edit"
-        placement : "left"
-        offset    : 5
-    , @memberData, defaultPlaceHolder
+      tooltip            :
+        title            : "Click to edit"
+        placement        : "left"
+        offset           : 5
+      defaultPlaceHolder : defaultPlaceHolder
+    , @memberData

     @windowController = @getSingleton 'windowController'

@@ -193,10 +194,10 @@
         @unsetClass 'active'

 class PersonalAboutView extends KDCustomHTMLView
-  constructor:(options, data, defaultPlaceHolder)->
+  constructor:(options, data)->
     super
     {profile} = @getData()
-    profile.about or= defaultPlaceHolder
+    profile.about or= options.defaultPlaceHolder

   viewAppended:->
     super


[fix] fix call to confirmDelete

[fix] fix call to confirmDelete

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -77,7 +77,7 @@

   delete: (e)->
     e?.preventDefault()
-    if confirmDelete(@resource)
+    if @confirmDelete(@resource)
       $(@el).removeClass('editable').addClass('deleted')

       # !todo: upgrade backbone, use {wait: true}


[skip] Blurred any focused input element before replacing the edit mode with the view mode (3m)

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -48,6 +48,7 @@
     e?.preventDefault()
     e?.stopImmediatePropagation()
     if @inEdit()
+      @$el.find(':focus').blur()
       @$el.removeClass('editor').addClass('neat-editable')
       @render()
       @trigger('edit:end')


add textInput event factory #270

add textInput event factory #270

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -58,6 +58,17 @@
   key = ' ' if key is 'space'
   buildKeydownEvent(key, options)

+buildTextInputEvent = (key) ->
+  eventArgs = [
+    true # bubbles
+    true # cancelable
+    window # view
+    key  # key char
+  ]
+  event = document.createEvent('TextEvent')
+  event.initTextEvent("textInput", eventArgs...)
+  event
+
 getHiddenInputElementForEditor = (editor) ->
   editorElement = atom.views.getView(editor)
   editorElement.component.hiddenInputComponent.getDomNode()
@@ -76,7 +87,7 @@
   event = buildKeydownEventFromKeystroke(key, target)
   atom.keymaps.handleKeyboardEvent(event)

-  # if not event.defaultPrevented and key isnt 'DUMMY'
+  # unless event.defaultPrevented
   #   editor = atom.workspace.getActiveTextEditor()
   #   target = getHiddenInputElementForEditor(editor)
   #   char = ' ' if key is 'space'


JUser::authenticateWithOauth: handle error from ::fetchUser

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -801,7 +801,8 @@
                 Account is already linked with another user.
               """
           else
-            @fetchUser client, (err, user)=>
+            @fetchUser client, (err, user) =>
+              return callback createKodingError err.message  if err
               @persistOauthInfo user.username, sessionToken, kallback
         else
           if user


refactored verifyUser function

refactored verifyUser function

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1215,7 +1215,7 @@
       callback()

-  verifyUser = (options, queue, callback) ->
+  verifyUser = (options, callback) ->

     { slug
       email
@@ -1223,22 +1223,20 @@
       userFormData
       foreignAuthType } = options

-    invitation = null
-
-    _queue = [
+    queue = [

       ->
         # verifying recaptcha if enabled
-        return _queue.next()  unless KONFIG.recaptcha.enabled
+        return queue.next()  unless KONFIG.recaptcha.enabled

         JUser.verifyRecaptcha recaptcha, { foreignAuthType, slug }, (err) ->
           return callback err  if err
-          _queue.next()
+          queue.next()

       ->
         JUser.validateAll userFormData, (err) ->
           return callback err  if err
-          _queue.next()
+          queue.next()

       ->
         JUser.emailAvailable email, (err, res) ->
@@ -1248,14 +1246,14 @@
           if res is no
             return callback new KodingError 'Email is already in use!'

-          _queue.next()
-
-      ->
-        queue.next()
+          queue.next()
+
+      ->
+        callback null

     ]

-    daisy _queue
+    daisy queue

   verifyEnrollmentEligibility = (options, queue, callback, fetchData) ->
@@ -1478,13 +1476,9 @@
           queue.next()

       ->
-        verifyUser {
-          slug
-          email
-          recaptcha
-          userFormData
-          foreignAuthType
-        }, queue, callback
+        verifyUser { slug, email, recaptcha, userFormData, foreignAuthType }, (err) ->
+          return callback err  if err
+          queue.next()

       ->
         verifyEnrollmentEligibility {


Social: change text for banned users

Social: change text for banned users

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -296,12 +296,14 @@
       if user.blockedUntil and user.blockedUntil > new Date
         toDate = user.blockedUntil.toUTCString()
         message = """
-            You cannot login until #{toDate}.
-            At least 10 moderators of Koding have decided that your participation is not of acceptable kind.
-            That's all I know.
-            You can demand further explanation from ban@koding.com. Please allow 1-2 days to receive a reply.
-            Your machines might be blocked, all types of activities might be suspended.
-            Your data is safe, you can access them when/if ban is lifted.
+Account suspended due to violation of our acceptable use policy.
+
+Hello,
+This account has been put on suspension by Koding moderators due to a violation of our acceptable use policy. The ban will be in effect until #{toDate} at which time you will be able to log back in again. Should you have any questions regarding this ban, please write to ban@koding.com and allow 2-3 business days for us to research and reply. Even though your account is banned, all your data is safe and will be accessible once the ban lifts.
+
+Please note, repeated violations of our acceptable use policy will result in the permanent deletion of your account.
+
+Team Koding
           """
         callback createKodingError message
       else


email changed notification

email changed notification

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -648,7 +648,7 @@
 Your password has been changed!  If you didn't request this change, please contact support@koding.com immediately!
 """
       }
-      email.save ()->
+      email.save()

   @changeEmail = secure (client,options,callback)->

@@ -664,6 +664,14 @@
         @fetchUser client, (err,user)->
           account = client.connection.delegate
           user.changeEmail account, options, callback
+          email = new JMail {
+            email: user.email
+            subject : "Your email has changed"
+            content : """
+    Your email has been changed!  If you didn't request this change, please contact support@koding.com immediately!
+    """
+          }
+          email.save()

   @emailAvailable = (email, callback)->
     @count {email}, (err, count)->


Remove tmp file after target file saved.

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -103,8 +103,9 @@
           callback err
         else
           @executeCommand {username, command:"cp #{tmpPath} #{path}"}, (err,res)->
+            fs.unlink tmpPath
             unless err
-              callback? null,path
+              callback? null, path
             else
               callback? "[ERROR] can't upload file : #{err}"


Improve description of new test

Improve description of new test

--- test/spec/gitlab_spec.coffee
+++ test/spec/gitlab_spec.coffee
@@ -736,7 +736,7 @@
         expect(lastAjaxCallData().ref_name).toEqual("slave")
       )

-      it("should call correct URL with default per_page parameter of 100", ->
+      it("should override per_page default with maximum allowed value when not supplied", ->
         spyOnAjax()
         tree = new gitlab.Tree([]
         ,


Tidy up species name options

Tidy up species name options

--- project.coffee
+++ project.coffee
@@ -61,16 +61,12 @@
         color: '#333'
         details:[{
           type: 'text'
-          key: 'common'
+          key: 'subject'
           choices:[{
             value: ''
             key: 'common'
             label: 'Common Name'
-          }]
-        },{
-          type: 'text'
-          key: 'scientific'
-          choices:[{
+          },{
             value: ''
             key: 'scientific'
             label: 'Scientific Name'


fixing user opinions

fixing user opinions

Former-commit-id: 35260ee542f1d9d124ea6da5f3e5191fb86478f0 [formerly 58fb9536f96cd07a0d4756aa1ea46dfc4e3cbe9c] [formerly 6217fc27cd02e862b305555ff6a69316e1c4f1ed [formerly 880fee34930310d8df76ef19d236fc762698ebfc]] [formerly e7361f1e73f771d14f76a2b949b0e360a1ab6672 [formerly 3ce681480fdafa7e8ead9d867296759dcab678a9] [formerly 70e7f554d40305abe22a1d2ef4e581c6158338bb [formerly 70e7f554d40305abe22a1d2ef4e581c6158338bb [formerly b89d064ac1221ad7680acdf734da9ebde8a10f13]]]]
Former-commit-id: 8a3877784ecf26545a9ea65044e85c01e4b8bafb [formerly 409a3040e4a1a76433759eb47f4580c2ad11af13] [formerly 091d8c756ed72d4cfa9b8f681c214989a3712d27 [formerly 7f50e8365390b27fad72feaca4ebde1313d6d156]]
Former-commit-id: 8e51a69940aff77fa0343944102298f4663a0f07 [formerly d469e55417b4e2c6df4aaec379bb33a5caf0220a]
Former-commit-id: 93bba6f66f492eb0df711f1ad18fefa46b9fbba9
Former-commit-id: d41962768eb2295761448204144b49446f8b1281

--- @client/modules/franklin/franklin_app.coffee
+++ @client/modules/franklin/franklin_app.coffee
@@ -180,7 +180,7 @@

         user = App.request 'user', parseInt(user_id)
         opinion = App.request('opinions:get').findWhere {long_id : long_id, user_id : user.id }
-        new Franklin.Opinion.OpinionController
+        new Franklin.UserOpinion.UserOpinionController
           model : opinion
           region: new Backbone.Marionette.Region
             el: $("body")


remove call to seal()

remove call to seal()

--- code/serv.coffee
+++ code/serv.coffee
@@ -134,7 +134,6 @@
         create_user #{box_name} &&
         create_user_directories #{box_name} &&
         furnish_box #{box_name} &&
-        seal_box #{box_name} &&
         mkdir /opt/cobalt/etc/sshkeys/#{box_name}
         """
   # insecure - sanitise box_name


Reply with stack too

Reply with stack too

--- scripts/boomtown.coffee
+++ scripts/boomtown.coffee
@@ -13,7 +13,6 @@
   robot.respond /(boo+m)(?: (emit with(?:out)? msg|timeout|throw))?/i, (msg) ->
     boom = msg.match[1]
     how = msg.match[2]
-    err = boomError(boom, how)

     switch msg.match[1]
       when 'emit with msg'
@@ -32,4 +31,4 @@
     robot.logger.error "BOOM"

     if msg?
-      msg.reply "BOOM"
+      msg.reply err.stack, "BOOM"


Auto add link

Auto add link

--- server/routes/post.coffee
+++ server/routes/post.coffee
@@ -20,7 +20,7 @@
         tagline: tagline
         link: link
         posterUser: userId
-        # displayDay: new Date()
+        displayDay: new Date()

       newProduct.save (err, product) ->
         res.redirect '/'


Fixing 'borderColor' not being propagated to css.

--- framer/LayerStyle.coffee
+++ framer/LayerStyle.coffee
@@ -158,6 +158,6 @@
 		return layer._properties.borderRadius + "px"

 	border: (layer) ->
-		return "#{layer._properties.borderWidth}px solid #{layer._properties.borderWidth}"
+		return "#{layer._properties.borderWidth}px solid #{layer._properties.borderColor}"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -84,6 +84,9 @@
   equals player.I.facing.x, Point(-1, -1).norm().x
   equals player.I.facing.y, Point(-1, -1).norm().y

+  window.keydown.left = false
+  window.ke
+
 test "should correctly track facing direction", ->
   ok player.I.facing.equal(Point(1, 0))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -27,7 +27,7 @@
 test "should move player up while key is pressed", ->
   ;

-test "should move player"
+test "should move player up while"

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -7,5 +7,6 @@
     width: 10
     height: 20

+  player.

 module()


Properly name test descriptions

Properly name test descriptions

--- test/entity-store.coffee
+++ test/entity-store.coffee
@@ -18,7 +18,7 @@
 		expect instance.get 'test'
 		.to.equal 'test'

-	it 'be able to set and get an item', () ->
+	it 'should be able to set and get an item', () ->

 		instance = new class TestEntityStore extends EntityStore
 			initialize: () ->
@@ -140,7 +140,7 @@
 		expect instance.getItems()
 		.to.be.equal instance.getItems()

-	it 'should return another map when an item changed', () ->
+	it 'should return a new map when an item changed', () ->

 		instance = new class TestEntityStore extends EntityStore
 			initialize: () ->


Matrix copy constructor

Matrix copy constructor

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -49,6 +49,9 @@
   @constructor
   ###
   Matrix = (a, b, c, d, tx, ty) ->
+    if Object.isObject(a)
+      {x, y} = a
+
     __proto__: Matrix::
     ###*
     @name a


we index from 0 here, folks

we index from 0 here, folks

--- test/suite_runner.coffee
+++ test/suite_runner.coffee
@@ -48,10 +48,10 @@
   run_attribute: ({constructor, context, attribute, attribute_suite, test_number}) ->
     context.test attribute, (context) =>
       if test_number
-        if suite = attribute_suite[parseInt(test_number) - 1]
+        if suite = attribute_suite[parseInt(test_number)]
           @run_subsuite({constructor, context, suite})
         else
-          console.log "Usage error: #{attribute} only has #{attribute_suite.length + 1} tests"
+          console.log "Usage error: #{attribute} only has #{attribute_suite.length} tests"
           process.exit()
       else
         for suite, i in attribute_suite


Fixed number trim when wrapping abbreviation

--- lib/editor-proxy.coffee
+++ lib/editor-proxy.coffee
@@ -32,7 +32,6 @@

 # Proprocess text data that should be used as snippet content
 # Currently, Atom’s snippets implementation has the following issues:
-# * supports $N or ${N:placeholder} notation, but not ${N}
 # * multiple $0 are not treated as distinct final tabstops
 preprocessSnippet = (value) ->
   order = []
@@ -52,7 +51,7 @@
         # recursively update nested tabstops
         placeholder = tabStops.processText(placeholder, tabstopOptions)

-      if placeholder then "${#{group}:#{placeholder}}" else "$#{group}"
+      if placeholder then "${#{group}:#{placeholder}}" else "${#{group}}"

     escape: (ch) ->
       if ch == '$' then '\\$' else ch


Add cron jobs for support time

Add cron jobs for support time

--- config/jobs.cson
+++ config/jobs.cson
@@ -35,3 +35,19 @@
     「目標達成」と話しかけてくださいね。",
     "もう夕方ですね、今日はどんな事をしましたか？ @everyone\n
     目標達成って叫んでも良いですよ！"]
+
+support_start:
+  time: "0 10 * * 1-5"
+  room: '#support'
+  message: [
+    "サポート開始しましょう！ @channel"
+    "サポートとは、漢道である！うぬら、始められいぃぃっ！！ @channel"
+    "Let's support! ウィ　ムッシュ〜〜 :feelsgood: @channel"]
+
+support_end:
+  time: "0 12 * * 1-5"
+  room: '#support'
+  message: [
+    "サポート終了時間です。お疲れ様でした @channel",
+    "サポートの道を究めんとする、ぬしら、漢よのう・・・ご苦労！！ @channel",
+    "Nice support!! ウィ　マドモワゼル〜〜 :panda_face: @channel"]


Remove World Cup script

Remove World Cup script

This reverts commit 4b50d0078415ae4a56e76253ba4abf783ff4c6cf.

--- scripts/worldcup.coffee
+++ scripts/worldcup.coffee
@@ -1,28 +0,0 @@
-# Description:
-#   World Cup 2014 matches and scores
-#
-# Commands:
-#   hubot world cup schedule - World Cup schedule for today
-#   hubot world cup score - Match scores
-
-module.exports = (robot) ->
-  robot.respond /world cup( schedule| matches)?/i, (msg) ->
-    robot.http('http://worldcup.sfg.io/matches/today')
-      .get() (err, res, body) ->
-        unless err? or res.statusCode isnt 200
-          data = JSON.parse(body)
-          data.forEach (match) ->
-            homeTeam = match.home_team.country
-            awayTeam = match.away_team.country
-            time = (new Date(match.datetime)).toTimeString()
-            msg.send "#{homeTeam} v. #{awayTeam} @ #{time}"
-
-  robot.respond /world cup scores?/i, (msg) ->
-    robot.http('http://worldcup.sfg.io/matches/current')
-      .get() (err, res, body) ->
-        unless err? or res.statusCode isnt 200
-          data = JSON.parse(body)
-          data.forEach (match) ->
-            homeTeam = "#{match.home_team.country} #{match.home_team.goals}"
-            awayTeam = "#{match.away_team.goals} #{match.away_team.country}"
-            msg.send "#{homeTeam} - #{awayTeam}"


:lipstick: Fix comment

:lipstick: Fix comment

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -60,7 +60,7 @@
     @on 'mousedown', '.minimap-visible-area', @onDragStart

     @subscribe @paneView.model.$activeItem, @onActiveItemChanged
-    # Fixed item move to other pane.
+    # Fix items movin to another pane.
     @subscribe @paneView.model, 'item-removed', (item) -> item.off? '.minimap'

     @subscribe @miniEditorView, 'minimap:updated', @updateMinimapView


Implement correct indicator dragging

Implement correct indicator dragging

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -266,6 +266,9 @@
     @isClicked = true
     e.preventDefault()
     e.stopPropagation()
+    # compute distance between indicator top and where it has been grabbed
+    y = e.pageY - @offsetTop
+    @grabY = y - (@indicator.y + @indicator.scroller.y)
     @on 'mousemove.visible-area', @onMove

   onMove: (e) =>
@@ -279,11 +282,9 @@
     # The logic for dragging the scroller is a bit different
     # than for a single click.
     # Here we have to compensate for the minimap scroll
-
-    # TODO: need to center the scroller where the mouse has grabbed it
     y = e.pageY - @offsetTop
-    top = y / @indicator.wrapper.height * @indicator.scroller.height/ @scaleY
-    @editorView.scrollTop(top)
+    top = (y-@grabY) * (@indicator.scroller.height-@indicator.height) / (@indicator.wrapper.height-@indicator.height)
+    @editorView.scrollTop(top / @scaleY)

   # OTHER PRIVATE METHODS


Rename _deserialize to handleJediToolsResponse

--- lib/python-tools.coffee
+++ lib/python-tools.coffee
@@ -65,14 +65,14 @@
         )

     @readline = require('readline').createInterface(input: @provider.stdout)
-    @readline.on 'line', (response) => @_deserialize(response)
+    @readline.on 'line', (response) => @handleJediToolsResponse(response)

   deactivate: ->
     @subscriptions.dispose()
     @readline.close()
     @provider.kill()

-  _deserialize: (response) ->
+  handleJediToolsResponse: (response) ->
     console.log "tools.py => #{response}"

     response = JSON.parse(response)


fix direct room name

fix direct room name

--- packages/rocketchat-ui/views/app/room.coffee
+++ packages/rocketchat-ui/views/app/room.coffee
@@ -72,8 +72,12 @@
 			return project_info.name
 		roomData = Session.get('roomData' + this._id)
 		return '' unless roomData
-
-		return RocketChat.roomTypes.getRoomName roomData?.t, roomData
+		roomname = RocketChat.roomTypes.getRoomName roomData?.t, roomData
+		user = Meteor.users.findOne({username:roomname})
+		if user
+			return user?.name
+		else
+			return roomname

 	roomTopic: ->
 		roomData = Session.get('roomData' + this._id)


Destructure tag to position

Destructure tag to position

--- lib/symbols-view.coffee
+++ lib/symbols-view.coffee
@@ -47,7 +47,7 @@
         position: editor.getCursorBufferPosition()
         file: editor.getUri()

-    position = tag.position
+    {position} = tag
     position = @getTagLine(tag) unless position
     if tag.file
       atom.workspaceView.open(tag.file).done =>


:fire: Remove jQuery from ::removeDropTargetClasses

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -419,9 +419,12 @@
       @isItemMovingBetweenPanes = false

   removeDropTargetClasses: ->
-    workspaceElement = $(atom.views.getView(atom.workspace))
-    workspaceElement.find('.tab-bar .is-drop-target').removeClass 'is-drop-target'
-    workspaceElement.find('.tab-bar .drop-target-is-after').removeClass 'drop-target-is-after'
+    workspaceElement = atom.views.getView(atom.workspace)
+    for dropTarget in workspaceElement.querySelectorAll('.tab-bar .is-drop-target')
+      dropTarget.classList.remove('is-drop-target')
+
+    for dropTarget in workspaceElement.querySelectorAll('.tab-bar .drop-target-is-after')
+      dropTarget.classList.remove('drop-target-is-after')

   getDropTargetIndex: (event) ->
     target = $(event.target)


Button changes

Button changes

--- lib/ui-demo-view.coffee
+++ lib/ui-demo-view.coffee
@@ -97,57 +97,70 @@

         @h2 'Standalone buttons'
         @exampleCode '''
-          @p =>
+          @div class: 'block', =>
             @button class: 'btn', 'Button'
-          @p =>
+          @div class: 'block', =>
+            @button class: 'btn btn-xs', 'Extra Small Button'
+          @div class: 'block', =>
             @button class: 'btn btn-sm', 'Small Button'
-          @p =>
+          @div class: 'block', =>
             @button class: 'btn btn-lg', 'Large Button'
         '''

         @h2 'Button Groups'
         @exampleCode '''
-          @div =>
+          @div class: 'block', =>
+            @div 'Normal size'
             @div class: 'btn-group', =>
               @button class: 'btn', 'One'
               @button class: 'btn', 'Two'
               @button class: 'btn', 'Three'
-          @div =>
+          @div class: 'block', =>
+            @div 'Extra Small'
+            @div class: 'btn-group btn-group-xs', =>
+              @button class: 'btn', 'One'
+              @button class: 'btn', 'Two'
+              @button class: 'btn', 'Three'
+          @div class: 'block', =>
+            @div 'Small'
             @div class: 'btn-group btn-group-sm', =>
               @button class: 'btn', 'One'
               @button class: 'btn', 'Two'
               @button class: 'btn', 'Three'
-          @div =>
+          @div class: 'block', =>
+            @div 'Large'
             @div class: 'btn-group btn-group-lg', =>
               @button class: 'btn', 'One'
               @button class: 'btn', 'Two'
               @button class: 'btn', 'Three'
         '''

-        @h2 'Button Toggle Groups'
-        @exampleCode '''
-          @div =>
-            @div class: 'btn-group btn-group-toggle', =>
+        @h2 'Selected buttons'
+        @p => @raw 'Buttons can be marked selected by adding a <code>.selected</code> class. Useful for toggle groups.'
+        @exampleCode '''
+          @div class: 'block', =>
+            @div class: 'btn-group', =>
               @button class: 'btn selected', 'One'
               @button class: 'btn', 'Two'
               @button class: 'btn', 'Three'
-          @div =>
-            @div class: 'btn-group btn-group-toggle', =>
+          @div class: 'block', =>
+            @div class: 'btn-group', =>
               @button class: 'btn', 'One'
               @button class: 'btn selected', 'Two'
               @button class: 'btn', 'Three'
-          @div =>
-            @div class: 'btn-group btn-group-toggle', =>
+          @div class: 'block', =>
+            @div class: 'btn-group', =>
               @button class: 'btn', 'One'
               @button class: 'btn', 'Two'
               @button class: 'btn selected', 'Three'
-          @div =>
-            @div class: 'btn-group btn-group-toggle', =>
+          @div class: 'block', =>
+            @div class: 'btn-group', =>
               @button class: 'btn selected', 'One'
               @button class: 'btn selected', 'Two'
               @button class: 'btn', 'Three'
         '''

+      ###
       @section class: 'bordered', 'data-name': 'tool-panel', =>
         @h1 class: 'section-heading', 'Tool Panel'
         @p 'A container attached to some side of the Atom UI. This UI Demo is in a tool panel.'


Translated 'menu_win32.cson' to Hebrew

Translated 'menu_win32.cson' to Hebrew

--- def/he/menu_win32.cson
+++ def/he/menu_win32.cson
@@ -1,319 +1,319 @@
 Menu:
   "&File":
-    value: "&File"
+    value: "&קובץ"
     submenu:
       "New &Window":
-        value: "New &Window"
+        value: "חלון &חדש"
       "&New File":
-        value: "&New File"
+        value: "קובץ &חדש"
       "&Open File…":
-        value: "&Open File…"
+        value: "&פתח קובץ"
       "Open Folder…":
-        value: "Open Folder…"
+        value: "פתח תיקייה"
       "Add Project Folder…":
-        value: "Add Project Folder…"
+        value: "הוסף תיקיית פרוייקט"
       "Reopen Project":
-        value: "Reopen Project"
+        value: "פתח פרוייקט מחדש"
         submenu:
           "Clear Project History":
-            value: "Clear Project History"
+            value: "נקה היסטוריית פרוייקט"
       "Reopen Last &Item":
-        value: "Reopen Last &Item"
+        value: "פתח מחדש &פריט אחרון"
       "Se&ttings":
-        value: "Se&ttings…"
+        value: "הג&דרות"
       "Config…":
-        value: "Config…"
+        value: "הגדר"
       "Init Script…":
-        value: "Init Script…"
+        value: "סקריפט התחלתי"
       "Keymap…":
-        value: "Keymap…"
+        value: "קובץ קיצור המקלדת"
       "Snippets…":
-        value: "Snippets…"
+        value: "תגזירים"
       "Stylesheet…":
-        value: "Stylesheet…"
+        value: "גליון סגנונות"
       "&Save":
-        value: "&Save"
+        value: "&שמור"
       "Save &As…":
-        value: "Save &As…"
+        value: "שמור &בשם"
       "Save A&ll":
-        value: "Save A&ll"
+        value: "שמור &הכל"
       "&Close Tab":
-        value: "&Close Tab"
+        value: "&סגור לשונית"
       "Close &Pane":
-        value: "Close &Pane"
+        value: "סגור &חלונית"
       "Clos&e Window":
-        value: "Clos&e Window"
+        value: "סגור &חלון"
       "E&xit":
-        value: "E&xit"
+        value: "צ&א"
       "Close All Tabs":
-        value: "Close All Tabs"
+        value: "סגור את כל הכרטיסיות"
   "&Edit":
-    value: "&Edit"
+    value: "&ערוך"
     submenu:
       "&Undo":
-        value: "&Undo"
+        value: "&בטל"
       "&Redo":
-        value: "&Redo"
+        value: "&בצע מחדש"
       "Cu&t":
-        value: "Cu&t"
+        value: "גז&ור"
       "&Copy":
-        value: "&Copy"
+        value: "&העתק"
       "Copy Pat&h":
-        value: "Copy Pat&h"
+        value: "העתק נ&תיב"
       "&Paste":
-        value: "&Paste"
+        value: "&הדבק"
       "Paste Without Reformatting":
-        value: "Paste Without Reformatting"
+        value: "הדבק ללא עיצוב מחדש"
       "Select &All":
-        value: "Select &All"
+        value: "בחר &הכל"
       "&Toggle Comments":
-        value: "&Toggle Comments"
+        value: "&הפעל/כבה תגובות"
       Lines:
-        value: "Lines"
+        value: "שורות"
         submenu:
           "&Indent":
-            value: "&Indent"
+            value: "&הזחה"
           "&Outdent":
-            value: "&Outdent"
+            value: "&הזחה החוצה"
           "&Auto Indent":
-            value: "&Auto Indent"
+            value: "&הזחה אוטומטית"
           "Move Line &Up":
-            value: "Move Line &Up"
+            value: "העבר שורה &מעלה"
           "Move Line &Down":
-            value: "Move Line &Down"
+            value: "העבר שורה &מטה"
           "Du&plicate Lines":
-            value: "Du&plicate Lines"
+            value: "&שכפל שורות"
           "D&elete Line":
-            value: "D&elete Line"
+            value: "&מחק שורה"
           "&Join Lines":
-            value: "&Join Lines"
+            value: "&חבר שורות יחד"
       Columns:
-        value: "Columns"
+        value: "עמודות"
         submenu:
           "Move Selection &Left":
-            value: "Move Selection &Left"
+            value: "הזז בחירה &שמאלה"
           "Move Selection &Right":
-            value: "Move Selection &Right"
+            value: "הזז בחירה &ימינה"
       Text:
-        value: "Text"
+        value: "טקסט"
         submenu:
           "&Upper Case":
-            value: "&Upper Case"
+            value: "&אותיות גדולות"
           "&Lower Case":
-            value: "&Lower Case"
+            value: "&אותיות קטנות"
           "Delete to End of &Word":
-            value: "Delete to End of &Word"
+            value: "מחק עד סוף &המילה"
           "Delete to Previous Word Boundary":
-            value: "Delete to Previous Word Boundary"
+            value: "מחק עד סוף המילה הקודמת"
           "Delete to Next Word Boundary":
-            value: "Delete to Next Word Boundary"
+            value: "מחק עד סוף המילה הבאה"
           "&Delete Line":
-            value: "&Delete Line"
+            value: "&מחק שורה"
           "&Transpose":
             value: "&Transpose"
       Folding:
-        value: "Folding"
+        value: "קיפול"
         submenu:
           "&Fold":
-            value: "&Fold"
+            value: "&קפל"
           "&Unfold":
-            value: "&Unfold"
+            value: "&בטל קיפול"
           "Unfold &All":
-            value: "Unfold &All"
+            value: "בטל קיפול &להכל"
           "Fol&d All":
-            value: "Fol&d All"
+            value: "קפל &הכל"
           "Fold Level 1":
-            value: "Fold Level 1"
+            value: "קיפול שלב 1"
           "Fold Level 2":
-            value: "Fold Level 2"
+            value: "קיפול שלב 2"
           "Fold Level 3":
-            value: "Fold Level 3"
+            value: "קיפול שלב 3"
           "Fold Level 4":
-            value: "Fold Level 4"
+            value: "קיפול שלב 4"
           "Fold Level 5":
-            value: "Fold Level 5"
+            value: "קיפול שלב 5"
           "Fold Level 6":
-            value: "Fold Level 6"
+            value: "קיפול שלב 6"
           "Fold Level 7":
-            value: "Fold Level 7"
+            value: "קיפול שלב 7"
           "Fold Level 8":
-            value: "Fold Level 8"
+            value: "קיפול שלב 8"
           "Fold Level 9":
-            value: "Fold Level 9"
+            value: "קיפול שלב 9"
       "Reflow Selection":
-        value: "Reflow Selection"
+        value: "בחר מחדש"
       Bookmark:
-        value: "Bookmark"
+        value: "סימניה"
         submenu:
           "View All":
-            value: "View All"
+            value: "הצג הכל"
           "Toggle Bookmark":
-            value: "Toggle Bookmark"
+            value: "הפעל/כבה סימניה"
           "Jump to Next Bookmark":
-            value: "Jump to Next Bookmark"
+            value: "קפוץ לסימניה הבאה"
           "Jump to Previous Bookmark":
-            value: "Jump to Previous Bookmark"
+            value: "קפוץ לסימניה הקודמת"
           "Select to Next Bookmark":
-            value: "Select to Next Bookmark"
+            value: "בחר עד הסימנייה הבאה"
           "Select to Previous Bookmark":
-            value: "Select to Previous Bookmark"
+            value: "בחר עד הסימניה הקודמת"
       "Select Encoding":
-        value: "Select Encoding"
+        value: "בחר קידוד"
       "Go to Line":
-        value: "Go to Line"
+        value: "לך לשורה"
       "Select Grammar":
-        value: "Select Grammar"
+        value: "בחר דקדוק"
   "&View":
-    value: "&View"
+    value: "&מראה"
     submenu:
       "Toggle &Full Screen":
-        value: "Toggle &Full Screen"
+        value: "הפעל/כבה &מסך מלא"
       "Toggle Menu Bar":
-        value: "Toggle Menu Bar"
+        value: "הפעל/כבה שורת תפריט"
       Panes:
-        value: "Panes"
+        value: "חלוניות"
         submenu:
           "Split Up":
-            value: "Split Up"
+            value: "פצל מעלה"
           "Split Down":
-            value: "Split Down"
+            value: "פצל מטה"
           "Split Left":
-            value: "Split Left"
+            value: "פצל שמאלה"
           "Split Right":
-            value: "Split Right"
+            value: "פצל ימינה"
           "Focus Next Pane":
-            value: "Focus Next Pane"
+            value: "התפקס על החלונית הבאה"
           "Focus Previous Pane":
-            value: "Focus Previous Pane"
+            value: "התפקס על החלונית הקודמת"
           "Focus Pane Above":
-            value: "Focus Pane Above"
+            value: "התפקס על החלונית מלמעלה"
           "Focus Pane Below":
-            value: "Focus Pane Below"
+            value: "התפקס על החלונית מלמטה"
           "Focus Pane On Left":
-            value: "Focus Pane On Left"
+            value: "התפקס על החלונית משמאל"
           "Focus Pane On Right":
-            value: "Focus Pane On Right"
+            value: "התפקס על החלונית מימין"
           "Close Pane":
-            value: "Close Pane"
+            value: "סגור חלונית"
       Developer:
-        value: "Developer"
+        value: "מפתח"
         submenu:
           "Open In &Dev Mode…":
-            value: "Open In &Dev Mode…"
+            value: "פתח במצב &פיתוח"
           "&Reload Window":
-            value: "&Reload Window"
+            value: "&טען חלון מחדש"
           "Run Package &Specs":
-            value: "Run Package &Specs"
+            value: "הרץ &מפרט חבילות"
           "Run &Benchmarks":
-            value: "Run &Benchmarks"
+            value: "הרץ &בדיקות"
           "Toggle Developer &Tools":
-            value: "Toggle Developer &Tools"
+            value: "הפעל/כבה &אפשרויות פיתוח"
       "&Increase Font Size":
-        value: "&Increase Font Size"
+        value: "&הגדל גודל גופן"
       "&Decrease Font Size":
-        value: "&Decrease Font Size"
+        value: "&הקטן גודל גופן"
       "Re&set Font Size":
-        value: "Re&set Font Size"
+        value: "&אפס את גודל הגופן"
       "Toggle Soft &Wrap":
-        value: "Toggle Soft &Wrap"
+        value: "הפעל/כבה גלישת טקסט רכה"
       "Toggle Command Palette":
-        value: "Toggle Command Palette"
+        value: "הפעל/כבה לוח בקרה"
       "Toggle Tree View":
-        value: "Toggle Tree View"
+        value: "הפעל/כבה תצוגת עץ"
   "&Selection":
-    value: "&Selection"
+    value: "&בחירה"
     submenu:
       "Add Selection &Above":
-        value: "Add Selection &Above"
+        value: "הוסף סימון &מלמעלה"
       "Add Selection &Below":
-        value: "Add Selection &Below"
+        value: "הוסף סימון &מלמטה"
       "S&plit into Lines":
-        value: "S&plit into Lines"
+        value: "&פצל סימון לשורות"
       "Single Selection":
-        value: "Single Selection"
+        value: "בחירה יחידה"
       "Select to &Top":
-        value: "Select to &Top"
+        value: "בחר עד החלק &העליון"
       "Select to Botto&m":
-        value: "Select to Botto&m"
+        value: "בחר עד החלק &התחתון"
       "Select &Line":
-        value: "Select &Line"
+        value: "בחר &שורה"
       "Select &Word":
-        value: "Select &Word"
+        value: "בחר &מילה"
       "Select to Beginning of W&ord":
-        value: "Select to Beginning of W&ord"
+        value: "בחר עד תחילת &המילה"
       "Select to Beginning of L&ine":
-        value: "Select to Beginning of L&ine"
+        value: "בחר עד תחילת &השורה"
       "Select to First &Character of Line":
-        value: "Select to First &Character of Line"
+        value: "בחר עד &התו הראשון בשורה"
       "Select to End of Wor&d":
-        value: "Select to End of Wor&d"
+        value: "בחר עד סוף &המילה"
       "Select to End of Lin&e":
-        value: "Select to End of Lin&e"
+        value: "בחר עד סוף &השורה"
       "Select Inside Brackets":
-        value: "Select Inside Brackets"
+        value: "בחר בתוך סוגריים"
   "F&ind":
-    value: "F&ind"
+    value: "מ&צא"
     submenu:
       "Find in Buffer":
-        value: "Find in Buffer"
+        value: "מצא במאגר"
       "Replace in Buffer":
-        value: "Replace in Buffer"
+        value: "החלף במאגר"
       "Select Next":
-        value: "Select Next"
+        value: "בחר הבא"
       "Select All":
-        value: "Select All"
+        value: "בחר הכל"
       "Toggle Find in Buffer":
-        value: "Toggle Find in Buffer"
+        value: "הפעל/כבה מציאה במאגר"
       "Find in Project":
-        value: "Find in Project"
+        value: "מצא בפרוייקט"
       "Toggle Find in Project":
-        value: "Toggle Find in Project"
+        value: "הפעל/כבה מציאה בפרוייקט"
       "Find All":
-        value: "Find All"
+        value: "מצא הכל"
       "Find Next":
-        value: "Find Next"
+        value: "מצא הבא"
       "Find Previous":
-        value: "Find Previous"
+        value: "מצא קודם"
       "Replace Next":
-        value: "Replace Next"
+        value: "החלף הבא"
       "Replace All":
-        value: "Replace All"
+        value: "החלף הכל"
       "Clear History":
-        value: "Clear History"
+        value: "נקה היסטוריה"
       "Find Buffer":
-        value: "Find Buffer"
+        value: "מצא מאגר"
       "Find File":
-        value: "Find File"
+        value: "מצא קובץ"
       "Find Modified File":
-        value: "Find Modified File"
+        value: "מצא קובץ ששונה"
   "&Packages":
-    value: "&Packages"
+    value: "&חבילות"
   "&Help":
-    value: "&Help"
+    value: "&עזרה"
     submenu:
       "View &Terms of Use":
-        value: "View &Terms of Use"
+        value: "הצג &תנאי שימוש"
       "View &License":
-        value: "View &License"
+        value: "הצג &רישיון"
       "Restart and Install Update":
-        value: "Restart and Install Update"
+        value: "הפעל מחדש והתקן עדכון"
       "Check for Update":
-        value: "Check for Update"
+        value: "סרוק עבור עדכונים"
       "Checking for Update":
-        value: "Checking for Update"
+        value: "סורק עבור עדכונים"
       "Downloading Update":
-        value: "Downloading Update"
+        value: "מוריד עדכון"
       "&Documentation":
-        value: "&Documentation"
+        value: "&תיעוד"
       "Frequently Asked Questions":
-        value: "Frequently Asked Questions"
+        value: "שאלות נפוצות"
       "Community Discussions":
-        value: "Community Discussions"
+        value: "דיונים קהילתיים"
       "Report Issue":
-        value: "Report Issue"
+        value: "דווח על בעיה"
       "Search Issues":
-        value: "Search Issues"
+        value: "חפש בעיות"
       "About Atom":
-        value: "About Atom"
+        value: "אודות אטום"
       "Welcome Guide":
-        value: "Welcome Guide"
+        value: "מדריך למשתמש מתחיל"


add platform property to all tags completions

--- lib/viewProvider.coffee
+++ lib/viewProvider.coffee
@@ -141,6 +141,8 @@
         description : 'TSS id'
       class :
         description : 'TSS class'
+      platform :
+        description : 'Platform condition'

     for attribute of additionalAttr when not prefix or firstCharsEqual(attribute, prefix)
       completions.push(@buildAttributeCompletion(attribute))


Fixed possible bug, with coordinates. Again...?

--- controllers/game_controller.coffee
+++ controllers/game_controller.coffee
@@ -170,6 +170,10 @@
     checkCoordinates: (uLat, uLng, tLat, tLng, delta) ->
         return false unless delta > 0
         earthRadius = 6372.8 # Km
+        uLat =  parseFloat (uLat).replace(',', '.')
+        uLng = parseFloat (uLng).replace(',', '.')
+        tLat = parseFloat (tLat).replace(',', '.')
+        tLng = parseFloat (tLng).replace(',', '.')
         uLat = uLat/180 * Math.PI
         uLng = uLng/180 * Math.PI
         tLat = tLat/180 * Math.PI


Ensure section content is a string.

Ensure section content is a string.

--- src/prompt.coffee
+++ src/prompt.coffee
@@ -110,7 +110,7 @@
           results = results.join ''

         # Ensure the content is a string.
-        results = results.toString()
+        results = if results then results.toString() else ''

         # Strip newlines unless they're explicitly requested
         results = results.replace(/\n/g, '') unless section.allowNewlines


Refresh after loading file; Model/Editor renamings

--- src/Model/Editor.coffee
+++ src/Model/Editor.coffee
@@ -52,14 +52,19 @@
   # TODO: get version via build process / ENV variable?
   version: "0.4.1"

-  load: (jsonString) ->
+  loadJsonStringIntoProject: (jsonString) ->
     json = JSON.parse(jsonString)
     # TODO: If the file format changes, this will need to check the version
     # and convert or fail appropriately.
     if json.type == "Apparatus"
       @project = @serializer.dejsonify(json)

-  save: ->
+  loadJsonStringIntoProjectFromExternalSource: (jsonString) ->
+    @loadJsonStringIntoProject(jsonString)
+    @checkpoint()
+    Apparatus.refresh()  # HACK: calling Apparatus seems funky here.
+
+  getJsonStringOfProject: ->
     json = @serializer.jsonify(@project)
     json.type = "Apparatus"
     json.version = @version
@@ -77,14 +82,14 @@
   localStorageName: "apparatus"

   saveToLocalStorage: ->
-    jsonString = @save()
+    jsonString = @getJsonStringOfProject()
     window.localStorage[@localStorageName] = jsonString
     return jsonString

   loadFromLocalStorage: ->
     jsonString = window.localStorage[@localStorageName]
     if jsonString
-      @load(jsonString)
+      @loadJsonStringIntoProject(jsonString)

   resetLocalStorage: ->
     delete window.localStorage[@localStorageName]
@@ -95,13 +100,13 @@
   # ===========================================================================

   saveToFile: ->
-    jsonString = @save()
+    jsonString = @getJsonStringOfProject()
     fileName = @project.editingElement.label + ".json"
     Storage.saveFile(jsonString, fileName, "application/json;charset=utf-8")

   loadFromFile: ->
     Storage.loadFile (jsonString) =>
-      @load(jsonString)
+      @loadJsonStringIntoProjectFromExternalSource(jsonString)

   # ===========================================================================
@@ -117,9 +122,7 @@
       return unless xhr.readyState == 4
       return unless xhr.status == 200
       jsonString = xhr.responseText
-      @load(jsonString)
-      @checkpoint()
-      Apparatus.refresh() # HACK: calling Apparatus seems funky here.
+      @loadJsonStringIntoProjectFromExternalSource(jsonString)
     xhr.open("GET", url, true)
     xhr.send()

@@ -131,7 +134,7 @@
   _setupRevision: ->
     # @current is a JSON string representing the current state. @undoStack and
     # @redoStack are arrays of such JSON strings.
-    @current = @save()
+    @current = @getJsonStringOfProject()
     @undoStack = []
     @redoStack = []
     @maxUndoStackSize = 100
@@ -149,14 +152,14 @@
     return unless @isUndoable()
     @redoStack.push(@current)
     @current = @undoStack.pop()
-    @load(@current)
+    @loadJsonStringIntoProject(@current)
     @saveToLocalStorage()

   redo: ->
     return unless @isRedoable()
     @undoStack.push(@current)
     @current = @redoStack.pop()
-    @load(@current)
+    @loadJsonStringIntoProject(@current)
     @saveToLocalStorage()

   isUndoable: ->


:green_heart: Use proper measure in tests

I could confirm the difference was coming from line height not having
the same value in stable and beta. This is now enforced in the
workspace helper, and these tests failed with the same values on both
version.

--- spec/minimap-spec.coffee
+++ spec/minimap-spec.coffee
@@ -170,10 +170,10 @@
       expect(minimap.getScrollTop()).toEqual(editorScrollRatio * minimap.getMaxScrollTop())

     it 'computes the first visible row in the minimap', ->
-      expect(minimap.getFirstVisibleScreenRow()).toEqual(66)
+      expect(minimap.getFirstVisibleScreenRow()).toEqual(58)

     it 'computes the last visible row in the minimap', ->
-      expect(minimap.getLastVisibleScreenRow()).toEqual(77)
+      expect(minimap.getLastVisibleScreenRow()).toEqual(69)

     describe 'down to the bottom', ->
       beforeEach ->


Construct a valid envelope for sending reminder

This fixes sending reminders in private (XMPP) chat.

--- src/remind.coffee
+++ src/remind.coffee
@@ -74,7 +74,7 @@

     Reminder = new Reminder (user, message, executionDate) ->
         message = "Reminder for #{user.name}: #{message}"
-        robot.send user, message
+        robot.send {room: user.room, user: user}, message
         old = [user, message, executionDate]
         BrainReminders().splice BrainReminders().indexOf old, 1

@@ -133,4 +133,4 @@
     robot.brain.on "loaded", ->
         robot.logger.info "hubot-remind-advanced: Loading reminders from brain."
         for reminder, index in BrainReminders()
-            Reminder.addCron reminder[0], reminder[1], reminder[2]+            Reminder.addCron reminder[0], reminder[1], reminder[2]


rename normalEditor->inputEditor: more descriptive

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -22,9 +22,9 @@
     editor.normalModeInputView.editorElement.getModel().setText(key)

   submitNormalModeInputText = (text) ->
-    normalEditor = editor.normalModeInputView.editorElement
-    normalEditor.getModel().setText(text)
-    atom.commands.dispatch(normalEditor, "core:confirm")
+    inputEditor = editor.normalModeInputView.editorElement
+    inputEditor.getModel().setText(text)
+    atom.commands.dispatch(inputEditor, "core:confirm")

   describe "simple motions", ->
     beforeEach ->
@@ -1189,7 +1189,7 @@
             expect(editor.getCursorBufferPosition()).toEqual [1, 0]

     describe "using search history", ->
-      normalEditor = null
+      inputEditor = null

       beforeEach ->
         keydown('/')
@@ -1200,27 +1200,27 @@
         submitNormalModeInputText('abc')
         expect(editor.getCursorBufferPosition()).toEqual [2, 0]

-        normalEditor = editor.normalModeInputView.editorElement
+        inputEditor = editor.normalModeInputView.editorElement

       it "allows searching history in the search field", ->
         keydown('/')
-        atom.commands.dispatch(normalEditor, 'core:move-up')
-        expect(normalEditor.getModel().getText()).toEqual('abc')
-        atom.commands.dispatch(normalEditor, 'core:move-up')
-        expect(normalEditor.getModel().getText()).toEqual('def')
-        atom.commands.dispatch(normalEditor, 'core:move-up')
-        expect(normalEditor.getModel().getText()).toEqual('def')
+        atom.commands.dispatch(inputEditor, 'core:move-up')
+        expect(inputEditor.getModel().getText()).toEqual('abc')
+        atom.commands.dispatch(inputEditor, 'core:move-up')
+        expect(inputEditor.getModel().getText()).toEqual('def')
+        atom.commands.dispatch(inputEditor, 'core:move-up')
+        expect(inputEditor.getModel().getText()).toEqual('def')

       it "resets the search field to empty when scrolling back", ->
         keydown('/')
-        atom.commands.dispatch(normalEditor, 'core:move-up')
-        expect(normalEditor.getModel().getText()).toEqual('abc')
-        atom.commands.dispatch(normalEditor, 'core:move-up')
-        expect(normalEditor.getModel().getText()).toEqual('def')
-        atom.commands.dispatch(normalEditor, 'core:move-down')
-        expect(normalEditor.getModel().getText()).toEqual('abc')
-        atom.commands.dispatch(normalEditor, 'core:move-down')
-        expect(normalEditor.getModel().getText()).toEqual ''
+        atom.commands.dispatch(inputEditor, 'core:move-up')
+        expect(inputEditor.getModel().getText()).toEqual('abc')
+        atom.commands.dispatch(inputEditor, 'core:move-up')
+        expect(inputEditor.getModel().getText()).toEqual('def')
+        atom.commands.dispatch(inputEditor, 'core:move-down')
+        expect(inputEditor.getModel().getText()).toEqual('abc')
+        atom.commands.dispatch(inputEditor, 'core:move-down')
+        expect(inputEditor.getModel().getText()).toEqual ''

   describe "the * keybinding", ->
     beforeEach ->


fix list senka not update

fix list senka not update

--- rank-list.cjsx
+++ rank-list.cjsx
@@ -65,7 +65,7 @@
                 for checked, index in @props.rankList[0]
                   continue if !checked
                   <span key={index} style={@props.getStatusStyle !@props.isUpdated[index+1]}>
-                    {@props.rankList[1][index]}
+                    {@props.baseDetail.senkaList[index]}
                   </span>
             }
           </div>


Rebuild grammar file

Rebuild grammar file

--- grammars/powershell.cson
+++ grammars/powershell.cson
@@ -139,7 +139,7 @@
       name: "support.function.powershell"
     }
     {
-      match: "(?<!\\w)((?i:begin|break|catch|continue|data|define|do|dynamicparam|else|elseif|end|exit|finally|for|foreach(?!-object)|from|if|in|inlinescript|parallel|param|process|return|switch|throw|trap|try|until|using|var|where(?!-object)|while)|%|\\?)(?!\\w)"
+      match: "(?<!\\w)((?i:begin|break|catch|continue|data|define|do|dynamicparam|else|elseif|end|exit|finally|for|foreach(?!-object)|from|if|in|inlinescript|parallel|param|process|return|switch|throw|trap|try|until|using|var|where(?!=-object)|while)|%|\\?)(?!\\w)"
       name: "keyword.control.powershell"
     }
     {
@@ -240,7 +240,7 @@
       patterns: [
         {
           comment: "Verb-Noun pattern:"
-          match: "(?:(\\p{L}|\\d|_|-|\\\\|\\:)*\\\\)?\\b(?i:Add|Approve|Assert|Backup|Block|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Mount|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Where|Write)\\-.+?(?:\\.(?i:exe|cmd|bat|ps1))?\\b"
+          match: "(?:(\\p{L}|\\d|_|-|\\\\|\\:)*\\\\)?\\b(?i:Add|Approve|Assert|Backup|Block|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Mount|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Write)\\-.+?(?:\\.(?i:exe|cmd|bat|ps1))?\\b"
           name: "support.function.powershell"
         }
         {
@@ -688,4 +688,4 @@
       ]

   scopeName: "source.powershell"
-}
+}


Added space between counts on staff grading panel. ORA-294

--- lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -427,8 +427,8 @@

     else if @state == state_grading
       @ml_error_info_container.html(@ml_error_info)
-      available = _.template(gettext("<%= num %> available"), {num: @num_pending})
-      graded = _.template(gettext("<%= num %> graded"), {num: @num_graded})
+      available = _.template(gettext("<%= num %> available "), {num: @num_pending})
+      graded = _.template(gettext("<%= num %> graded "), {num: @num_graded})
       needed = _.template(gettext("<%= num %> more needed to start ML"),
         {num: Math.max(@min_for_ml - @num_graded, 0)})
       meta_list = $("<div>")


kill assertions about directoryProviders

--- spec/project-spec.coffee
+++ spec/project-spec.coffee
@@ -35,9 +35,6 @@
       atom.packages.serviceHub.provide(
         "atom.directory-provider", "0.1.0", directoryProvider)

-      expect(atom.project.directoryProviders.length).toBe 2
-      expect(atom.project.directoryProviders[0]).toBe directoryProvider
-
       tmp = temp.mkdirSync()
       atom.project.setPaths([tmp, remotePath])
       directories = atom.project.getDirectories()
@@ -74,9 +71,6 @@
         directoryForURI: (uri) -> throw new Error("This should not be called.")
       atom.packages.serviceHub.provide(
         "atom.directory-provider", "0.1.0", directoryProvider)
-
-      expect(atom.project.directoryProviders.length).toBe 2
-      expect(atom.project.directoryProviders[0]).toBe directoryProvider

       tmp = temp.mkdirSync()
       atom.project.setPaths([tmp])


Removed now redundant spec

Removed now redundant spec

--- spec/welcome-spec.coffee
+++ spec/welcome-spec.coffee
@@ -19,20 +19,6 @@
       expect(panes).toHaveLength 2
       expect(panes[0].getItems()[0].getTitle()).toBe 'Welcome'
       expect(panes[1].getItems()[0].getTitle()).toBe 'Welcome Guide'
-
-  describe "when activated again", ->
-    beforeEach ->
-      atom.workspace.getPanes().map (pane) -> pane.destroy()
-      atom.packages.deactivatePackage("welcome")
-      atom.workspace.open.reset()
-
-      waitsForPromise ->
-        atom.packages.activatePackage("welcome")
-
-      waits 1 # let the opener next tick run
-
-    it "doesn't show the welcome buffer", ->
-      expect(atom.workspace.open).not.toHaveBeenCalled()

   describe "the welcome:show command", ->
     workspaceElement = null


Restore code lost in bad merge

Restore code lost in bad merge

--- app/assets/javascripts/photo_overlay.js.coffee
+++ app/assets/javascripts/photo_overlay.js.coffee
@@ -83,3 +83,10 @@

   setCommentPaneHeight: =>
     @setMaskHeight()
+
+    if window.mobileLayout()
+      @dom.commentList().height(@dom.comments().height())
+    else
+      @dom.commentList().height(@dom.rightPane().innerHeight() - @dom.topRight().height())
+      @dom.commentList().scrollTop(@dom.comments().height())
+      @dom.imageContainer().height(@dom.image().height())


Wasn't elegant at all

Wasn't elegant at all


重构，将路由变为config中可以配置项

重构，将路由变为config中可以配置项

--- src/router.coffee
+++ src/router.coffee
@@ -2,38 +2,11 @@
   路由
 ###
 _ = require 'underscore'
+_config = require './config.json'

-#API的路由
-apiRouter = (biz, path, app)->
-  path = "/api/#{path}"
-  #读取信息
-  app.get "#{path}/:id?", (req, res, next)->
-    data = req.query || {}
-    #可能存在parentId
-    #data.parent_id = req.params.parent_id
-    data = _.extend data, req.params
-    #data.id = req.params.id
-
-    biz.find data, (err, results)->
-      res.json results
-
-  #增加数据
-  app.post path, (req, res, next)->
-    data = req.body || {}
-    data = _.extend data, req.params
-
-    #保存数据
-    biz.save data, (err, new_id)->
-      res.json {id: new_id}
-
-  #更新数据
-  app.put "#{path}/:id", (req, res, next)->
-
-  #删除数据
-  app.delete "#{path}/:id", (req, res, next)->

 #获取crud的默认path
-getPaths = (map)->
+getPaths = (router)->
   paths = {}

   pathPuffix =
@@ -43,18 +16,22 @@
     delete: "/:id(\\d+)"

   ["post", "get", "put", "delete"].forEach (method)->
-    path = (map.paths && map.paths[method]) || "#{map.path}#{pathPuffix[method]}"
+    path = (router.paths && router.paths[method]) || (()->
+      #如果有fullPath，则返回fullPath，否则重新组装path
+      return router.fullPath || "#{_config.rootAPI}#{router.path}#{pathPuffix[method]}"
+    )()
+    console.log path
     paths[method] = path    #"/api/#{path}"
   paths

-apiRouterTo = (app, map)->
-  biz = require "./biz/#{map.biz}"
-  paths = getPaths(map)
+apiRouter = (app, router)->
+  biz = require "./biz/#{router.biz}"
+  paths = getPaths(router)

   ["get", "post", "delete", "put"].forEach (method)->
     path = paths[method]
     #如果在map中，有指定业务逻辑的处理方法，则交给业务逻辑处理
-    specialMethod = (map.methods || {})[method]
+    specialMethod = (router.methods || {})[method]
     #如果指定的方法为false，则不处理这个method
     return if specialMethod is false
     #由业务逻辑指定的处理处理
@@ -82,6 +59,7 @@
             res.json results
           break
         when "post", "put"
+          #return console.log data
           #保存数据
           biz.save data, (err, new_id)->
             res.json {id: new_id}
@@ -91,26 +69,6 @@
             res.end()
           break

-
-###
-#项目的路由
-projectRounter = (app)->
-  #获取project信息
-  path = '/api/project'
-
-  #读取项目信息
-  app.get path, (req, res, next)->
-
-  #添加
-  app.post path, (req, res, next)->
-
-  #更新
-  app.put "#{path}/:id", (req, res, next)->
-
-  #删除数据
-  app.delete "#{path}/:id", (req, res, next)->
-    _project.remove
-###

 #响应404错误
 response404 = (req, res, next)->
@@ -134,91 +92,7 @@

   #校验校验权限

-  apiRoot = "/api/"
-  mapping = [
-      #项目
-      path: "#{apiRoot}project"
-      biz: "project"
-    ,
-      #素材
-      path: "#{apiRoot}project/:project_id(\\d+)/asset"
-      ### paths
-      paths:
-        post: "post path"
-        get: "get path"
-      ###
-      biz: "asset"
-      methods:
-        post: "uploadFile"
-        delete: false
-        put: false
-    ,
-      #查看素材
-      path: "/asset/:project_id(\\d+)/:filename",
-      biz: "asset"
-      methods:
-        get: "readFile"
-        put: false,
-        post: false,
-        delete: false
-    ,
-      #issue
-      path: "#{apiRoot}project/:project_id(\\d+)/issue"
-      biz: "issue"
-    ,
-      #针对issue的评论
-      path: "#{apiRoot}issue/:issue_id(\\d+)/comment"
-      biz: "comment"
-    ,
-      #建立或者解除asset与issue的关系
-      path: "#{apiRoot}issue/:issue_id(\\d+)/asset"
-      biz: "asset_issue_relation"
-      methods:
-        put: false
-    ,
-      #更改issue的状态，仅能更新
-      path: "#{apiRoot}issue/status"
-      biz: "issue"
-      methods:
-        get: false,
-        delete: false,
-        post: false
-        put: "changeStatus"
-    ,
-      #获取项目状态，及修改项目状态的路由
-      path: "#{apiRoot}project/:project_id(\\d+)/status"
-      biz: "project"
-      methods:
-        get: "getStatus",
-        delete: false,
-        post: false
-        put: "changeStatus"
-    ,
-      #用户登录
-      path: "#{apiRoot}signup"
-      biz: "member"
-      methods:
-        post: "signUp"
-        put: false,
-        delete: false,
-        get: false,
-  ]
-
-  mapping.forEach (map)->
-    apiRouterTo app, map
+  _config.routers.forEach (router)->
+    apiRouter app, router

   app.get "*", response404
-
-  #require('fs').renameSync '/Users/conis/WorkStation/BHF/src/uploads/2692-10y4zi.png', '/Users/conis/WorkStation/BHF/src/assets/2692-10y4zi.png'
-  ###
-  return
-  mapping =
-    "project": "project"
-    "issue": "project/:project_id/issue"
-    "comment": "issue/:issue_id/comment"
-    "asset": "project/:project_id/asset"
-
-  for key, path of mapping
-    biz = require "./biz/#{key}"
-    apiRouter biz, path, app
-###


Add HEAD request support

Add HEAD request support

--- src/routes.coffee
+++ src/routes.coffee
@@ -9,12 +9,15 @@

 # Submit and retrieve SRI tests
 app.get '/api/v1/tests', main.notImplemented
+app.head '/api/v1/tests', main.create
 app.post '/api/v1/tests', main.create
+app.head '/api/v1/tests/:id', main.getOne
 app.get '/api/v1/tests/:id', main.getOne
 app.put '/api/v1/tests/:id', main.notImplemented
 app.del '/api/v1/tests/:id', main.notImplemented

 # Retrieve stats
+app.head '/api/v1/stats', main.getStats
 app.get '/api/v1/stats', main.getStats

 # Health Checks


Fixed navbar status bug.

Fixed navbar status bug.

--- web/coffee/navbar.coffee
+++ web/coffee/navbar.coffee
@@ -1,6 +1,16 @@

 apiOffline =
   News: "/news"
+
+teacherLoggedIn =
+  Problems: "/problems"
+  Scoreboard: "/scoreboard"
+  Stats: "/stats"
+  About:
+    FAQ: "/faq"
+    Sponsors: "/sponsors"
+    News: "/news"
+  Logout: "/logout"

 userLoggedIn =
   Problems: "/problems"
@@ -21,17 +31,6 @@
     News: "/news"
   Login: "/login"

-teacherLoggedIn =
-  Problems: "/problems"
-  Scoreboard: "/scoreboard"
-  Stats: "/stats"
-  About:
-    FAQ: "/faq"
-    Sponsors: "/sponsors"
-    News: "/news"
-  Logout: "/logout"
-
-
 loadNavbar = (renderNavbarLinks, renderNestedNavbarLinks) ->

   navbarLayout = {
@@ -42,13 +41,14 @@
   apiCall "GET", "/api/user/status", {}
   .done (data) ->
     navbarLayout.links = userNotLoggedIn
-
-    if data["status"] == 0
+
+    console.log data
+    if data["status"] == 1
       if data.data["teacher"]
         navbarLayout.links = teacherLoggedIn
       else if data.data["logged_in"]
         navbarLayout.links = userLoggedIn
-
+    console.log navbarLayout.links
     $("#navbar-links").html renderNavbarLinks(navbarLayout)

   .fail ->