Disable JS client reconnect for now until it works properly

Otherwise it generates tons of noise because it doesn't
resync with the server document and goes haywire.

--- bokehjs/src/coffee/common/client.coffee
+++ bokehjs/src/coffee/common/client.coffee
@@ -165,7 +165,12 @@

   _schedule_reconnect : (milliseconds) ->
     retry = () =>
-      if @closed_permanently
+      # TODO "true or" below until we fix reconnection to repull
+      # the document when required. Otherwise, we get a lot of
+      # confusing errors that are causing trouble when debugging.
+      if true or @closed_permanently
+        if not @closed_permanently
+          logger.info("Bokeh client #{@_number} never reconnects for now; connection to server lost")
         return
       else
         logger.debug("Attempting to reconnect websocket #{@_number}")


enforce minimum size of 1k for compression

--- services/document-updater/app/coffee/ZipManager.coffee
+++ services/document-updater/app/coffee/ZipManager.coffee
@@ -60,7 +60,8 @@
 			callback(null, result)

 	compressIfNeeded: (doc_id, text, callback) ->
-		if ZIP_WRITES_ENABLED && ZIP_MINSIZE > 0 and text.length > ZIP_MINSIZE
+		if ZIP_WRITES_ENABLED and ZIP_MINSIZE > 1024 and text.length > ZIP_MINSIZE
+			# N.B. skip files of 1k or less, because gzip increases the size
 			zlib.gzip text, (err, buf) ->
 				if err?
 					logger.err doc_id:doc_id, err:err, "error compressing doc"


Remove unused feature flag

Remove unused feature flag

--- services/web/app/coffee/infrastructure/Features.coffee
+++ services/web/app/coffee/infrastructure/Features.coffee
@@ -14,8 +14,6 @@
 				return Settings.enableGithubSync
 			when 'v1-return-message'
 				return Settings.accountMerge? and Settings.overleaf?
-			when 'rich-text'
-				return Settings.showRichText
 			when 'custom-togglers'
 				return Settings.overleaf?
 			else


Remove "teaser-dropbox-text" experiment

Remove "teaser-dropbox-text" experiment

--- services/web/public/coffee/main/account-upgrade.coffee
+++ services/web/public/coffee/main/account-upgrade.coffee
@@ -19,13 +19,7 @@
 						url = "#{url}&cc=#{couponCode}"
 				$scope.startedFreeTrial = true

-				switch source
-					when "dropbox"
-						sixpack.participate 'teaser-dropbox-text', ['default', 'dropbox-focused'], (variant) ->
-							event_tracking.sendMB "subscription-start-trial", { source, plan, variant }
-
-					else
-						event_tracking.sendMB "subscription-start-trial", { source, plan }
+				event_tracking.sendMB "subscription-start-trial", { source, plan }

 				w.location = url


COMMENTED OUT - alternate addDirtyDrawArea implementation for bug testing

--- source/Art/Engine/Core/Drawing/ElementDrawLib.coffee
+++ source/Art/Engine/Core/Drawing/ElementDrawLib.coffee
@@ -239,6 +239,61 @@

     dirtyDrawAreas

+  ###
+  # 2018-8-14
+  # This is an easier-to-understand version of addDirtyDrawArea, though it is not
+  # object-creation optimized.
+  # I added this because I suspected the other version was buggy.
+  # However, after lots of playing, this, clearly correct version, produced
+  # the same results - which probably means the bug is elsewhere. Still...
+  # since I haven't found the bug, I'm keeping this around.
+  # The bugs are:
+  #     Zo initial fade screen has a moment where the blue is almost gone and
+  #     the rest of the screen draws and the result is everal white rectangles
+  #     - just for a milisecond...
+  # The other bug is the zo stream manager - dragging a pile of posts around
+  # sometimes results in an actual left-behind glitch which doesn't update until
+  # something else overlaps it.
+
+  @addDirtyDrawArea: (dirtyDrawAreas, dirtyArea, snapTo) =>
+    if dirtyArea.area > 0
+      dirtyArea = dirtyArea.roundOut snapTo, colorPrecision if snapTo?
+      @_addDirtyDrawArea dirtyDrawAreas, dirtyArea
+    else
+      dirtyDrawAreas
+
+  @_addDirtyDrawArea: (dirtyDrawAreas, dirtyArea) =>
+
+    if dirtyDrawAreas
+      if false # brute force
+        for area in dirtyDrawAreas
+          dirtyArea = dirtyArea.union area
+        for area in dirtyDrawAreas
+          throw new Error "not covered" unless dirtyArea.contains area
+        [dirtyArea]
+
+      else
+        if (i = findOverlappingArea dirtyDrawAreas, dirtyArea)?
+          area = dirtyDrawAreas[i]
+          if area.contains dirtyArea
+            dirtyDrawAreas
+
+          else
+            @_addDirtyDrawArea(
+              arrayWithout dirtyDrawAreas, i
+              area.union dirtyArea
+            )
+
+        else
+          dirtyDrawAreas.push dirtyArea
+          dirtyDrawAreas
+
+    else
+      [dirtyArea]
+
+
+  ###
+
   @partitionAreasByInteresection: (partitioningArea, areas) ->
     insideAreas = null
     outsideAreas = null


Bug fix for body resizing

Bug fix for body resizing

--- SingularityUI/app/views/fileBrowserSubview.coffee
+++ SingularityUI/app/views/fileBrowserSubview.coffee
@@ -28,7 +28,8 @@
             breadcrumbs: breadcrumbs

         # make sure body is large enough so we can fit the browser
-        @$('body').css 'min-height', @$el.offset().top + $(window).height()
+        minHeight = @$el.offset().top + $(window).height()
+        $('body').css 'min-height', "#{ minHeight }px"

         scroll = => $(window).scrollTop @$el.offset().top - 20
         if @scrollAfterRender


fix(observables): lazyloading observable refresh uses proper variable now

--- src/lib/observable.coffee
+++ src/lib/observable.coffee
@@ -1,5 +1,5 @@
-if require?
-  ko = require '../../../knockout/build/output/knockout-latest.debug'
+#if require?
+#  ko = require '../../../knockout/build/output/knockout-latest.debug'

 Observable = (self, property, initial_value) ->
   initial_value = self[property] if initial_value == undefined
@@ -47,6 +47,7 @@
   self[property] = ko.computed
     read: ->
       if self[property].loaded() == false
+        console.debug 'LazyLoading property', property
         callback.apply(self, params)
       return _value()
     write: (newValue) ->
@@ -66,7 +67,7 @@
   self[property].loaded = ko.observable false
   #load it again
   self[property].refresh = ->
-    result.loaded(false)
+    self[property].loaded(false)

 LazyObservableArray = (self, property, callback, params = [], init_value = null) ->
   LazyObservable self, property, callback, params, init_value, true


Ensure open methods pass documents in an open state

--- src/client/connection.coffee
+++ src/client/connection.coffee
@@ -145,7 +145,7 @@
   # callback(error, doc)
   openExisting: (docName, callback) ->
     return callback 'connection closed' if @state is 'stopped'
-    return callback null, @docs[docName] if @docs[docName]
+    return @_ensureOpenState(@docs[docName], callback) if @docs[docName]
     doc = @makeDoc docName, {}, callback

   # Open a document. It will be created if it doesn't already exist.
@@ -176,12 +176,19 @@
     if @docs[docName]
       doc = @docs[docName]
       if doc.type == type
-        callback null, doc
+        @_ensureOpenState(doc, callback)
       else
         callback 'Type mismatch', doc
       return

     @makeDoc docName, {create:true, type:type.name}, callback
+
+  _ensureOpenState: (doc, callback) ->
+    switch doc.state
+      when 'open' then callback null, doc
+      when 'opening' then @on 'open', -> callback null, doc
+      when 'closed' then doc.open -> callback null, doc
+    return

 # Not currently working.
 #  create: (type, callback) ->


fix up click tool to work with layout refactor

--- bokehjs/src/coffee/tool/click_tool.coffee
+++ bokehjs/src/coffee/tool/click_tool.coffee
@@ -13,8 +13,8 @@

     view_coords: (sx, sy) ->
       [vx, vy] = [
-        @plot_view.view_state.sx_to_vx(sx),
-        @plot_view.view_state.sy_to_vy(sy)
+        @plot_view.canvas.sx_to_vx(sx)
+        @plot_view.canvas.sy_to_vy(sy)
       ]
       return [vx, vy]

@@ -43,14 +43,14 @@
           @tool_button.addClass('active')
         )

-      @plot_view.canvas.bind("mousedown", (e) =>
+      @plot_view.canvas_view.canvas_wrapper.bind("mousedown", (e) =>
         if not @active and not @mget('always_active')
           return
         @start_posx = e.pageX
         @start_posy = e.pageY
       )

-      @plot_view.canvas.bind("mouseup", (e) =>
+      @plot_view.canvas_view.canvas_wrapper.bind("mouseup", (e) =>
         if not @active and not @mget('always_active')
           return
         if @start_posx != e.pageX or @start_posy != e.pageY
@@ -65,7 +65,6 @@

         @_select(vx, vy, e)
       )
-      @plot_view.canvas_wrapper.css('cursor', 'crosshair')

     _select: (vx, vy, e) ->
       geometry = {


Document env vars

Document env vars

--- src/highfive.coffee
+++ src/highfive.coffee
@@ -1,9 +1,15 @@
 # Description:
 #   Reward awesomeness with public recognition and (maybe) dollars.
 #
-# Commands
+# Commands:
 #   hubot highfive @<user> for <awesome thing> - makes a loud announcement in a public chatroom
 #   hubot highfive @<user> $<amount> for <awesome thing> - makes a loud announcement and sends the user an Amazon.com giftcard
+#
+# Configuration:
+#   HUBOT_HIGHFIVE_EMAIL_SERVICE - Service for looking up email addresses by user names (defaults to 'slack')
+#   HUBOT_SLACK_API_TOKEN - If using the 'slack' email service, this is your API token. Get one from https://api.slack.com/tokens
+#   HUBOT_HIGHFIVE_ROOM - Room for making noise when someone is high-fived. Defaults to the room the request was made in
+#   HUBOT_HIGHFIVE_AWARD_LIMIT - upper limit for giftcard awards. Set to 0 to disable giftcards. Default is 150.

 module.exports = (robot) ->
     # Services for getting emails from users


Default decimal for Utils.round

Default decimal for Utils.round

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -280,7 +280,7 @@
 ######################################################
 # MATH FUNCTIONS

-Utils.round = (value, decimals) ->
+Utils.round = (value, decimals=0) ->
 	d = Math.pow 10, decimals
 	Math.round(value * d) / d


Fix error on closed socket

Fix error on closed socket

--- raw-socket.coffee
+++ raw-socket.coffee
@@ -3,7 +3,10 @@
 server = (comp) ->
   net.createServer (socket) ->
     sendEvent = (event) ->
-      socket.write JSON.stringify(event) + "\n"
+      try
+        socket.write JSON.stringify(event) + "\n"
+      catch error
+        do stopListening

     stopListening = comp.events.onValue sendEvent


decided not to implement copy-annotations command

--- lib/code-annotation-manager.coffee
+++ lib/code-annotation-manager.coffee
@@ -384,39 +384,6 @@

         @_reinitializeEditor(editor, editorPath)
         return @
-
-    # _onDidRenameAsset: () ->
-    #     console.log arguments
-    #     debugger
-    #     newEditorPath = editor.getPath()
-    #     newAssetDirectory = @_getAssetDirectoryForEditor(editor, false)
-    #     oldEditorData = @initializedEditors[oldEditorPath]
-    #
-    #     # editor has been moved within the same project folder => just update key of @initializedEditors
-    #     if newAssetDirectory?.getPath() is oldEditorData.assetDirectory.getPath()
-    #         delete @initializedEditors[oldEditorPath]
-    #         @initializedEditors[newEditorPath] = oldEditorData
-    #         oldEditorPath = newEditorPath
-    #         return @
-    #
-    #     # editor has been moved into another project folder
-    #     # no .code-annotations directory => create it or stop right here
-    #     if not newAssetDirectory?
-    #         newAssetDirectory = @_initAssetDirectory(editor)
-    #
-    #     # move asset files to new assetDirectory and update according references
-    #     newAssetManager = @assetManagers[newAssetDirectory.getPath()]
-    #     for codeAnnotation in oldEditorData.codeAnnotations
-    #         name = codeAnnotation.name
-    #         if newAssetManager.has(name)
-    #             if Config.showReplaceConfirmDialog
-    #                 if not Utils.confirm({message: CodeAnnotations.REPLACE_CONFIRM_MESSAGE(name)})
-    #                     continue
-    #             newAssetManager.delete(name)
-    #         assetManager.move(name, newAssetManager)
-    #
-    #     @_reinitializeEditor(editor, editorPath)
-    #     return @

     _getEditorData: (editor) ->
         return @initializedEditors[editor.getPath()] or null
@@ -439,8 +406,6 @@
                 return @showAll()
             'code-annotations:show-commands': () =>
                 return @showCommands()
-            'code-annotations:copy-assets': () =>
-                return @copyAssets()
             'code-annotations:reload': () =>
                 return @reload()
             'code-annotations:load-current-editor': () =>


KodingAppsController: add custom route handler for 3rd party apps

We had to add this route into kodingappscontroller instead the Apps application because that was preventing us to run 3rd party apps before loading the Apps application

--- client/Main/kodingappscontroller.coffee
+++ client/Main/kodingappscontroller.coffee
@@ -16,6 +16,15 @@
       @apps or= {}

       @emit 'ready'
+
+    KD.singletons.router.addRoute "/:name?/Apps/:username/:app?", (route)->
+      {username} = route.params
+      if username[0] is username[0].toUpperCase()
+        app = route.params.username
+        username = route.params.name
+        KD.remote.api.JNewApp.one slug:"#{username}/Apps/#{app}", (err, app)->
+          if not err and app
+            KodingAppsController.runExternalApp app, dontUseRouter:yes

   @loadInternalApp = (name, callback)->


KodingAppsController: appendHeadElements which is a helper function to add multiple head elements at once

--- client/Main/kodingappscontroller.coffee
+++ client/Main/kodingappscontroller.coffee
@@ -161,6 +161,23 @@

   @destroyScriptElement = (type, identifier)->
     (document.getElementById "internal-#{type}-#{identifier}")?.remove()
+
+  @appendHeadElements = (options, callback)->
+
+    {items, identifier} = options
+
+    stack = []
+    items.forEach ({url, type}, index)->
+      stack.push (cb)->
+        KodingAppsController.appendHeadElement type, {
+          identifier : "#{identifier}-#{index}"
+          callback   : cb
+          url
+        }
+        KD.utils.defer cb  if type is 'style'
+
+    async.series stack, callback
+

   # #
   # MAKE NEW APP


gulp bump task: Add task to increase major version

--- gulp/bump.coffee
+++ gulp/bump.coffee
@@ -1,6 +1,11 @@
 bump = require 'gulp-bump'

 module.exports = (gulp) ->
+  gulp.task 'bump:major', ->
+    gulp.src(['package.json', 'bower.json'])
+    .pipe bump(type: 'major')
+    .pipe gulp.dest('./')
+
   gulp.task 'bump:minor', ->
     gulp.src(['package.json', 'bower.json'])
     .pipe bump(type: 'minor')


ShellEditorView.spec: events block

ShellEditorView.spec: events block

--- coffee/test/views/shell_editor_view.spec.coffee
+++ coffee/test/views/shell_editor_view.spec.coffee
@@ -202,48 +202,50 @@
       expect(view.remixerViews.length).toBe 1 # stay at 1
       expect(firstShell() instanceof EmptyShell.Model).toBe true

-  describe 'ShellEditor Duplicate Shell', ->
-
-    it 'should be triggered by a Remixer:Duplicate event', ->
-      view = new ShellEditorView options
-      view.render()
-      remixer = view.remixerViews[0]
-
-      spy = spyOn(view, 'addShell').andCallThrough()
-      remixer.trigger 'Remixer:Duplicate', remixer
-      expect(spy).toHaveBeenCalled()
-
-    it 'should add a clone of the remixerView\'s shell', ->
-      view = new ShellEditorView options
-      view.render()
-      remixer = view.remixerViews[0]
-
-      spy = spyOn(view, 'addShell').andCallThrough()
-      remixer.trigger 'Remixer:Duplicate', remixer
-
-      shell = spy.mostRecentCall.args[0]
-      expect(shell instanceof Shell.Model).toBe true
-      expect(shell.attributes).toEqual remixer.model.attributes
-
-  describe 'ShellEditor Delete Shell', ->
-
-    it 'should be triggered by a Remixer:Delete event', ->
-      view = new ShellEditorView options
-      view.render()
-      remixer = view.remixerViews[0]
-
-      spy = spyOn(view, 'removeShell').andCallThrough()
-      remixer.trigger 'Remixer:Delete', remixer
-      expect(spy).toHaveBeenCalled()
-
-    it 'should remove the remixerView\'s shell', ->
-      view = new ShellEditorView options
-      view.render()
-      remixer = view.remixerViews[0]
-
-      spy = spyOn(view, 'removeShell').andCallThrough()
-      remixer.trigger 'Remixer:Delete', remixer
-      expect(spy).toHaveBeenCalledWith remixer.model
+  describe 'ShellEditorView events', ->
+
+    describe 'on Remixer:Duplicate', ->
+
+      it 'should be triggered by a Remixer:Duplicate event', ->
+        view = new ShellEditorView options
+        view.render()
+        remixer = view.remixerViews[0]
+
+        spy = spyOn(view, 'addShell').andCallThrough()
+        remixer.trigger 'Remixer:Duplicate', remixer
+        expect(spy).toHaveBeenCalled()
+
+      it 'should add a clone of the remixerView\'s shell', ->
+        view = new ShellEditorView options
+        view.render()
+        remixer = view.remixerViews[0]
+
+        spy = spyOn(view, 'addShell').andCallThrough()
+        remixer.trigger 'Remixer:Duplicate', remixer
+
+        shell = spy.mostRecentCall.args[0]
+        expect(shell instanceof Shell.Model).toBe true
+        expect(shell.attributes).toEqual remixer.model.attributes
+
+    describe 'on Remixer:Delete', ->
+
+      it 'should be triggered by a Remixer:Delete event', ->
+        view = new ShellEditorView options
+        view.render()
+        remixer = view.remixerViews[0]
+
+        spy = spyOn(view, 'removeShell').andCallThrough()
+        remixer.trigger 'Remixer:Delete', remixer
+        expect(spy).toHaveBeenCalled()
+
+      it 'should remove the remixerView\'s shell', ->
+        view = new ShellEditorView options
+        view.render()
+        remixer = view.remixerViews[0]
+
+        spy = spyOn(view, 'removeShell').andCallThrough()
+        remixer.trigger 'Remixer:Delete', remixer
+        expect(spy).toHaveBeenCalledWith remixer.model


Reject `linkIdentity` and `unlinkIdenity` when there is no user

--- src/hull.api.coffee
+++ src/hull.api.coffee
@@ -1,12 +1,13 @@
 define [
   'underscore'
+  'lib/utils/promises',
   'lib/utils/emitter',
   'lib/api/api',
   'lib/api/reporting',
   'lib/utils/entity',
   'lib/utils/config',
   'lib/api/current-user'
-  ], (_, emitter, api, reporting, entity, configParser, currentUser)->
+  ], (_, promises, emitter, api, reporting, entity, configParser, currentUser)->

     create = (config)->
       _emitter = emitter()
@@ -15,13 +16,19 @@
         _emitter.on 'hull.auth.login', (me)-> _reporting.track('hull.auth.login', me)
         _emitter.on 'hull.auth.logout', ()-> _reporting.track('hull.auth.logout')

-        resolveUserPromise = (promise)->
+        handleUserPromise = (promise)->
           promise.then (user)->
             _emitter.emit('hull.auth.login', user)
           , (error) ->
             _emitter.emit('hull.auth.fail', error)

           promise
+
+        noCurentUserDeferred = promises.deferred()
+        noCurentUserDeferred.reject(
+          reason: 'no_current_user',
+          message: 'User must be logged in to perform this action'
+        )

         created =
           config: configParser(config, _emitter)
@@ -32,21 +39,28 @@
           flag: _reporting.flag
           api: api.api
           currentUser: currentUser(_emitter)
-          signup: (args...)->
-            resolveUserPromise(api.auth.signup(args...))
+          signUp: (args...)->
+            handleUserPromise(api.auth.signUp(args...))
           login: (args...)->
             if (api.auth.isAuthenticating())
               return console.info "Authentication is in progress. Use `Hull.on('hull.auth.login', fn)` to call `fn` when done."

-            resolveUserPromise(api.auth.login(args...))
+            handleUserPromise(api.auth.login(args...))
           logout: api.auth.logout
-          linkIdentity: (provider, opts={}, callback=->)->
-            options = _.extend opts, { mode: 'connect' }
-            created.login provider, options, callback
-          unlinkIdentity: (provider, callback=->)->
+          linkIdentity: (provider, options = {}, callback)->
+            return noCurentUserDeferred.promise unless created.currentUser()
+
+            options.mode = 'connect'
+            created.login(provider, options, callback)
+          unlinkIdentity: (provider, callback)->
+            return noCurentUserDeferred.promise unless created.currentUser()
+
             promise = api.api("me/identities/#{provider}", 'delete').then(api.api.bind(api, 'me'))
-            promise.then callback
-            promise.then _emitter.emit.bind(_emitter, 'hull.auth.login')
+            promise.then (user)->
+              _emitter.emit('hull.auth.login', user)
+              callback(user)
+
+            promise
           util:
             entity: entity
             eventEmitter: _emitter


Explain recursive deletions

Explain recursive deletions

--- backend/merge.coffee
+++ backend/merge.coffee
@@ -365,9 +365,11 @@
                     bulk.push dst
                 @pouch.db.bulkDocs bulk, callback

-    # Expectations:
-    #   - the file still exists in pouch
-    #   - the file can be found by its _id
+    # Remove a file from PouchDB
+    #
+    # As the watchers often detect the deletion of a folder before the deletion
+    # of the files inside it, deleteFile can be called for a file that has
+    # already been removed. This is not considerated as an error.
     deleteFile: (side, doc, callback) ->
         @pouch.db.get doc._id, (err, file) =>
             if err?.status is 404
@@ -379,11 +381,14 @@
                 file._deleted = true
                 @pouch.db.put file, callback

-    # Expectations:
-    #   - the folder still exists in pouch
-    #   - the folder can be found by its _id
-    # Actions:
-    #   - delete every file and folder inside this folder
+    # Remove a folder and every file and folder inside it
+    #
+    # When a folder is removed in PouchDB, we also remove the files and folders
+    # inside it to ensure consistency. The watchers often detects the deletion
+    # of a nested folder after the deletion of its parent. In this case, the
+    # call to deleteFolder for the child is considered as successful, even if
+    # the folder is missing in pouchdb (error 404).
+    #
     # TODO add an integration test where a folder with a lot of files is removed
     deleteFolder: (side, doc, callback) ->
         @pouch.db.get doc._id, (err, folder) =>


compute 'nearest' to span only in one dimension (#7073)

* compute 'nearest' to span only in one dimension

* changes requested

* restore old loop logic

--- bokehjs/src/coffee/models/tools/inspectors/hover_tool.coffee
+++ bokehjs/src/coffee/models/tools/inspectors/hover_tool.coffee
@@ -6,21 +6,41 @@
 import {replace_placeholders} from "core/util/templating"
 import {div, span} from "core/dom"
 import * as p from "core/properties"
+import {color2hex} from "core/util/color"
 import {values, isEmpty} from "core/util/object"
 import {isString, isFunction} from "core/util/types"
 import {build_views, remove_views} from "core/build_views"

-_color_to_hex = (color) ->
-  if (color.substr(0, 1) == '#')
-      return color
-  digits = /(.*?)rgb\((\d+), (\d+), (\d+)\)/.exec(color)
-
-  red = parseInt(digits[2])
-  green = parseInt(digits[3])
-  blue = parseInt(digits[4])
-
-  rgb = blue | (green << 8) | (red << 16)
-  return digits[1] + '#' + rgb.toString(16)
+export _nearest_line_hit = (canvas, i, geometry, sx, sy, dx, dy) ->
+  d1x = dx[i]
+  d1y = dy[i]
+
+  d2x = dx[i+1]
+  d2y = dy[i+1]
+
+  if geometry.type == "span"
+    switch geometry.direction
+      when "h"
+        dist1 = Math.abs(d1x-sx)
+        dist2 = Math.abs(d2x-sx)
+      when "v"
+        dist1 = Math.abs(d1y-sy)
+        dist2 = Math.abs(d2y-sy)
+  else
+    dist1 = hittest.dist_2_pts(d1x, d1y, sx, sy)
+    dist2 = hittest.dist_2_pts(d2x, d2y, sx, sy)
+
+  if dist1 < dist2
+    rx = canvas.sx_to_vx(d1x)
+    ry = canvas.sy_to_vy(d1y)
+    return [[rx, ry], i]
+  else
+    rx = canvas.sx_to_vx(d2x)
+    ry = canvas.sy_to_vy(d2y)
+    return [[rx, ry], i+1]
+
+export _line_hit = (canvas, xs, ys, ind) ->
+  return [[canvas.sx_to_vx(xs[ind]), canvas.sy_to_vy(ys[ind])], ind]

 export class HoverToolView extends InspectToolView

@@ -175,51 +195,34 @@
     x = xscale.invert(vx)
     y = yscale.invert(vy)

+    glyph = renderer_view.glyph
+
     for i in indices['0d'].indices
-      data_x = renderer_view.glyph._x[i+1]
-      data_y = renderer_view.glyph._y[i+1]
+      data_x = glyph._x[i+1]
+      data_y = glyph._y[i+1]
       ii = i

       switch @model.line_policy
         when "interp" # and renderer.get_interpolation_hit?
-          [data_x, data_y] = renderer_view.glyph.get_interpolation_hit(i, geometry)
+          [data_x, data_y] = glyph.get_interpolation_hit(i, geometry)
           rx = xscale.compute(data_x)
           ry = yscale.compute(data_y)

         when "prev"
-          rx = canvas.sx_to_vx(renderer_view.glyph.sx[i])
-          ry = canvas.sy_to_vy(renderer_view.glyph.sy[i])
+          [[rx, ry], ii] = _line_hit(canvas, glyph.sx, glyph.sy, i)

         when "next"
-          rx = canvas.sx_to_vx(renderer_view.glyph.sx[i+1])
-          ry = canvas.sy_to_vy(renderer_view.glyph.sy[i+1])
-          ii = i+1
+          [[rx, ry], ii] = _line_hit(canvas, glyph.sx, glyph.sy, i+1)

         when "nearest"
-          d1x = renderer_view.glyph.sx[i]
-          d1y = renderer_view.glyph.sy[i]
-          dist1 = hittest.dist_2_pts(d1x, d1y, sx, sy)
-
-          d2x = renderer_view.glyph.sx[i+1]
-          d2y = renderer_view.glyph.sy[i+1]
-          dist2 = hittest.dist_2_pts(d2x, d2y, sx, sy)
-
-          if dist1 < dist2
-            [sdatax, sdatay] = [d1x, d1y]
-          else
-            [sdatax, sdatay] = [d2x, d2y]
-            ii = i+1
-
-          data_x = renderer_view.glyph._x[i]
-          data_y = renderer_view.glyph._y[i]
-          rx = canvas.sx_to_vx(sdatax)
-          ry = canvas.sy_to_vy(sdatay)
+          [[rx, ry], ii] = _nearest_line_hit(canvas, i, geometry, sx, sy, glyph.sx, glyph.sy)
+          data_x = glyph._x[ii]
+          data_y = glyph._y[ii]

         else
           [rx, ry] = [vx, vy]

       vars = {index: ii, x: x, y: y, vx: vx, vy: vy, sx: sx, sy: sy, data_x: data_x, data_y: data_y, rx:rx, ry:ry}
-

       tooltip.add(rx, ry, @_render_tooltips(ds, ii, vars))

@@ -227,44 +230,26 @@
       # multiglyphs will set '1d' and '2d' results, but have different tooltips
       if not isEmpty(indices['2d'].indices)
         for i, [j] of indices['2d'].indices
-          data_x = renderer_view.glyph._xs[i][j]
-          data_y = renderer_view.glyph._ys[i][j]
+          data_x = glyph._xs[i][j]
+          data_y = glyph._ys[i][j]
           jj = j

           switch @model.line_policy
             when "interp" # and renderer.get_interpolation_hit?
-              [data_x, data_y] = renderer_view.glyph.get_interpolation_hit(i, j, geometry)
+              [data_x, data_y] = glyph.get_interpolation_hit(i, j, geometry)
               rx = xscale.compute(data_x)
               ry = yscale.compute(data_y)

             when "prev"
-              rx = canvas.sx_to_vx(renderer_view.glyph.sxs[i][j])
-              ry = canvas.sy_to_vy(renderer_view.glyph.sys[i][j])
+              [[rx, ry], jj] = _line_hit(canvas, glyph.sxs[i], glyph.sys[i], j)

             when "next"
-              rx = canvas.sx_to_vx(renderer_view.glyph.sxs[i][j+1])
-              ry = canvas.sy_to_vy(renderer_view.glyph.sys[i][j+1])
-              jj = j+1
+              [[rx, ry], jj] = _line_hit(canvas, glyph.sxs[i], glyph.sys[i], j+1)

             when "nearest"
-              d1x = renderer_view.glyph.sxs[i][j]
-              d1y = renderer_view.glyph.sys[i][j]
-              dist1 = hittest.dist_2_pts(d1x, d1y, sx, sy)
-
-              d2x = renderer_view.glyph.sxs[i][j+1]
-              d2y = renderer_view.glyph.sys[i][j+1]
-              dist2 = hittest.dist_2_pts(d2x, d2y, sx, sy)
-
-              if dist1 < dist2
-                [sdatax, sdatay] = [d1x, d1y]
-              else
-                [sdatax, sdatay] = [d2x, d2y]
-                jj = j+1
-
-              data_x = renderer_view.glyph._xs[i][j]
-              data_y = renderer_view.glyph._ys[i][j]
-              rx = canvas.sx_to_vx(sdatax)
-              ry = canvas.sy_to_vy(sdatay)
+              [[rx, ry], jj] = _nearest_line_hit(canvas, j, geometry, sx, sy, glyph.sxs[i], glyph.sys[i])
+              data_x = glyph._xs[i][jj]
+              data_y = glyph._ys[i][jj]

           vars = {index: i, segment_index: jj, x: x, y: y, vx: vx, vy: vy, sx: sx, sy: sy, data_x: data_x, data_y: data_y}

@@ -272,15 +257,15 @@

       else
         # handle non-multiglyphs
-        data_x = renderer_view.glyph._x?[i]
-        data_y = renderer_view.glyph._y?[i]
+        data_x = glyph._x?[i]
+        data_y = glyph._y?[i]
         if @model.point_policy == 'snap_to_data' # and renderer.glyph.sx? and renderer.glyph.sy?
           # Pass in our screen position so we can determine
           # which patch we're over if there are discontinuous
           # patches.
-          pt = renderer_view.glyph.get_anchor_point(@model.anchor, i, [sx, sy])
+          pt = glyph.get_anchor_point(@model.anchor, i, [sx, sy])
           if not pt?
-            pt = renderer_view.glyph.get_anchor_point("center", i, [sx, sy])
+            pt = glyph.get_anchor_point("center", i, [sx, sy])

           rx = canvas.sx_to_vx(pt.x)
           ry = canvas.sy_to_vy(pt.y)
@@ -359,7 +344,7 @@
             cell.appendChild(el)
             continue
           if hex
-            color = _color_to_hex(color)
+            color = color2hex(color)
           el = span({}, color)
           cell.appendChild(el)
           if swatch
@@ -384,7 +369,7 @@
       ["index",         "$index"    ]
       ["data (x, y)",   "($x, $y)"  ]
       ["canvas (x, y)", "($sx, $sy)"]
-    ]] # TODO (bev)
+    ]]
     formatters:   [ p.Any,    {}             ]
     renderers:    [ p.Array,  []             ]
     names:        [ p.Array,  []             ]


Client: Activity: Added markUserAsTroll action to user actions

--- client/app/lib/flux/actions/user.coffee
+++ client/app/lib/flux/actions/user.coffee
@@ -76,6 +76,27 @@
   showNotification "User is blocked!"

+###*
+ * Action to mark user as troll
+###
+markUserAsTroll = (account) ->
+
+  { MARK_USER_AS_TROLL_BEGIN
+    MARK_USER_AS_TROLL_SUCCESS
+    MARK_USER_AS_TROLL_FAIL } = actions
+
+  dispatch MARK_USER_AS_TROLL_BEGIN, account
+
+  account.markUserAsExempt yes, (err, res)=>
+    if err
+      dispatch MARK_USER_AS_TROLL_FAIL, { err, account }
+      showMarkUserAsTrollFail err, account
+      return
+
+    dispatch MARK_USER_AS_TROLL_SUCCESS, account
+    showMarkUserAsTrollSuccess account
+
+
 module.exports = {
   loadAccount
   searchAccounts


Fixes #508

Fixes #508

Callback might be undefined.

--- src/commands/scaffold.coffee
+++ src/commands/scaffold.coffee
@@ -32,7 +32,7 @@
   fs.unlink path, (error) ->
     return logger.error "#{error}" if error?
     logger.info "destroy #{path}"
-    callback error
+    callback error if callback?

 scaffoldFile = (rollback, from, to, templateData, parentDir, callback) ->
   [from, to] = [(sysPath.join parentDir, from), (sysPath.join parentDir, to)]


added test for constant foreach

added test for constant foreach

--- platform/src/tests/tests.coffee
+++ platform/src/tests/tests.coffee
@@ -315,6 +315,46 @@
       expect(models[2].child1TimesCalled).toBe(1)
       expect(models[2].activeChild).toBe(2)

+    it "binds across constant arrays", ->
+      people = [
+        { first: "bill", last: "bobson" }
+        { first: "joe", last: "johnson" }
+      ]
+
+      actions =
+        getName:
+          paramDefs:
+            name:
+              direction: "in"
+            index:
+              direction: "in"
+          update: ->
+            expect(@params.index).toEqual(if @params.name is "bill" then "0" else "1")
+
+      spyOn(actions.getName, "update").andCallThrough()
+
+      layout =
+        foreach:
+          from: people
+          bindTo: "person"
+          index: "personIndex"
+        children: [
+          {
+            action: "getName"
+            params:
+              in:
+                name: "bindings.person.first"
+                index: "bindings.personIndex"
+          }
+        ]
+
+      constants = new GE.NodeVisitorConstants
+        actions: actions
+        evaluator: GE.makeEvaluator()
+      GE.visitNode(layout, constants, {})
+
+      expect(actions.getName.update).toHaveBeenCalled()
+
     it "binds across model arrays", ->
       oldModel =
         people: [


markdown code highlighting fixed in kdinputviewwithpreview

--- components/inputs/KDInputViewWithPreview.coffee
+++ components/inputs/KDInputViewWithPreview.coffee
@@ -185,7 +185,7 @@

     @$().after """

-

+

"""

   viewAppended:->
@@ -215,6 +215,6 @@
   generatePreview:=>
     if @showPreview
       if @options.preview.language is "markdown"
-        @$("div.preview_content").html @utils.applyMarkdown @getValue()
-        @$("div.preview_content pre").addClass("prettyprint").each (i,element)=>
+        @$("div.preview_content span.data").html @utils.applyMarkdown @getValue()
+        @$("div.preview_content span.data pre").each (i,element)=>
           hljs.highlightBlock element


fix for show/hide accepted and pagination [#97186346]

--- review_front/app/controllers/project/commits.coffee
+++ review_front/app/controllers/project/commits.coffee
@@ -7,7 +7,7 @@

   searchResults: Ember.computed.oneWay('arrangedContent')

-  filteredContent: Ember.observer('model', 'hideAccepted', ->
+  filteredContent: Ember.observer('model', 'hideAccepted', 'page', ->
     searchInput = @get('model')
     if !searchInput or !@get('hideAccepted')
       @set 'searchResults', @get("arrangedContent")
@@ -24,4 +24,5 @@
   pageBinding: 'content.page'
   page: 1

+
 `export default ProjectCommitsController`


Enabled revocation step for incoming transactions

--- src/middleware/tlsAuthentication.coffee
+++ src/middleware/tlsAuthentication.coffee
@@ -198,24 +198,24 @@

         # MyEdit
       #lookup client in revocation list
-
-      #try
-      #  revoked = yield revokedLookup cert.fingerprint, cert.issuer.CN
-      #catch err
-      #  logger.error "Failed to lookup client for revocation: #{err}"
+
+      try
+        revoked = yield revokedLookup cert.fingerprint, cert.issuer.CN
+      catch err
+        logger.error "Failed to lookup client for revocation: #{err}"

       # Edited - MyEdit May 25
-      if this.authenticated? #and not revoked
+      if this.authenticated? and not revoked
         sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
         this.authenticationType = 'tls'
         yield next

-      #else if revoked?
-      #  this.authenticated = null
-      #  logger.info "Certificate Authentication Failed: revoked cert - fingerprint: #{cert.fingerprint} issuer: #{cert.issuerCN}, trying next auth mechanism if any..."
-      #  sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
-      #  yield next
+      else if revoked?
+        this.authenticated = null
+        logger.info "Certificate Authentication Failed: revoked cert - fingerprint: #{cert.fingerprint} issuer: #{cert.issuerCN}, trying next auth mechanism if any..."
+        sdc.timing "#{domain}.tlsAuthenticationMiddleware", startTime if statsdServer.enabled
+        yield next
       else
         this.authenticated = null
         logger.info "Certificate Authentication Failed: the certificate's fingerprint #{cert.fingerprint} did not match any client's certFingerprint attribute, trying next auth mechanism if any..."


key view focus/blur support for webterm

key view focus/blur support for webterm

--- client/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -39,11 +39,15 @@

     @listenWindowResize()

-    $(window).bind "focus", =>
-      @terminal.setFocused true
+    @on "ReceivedClickElsewhere", =>
+      @terminal.setFocused false
+      @getSingleton('windowController').removeLayer @
+
+    # $(window).bind "focus", =>
+    #   @terminal.setFocused true

-    $(window).bind "blur", =>
-      @terminal.setFocused false
+    # $(window).bind "blur", =>
+    #   @terminal.setFocused false

     KD.whoami().tellKite
       kiteName: 'webterm',
@@ -66,7 +70,7 @@
             name: sessions[key]
             mainView: this
         @sessionBox.show()
-
+
       KD.whoami().tellKite
         kiteName: 'webterm',
         method: 'createServer',
@@ -76,6 +80,13 @@
         @terminal.showSessions()
     , 3000

+  setKeyView:->
+    @getSingleton('windowController').addLayer @
+    # checking existence of terminal because of the initial timeout
+    # this may run before it is created
+    @terminal.setFocused true if @terminal
+    super
+
   click: ->
     @setKeyView()


Remove extra check for file path

Remove extra check for file path

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -322,7 +322,7 @@
     return filePath

-  isDummyFilePath: (filePath) -> (filePath?.indexOf @getDummyFilePath false) is 0
+  isDummyFilePath: (filePath) -> (filePath.indexOf @getDummyFilePath false) is 0

   openMachineTerminal: (machine) -> @createTerminal { machine }


Added Farming Village, Hunting Party, Sea Hag, Treasure Map, and Vineyard.

--- cards.coffee
+++ cards.coffee
@@ -536,6 +536,27 @@
     vp
 }

+makeCard 'Farming Village', action, {
+  cost: 4
+  actions: 2
+
+  playEffect: (state) ->
+    cardsDrawn = 0;
+    while cardsDrawn < 1
+      drawn = state.current.getCardsFromDeck(1)
+      if drawn.length == 0
+        break
+      card = drawn[0]
+      if card.isAction or card.isTreasure
+        cardsDrawn += 1
+        state.current.hand.push(card)
+        state.log("...drawing a #{card}.")
+      else
+        state.current.setAside.push(card)
+    state.current.discard = state.current.discard.concat(state.current.setAside)
+    state.current.setAside = []
+}
+
 makeCard "Followers", action, {
   cost: 0
   isAttack: true
@@ -612,6 +633,31 @@
   attackReaction:
     (player) ->
       transferCard(c['Horse Traders'], player.hand, player.duration)
+}
+
+makeCard 'Hunting Party', action, {
+  cost: 5
+  actions: 1
+  cards: 1
+
+  playEffect: (state) ->
+    state.revealHand(state.current)
+    cardsDrawn = 0;
+    while cardsDrawn < 1
+      drawn = state.current.getCardsFromDeck(1)
+      if drawn.length == 0
+        break
+      card = drawn[0]
+      state.log("...revealing a #{card}")
+
+      if card not in state.current.hand
+        cardsDrawn += 1
+        state.current.hand.push(card)
+        state.log("...drawing a #{card}.")
+      else
+        state.current.setAside.push(card)
+    state.current.discard = state.current.discard.concat(state.current.setAside)
+    state.current.setAside = []
 }

 makeCard "Menagerie", action, {
@@ -751,6 +797,21 @@
   playEffect:
     (state) ->
       state.quarries += 1
+}
+
+makeCard 'Sea Hag', action, {
+  cost: 4
+  isAttack: true
+
+  playEffect: (state) ->
+    state.attackOpponents (opp) ->
+      state.discardFromDeck(opp, 1)
+
+      # If no Curses are left to gain, we don't want a Curse to be
+      # fished out of the opponent's discard pile
+      if state.supply[c.Curse] > 0
+        state.gainCard(opp, c.Curse)
+        transferCardToTop(c.Curse, opp.discard, opp.draw)
 }

 makeCard 'Shanty Town', action, {
@@ -806,6 +867,27 @@
     state.tradeRouteValue
 }

+makeCard 'Treasure Map', action {
+  cost: 4
+
+  playEffect: (state) ->
+    trashedMaps = 0
+
+    if c['Treasure Map'] in state.current.inPlay
+      state.current.inPlay.remove(c['Treasure Map'])
+      trashedMaps += 1
+
+    if c['Treasure Map'] in state.current.hand
+      doTrash(c['Treasure Map'])
+      trashedMaps += 1
+
+    if trashedMaps == 2
+      for num in [1..4]
+        state.gainCard(state.current, c.Gold, true)
+        transferCardToTop(c.Gold, state.current.discard, state.current.draw)
+      state.log("…gaining 4 Golds, putting them on top of the deck.")
+}
+
 makeCard "Trusty Steed", c["Bag of Gold"], {
   actions: 0
   playEffect: (state) ->
@@ -832,6 +914,12 @@
       if potions == 0 and coins <= 5 and card.isAction
         choices.push(card)
     state.gainOneOf(state.current, choices)
+}
+
+makeCard 'Vineyard', c.Estate, {
+  cost: 0
+  costPotion: 1
+  getVP: (state) -> Math.floor(state.current.numActionCardsInDeck().length / 3)
 }

 makeCard 'Warehouse', action, {


#DEV35: Added loading state when "Agendar button is clicked"

--- app/assets/javascripts/application-ajax.js.coffee
+++ app/assets/javascripts/application-ajax.js.coffee
@@ -1,151 +1,152 @@
 (($) ->
-	App =
-
-		###*
-		Inicia as funções
-		###
-		init: ->
-			App.BellNotification()
-			App.AgendaButton()
-			App.FriendActions()
-			App.NewsfeedNotification()
-			return
+  App =

-
-
-
-		###*
-		Notificações de amizade
-		###
-		BellNotification: ->
-			$(document).on 'click', '#bell', ->
-				$.ajax(
-					url: "/notify/bell"
-				).done (data) ->
-					if data.success
-						$("#bell").removeClass("white-font").addClass("green-dark-font")
-						$(".bell-wrapper .counter").removeClass("white-font").addClass("green-dark-font")
-
-					return
-				return
-
-			return
+    ###*
+    Inicia as funções
+    ###
+    init: ->
+      App.BellNotification()
+      App.AgendaButton()
+      App.FriendActions()
+      App.NewsfeedNotification()
+      return

-		###*
-		Notificações do newsfeed
-		###
-		NewsfeedNotification: ->
-			$(document).on 'click', '#newsfeed-link', ->
-				$.ajax(
-					url: "/notify/newsfeed"
-				).done (data) ->
-					if data.success
-						$("#newsfeed-link").find(".badge").html("")

-					return
-				return
+    ###*
+    Notificações de amizade
+    ###
+    BellNotification: ->
+      $(document).on 'click', '#bell', ->
+        $.ajax(
+          url: "/notify/bell"
+        ).done (data) ->
+          if data.success
+            $("#bell").removeClass("white-font").addClass("green-dark-font")
+            $(".bell-wrapper .counter").removeClass("white-font").addClass("green-dark-font")

-			return
+          return
+        return
+
+      return

-
-		###*
-		Ativa o botão para agendar eventos
-		###
-		AgendaButton: ->
-			$(document).on 'click', '.js-EventAgendaButton', ->
+    ###*
+    Notificações do newsfeed
+    ###
+    NewsfeedNotification: ->
+      $(document).on 'click', '#newsfeed-link', ->
+        $.ajax(
+          url: "/notify/newsfeed"
+        ).done (data) ->
+          if data.success
+            $("#newsfeed-link").find(".badge").html("")

-				$.ajax(
-					url: $(this).attr("data-schedule-url")
-				).done (data) ->
-
-					event = $("."+data.event)
-					agended = data.agended
-					agenda_new_count = data.count
-					agended_by_new_count = data.agended_by_count
-					new_title = data.new_title
+          return
+        return

-					if agended
-						event.find(".js-EventAgendaButton").addClass("is-schedule")
-						event.find(".js-EventButtonText").text("Agendado")
-						event.find(".js-EventDayButton").addClass("is-schedule")
-					else
-						event.find(".js-EventAgendaButton").removeClass("is-schedule")
-						event.find(".js-EventButtonText").text("Agendar")
-						event.find(".js-EventDayButton").removeClass("is-schedule")
+      return

-					$("#agenda-count").text("").text(agenda_new_count)
-					event.find(".js-agendedByCount").text("").text(agended_by_new_count)
-					event.find(".js-agendedByCount").attr("title", new_title)

-					return
-				return
-			return
+
+
+    ###*
+    Ativa o botão para agendar eventos
+    ###
+    AgendaButton: ->
+      $(document).on 'click', '.js-EventAgendaButton', ->
+        $(this).find(".js-EventButtonText").text("Agendando...")
+
+        $.ajax(
+          url: $(this).attr("data-schedule-url")
+        ).done (data) ->
+
+          event = $("."+data.event)
+          agended = data.agended
+          agenda_new_count = data.count
+          agended_by_new_count = data.agended_by_count
+          new_title = data.new_title
+
+          if agended
+            event.find(".js-EventAgendaButton").addClass("is-schedule")
+            event.find(".js-EventButtonText").text("Agendado")
+            event.find(".js-EventDayButton").addClass("is-schedule")
+          else
+            event.find(".js-EventAgendaButton").removeClass("is-schedule")
+            event.find(".js-EventButtonText").text("Agendar")
+            event.find(".js-EventDayButton").removeClass("is-schedule")
+
+          $("#agenda-count").text("").text(agenda_new_count)
+          event.find(".js-agendedByCount").text("").text(agended_by_new_count)
+          event.find(".js-agendedByCount").attr("title", new_title)
+
+          return
+        return
+      return

-		###
-		Controla a requisição, aceitação e destruição de amizades
-		###
-		FriendActions: ->
-			# Requisição de amizade
-			$(document).on 'click', '.friend-request', ->
-				$.ajax(
-					url: "/friendships/request"
-					data:
-						friend: $(this).attr("data-friend-object")
-				).done (data) ->
-					if data.success
-						friend = $("#friend-"+data.friend_id)
-						btn = $("#friend-btn-"+data.friend_id)
-						friend.attr "data-content", "<button class='btn btn-success'>Amizade solicitada</button>"
-						btn.text("Amizade solicitada").toggleClass("btn-default btn-success")
+    ###
+    Controla a requisição, aceitação e destruição de amizades
+    ###
+    FriendActions: ->
+      # Requisição de amizade
+      $(document).on 'click', '.friend-request', ->
+        $.ajax(
+          url: "/friendships/request"
+          data:
+            friend: $(this).attr("data-friend-object")
+        ).done (data) ->
+          if data.success
+            friend = $("#friend-"+data.friend_id)
+            btn = $("#friend-btn-"+data.friend_id)
+            friend.attr "data-content", "<button class='btn btn-success'>Amizade solicitada</button>"
+            btn.text("Amizade solicitada").toggleClass("btn-default btn-success")

-					return
-				return
+          return
+        return

-			# Aceitar amizade
-			$(document).on 'click', '.friend-accept', ->
-				$.ajax(
-					url: "/friendships/accept"
-					data:
-						friend: $(this).attr("data-friend-object")
-				).done (data) ->
-					if data.success
-						friend = $("#friend-"+data.friend_id)
-						friend.attr "data-content", "<button class='btn btn-success'>Amizade aceita</button>"
+      # Aceitar amizade
+      $(document).on 'click', '.friend-accept', ->
+        $.ajax(
+          url: "/friendships/accept"
+          data:
+            friend: $(this).attr("data-friend-object")
+        ).done (data) ->
+          if data.success
+            friend = $("#friend-"+data.friend_id)
+            friend.attr "data-content", "<button class='btn btn-success'>Amizade aceita</button>"

-					return
-				return
+          return
+        return

-			# Desfazer amizade
-			$(document).on 'click', '.friend-destroy', ->
-				$.ajax(
-					url: "/friendships/destroy"
-					data:
-						friend: $(this).attr("data-friend-object")
-				).done (data) ->
-					if data.success
-						friend = $("#friend-"+data.friend_id)
-						friend.attr "data-content", "<button class='btn btn-success'>Amizade desfeita</button>"
+      # Desfazer amizade
+      $(document).on 'click', '.friend-destroy', ->
+        $.ajax(
+          url: "/friendships/destroy"
+          data:
+            friend: $(this).attr("data-friend-object")
+        ).done (data) ->
+          if data.success
+            friend = $("#friend-"+data.friend_id)
+            friend.attr "data-content", "<button class='btn btn-success'>Amizade desfeita</button>"

-					return
-				return
-
-			return
+          return
+        return
+
+      return

-	$ ->
-		App.init()
-		return
+  $ ->
+    App.init()
+    return

-	return
+  return
 ) jQuery


Potential bug in name discovery fixed

Potential bug in name discovery fixed

--- lib/codo.coffee
+++ lib/codo.coffee
@@ -89,11 +89,11 @@
     if FS.existsSync(Path.join path, 'package.json')
       name = JSON.parse(FS.readFileSync Path.join(path, 'package.json'), 'utf-8')['name']

-    else if FS.existsSync(Path.join path, '.git', 'config')
-      config = fs.readFileSync(Path.join(path, '.git', 'config'), 'utf-8')
+    if !name && FS.existsSync(Path.join path, '.git', 'config')
+      config = FS.readFileSync(Path.join(path, '.git', 'config'), 'utf-8')
       name   = /github\.com[:/][^/]+\/(.*)\.git/.exec(config)?[1]

-    else
+    if !name
       name = Path.basename(path)

     return name.charAt(0).toUpperCase() + name.slice(1)


Material: add documentation on how to add custom shaders.

--- engine/material.coffee
+++ engine/material.coffee
@@ -62,6 +62,9 @@
 CD_MTFACE = 5
 CD_ORCO = 14
 CD_TANGENT = 18
+CD_SHAPE_KEY = 99 # not supplied by blender but by the export script
+CD_ARMATURE_DEFORM = 88 # not supplied by blender but by the export script
+CD_ARMATURE_DEFORM_6 = 86 # not supplied by blender but by the export script
 GPU_DATA_1I = 1
 GPU_DATA_1F = 2
 GPU_DATA_2F = 3
@@ -131,6 +134,31 @@
         @alpha = params.alpha

 class Material
+    # data is an object with:
+    # * vertex: string with vertex shader code
+    #       (optional, auto generated here for blender materials)
+    # * fragment: string with vertex shader code,
+    #       or list of strings (concatenated here)
+    # * uniforms: list of uniforms, each have this format:
+    #   {
+    #       varname: name of GLSL variable
+    #       type: (optional) supply a GPU_DYNAMIC_* constant
+    #             to have it assigned to some scene parameter
+    #             Otherwise, mesh.custom_uniform_values will be used instead.
+    #       image: If type is GPU_DYNAMIC_SAMPLER_2DIMAGE, put the texture name
+    #             here, it must be defined in context.textures
+    #   }
+    #   Data type of each uniform is inferred from the type or the custom value.
+    # * attributes: list of vertex attributes, each have this format:
+    #   {
+    #       varname: name of GLSL variable
+    #       type: one of these:
+    #        * CD_MTFACE: Texture UV coordinates, vec2
+    #        * CD_MCOL: Vertex color, vec4
+    #        * CD_TANGENT: Vertex tangent vector (for normal mapping), vec4
+    #        * there are more types but of limited use outside Blender for now
+    #   }
+    #   There are two implicit vec3 attributes: vertex, vnormal.
     constructor: (@context, data, @scene) ->
         if @context.all_materials.indexOf(@) == -1
             @context.all_materials.push @
@@ -289,6 +317,8 @@
                     var_strand_type = u.gltype
                 when -1 # custom
                     var_custom.push u.varname
+                when undefined # custom
+                    var_custom.push u.varname
                 else
                     console.log u
                     console.log "Warning: unknown uniform", u.varname, \
@@ -321,14 +351,14 @@
                     attribute_asgn += v+"="+a.varname+";\n"
                     @attrib_locs[a.varname] = -1
                 when CD_TANGENT # tangent vectors
-                    attribute_decl += "attribute vec4 tangent;\n"\
+                    attribute_decl += "attribute vec4 "+a.varname+";\n"\
                         +"varying vec4 "+v+";\n"
                     #attribute_asgn += v+".xyz = normalize("\
                         #+var_model_view_matrix+"*vec4("+a.varname+",0.0)).xyz;\n"
-                    attribute_asgn += v+".xyz = normalize(("+var_model_view_matrix+"*vec4(tangent.xyz,0)).xyz);\n"
-                    attribute_asgn += v+".w = tangent.w;\n"
-                    @attrib_locs["tangent"] = -1
-                when 99 # shape key
+                    attribute_asgn += v+".xyz = normalize(("+var_model_view_matrix+"*vec4("+a.varname+".xyz,0)).xyz);\n"
+                    attribute_asgn += v+".w = "+a.varname+".w;\n"
+                    @attrib_locs[a.varname] = -1
+                when CD_SHAPE_KEY # shape key
                     num_shapes = a.count
                     for i in [0...num_shapes]
                         attribute_decl += "attribute vec3 shape"+i+";\n"
@@ -336,7 +366,7 @@
                         @attrib_locs["shape"+i] = -1
                         @attrib_locs["shapenor"+i] = -1
                     uniform_decl += "uniform float shapef["+num_shapes+"];"
-                when 88 # armature deform
+                when CD_ARMATURE_DEFORM # armature deform
                     num_bones = a.count
                     @num_bone_uniforms = num_bones
                     attribute_decl += "attribute vec4 weights;\n"
@@ -362,7 +392,7 @@
                     # holds mesh -> armature transformation and use that in the code above
                     # but add an optimization pass at export to apply mesh transformations
                     # where possible.
-                when 86 # armature deform, 6 weights
+                when CD_ARMATURE_DEFORM_6 # armature deform, 6 weights
                     num_bones = a.count
                     @num_bone_uniforms = num_bones
                     attribute_decl += "attribute vec4 weights;\n"
@@ -493,6 +523,7 @@
         if not gl.getShaderParameter(fragment_shader, gl.COMPILE_STATUS) and not gl.isContextLost()
             error_msg = """Error compiling fragment shader of material #{@name}
             #{gl.getShaderInfoLog fragment_shader}"""
+            console.log fs
             # ext = gl.getExtension "WEBGL_debug_shaders"
             # if ext
             #     console.log  '\n' + ext.getTranslatedShaderSource(fragment_shader)).split('\n')
@@ -650,4 +681,24 @@

         return cloned

-module.exports = {Material}
+module.exports = {Material,
+CD_MCOL, CD_MTFACE, CD_ORCO, CD_TANGENT, CD_SHAPE_KEY, CD_ARMATURE_DEFORM,
+CD_ARMATURE_DEFORM_6, GPU_DATA_1I, GPU_DATA_1F, GPU_DATA_2F, GPU_DATA_3F,
+GPU_DATA_4F, GPU_DATA_9F, GPU_DATA_16F, GPU_DATA_4UB, GPU_DYNAMIC_AMBIENT_COLOR,
+GPU_DYNAMIC_GROUP_LAMP, GPU_DYNAMIC_GROUP_MAT, GPU_DYNAMIC_GROUP_MISC,
+GPU_DYNAMIC_GROUP_MIST, GPU_DYNAMIC_GROUP_OBJECT, GPU_DYNAMIC_GROUP_SAMPLER,
+GPU_DYNAMIC_GROUP_WORLD, GPU_DYNAMIC_HORIZON_COLOR, GPU_DYNAMIC_LAMP_ATT1,
+GPU_DYNAMIC_LAMP_ATT2, GPU_DYNAMIC_LAMP_DISTANCE, GPU_DYNAMIC_LAMP_DYNCO,
+GPU_DYNAMIC_LAMP_DYNCOL, GPU_DYNAMIC_LAMP_DYNENERGY, GPU_DYNAMIC_LAMP_DYNIMAT,
+GPU_DYNAMIC_LAMP_DYNPERSMAT, GPU_DYNAMIC_LAMP_DYNVEC, GPU_DYNAMIC_LAMP_SPOTBLEND,
+GPU_DYNAMIC_LAMP_SPOTSCALE, GPU_DYNAMIC_LAMP_SPOTSIZE, GPU_DYNAMIC_MAT_ALPHA,
+GPU_DYNAMIC_MAT_AMB, GPU_DYNAMIC_MAT_DIFFRGB, GPU_DYNAMIC_MAT_EMIT,
+GPU_DYNAMIC_MAT_HARD, GPU_DYNAMIC_MAT_REF, GPU_DYNAMIC_MAT_SPEC,
+GPU_DYNAMIC_MAT_SPECRGB, GPU_DYNAMIC_MIST_COLOR, GPU_DYNAMIC_MIST_DISTANCE,
+GPU_DYNAMIC_MIST_ENABLE, GPU_DYNAMIC_MIST_INTENSITY, GPU_DYNAMIC_MIST_START,
+GPU_DYNAMIC_MIST_TYPE, GPU_DYNAMIC_NONE, GPU_DYNAMIC_OBJECT_AUTOBUMPSCALE,
+GPU_DYNAMIC_OBJECT_COLOR, GPU_DYNAMIC_OBJECT_IMAT,
+GPU_DYNAMIC_OBJECT_LOCTOVIEWIMAT, GPU_DYNAMIC_OBJECT_LOCTOVIEWMAT,
+GPU_DYNAMIC_OBJECT_MAT, GPU_DYNAMIC_OBJECT_VIEWIMAT, GPU_DYNAMIC_OBJECT_VIEWMAT,
+GPU_DYNAMIC_SAMPLER_2DBUFFER, GPU_DYNAMIC_SAMPLER_2DIMAGE,
+GPU_DYNAMIC_SAMPLER_2DSHADOW, GPU_DYNAMIC_ZENITH_COLOR}


Slider Component: Added event emits and fixed a bug about events

--- client/Framework/components/sliderbar/KDSliderBarView.coffee
+++ client/Framework/components/sliderbar/KDSliderBarView.coffee
@@ -8,7 +8,6 @@
     options.drawBar    ?= yes
     options.showLabels ?= yes
     options.snap       ?= yes
-    data.handles

     super options, data

@@ -21,8 +20,8 @@

     sortRef = (a,b) ->
       return -1 if a.getData().value < b.getData().value
-      return 1 if a.getData().value > b.getData().value
-      return 0
+      return  1 if a.getData().value > b.getData().value
+      return  0
     @handles.sort sortRef

   drawBar:->
@@ -82,15 +81,15 @@
     {maxValue, minValue, interval} = @getOptions()

     if @handles.length is 1
-      @handles.first.leftLimit     = minValue
-      @handles.first.rightLimit    = maxValue
+      @handles.first.data.leftLimit     = minValue
+      @handles.first.data.rightLimit    = maxValue
     else
       for handle in @handles
         i    = @handles.indexOf(handle)
         data = handle.data

         data.leftLimit  = @handles[i-1]?.getData().value + interval or minValue
-        data.rightLimit = @handles[i+1]?.getData().value - interval or maxValue
+        data.rightLimit = @handles[i+1]?.getData().value - interval or maxValue

   viewAppended:->
     @createHandles()
@@ -101,7 +100,7 @@
 class KDSliderBarHandleView extends KDCustomHTMLView
   constructor:(options = {}, data = {})->
     options.cssClass  = "handle"
-    options.name
+    options.name    or= "defaultHandle"
     data.value       ?= 0

     super options, data
@@ -113,18 +112,23 @@
     @on "mousedown", ->
       @parent.bindEvent "mousemove"
       windowController.setDragInAction true
+      @parent.emit "dragstart"

       @parent.on "mousemove", (event) =>
         relPos  = (((event.pageX - @parent.getX())/@parent.getWidth())*100)
         @setValue ((relPos*(maxValue - minValue))/100)+minValue

-      @on "mouseup", dragEnded
-      @parent.on "mouseleave", dragEnded
+        @parent.emit "draginaction"

-    dragEnded = ->
+      @on "mouseup", dragDidEnd
+      @parent.on "mouseleave", dragDidEnd
+
+    dragDidEnd =(e) ->
+      @off "mouseup"
       @parent.off "mousemove"
       windowController.setDragInAction false
       @snap() if @parent.getOption "snap"
+      @parent.emit "dragend"

   getPosition:->
     {maxValue, minValue} = @parent.getOptions()
@@ -147,6 +151,7 @@
     @parent.drawBar()
     @parent.setLimits()
     @setX "#{@getPosition()}%"
+    @parent.emit "valuechange"

   getSnappedValue:->
     {interval}  = @parent.getOptions()
@@ -173,4 +178,5 @@
   viewAppended:->
     @setX "#{@getPosition()}%"
     @attachEvents()
-    @snap() if @parent.getOption "snap"+    @snap() if @parent.getOption "snap"
+


Hackathon: apllications are closed!

Hackathon: apllications are closed!

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -50,16 +50,16 @@

     if KD.isLoggedIn()
     then @createApplyWidget()
-    else
-
-      @signUpForm = new HomeRegisterForm
-        cssClass    : 'login-form register no-anim'
-        buttonTitle : 'LET\'S DO THIS'
-        callback    : (formData) =>
-          router.requireApp 'Login', (controller) =>
-            controller.getView().showExtraInformation formData, @signUpForm
-
-      @addSubView @signUpForm, '.form-wrapper'
+    else @$('.form-wrapper').hide()
+
+    #   @signUpForm = new HomeRegisterForm
+    #     cssClass    : 'login-form register no-anim'
+    #     buttonTitle : 'LET\'S DO THIS'
+    #     callback    : (formData) =>
+    #       router.requireApp 'Login', (controller) =>
+    #         controller.getView().showExtraInformation formData, @signUpForm
+
+    #   @addSubView @signUpForm, '.form-wrapper'

   createApplyWidget: ->
@@ -96,11 +96,12 @@
       title : "Hey, #{firstName or nickname}!"

-    @section.addSubView @button = new KDButtonView
-      cssClass : 'apply-button solid green medium'
-      title    : 'APPLY NOW'
-      loader   : yes
-      callback : @bound 'apply'
+    # @section.addSubView @button = new KDButtonView
+    # @section.addSubView @button = new KDButtonView
+    #   cssClass : 'apply-button solid green medium'
+    #   title    : 'APPLY NOW'
+    #   loader   : yes
+    #   callback : @bound 'apply'

   apply: ->
@@ -110,13 +111,13 @@
       type        : 'POST'
       xhrFields   : withCredentials : yes
       success     : (stats) =>
-        @button.hideLoader()
+        # @button.hideLoader()
         KD.campaignStats = stats
         @updateWidget()
         KD.singletons.router.handleRoute '/Hackathon'
       error       : (xhr) =>
         {responseText} = xhr
-        @button?.hideLoader()
+        # @button?.hideLoader()
         new KDNotificationView title : responseText
         KD.singletons.router.handleRoute '/Hackathon'

@@ -345,8 +346,7 @@
       </video>

To celebrate 500,000 developers...

ANNOUNCING THE WORLD’S FIRST GLOBAL VIRTUAL #HACKATHON

-
Let's hack together, wherever we are!

-
More than $#{prize.toLocaleString()} in cash prizes.

+
Applications are now closed!

#{@getStats()}

@@ -435,7 +435,7 @@

Third prize: $2,000

Student Hacker (High school or less):

-    (Please note: Student hackers are not precluded from submitting their project for the Top Hacker
+    (Please note: Student hackers are not precluded from submitting their project for the Top Hacker
     category.)

First prize: $1,500

@@ -607,18 +607,7 @@

How to submit your project

-
Send the following details to hackathon@koding.com
-

-
Koding VM URL where the judges can see your project.

-
URL to the team in the hackathon Git repo

-
A brief description of your project (not more than 250 words).

-
A brief introduction to your team members (who did what).

-
How your project addresses the theme of the hackathon.

-
What tools did you use to collaborate.

-

-
Your submission should have a time label less than #{secondDay} 2359 PST.
-          Submissions received after the deadline will not be considered. No exceptions.
-

+
Please follow the submission details that were emailed to you.

Awesome API's for you to check out


test/myteam: added selector helper functions

--- client/test/lib/dashboard/myteam.coffee
+++ client/test/lib/dashboard/myteam.coffee
@@ -180,4 +180,14 @@
     myteamhelpers.newInviteFromResendModal browser, 'member'
     browser.pause 5000
     myteamhelpers.newInviteFromResendModal browser, 'admin'
-    browser.end()+    browser.end()
+
+
+selector = (type, index) ->
+  sectionSelector = '.HomeAppView--section.teammates'
+  memberSelector = '.kdview.kdlistitemview.kdlistitemview-member'
+  "#{sectionSelector} .ListView-row:nth-of-type(#{index}) #{memberSelector} .#{type}"
+
+
+action = (index) ->
+  ".menu-class ul.ButtonWithMenuItemsList li:nth-of-type(#{index})"


Removed debug output

Removed debug output

--- app/assets/javascripts/modules/filter_form.coffee
+++ app/assets/javascripts/modules/filter_form.coffee
@@ -15,9 +15,6 @@

       sessionStorage.setItem("offer_filter_open", "false")

-    console.log startHeight
-
-
     if !$('.filter-form__expander').length
       $filterForm.prepend '
' + expandLabel + '
'

@@ -32,9 +29,6 @@

       if $filterForm.hasClass('filter-form--isCollapsed')

-        console.log startHeight
-
-
         $expander
             .html collapseLabel
             .attr 'aria-expanded', true
@@ -46,7 +40,6 @@
         sessionStorage.setItem("offer_filter_open", "true")

       else
-        console.log startHeight

         $expander
             .html expandLabel


Update Pjax hook

Update Pjax hook

--- app/assets/javascripts/nembrot.document.js.coffee
+++ app/assets/javascripts/nembrot.document.js.coffee
@@ -203,7 +203,7 @@
 document_initializers = () ->
   # Implementing a spinner may be a better idea: https://github.com/defunkt/jquery-pjax/issues/129
   $.pjax.defaults.timeout = false
-  $(document).pjax('#main a, #tools a', '[data-pjax-container]')
+  $(document).pjax('#main a, #tools a, footer a', '[data-pjax-container]')

   $(document).on 'click', 'a[href^=http]:not(.share a)', ->
     track_outbound_link(@href, 'Outbound Link', @href.toString().replace(/^https?:\/\/([^\/?#]*).*$/, '$1'))


Removed annoying ping debug prints

Removed annoying ping debug prints

--- app/assets/javascripts/oxalis/model/binary.coffee
+++ app/assets/javascripts/oxalis/model/binary.coffee
@@ -80,7 +80,7 @@
       @lastZoomStep = zoomStep.slice()
       @lastArea     = area.slice()

-      console.log "ping", @queue.roundTripTime, @queue.bucketsPerSecond, @cube.bucketCount
+      # console.log "ping", @queue.roundTripTime, @queue.bucketsPerSecond, @cube.bucketCount

       for strategy in @pingStrategies
         if strategy.inVelocityRange(1) and strategy.inRoundTripTimeRange(@queue.roundTripTime)


lib: added require of util.test

lib: added require of util.test

--- coffee/src/lib.coffee
+++ coffee/src/lib.coffee
@@ -1,6 +1,7 @@
 goog.provide 'athena.lib'

 goog.require 'athena.lib.util'
+goog.require 'athena.lib.util.test'
 goog.require 'athena.lib.util.RemoteResource'
 goog.require 'athena.lib.util.RemoteResourceInterface'


Change API reference

Change API reference

--- lib/core.coffee
+++ lib/core.coffee
@@ -158,12 +158,12 @@
 		######
 		# Call API
 		#
-		# @param {String} module The Bandcamp API module
+		# @param {String} module The Msgme API module
 		# @param {String} method Module method
 		# @param {Object} parameters Parameters to pass to the method
-		# @param {Function} callback Callback to handle Bandcamp response
+		# @param {Function} callback Callback to handle Msgme response
 		# @param {Integer} [ver] Version of the module you want to call. This is an override of defaults (latest).
-		# @return {Object} Bandcamp response
+		# @return {Object} Msgme response
 		######
 		callApi: (module, method, params, payload, callback, ver) ->
 			if(typeof callback isnt 'function')


added value marks

added value marks

--- grammars/jbase.cson
+++ grammars/jbase.cson
@@ -28,13 +28,29 @@
     'name': 'constant.numeric.jbase'
   },
   {
-    'match': '''(?x)\\b(?<!\\$)
+    'match': '@AM|@VM|@SVM'
+    'name': 'constant.attributemark.jbase'
+  },
+  {
+    'match': '''(?x)\\b(?<!\\.)
                 (ABORT|BEFORE|BREAK|CALL|CALLC|CALLDOTNET|CALLJ|CALLONEXIT|CAPTURING|CHAIN|CHANGE|CLEAR|CLEARCOMMON|CLEARDATA|CLEARFILE|CLEARINPUT|CLEARSELECT|CLOSE|CLOSESEQ|COLLECTDATA|COMMON|CONTINUE|CONVERT|CREATE|CRT|DATA|DEBUG|DEFC|DEFCE|DEFFUN|DEL|DELETE|DELETELIST|DELETESEQ|DELETEU|DIM|DIMENSION|ECHO|ENTER|EQU|EQUATE|EXECUTE|EXIT|FILELOCK|FILEUNLOCK|FIND|FINDSTR|FLUSH|FMT|FMTS|FOLD|FOOTING|FORMAT|FORMLIST|FUNCTION|GET|GETLIST|GETX|HEADING|HEADINGE|HEADINGN|HUSH|IF|IN|INPUT|INPUTNULL|INS|LOCATE|LOCK|MAT|MATBUILD|MATPARSE|MATREAD|MATREADU|MATWRITE|MATWRITEU|MSLEEP|NOBUFF|NULL|OBJEXCALLBACK|OPEN|OPENDEV|OPENPATH|OPENSEQ|OPENSER|OSBREAD|OSBWRITE|OSCLOSE|OSDELETE|OSOPEN|OSREAD|OSWRITE|OUT|PAGE|PAUSE|PASSLIST|PCPERFORM|PERFORM|PRECISION|PRINT|PRINTER|PRINTERR|PROCREAD|PROCWRITE|PROGRAM|PROMPT|READ|READBLK|READL|READLIST|READNEXT|READSELECT|READSEQ|READT|READU|READV|READVL|READVU|READXML|RELEASE|REMOVE|RETURN|RETURNING|REWIND|RQM|RTNDATA|RTNLIST|SEEK|SELECT|SETTING|SEND|SENDX|SLEEP|SSELECT|SSELECTN|SSELECTV|STOP|SUBROUTINE|TIMEOUT|UNLOCK|UDTEXECUTE|WAKE|WEOF|WEOFSEQ|WRITE|WRITEBLK|WRITELIST|WRITESEQ|WRITET|WRITEU|WRITEV|WRITEVU|WRITEXML)\\b
              '''
     'name': 'keyword.statement.jbase'
   },
   {
-    'match': '''(?x)\\b(?<!\\$)
+    'match': '''(?x)\\b(?<!\\.)
+                (ABS|ABSS|ADDS|ALPHA|ANDS|ASCII|ASSIGNED|BITCHANGE|BITCHECK|BITRESET|BITSET|BYTELEN|CATS|CHANGETIMESTAMP|CHAR|CHARS|CHDIR|CHECKSUM|COL1|COL2|COMPARE|CONVERT|COS|COUNT|COUNTS|DATE|DCOUNT|DECRYPT|DIV|DIVS|DOWNCASE|DQUOTE|DROUND|DTX|DYNTOXML|EBCDIC|ENCRYPT|EQS|EREPLACE|EXIT|EXP|EXTRACT|FIELD|FIELDS|FILEINFO|GES|GETCWD|GETENV|GETUSERGROUP|GROUP|ICONV|ICONVS|IFS|INDEX|INMAT|INSERT|INT|ISALPHA|ISALNUM|ISDIGIT|ISPRINT|ISCNTRL|ISSPACE|ISLOWER|ISUPPER|JBASECOREDUMP|JBASETHREADCreate|JBASETHREADStatus|ITYPE|KEYIN|LATIN1|LEFT|LEN|LENS|LENDP|LES|LN|LOCALDATE|LOCALTIME|LOWCASE|LOWER|MAKETIMESTAMP|MATCHFIELD|MOD|MODS|MULS|NEGS|NES|NOTS|NUM|NUMS|OCONV|OCONVS|ORS|PUTENV|PWR|QUOTE|RAISE|RECORDLOCKED|REPLACE|RIGHT|RND|SADD|SDIV|SENTENCE|SEQ|SEQS|SIN|SMUL|SORT|SOUNDEX|SPACE|SPACES|SPLICE|SPOOLER|SQRT|SQUOTE|SSUB|STR|STRS|SUM|SWAP|SYSTEM|SUBS|SUBSTRINGS|TAN|TIME|TIMEDATE|TIMEDIFF|TIMESTAMP|TRANS|TRIM|TRIMB|TRIMBS|TRIMF|TRIMFS|UNASSIGNED|UNIQUEKEY|UPCASE|UTF8|XLATE|XMLTODYN|XMLTOXML|XTD|JQLCOMPILE|JQLEXECUTE|JQLFETCH|JQLGETPROPERTY|JQLPUTPROPERTY)\\b
+             '''
+    'name': 'keyword.function.jbase'
+  },
+  {
+    'match': '''(?x)\\b(?<!\\.)
+                (BEGIN|CASE|DO|ELSE|END|ERROR|FOR|FROM|GO|GOSUB|GOTO|IF|LOOP|NEXT|ON|REPEAT|THEN|TO|UNTIL|WHILE)\\b
+             '''
+    'name': 'keyword.conditional.jbase'
+  },
+  {
+    'match': '''(?x)\\b(?<!\\.)
                 (AND|EQ|GT|LT|MATCH|MATCHES|NE|NOT|OFF|ON|OR)\\b
              '''
     'name': 'constant.operator.jbase'
@@ -76,10 +92,6 @@
     'name': 'constant.operator.jbase'
   },
   {
-    'match': '(!|&&|\\|\\|)'
-    'name': 'constant.operator.jbase'
-  },
-  {
     'match': '(\\||&)'
     'name': 'constant.operator.jbase'
   }


Testing atom commit

Testing atom commit

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -7,6 +7,7 @@
 # Rules to be implemented. Please add to repository and link here to include.
 patterns: [
     {
+        comment: 'Always look for comments first so no bad matches happen.'
         include: '#commentMeta'
     }
     {


render guild faster

render guild faster

--- src/coffee/controllers/player/player-guild-ctrl.coffee
+++ src/coffee/controllers/player/player-guild-ctrl.coffee
@@ -10,7 +10,7 @@
       player = CurrentPlayer.getPlayer()
       $scope.currentlyInGuild = player?.guild

-      if $scope.guild
+      if $scope.currentlyInGuild
         $scope.setupGuildData()
         $scope.loadBuffsIntoHash()
         $scope.getDonationTiers()


Remove unnecessary fat arrow

Remove unnecessary fat arrow

--- cms/static/coffee/src/views/week.coffee
+++ cms/static/coffee/src/views/week.coffee
@@ -14,7 +14,7 @@
       new CMS.Views.Module(el: this).render()
     return @

-  edit: (event) =>
+  edit: (event) ->
     event.preventDefault()
     CMS.replaceView(new CMS.Views.WeekEdit())


Fix TransferComplete fault not saved

Fix TransferComplete fault not saved

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -155,7 +155,7 @@
       for k, v of operation.retries
         sessionContext.retries[k] = v

-      recordFault(sessionContext, fault)
+      recordFault(sessionContext, fault, operation.provisions, operation.channels)

     res = soap.response({
       id : rpc.id,


Indeed don't pass the fact but the collection

--- core/app/backbone/collections/ndp_evidence_collection.coffee
+++ core/app/backbone/collections/ndp_evidence_collection.coffee
@@ -7,9 +7,9 @@
     super
     unless models and models.length > 0
       @reset [
-        new OpinionatersEvidence({type: 'believe'   }, fact: @fact),
-        new OpinionatersEvidence({type: 'disbelieve'}, fact: @fact),
-        new OpinionatersEvidence({type: 'doubt'     }, fact: @fact)
+        new OpinionatersEvidence {type: 'believe'},    collection: this
+        new OpinionatersEvidence {type: 'disbelieve'}, collection: this
+        new OpinionatersEvidence {type: 'doubt'},      collection: this
       ]

   comparator: (item) -> - item.get('impact')


Add .zip to filename in releases

Add .zip to filename in releases

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -123,7 +123,7 @@
         token: process.env.TOKEN

       Q
-        .all dirs.map ({dir, name, zip}) ->
+        .all dirs.map ({name, zip}) ->
           d = Q.defer()
           console.log "zip #{name} to #{zip}"
           zip = spawn 'zip', [zip, '-r', name]
@@ -163,7 +163,7 @@
           github.releases.createRelease
             owner: 'minodisk'
             repo: 'markn'
-            tag_name: 'v0.0.2' #TODO bump package.json
+            tag_name: 'v0.0.3' #TODO bump package.json
           , (err, res) ->
             throw err if err
             console.log JSON.stringify res, null, 2
@@ -171,19 +171,21 @@
           d.promise

         .then (id) ->
+          console.log 'complete to create new release: #{id}'
+
           Q
-            .all dirs.map ({name, zip}) ->
+            .all dirs.map ({zip}) ->
               d = Q.defer()
               github.releases.uploadAsset
                 owner: 'minodisk'
                 repo: 'markn'
                 id: id
-                name: name
+                name: zip
                 filePath: zip
               , (err, res) ->
                 throw err if err?
                 console.log JSON.stringify res, null, 2
                 d.resolve()
-              d
+              d.promise
             .then ->
               console.log 'complete to upload'


Disable connect gulp pluggin (now useless)

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -16,7 +16,7 @@
 watch = require("gulp-watch")
 size = require("gulp-filesize")
 notify = require("gulp-notify")
-connect = require("gulp-connect")
+# connect = require("gulp-connect")
 scsslint = require("gulp-scss-lint")
 newer = require("gulp-newer")
 cache = require("gulp-cached")
@@ -194,12 +194,12 @@
         .pipe(gulp.dest("#{paths.dist}/plugins/"))

-gulp.task "connect", ->
-    connect.server({
-        root: paths.dist
-        port: 9000
-        livereload: true
-    })
+# gulp.task "connect", ->
+#     connect.server({
+#         root: paths.dist
+#         port: 9000
+#         livereload: true
+#     })

 gulp.task "express", ->
@@ -236,7 +236,7 @@
     "copy",
     "coffee",
     "jslibs",
-    "connect",
+    # "connect",
     "express",
     "watch"
 ]


Fix typo

Fix typo

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -10,7 +10,7 @@
 cssmin      = require 'gulp-cssmin'
 addsrc      = require 'gulp-add-src'
 changed     = require 'gulp-changed'
-shortand    = require 'gulp-shortand'
+shorthand    = require 'gulp-shorthand'
 pkg         = require './package.json'
 _s          = require 'underscore.string'
 prefix      = require 'gulp-autoprefixer'
@@ -69,7 +69,7 @@
   .pipe concat dist.name + '.css'
   .pipe prefix()
   .pipe strip all: true
-  .pipe shortand()
+  .pipe shorthand()
   .pipe cssmin()
   .pipe header banner, pkg: pkg
   .pipe gulp.dest dist.css
@@ -84,7 +84,7 @@
     .pipe coffee()
     .pipe addsrc src.js.vendor
     .pipe concat dist.name + ".#{lang}.js"
-    .pipe uglify mangle: false
+    .pipe uglify()
     .pipe header banner, pkg: pkg
     .pipe gulp.dest dist.js
     return
@@ -95,7 +95,7 @@
   .pipe coffee()
   .pipe addsrc src.js.vendor
   .pipe concat dist.name + ".js"
-  .pipe uglify mangle: false
+  .pipe uglify()
   .pipe header banner, pkg: pkg
   .pipe gulp.dest dist.js
   return


One more small fix for gulpfie watcher.

One more small fix for gulpfie watcher.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -42,7 +42,7 @@

 COFFEE_SRC_DIR = "#{THEME_PATH}/src/coffee"
 COFFEE_SRC = "#{COFFEE_SRC_DIR}/main.coffee"
-CJXS_SRC = "#{COFFEE_SRC_DIR}/**/*.cjsx"
+CJSX_SRC = "#{COFFEE_SRC_DIR}/**/*.cjsx"

 SVG_SRC = "#{THEME_PATH}/src/svg/**/*.svg"

@@ -146,7 +146,10 @@
   do livereload.listen
   bIsDevel = yes
   gulp.watch "#{STYLUS_SRC_DIR}/**/*.styl", ['stylus']
-  gulp.watch COFFEE_SRC, ['coffee']
+  gulp.watch [
+    "#{COFFEE_SRC_DIR}/**/.coffee"
+    "#{CJSX_SRC}"
+  ], ['coffee']
   gulp.watch SVG_SRC, ['svg']
   gulp.watch "#{THEME_PATH}/views/**/*.jade", ['refresh']


Remove depency to nw builder wrapper

Remove depency to nw builder wrapper

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -3,6 +3,7 @@
 mergeStream = require 'merge-stream'
 runSequence = require 'run-sequence'
 manifest = require './package.json'
+NwBuilder = require 'nw-builder'
 $ = require('gulp-load-plugins')()

 # Remove directories used by the tasks
@@ -16,19 +17,19 @@
     if process.argv.indexOf('--toolbar') > 0
       shelljs.sed '-i', '"toolbar": false', '"toolbar": true', './src/package.json'

-    gulp.src './src/**'
-      .pipe $.nwBuilder
-        platforms: [platform]
-        version: '0.12.3'
-        winIco: if process.argv.indexOf('--noicon') > 0 then undefined else './assets-windows/icon.ico'
-        macIcns: './assets-osx/icon.icns'
-        macZip: true
-        macPlist:
-          NSHumanReadableCopyright: 'aluxian.com'
-          CFBundleIdentifier: 'com.aluxian.messengerfordesktop'
-      .on 'end', ->
-        if process.argv.indexOf('--toolbar') > 0
-          shelljs.sed '-i', '"toolbar": true', '"toolbar": false', './src/package.json'
+    nw = new NwBuilder(
+      files: './src/**'
+      platforms: [platform]
+      version: '0.12.3'
+      winIco: if process.argv.indexOf('--noicon') > 0 then undefined else './assets-windows/icon.ico'
+      macIcns: './assets-osx/icon.icns'
+      macZip: true
+      macPlist:
+        NSHumanReadableCopyright: 'aluxian.com'
+        CFBundleIdentifier: 'com.aluxian.messengerfordesktop'
+    )
+
+    nw.build()

 # Only runs on OSX (requires XCode properly configured)
 gulp.task 'sign:osx64', ['build:osx64'], ->


chore - moved redid spin up from .sh to gulp script

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -80,6 +80,7 @@

 #gulp.task 'server', -> spawn 'bash', ['./scripts/start.sh'], {stdio: 'inherit'} # node-dev
 gulp.task 'server', -> spawn 'foreman', ['start'], {stdio: 'inherit'} # foreman
+gulp.task 'redis', -> spawn 'redis-server', ['./scripts/redis.conf'], {stdio: 'inherit'}

 # ==========================

@@ -100,4 +101,4 @@

 gulp.task 'prod', ['css', 'js', 'html']

-gulp.task 'default', ['dev', 'server']+gulp.task 'default', ['dev', 'redis', 'server']


Watch by default, add "gulp nowatch" variant

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -10,10 +10,10 @@
     .pipe source 'OperationalTransform.js'
     .pipe gulp.dest './lib'

-# Watch
+
 tasks = [ 'build' ]

-gulp.task 'watch', tasks, ->
-  gulp.watch [ 'src/**/*.coffee' ], ['build']
+gulp.task 'default', tasks, ->
+  gulp.watch [ 'src/**/*.coffee' ], [ 'build' ]

-gulp.task 'default', tasks
+gulp.task 'nowatch', tasks


adding jade refresh to pipeline

adding jade refresh to pipeline

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -52,6 +52,7 @@
   browserSync.init({server: "build", port:3000})
   gulp.watch "assets/**/*.{js,coffee}", ['js:build:preview']
   gulp.watch "./assets/stylus/**/*.styl", ['css:build:preview']
+  gulp.watch "templates/**/*.jade", ['build_wintersmith']

 gulp.task "build:clean", ->
@@ -60,6 +61,7 @@

 gulp.task "build_log", ->
   return gutil.log gutil.colors.white.bgBlue("Build..."), "Complete"
+

 # =================================================================
 # css build tasks


add gulp task for combining html partials

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -8,6 +8,7 @@
 uglify = require('gulp-uglifyjs')
 minifycss = require('gulp-minify-css')
 sass = require('gulp-sass')
+include = require('gulp-file-include')
 autoprefixer = require('gulp-autoprefixer')
 header = require('gulp-header')
 size = require('gulp-size')
@@ -107,10 +108,19 @@
     }))
     .pipe(gulp.dest(sassBuildDir))

+gulp.task 'compile-html', ->
+  gulp
+    .src('./src/html/*.html')
+    .pipe(include(
+      prefix: '@@'
+      basepath: '@file'))
+    .pipe(gulp.dest('./'))
+
 gulp.task 'refresh', (callback) ->
   run(
     'compile-js',
     'compile-dev-css',
+    'compile-html',
     'browserify',
     'html',
     callback
@@ -142,6 +152,7 @@
     .watch [
       pluginScript
       './src/sass/*'
+      './src/html/*'
       './example/*/*.html'
       './example/*/script.js'
     ], [


Fix JS exception on empty mailboxes

Fix JS exception on empty mailboxes

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -185,16 +185,17 @@
             return

         # listen to scroll events
-        scrollable = @refs.scrollable.getDOMNode()
-        setTimeout =>
-            scrollable.removeEventListener 'scroll', @_loadNext
-            scrollable.addEventListener 'scroll', @_loadNext
-            @_loadNext()
-            # a lot of event can make the "more messages" label visible,
-            # so we check every few seconds
-            if not @_checkNextInterval?
-                @_checkNextInterval = window.setInterval @_loadNext, 10000
-        , 0
+        if @refs.scrollable?
+            scrollable = @refs.scrollable.getDOMNode()
+            setTimeout =>
+                scrollable.removeEventListener 'scroll', @_loadNext
+                scrollable.addEventListener 'scroll', @_loadNext
+                @_loadNext()
+                # a lot of event can make the "more messages" label visible,
+                # so we check every few seconds
+                if not @_checkNextInterval?
+                    @_checkNextInterval = window.setInterval @_loadNext, 10000
+            , 0

     componentDidMount: ->
         @_initScroll()
@@ -204,10 +205,11 @@
         @_handleRealtimeGrowth()

     componentWillUnmount: ->
-        scrollable = @refs.scrollable.getDOMNode()
-        scrollable.removeEventListener 'scroll', @_loadNext
-        if @_checkNextInterval?
-            window.clearInterval @_checkNextInterval
+        if @refs.scrollable?
+            scrollable = @refs.scrollable.getDOMNode()
+            scrollable.removeEventListener 'scroll', @_loadNext
+            if @_checkNextInterval?
+                window.clearInterval @_checkNextInterval


Socialapicontroller: try to fetch from cache first then create the channel instance

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -204,17 +204,36 @@

   mapChannel = (channel) ->
-
-    data                     = channel.channel
-    data._id                 = data.id
-    data.isParticipant       = channel.isParticipant
-    data.participantCount    = channel.participantCount
-    data.participantsPreview = mapAccounts channel.participantsPreview
-    data.unreadCount         = channel.unreadCount
-    data.lastMessage         = mapActivity channel.lastMessage  if channel.lastMessage
-
-
-    channelInstance = new remote.api.SocialChannel data
+    { socialapi } = kd.singletons
+
+    data  = channel.channel
+
+    # hold state of cache hit
+    cacheFound = no
+
+    # item is initially our data
+    item = data
+
+    # if we find the channel in cache, replace item with it
+    if cacheItem = socialapi.retrieveCachedItem item.typeConstant, item.id
+      cacheFound = yes
+      item = cacheItem
+
+    item._id                 = data.id
+    item.isParticipant       = channel.isParticipant
+    # we only allow name, purpose and payload to be updated
+    item.payload             = data.payload
+    item.name                = data.name
+    item.purpose             = data.purpose
+    item.participantCount    = channel.participantCount
+    item.participantsPreview = mapAccounts channel.participantsPreview
+    item.unreadCount         = channel.unreadCount
+    item.lastMessage         = mapActivity channel.lastMessage  if channel.lastMessage
+
+    unless cacheFound
+      channelInstance = new remote.api.SocialChannel item
+    else
+      channelInstance = item

     kd.singletons.socialapi.cacheItem channelInstance

@@ -574,6 +593,11 @@
       validateOptionsWith: ['name']
       mapperFn           : mapChannel

+    update               : channelRequesterFn
+      fnName             : 'update'
+      validateOptionsWith: ["id"]
+      mapperFn           : mapChannel
+
     list                 : channelRequesterFn
       fnName             : 'fetchChannels'
       mapperFn           : mapChannels


fix(aix): remove unused snippet (#156)

fix(aix): remove unused snippet (#156)

--- snippets/language-coursescript.cson
+++ snippets/language-coursescript.cson
@@ -1312,7 +1312,6 @@
     'body':"""
       [TYPE AIXPresentationTeaching]
       Video(stream=true): $1
-      Video(video_point=true): $2
       \"""
       display_layout: REGULAR
       \"""


update sequece from Text to Audio

update sequece from Text to Audio

--- snippets/language-coursescript.cson
+++ snippets/language-coursescript.cson
@@ -80,9 +80,9 @@
     'prefix': 'seq'
     'body': """
       [TYPE Sequencing]
-      Text: $1
-      Text: $2
-      Text: $3
+      Audio: $1
+      Audio: $2
+      Audio: $3
     """
   'RP':
     'prefix': 'rp'


IDE: added temp back to chat button

IDE: added temp back to chat button

--- client/ide/views/chat/settingspane.coffee
+++ client/ide/views/chat/settingspane.coffee
@@ -42,6 +42,11 @@
       disabled : yes
       cssClass : 'solid red hidden'
       callback : @bound 'stopSession'
+
+    @back = new KDButtonView
+      title    : 'back to chat'
+      cssClass : 'solid green mini'
+      callback : => @getDelegate().showChatPane()

     @defaultSetting = new KDSelectBox
       defaultValue  : 'edit'
@@ -114,6 +119,7 @@

Anyone who joins{{> @defaultSetting}}

     {{> @everyone}}
+    {{> @back}}
     """


Account: Password recovery is enabled for logged in users

--- client/landing/Login/AppController.coffee
+++ client/landing/Login/AppController.coffee
@@ -43,6 +43,10 @@
     KD.singleton('appManager').open 'Login', (app) ->
       app.prepareFinishRegistrationForm token  unless KD.isLoggedIn()

+  handleRecovery = ->
+    KD.singleton('appManager').open 'Login', (app)->
+      app.getView().animateToForm 'recover'
+
 #  handleFailureOfRestriction =->
 #    KD.utils.defer -> new KDNotificationView title: "Login restricted"

@@ -70,7 +74,7 @@
       '/:name?/Login/:token?'    : handler (app)-> app.getView().animateToForm 'login'
       '/:name?/Redeem'           : handler (app)-> app.getView().animateToForm 'redeem'
       '/:name?/Register/:token?' : handler (app)-> app.getView().animateToForm 'register'
-      '/:name?/Recover'          : handler (app)-> app.getView().animateToForm 'recover'
+      '/:name?/Recover'          : handleRecovery
       '/:name?/Reset'            : handler (app)-> app.getView().animateToForm 'reset'
       '/:name?/Reset/:token'     : handleResetRoute
       '/:name?/Confirm/:token'   : handleResetRoute


monitor: buggy social workers don't cause errors anymore

--- client/landing/app/MainApp/monitor.coffee
+++ client/landing/app/MainApp/monitor.coffee
@@ -87,7 +87,7 @@
             if item.indexOf(key.replace('*','')) isnt -1
               @socialWorkers++
               socialWorker = @monitorData[item]
-          socialWorker.count = @socialWorkers
+          socialWorker?.count = @socialWorkers
           return socialWorker

   getOnlineServices:->


rename KD rollbar wrapper to KD.logToExternal

--- client/landing/app/MainApp/rollbar.coffee
+++ client/landing/app/MainApp/rollbar.coffee
@@ -1,5 +1,5 @@
 # Wrapper for pushing events to Rollbar.
-logToExternal = KD.rollbar = (args) ->
+logToExternal = KD.logToExternal = (args) ->
   _rollbar.push args

 # Push status events to Rollbar.


remove underscore from settings.coffee

remove underscore from settings.coffee

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -1,5 +1,4 @@
 Path = require('path')
-_ = require("underscore")

 # These credentials are used for authenticating api requests
 # between services that may need to go over public channels
@@ -427,8 +426,13 @@
 			ca:ca_paths  # e.g.'/etc/ldap/ca_certs.pem'

 if settings.externalAuth
-	settings.nav.header = _.filter settings.nav.header, (button)-> button.url != "/register"
-
+	results = []
+	for button in settings.nav.header
+		if button.url != "/register"
+			results.push(button)
+	settings.nav.header = results
+
+
 # Compiler
 # --------
 if process.env["SANDBOXED_COMPILES"] == "true"


Simplyfiy Q integration with trade engine

--- lib/processingchainentrance.coffee
+++ lib/processingchainentrance.coffee
@@ -22,10 +22,5 @@
     ]).then(=> @package_operation(operation))

   package_operation: (operation) =>
-    deferred = Q.defer()
-    try
-      deferred.resolve @engine.execute_operation(operation)
-    catch err
-      deferred.reject(err)
+    Q.fcall @engine.execute_operation(operation)

-    return deferred.promise


don't init booting twice

don't init booting twice

--- lib/connection/process.coffee
+++ lib/connection/process.coffee
@@ -52,9 +52,9 @@
       """
       dismissable: true

-  start: (port, cons) ->
+  start: (port, cons, boot = true) ->
     return if @proc?
-    client.booting()
+    boot && client.booting()

     @checkExe @jlpath(), (exists) =>
       if not exists
@@ -63,7 +63,7 @@
         return

       if atom.config.get 'julia-client.initialiseClient'
-        return @initialiseClient => @start port, cons
+        return @initialiseClient => @start port, cons, false
       @spawnJulia(port, cons)
       @onStart()
       @proc.on 'exit', (code, signal) =>


open the console if necessary

open the console if necessary

--- lib/connection/process.coffee
+++ lib/connection/process.coffee
@@ -17,8 +17,7 @@
       atom.notifications.addSuccess "Welcome to Juno!",
         detail: """
         Success! Juno is set up and ready to roll.
-        Try opening the console (Packages → Julia → Open Console)
-        and entering `2+2`.
+        Try entering `2+2` in the console below.
         """
         dismissable: true

@@ -97,6 +96,10 @@
       """
       dismissable: true

+  openConsole: ->
+    atom.commands.dispatch atom.views.getView(atom.workspace),
+      'julia-client:open-console'
+
   bootErr: (err) ->
     switch err
       when 'juno-err-install'
@@ -121,14 +124,16 @@
         atom.notifications.addInfo "Installing Julia packages...",
           detail: """
           Julia's first run will take a couple of minutes.
-          Go to Packages → Julia → Open Console to see progress.
-          """
+          See the console below for progress.
+          """
+        @openConsole()
       when 'juno-err-precompiling'
         atom.notifications.addInfo "Compiling Julia packages...",
           detail: """
           Julia's first run will take a couple of minutes.
-          Go to Packages → Julia → Open Console to see progress.
-          """
+          See the console below for progress.
+          """
+        @openConsole()
       else client.stderr err

   init: (conn) ->


Fixed a minor syntax error

Fixed a minor syntax error

--- app.coffee
+++ app.coffee
@@ -83,6 +83,7 @@
 			async.series(tasks2, callback)
 		else
 			callback()
+	)

 stage2Tasks = [
 	setHakiEnv


basic option parser

basic option parser

--- app.coffee
+++ app.coffee
@@ -1,3 +1,15 @@
+optimist = require('optimist')
+argv = optimist
+  .usage('Usage: $0 -p [port]')
+  .default('p', 4011)
+  .alias('p', 'port')
+  .alias('h', 'help')
+  .argv
+
+if argv.h
+  optimist.showHelp()
+  process.exit -1
+
 express = require('express')
 #routes = require('./routes')
 #user = require('./routes/user')
@@ -11,7 +23,7 @@

 express.mime.default_type = 'text/plain'

-app.set('port', process.env.PORT || 4011)
+app.set('port', process.env.PORT || argv.port)
 app.set('views', __dirname + '/views')
 app.set('view engine', 'jade')
 #app.use(express.favicon())


Fix error when create shortcut failed

Fix error when create shortcut failed

--- app.coffee
+++ app.coffee
@@ -23,8 +23,9 @@
     windowsShortcuts.edit shortcutPath, {target: targetPath, args: argPath}, ->
       windowsShortcuts.addAppId shortcutPath, 'org.poi.poi'
   catch error
-    windowsShortcuts.create shortcutPath, {target: targetPath, args: argPath}, ->
-       windowsShortcuts.addAppId shortcutPath, 'org.poi.poi'
+    try
+      windowsShortcuts.create shortcutPath, {target: targetPath, args: argPath}, ->
+         windowsShortcuts.addAppId shortcutPath, 'org.poi.poi'

 if dbg.isEnabled()


redirect to chart from index

redirect to chart from index

--- app.coffee
+++ app.coffee
@@ -62,6 +62,9 @@

 app.configure 'production', ->
     app.use express.errorHandler()
+
+# Redirect to chart from index.
+app.get '/', (req, res) -> res.redirect '/burndown'

 # Show burndown chart.
 app.get '/burndown', (req, res) ->


Fix crash

Fix crash

--- app.coffee
+++ app.coffee
@@ -10,6 +10,14 @@
 global.EXROOT = global.APPDATA_PATH
 global.DEFAULT_CACHE_PATH = path.join(global.EXROOT, 'MyCache')
 global.MODULE_PATH = path.join(global.ROOT, "node_modules")
+
+CONST = require './lib/constant'
+config = require './lib/config'
+proxy = require './lib/proxy'
+proxy.setMaxListeners 30
+update = require './lib/update'
+shortcut = require './lib/shortcut'
+{log, warn, error} = require './lib/utils'

 # Add shortcut to start menu when os is windows
 app.setAppUserModelId 'org.poi.poi'
@@ -31,14 +39,6 @@
   global.SERVER_HOSTNAME = '127.0.0.1:17027'
 else
   global.SERVER_HOSTNAME = 'poi.0u0.moe'
-
-CONST = require './lib/constant'
-config = require './lib/config'
-proxy = require './lib/proxy'
-proxy.setMaxListeners 30
-update = require './lib/update'
-shortcut = require './lib/shortcut'
-{log, warn, error} = require './lib/utils'

 global.mainWindow = mainWindow = null


Provide all parameters to futimesSync()

Provide all parameters to futimesSync()

--- app.coffee
+++ app.coffee
@@ -67,7 +67,7 @@
   if app_env == "production"
     # Start listening to a unix socket.

-    fs.futimesSync config.faye.socket
+    fs.futimesSync config.faye.socket, new Date(), new Date()
     fs.unlinkSync config.faye.socket

     oldmask = process.umask(0o0000)
@@ -84,7 +84,7 @@
   if app_env == "production"
     # Start listening to the secure port using SSL.

-    fs.futimesSync config.faye.socketSecure
+    fs.futimesSync config.faye.socketSecure, new Date(), new Date()
     fs.unlinkSync config.faye.socketSecure

     oldmask = process.umask(0o0000)


Add /list/<languageCode>.xml endpoint

Add /list/<languageCode>.xml endpoint

--- app.coffee
+++ app.coffee
@@ -191,7 +191,7 @@
 	res.set 'wcf-update-server-ssl', 'false'
 	do next

-app.all '/', (req, res) ->
+app.all /^\/(list\/[a-z-]{2,}\.xml)?$/, (req, res) ->
 	host = config.basePath ? "#{req.protocol}://#{req.header 'host'}"

 	if req.query?.doAuth?