Groups: bot creation is added to product initial data importer

--- workers/productimport/main.coffee
+++ workers/productimport/main.coffee
@@ -107,6 +107,23 @@

     insertPlans plan for plan in data

+createBot = (callback) ->
+  {JUser} = worker.models
+
+  userInfo =
+    username  : "bot"
+    email     : "bot@koding.com"
+    firstName : "bot"
+
+  JUser.createUser userInfo, (err) ->
+    return console.log err if err
+    JUser.one username : "bot", (err, user) ->
+      user.confirmEmail (err) ->
+        return console.error err if err
+        console.log "bot created"
+        callback null
+
+
 initProducts = ->
   worker.on 'dbClientReady', ->
     queue = [
@@ -119,7 +136,9 @@
       -> importPacks ->
         console.log 'All Packs are added'
         queue.next()
-      -> process.kill()
+      -> createBot ->
+        queue.next()
+      -> process.exit(1)
     ]

     daisy queue


Removed debug line

Removed debug line

--- .metalsmith/metal.coffee
+++ .metalsmith/metal.coffee
@@ -40,10 +40,6 @@
       collection: 'video'
       output: 'videos/index'
       metadata: template: 'videos.toffee'
-    .use (f, m, d) ->
-      c = m.metadata().collections
-      console.log 'Collections!', c
-      d()
     .use permalinks()
     .use templates 'toffee'
     .build callback


Fix offline user cleanup

Fix offline user cleanup

It used to work, and then I screwed up a merge in
eb4ab88ecda76cb51aa687cb7d8561393a6a6f2f and removed the call to
clean-offline-user :(

--- src/router.coffee
+++ src/router.coffee
@@ -286,10 +286,14 @@
     # * Disco info may not be available anymore
     # * If missing from anonymousUsers no clean-up is needed
     onUserOffline: (user) ->
-        if @anonymousUsers.hasOwnProperty(user) and @anonymousUsers[user]
-            delete @anonymousUsers[user]
-            req =
-                operation: 'remove-user'
-                actor: user
-                sender: user
-            @runLocally req, ->
+        req =
+            operation: 'clean-offline-user'
+            actor: user
+        @runLocally req, =>
+            if @anonymousUsers.hasOwnProperty(user) and @anonymousUsers[user]
+                delete @anonymousUsers[user]
+                req =
+                    operation: 'remove-user'
+                    actor: user
+                    sender: user
+                @runLocally req, ->


added pattern match for trailing slashes in route config

--- src/router.coffee
+++ src/router.coffee
@@ -8,11 +8,11 @@
 routes = (
   <Route path='/' handler={App}>
     <Redirect from='/' to='dashboard' />
-    <Route path='dashboard' name='dashboard' handler={Dashboard} />
-    <Route path='tasks' name='tasks' handler={Tasks} />
-    <Route path='tasks/:id' name='task' handler={SingleTask} />
-    <Route path='readings' name='createReading' handler={ReadingPlan} />
-    <Route path='readings/:id' name='editReading' handler={ReadingPlan} />
+    <Route path='dashboard/?' name='dashboard' handler={Dashboard} />
+    <Route path='tasks/?' name='tasks' handler={Tasks} />
+    <Route path='tasks/:id/?' name='task' handler={SingleTask} />
+    <Route path='readings/?' name='createReading' handler={ReadingPlan} />
+    <Route path='readings/:id/?' name='editReading' handler={ReadingPlan} />
     <NotFoundRoute handler={Invalid} />
   </Route>
 )


Add linearscale default to Plot in Bokehjs

--- bokehjs/src/coffee/models/plots/plot.coffee
+++ bokehjs/src/coffee/models/plots/plot.coffee
@@ -6,6 +6,7 @@

 import {LayoutDOM, LayoutDOMView} from "../layouts/layout_dom"
 import {Title} from "../annotations/title"
+import {LinearScale} from "../scales/linear_scale"
 import {Toolbar} from "../tools/toolbar"
 import {ToolEvents} from "../tools/tool_events"
 import {PlotCanvas, PlotCanvasView} from "./plot_canvas"
@@ -326,8 +327,8 @@
       y_range:           [ p.Instance                         ]
       extra_y_ranges:    [ p.Any,      {}                     ] # TODO (bev)

-      x_scale:           [ p.Instance                         ]
-      y_scale:           [ p.Instance                         ]
+      x_scale:           [ p.Instance, () -> new LinearScale() ]
+      y_scale:           [ p.Instance, () -> new LinearScale() ]

       tool_events:       [ p.Instance, () -> new ToolEvents() ]


HasParent: share one instance of display_defaults per class, not per instance

--- bokehjs/src/coffee/common/has_parent.coffee
+++ bokehjs/src/coffee/common/has_parent.coffee
@@ -40,9 +40,12 @@
   nonserializable_attribute_names: () ->
     super().concat(['parent'])

+  _display_defaults_cached: () ->
+    if not @constructor._display_defaults_cache?
+      @constructor._display_defaults_cache = @display_defaults()
+    @constructor._display_defaults_cache
+
   get: (attr, resolve_refs=true) ->
-    if not @_display_defaults
-      @_display_defaults = @display_defaults()
     if attr == 'parent'
       return @_parent
     val = super(attr, resolve_refs)
@@ -52,7 +55,7 @@
       val = @_parent.get(attr, resolve_refs)
       if not _.isUndefined(val)
         return val
-    return @_display_defaults[attr]
+    return @_display_defaults_cached()[attr]

   display_defaults: () ->
     return {}


Singularise class name. Active callbakc for Article screen

--- assets/javascripts/screens/article.coffee
+++ assets/javascripts/screens/article.coffee
@@ -1,10 +1,22 @@
+# Article display screen
+# ======================
+#
+# Shows an individidual article in the screen.
+
 class ArticleScreen extends Tres.Screen
-  className : 'articles-screen'
+  className : 'article-screen'
   template  : _.template $('#article-template').html()

   events:
     'click .back-button': 'goBack'

+  active: (title) ->
+    @title title
+    @model.set 'title', title
+    @model.retrieve =>
+      @render()
+      @title title
+
   goBack: ->
     history.back()


json responses for errors

json responses for errors

--- src/server.coffee
+++ src/server.coffee
@@ -43,17 +43,13 @@
 if app.get("env") is "development"
   app.use (err, req, res, next) ->
     res.status err.status or 500
-    res.render "error",
-      message: err.message
-      error: err
+    res.send(message: err.message, status: err.status, stack: err.stack)

 # production error handler
 # no stacktraces leaked to user
 app.use (err, req, res, next) ->
   res.status err.status or 500
-  res.render "error",
-    message: err.message
-    error: {}
+  res.send(message: err.message)

 #
 # HTTPS support
@@ -65,4 +61,4 @@

 server = http.createServer(app)
 server.listen app.get("port"), ->
-  debug "Express server listening on port " + server.address().port+  debug "Express server listening on port " + server.address().port


Specify IP and PORT for Openshift

Specify IP and PORT for Openshift

--- src/server.coffee
+++ src/server.coffee
@@ -9,7 +9,8 @@
 logger = require './helpers/logger'

 app = module.exports = restify.createServer()
-listenPort = process.env.PORT or 8080
+listenHost = process.env.OPENSHIFT_NODEJS_IP or "0.0.0.0"
+listenPort = process.env.OPENSHIFT_NODEJS_PORT or 8080

 #
 # Applying Restify built-in plugins and other helpers
@@ -28,7 +29,7 @@
 # Launching server
 #

-server = app.listen listenPort, ->
+server = app.listen listenPort, listenHost, ->
   address = server.address().address
   port = server.address().port
   logger.info \


Add a prompt buffer to the server.

Add a prompt buffer to the server.

--- src/server.coffee
+++ src/server.coffee
@@ -1,6 +1,22 @@
 http = require 'http'
 fork = require('child_process').fork
 Impromptu = require '../lib/impromptu'
+
+prompt =
+  BUFFER_SIZE: 2
+  buffer: []
+
+  spawn: ->
+    fork "#{__dirname}/../lib/child.js"
+
+  refresh: ->
+    while @buffer.length < @BUFFER_SIZE
+      @buffer.push @spawn()
+
+  get: ->
+    if @buffer.length then @buffer.shift() else @spawn()
+
+prompt.refresh()

 server = http.createServer (request, response) ->
   if request.method isnt 'POST'
@@ -19,13 +35,14 @@
       request.connection.destroy()

   request.on 'end', ->
-    child = fork "#{__dirname}/../lib/child.js"
+    child = prompt.get()

     child.on 'message', (message) ->
       if message.type is 'prompt'
         response.writeHead 200, "OK", {'Content-Type': 'text/plain'}
         response.write message.data
         response.end()
+        prompt.refresh()

     child.send
       type: 'options'


Fix webhcat stop command

Fix webhcat stop command

--- hdp/lib/lifecycle.coffee
+++ hdp/lib/lifecycle.coffee
@@ -398,8 +398,8 @@
       return callback err, false if err or not running
       ctx.log "WebHCat stop"
       ctx.execute
-        # su -l hcat -c "/usr/lib/hcatalog/sbin/webhcat_server.sh stop"
-        cmd: "su -l #{webhcat_user} -c \"/usr/lib/hcatalog/sbin/webhcat_server.sh stop\""
+        # su -l hcat -c "export WEBHCAT_CONF_DIR=/etc/hcatalog/conf/webhcat; /usr/lib/hcatalog/sbin/webhcat_server.sh start"
+        cmd: "su -l #{webhcat_user} -c \"export WEBHCAT_CONF_DIR=#{webhcat_conf_dir}; /usr/lib/hcatalog/sbin/webhcat_server.sh stop\""
       , (err, stopped) ->
         callback err, stopped
   hue_status: (ctx, callback) ->


Support IFS

Support IFS

--- src/formula/evaluator.coffee
+++ src/formula/evaluator.coffee
@@ -17,6 +17,7 @@
 TYPE_BOOLEAN = 6
 TYPE_PURE_NUMBER = 7
 TYPE_PURE_INTEGER = 8
+TYPE_ANY = 100

 class FormulaEvaluator

@@ -499,6 +500,17 @@
     if x == FormulaError.NA
       return @getValue(args[1])
     x
+
+  IFS: (args)->
+    @checkArgumentSize(args, 2, Number.MAX_VALUE)
+    args = @expandRange(TYPE_ANY, args)
+    if args.length % 2 == 1
+      @error(FormulaError.NA)
+
+    for x,i in args when i % 2 == 0
+      if @expectBoolean(x)
+        return args[i+1]
+    @error(FormulaError.NA)

   INT: (args)->
     @checkArgumentSize(args, 1)
@@ -1293,6 +1305,8 @@
         @expectPureNumber(x)
       when TYPE_PURE_INTEGER
         @expectPureInteger(x)
+      when TYPE_ANY
+        @getValue(x)

   expandRange:(type, args)->


Support NA and IFNA

Support NA and IFNA

--- src/formula/evaluator.coffee
+++ src/formula/evaluator.coffee
@@ -493,6 +493,13 @@
       return @getValue(args[1])
     x

+  IFNA: (args)->
+    @checkArgumentSize(args, 2)
+    x = @getValue(args[0])
+    if x == FormulaError.NA
+      return @getValue(args[1])
+    x
+
   INT: (args)->
     @checkArgumentSize(args, 1)
     args.push 1
@@ -625,6 +632,10 @@
     args = @expandRange(TYPE_NUMBER, args)
     math.multinomial args

+
+  NA: (args)->
+    @checkArgumentSize(args, 0)
+    FormulaError.NA

   NETWORKDAYS: (args)->
     @checkArgumentSize(args, 2, 3)


updated online user management to only send cursor position when people are collaborate

if someone is in the project with them cursor info is sent every 500ms, if they are
by themself it is sent every 5mins so they are still marked as online.

--- services/web/public/coffee/ide/online-users/OnlineUsersManager.coffee
+++ services/web/public/coffee/ide/online-users/OnlineUsersManager.coffee
@@ -3,6 +3,9 @@
 	"ide/online-users/controllers/OnlineUsersController"
 ], () ->
 	class OnlineUsersManager
+
+		cursorUpdateInterval:500
+
 		constructor: (@ide, @$scope) ->
 			@$scope.onlineUsers = {}
 			@$scope.onlineUserCursorHighlights = {}
@@ -70,7 +73,14 @@
 					hue: @getHueForUserId(client.user_id)
 				}

-		UPDATE_INTERVAL: 500
+			if @$scope.onlineUsersArray.length > 0
+				delete @cursorUpdateTimeout
+				@cursorUpdateInterval = 500
+			else
+				delete @cursorUpdateTimeout
+				@cursorUpdateInterval = 60 * 1000 * 5
+
+
 		sendCursorPositionUpdate: (position) ->
 			if !@cursorUpdateTimeout?
 				@cursorUpdateTimeout = setTimeout ()=>
@@ -83,7 +93,7 @@
 					}

 					delete @cursorUpdateTimeout
-				, @UPDATE_INTERVAL
+				, @cursorUpdateInterval

 		OWN_HUE: 200 # We will always appear as this color to ourselves
 		ANONYMOUS_HUE: 100


Support some mathematical functions

Support some mathematical functions

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -297,6 +297,11 @@
       @error(ERROR_NUM)
     Math.round(m/n) * n

+  MULTINOMIAL: (args)->
+    @checkArgumentSize(args, 1, Number.MAX_VALUE)
+    args = @expandRange(TYPE_NUMBER, args)
+    math.multinomial args
+

   ODD: (args)->
     @checkArgumentSize(args, 1)
@@ -306,6 +311,13 @@
     else
       @EVEN([num-1])+1

+  PI: (args)->
+    Math.PI
+
+  POWER: (args)->
+    @checkArgumentSize(args, 2)
+    Math.pow(@expectNumber(args[0]), @expectNumber(args[1]))
+
   PERMUT: (args)->
     @checkArgumentSize(args, 2)
     n = @expectInteger(args[0])
@@ -322,6 +334,66 @@
     r = @expectInteger(args[1])
     Math.pow(n, r)

+  QUOTIENT: (args)->
+    @checkArgumentSize(args, 2)
+    parseInt(@expectNumber(args[0]) / @expectNumber(args[1]))
+
+  RADIANS: (args)->
+    @checkArgumentSize(args, 1)
+    @expectNumber(args[0]) * Math.PI / 180
+
+  RAND: (args)->
+    @checkArgumentSize(args, 0)
+    Math.random()
+
+  RANDBETWEEN: (args)->
+    @checkArgumentSize(args, 2)
+    min = @expectInteger(args[0])
+    max = @expectInteger(args[1])
+    Math.floor(Math.random() * (max-min+1)) + min
+
+  ROMAN: (args)->
+    @checkArgumentSize(args, 1, 2)
+    n = @expectInteger(args[0])
+    if n >= 4000
+      @error(ERROR_VALUE)
+    opt = @getValue(args[1])
+    if Utils.isBoolean(opt)
+      if opt
+        opt = 0
+      else
+        opt = 4
+    else
+      opt = @expectInteger(opt)
+
+    codes = {1:'I', 4:'IV', 5:'V', 9:'IX', 10:'X', 40:'XL', 50:'L', 90:'XC', 100:'C', 400:'CD', 500:'D', 900:'CM', 1000:'M'}
+    if opt > 0
+      codes[45] = 'VL'
+      codes[95] = 'VC'
+      codes[450] = 'LD'
+      codes[950] = 'LM'
+    if opt > 1
+      codes[49] = 'IL'
+      codes[99] = 'IC'
+      codes[490] = 'XD'
+      codes[990] = 'XM'
+    if opt > 2
+      codes[495] = 'VD'
+      codes[995] = 'VM'
+    if opt > 3
+      codes[499] = 'ID'
+      codes[999] = 'IM'
+
+    nums = Object.keys(codes).map (x)-> parseInt(x)
+    nums.sort (a, b)-> b - a
+
+    roman = ""
+    for num in nums
+      while n >= num
+        roman += codes[num]
+        n -= num
+
+    roman

   ROUND: (args)->
     @checkArgumentSize(args, 2)
@@ -351,6 +423,39 @@
     digit = Math.pow(10, n)
     sign * @FLOOR([expr, digit])

+  SEC: (args)->
+    @checkArgumentSize(args, 1)
+    1 / Math.cos(@expectNumber(args[0]))
+
+  SECH: (args)->
+    @checkArgumentSize(args, 1)
+    1 / Math.cosh(@expectNumber(args[0]))
+
+  SIGN: (args)->
+    @checkArgumentSize(args, 1)
+    Math.sign @expectNumber(args[0])
+
+  SIN: (args)->
+    @checkArgumentSize(args, 1)
+    Math.sin @expectNumber(args[0])
+
+  SINH: (args)->
+    @checkArgumentSize(args, 1)
+    Math.sinh @expectNumber(args[0])
+
+  SQRT: (args)->
+    @checkArgumentSize(args, 1)
+    n = @expectNumber(args[0])
+    if n < 0
+      @error(ERROR_NUM)
+    Math.sqrt n
+
+  SQRTPI: (args)->
+    @checkArgumentSize(args, 1)
+    n = @expectNumber(args[0])
+    if n < 0
+      @error(ERROR_NUM)
+    Math.sqrt n*Math.PI

   SUM: (args)->
     @checkArgumentSize(args, 1, Number.MAX_VALUE)
@@ -360,6 +465,21 @@
       sum += num
     sum

+  SUMSQ: (args)->
+    @checkArgumentSize(args, 1, Number.MAX_VALUE)
+    args = @expandRange(TYPE_NUMBER, args)
+    sum = 0
+    for num in args
+      sum += num*num
+    sum
+
+  TAN: (args)->
+    @checkArgumentSize(args, 1)
+    Math.tan @expectNumber(args[0])
+
+  TANH: (args)->
+    @checkArgumentSize(args, 1)
+    Math.tanh @expectNumber(args[0])

   TRUNC: (args)->
     @checkArgumentSize(args, 1, 2)


display ride function

display ride function

--- client/ride2go.coffee
+++ client/ride2go.coffee
@@ -44,7 +44,7 @@
     for ride in rides
       alert ride.provider
       console.log(ride)
-      $("#rides").append $("
provider: #{ride.provider} visit
")
+      displayRide ride

   socket.on "connect", ->
     $("#status").html("connected")
@@ -84,6 +84,9 @@
 from = {}
 socket = {}

+displayRide = (ride) ->
+      $("#rides").append $("
provider: #{ride.provider} visit
")
+
 $().ready ->
   setgeotypes "whereto", [ "locality", "premise", "subpremise", "route", "street_address" ]
   setgeotypes "wherefrom", [ "locality", "route", "premise", "subpremise", "locality" ]


Fixed a bug...

Fixed a bug...

--- src/Volta.Web/main.coffee
+++ src/Volta.Web/main.coffee
@@ -8,7 +8,7 @@
 		"postal": "scripts/postal/postal"
 		"postalajax": "scripts/postal/postal.ajax"
 		"postalscroll": "scripts/postal/postal.scroll"
-        "bootstrap": "scripts/bootstrap/bootstrap"
+		"bootstrap": "scripts/bootstrap/bootstrap"

 require ['jquery', 'dashboard', 'bootstrap'], ($, Dashboard) ->
 	content = $ '#content'


By default use https cdn, that's more foolproof.

--- src/sockjs.coffee
+++ src/sockjs.coffee
@@ -137,7 +137,7 @@
             heartbeat_delay: 25000
             disconnect_delay: 5000
             log: (severity, line) -> console.log(line)
-            sockjs_url: 'http://cdn.sockjs.org/sockjs-0.3.min.js'
+            sockjs_url: 'https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.min.js'
         if user_options
             utils.objectExtend(@options, user_options)


chore(dependencies): update config dependencies

--- generators/app/templates/_config.coffee
+++ generators/app/templates/_config.coffee
@@ -1,14 +1,19 @@
 APP_NAME = 'app'

 BOWER_COMPONENTS =
-	'angular': '1.2.17':
+	'angular': '1.2.20':
 		scripts: 'angular.min.js'
-	'angular-mocks': '1.2.17':
+	'angular-animate': '1.2.20':
+		scripts: 'angular-animate.min.js'
+	'angular-mocks': '1.2.20':
 		scripts: 'angular-mocks.js'
-	'angular-route': '1.2.17':
+	'angular-route': '1.2.20':
 		scripts: 'angular-route.min.js'
+	'angular-loading-bar': '0.4.3':
+		scripts: 'build/loading-bar.min.js'
+		styles:  'build/loading-bar.min.css'
 	<% if (includeTwitterBootstrap) { %>
-	'bootstrap': '3.1.1':
+	'bootstrap': '3.2.0':
 		fonts:   'dist/fonts/**/*.{eot,svg,ttf,woff}'
 		styles:  'dist/css/*.min.css'
 	<% } %>
@@ -22,8 +27,10 @@

 SCRIPTS = [
 	'**/angular.min.js'
+	'**/angular-animate.min.js'
 	'**/angular-mocks.js'
 	'**/angular-route.min.js'
+	'**/loading-bar.min.js'
 	'**/app.js'
 	'**/*.js'
 ]


Troubleshoot: runHelper is regressed

Troubleshoot: runHelper is regressed

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -24,9 +24,6 @@
       options =
         withArgs : command

-    @runHelper options, callback
-
-  runHelper: (options = {}, callback) ->
     @fetchVmName options, (err, vmName) =>
       return callback err  if err?
       options.correlationName = vmName
@@ -36,7 +33,7 @@

   ping: (callback) ->
     options = {withArgs : ""}
-    @runHelper options, callback
+    @run options, callback

   _runWrapper:(command, vm, callback)->
     if vm and 'string' isnt typeof vm


Decrease game length to 10min

Decrease game length to 10min

--- coffee/rungame.coffee
+++ coffee/rungame.coffee
@@ -2,7 +2,7 @@
 CANVAS_HEIGHT = 700
 CANVAS_WIDTH  = 700
 SENSITIVITY = 50
-GAME_TOTAL_TIME = 1000 * 60 * 15
+GAME_TOTAL_TIME = 1000 * 60 * 10
 POINT_RADIUS = 5
 RING_RADIUS  = 24


changed require path for loginhandlerhelper

--- servers/lib/server/handlers/login.test.coffee
+++ servers/lib/server/handlers/login.test.coffee
@@ -7,11 +7,9 @@
 { expect }                                = require 'chai'
 { generateRandomEmail
   generateRandomString
-  generateRandomUsername
-
-  LoginHandlerHelper }                    = require '../../../testhelper'
+  generateRandomUsername }                = require '../../../testhelper'
+{ generateLoginRequestParams }            = require '../../../testhelper/loginhandlerhelper'
 { generateRegisterRequestParams }         = require '../../../testhelper/registerhandlerhelper'
-{ generateLoginRequestParams }            = LoginHandlerHelper

 JLog                                      = null
 JUser                                     = null


manage data

manage data

--- client/src/index.coffee
+++ client/src/index.coffee
@@ -8,7 +8,7 @@
 # Utilitaires pour requêter notre serveur.
 homedata =

-    # Avec cette fonction, on récupère la home.geojson
+    # Avec cette fonction, on récupère le home.geojson
     getHomeBookmarks: (callback) ->
         request
             .get('/geojsondata/home.geojson')
@@ -18,7 +18,7 @@

 placesdata =

-    # Avec cette fonction, on récupère la home.geojson
+    # Avec cette fonction, on récupère le places.geojson
     getPlacesBookmarks: (callback) ->
         request
             .get('/geojsondata/places.geojson')
@@ -26,7 +26,7 @@
             .end (err, res) ->
                 callback err, res.body

-data =
+originaldata =

     # Avec cette fonction, on récupère les bookmarks depuis le serveur.
     getBookmarks: (callback) ->
@@ -51,6 +51,12 @@
             .del('/api/bookmarks/' + bookmark.id)
             .end callback

+data = {
+    getHomeBookmarks: homedata
+    getPlacesBookmarks: placesdata
+    getBookmarks: originaldata
+    }
+

 # C'est le composant principal de l'application.
 App = React.createClass
@@ -73,7 +79,7 @@
 # Ici on démarre !
 #
 # On récupère d'abord les bookmarks stockées sur le home.geoJson
-homedata.getHomeBookmarks (err, homedata) ->
+homedata.getHomeBookmarks (err, res) ->
     console.log homedata

 # On récupère d'abord les bookmarks stockées sur le places.geoJson
@@ -81,7 +87,7 @@
     console.log placesdata

 # On récupère d'abord les bookmarks stockées sur le serveur.
-data.getBookmarks (err, bookmarks) ->
+originaldata.getBookmarks (err, bookmarks) ->
     console.log bookmarks

     if not bookmarks?


Expose batch on the client too.

Expose batch on the client too.

Seems messy to duplicate this code.

--- client/ts_client.coffee
+++ client/ts_client.coffee
@@ -27,6 +27,10 @@
 TurkServer.treatment = ->
   Experiments.findOne({}, fields: {treatment: 1})?.treatment

+TurkServer.batch = ->
+  experiment = Experiments.findOne({}, fields: {batchId: 1})
+  Batches.findOne(experiment.batchId) if experiment?
+
 TurkServer.currentRound = ->
   RoundTimers.findOne(active: true)


test unfreeze

test unfreeze

--- src/mixinfactory.spec.coffee
+++ src/mixinfactory.spec.coffee
@@ -1,6 +1,7 @@
 fdescribe 'mix.it.mixinfactory', ->

   Mixin = require './mixinfactory'
+  _ = require 'underscore'

   describe 'factory', ->
@@ -40,16 +41,40 @@
       mixin = Mixin.from_obj
         speak: ->
           'Hello, World!'
-        name: 'Speaker'
+        name: 'Example Mixin'

       expect(mixin instanceof Mixin).toBe true

+    it 'should not allow adding or modifying properties', ->
+      mixin = Mixin.from_obj
+        speak: ->
+          'Hello, World!'
+        name: 'Example Mixin'
+
+      mixin.foo = 'bar'
+      delete mixin.name
+      delete mixin.speak
+
+      expect(mixin.foo).toBeUndefined()
+      expect(mixin.name).toBe('Example Mixin')
+      expect(mixin.speak()).toBe('Hello, World!')
+
+    it 'should allow adding properties but not modifying existing if freeze = false', ->
+      mixin = Mixin.from_obj({name: 'Example Mixin', foo: ->}, false)
+      delete mixin.name
+      delete mixin.foo
+
+      mixin.bar = ->
+
+      expect(mixin.name).toBe('Example Mixin')
+      expect(_.isFunction mixin.foo).toBe true
+      expect(_.isFunction mixin.bar).toBe true

   describe 'Mixin', ->

     beforeEach ->
       @mixin = Mixin.from_obj
-        name: 'Speaker'
+        name: 'Example Mixin'
         speak: ->
           "Hello, my name is #{@name}!"
         shout: ->
@@ -61,26 +86,13 @@
     it 'should have a sorted mixin_keys and the mixin attributes', ->
       expect(@mixin.mixin_keys).toEqual ['shout', 'speak', 'whisper']

-      expect(@mixin.name).toBe('Speaker')
-      expect(@mixin.speak()).toBe('Hello, my name is Speaker!')
-      expect(@mixin.shout()).toBe('HELLO, MY NAME IS SPEAKER!')
-      expect(@mixin.whisper()).toBe('...hello, my name is speaker...!')
-
-    it 'should not allow adding or modifying properties', ->
-      @mixin.foo = 'bar'
-      delete @mixin.name
-      delete @mixin.speak
-      delete @mixin.shout
-      delete @mixin.whisper
-
-      expect(@mixin.foo).toBeUndefined()
-      expect(@mixin.name).toBe('Speaker')
-      expect(@mixin.speak()).toBe('Hello, my name is Speaker!')
-      expect(@mixin.shout()).toBe('HELLO, MY NAME IS SPEAKER!')
-      expect(@mixin.whisper()).toBe('...hello, my name is speaker...!')
+      expect(@mixin.name).toBe('Example Mixin')
+      expect(@mixin.speak()).toBe('Hello, my name is Example Mixin!')
+      expect(@mixin.shout()).toBe('HELLO, MY NAME IS EXAMPLE MIXIN!')
+      expect(@mixin.whisper()).toBe('...hello, my name is example mixin...!')

     it 'should have enumerate properties in toString()', ->
-      expect(@mixin.toString()).toEqual 'Mixin(Speaker: shout, speak, whisper)'
+      expect(@mixin.toString()).toEqual 'Mixin(Example Mixin: shout, speak, whisper)'

     it 'should be immutable with loud failures on change attempts', ->
       for key in @mixin.mixin_keys


I messed up when I changed this string...

--- client/main.coffee
+++ client/main.coffee
@@ -43,8 +43,8 @@
           socket.on 'unauthorized', =>
             @connecting = false
             console.log 'Auth Failed'
-          socket.on 'Authenticated', =>
-            console.log 'Sucess!'
+          socket.on 'authenticated', =>
+            console.log 'Authenticated!'
             @connecting = false
             @socket = socket
             initializeSocket.call this


Fix setSource signature

Fix setSource signature

--- src/lib/loader/NodeJs.coffee
+++ src/lib/loader/NodeJs.coffee
@@ -104,7 +104,7 @@
   instance.componentName = name if typeof name is 'string'
   callback null, instance

-exports.setSource = (loader, packageId, source, language, callback) ->
+exports.setSource = (loader, packageId, name, source, language, callback) ->
   Module = require 'module'
   if language is 'coffeescript'
     try


Group shim lines

Group shim lines

--- src/syntax.coffee
+++ src/syntax.coffee
@@ -23,15 +23,15 @@

   constructor: ->
     @registry = new TextMateGrammarRegistry()
+
+    #TODO Remove once packages have been updated
     @subscribe @registry, 'grammar-added', (grammar) =>
       @emit 'grammar-added', grammar
     @subscribe @registry, 'grammar-updated', (grammar) =>
       @emit 'grammar-updated', grammar
+    @__defineGetter__ 'grammars', -> @registry.grammars
+    @nullGrammar = @registry.nullGrammar

-    #TODO Remove once packages have been updated
-    @__defineGetter__ 'grammars', -> @registry.grammars
-
-    @nullGrammar = @registry.nullGrammar
     @scopedPropertiesIndex = 0
     @scopedProperties = []


Use FieldWrapper

Use FieldWrapper

--- client/skr/components/PrintFormChooser.cjsx
+++ client/skr/components/PrintFormChooser.cjsx
@@ -5,9 +5,8 @@
         model: Lanes.PropTypes.Model.isRequired
         choices: React.PropTypes.array

-    mixins: [
-        Lanes.React.Mixins.ReadEditingState
-    ]
+    getDefaultProps: ->
+        name: 'form'

     onChange: (val) ->
         if @props.onChange
@@ -15,22 +14,26 @@
         else
             @props.model[@props.name] = val

-    renderEdit: (value) ->
-        choices = @props.choices || @props.model.constructor.Templates
-        <Lanes.Vendor.ReactWidgets.DropdownList
-            data={choices}
-            value={value}
-            onChange={@onChange}
-        />
-
     renderValue: (value) ->
         value

     render: ->
-        value = @props.value or @props.model[@props.name or 'form'] or 'default'
-        props = _.omit(@props, 'choices', 'name')
-        <LC.FormGroup editing={@isEditingRecord()}
-            className="field" {...props}
-        >
-            {if @isEditingRecord() then @renderEdit(value) else @renderValue(value)}
-        </LC.FormGroup>
+        choices = @props.choices || @props.model.constructor.Templates
+        value = @props.value or @props.model[@props.name]
+        console.log @props.model.form, @props
+
+        <LC.FieldWrapper {...@props} value={value}>
+
+            <Lanes.Vendor.ReactWidgets.DropdownList
+                data={choices}
+                value={value}
+                onChange={@onChange}
+            />
+        </LC.FieldWrapper>
+        # value = @props.value or @props.model[@props.name]
+        # props = _.omit(@props, 'choices', 'name')
+        # <LC.FormGroup editing={@isEditingRecord()}
+        #     className="field" {...props}
+        # >
+        #     {if @isEditingRecord() then @renderEdit(value) else @renderValue(value)}
+        # </LC.FormGroup>


Refactor active nav styling

Refactor active nav styling

Now checks every new page URL's root against the hrefs in the primary nav. Caches the links selector, but refreshes on render (in case user adds a new bucket).

--- client/source/views/layouts/loggedin.coffee
+++ client/source/views/layouts/loggedin.coffee
@@ -10,9 +10,6 @@
   autoRender: yes
   container: '#bkts-content'

-  events:
-    'click .nav-primary a': 'clickNav'
-
   regions:
     content: '.page'

@@ -22,8 +19,21 @@

   initialize: ->
     super
+    @subscribeEvent 'dispatcher:dispatch', @checkNav
     @listenTo mediator.buckets, 'sync add', => @render()

-  clickNav: (e) ->
-    $el = @$(e.currentTarget)
-    $el.closest('li').addClass('active').siblings().removeClass('active')
+  render: ->
+    super
+    @$navLinks = @$('.nav-primary a')
+
+  checkNav: (controller, params, route) ->
+    return unless route?.path
+
+    for link in @$navLinks
+      $link = $(link)
+      href = $link.attr('href')
+      newURL = "/#{mediator.options.adminSegment}/#{route.path}"
+
+      if newURL.substr(0, href.length + 1) is href
+        $link.parent().addClass('active').siblings().removeClass('active')
+        break


Fix missing `` ` `` - #2926

Fix missing `` ` `` - #2926

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -24,7 +24,7 @@
 # options that can be passed to `SourceMap#generate` may also be passed here.
 #
 # This returns a javascript string, unless `options.sourceMap` is passed,
-# in which case this returns a `{js, v3SourceMap, sourceMap}
+# in which case this returns a `{js, v3SourceMap, sourceMap}`
 # object, where sourceMap is a sourcemap.coffee#SourceMap object, handy for doing programatic
 # lookups.
 exports.compile = compile = (code, options = {}) ->


bumping to 0.5.1

bumping to 0.5.1

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -24,7 +24,7 @@
   showPosition: -> @pos
 }

-exports.VERSION: '0.5.0'
+exports.VERSION: '0.5.1'

 # Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
 exports.compile: (code, options) ->


Add support for HEAD /:col/:doc

Add support for HEAD /:col/:doc

--- coffee/server.coffee
+++ coffee/server.coffee
@@ -69,7 +69,7 @@
 app.all '/:collection/:objectid', (req, res, next) ->
   switch req.method
     when 'OPTIONS' then document.options req, res, next
-    when 'GET' then document.get req, res, next
+    when 'HEAD', 'GET' then document.get req, res, next
     when 'PUT' then document.put req, res, next
     when 'PATCH' then document.patch req, res, next
     when 'DELETE' then document.delete req, res, next


parameter tweak so that avatar sizes are better

--- @client/histogram.coffee
+++ @client/histogram.coffee
@@ -837,7 +837,7 @@

   # first, calculate a moving average of the number of opinions
   # across around all possible stances
-  window_size = .1
+  window_size = .3
   avg_inc = .01
   moving_avg = []
   idx = 0


:green_heart: Fix specs to handle untrimmed input

--- spec/markdown-preview-view-spec.coffee
+++ spec/markdown-preview-view-spec.coffee
@@ -95,6 +95,7 @@
           def func
             x = 1
           end
+
         """

         # nested in a list item
@@ -104,6 +105,7 @@
           if a === 3 {
           b = 5
           }
+
         """

     describe "when the code block's fence name doesn't have a matching grammar", ->
@@ -114,6 +116,7 @@
           function f(x) {
             return x++;
           }
+
         """

   describe "image resolving", ->


Teambuilder: Fix EV sliders for Firefox

Teambuilder: Fix EV sliders for Firefox

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -40,6 +40,7 @@
     'focus .ev-entry': 'focusEv'
     'blur .ev-entry': 'changeEv'
     'change .ev-entry': 'changeEv'
+    'input .ev-entry[type=range]': 'changeEv'  # fix for firefox
     'change .select-hidden-power': 'changeHiddenPower'
     'keydown .selected_moves input': 'keydownMoves'
     'blur .selected_moves input': 'blurMoves'


minor: removed obsolete code

minor: removed obsolete code

--- client/app/lib/util/registerAppClass.coffee
+++ client/app/lib/util/registerAppClass.coffee
@@ -3,7 +3,6 @@
 unregisterAppClass = require './unregisterAppClass'
 registerRoute = require './registerRoute'
 registerRoutes = require './registerRoutes'
-registerNavItem = require './registerNavItem'

 module.exports = (fn, options) ->


Minor bugfix, extra padding on prod shots gallery.

Extra padding when prod shots gallery is closed with floatly control bar.

--- _coffee/show-photos.coffee
+++ _coffee/show-photos.coffee
@@ -101,6 +101,8 @@
       else
         @controls.classList.add('show-photos-controls--sticky')
         @elemContainer.style.paddingBottom = "#{controlsHeight}px"
+    else
+      @elemContainer.style.paddingBottom = ""

     window.requestAnimationFrame(@computeControlStickyness)


Footer: links fixed

Footer: links fixed

--- client/Main/CommonViews/footerview.coffee
+++ client/Main/CommonViews/footerview.coffee
@@ -34,25 +34,25 @@

COMMUNITY

+          ACTIVITY
           KODING BLOG
           MEETUPS
-          NEWSLETTER
           TESTIMONALS
           BRAND GUIDELINES

KODING BLOG

-          Koding introduces 250TB week
-          Just opensourced KDFramework
-          Hired Jony Ive for office management
-          Cried all day, funny stuff
-          Last of us
+          Now Everyone Can Make Koding Apps!
+          You ask, we do! We’re extending #Crazy250TBWeek with another week and +250TB – Enjoy :)
+          Use your own domain with Koding! For free!
+          Groups and Pricing – deployed!
+          Other posts...

-        <address>2014 © Koding, Inc. 358 Brannan Street, San Francisco, CA, 94107</address>
+        <address>#{(new Date).getFullYear()} © Koding, Inc. 358 Brannan Street, San Francisco, CA, 94107</address>
         Lovingly made in Istanbul & San Francisco <3


Membership: Changed membership checking method for users for any given group

--- client/landing/app/MainApp/KD.extend.coffee
+++ client/landing/app/MainApp/KD.extend.coffee
@@ -51,15 +51,14 @@
   joinGroup_:(groupName, callback)->
     unless groupName then return callback true
     user = @whoami()
-    user.fetchGroups (err, groups)=>
-      if err or !groups then return callback false
+    user.checkGroupMembership (err, isMember)=>
+      return callback false if err
+      return callback true  if isMember
+
       @remote.api.JGroup.one { slug: groupName }, (err, currentGroup)=>
-        if err then return @notify_ err.message, "error"
-        for group in groups
-          if groupName is group.group.slug
-            return callback true
+        return callback false if err
         currentGroup.join (err)=>
-          if err then return callback false
+          return callback false if err
           @notify_ "You have joined to #{groupName} group!", "success"
           return callback true


Fix bug in detecting a blog post thumbnail image

--- collections/post.coffee
+++ collections/post.coffee
@@ -19,7 +19,7 @@
     regex = new RegExp /img src=[\'"]([^\'"]+)/ig

     while match = regex.exec html
-      return match
+      return match[1]

   excerpt: ->
     # Convert markdown to HTML


Slack: comments no longer look like errors.

--- plugins/slack/index.coffee
+++ plugins/slack/index.coffee
@@ -21,12 +21,16 @@
         attachment.color = "#FD9149"
       when "info"
         attachment.color = "#20A6DF"
-    if event.comment
-      attachment.fields.push
-        title: 'Comment'
-        value: event.comment.message.truncate(80)

     attachment
+
+  @commentAttachment = (event) ->
+    {
+      color: "good"
+      fallback: "Somebody commented"
+      author_name: event.user.name
+      text: event.comment.message
+    }

   @receiveEvent = (config, event, callback) ->

@@ -53,7 +57,10 @@
     }

     # Attach error information
-    payload.attachments.push(@errorAttachment(event)) if event.error
+    if event.comment
+      payload.attachments.push(@commentAttachment(event))
+    else if event.error
+      payload.attachments.push(@errorAttachment(event))

     # Post to slack
     @request


`build/05-stations.coffee`: improved `weight` computation, fixes #23

--- build/05-stations.coffee
+++ build/05-stations.coffee
@@ -10,6 +10,24 @@

 # todo: `data.pickup_type` & `data.drop_off_type` ?
+
+
+
+typeWeight =
+	'regional':	1
+	'suburban':	.8
+	'subway':	.7
+	'bus':		.3
+	'tram':		.35
+	'ferry':	.6
+
+processLine = (lines) ->
+	return (data) -> lines[data.id] = typeWeight[data.type] or 0
+
+
+
+processTrip = (trips) ->
+	return (data) -> trips[data.id] = data.lineId

@@ -29,11 +47,29 @@
 	'2':	1
 	'3':	3

-processTripStation = (stations) ->
+processTripStation = (lines, trips, stations) ->
 	return (data) ->
-		stations[parseInt data.stop_id].weight += dropOffWeight[data.drop_off_type] or 0
+		weight = lines[trips[parseInt data.trip_id]] or 1
+		weight *= (dropOffWeight[data.drop_off_type] or 0) + (dropOffWeight[data.pickup_type] or 0)
+		console.log stations[parseInt data.stop_id].name, data.drop_off_type, weight
+		stations[parseInt data.stop_id].weight += weight

+
+
+
+readNdjson = (file, handle) ->
+	task = Q.defer()
+
+	parse = fs.createReadStream path.join __dirname, '../data', file
+	.pipe ndjson.parse()
+	parse.on 'error', (err) ->
+		console.error err.stack   # todo: remove?
+		task.reject err
+	parse.on 'data', handle
+	parse.on 'end', task.resolve
+
+	return task.promise

@@ -80,14 +116,20 @@

+lines = []
+trips = []
 stations = {}

 console.log 'Merging stations.csv & trips-stations.csv into stations.ndjson:'

 # read & accumulate data
-readCsv 'stations.csv', processStation stations
+readNdjson 'lines.ndjson', processLine lines
 .then () ->
-	return readCsv 'trips-stations.csv', processTripStation stations
+	return readNdjson 'trips.ndjson', processTrip trips
+.then () ->
+	return readCsv 'stations.csv', processStation stations
+.then () ->
+	return readCsv 'trips-stations.csv', processTripStation lines, trips, stations

 # pass `stations` in
 .then () ->


Fixing issue with saves if crudflag is set to none

--- src/main/webapp/lib/overrides/Model.coffee
+++ src/main/webapp/lib/overrides/Model.coffee
@@ -25,7 +25,7 @@
     switch @crud(offset)
       # When crudflag is not set, remove model if delete
       # otherwise change value to flag
-      when null, undefined
+      when null, undefined, $a.CrudFlag.NONE
         if flag == $a.CrudFlag.DELETE
           @remove()
         else
@@ -52,7 +52,7 @@
     switch @crud()
       # When crudflag is not set, remove model if delete
       # otherwise change value to flag
-      when null, undefined
+      when null, undefined, $a.CrudFlag.NONE
         if flag == $a.CrudFlag.DELETE
           @remove()
         else


Better math terminology.

Better math terminology.

--- src/timber.coffee
+++ src/timber.coffee
@@ -163,8 +163,9 @@
     elapse: (element, milliseconds) ->

         velocity = element.speed * milliseconds
-        d = element.direction.scale(velocity)
-        element.position = element.position.sum(d)
+        displacement = element.direction.scale(velocity)
+        position = element.position.sum(displacement)
+        element.position = position
         return element

 #


commands needs to see the editor, and be globally visible

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -1,6 +1,7 @@
 # To be initialized on document load
 user_input = null
 controller = null
+commands   = null
 # For caching the runtime state across program runs
 rs = null

@@ -97,57 +98,58 @@
       controller.promptLabel = oldPrompt
       true

-commands =
-  javac: (args, report) ->
-    return "Usage: javac <source file>" unless args[0]?
-    compile_source args[0], report
-  java: (args, report) ->
-    return "Usage: java class [args...]" unless args[0]?
-    class_data = process_bytecode localStorage["file::#{args[0]}.class"]
-    return "Could not find class '#{args[0]}'" unless class_data?
-    stdout = (str) -> report str, true # no reprompting
-    rs ?= new runtime.RuntimeState(stdout, user_input, read_classfile)
-    jvm.run_class(rs, class_data, args, ->
-      $('#heap_size').text rs.heap.length-1
-      controller.reprompt()
-    )
-  javap: (args) ->
-    return "Usage: javap class" unless args[0]?
-    class_data = process_bytecode localStorage["file::#{args[0]}.class"]
-    return "Could not find class '#{args[0]}'" unless class_data?
-    disassembler.disassemble class_data
-  clear_heap: (args) ->
-    rs = null
-    $('#heap_size').text 0
-    "Heap cleared."
-  ls: (args) ->
-    (name[6..] for name, contents of localStorage when name[..5] == 'file::').join '\n'
-  edit: (args) ->
-    data = localStorage["file::#{args[0]}"]
-    return "No such file '#{args[0]}'." unless data
-    editor.getSession().setValue(data)
-    true
-  rm: (args) ->
-    return "Usage: rm <file>" unless args[0]?
-    # technically we should look only for keys starting with 'file::', but at the
-    # moment they are the only kinds of keys we use
-    if args[0] == '*' then localStorage.clear()
-    else localStorage.removeItem("file::#{args[0]}")
-    true
-  save: (args) ->
-    return "Usage: save <file>" unless args[0]?
-    localStorage["file::#{args[0]}"] = editor.getSession().getValue()
-    "File saved as '#{args[0]}'."
-  help: (args) ->
-    """
-javac <source file>    -- Compile Java source.
-java <class> [args...] -- Run with command-line arguments.
-javap <class>          -- Display disassembly.
-ls                     -- List all files.
-save <file>            -- Save editor contents as <file>.
-rm <file>              -- Delete a file.
-clear_heap             -- Clear the heap.
-    """
+  commands =
+    javac: (args, report) ->
+      return "Usage: javac <source file>" unless args[0]?
+      compile_source args[0], report
+    java: (args, report) ->
+      return "Usage: java class [args...]" unless args[0]?
+      class_data = process_bytecode localStorage["file::#{args[0]}.class"]
+      return "Could not find class '#{args[0]}'" unless class_data?
+      stdout = (str) -> report str, true # no reprompting
+      rs ?= new runtime.RuntimeState(stdout, user_input, read_classfile)
+      jvm.run_class(rs, class_data, args, ->
+        $('#heap_size').text rs.heap.length-1
+        controller.reprompt()
+      )
+    javap: (args) ->
+      return "Usage: javap class" unless args[0]?
+      class_data = process_bytecode localStorage["file::#{args[0]}.class"]
+      return "Could not find class '#{args[0]}'" unless class_data?
+      disassembler.disassemble class_data
+    clear_heap: (args) ->
+      rs = null
+      $('#heap_size').text 0
+      "Heap cleared."
+    ls: (args) ->
+      (name[6..] for name, contents of localStorage when name[..5] == 'file::').join '\n'
+    edit: (args) ->
+      data = localStorage["file::#{args[0]}"]
+      return "No such file '#{args[0]}'." unless data
+      editor.getSession().setValue(data)
+      true
+    rm: (args) ->
+      return "Usage: rm <file>" unless args[0]?
+      # technically we should look only for keys starting with 'file::', but at the
+      # moment they are the only kinds of keys we use
+      if args[0] == '*' then localStorage.clear()
+      else localStorage.removeItem("file::#{args[0]}")
+      true
+    save: (args) ->
+      return "Usage: save <file>" unless args[0]?
+      localStorage["file::#{args[0]}"] = editor.getSession().getValue()
+      "File saved as '#{args[0]}'."
+    help: (args) ->
+      """
+  javac <source file>    -- Compile Java source.
+  java <class> [args...] -- Run with command-line arguments.
+  javap <class>          -- Display disassembly.
+  ls                     -- List all files.
+  edit <file>            -- Edit <file> in the editor.
+  save <file>            -- Save editor contents as <file>.
+  rm <file>              -- Delete a file.
+  clear_heap             -- Clear the heap.
+      """

 tabComplete = ->
   promptText = controller.promptText()


Update main.coffee

Update main.coffee

--- playground/main.coffee
+++ playground/main.coffee
@@ -6,7 +6,7 @@
     @setCss 'padding-left' : '50px'

     {randomIndex, id} = @getData()
-    "#{id} numarali itemim ulan indexim de #{randomIndex or 'yok'}!!

"
+    "I'm the number #{id} and my index: #{randomIndex or 'N/A'}!!

"

 mainView = new KDView
@@ -22,7 +22,7 @@
 mainView.addSubView input = new KDInputView

 mainView.addSubView new KDButtonView
-  title    : 'Random indexli eleman ekle'
+  title    : 'Add random indexed item'
   callback : ->
     randomIndex = parseInt(input.getValue(), 10) or 0
     listController.addItem {title : Date.now(), id : ++id, randomIndex}, randomIndex
@@ -45,4 +45,4 @@

 # listController.getListItems()

-window.kamil = listController+window.kamil = listController


remove log

remove log

--- client/views/app/chatWindowDashboard.coffee
+++ client/views/app/chatWindowDashboard.coffee
@@ -478,8 +478,6 @@
 		Session.set('editRoomTitle', false)
 		return false

-	console.log name, Session.get('roomData' + roomId).name
-
 	Meteor.call 'saveRoomName', { rid: roomId, name: name }, (error, result) ->
 		if result
 			Session.set('editRoomTitle', false)


switch to setting user in _rollbarParams so rollbar.com can track them seperately

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

 # Push status events to Rollbar.
@@ -19,3 +18,10 @@
     connections:connections
     disconnections: disconnections
   logToExternal msg:"disconnected, then reconnected", data:data
+
+KD.getSingleton('mainController').on "AccountChanged", (account) ->
+  user = KD.whoami?().profile or KD.whoami()
+  _rollbarParams.person =
+    id: user.hash or user.nickname
+    username: user.nickname
+    name: user.firstName + " " + user.lastName


Use default label value if present

Use default label value if present

--- client/lanes/components/shared/Input.cjsx
+++ client/lanes/components/shared/Input.cjsx
@@ -18,17 +18,16 @@
         @props.onEnter() if ev.key is 'Enter'

     renderEdit: (label) ->
-        value = @props.model[@props.name] or ''
-        label = @props.label or _.field2title(@props.name)
+        value = @model[@props.name] or ''
+        label ||= @props.label or _.field2title(@props.name)
         props = _.extend({
             ref:       'input'
             className: _.classnames('value', changeset: @state.changeset)
-            label:     if @props.unlabeled then label else false
+            label:     if @props.unlabeled then false else label
             value:     value
             onKeyDown: @handleKeyDown if @props.onEnter
             onChange:  @handleChange
         }, @props)
-
         if @props.inputOnly then @renderPlain(props) else @renderStyled(props, label)

     renderPlain: (props) ->


Clean up dem variables

Clean up dem variables

--- scripts/github-pull-request-notifier.coffee
+++ scripts/github-pull-request-notifier.coffee
@@ -29,19 +29,16 @@
 module.exports = (robot) ->
   robot.router.post "/hubot/gh-pull-requests", (req, res) ->
     query = querystring.parse(url.parse(req.url).query)
-    console.log("received request: #{req.url}")
-    console.log("query: #{query}")

     res.end

-    user = {}
-    user.room = query.room if query.room
-    user.type = query.type if query.type
-    console.log("going to push to room #{user.room}")
+    data = req.body
+    room = query.room
+    console.log("going to push to room #{room}")
     console.log("#{typeof(data)} == #{data}")

     try
-      announcePullRequest req.body, (what) ->
+      announcePullRequest data, (what) ->
         robot.messageRoom user.room, what
     catch error
       console.log "github pull request notifier error: #{error}. Request: #{req.body}"


Add id's to header elements after markdown render

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -13,7 +13,7 @@
 state_ = require './state.coffee'
 require './state-gist.coffee'

-{number, index, toc} = require './utils.coffee'
+{link, number, index, toc} = require './utils.coffee'

 extend = (r={}, d) -> r[k] = v for k, v of d when v?; r
 extendA = (r={}, a) -> r[k] = v for [k, v] in a when v?; r
@@ -90,6 +90,7 @@

 setViewHtml = (html) ->
   viewEl.innerHTML = html
+  link viewEl.querySelectorAll 'H1,H2,H3,H4,H5,H6'
   updateIndex() if state.index
   updateToc() if state.toc


log fixed.

log fixed.

--- coffee_mate.coffee
+++ coffee_mate.coffee
@@ -8,8 +8,8 @@
 		ball = []
 		for f in args
 			if typeof f == 'function'
-				expr = f.toString().replace(/\s*function\s*\(\s*\)\s*{\s*return\s*([^]*);\s*}/, '$1')
-				expr = expr.replace /[\r\n]{1,2}\s*/g, '' if expr.length <= 100
+				expr = f.toString().replace(/^\s*function\s?\(\s?\)\s?{\s*return\s*([^]*?);?\s*}$/, '$1')
+				expr = expr.replace(/[\r\n]{1,2}\s*/g, '') if expr.length <= 100
 				ball.push("## #{expr} ==>", f())
 			else
 				ball.push('##', f)
@@ -50,6 +50,14 @@
 	r

 ######################## reinforce array #########################
+
+Object.defineProperties Array.prototype,
+    first:
+        get: -> this[0]
+        set: (v) -> this[0] = v
+    last:
+        get: -> this[@length - 1]
+        set: (v) -> this[@length - 1] = v

 reversed = (arr) ->
 	arr.slice().reverse()
@@ -93,7 +101,7 @@

 random_gen = (seed = Math.random()) ->
 	->
-		x = Math.sin(++seed) * 10000
+		x = Math.sin(++seed) * 1e4
 		x - Math.floor(x)

 ranged_random_gen = (range, seed = 0) ->
@@ -139,3 +147,4 @@

 	square: square
 	cube: cube
+


ID collisions are causing test failures

ID collisions are causing test failures

--- test/spec/iterationplanningboard/IterationPlanningBoardAppSpec.coffee
+++ test/spec/iterationplanningboard/IterationPlanningBoardAppSpec.coffee
@@ -342,10 +342,7 @@
     firstStoryName = 'Story 1'
     userStory1 =
       Iteration: @getIteration1(),
-      _ref: '/hierarchicalrequirement/1'
-      _refObjectName: firstStoryName
       Name: firstStoryName
-      ObjectID: 1,
       Rank: storyRank

     @ajax.whenQuerying('artifact').respondWith(userStory1)
@@ -354,13 +351,10 @@
       secondStoryName = 'Story ranked lower than 1'
       userStory2 =
         Iteration: @getIteration1(),
-        _ref: '/hierarchicalrequirement/2'
-        _refObjectName: secondStoryName
         Name: secondStoryName
         ObjectID: 2,
         Rank: storyRank + 1

-      @ajax.whenQuerying('artifact').respondWith([userStory1, userStory2])
       @ajax.whenQuerying('artifact').respondWith [userStory2]
       Rally.environment.getMessageBus().publish Rally.Message.objectCreate, [@createUserStoryRecord userStory2]


[FIX] Move renderHTMLCode to the callback of save

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -92,7 +92,10 @@
   renderHTML: ->
     @showLoading()
     if @editor?
-      @renderHTMLCode()
+      if not atom.config.get("atom-html-preview.triggerOnSave") && @editor.getPath()?
+        @save(@renderHTMLCode)
+      else
+        @renderHTMLCode()

   save: (callback) ->
     # Temp file path
@@ -128,10 +131,10 @@
     out += @editor.getText()

     @tmpPath = outPath
-    fs.writeFile outPath, out, callback
+    fs.writeFile outPath, out, =>
+      @renderHTMLCode()

-  renderHTMLCode: (text) ->
-    if not atom.config.get("atom-html-preview.triggerOnSave") and @editor.getPath()? then @save()
+  renderHTMLCode: () ->
     iframe = document.createElement("iframe")
     # Fix from @kwaak (https://github.com/webBoxio/atom-html-preview/issues/1/#issuecomment-49639162)
     # Allows for the use of relative resources (scripts, styles)


open atom.io from package card

open atom.io from package card

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -64,6 +64,9 @@
       @installButton.hide()
       @uninstallButton.hide()

+    @on 'click', =>
+      @parents('.settings-view').view()?.showPanel(@pack.name, {back: opts?.back})
+
     @installButton.on 'click', =>
       @install()

@@ -73,8 +76,10 @@
     @settingsButton.on 'click', =>
       @parents('.settings-view').view()?.showPanel(@pack.name, {back: opts?.back})

-    @packageName.on 'click', =>
-      @parents('.settings-view').view()?.showPanel(@pack.name, {back: opts?.back})
+    @packageName.on 'click', (event) =>
+      event.stopPropagation()
+      packageType = if @pack.theme then 'themes' else 'packages'
+      shell.openExternal("https://atom.io/#{packageType}/#{@pack.name}")

     @enablementButton.on 'click', =>
       if @isDisabled()


change card header title to input

change card header title to input

--- jsapp/xlform/src/view.row.templates.coffee
+++ jsapp/xlform/src/view.row.templates.coffee
@@ -62,7 +62,7 @@

-
+            <input type="text" placeholder="#{_t("Question title")}" class="card__header-title js-cancel-select-row js-cancel-sort">

@@ -105,7 +105,7 @@

-
+            <input type="text" placeholder="#{_t("Question title")}" class="card__header-title js-cancel-select-row js-cancel-sort">


fix #1432 nicEdit text_area in card_form is not initialized in Safari

--- app/assets/javascripts/card_markup.js.coffee
+++ app/assets/javascripts/card_markup.js.coffee
@@ -2,6 +2,8 @@
   modalpane = $ document.createElement("div")
   modalpane.attr "id", "modal-pane"
   $(document.body).append modalpane
+
+  is_safari = if window.navigator.userAgent.toLowerCase().indexOf("safari") != -1 then true else false

   nicMarkupButton = nicEditorAdvancedButton.extend {
     width: 400,
@@ -94,7 +96,15 @@
       this.container = container

       selectionText = $.selection()
-      this.link = this.ne.selectedInstance.selElm().parentTag "A"
+      if is_safari
+        sel_elm = this.ne.selectedInstance.selElm()
+        unless sel_elm?
+          this.link = false
+        else
+          this.link = sel_elm.parentTag "A"
+      else
+        this.link = this.ne.selectedInstance.selElm().parentTag "A"
+
       unless this.link
         if selectionText.length > 0
           nameInput.val selectionText
@@ -167,7 +177,10 @@
       element.text name

       unless this.link
-        this.ne.nicCommand "insertHTML", element.get(0).outerHTML
+        if is_safari
+          this.ne.selectedInstance.elm.appendChild element.get(0)
+        else
+          this.ne.nicCommand "insertHTML", element.get(0).outerHTML

     clickCancel: ->
       this.removePane()


Fixed bug where I was not fetching the owner_id attribute from a cloud.

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -98,7 +98,7 @@
   # @param {cloud} an instance of {Cloud}
   # @return {number} 0 is not a member, 1 is a normal member, 2 is a moderator and 3 is the owner
   rankOn: (cloud) ->
-    if cloud.owner_id == @id
+    if cloud.get('owner_id') == @id
       return 3
     else if _.include(cloud.get('moderator_ids'), @id)
       return 2


Reversed

Reversed

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -58,12 +58,6 @@
   toBroadcastJSON: ->
     obj = @toJSON()
     _.pick obj, "id"
-
-  toJSON: ->
-    name: @get('name')
-    email: @get('email')
-    time_zone: @get('time_zone')
-    invisible: @get('invisible')

   toSelectable: ->
     return { id: @id, text: @get('name'), avatar: @get('avatar').mini }


Rewrite `unindentMessage`, use os-specific EOL

Should help with atom-haskell/ide-haskell#183

--- lib/builders/cabal-process.coffee
+++ lib/builders/cabal-process.coffee
@@ -2,6 +2,7 @@
 child_process = require 'child_process'
 {kill}        = require 'process'
 path          = require 'path'
+{EOL}         = require 'os'

 # Atom dependencies
 {Directory, Point} = require 'atom'
@@ -75,16 +76,10 @@
     msgs.filter (msg) -> msg?

   unindentMessage: (message) ->
-    lines = message.split('\n')
-    minIndent = null
-    for line in lines
-      match = line.match /^[\t\s]*/
-      lineIndent = match[0].length
-      minIndent = lineIndent if lineIndent < minIndent or not minIndent?
-    if minIndent?
-      lines = for line in lines
-        line.slice(minIndent)
-    lines.join('\n')
+    lines = message.split(EOL)
+    minIndent = Math.min((lines.map (line) -> line.match(/^[\t\s]*/)[0].length)...)
+    lines.map (line) -> line.slice(minIndent)
+    .join('\n')

   parseMessage: (raw) ->


refactoring

refactoring

--- app/assets/javascripts/s3_form_for.js.coffee
+++ app/assets/javascripts/s3_form_for.js.coffee
@@ -87,10 +87,9 @@
           canvas: false
     else
       # Fallback; just use a placeholder
-      image = $('<img>')
-      image.attr('src', assetPath('media/mrx-placeholder-120x90.png'))
-      $('#upload_thumbnail').empty()
-      $('#upload_thumbnail').append(image)
+      image = $('<img/>', {src: assetPath('media/mrx-placeholder-120x90.png')})
+      window.image = image
+      $('#upload_thumbnail, .s3_image').html(image)

   if settings.click_submit_target
     settings.click_submit_target.click =>


fix typo

fix typo

--- test/collections/partner_artist_artworks.coffee
+++ test/collections/partner_artist_artworks.coffee
@@ -13,6 +13,6 @@

   describe '#serialize', ->

-    it 'plucks artworks from the PartnerArtistArtwork resluts', ->
+    it 'plucks artworks from the PartnerArtistArtwork results', ->
       @partner_artist_artworks.first.should.equal @artworks.first


Fix js street view errors

Fix js street view errors

--- app/assets/javascripts/street_view.js.coffee
+++ app/assets/javascripts/street_view.js.coffee
@@ -56,7 +56,13 @@
     return

   initialize = ->
-    panorama = new google.maps.StreetViewPanorama(document.getElementById("newStreetViewPano"))
+    addStreetView(streetView) for streetView in streetViews
+
+    newStreetViewPano = document.getElementById("newStreetViewPano")
+
+    return unless newStreetViewPano
+
+    panorama = new google.maps.StreetViewPanorama(newStreetViewPano) if newStreetViewPano

     svs.getPanoramaByLocation(issue, 200, processSVData)

@@ -70,8 +76,6 @@
       google.maps.event.trigger(panorama, 'resize')
       google.maps.event.trigger(map, 'resize')

-    addStreetView(streetView) for streetView in streetViews
-
     return
   initialize()


fix $scope.$apply issues

fix $scope.$apply issues

--- src/app/projects/partials/directives/project-contexts.coffee
+++ src/app/projects/partials/directives/project-contexts.coffee
@@ -186,6 +186,8 @@
           viewport = page.getViewport(scale)

         canvas = document.getElementById("pdf")
+        if not canvas
+          return
         context = canvas.getContext("2d")
         canvas.height = viewport.height
         canvas.width = viewport.width
@@ -194,9 +196,6 @@
         page.render(renderContext).then ( ()->
           pdfLoaded = true
         )
-      # FORCE update scope to recheck its functions inside HTML
-      # This is needed for nav of pdf pages to be force-checked
-      $scope.$apply()
       )
     #
     # PDF Interaction Funcs


clean

clean

--- examples/basic-server.coffee
+++ examples/basic-server.coffee
@@ -6,7 +6,6 @@

 server.root "#{__dirname}/public"
 server.enable 'csrf', 'lfi', 'xss', '404', 'mime', 'static'
-#server.engine 'jade', jade, {some: 'options'}

 params =
   data:
@@ -16,14 +15,9 @@
           {name: "green", link: true, url: "#Green"}
           {name: "blue", link: true, url: "#Blue"}
       ]
-
+
 server.engine 'mustache', hogan, params
-
-hogan = require 'hogan.js'
-compile = hogan.compile
-compiled = compile '{{test}}'
-rendered = compiled.render test: "hi"
-console.log rendered
+#server.engine 'jade', jade, params

 server.set 'production'
 server.set 'debug', false


adding conversations

adding conversations

--- app/assets/javascripts/conversations.coffee
+++ app/assets/javascripts/conversations.coffee
@@ -1,7 +1,27 @@
 jQuery(document).on 'turbolinks:load', ->
+	messages_to_bottom = -> messages.scrollTop(messages.prop("scrollHeight"))
 	messages = $('#conversation-body')
-	if messages.length > 0
-		messages_to_bottom = -> messages.scrollTop(messages.prop("scrollHeight"))
+

-		messages_to_bottom()
-		+	if $('#current-user').size() > 0
+		App.personal_chat = App.cable.subscriptions.create {
+			channel: "NotificationsChannel"
+		},
+		connected: ->
+			# Called when the subscription is ready for use the server
+
+		disconnected: ->
+			# Called when the subscription has been terminated by the server
+
+		received: (data) ->
+			# Called when data is received
+		if messages.length > 0
+			messages_to_bottom()
+			$('#new_personal_message').submit (e) ->
+				$this = $(this)
+				textarea = $this.find('#personal_message_body')
+				if $.trim(textarea.val()).length > 1
+					App.personal_chat.send_message textarea.val(), $this.find('#conversation_id').val()
+					textarea.val('')
+				e.preventDefault()
+				return false


tweaks to classNames to get toggles working

--- jsapp/xlform_model_view/view.row.coffee
+++ jsapp/xlform_model_view/view.row.coffee
@@ -62,14 +62,11 @@
     _renderRow: ->
       @$el.html $viewTemplates.$$render('row.xlfRowView')

-      @$srItem = @$('.survey__row__item').eq(0)
       @$label = @$('.card__header-title')
       @$card = @$('.card')
       @$header = @$('.card__header')
       if 'getList' of @model and (cl = @model.getList())
-        @$card.addClass('card--selectquestion')
-        if @is_expanded
-          @$card.addClass('card--expandedchoices')
+        @$card.addClass('card--selectquestion card--expandedchoices')
         @listView = new $viewChoices.ListView(model: cl, rowView: @).render()

       for [key, val] in @model.attributesArray() when key is 'label' or key is 'type'
@@ -87,11 +84,11 @@

       if show and !@_settingsExpanded
         @_expandedRender()
-        @$srItem.addClass('card--expanded-settings')
-        @$el.removeClass('card--expanded-choices')
+        @$card.addClass('card--expanded-settings')
+        @$card.removeClass('card--expandedchoices')
         @_settingsExpanded = true
       else if !show and @_settingsExpanded
-        @$srItem.removeClass('card--expanded-settings')
+        @$card.removeClass('card--expanded-settings')
         @_cleanupExpandedRender()
         @_settingsExpanded = false
       ``
@@ -124,11 +121,11 @@
     render: ->
       if !@already_rendered
         @$el.html $viewTemplates.row.groupView(@model)
-        @$srItem = @$('.survey__row__item').eq(0)
         @$label = @$('.group__label').eq(0)
         @$rows = @$('.group__rows').eq(0)
+        @$card = @$('.card')
+        @$header = @$('.card__header,.group__header').eq(0)

-
       @model.rows.each (row)=>
         @getApp().ensureElInView(row, @, @$rows).render()

@@ -162,6 +159,7 @@
         @is_expanded = false
       else
         @$card.addClass('card--expandedchoices')
+        @$card.removeClass('card--expanded-settings')
         @is_expanded = true
       ``


stripped punctuation from url title

stripped punctuation from url title

--- src/scripts/helpers/handlebars/trim.coffee
+++ src/scripts/helpers/handlebars/trim.coffee
@@ -1,7 +1,8 @@
 define (require) ->
   Handlebars = require('hbs/handlebars')

-  trim = (str = '') -> str.replace(/\s/g, '-').substring(0, 30)
+  trim = (str = '') ->
+    str.replace(/\s/g, '-').replace(/[,.!?]/g, '').substring(0, 30)

   Handlebars.registerHelper 'trim', (str) -> trim(str)


issue #2893 - Fix role selector in admin > members

--- app/coffee/modules/admin/memberships.coffee
+++ app/coffee/modules/admin/memberships.coffee
@@ -90,7 +90,6 @@
     loadInitialData: ->
         promise = @.loadProject()
         promise.then =>
-            @.loadUsersAndRoles()
             @.loadMembers()

         return promise
@@ -301,7 +300,7 @@
     link = ($scope, $el, $attrs) ->
         render = (member) ->
             ctx = {
-                roleList: $scope.roles,
+                roleList: $scope.project.roles,
                 selectedRole: member.role
             }


More razonable ux on memberships email showing

--- app/coffee/modules/admin/memberships.coffee
+++ app/coffee/modules/admin/memberships.coffee
@@ -245,7 +245,7 @@
         render = (member) ->
             ctx = {
                 full_name: if member.full_name then member.full_name else ""
-                email: member.email
+                email: if member.user_email then member.user_email else member.email
                 imgurl: if member.photo then member.photo else "/images/unnamed.png"
             }


[Backport] fix delete redirection

[Backport] fix delete redirection

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -80,12 +80,17 @@
             docEl.off(".lightbox")
             docEl.off(".keyboard-navigation") # Hack: to fix problems in the WYSIWYG textareas when press ENTER

+            $el.addClass('close-started') # don't attach animations
+
             @animationFrame.add =>
                 $el.addClass('close')

                 $el.one "transitionend", =>
                     $el.removeAttr('style')
-                    $el.removeClass("open").removeClass('close')
+                    $el
+                        .removeClass("open")
+                        .removeClass('close')
+                        .removeClass('close-started')

                     if @.onClose
                         @rootScope.$apply(@.onClose)
@@ -99,7 +104,7 @@

     getLightboxOpen: ->
-        return $(".lightbox.open")
+        return $(".lightbox.open:not(.close-started)")

     closeAll: ->
         docEl = angular.element(document)


Handle edge cases when updating [link]s

Handle edge cases when updating [link]s

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -98,10 +98,10 @@
           $el = $(el)
           href = $el.attr('href')

-          if href.substr(0, 1) is '#' and $el.data('type') isnt 'footnote-ref'
+          if href.substr(0, 1) is '#' and href.length > 1 and $el.data('type') isnt 'footnote-ref'
             $target = $temp.find(href)
             tag = $target?.prop('tagName')?.toLowerCase()
-            if $el.text() is '[link]'
+            if $el.text() is '[link]' and tag
               tag = tag.charAt(0).toUpperCase() + tag.substring(1)
               $el.text("#{tag}") if tag isnt 'undefined'


Reintroduce core_loaded event at the right moment

--- js-library/app/js/loader/loader_common.coffee
+++ js-library/app/js/loader/loader_common.coffee
@@ -70,7 +70,10 @@

     root.core_loaded_promise
-    .then( -> root.ready_promise)
+    .then( ->
+        root.perf.add_timing_event 'core_loaded'
+        root.ready_promise
+    )
     .then ->
       #called from jail-iframe when core iframe is ready.
       for name in methods


Fixed: Incorrect cache logic of friendship.

--- src/routes/friendship.coffee
+++ src/routes/friendship.coffee
@@ -159,11 +159,13 @@
                     timestamp

                 Cache.del "friendship_all_#{currentUserId}"
+                Cache.del "friendship_all_#{friendId}"

                 Utility.log 'Invite result: %s %s', action, resultMessage
                 res.send new APIResult 200, action: action, resultMessage
             else
               Cache.del "friendship_all_#{currentUserId}"
+              Cache.del "friendship_all_#{friendId}"

               Utility.log 'Invite result: %s %s', action, resultMessage
               res.send new APIResult 200, action: action, resultMessage
@@ -184,6 +186,7 @@
         ]
         .then ->
           Cache.del "friendship_all_#{currentUserId}"
+          Cache.del "friendship_all_#{friendId}"

           Utility.log 'Invite result: %s %s', action, resultMessage
           res.send new APIResult 200, action: action, resultMessage
@@ -227,6 +230,7 @@
                   timestamp

               Cache.del "friendship_all_#{currentUserId}"
+              Cache.del "friendship_all_#{friendId}"

               Utility.log 'Invite result: %s %s', 'Sent', 'Request sent.'
               res.send new APIResult 200, action: 'Sent', 'Request sent.'
@@ -282,6 +286,7 @@
               timestamp

           Cache.del "friendship_all_#{currentUserId}"
+          Cache.del "friendship_all_#{friendId}"

           res.send new APIResult 200
   .catch next
@@ -309,6 +314,7 @@
     DataVersion.updateFriendshipVersion currentUserId, timestamp
     .then ->
       Cache.del "friendship_all_#{currentUserId}"
+      Cache.del "friendship_all_#{friendId}"

       res.send new APIResult 200
   .catch next
@@ -339,6 +345,7 @@
     .then ->
       Cache.del "friendship_profile_displayName_#{currentUserId}_#{friendId}"
       Cache.del "friendship_all_#{currentUserId}"
+      Cache.del "friendship_all_#{friendId}"

       res.send new APIResult 200
   .catch next


Client: Activity: Removed unnecessary name field validation.

--- client/activity/lib/components/createprivatechannelmodal/index.coffee
+++ client/activity/lib/components/createprivatechannelmodal/index.coffee
@@ -30,7 +30,6 @@
       purpose             : ''
       query               : ''
       deleteMode          : no
-      invalidName         : no
       invalidParticipants : no
       placeholder         : 'type a @username and hit enter'

@@ -58,11 +57,6 @@
     'CreateChannel-participantsWrapper' : yes

-  getNameFieldClassnames: -> classnames
-    'Reactivity-formfield' : yes
-    'invalid'              : @state.invalidName
-
-
   getDropboxFieldClassnames: -> classnames
     'Reactivity-formfield' : yes
     'dropdown'             : yes
@@ -84,7 +78,6 @@
     value = event.target.value
     value = value.toLowerCase()
     @setState name: value
-    @validateName(value)

   setPurpose: (event) ->
@@ -114,24 +107,7 @@
       .toJS()

-  validateName: (value) ->
-
-    pattern =  /^[a-z0-9]+$/i
-
-    unless value
-      @setState invalidName: no
-      return yes
-
-    if value and pattern.test value
-      @setState invalidName: no
-      return yes
-
-    else
-      @setState invalidName: yes
-      return no
-
-
-  validateParticipants: () ->
+  validateParticipants: ->

     recipients = @prepareRecipients()

@@ -143,19 +119,9 @@
       return no

-  validateForm : ->
-
-    isValidName         = @validateName(@state.name)
-    isValidParticipants = @validateParticipants()
-
-    if isValidName and isValidParticipants
-      return yes
-    return no
-
-
   createChannel: ->

-    return  unless @validateForm()
+    return  unless @validateParticipants()

     recipients = @prepareRecipients()
     options =
@@ -326,7 +292,7 @@
           Invite Members
           {@renderAddParticipantInput()}

-

+

             Name
              (optional)


Update compile_config.coffee

Update compile_config.coffee

--- src/tasks/build/base/compile_config.coffee
+++ src/tasks/build/base/compile_config.coffee
@@ -11,10 +11,11 @@
     configXml = helpers.config 'config'

     dest = path.join phonegapPath, 'www', 'config.xml'
-    root = path.join phonegapPath, 'config.xml'
+    root = path.join phonegapRoot, 'config.xml'

     if grunt.util.kindOf(configXml) == 'string'
       grunt.log.writeln "Copying static #{configXml}"
+      yield cp configXml, root
       cp configXml, dest, (err) ->
         if fn then fn(err)

@@ -22,5 +23,6 @@
       grunt.log.writeln "Compiling template #{configXml.template}"
       template = grunt.file.read configXml.template
       compiled = grunt.template.process template, data: configXml.data
+      grunt.file.write root, compiled
       grunt.file.write dest, compiled
       if fn then fn()


remove confusing message from hubot pager incident

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -75,6 +75,8 @@
     msg.send "Okay, I've forgotten your PagerDuty email"

   robot.respond /(pager|major)( me)? incident (.*)$/i, (msg) ->
+    msg.finish()
+
     if pagerduty.missingEnvironmentForApi(msg)
       return


Log an error if the required environment variables are not set.

--- src/scripts/turntable.coffee
+++ src/scripts/turntable.coffee
@@ -38,3 +38,5 @@
       robot.messageRoom env.TT_CHAN, "#{data.name}@turntable.fm: #{data.text}"

     ttbot.on 'speak', speak_callback
+  else
+    console.log 'Set the TT_AUTH, TT_USERID, TT_ROOMID and TT_CHAN environment variables to enable the turntable.fm script'


adapter introducing

adapter introducing

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -48,11 +48,14 @@
 						isDatasource = (datasource) ->
 							angular.isObject(datasource) and datasource.get and angular.isFunction(datasource.get)

-						getValueChain = (targetScope, target) ->
-							return null if not targetScope
+						getValueChain = (targetScope, target, isSet) ->
+							return if not targetScope
 							chain = target.match(/^([\w]+)\.(.+)$/)
-							return targetScope[target] if not chain or chain.length isnt 3
-							return getValueChain(targetScope[chain[1]], chain[2])
+							if not chain or chain.length isnt 3
+								return targetScope[target] = {} if isSet and not angular.isObject(targetScope[target])
+								return targetScope[target]
+							targetScope[chain[1]] = {} if isSet and not angular.isObject(targetScope[chain[1]])
+							return getValueChain(targetScope[chain[1]], chain[2], isSet)

 						datasource = getValueChain($scope, datasourceName)

@@ -60,6 +63,8 @@
 							datasource = $injector.get(datasourceName)
 							throw new Error "#{datasourceName} is not a valid datasource" unless isDatasource datasource

+						adapterAttr = getValueChain($scope, $attr.adapter, true) if $attr.adapter
+
 						bufferSize = Math.max(3, +$attr.bufferSize || 10)
 						bufferPadding = -> viewport.outerHeight() * Math.max(0.1, +$attr.padding || 0.1) # some extra space to initate preload

@@ -67,6 +72,7 @@
 							elem[0].scrollHeight ? elem[0].document.documentElement.scrollHeight

 						builder = null
+						adapter = {}

 						# Calling linker is the only way I found to get access to the tag name of the template
 						# to prevent the directive scope from pollution a new scope is created and destroyed
@@ -409,7 +415,7 @@
 							viewport.unbind 'scroll', resizeAndResizeHandler
 							viewport.unbind 'mousewheel', wheelHandler

-						eventListener.$on "update.items", (event, locator, newItem)->
+						adapter.update = (locator, newItem) ->
 							if angular.isFunction locator
 								((wrapper)->
 									locator wrapper.scope
@@ -419,7 +425,7 @@
 									buffer[locator-first-1].scope[itemName] = newItem
 							null

-						eventListener.$on "delete.items", (event, locator)->
+						adapter.delete = (locator) ->
 							if angular.isFunction locator
 								temp = []
 								temp.unshift item for item in buffer
@@ -436,21 +442,9 @@
 							item.scope.$index = first + i for item,i in buffer
 							adjustBuffer()

-						eventListener.$on "insert.item", (event, locator, item)->
+						adapter.insert = (locator, item) ->
 							inserted = []
 							if angular.isFunction locator
-#								temp = []
-#								temp.unshift item for item in buffer
-#								((wrapper)->
-#									if newItems = locator wrapper.scope
-#										insert = (index, newItem) ->
-#											insert index, newItem
-#											next++
-#										if isArray newItems
-#											inserted.push(insert i+j, item) for item,j in newitems
-#										else
-#											inserted.push (insert i, newItems)
-#								) wrapper for wrapper,i in temp
 								throw new Error('not implemented - Insert with locator function')
 							else
 								if 0 <= locator-first-1 < buffer.length
@@ -460,6 +454,13 @@
 							item.scope.$index = first + i for item,i in buffer
 							adjustBuffer(null, inserted)

+						# deprecated since v1.1.0
+						eventListener.$on "insert.item", (event, locator, item)-> adapter.insert(locator, item)
+						eventListener.$on "update.items", (event, locator, newItem)-> adapter.update(locator, newItem)
+						eventListener.$on "delete.items", (event, locator)-> adapter.delete(locator)
+
+						angular.extend(adapterAttr, adapter) if adapterAttr
+
 		])

 ###


Minor update to comments

Minor update to comments

--- src/scripts/ui/events.coffee
+++ src/scripts/ui/events.coffee
@@ -1,6 +1,6 @@
 class ContentTools.Event

-    # The `Event` class provides information about events dispatched from/to
+    # The `Event` class provides information about events dispatched by
     # `UIComponents`.

     constructor: (name, detail) ->


added the error response and configuration for the self-host instance.

--- src/scripts/voicechat.coffee
+++ src/scripts/voicechat.coffee
@@ -9,13 +9,13 @@
 #
 # Configuration:
 #   HUBOT_VOICECHAT_HOST - (optional) The base URL of a self-hosted instance of the Voice Chat API.
-#   HUBOT_VOICECHAT_EXPIRE_CONFERENCE - (optional) True if conference is set to expire after 24 hours of inactivity
+#   HUBOT_VOICECHAT_EXPIRE_CONFERENCE - (optional) True if conference is set to expire after 24 hours since its creation.
 #
 # Commands:
 #   hubot conference - create an audio conference room.
 #
 # Author:
-#   DHfromKorea <dh@dhfromkorea.com>
+#   DHfromKorea <dhllee424@gmail.com>
 module.exports = (robot) ->
   url = "http://www.voicechatapi.com"
   purgeMessage = ""


Adding the annotate appearance that was missing

--- jsapp/xlform_model_view/view.rowDetail.coffee
+++ jsapp/xlform_model_view/view.rowDetail.coffee
@@ -264,7 +264,7 @@
         text: ['multiline', 'numbers']
         select_one: ['minimal', 'quick', 'horizontal-compact', 'horizontal', 'likert', 'compact', 'quickcompact', 'label', 'list-nolabel']
         select_multiple: ['minimal', 'horizontal-compact', 'horizontal', 'compact', 'label', 'list-nolabel']
-        image: ['signature', 'draw']
+        image: ['signature', 'draw', 'annotate']
         date: ['month-year', 'year']
         group: ['select', 'field-list', 'table-list', 'other']


Re-render when the facets are reset in concept column control

This also moves the event listener registration for add and remove into initialize so they aren't reregistered each time the facets are reset.

--- src/coffee/cilantro/ui/concept/columns.coffee
+++ src/coffee/cilantro/ui/concept/columns.coffee
@@ -183,17 +183,18 @@
             @listenToOnce(@data.view, 'change:json', @resetFacets)
             @listenToOnce(@data.concepts, 'reset', @resetFacets)

-            @$el.modal(show: false)
-
-        resetFacets: ->
-            @data.facets.reset(@data.view.facets.toJSON())
-
-        onRender: ->
             # Sync and map between available columns and selected
             # columns (represented as facets)
             @listenTo(@data.concepts, 'columns:add', @addColumn, @)
             @listenTo(@data.facets, 'columns:remove', @removeColumn, @)

+            @$el.modal(show: false)
+
+        resetFacets: =>
+            @data.facets.reset(@data.view.facets.toJSON())
+            @render()
+
+        onRender: ->
             @available.show new @regionViews.available
                 collection: @data.concepts
                 collapsable: false


Fixed whitespacing.

Fixed whitespacing.

--- src/server/auth/route.coffee
+++ src/server/auth/route.coffee
@@ -67,8 +67,8 @@
         done(null, user)

     config.map 'doorman.providers', (name, provider)->
-      provider.callbackURL = "/#{base}/#{name}/callback"
-      provider
+        provider.callbackURL = "/#{base}/#{name}/callback"
+        provider
     for provider, providerConfig of config.find 'doorman.providers'
         try
             attach provider, providerConfig, app, base


Update team information

Update team information

--- client/About/team.coffee
+++ client/About/team.coffee
@@ -25,7 +25,7 @@
         username  : 'sent-hil'
         title     : 'Software Engineer'
       ,
-        username  : 'siesta'
+        username  : 'cihangirsavas'
         title     : 'Software Engineer'
       ,
         username  : 'geraint'
@@ -37,7 +37,7 @@
         username  : 'erdinc'
         title     : 'Software Engineer'
       ,
-        username  : 'sonmez'
+        username  : 'szkl'
         title     : 'Software Engineer'
       ,
         username  : 'samet'
@@ -46,7 +46,7 @@
         username  : 'cenk6'
         title     : 'Software Engineer'
       ,
-        username  : 'neoberg'
+        username  : 'burakcan'
         title     : 'CSS Wizard'
       ,
         username  : 'emred'
@@ -68,7 +68,7 @@
         username  : 'meelash'
         title     : 'Software Engineer'
       ,
-        username  : 'rgoodness'
+        username  : 'rgoodman'
         title     : 'Designer'
       ,
         username  : 'sntran'


disable entry when offline

disable entry when offline

--- src/scripts/entryarea.coffee
+++ src/scripts/entryarea.coffee
@@ -118,6 +118,10 @@
   ### public ###
   init: ->
     super()
+    @channel.bind
+      online: => @el.disabled = no
+      offline: => @el.disabled = yes
+      disconnected: => @el.disabled = yes
     # for keydown and changed
     _changed = @_onchanged.bind @
     @on 'keydown', (e) => @_onkeydown e


add lazyload fallback

add lazyload fallback

--- src/scripts/main/main.coffee
+++ src/scripts/main/main.coffee
@@ -62,3 +62,8 @@
         $(".#{target}").addClass("item show").removeClass("hide")
         status[target] = "show"
         $(@).addClass("press")
+
+    $(window).bind "load", ->
+      load = ->
+        $("img.lazy").trigger "sporty"
+      timeout = setTimeout load, 5000


GameObject: Fix cloning materials of meshes in the same scene.

--- engine/gameobject.coffee
+++ engine/gameobject.coffee
@@ -473,8 +473,8 @@

         # Warning! This only works reliably
         # if the target scene have the same type of lamps!
+        n.materials = materials = n.materials?[...]
         if n.materials and scene != this.scene
-            n.materials = materials = n.materials[...]
             for i in [0...materials.length]
                 mat = materials[i] = materials[i].clone_to_scene scene


proper redirect

proper redirect

--- client/lib/legacy.coffee
+++ client/lib/legacy.coffee
@@ -333,7 +333,7 @@
     # if the page appears to be a server generated page, but we're running javascript then
     # redirect to the full-featured client-side version of the page.
     # E.G. visiting /welcome-visitors.html with a JS-enabled browser will bounce you to /view/welcome-visitors
-    document.location = "/view/#{$firstPage.attr('id')}"
+    window.location = "/view/#{$firstPage.attr('id')}"
   else
     # CODE that gets the web page application started
     state.first()


config/prod: remove client builder from runFile :zap:

--- config/main.prod.coffee
+++ config/main.prod.coffee
@@ -555,10 +555,6 @@
       #!/bin/bash
       export HOME=/home/user-ec2
       export KONFIG_JSON='#{KONFIG.JSON}'
-
-      cd #{projectRoot}/client
-      npm install --unsafe-perm
-      make dist
       """

   KONFIG.ENV             = (require "../deployment/envvar.coffee").create KONFIG


updated the doc

updated the doc

--- js/outer/versions.coffee
+++ js/outer/versions.coffee
@@ -189,10 +189,10 @@
   erlang: 'r14b04'

   default_java_package: "oraclejdk7"
-  default_java_version: "1.7.0_21"
+  default_java_version: "1.7.0_55"
   java_packages: [
     { name: "Oracle JDK 8", package: "oraclejdk8", version: "1.8.0" },
-    { name: "Oracle JDK 7", package: "oraclejdk7", version: "1.7.0_21", default: true },
+    { name: "Oracle JDK 7", package: "oraclejdk7", version: "1.7.0_55", default: true },
     { name: "Oracle JDK 6", package: "oraclejdk6", version: "1.6.0_37" },
     { name: "Open JDK 7", package: "openjdk7" },
     { name: "Open JDK 6", package: "openjdk6" }


use u().height()

use u().height()

--- js/umobi.scroller.coffee
+++ js/umobi.scroller.coffee
@@ -121,8 +121,8 @@
         @contentOffsetY = newY

       overBottomSnapLimit: (newY) ->
-        contentHeight = @getElementHeight(@element)
-        parentHeight  = @getElementHeight(@element.parentNode)
+        contentHeight = u(@element).height()
+        parentHeight  = u(@element.parentNode).height()
         # console.log 'overBottomSnapLimit', (parentHeight - (contentHeight + newY)) , @snapBoundary
         return (parentHeight - (contentHeight + newY)) >= @snapBoundary

@@ -204,13 +204,11 @@
           # Set the element transform to where it is right now.
           @animateTo(transform.m42)

-      getElementHeight: (el) -> parseInt(window.getComputedStyle(el).height)
-
       snapToBounds: () ->
         offsetY = @getContentOffsetY()
         if @overBottomSnapLimit(offsetY)
-          contentHeight = @getElementHeight(@element)
-          parentHeight  = @getElementHeight(@element.parentNode)
+          contentHeight = u(@element).height()
+          parentHeight  = u(@element.parentNode).height()
           @cubicBezierAnimateTo(@snapDuration, (parentHeight - contentHeight) )
         else
           @cubicBezierAnimateTo(@snapDuration,0)


more projects width

more projects width

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -1493,7 +1493,7 @@
                 return
 <Loading />

         visible_projects = @visible_projects()

-            <Grid fluid className='constrained smc-vfill'>
+            <Grid fluid className='constrained smc-vfill' style={{minWidth:'90%'}}>
                 <Well className="smc-vfill" style={marginTop:'15px'}>
                     <Row>
                         <Col sm={4}>


Update app.coffee

Update app.coffee

fix bug init sblocks before layout update

--- app/templates/app/scripts/coffee/app.coffee
+++ app/templates/app/scripts/coffee/app.coffee
@@ -42,6 +42,8 @@
       #  ok: new social.OKApi preprocess.social.ok.appID, preprocess.social.ok.appKey

     start:->
+      for sblock in sblocks_components
+        common.sblocks.add sblock
       layout = {}
       layout.header   = new Layout.HeaderLayout   el: "#header-layout"
       layout.content  = new Layout.ContentLayout  el: "#content-layout"
@@ -50,6 +52,4 @@
       for key, item of layout
         item.showCurrent()
         this[key] = item
-      for sblock in sblocks_components
-        common.sblocks.add sblock
       Backbone.history.start()


fix no sharedClassroom case

fix no sharedClassroom case

--- app/views/courses/TeacherClassesView.coffee
+++ app/views/courses/TeacherClassesView.coffee
@@ -142,9 +142,10 @@
           helper.calculateDots(@classrooms, @courses, @courseInstances)
           @calculateQuestCompletion()
           @render()
-      @sharedCourseInstances = new CourseInstances()
-      @sharedCourseInstances.fetchByClassrooms(sharedClassroomIds)
-      @supermodel.trackCollection(@sharedCourseInstances)
+      if sharedClassroomIds.length
+        @sharedCourseInstances = new CourseInstances()
+        @sharedCourseInstances.fetchByClassrooms(sharedClassroomIds)
+        @supermodel.trackCollection(@sharedCourseInstances)

     window.tracker?.trackEvent 'Teachers Classes Loaded', category: 'Teachers'

@@ -264,7 +265,8 @@

   onLoaded: ->
     helper.calculateDots(@classrooms, @courses, @courseInstances)
-    helper.calculateDots(@classrooms, @courses, @sharedCourseInstances)
+    if @sharedCourseInstances
+      helper.calculateDots(@classrooms, @courses, @sharedCourseInstances)
     @calculateQuestCompletion()

     showOzariaEncouragementModal = window.localStorage.getItem('showOzariaEncouragementModal')


Use last config.userPackageDirPaths as the default target directory

--- lib/package-generator-view.coffee
+++ lib/package-generator-view.coffee
@@ -25,7 +25,7 @@
     @previouslyFocusedElement = $(':focus')
     @message.text("Enter package path")
     placeholderName = "package-name"
-    @miniEditor.setText(path.join(config.userPackagesDirPath, placeholderName))
+    @miniEditor.setText(path.join(_.last(config.userPackageDirPaths), placeholderName))
     pathLength = @miniEditor.getText().length
     @miniEditor.setSelectedBufferRange([[0, pathLength - placeholderName.length], [0, pathLength]])


Turtles run and stop

Turtles run and stop

--- src/config/turtles.coffee
+++ src/config/turtles.coffee
@@ -64,17 +64,23 @@
     turtles.map_children (child) ->
       child.call(action['do'], action) if action?
   interval: 1000
-  running: false
+  speed: 0
   run: (world, args) ->
     step = world.get_raw('step')
+    {speed} = args
+    console.log "run with args", args, speed
     assert _.isFunction(step), "step is not a function"
+    assert speed?, "run: requires speed"
+    console.log "run with speed", speed
+    world.owner('speed').put('speed', speed)
     step_and_repeat = (self) ->
-      console.log "step_and_repeat", self
-      interval = world.get('interval')
-      running = world.get('running')
-      if running
+      speed = world.get('speed')
+      console.log "called with speed", speed
+      if speed > 0
+        delay = world.get('interval') / speed
+        console.log "run with delay", delay
         step(world, args)
-        setTimeout(self, interval)
+        setTimeout((-> self(self)), delay)
     step_and_repeat(step_and_repeat)
   reset: (world, args) ->
     world.put 'program_counter', 0


add toggle shortcut

add toggle shortcut

--- keymaps/split-diff.cson
+++ keymaps/split-diff.cson
@@ -10,3 +10,4 @@
 'atom-workspace':
   'ctrl-alt-n': 'split-diff:next-diff'
   'ctrl-alt-p': 'split-diff:prev-diff'
+  'ctrl-alt-t': 'split-diff:toggle'


add optional target prop to Card component

--- components/Card.cjsx
+++ components/Card.cjsx
@@ -16,6 +16,7 @@
         link        : React.PropTypes.string
         id          : React.PropTypes.string
         title       : React.PropTypes.string
+        target      : React.PropTypes.string
         className   : React.PropTypes.oneOfType([
                 React.PropTypes.string
                 React.PropTypes.object
@@ -34,8 +35,8 @@
             className   : "Card #{ @props.className or '' } #{ variants }"
             href        : @props.link
             title       : @props.title
+            target      : if @props.link then @props.target or '_self' else null
         , @props.children

-
-module.exports = Card+module.exports = Card


update z3 pricing

update z3 pricing

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -576,7 +576,7 @@
       description_cart: "Nexus 5x pre-loaded with Pupil Mobile app, sports arm band, and USBC-USBC cable. Connects to Pupil headsets via USBC-USBC cable."
       img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
     pm_motoz2:
-      cost: 650
+      cost: 750
       title_product: 'Pupil Mobile Bundle'
       title_store: "Pupil Mobile Bundle"
       description_store: "Connect your Pupil headset to the Moto Z3 play. Record up to 4 hours of video locally on the phone, or stream video and sensor data over Wifi. The bundle comes with: Moto Z3 Play (blue), hot-swappable Moto power pack, 64gb SD card, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. (Note: Pupil headsets with 3d world camera are not compatible with Pupil Mobile)."


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -132,7 +132,10 @@
     this.toString()

 ###*
-Returns true if this string starts with the given string
+Returns true if this string starts with the given string.
+
+@name startsWith
+@methodOf
 ###
 String::startsWith = (str) ->
   @lastIndexOf(str, 0) is 0


Fix ie travis failures

Fix ie travis failures

--- src/core/selection.coffee
+++ src/core/selection.coffee
@@ -151,7 +151,7 @@
         nativeRange.setStart(startNode, startOffset)
         nativeRange.setEnd(endNode, endOffset)
         selection.addRange(nativeRange)
-        @doc.root.focus() unless this.checkFocus()
+      @doc.root.focus() if nativeRange? and !this.checkFocus()
     else
       selection.removeAllRanges()
       @doc.root.blur()


fix input not working under IME

fix input not working under IME

--- src/core/selection.coffee
+++ src/core/selection.coffee
@@ -142,6 +142,7 @@
     selection = document.getSelection()
     return unless selection
     if startNode?
+      nativeRange = this._getNativeRange()
       # Need to focus before setting or else in IE9/10 later focus will cause a set on 0th index on line div
       # to be set at 1st index
       @doc.root.focus() unless this.checkFocus()


markdown

markdown

--- resource/translators/unicode_translator.coffee
+++ resource/translators/unicode_translator.coffee
@@ -99,7 +99,7 @@
         @latex += '\\textsc{' if tag.smallcaps

       when 'td', 'th'
-        @html += ' '
+        @latex += ' '

       when 'tbody' then # ignore

@@ -128,7 +128,7 @@
         @latex += '}' if @stack[0].smallcaps || @stack[0].enquote

       when 'td', 'th'
-        @html += ' '
+        @latex += ' '

       when 'ol'
         @latex += "\n\n\\end{enumerate}\n"
@@ -156,3 +156,103 @@
           @latex += "\\ensuremath{#{block.text}}"
       else
         @latex += block.text
+
+MarkDown = {}
+class MarkDown.HTML
+  constructor: (html) ->
+    @md = ''
+    @stack = []
+    HTMLtoDOM.Parser(html, @)
+
+  start: (tag, attrs, unary) ->
+    tag = {name: tag.toLowerCase(), attrs: {}}
+    for attr in attrs
+      tag.attrs[attr.name.toLowerCase()] = attr.value
+    @stack.unshift(tag) unless unary
+
+    switch tag.name
+      when 'i', 'em', 'italic'
+        @md += '_'
+
+      when 'b', 'strong'
+        @md += '**'
+
+      when 'a'
+        @md += '[' if tag.attrs.href?.length > 0
+
+      when 'sup'
+        @md += '<sup>'
+      when 'sub'
+        @md += '<sub>'
+
+      when 'br'
+        @md += "  \n"
+
+      when 'p', 'div', 'table', 'tr'
+        @md += "\n\n"
+
+      when 'h1', 'h2', 'h3', 'h4'
+        @md += "\n\n\\#{(new Array(parseInt(tag.name[1]))).join('#')} "
+
+      when 'ol', 'ul'
+        @md += "\n\n"
+
+      when 'li'
+        switch @stack[1]?.name
+          when 'ol'
+            @md += "\n1. "
+          when 'ul'
+            @md += "\n* "
+
+      when 'span', 'sc' then # ignore
+
+      when 'td', 'th'
+        @md += ' '
+
+      when 'tbody' then # ignore
+
+      else
+        Translator.debug("unexpected tag '#{tag.name}'")
+
+  end: (tag) ->
+    tag = tag.toLowerCase()
+
+    throw new Error("Unexpected close tag #{tag}") unless tag == @stack[0]?.name
+
+    switch tag
+      when 'i', 'italic', 'em'
+        @md += '_'
+
+      when 'sup', 'sub'
+        @md += "</#{tag}>"
+
+      when 'b', 'strong'
+        @md += '**'
+
+      when 'a'
+        @md += "](#{@stack[0].attrs.href})" if @stack[0].attrs.href?.length > 0
+
+      when 'h1', 'h2', 'h3', 'h4' then #ignore
+
+      when 'p', 'div', 'table', 'tr'
+        @md += "\n\n"
+
+      when 'span', 'sc' then # ignore
+
+      when 'td', 'th'
+        @md += ' '
+
+      when 'ol', 'ul'
+        @md += "\n\n"
+
+    @stack.shift()
+
+  cdata: (text) ->
+    @md += text
+
+  chars: (text) ->
+    txt = LaTeX.he.decode(text)
+
+    txt = txt.replace(/([-"\\`\*_{}\[\]\(\)#\+!])/g, "\\$1")
+    txt = txt.replace(/(^|[\n])(\s*[0-9]+)\.(\s)/g, "$1\\.$2")
+    @md += text


Client: added error code into doXhrRequest error cases

--- client/app/lib/util/doXhrRequest.coffee
+++ client/app/lib/util/doXhrRequest.coffee
@@ -10,20 +10,37 @@
   xhr.open type, endPoint, async
   xhr.setRequestHeader "Content-Type", "application/json;"
   xhr.onload = (result) =>
+    return if xhr.readyState isnt 4
+
+    # 0 - connection failed
+    if xhr.status is 0 or xhr.status >= 500
+      return callback {
+        message : "internal server error"
+        code    : xhr.status
+      }
+
     try
       response = JSON.parse xhr.responseText
     catch e
-      return callback { message : "invalid json: could not parse response", code: xhr.status }
+      return callback {
+        message : "invalid json: could not parse response"
+        code    : xhr.status
+      }

-    # 0     - connection failed
+
     # >=400 - http errors
-    if xhr.status is 0 or xhr.status >= 400
-      return callback { message: response.description}
+    if xhr.status >= 400
+      return callback {
+        message : response.description
+        code    : xhr.status
+      }

-    return if xhr.readyState isnt 4

     if xhr.status not in [200, 304]
-      return callback { message: response.description}
+      return callback {
+        message : response.description
+        code    : xhr.status
+      }

     return callback null, response


added null check on log entry files in the client

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -70,16 +70,17 @@

 					$scope.pdf.logEntryAnnotations = {}
 					for entry in logEntries.all
-						entry.file = normalizeFilePath(entry.file)
-
-						entity = ide.fileTreeManager.findEntityByPath(entry.file)
-						if entity?
-							$scope.pdf.logEntryAnnotations[entity.id] ||= []
-							$scope.pdf.logEntryAnnotations[entity.id].push {
-								row: entry.line - 1
-								type: if entry.level == "error" then "error" else "warning"
-								text: entry.message
-							}
+						if entry.file?
+							entry.file = normalizeFilePath(entry.file)
+
+							entity = ide.fileTreeManager.findEntityByPath(entry.file)
+							if entity?
+								$scope.pdf.logEntryAnnotations[entity.id] ||= []
+								$scope.pdf.logEntryAnnotations[entity.id].push {
+									row: entry.line - 1
+									type: if entry.level == "error" then "error" else "warning"
+									text: entry.message
+								}

 				.error () ->
 					$scope.pdf.logEntries = []


Revert "Open permissions"

Revert "Open permissions"

This reverts commit 94e91f4fa2cb671b673b08129a080502e0469afb.

--- collections/needs.coffee
+++ collections/needs.coffee
@@ -1,6 +1,6 @@
 @Needs = new Meteor.Collection("needs")

 @Needs.allow
-  insert: (userId, doc) -> true
-  update: (userId, doc) -> true
-  remove: (userId, doc) -> true
+  insert: (userId, doc) -> userId
+  update: (userId, doc) -> userId
+  remove: (userId, doc) -> userId


Don't create ghosts when not transitioning

--- lib/assets/javascripts/up/motion.js.coffee
+++ lib/assets/javascripts/up/motion.js.coffee
@@ -178,8 +178,10 @@
       $new = $(target)
       finish($old)
       finish($new)
-      transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]
-      if transition
+      if transitionOrName == 'none'
+        # don't create ghosts if we aren't really transitioning
+        none()
+      else if transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]
         withGhosts $old, $new, ($oldGhost, $newGhost) ->
           assertIsDeferred(transition($oldGhost, $newGhost, options), transitionOrName)
       else if animation = animations[transitionOrName]


redundant dep

redundant dep

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -103,7 +103,6 @@
     @bower "platform", "platform.js"
     @bower "async", "lib/async.js"
     @bower "eventemitter2"
-    @bower "bootstrap#3"
     @bower "loglevel"
     @bower "lodash"
     @bower "chai"