Account for headers

Account for headers

--- framer/Components/FlowComponent.coffee
+++ framer/Components/FlowComponent.coffee
@@ -94,6 +94,7 @@
 			return unless layer instanceof Layer
 			@_header = layer
 			@_header.name = "header"
+			@_header.width = @width
 			@_header.setParentPreservingConstraintValues(@)
 			if not @_header.constraintValues?
 				@_header.x = Align.center
@@ -106,6 +107,7 @@
 			return unless layer instanceof Layer
 			@_footer = layer
 			@_footer.name = "footer"
+			@_footer.width = @width
 			@_footer.setParentPreservingConstraintValues(@)
 			if not @_footer.constraintValues?
 				@_footer.x = Align.center
@@ -158,7 +160,7 @@
 		wrappedLayer.parent = @
 		wrappedLayer.visible = not options.animate

-		layerA = @_wrappedLayer(@current)
+		layerA = @current
 		layerB = wrappedLayer
 		overlay = @overlay

@@ -171,6 +173,7 @@

 		# Run the transition and update the history
 		@_runTransition(transition, "forward", options.animate, @current, layer)
+
 		@_stack.push({layer: layer, transition: transition})

@@ -184,7 +187,6 @@

 		# Maybe people (Jorn) pass in a layer accidentally
 		options = {} if options instanceof(Framer._Layer)
-
 		options = _.defaults({}, options, {count: 1, animate: true})

 		if options.count > 1
@@ -230,8 +232,9 @@
 		flowLayer.width = Math.max(flowLayer.width, @width)
 		flowLayer.height = Math.max(flowLayer.height, @height)

-		layer = layoutPage(flowLayer, @size)
-		layer = layoutScroll(layer, @size)
+		size = @size
+		layer = layoutPage(flowLayer, size)
+		layer = layoutScroll(layer, size)

 		# Mark the layer so we don't layout it twice'
 		layer._flowLayer = flowLayer
@@ -243,6 +246,9 @@
 		# Set the background color for he created scroll component
 		if layer instanceof ScrollComponent
 			scroll.backgroundColor = @backgroundColor
+			scroll.contentInset =
+				top: @header?.height or 0
+				bottom: @footer?.height or 0

 		return layer

@@ -263,12 +269,6 @@

 		scroll._flowForward = true

-	_wrappedLayer: (layer) ->
-		# Get the ScrollComponent for a layer if it was wrapped,
-		# or just the layer itself if it was not.
-		return null unless layer
-		return layer[FlowComponentLayerScrollKey] or layer
-
 	_runTransition: (transition, direction, animate, from, to) =>

 		if direction is "forward"
@@ -391,6 +391,15 @@
 # Layout helpers

 detectHeaderFooter = (layer) ->
+
+	# This function tries to detect a stack of:
+	# 1) header / body / footer
+	# 2) header / body
+	# 3) body / footer
+
+	# A valid layout is when:
+	# - The child layers cover the entire parent edge to edge.
+	# - The child layers are vertically stacked, edge to edge.

 	result =
 		header: null


history back and forth

history back and forth

--- app/js/controllers/sidenav.coffee
+++ app/js/controllers/sidenav.coffee
@@ -1,9 +1,9 @@
-angular.module("app").controller "SidenavController", ($scope, $location, $state) ->
+angular.module("app").controller "SidenavController", ($scope, $rootScope) ->
   $scope.back = ->
-    $scope.history_back()
+    $rootScope.history_back()

   $scope.forward = ->
-    $scope.history_forward()
+    $rootScope.history_forward()

   $scope.close = ->
-    # close sidenav+    console.log 'to be implemented'


Made sure items equip themselves within the SpriteBoss, too.

--- app/lib/surface/SpriteBoss.coffee
+++ app/lib/surface/SpriteBoss.coffee
@@ -181,6 +181,7 @@
     # Add anything new, remove anything old, update everything current
     updateCache = false
     for thang in @world.thangs when thang.exists
+      thang.equip() if thang.equip and not thang.equipped  # Pretty hacky, since initialize may not be called
       if sprite = @sprites[thang.id]
         sprite.setThang thang  # make sure Sprite has latest Thang
       else


update site intro text for #62

update site intro text for #62

--- app/lib/slide-tutorial-slides.coffee
+++ app/lib/slide-tutorial-slides.coffee
@@ -7,7 +7,7 @@
   {
     image: './images/site-intro/stronger-storm.gif',
     title: 'Pick the stronger storm',
-    content: 'You’ll be given two radar images and asked to select the stronger storm. After that, you’ll be asked additional questions about the image on the right. Please note that the image you are asked to classify may not always be the stronger storm.'
+    content: 'You’ll be given two radar images and asked to select the stronger storm. After that, you’ll be asked additional questions about the image on the right. Please note that this may not always be the stronger storm.'
   },
   {
     image: 'http://placehold.it/400X200&text=slide%203',


Remove apangea cruft

Remove apangea cruft

--- src/javascripts/calculator.coffee
+++ src/javascripts/calculator.coffee
@@ -7,16 +7,7 @@
 math_buttons_lib = ttm.widgets.ButtonBuilder
 components = ttm.lib.math.ExpressionComponentSource.build()

-
 calculator_wrapper_class = 'javascript-calculator'
-
-
-open_widget_dialog = (element)->
-  if element.empty()
-    Calculator.build_widget(element)
-  element.dialog(dialogClass: "calculator-dialog", title: "Calculator")
-  element.dialog("open")
-  element.dialog({ position: { my: 'right center', at: 'right center', of: window}})

 class Calculator
   @build_widget: (element, buttonsToRender=null)->
@@ -186,7 +177,4 @@

 class_mixer(CalculatorView)

-
-Calculator.openWidgetDialog = open_widget_dialog
-
 ttm.widgets.Calculator = Calculator


Fix typo, add missing link on lab policies (#3343)

* fixed a typo and added in a missing link

There's one 'contact us' that didn't have a link to the contact page, so I added it in. Also fixed a typo; --> :

* fixed weird text removal I accidentally added

fixed weird text removal I accidentally added

* removed extra space

removed extra space

* removed extra space I added in original pull request

removed extra space I added in original pull request

--- app/pages/lab/help/lab-policies.cjsx
+++ app/pages/lab/help/lab-policies.cjsx
@@ -10,7 +10,7 @@

       ### How to Launch your Project
-      A live project can exist in three different states, it may be a;
+      A live project can exist in three different states, it may be a:
       1. Public Project
       2. Review Project
       3. Zooniverse Project
@@ -50,7 +50,7 @@
       - The content is likely to cause offense, or is suitable only for an adult audience.
       - The copyright on material uploaded to the site is not clear; please only use content you have the right to use.

-      We reserve the right to decide which projects appear on the main project page and which are promoted to the Zooniverse community. In particular, if you have a project that is very close to an existing Zooniverse project, please contact us to discuss.
+      We reserve the right to decide which projects appear on the main project page and which are promoted to the Zooniverse community. In particular, if you have a project that is very close to an existing Zooniverse project, please [contact us](/about/contact) to discuss.

       Projects promoted to the Zooniverse community **must**:


Improve curve length precision

Improve curve length precision

--- app/scripts/lib/BezierPath.coffee
+++ app/scripts/lib/BezierPath.coffee
@@ -24,16 +24,27 @@
     # Draw-a-Smooth-Curve-through-a-Set-of-2D-Points-wit
     [firstControlPoints, secondControlPoints] = @getCurveControlPoints(points)

+    # Increase this for a preciser, but slower calculation of curve length
+    samplePoints = 3
+
     for i in [0...points.length - 1]
       a = points[i]
       b = points[i + 1]
       c1 = firstControlPoints[i]
       c2 = secondControlPoints[i]

-      dx = Math.abs(a.x - b.x)
-      dy = Math.abs(a.y - b.y)
-      curveDistance = Math.sqrt((dx ** 2) + (dy ** 2))
       curvePoints = [b, c2, c1, a]
+
+      curveDistance = 0
+      pc = jsBezier.pointOnCurve(curvePoints, 0)
+      step = 1 / samplePoints
+      for i in [step..1] by step
+        pn = jsBezier.pointOnCurve(curvePoints, i)
+        dx = Math.abs(pc.x - pn.x)
+        dy = Math.abs(pc.y - pn.y)
+        curveDistance += Math.sqrt((dx ** 2) + (dy ** 2))
+        pc = pn
+
       result.distance += curveDistance
       result.curves.push {
         distance: curveDistance


bugfix when creating new `CierreCaja`

bugfix when creating new `CierreCaja`

only `deleteRecord` if it hasn’t been persisted yet

--- app/assets/javascripts/routes/cierres_caja/cierres_caja_new_route.js.coffee
+++ app/assets/javascripts/routes/cierres_caja/cierres_caja_new_route.js.coffee
@@ -26,4 +26,5 @@
     willTransition: (transition) ->
       model = @get("controller.model")
       model.rollbackAssociations()
-      model.deleteRecord()
+      if model.get("isNew")
+        model.deleteRecord()


updated load-stuff to take an array from application.appNpmTasks for extra task loading.

--- tasks/load-stuff.coffee
+++ tasks/load-stuff.coffee
@@ -1,7 +1,8 @@
 fs = require('fs')

 module.exports = (grunt) ->
-  grunt.util._([
+  appNpmTasks = require(process.cwd() + "/config/application").appNpmTasks ? []
+  linemanNpmTasks = [
     "grunt-contrib-clean",
     "grunt-contrib-coffee",
     "grunt-contrib-concat",
@@ -13,9 +14,14 @@
     "grunt-contrib-cssmin",
     "grunt-contrib-uglify",
     "grunt-watch-nospawn"
-  ]).each (module) ->
-    if fs.existsSync("node_modules/#{module}")
-      grunt.loadNpmTasks(module)
-    else
-      grunt.loadTasks(__dirname+"/../node_modules/#{module}/tasks")
+  ]

+  grunt.util._
+    .chain(linemanNpmTasks)
+    .union(appNpmTasks)
+    .each (module) ->
+      if fs.existsSync("node_modules/#{module}")
+        grunt.loadNpmTasks(module)
+      else
+        grunt.loadTasks(__dirname+"/../node_modules/#{module}/tasks")
+


Clear the releases dir for now

Clear the releases dir for now

--- build/tasks/create-installer.coffee
+++ build/tasks/create-installer.coffee
@@ -1,6 +1,7 @@
 fs = require 'fs'
 path = require 'path'
 _ = require 'underscore-plus'
+rimraf = require 'rimraf'

 module.exports = (grunt) ->
   {spawn} = require('./task-helpers')(grunt)
@@ -33,7 +34,12 @@

       pkgs = pkg for pkg in fs.readdirSync(buildDir) when pkg.match /.nupkg$/i

+      releasesDir = path.join(buildDir, 'Releases')
+
+      ## NB: Gonna clear Releases for now, in the future we need to pull down
+      ## the existing version
+      rimraf.sync(releasesDir)
+
       cmd = 'build/windows/update.com'
-      args = ['--releasify', path.join(buildDir, pkgs), '-r', path.join(buildDir, 'Releases')]
-
+      args = ['--releasify', path.join(buildDir, pkgs), '-r', releasesDir]
       spawn {cmd, args}, (error, result, code) -> done(error)


fix for already configured PhotoBanners

fix for already configured PhotoBanners

--- @client/banner.coffee
+++ @client/banner.coffee
@@ -58,9 +58,8 @@
     edit_banner = fetch 'edit_banner'
     is_admin = fetch('/current_user').is_admin

-    description = fetch("forum-description").html or customization('banner')?.description
-
     has_description = description?.trim().length > 0 && description.trim() != '

'
+
     is_light = is_light_background()

     focus_on_mount = @local.focus_on_mount
@@ -93,7 +92,7 @@
             style:
               fontSize: 18
               padding: '6px 8px'
-            dangerouslySetInnerHTML: __html: description
+            dangerouslySetInnerHTML: __html: fetch("forum-description").html or customization('banner')?.description
             onDoubleClick: if is_admin then =>
               edit_banner.editing = true
               @local.focus_on_mount = true
@@ -102,8 +101,8 @@
                 @refs.primary_input?.getDOMNode().focus()
                 @refs.primary_input?.getDOMNode().setSelectionRange(-1, -1) # put cursor at end

-        else if @props.supporting_text
-          @props.supporting_text()
+        else if @props.opts.supporting_text
+          @props.opts.supporting_text()

 UploadFileSVG = (opts) ->
@@ -933,8 +932,8 @@
       parseInt(value).toString(16)

-  description = fetch("forum-description").html or customization('banner')?.description
-  has_description = description?.trim().length > 0 && description.trim() != '

'
+  description = fetch("forum-description").html or customization('banner')?.description or opts.supporting_text
+  has_description = opts.supporting_text || (description?.trim().length > 0 && description.trim() != '

')

   is_dark_theme = !is_light_background()
   text_block_color = edit_banner.text_background_css or customization('banner')?.text_background_css or DEFAULT_TEXT_BLOCK_COLOR
@@ -991,6 +990,7 @@

         CustomizeDescription
           key: 'editable_description'
+          opts: opts
           style:
             border: if !has_description then (if is_dark_theme then '1px solid rgba(255,255,255,.5)' else '1px solid rgba(0,0,0,.5)')
             padding: "6px 8px"


proofed the SearchView's runSearch

proofed the SearchView's runSearch

--- app/views/kinds/SearchView.coffee
+++ app/views/kinds/SearchView.coffee
@@ -46,6 +46,7 @@
     searchInput.focus()

   runSearch: =>
+    return if @destroyed
     term = @$el.find('input#search').val()
     return if @sameSearch(term)
     @removeOldSearch()


Allow embedded help videos to go fullscreen

--- app/views/play/menu/GuideView.coffee
+++ app/views/play/menu/GuideView.coffee
@@ -122,6 +122,7 @@
     tag.height = @helpVideoHeight
     tag.width = @helpVideoWidth
     tag.frameborder = '0'
+    tag.allowFullscreen = true
     @$el.find('#help-video-player').replaceWith(tag)

     @onMessageReceived = (e) =>


Update loader

Update loader

--- src/moka/applicationLoader.coffee
+++ src/moka/applicationLoader.coffee
@@ -35,15 +35,17 @@
   else
     callback()

-domDidLoad = ->
-  ready = 0
-  if doc.attachEvent and doc.readyState is "complete"
+unlistenDOMLoaded = ->
+  if doc.addEventListener
+    doc.removeEventListener DOMContentLoadedString, domDidLoad, false
+    _window.removeEventListener "load", domDidLoad, false
+  else
     doc.detachEvent onreadystatechangeString, domDidLoad
-    ready = 1
-  else if doc.addEventListener
-    doc.removeEventListener DOMContentLoadedString, domDidLoad, false
-    ready = 1
-  if ready and not domLoaded_
+    _window.detachEvent "onload", domDidLoad
+
+domDidLoad = (_event) ->
+  if doc.addEventListener or _event.type is "load" or doc.readyState is "complete"
+    unlistenDOMLoaded()
     domLoaded_ = true
     loadApplication() if shouldLoadApplication() or forceAppLoading_

@@ -68,9 +70,12 @@
       return
     domDidLoad()

-if not window.console
+###
+TO REMOVE PLEASE
+###
+if not _window.console
   f = ->
-  window.console =
+  _window.console =
     log: f
     warn: f
     error: f
@@ -82,17 +87,17 @@
   return  unless appLoadingStatus_ is AppNotLoaded
   appLoadingStatus_ = LoadingApp
   loadScript applicationURL(applicationOptions), ->
-    window[privateNamespace]["init"] applicationOptions, ->
+    _window[privateNamespace]["init"] applicationOptions, ->
       appLoadingStatus_ = AppLoaded
       notifyObservers readyObservers_
       applicationDidLoad()
   `undefined`

-api = window[namespace]
+api = _window[namespace]
 if api and api['ready'] and api['load']
   console.error 'SublimeVideo loader has been installed more than once.'
 else
-  api = window[namespace] = {}
+  api = _window[namespace] = {}
   api["ready"] = (observer) ->
     if appLoadingStatus_ is AppLoaded
       observer()
@@ -112,5 +117,8 @@
     # document is already laoded, fire callback
     domDidLoad()
   else
-    bindReady()
+    if _window.jQuery
+      _window.jQuery(doc).ready(domDidLoad)
+    else
+      bindReady()


Clean up Poller

Clean up Poller

--- assets/javascripts/models.js.coffee
+++ assets/javascripts/models.js.coffee
@@ -51,7 +51,7 @@
     @started = true
     @messages = []
     @beforeMessageId = null
-    @timer = setInterval @onPoll.bind(@), 3000
+    @timer = setInterval(@fetchMessages.bind(@), 3000)

   setRoom: (room)->
     @room = room
@@ -62,36 +62,41 @@
     clearInterval(@timer)
     @started = false

-  onPoll: ->
+
+  onEachMessage: (index, message)->
+    return true if (@store.recordIsLoaded("message", message.id))
+    messageParams =
+      id: message.id
+      type: message.type
+      body: message.body
+      createdAt: message.created_at
+
+    messageObj = @store.createRecord("message", messageParams)
+    messageObj.set("room", @room)
+    @afterMessageId = message.id
+
+    if(@store.recordIsLoaded("user", message.user.id))
+      @store.find("user", message.user.id).then (user)=>
+        messageObj.set("user", user)
+        #TODO push or shift depending on the query
+        @room.get("messages").pushObject(messageObj)
+    else
+      userParams =
+        id: message.user.id
+        firstName: message.user.first_name
+        lastName: message.user.last_name
+        role: message.user.role
+      user = @store.createRecord("user", userParams)
+      messageObj.set("user", user)
+      #TODO push or shift depending on the query
+      @room.get("messages").pushObject(messageObj)
+
+
+  fetchMessages: ->
     if @afterMessageId
       url = "/api/messages/#{@roomId}?after=#{@afterMessageId}"
     else
       url = "/api/messages/#{@roomId}"

     $.getJSON url, (response)=>
-      Em.$.each response.messages, (index, message)=>
-        if (! @store.recordIsLoaded(App.Message, message.id))
-          messageParams =
-            id: message.id
-            type: message.type
-            body: message.body
-            createdAt: message.created_at
-          messageObj = @store.createRecord("message", messageParams)
-          messageObj.set("room", @room)
-          @afterMessageId = message.id
-
-          if(@store.recordIsLoaded("user", message.user.id))
-            @store.find("user", message.user.id).then (user)=>
-              messageObj.set("user", user)
-              #TODO push or shift depending on the query
-              @room.get("messages").pushObject(messageObj)
-          else
-            userParams =
-              id: message.user.id
-              firstName: message.user.first_name
-              lastName: message.user.last_name
-              role: message.user.role
-            user = @store.createRecord("user", userParams)
-            messageObj.set("user", user)
-            #TODO push or shift depending on the query
-            @room.get("messages").pushObject(messageObj)+      Em.$.each response.messages, @onEachMessage.bind(@)


Fix typo

Fix typo

--- assets/javascripts/models.js.coffee
+++ assets/javascripts/models.js.coffee
@@ -25,7 +25,7 @@
   attrs: {
     room: {embedded: "always"}
   }
-
+})

 App.Message = DS.Model.extend
   body:   DS.attr("string")


Update Angular in New menu. Add 1.5.x

Update Angular in New menu. Add 1.5.x

--- assets/js/directives/toolbar.coffee
+++ assets/js/directives/toolbar.coffee
@@ -65,7 +65,7 @@

jQuery UI

-            AngularJS
+            Angular

1.0.x

1.0.x + Jasmine

@@ -77,6 +77,8 @@

1.3.x

1.4.x

+

+
1.5.x

2.0.x (ES6)

2.0.x (ES5)


Failing later

Failing later

--- src/reactive.coffee
+++ src/reactive.coffee
@@ -864,9 +864,9 @@
     setSVGProp = (elt, name, value) ->
     	if value instanceof ObsCell
     		do (name) ->
-    			value.onSet.sub ([old, val]) -> elt.setAttribute name, val)
+    			value.onSet.sub ([old, val]) -> elt.setAttribute name, value
     	else
-    		elt.setAttribute name, val)
+    		elt.setAttribute name, value

     rxt.svg_mktag = mktag = (tag) ->
       (arg1, arg2) ->


Workspace: Refactored drawing pane. Separated spaghetti logic to methods. Fixed size of canvas.

--- client/landing/CollaborativeWorkspace/panes/collaborativedrawingpane.coffee
+++ client/landing/CollaborativeWorkspace/panes/collaborativedrawingpane.coffee
@@ -1,6 +1,8 @@
 class CollaborativeDrawingPane extends CollaborativePane

   constructor: (options = {}, data) ->
+
+    options.cssClass = KD.utils.curry "ws-drawing-pane", options.cssClass

     super options, data

@@ -8,21 +10,52 @@
     @linesRef    = @workspaceRef.child "lines"
     @stateRef    = @workspaceRef.child "state"
     @usersRef    = @workspaceRef.child "users"
+    @drawedQueue = []
+    @userColors  = {}

+    @container.on "viewAppended", =>
+      @createCanvas()
+      @setUserColor()
+
+      @bindMouseDownOnCanvas()
+      @bindMouseUpOnCanvas()
+      @bindMouseMoveCanvas()
+
+      @workspaceRef.onDisconnect().remove()  if @amIHost
+      @redrawCanvas()  if @isJoinedASession
+
+      @bindRemoteEvents()
+      @container.addSubView @canvas
+
+  createCanvas: ->
     @canvas      = new KDCustomHTMLView
       tagName    : "canvas"
-      domId      : "paintCanvas"
       bind       : "mousemove mousedown mouseup"
       attributes :
-        width    : 640
-        height   : 480
-        style    : "border: 2px solid #333;"
+        width    : @getWidth()
+        height   : @getHeight()

     @context     = @canvas.getElement().getContext "2d"
-    @drawedQueue = []
-    @userColors  = {}
-    @setUserColor()

+  redrawCanvas: ->
+    @context.closePath()
+    @linesRef.once "value", (snapshot) =>
+      value = snapshot.val()
+      return unless value
+      @context.beginPath()
+      for own key, points of value
+        pointsArr = points.split "|"
+        [username, color]     = pointsArr.splice 0, 2
+        @userColors[username] = color
+
+        @context.closePath()
+        @context.beginPath()
+        for point, index in pointsArr
+          [x, y] = point.split ","
+          @context.moveTo x, y  if index is 0
+          @addPoint x, y, username
+
+  bindMouseDownOnCanvas: ->
     @canvas.on "mousedown", (e) =>
       KD.utils.stopDOMEvent e
       x             = e.offsetX
@@ -37,15 +70,7 @@
       @pointRef.set { x, y, nickname: KD.nick() }
       @stateRef.set yes

-    @canvas.on "mouseup", (e) =>
-      @context.closePath()
-      @startDrawing = no
-      @stateRef.set   no
-
-      @drawedQueue.unshift KD.nick(), @userColors[KD.nick()]
-      @linesRef.push @drawedQueue.join "|"
-      @drawedQueue.length = 0
-
+  bindMouseMoveCanvas: ->
     @canvas.on "mousemove", (e) =>
       if @startDrawing
         x = e.offsetX
@@ -56,28 +81,17 @@

         @pointRef.set { x, y, nickname: KD.nick() }

-    @container.addSubView @canvas
+  bindMouseUpOnCanvas: ->
+    @canvas.on "mouseup", (e) =>
+      @context.closePath()
+      @startDrawing = no
+      @stateRef.set   no

-    @workspaceRef.onDisconnect().remove()  if @amIHost
+      @drawedQueue.unshift KD.nick(), @userColors[KD.nick()]
+      @linesRef.push @drawedQueue.join "|"
+      @drawedQueue.length = 0

-    if @isJoinedASession
-      @context.closePath()
-      @linesRef.once "value", (snapshot) =>
-        value = snapshot.val()
-        return unless value
-        @context.beginPath()
-        for own key, points of value
-          pointsArr = points.split "|"
-          [username, color]     = pointsArr.splice 0, 2
-          @userColors[username] = color
-
-          @context.closePath()
-          @context.beginPath()
-          for point, index in pointsArr
-            [x, y] = point.split ","
-            @context.moveTo x, y  if index is 0
-            @addPoint x, y, username
-
+  bindRemoteEvents: ->
     @isContextMoved = yes

     @pointRef.on "value", (snapshot) =>
@@ -100,7 +114,6 @@
       for own key, userData of value
         @userColors[userData.nickname] = userData.color

-
   addPoint: (x, y, nickname = KD.nick()) ->
     ctx             = @context
     ctx.strokeStyle = @userColors[nickname]


Wait until appIsReady

Wait until appIsReady

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -38,14 +38,15 @@

     @currentFilter     = activityTypes
     @appStorage        = new AppStorage 'Activity', '1.0'
-    activityController = @getSingleton('activityController')
-    activityController.on "ActivityListControllerReady", @attachEvents.bind @
+
+    @mainController = @getSingleton 'mainController'
+    @mainController.on 'AppIsReady', =>
+      activityController = @getSingleton('activityController')
+      activityController.on   "ActivityListControllerReady", @attachEvents.bind @
+      activityController.once "ActivityListControllerReady", @bound "populateActivity"

   loadView:->
-    if @listController then @populateActivity()
-    else
-      ac = @getSingleton('activityController')
-      ac.once "ActivityListControllerReady", @bound "populateActivity"
+    @populateActivity() if @listController

   resetList:->


permission denied error shown if cannot open dir because of permission error

--- client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -134,8 +134,13 @@

     failCallback = (err)=>
       unless silence
-        KD.logToExternal "Couldn't fetch files"
-        @notify "Couldn't fetch files! Click to retry", 'clickable', \
+        if err?.message?.match /permission denied/i
+          message = "Permission denied!"
+          KD.logToExternal "Couldn't fetch files, permission denied"
+        else
+          message = "Couldn't fetch files! Click to retry"
+          KD.logToExternal "Couldn't fetch files"
+        @notify message, 'clickable', \
                 """Sorry, a problem occured while communicating with servers,
                    please try again later.""", yes
         @once 'fs.retry.scheduled', => @expandFolder nodeView, callback


removed the spec

removed the spec

--- spec/lib/router-spec.coffee
+++ spec/lib/router-spec.coffee
@@ -218,14 +218,3 @@
       Given -> @name = 'test'
       When -> @res = @router.fns @name
       Then -> expect(@res).toEqual []
-
-    describe '#fns (name:String="tester")', ->
-
-      Given -> @a = jasmine.createSpy 'a'
-      Given -> @b = jasmine.createSpy 'b'
-      Given -> @router.use 'test*', @a
-      Given -> @router.use 'tes*', @b
-      Given -> @name = 'tester'
-      When -> @res = @router.fns @name
-      Then -> expect(@res).toEqual [@a, @b]
-


Export jQuery directly from vendor/

Export jQuery directly from vendor/

--- exports/jquery.coffee
+++ exports/jquery.coffee
@@ -1 +1 @@
-module.exports = require('./atom-api').$
+module.exports = require '../vendor/jquery'


Extend test/activation

Extend test/activation

sgda-mode should be activated after triggering "agda-mode:input-symbol"

--- spec/test/activation.coffee
+++ spec/test/activation.coffee
@@ -60,6 +60,8 @@

     describe 'activating agda-mode', ->

+        textEditor_ = null
+
         beforeEach ->
             atom.packages.deactivatePackage('agda-mode')
             atom.packages.activatePackage('agda-mode')
@@ -70,9 +72,11 @@
                 .then (textEditor) ->
                     element = atom.views.getView(textEditor)
                     atom.commands.dispatch(element, 'agda-mode:load')
+                    textEditor_ = textEditor
                     return atom.packages.activatePackage('agda-mode')
                 .then ->
                     getActivePackageNames().should.contain 'agda-mode'
+                    textEditor_.core.should.be.defined
                     done()

         it 'should be activated after triggering "agda-mode:load" in .lagda files', (done) ->
@@ -80,31 +84,33 @@
                 .then (textEditor) ->
                     element = atom.views.getView(textEditor)
                     atom.commands.dispatch(element, 'agda-mode:load')
+                    textEditor_ = textEditor
                     return atom.packages.activatePackage('agda-mode')
                 .then ->
                     getActivePackageNames().should.contain 'agda-mode'
+                    textEditor_.core.should.be.defined
                     done()

-        it 'should have property `core` after triggering "agda-mode:load" in .agda files', (done) ->
-            textEditor_ = null
+        it 'should be activated after triggering "agda-mode:input-symbol" in .agda files', (done) ->
             openFile agdaFD
                 .then (textEditor) ->
                     element = atom.views.getView(textEditor)
-                    atom.commands.dispatch(element, 'agda-mode:load')
+                    atom.commands.dispatch(element, 'agda-mode:input-symbol')
                     textEditor_ = textEditor
                     return atom.packages.activatePackage('agda-mode')
                 .then ->
+                    getActivePackageNames().should.contain 'agda-mode'
                     textEditor_.core.should.be.defined
                     done()

-        it 'should have property `core` after triggering "agda-mode:load" in .lagda files', (done) ->
-            textEditor_ = null
+        it 'should be activated after triggering "agda-mode:input-symbol" in .lagda files', (done) ->
             openFile lagdaFD
                 .then (textEditor) ->
                     element = atom.views.getView(textEditor)
-                    atom.commands.dispatch(element, 'agda-mode:load')
+                    atom.commands.dispatch(element, 'agda-mode:input-symbol')
                     textEditor_ = textEditor
                     return atom.packages.activatePackage('agda-mode')
                 .then ->
+                    getActivePackageNames().should.contain 'agda-mode'
                     textEditor_.core.should.be.defined
                     done()


Fixed demo.

Fixed demo.

--- demo/server/demo.coffee
+++ demo/server/demo.coffee
@@ -7,6 +7,9 @@
   Meteor.setInterval ->
     for demo in ['demo5', 'demo6']
       Values.upsert demo,
+        # Because we are setting the whole new document, we should include
+        # _id as well. See https://github.com/meteor/meteor/issues/4060
+        _id: demo
         # Remove the sentence dot and convert to lower case.
         value: dimsum.sentence(1).slice(0, -1).toLowerCase()


fix ipad zooming on editor

fix ipad zooming on editor

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -11,10 +11,11 @@
       'line-height': '15px'
       'min-height': '100%'
       'outline': 'none'
+      'padding': '10px 15px'
       'tab-size': '4'
       'white-space': 'pre-wrap'
     }
-    'body' : { 'margin': '0px', 'padding': '10px 15px' }
+    'body' : { 'margin': '0px', 'padding': '0px' }
     'a'    : { 'cursor': 'pointer', 'text-decoration': 'underline' }
     'b'    : { 'font-weight': 'bold' }
     'i'    : { 'font-style': 'italic' }


remove `Response#http` deprecation

remove `Response#http` deprecation

--- src/response.coffee
+++ src/response.coffee
@@ -78,7 +78,6 @@
   #
   # Returns a ScopedClient instance.
   http: (url) ->
-    @robot.logger.warning '@http() is going to be deprecated in 3.0.0 use @robot.http()'
     @robot.http(url)

 module.exports = Response


Add timestamp when new peripheral is discovered. Add log lines for debug purpose

--- src/roadtrip.coffee
+++ src/roadtrip.coffee
@@ -41,12 +41,19 @@
       {timeout, frequency, base_url} = config.sync
       uri = "#{base_url}/beacons"

+      console.log "## TRIED TO SYNC BUT OFFLINE ##" unless @online
       if @online
         @peripherals.pop (complete) ->
           (err, data) ->
+            if err?
+              console.log "## SYNC ERR ##", err
             if not err? and data?
-              form = data: JSON.stringify data
-              post {form, timeout, uri}, complete
+              console.log "## SYNC ##", data
+              {timestamp, peripheral, location} = data
+              delete peripheral._id
+              delete location._id
+              json = {timestamp, peripheral, location}
+              post {json, timeout, uri}, complete

       setTimeout sync, frequency

@@ -64,7 +71,12 @@
     @locations.push location

   addPeripheral: (peripheral) =>
+    now = new Date()
+    console.log "## NEW PERIPHERAL DISCOVERED AT #{now}##"
+    console.log peripheral
+    console.log "------------"
     @peripherals.push
+      timestamp: now.getTime()
       peripheral: peripheral
       location: @locationScanner.currentLocation


Avoid error in donna npm while generating documentation

--- src/token-iterator.coffee
+++ src/token-iterator.coffee
@@ -2,8 +2,6 @@

 module.exports =
 class TokenIterator
-  @instance: new this
-
   constructor: (line) ->
     @reset(line) if line?

@@ -83,3 +81,5 @@

   isAtomic: ->
     @isSoftTab() or @isHardTab() or @isSoftWrapIndentation() or @isPairedCharacter()
+
+TokenIterator.instance = new TokenIterator


get_type_of_hand function added, plus some improvements

--- KataPokerHands.coffee
+++ KataPokerHands.coffee
@@ -16,26 +16,32 @@
     [p[1].name, p[1].hand] = p1.slice(1)
     [p[2].name, p[2].hand] = p2.slice(1)

-    p[1].hand = parse_hand(p[1].hand)
-    p[2].hand = parse_hand(p[2].hand)
+    for i in [1, 2]
+        p[i].hand = parse_hand(p[i].hand)
+        p[i].type = get_hand_type(p[i].hand)

-    p[1].type = check_hand_types(p[1].hand)
-    p[2].type = check_hand_types(p[2].hand)
+    result = '%1 wins. - with %2'

-    result = ""
+    if (p[1].type[0] == p[2].type[0])
+        # check if all cards have same values
+        # TODO refactor, and set the result type (e.g. 'high card: 9')
+        for i in [1..5]
+            return [p[1], p[2]]
+            if (p[1].type[i].value > p[2].type[i].value)
+                result.replace('%1', p[1].name)
+                break
+            else if (p[1].type[i].value < p[2].type[i].value)
+                result.replace('%1', p[2.name])
+                break

-    if (p[1].type == p[2].type)
-        i = 0
-        while ((i < 5) && (p[1].hand[4-i].value == p[2].hand[4-i].value))
-            i++
-
-        # each player has same cards
-        return 'Tie.' if (i == 5)
+            return 'Tie.' if (i == 5)

-check_hand_types = (hand) ->
-    hand_types[0] # not implemented

+
+# Parse a string describing a hand ("XX XX XX XX XX") and
+# return a Card Array. Each Card has two attributes: suit (C, D, H or S), and
+# value (2-14)
 parse_hand = (s) ->

     class Card
@@ -60,5 +66,43 @@

     cards

+# return true if all cards of the given hand are of the same suit, else false
+has_unique_suit = (hand) ->
+    suit = hand[0].suit
+
+    for card in hand[1..]
+        return false if card.suit != suit
+
+    true
+
+
+# Return the maximal hand type found in an hand. This is a numbers Array,
+# the first one is the indice of the type in `hand_types`, and the following
+# ones are cards values ordered by importance.
+# Example for a 'Four of a kind' of Aces : [ 7, 14, 14, 14, 14, … ]
+get_hand_type = (hand) ->
+
+    same_suit = has_unique_suit(hand)
+
+    # sort the hand by values
+    hand.sort (a, b) -> a.value < b.value
+
+    # hand's cards values
+    values = hand.map (card) -> card.value
+
+    # Straight Flush
+    if (same_suit)
+        # check if all cards have consecutive values
+        if (!![1..4].reduce (b, i) -> b & (hand[i-1].value + 1 == hand[i].value))
+            return [7].concat(values)
+
+    # TODO
+
+    # default type : high card
+    [0].concat(values)
+
+
+    #renamining hand_types : [ 'High Card', 'Pair', 'Two Pairs', 'Three of a Kind', 'Straight',
+    #'Flush', 'Full House', 'Four of a kind' ]

 exports.compare_hands = compare_hands


Touch by full overwrite

Touch by full overwrite

--- src/upgrade/sensor.coffee
+++ src/upgrade/sensor.coffee
@@ -10,15 +10,19 @@
 exports.v = ->
   ver = read()
   if ver and 0 < semver.$ ver, filter PACKAGE.version
-    ver.$[0].join '.'
+    vvv ver

 # Ping for new version
 # To be run in the backgroung
 exports.p = ->
   return unless exports()
-  touch()
+  write() # Emulate touch
   write latest()
   return
+
+# Is file old?
+expired = ->
+  !path().ok 1000*60*60*24*3

 # Path to file with latest version
 path = ->
@@ -31,20 +35,21 @@
   .replace /// \w/ ///, '$&repos/'
   }/tags?per_page=8"

+# Build vfilter from string
 filter = (str)->
   new vfilter._ semver str
+
+# Convert vfilter back to string
+vvv = (filter)->
+  if filter
+    filter.$[0].join '.'

 # Get most fresh tag from GitHub
 latest = ->
   tags = json2 ajax api()
   for tag, i in tags by -1
     tags[i] = filter tag.name
-  tag =
-  tags.
-  sort semver.$
-  .pop()
-  if tag
-    tag.$[0].join '.'
+  vvv tags.sort(semver.$).pop()

 # Save to file
 write = (version)->
@@ -66,18 +71,5 @@
   s = s.replace /^\s+/, ''
   .split /\s+/, 2
   .shift()
-  filter s unless /^\W/.test s
-
-# Touch file
-touch = ->
-  f = path()
-  unless f.y()
-    write()
-    return
-  # Emulate touch
-  f.save f.load()
-  return
-
-# Is file old?
-expired = ->
-  !path().ok 1000*60*60*24*3
+  unless /^\W/.test s
+    filter s


:white_check_mark: improve test coverage

--- spec/paner-spec.coffee
+++ spec/paner-spec.coffee
@@ -271,6 +271,18 @@
           moveToVery(initialPane: p3, command: 'paner:very-right')
           ensurePaneLayout(horizontal: [[e1], [e2], [e3]])

+      describe "complex operation", ->
+        it "case 1", ->
+          p1.activate()
+          dispatchCommand('paner:very-top')
+          ensurePaneLayout(vertical: [[e1], {horizontal: [[e2], [e3]]}])
+          dispatchCommand('paner:very-left')
+          ensurePaneLayout(horizontal: [[e1], [e2], [e3]])
+          dispatchCommand('paner:very-bottom')
+          ensurePaneLayout(vertical: [{horizontal: [[e2], [e3]]}, [e1]])
+          dispatchCommand('paner:very-right')
+          ensurePaneLayout(horizontal: [[e2], [e3], [e1]])
+
     describe "all vertical", ->
       beforeEach ->
         waitsForPromise -> atom.workspace.open(f1).then (e) -> e1 = e
@@ -329,3 +341,15 @@
         it "case 3", ->
           moveToVery(initialPane: p3, command: 'paner:very-right')
           ensurePaneLayout(horizontal: [{vertical: [[e1], [e2]]}, [e3]])
+
+      describe "complex operation", ->
+        it "case 1", ->
+          p1.activate()
+          dispatchCommand('paner:very-top')
+          ensurePaneLayout(vertical: [[e1], [e2], [e3]])
+          dispatchCommand('paner:very-left')
+          ensurePaneLayout(horizontal: [[e1], {vertical: [[e2], [e3]]}])
+          dispatchCommand('paner:very-bottom')
+          ensurePaneLayout(vertical: [[e2], [e3], [e1]])
+          dispatchCommand('paner:very-right')
+          ensurePaneLayout(horizontal: [{vertical: [[e2], [e3]]}, [e1]])


fixed reference to non-existent var

fixed reference to non-existent var

--- coffee/directives/fixtable.coffee
+++ coffee/directives/fixtable.coffee
@@ -52,7 +52,7 @@
 			# get new page data
 			getPageData = ->
 				cb = scope.$parent[scope.options.pagingOptions.callback]
-				cb scope.pagingOptions, null, scope.appliedFilters
+				cb scope.options.pagingOptions, null, scope.appliedFilters

 			# provide methods to page forward/back in footer template
 			scope.nextPage = ->


Set loaded property after active

Set loaded property after active

Some views are listening for `loaded` to change
but require that `active` be `true` to do anything.
Make sure we set the `active` value first.

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -30,8 +30,6 @@
         @set('version', options.version) if options.version
         @fetch
           reset: true
-        .always () =>
-          @set('loaded', true)
         .done () =>
           @set('error', false)
           if @isBook()
@@ -39,6 +37,8 @@
               @setPage(options.page or 1) # Default to page 1
           else
             @set('active', true)
+        .always () =>
+          @set('loaded', true)

         .fail (model, response, options) =>
           @set('error', response?.status or model?.status or 9000)


:art: cleaner code for focusing on find editor on use-selection-as-find-pattern

--- lib/find.coffee
+++ lib/find.coffee
@@ -49,11 +49,8 @@

     @subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:use-selection-as-find-pattern', =>
       return if @projectFindPanel?.isVisible() or @findPanel?.isVisible()
-
       @createViews()
-      @projectFindPanel.hide()
-      @findPanel.show()
-      @findView.focusFindEditor()
+      showPanel @findPanel, @projectFindPanel, => @findView.focusFindEditor()

     @subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:toggle', =>
       @createViews()


Find and replace views close on core:close

--- lib/find.coffee
+++ lib/find.coffee
@@ -20,7 +20,7 @@
       @projectFindView.detach()
       @findView.showReplace()

-    rootView.on 'core:cancel', =>
+    rootView.on 'core:cancel core:close', =>
       @findView.detach()
       @projectFindView.detach()


Fixed custom error class

Fixed custom error class

--- lib/form.coffee
+++ lib/form.coffee
@@ -231,7 +231,7 @@
     message: React.PropTypes.string

   render: ->
-    className = if @context.message? then @props.className or "error-message"
+    className = if @context.message? then (@props.className or "error-message")
     @transferPropsTo div {className}, @context.message

 Submit = React.createClass


play toggle command

play toggle command

--- spotify-control.coffee
+++ spotify-control.coffee
@@ -10,7 +10,7 @@
 #   None
 #
 # Commands:
-#   hubot sp play
+#   hubot sp playtoggle
 #   hubot sp pause
 #   hubot sp prev
 #   hubot sp next
@@ -42,6 +42,15 @@
       else
         iface.Pause()
         msg.send "Music paused"
+
+  robot.respond /sp playtoggle$/i, (msg) ->
+    getInterface (err, iface) ->
+      if err
+        msg.send "Error: #{err}"
+      else
+        iface.PlayPause()
+        msg.send "Play toggled"
+

 spotifyPid = (callback) ->
   pidof 'spotify', (err, pid) ->


Add Atom Configuration Variables for Commands

--- lib/init.coffee
+++ lib/init.coffee
@@ -1,5 +1,7 @@
 module.exports =
   configDefaults:
+    clangCommand: 'clang'
+    clangPlusPlusCommand: 'clang++'
     clangExecutablePath: null
     clangIncludePath: '.'
     clangSuppressWarnings: false


added null check on subscription for group plans

--- services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
+++ services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
@@ -92,7 +92,7 @@
 						plans = SubscriptionViewModelBuilder.buildViewModel()
 						res.render "subscriptions/dashboard",
 							title: "your_subscription"
-							recomendedCurrency: subscription.currency
+							recomendedCurrency: subscription?.currency
 							plans: plans
 							subscription: subscription
 							groups: groups


Fix spelling in settings

Fix spelling in settings

--- lib/main.coffee
+++ lib/main.coffee
@@ -14,7 +14,7 @@
     wrapSelectionsInBrackets:
       type: 'boolean'
       default: true
-      description: 'Wrap selected text in brackets or quotes when the editor contains selections and the openeing bracket or quote is typed.'
+      description: 'Wrap selected text in brackets or quotes when the editor contains selections and the opening bracket or quote is typed.'

   activate: ->
     atom.workspace.observeTextEditors (editor) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -8,7 +8,7 @@

 @name average
 @methodOf Array#
-@returns {Array} A new array that contains only the non-null values.
+@returns {N} A new array that contains only the non-null values.
 ###
 Array::average = ->
   @sum()/@length


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -1,4 +1,4 @@
-
+Array

 ###*
 Returns a copy of the array without null and undefined values.


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -503,9 +503,7 @@

 @name inject
 @methodOf Array#
-
-@type Object
-@returns The result of a
+@returns {Object} The result of a
 ###
 Array::inject = (initial, iterator) ->
   this.each (element) ->


Removed debug output

Removed debug output

--- client/components/SubmitForm.coffee
+++ client/components/SubmitForm.coffee
@@ -34,7 +34,6 @@
         @submit = @submit.bind(this)

     setField: (field, value) ->
-        console.log field, value
         newState = {@state...}
         if field == "file"
             newState.wasFile = true
@@ -59,7 +58,6 @@
                 submit: undefined

     submit: (event) ->
-        console.log @state
         event.preventDefault()
         newState = {
             @state...
@@ -71,10 +69,8 @@
             fileName = document.getElementById("file").files[0]
             fileText = await PromiseFileReader.readAsArrayBuffer(fileName)
             fileText = Array.from(new Uint8Array(fileText))
-            console.log @state
             languageName = undefined
             for lang in LANGUAGES
-                console.log lang
                 if ""+@state.lang_id == ""+lang[0]
                     languageName = lang[1]
             dataToSend =


Remove second copy of knockout from build

--- config/webpack/base-config.coffee
+++ config/webpack/base-config.coffee
@@ -1,6 +1,9 @@
 module.exports =
   module:
-    loaders: [{test: /\.coffee$/, loader: 'coffee'}]
+    loaders: [
+      {test: /\.coffee$/, loader: 'coffee'}
+      {test: /knockout.build.output.knockout-latest\.js/, loader: 'imports?require=>false'}
+    ]

   resolve:
     root: '.'


Layer.position: Some fixes.

Layer.position: Some fixes.

- Fixed placement when knownPlacements don't cover all needed placements.
- Fixed placement for cases where the layer has 0 width or 0 height.

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -314,6 +314,8 @@
 		else
 			CUI.dom.setStyleOne(@__layer_root.DOM, "visibility", "hidden")

+	__allPlacements: ["s", "e", "w", "ws", "wn", "n", "se", "ne", "es", "en", "nw", "sw", "c"]
+
 	knownPlacements: ["s", "e", "w", "ws", "wn", "n", "se", "ne", "es", "en", "nw", "sw", "c"]

 	__setElement: (element) ->
@@ -440,7 +442,7 @@
 		vp_pl = {}

 		# calc all possible layer viewports
-		for placement in @knownPlacements
+		for placement in @__allPlacements
 			if placement not in ["n", "s", "e", "w", "c"]
 				continue

@@ -499,7 +501,7 @@
 			vp.overlap_align = null

 		# add two-direction placements
-		for placement in @knownPlacements
+		for placement in @__allPlacements
 			if placement in ["n", "s", "e", "w", "c"]
 				continue

@@ -534,7 +536,7 @@
 					vp.overlap_align = "left"

 		# throw out placements which are too small
-		for placement in @knownPlacements
+		for placement in @__allPlacements
 			if placement not in allowed_placements
 				delete(vp_pl[placement])
 				continue
@@ -745,8 +747,8 @@

 			# the higher this number, the better
 			vp.layer_pos.estate = vp.layer_pos.width * vp.layer_pos.height
-			vp.layer_pos.aspect_ratio = vp.layer_pos.width / vp.layer_pos.height
-			vp.dim_layer.aspect_ratio = vp.dim_layer.borderBoxWidth / vp.dim_layer.borderBoxHeight
+			vp.layer_pos.aspect_ratio = (vp.layer_pos.width or 1) / (vp.layer_pos.height or 1)
+			vp.dim_layer.aspect_ratio = (vp.dim_layer.borderBoxWidth or 1) / (vp.dim_layer.borderBoxHeight or 1)

 			wanted_rank = (allowed_placements.length - CUI.util.idxInArray(placement, allowed_placements))
 			if wanted_placement == placement
@@ -781,6 +783,7 @@
 		placement = available_placements[0]

 		if ev?.hasModifierKey()
+			console.debug "Layer.position", @, @opts
 			console.debug "layer", dim_layer
 			console.debug "element", dim_element
 			console.debug "pointer", dim_pointer


Adding new features like subscribeAll and reroute

--- src/untangle.coffee
+++ src/untangle.coffee
@@ -1,7 +1,9 @@

 class Untangle
 	subscribers = {}
+	subscribersAll = {}
 	responders = {}
+	reroutes = {}

 	@subscribe: (type, callback) ->
 		unless typeof callback == "function"
@@ -36,7 +38,17 @@
 		if subscribers[type]
 			for subscriber in subscribers[type]
 				setTimeout(subscriber(data), 0)
-
+
+		for subscribesToAll, callback of subscribersAll
+			setTimeout(callback(type, data), 0)
+
+		if reroutes[type]
+			for toType, callback of reroutes[type]
+				if typeof callback == "function"
+					Untangle.publish(toType, callback(data))
+				else
+					Untangle.publish(toType, data)
+
 	@request: (type, data) ->
 		return responders[type](data) if responders[type]
 		return null
@@ -55,4 +67,21 @@
 		String.prototype.request = (data) ->
 			Untangle.request(this, data)

+	@subscribeAll: (callback) ->
+		unless typeof callback == "function"
+			throw new Error "Callback not a function"
+		subscribersAll[callback] = callback
+
+	@unSubscribeAll: (callback) ->
+		delete subscribersAll[callback]
+
+	@reroute: (fromType, toType, callback=true) ->
+		unless reroutes[fromType]
+			reroutes[fromType] = {}
+
+		reroutes[fromType][toType] = callback
+
+	@unReroute: (fromType, toType) ->
+		delete reroutes[fromType][toType] if reroutes[fromType] && reroutes[fromType][toType]
+
 module.exports = Untangle


:fire: simplify

:fire: simplify

--- lib/view.coffee
+++ lib/view.coffee
@@ -136,21 +136,22 @@

 class Hover
   constructor: (@vimState, text='') ->
-    @emitter = new Emitter()
     @view = atom.views.getView(this)
     @set text

-  onDidSet: (callback) -> @emitter.on 'did-set', callback
-  onDidAdd: (callback) -> @emitter.on 'did-add', callback
-  onDidReset: (callback) -> @emitter.on 'did-reset', callback
-  onDidDestroy: (callback) -> @emitter.on 'did-destroy', callback
-
-  set: (text) -> @emitter.emit 'did-set', text
-  add: (text) -> @emitter.emit 'did-add', text
-  reset: -> @emitter.emit 'did-reset'
+  set: (text) ->
+    @view.textContent = text
+    @view.show()
+
+  add: (text) ->
+    @view.textContent += text
+    @view.show()
+
+  reset: -> @view.reset()
+
   destroy: ->
     @vimState = null
-    @emitter.emit 'did-destroy'
+    @view.destroy()

 class HoverElement extends HTMLElement
   createdCallback: ->
@@ -158,10 +159,6 @@
     this

   initialize: (@model) ->
-    @model.onDidSet (text) => @set(text)
-    @model.onDidAdd (text) => @add(text)
-    @model.onDidReset => @reset()
-    @model.onDidDestroy => @destroy()
     this

   show: ->
@@ -180,14 +177,8 @@
       type: 'overlay'
       item: this

-  set: (@textContent) ->
-    @show()
-
-  add: (text) ->
-    @textContent += text
-
   reset: ->
-    @set ''
+    @textContent = ''
     @marker?.destroy()

   destroy: ->
@@ -206,7 +197,6 @@
 module.exports = {
   ViewModel
   SearchViewModel
-  Hover
-  HoverElement
+  Hover, HoverElement
   VimNormalModeInputElement
 }


Updated atom's config

Updated atom's config

--- copies/atom/atom.copy/init.coffee
+++ copies/atom/atom.copy/init.coffee
@@ -1,4 +1,14 @@
 # Atom's init script
+
+# --------------------------------------------- #
+# | Close EVERYTHING at startup
+# --------------------------------------------- #
+
+# Close all tabs at startup
 process.nextTick ->
-  atom.workspace.getPaneItems().forEach ->
-    atom.workspace.destroyActivePaneItem()
+    atom.workspace.getPaneItems().forEach ->
+        atom.workspace.destroyActivePaneItem()
+
+# Close the tree at Startup
+#workspaceView = atom.views.getView(atom.workspace)
+#atom.commands.dispatch workspaceView, 'tree-view:toggle'


Scopes the error handler with the component

--- src/client/component.coffee
+++ src/client/component.coffee
@@ -127,6 +127,7 @@
             ds = @datasources[k]
             ds.parse(_.extend({}, @, @options || {}))
             handler = @["on#{_.string.capitalize(_.string.camelize(k))}Error"]
+            handler = _.bind(handler, @) if _.isFunction(handler)
             ctx.addDatasource(k, ds.fetch(), handler).then (res)=>
               @data[k] = res
           componentDeferred = @sandbox.data.when.apply(undefined, promiseArray)


Fix for coffeelint

Fix for coffeelint

--- _includes/coffee/simulation.coffee
+++ _includes/coffee/simulation.coffee
@@ -185,7 +185,7 @@
   ###
   codes_data.filter(
     (y) -> y.name.toLowerCase().includes(x)).concat(
-      {name:'custom',home_page:''}
+      {name:'custom', home_page:''}
   )[0]


Don't manually fade, instead use iframe control's fade

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -18,15 +18,11 @@
     FactlinkJailRoot.set_position_of_element @_top, @_left, window, @frame.$el
     #TODO:what's this line do?
     #FactlinkJailRoot.$factlinkCoreContainer.find('div.fl-button').removeClass('active')
-    @$el.addClass 'active'
-    @frame.$el.addClass 'factlink-control-visible'
+    @frame.fadeIn()

-  hide: =>
-    @$el.removeClass 'active'
-    @frame.$el.removeClass 'factlink-control-visible'
+  hide: => @frame.fadeOut()

-  destroy: =>
-    @frame.destroy()
+  destroy: => @frame.destroy()

 class FactlinkJailRoot.ShowButton extends Button
   content: """


Fix reinitialization of periodicCheckId

Fix reinitialization of periodicCheckId

Among other things, the code to check that resources is empty was incorrect

--- scripts/watch-web-resources.coffee
+++ scripts/watch-web-resources.coffee
@@ -68,22 +68,23 @@
     firstPeriodicCheck = false
   else
     resources = robot.brain.get('web_resources')
-    if resources is null
+    if resources is null or Object.keys(resources).length == 0
       clearInterval(periodicCheckId)
+      periodicCheckId = null
       firstPeriodicCheck = true
     else
       changedWebResources(robot, room)

 init = (robot) ->
-  resources = robot.brain.get('web_resources')
-  room = robot.brain.get('room_web_resources')
-  if resources isnt null and room isnt null
-    periodicCheckId = setInterval(periodicCheck, PERIODIC_CHECKS_INTERVAL, robot, room)
+  if periodicCheckId is null
+    resources = robot.brain.get('web_resources')
+    room = robot.brain.get('room_web_resources')
+    if resources isnt null and room isnt null and Object.keys(resources).length > 0
+      periodicCheckId = setInterval(periodicCheck, PERIODIC_CHECKS_INTERVAL, robot, room)

 module.exports = (robot) ->
   robot.brain.on 'loaded', (_) ->
-    if periodicCheckId is null
-      init(robot)
+    init(robot)

   robot.respond /watch ((?:https?:\/\/)?([^\s]+))$/, (res) ->
     resource = res.match[1]
@@ -99,7 +100,7 @@
         resources = robot.brain.get('web_resources') or {}
         resources[res.match[2]] = hash
         robot.brain.set 'web_resources', resources
-        if firstPeriodicCheck
+        if periodicCheckId is null
           room = res.envelope.room
           if typeof room is "undefined"
           # Direct message, we need to find the room where we last saw the user.


less chatty logging

less chatty logging

--- lib/assets/javascripts/stark/model_binding.js.coffee
+++ lib/assets/javascripts/stark/model_binding.js.coffee
@@ -9,7 +9,7 @@
     parent = view.parent
     while parent?
       if _.include @bound_views, parent
-        @log "found a parent view already bound, bailing"
+        # @log "found a parent view already bound, bailing"
         return
       parent = parent.parent


Ganti Background jadi Biru Tua

Ganti Background jadi Biru Tua

--- WebGL-06/coffeescript/webgl.coffee
+++ WebGL-06/coffeescript/webgl.coffee
@@ -2,8 +2,10 @@
 # Tidak ada variabel global dan mutable untuk menghindari kesalahan
 # Pemrograman

+# r = Window
 r = exports ? this

+# Entry Point
 r.startWebGL = () ->
   gl = null
   canvas = document.getElementById "webgl-canvas"
@@ -25,11 +27,16 @@
   loadFile "js/models/cone.json",
     handleLoadFile.bind(null, gl)

+# Fungsi untuk menangani pemuatan model
+# gl: WebGL Context
+# model: model data dengan vertex dan index
 handleLoadFile = (gl, model) ->
+  # Buat Vertex dan Index Buffers
   vertbuf = createBuffer gl, gl.ARRAY_BUFFER, model.vertices, 3
   indexbuf = createBuffer gl, gl.ELEMENT_ARRAY_BUFFER, model.indices,
     1, Uint16Array
-
+
+  # Vertex Shader
   vert = """
   attribute vec3 position;

@@ -40,7 +47,8 @@
     gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
   }
   """
-
+
+  # Fragment Shader
   frag = """
   precision mediump float;

@@ -48,20 +56,25 @@
     gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
   }
   """
-
+
+  # Daftar Attribute dan Uniform
   attributes = ['position']
   uniforms   = ['modelViewMatrix','projectionMatrix']
-
+
+  # Buat Shader Program
   shaderProgram = createShaderProgram gl, vert, frag, attributes, uniforms

+  # Aktifkan program
   gl.useProgram shaderProgram.program
-
+
+  # Fungsi animate () untuk mengupdate scene
   animate = () ->
     timeNow = new Date().getTime()
     if animate.lastTime? and animate.lastTime != 0
       elapsed = timeNow - animate.lastTime
     animate.lastTime = timeNow
-
+
+  # Render mesh
   render  = (program, vertbuf, indexbuf, gl) ->
     gl.clear gl.COLOR_BUFFER_BIT
     gl.viewport 0, 0, gl.viewportWidth, gl.viewportHeight
@@ -89,28 +102,33 @@
     gl.drawElements gl.LINE_STRIP, indexbuf.numItems, gl.UNSIGNED_SHORT, 0

     stack = mvMatrixPop(stack)
-
+
+  # Update Loop
   update = (gl, renderFunc) ->
     window.requestAnimationFrame(update.bind(null,gl, renderFunc))
     renderFunc(gl)
     animate()

-  gl.clearColor 0.0, 0.0, 0.0, 1.0
+  # Set warna dan depth serta aktifkan depth test
+  gl.clearColor 0.0, 0.0, 0.233, 1.0
   gl.clearDepth 1.0
   gl.enable gl.DEPTH_TEST

   update(gl, render.bind(null, shaderProgram, vertbuf, indexbuf))

+# Matrix Push
 mvMatrixPush = (stack, current) ->
   copy = mat4.create()
   mat4.set current, copy
   newStack = stack.concat(copy)
   return newStack

+# Matrix Pop
 mvMatrixPop = (stack) ->
   throw "Invalid popMatrix" if stack.length == 0
   return stack.slice(0,-1)

+# Buat Shader Program
 createShaderProgram = (gl, vertexSource, fragmentSource, attributes, uniforms) ->
   createShader = (gl, shaderSource,shaderType) ->
     shader = gl.createShader shaderType
@@ -126,6 +144,7 @@
     window.alert "Shader Error!"
     return null

+  # Buat Map berisi program, lokasi attribute, dan lokasi uniform
   rMap = {}
   rMap.program    = program
   rMap.attributes = {}


perf(express): speed up express by setting process.env.NODE_ENV to production on prod builds

--- src/src/server/init-server.coffee
+++ src/src/server/init-server.coffee
@@ -6,6 +6,10 @@
 appFilePath   = path.resolve dir, '..', config.dist.app.server.scripts.file
 clientDirPath = path.resolve dir, '..', '..', config.dist.app.client.dirName # creates absolute path to the client folder
 serverDirPath = path.resolve dir, '..', '..', config.dist.app.server.dirName
+
+# set NODE_ENV (conditionally)
+# ============================
+process.env.NODE_ENV = 'production' if config.env.is.prod

 # create the server object
 # ========================


re-enable selenium tests

re-enable selenium tests

--- test-integration/all.coffee
+++ test-integration/all.coffee
@@ -1,13 +1,13 @@
 # require './simple'
 require './cc-dashboard'
-# require './calendar'
-# require './task-plan/homework'
-# require './student/dashboard'
-# require './task-plan/cleanup'
-# require './task-plan/draft'
-# require './task-plan/publish'
-# require './task'
-# require './scores'
-# require './roster'
+require './calendar'
+require './task-plan/homework'
+require './student/dashboard'
+require './task-plan/cleanup'
+require './task-plan/draft'
+require './task-plan/publish'
+require './task'
+require './scores'
+require './roster'

 # require './exhaustive' # Exhaustive Tests


account

account

--- models/account.coffee
+++ models/account.coffee
@@ -13,7 +13,6 @@
     { authId, authToken } = authTokenPair()
     options.data ?= {}
     options.data = _.extend options.data, { auth_id: authId, auth_token: authToken }
-    console.log 'options', options
     super

   idOr_id: ->


Do not deduplicate javascript: URLs.

Do not deduplicate javascript: URLs.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -217,9 +217,10 @@
         queryTerms: @currentSearch.queryTerms
         type: "bookmark"
         url: bookmark.url
-        displayUrl: bookmark.displayUrl
         title: if usePathAndTitle then bookmark.pathAndTitle else bookmark.title
         relevancyFunction: @computeRelevancy
+        displayUrl: bookmark.displayUrl
+        deDuplicate: not bookmark.displayUrl?
     onComplete = @currentSearch.onComplete
     @currentSearch = null
     onComplete suggestions


call: remove reference to test resources

--- test/api/call/module.coffee
+++ test/api/call/module.coffee
@@ -8,14 +8,34 @@

   scratch = test.scratch @

-  it 'string requires a module', ->
+  it 'string referencing a sync module', ->
     logs = []
     nikita
     .on 'text', (log) -> logs.push log.message
-    .call who: 'sync', 'test/resources/module_sync'
-    .call who: 'async', 'test/resources/module_async'
+    .file
+      target: "#{scratch}/sync.coffee"
+      content: """
+      module.exports = ({options}) ->
+        @log "Hello \#{options.who or 'world'}"
+      """
+      log: false
     .call ->
-      logs.should.eql ['Hello sync', 'Hello async']
+      @call who: 'sync', "#{scratch}/sync"
+    .call ->
+      logs.should.eql ['Hello sync']
+    .promise()
+
+  it 'string referencing an async module', ->
+    nikita
+    .file
+      target: "#{scratch}/async.coffee"
+      content: """
+      module.exports = ({options}, callback) ->
+        callback null, {message: 'hello'}
+      """
+    .call ->
+      @call who: 'async', "#{scratch}/async", (err, {message}) ->
+        message.should.eql 'hello' unless err
     .promise()

   it 'string requires a module from process cwd', ->


fixed attr id catch

fixed attr id catch

--- src/core/fetch/value.coffee
+++ src/core/fetch/value.coffee
@@ -31,9 +31,12 @@
       node = node[vars.id.value]
       if depth isnt variable
         returned = checkData(vars, node, depth, vars.id.value)
-      if returned is null
+      if returned is null or returned is undefined
         returned = checkAttrs(vars, node, depth, vars.id.value)
-      return null if returned is null
+      if returned is null or returned is undefined
+        return null
+      else if depth is variable
+        return returned
       node = returned
     else
       return null


- updated injectors

- updated injectors

--- src/crystal/generate.coffee
+++ src/crystal/generate.coffee
@@ -245,7 +245,7 @@
 			# get content from output
 			template = generator.template
 			if template
-				template = template.replace /([\s]+)?>>>[a-z_]*<<<\n?/ig, (injector) ->
+				template = template.replace /([  |\t]+)?>>>[a-z_]*<<<\n?/ig, (injector) ->
 					injector_tabs = injector.match /^[\s]+>/g
 					if injector_tabs
 						injector_tabs = injector_tabs[0].substr(0, injector_tabs[0].length-1)
@@ -261,7 +261,7 @@
 						inject = ''
 						for inj in injected.split "\n"
 							inject += "#{injector_tabs}#{inj}\n"
-						inject += "\n"
+						inject += ""
 					else
 						''
 			if engine


Set new value of window matches after removing match at @removeMatchesOfNonExistingCompetitors.

--- app/assets/javascripts/voluntary/dom_manipulation/competitive_list.js.coffee
+++ app/assets/javascripts/voluntary/dom_manipulation/competitive_list.js.coffee
@@ -58,7 +58,7 @@
       )

       if notExistingCompetitors.length > 0
-        window.Voluntary.DomManipulation.CompetitiveList.removeItemFromArray(window.matches, match)
+        window.matches = window.Voluntary.DomManipulation.CompetitiveList.removeItemFromArray(window.matches, match)
       else
         window.competitorsOfCompetitor[match['competitors'][0]] ||= []
         window.competitorsOfCompetitor[match['competitors'][0]].push match['competitors'][1]


client: team-settings: don't show member sections if not allowed

--- client/home/lib/myteam/index.coffee
+++ client/home/lib/myteam/index.coffee
@@ -10,6 +10,7 @@
 remote               = require('app/remote').getInstance()
 camilizeString = require 'app/util/camelizeString'
 toImmutable = require 'app/util/toImmutable'
+canSeeMembers = require 'app/util/canSeeMembers'
 isAdmin = require 'app/util/isAdmin'

 SECTIONS =
@@ -121,18 +122,20 @@
     @wrapper.addSubView header  'Team Settings'
     @wrapper.addSubView @teamSettings = section 'Team Settings'

-    @wrapper.addSubView header  'Send Invites'
-    @wrapper.addSubView @sendInvites = section 'Send Invites'
     if isAdmin()
       @wrapper.addSubView header  'Permissions'
       @wrapper.addSubView @permissions = section 'Permissions'

+    if isAdmin() or canSeeMembers()

-    @wrapper.addSubView header  'Invite Using Slack'
-    @wrapper.addSubView @connectSlack = section 'Invite Using Slack'
-    @connectSlack.on 'InvitationsAreSent', -> TeamFlux.actions.loadPendingInvitations()
+      @wrapper.addSubView header  'Send Invites'
+      @wrapper.addSubView @sendInvites = section 'Send Invites'

-    @wrapper.addSubView header  'Teammates'
-    @wrapper.addSubView @teammates = section 'Teammates'
+      @wrapper.addSubView header  'Invite Using Slack'
+      @wrapper.addSubView @connectSlack = section 'Invite Using Slack'
+      @connectSlack.on 'InvitationsAreSent', -> TeamFlux.actions.loadPendingInvitations()
+
+      @wrapper.addSubView header  'Teammates'
+      @wrapper.addSubView @teammates = section 'Teammates'

     @scrollToSectionArea()


Fix signup

Fix signup

--- client/services/loginservice.coffee
+++ client/services/loginservice.coffee
@@ -61,7 +61,7 @@

         false
       ,
-      'submit #signup-form': (e, t) ->
+      'submit #signup-form': (e, t) =>
         logger.debug("User submitted signup form")
         errorCaption = "Signup Error"
         passwordLength = 8
@@ -81,7 +81,7 @@
         if !password?
           return
         website = trimWhitespace(t.find(".account-website").value)
-        if not /^https?:\/\/.+$/.test(website)
+        if !S.isBlank(website) and not /^https?:\/\/.+$/.test(website)
           notificationService.warn(errorCaption, "You must supply a valid URL")
           return false


suppr key is pressed : Select Previous Message

--- client/app/utils/api_utils.coffee
+++ client/app/utils/api_utils.coffee
@@ -162,9 +162,15 @@
             return

         deleteMessage = (isModal) ->
+            # Get next message information
+            # before delete (context changes)
+            next = MessageStore.getPreviousConversation()
+
             MessageActionCreator.delete {messageID}
             LayoutActionCreator.hideModal() if isModal
-            Utils.messageClose()
+
+            # Goto next message
+            Utils.messageSetCurrent next

         settings = SettingsStore.get()


Update Checkins.coffee

Update Checkins.coffee

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -65,6 +65,7 @@

             Не забудьте с собой паспорт (если паспорта еще нет, то свидетельство о рождении) -- его могут спросить охранники на входе!

+
25 ноября занятия не будет, я немного приболел.

             {
             if @state.result?.error
                 <Alert bsStyle="danger">


Fix undefined in register form

Fix undefined in register form

--- client/components/Register.coffee
+++ client/components/Register.coffee
@@ -221,7 +221,7 @@
                     <FieldGroup
                         id="informaticsClass"
                         label={"Класс" + (@state.informaticsData &&
-                        " в #{@state.informaticsData.currentYearStart}-#{@state.informaticsData.currentYearStart+1} учебном году")}
+                        " в #{@state.informaticsData.currentYearStart}-#{@state.informaticsData.currentYearStart+1} учебном году" || "")}
                         type="text"
                         value={@state.informaticsData?.class || ""}
                         disabled/>


virtaulmachines/connectedmachines: will show machine detail modal

--- client/home/lib/virtualmachines/components/connectedmachineslist/view.coffee
+++ client/home/lib/virtualmachines/components/connectedmachineslist/view.coffee
@@ -4,6 +4,39 @@
 MachineItem = require '../machineslist/listitem'

 module.exports = class ConnectedMachinesListView extends React.Component
+
+  @propTypes =
+    onChangeAlwaysOn    : React.PropTypes.func
+    onChangePowerStatus : React.PropTypes.func
+    onDetailOpen        : React.PropTypes.func
+    onSharedWithUser    : React.PropTypes.func
+    onUnsharedWithUser  : React.PropTypes.func
+
+  @defaultProps =
+    onChangeAlwaysOn    : kd.noop
+    onChangePowerStatus : kd.noop
+    onDetailOpen        : kd.noop
+    onSharedWithUser    : kd.noop
+    onUnsharedWithUser  : kd.noop
+
+
+  onDisconnectVM: (machine) ->
+    @props.onDisconnectVM machine
+
+
+  onDetailOpen: (machine) ->
+
+    @props.onDetailOpen machine.get '_id'
+
+
+  onSharedWithUser: (machine, nickname) ->
+
+    @props.onSharedWithUser machine.get('_id'), nickname
+
+
+  onUnsharedWithUser: (machine, nickname) ->
+
+    @props.onUnsharedWithUser machine.get('_id'), nickname

   numberOfSections: -> 1

@@ -18,12 +51,18 @@

     machines = stack.get 'machines'
       .sort (a, b) -> a.get('label').localeCompare(b.get('label')) # Sorting from a to z
-      .map (machine) ->
+      .map (machine) =>
         <MachineItem
-          key={machine.get '_id'}
-          stack={stack}
-          machine={machine}
-          shouldRenderDetails={no} />
+          key={machine.get '_id'} stack={stack} machine={machine}
+          shouldRenderDetails={yes}
+          shouldRenderSpecs={yes}
+          shouldRenderDisconnect={yes}
+          shouldRenderSharing={yes}
+          onDisconnectVM={@lazyBound 'onDisconnectVM', machine}
+          onDetailOpen={@lazyBound 'onDetailOpen', machine}
+          onSharedWithUser={@lazyBound 'onSharedWithUser', machine}
+          onUnsharedWithUser={@lazyBound 'onUnsharedWithUser', machine}
+        />

   renderEmptySectionAtIndex: ->
No connected machines.

@@ -37,4 +76,6 @@
       renderSectionHeaderAtIndex={@bound 'renderSectionHeaderAtIndex'}
       renderRowAtIndex={@bound 'renderRowAtIndex'}
       renderEmptySectionAtIndex={@bound 'renderEmptySectionAtIndex'}
+      sectionClassName='HomeAppViewVMSection'
+      rowClassName='stack'
     />


remove use of ngettext in discussion jasmine test

--- common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee
+++ common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee
@@ -146,11 +146,7 @@
             expect(view.$(".js-response-list .discussion-response").length).toEqual(numNonEndorsed)
             assertResponseCountAndPaginationCorrect(
                 view,
-                ngettext(
-                    "#{numNonEndorsed} #{if numEndorsed then "other " else ""}response",
-                    "#{numNonEndorsed} #{if numEndorsed then "other " else ""}responses",
-                    numNonEndorsed
-                )
+                "#{numNonEndorsed} #{if numEndorsed then "other " else ""}#{if numNonEndorsed == 1 then "response" else "responses"}",
                 if numNonEndorsed then "Showing all responses" else null,
                 null
             )


Fix for moving to a new location.

Fix for moving to a new location.

--- packages/landsofillusions/memory/actions/move.coffee
+++ packages/landsofillusions/memory/actions/move.coffee
@@ -24,6 +24,8 @@
     "_person_ enters."

   createStartScript: (person, nextNode, nodeOptions = {}) ->
+    return unless @content
+
     if @content.landmark
       coordinates = LOI.adventure.world.getPositionVector @content.landmark


Don't use decimals for category count column charts

--- static/coffee/trac.coffee
+++ static/coffee/trac.coffee
@@ -72,7 +72,10 @@
     xAxis: {
       categories: data[0]
     },
-    yAxis: { title: null },
+    yAxis: {
+      title: null,
+      allowDecimals: false
+    },
     series: [{
       data: data[1]
     }],


consistent url

consistent url

--- static/index.coffee
+++ static/index.coffee
@@ -154,7 +154,7 @@

     $.ajax({
       method: 'GET'
-      url: 'tastes'
+      url: 'tastes/'
     }).always(always).done(done).fail(fail)

   tryLoading = (hash) ->


Seems to be able to download submits properly

--- server/cron/downloadSubmits.coffee
+++ server/cron/downloadSubmits.coffee
@@ -1,6 +1,6 @@
 Future = Npm.require('fibers/future');

-class BasicSubmitDownloader
+class AllSubmitDownloader
     # Лицей 40
     baseUrl: (page) ->
         'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=3696&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=10&with_comment=&page=' + page + '&action=getHTMLTable'
@@ -9,8 +9,12 @@

     AC: 'Зачтено/Принято'
     IG: 'Проигнорировано'
+
+    needContinueFromSubmit: (runid) ->
+        true

     processSubmit: (uid, name, pid, runid, prob, date, outcome, childrenResults) ->
+        res = @needContinueFromSubmit(runid)
         if (outcome == @AC)
             outcome = "AC"
         if (outcome == @IG)
@@ -18,7 +22,7 @@
         console.log uid, name, pid, runid, prob, date, "'"+outcome+"'"
         Submits.addSubmit(runid, date, uid, pid, outcome)
         Users.addUser(uid, name, "lic40")
-        true
+        res

 #    if date > endDate:  # we do not need this, but continue search
 #        return True
@@ -35,7 +39,7 @@

     parseSubmits: (submitsTable, childrenResults) ->
         submitsRows = submitsTable.split("
")
-        result = false
+        result = true
         for row in submitsRows
             re = new RegExp '[^<]*	\\s*([^<]*)	\\s*([^<]*)	\\s*([^<]*)	\\s*[^<]*	\\s*([^<]*)	', 'gm'
             data = re.exec row
@@ -49,7 +53,7 @@
             date = data[6]
             outcome = data[7].trim()
             resultSubmit = @processSubmit(uid, name, pid, runid, prob, date, outcome, childrenResults)
-            result = result or resultSubmit
+            result = result and resultSubmit
         return result

     run: ->
@@ -63,6 +67,18 @@
             if not result
                 break
             page = page + 1
+        updateResults()
+
+class LastSubmitDownloader extends AllSubmitDownloader
+    needContinueFromSubmit: (runid) ->
+        !Submits.findById(runid)
+
+class UntilIgnoredSubmitDownloader extends AllSubmitDownloader
+    needContinueFromSubmit: (runid) ->
+        res = Submits.findById(runid)?.outcome
+        r = !((res == "AC") || (res == "IG"))
+        return r
+

 updateResults = ->
     for user in Users.findAll().fetch()
@@ -76,13 +92,13 @@
                 Results.addResult(user._id, problem._id, Submits.problemResult(user._id, problem))

-#SyncedCron.add
-#    name: 'loadTable',
-#    schedule: (parser) ->
-#        return parser.text('every 10 seconds');
-##        return parser.text('every 5 minutes');
-#    job: ->
-#        (new BasicSubmitDownloader()).run()
+SyncedCron.add
+    name: 'loadTable',
+    schedule: (parser) ->
+        return parser.text('every 10 seconds');
+#        return parser.text('every 5 minutes');
+    job: ->
+        (new UntilIgnoredSubmitDownloader()).run()

 SyncedCron.start()
 #Meteor.startup ->


Syntatctic sugar is so easy to misuse

Syntatctic sugar is so easy to misuse

--- server/initializers/logging.coffee
+++ server/initializers/logging.coffee
@@ -4,7 +4,7 @@
 LOG_DIRECTORY = './logs'

 module.exports = (app) ->
-  if app.get('env') is not 'development'
+  if app.get('env') !='development'
     fs.mkdirSync(LOG_DIRECTORY) unless fs.statSync(LOG_DIRECTORY)
     app.use express.logger(
       stream:


Update the timeout

Update the timeout

--- server/serversystem/manager.coffee
+++ server/serversystem/manager.coffee
@@ -91,6 +91,7 @@
 configureServer = (serverObj, lobby, instance)->
   console.log "configuring server "+instance.ip+":"+instance.port+" rcon pass "+instance.rconPass
   srvr = new Rcon instance.ip, instance.port, instance.rconPass, {tcp: true, challenge: true}
+  srvr.setTimeout 15000
   connecting = true
   srvr.connect()
   srvr.on('auth', ->


Fix shoddy VPL   codes.

Fix shoddy VPL   codes.

--- public/main.coffee
+++ public/main.coffee
@@ -206,6 +206,7 @@
     book.vpl_url = "http://vpl.bibliocommons.com#{book.vpl_url}"
     book.goodreads_url = "https://www.goodreads.com/book/show/#{book.goodreads_id}"
     book.available = book.availability is "Available"
+    book.description = book.description.replace(/&?nbsp;?/g, " ") if book.description

     if categories = book.goodreads_categories
       book.fiction = if "non-fiction" in categories or "nonfiction" in categories


Index the previous decoration cache by lineNumberId rather than screenRow

Why? Screen rows change. If some operation (folding?) changes the
screen rows and the decorations at the same time, the previous
decorations will no longer be valid and can no longer be diffed against
the decorations to-be-rendered.

--- src/gutter-component.coffee
+++ src/gutter-component.coffee
@@ -28,7 +28,7 @@
     @lineNumberNodesById = {}
     @lineNumberIdsByScreenRow = {}
     @screenRowsByLineNumberId = {}
-    @previousDecorations = {}
+    @renderedDecorationsByLineNumberId = {}

   componentDidMount: ->
     @appendDummyLineNumber()
@@ -100,14 +100,16 @@
       visibleLineNumberIds.add(id)

       if @hasLineNumberNode(id)
-        @updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0, lineDecorations[screenRow])
+        @updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0)
       else
         newLineNumberIds ?= []
         newLineNumbersHTML ?= ""
         newLineNumberIds.push(id)
-        newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow, lineDecorations[screenRow])
+        newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
         @screenRowsByLineNumberId[id] = screenRow
         @lineNumberIdsByScreenRow[screenRow] = id
+
+      @renderedDecorationsByLineNumberId[id] = lineDecorations[screenRow]

     if newLineNumberIds?
       WrapperDiv.innerHTML = newLineNumbersHTML
@@ -119,7 +121,6 @@
         @lineNumberNodesById[lineNumberId] = lineNumberNode
         node.appendChild(lineNumberNode)

-    @previousDecorations = lineDecorations
     visibleLineNumberIds

   removeLineNumberNodes: (lineNumberIdsToPreserve) ->
@@ -131,10 +132,11 @@
         delete @lineNumberNodesById[lineNumberId]
         delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
         delete @screenRowsByLineNumberId[lineNumberId]
+        delete @renderedDecorationsByLineNumberId[lineNumberId]
         node.removeChild(lineNumberNode)

-  buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow, decorations) ->
-    {editor, lineHeightInPixels} = @props
+  buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
+    {editor, lineHeightInPixels, lineDecorations} = @props
     if screenRow?
       style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
     else
@@ -142,7 +144,7 @@
     innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)

     classes = ''
-    if decorations?
+    if lineDecorations and decorations = lineDecorations[screenRow]
       for decoration in decorations
         if editor.decorationMatchesType(decoration, 'gutter')
           classes += decoration.class + ' '
@@ -162,15 +164,17 @@
     iconHTML = '

'
     padding + lineNumber + iconHTML

-  updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped, decorations) ->
-    {editor} = @props
+  updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped) ->
+    {editor, lineDecorations} = @props
     node = @lineNumberNodesById[lineNumberId]
-    previousDecorations = @previousDecorations[screenRow]

     if editor.isFoldableAtBufferRow(bufferRow)
       node.classList.add('foldable')
     else
       node.classList.remove('foldable')
+
+    decorations = lineDecorations[screenRow]
+    previousDecorations = @renderedDecorationsByLineNumberId[lineNumberId]

     if previousDecorations?
       for decoration in previousDecorations


Override license for promzard@0.2.0

Override license for promzard@0.2.0

It's a dependency of npm which makes it difficult to upgrade

--- build/tasks/license-overrides.coffee
+++ build/tasks/license-overrides.coffee
@@ -39,3 +39,24 @@
     repository: 'https://github.com/keeganstreet/specificity'
     license: 'MIT'
     source: 'package.json in repository'
+
+  'promzard@0.2.0':
+    license: 'ISC'
+    source: 'LICENSE in the repository'
+    sourceText: """
+        The ISC License
+
+        Copyright (c) Isaac Z. Schlueter
+
+        Permission to use, copy, modify, and/or distribute this software for any
+        purpose with or without fee is hereby granted, provided that the above
+        copyright notice and this permission notice appear in all copies.
+
+        THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+        WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+        MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+        ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+        WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+        ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+        IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+      """


Removed unnecessary global Factlink function, making it local

--- js-library/app/js/util/scrollto.coffee
+++ js-library/app/js/util/scrollto.coffee
@@ -1,4 +1,4 @@
-Factlink.scrollTo = (fact_id) ->
+scrollTo = (fact_id) ->
   $('body')
     ._scrollable()
     .scrollTo "span[data-factid=#{fact_id}]", 800,
@@ -8,4 +8,4 @@

 $(window).bind 'factlink.factsLoaded', ->
   if FactlinkConfig?.scrollto
-    Factlink?.scrollTo FactlinkConfig.scrollto
+    scrollTo FactlinkConfig.scrollto


removed test of criterion._get_question (not helping right now)


Clicking on a hackathon marker redirects to the hackathon's news page

--- app/assets/javascripts/maps.coffee
+++ app/assets/javascripts/maps.coffee
@@ -13,6 +13,7 @@

     google.maps.event.addListenerOnce(map, 'tilesloaded', addMarkers(map, markerImages))

+
 addMarkers = (map, markerImages) ->
     $.getJSON('/hackathons.json', (data) ->
         latlngbounds = new google.maps.LatLngBounds()
@@ -20,8 +21,12 @@
             latLng = new google.maps.LatLng(val.location.latitude, val.location.longitude);
             marker = new google.maps.Marker(position: latLng, map: map, icon: markerImages[val.status])
             latlngbounds.extend(latLng)
+            google.maps.event.addListener(marker, 'click', () ->
+            	window.location.href = '/hackathons/' + val.id + '/news'
+            )
         )
         map.fitBounds(latlngbounds)
     )

-google.maps.event.addDomListener(window, 'load', initialize);+google.maps.event.addDomListener(window, 'load', initialize);
+


fixed bug in view

fixed bug in view

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -80,7 +80,7 @@

 		#renders all geometries in geometry-array
 		totalNumberOfVertices = 0
-		#totalNumberOfVertices += drawTriangleplane()
+		totalNumberOfVertices += drawTriangleplane()

 		# first Mesh is always the coordinate axis mini-map
 		if meshes[0]


scope I18n in slideshow tooltips

scope I18n in slideshow tooltips

Change-Id: I88d4e27580cf3e494a2727332e44e0c05528795b
Reviewed-on: https://gerrit.instructure.com/5178
Reviewed-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>
Tested-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>

--- app/coffeescripts/slideshow.coffee
+++ app/coffeescripts/slideshow.coffee
@@ -1,132 +1,133 @@
-class Slide
-  constructor: (title, slideshow) ->
-    @title = title
-    @slideshow = slideshow
-    @body = $('<li/>').addClass('slide')
-    @slideshow.slides.append(@body)
+I18n.scoped 'slideshow', (I18n) ->
+  class Slide
+    constructor: (title, slideshow) ->
+      @title = title
+      @slideshow = slideshow
+      @body = $('<li/>').addClass('slide')
+      @slideshow.slides.append(@body)

-    @prevSlide = @slideshow.slideObjects[@slideshow.slideObjects.length - 1]
-    @prevSlide?.nextSlide = this
-    @nextSlide = null
+      @prevSlide = @slideshow.slideObjects[@slideshow.slideObjects.length - 1]
+      @prevSlide?.nextSlide = this
+      @nextSlide = null

-    @indicator = $("<a/>").addClass('slide').attr('href', '#').attr('title', $.h(@title)).html(' ')
-    @indicator.data('slide', this)
-    @slideshow.navigation.append(@indicator)
-    slide = this
-    @indicator.click ->
-      slideshow.showSlide(slide)
-      return false
+      @indicator = $("<a/>").addClass('slide').attr('href', '#').attr('title', $.h(@title)).html(' ')
+      @indicator.data('slide', this)
+      @slideshow.navigation.append(@indicator)
+      slide = this
+      @indicator.click ->
+        slideshow.showSlide(slide)
+        return false

-    @hide()
+      @hide()

-  addParagraph: (text, klass) ->
-    paragraph = $("<p/>")
-    paragraph.addClass(klass) if klass?
-    paragraph.html($.h(text))
-    @body.append(paragraph)
+    addParagraph: (text, klass) ->
+      paragraph = $("<p/>")
+      paragraph.addClass(klass) if klass?
+      paragraph.html($.h(text))
+      @body.append(paragraph)

-  addImage: (src, klass, url) ->
-    image = $("<img/>").attr('src', src)
-    image.addClass(klass) if klass?
-    if url
-      link = $("<a/>").attr('href', url).attr('target', '_blank')
-      link.append(image)
-      @body.append(link)
-    else
-      @body.append(image)
+    addImage: (src, klass, url) ->
+      image = $("<img/>").attr('src', src)
+      image.addClass(klass) if klass?
+      if url
+        link = $("<a/>").attr('href', url).attr('target', '_blank')
+        link.append(image)
+        @body.append(link)
+      else
+        @body.append(image)

-  show: ->
-    @body.show()
-    @indicator.addClass('current_slide')
+    show: ->
+      @body.show()
+      @indicator.addClass('current_slide')

-  hide: ->
-    @indicator.removeClass('current_slide')
-    @body.hide()
+    hide: ->
+      @indicator.removeClass('current_slide')
+      @body.hide()

-class Slideshow
-  constructor: (id) ->
-    slideshow = this
-    @dom = $('<div/>').attr('id', id)
+  class Slideshow
+    constructor: (id) ->
+      slideshow = this
+      @dom = $('<div/>').attr('id', id)

-    @slides = $('<ul/>').addClass('slides')
-    @dom.append(@slides)
+      @slides = $('<ul/>').addClass('slides')
+      @dom.append(@slides)

-    @separator = $("<div/>").addClass('separator')
-    @dom.append(@separator)
+      @separator = $("<div/>").addClass('separator')
+      @dom.append(@separator)

-    @navigation = $("<div/>").addClass('navigation')
-    @dom.append(@navigation)
+      @navigation = $("<div/>").addClass('navigation')
+      @dom.append(@navigation)

-    @backButton = $("<a/>").addClass('back').
-      attr('href', '#').
-      attr('title', $.h(I18n.t('titles.back', 'Back'))).
-      html(' ')
-    @navigation.append(@backButton)
-    @backButton.click ->
-      slideshow.showPrevSlide()
-      return false
+      @backButton = $("<a/>").addClass('back').
+        attr('href', '#').
+        attr('title', $.h(I18n.t('titles.back', 'Back'))).
+        html(' ')
+      @navigation.append(@backButton)
+      @backButton.click ->
+        slideshow.showPrevSlide()
+        return false

-    @forwardButton = $("<a/>").addClass('forward').
-      attr('href', '#').
-      attr('title', $.h(I18n.t('titles.forward', 'Forward'))).
-      html(' ')
-    @navigation.append(@forwardButton)
-    @forwardButton.click ->
-      slideshow.showNextSlide()
-      return false
+      @forwardButton = $("<a/>").addClass('forward').
+        attr('href', '#').
+        attr('title', $.h(I18n.t('titles.forward', 'Forward'))).
+        html(' ')
+      @navigation.append(@forwardButton)
+      @forwardButton.click ->
+        slideshow.showNextSlide()
+        return false

-    @closeButton = $("<a/>").addClass('close').
-      attr('href', '#').
-      attr('title', $.h(I18n.t('titles.close', 'Close'))).
-      html(' ')
-    @navigation.append(@closeButton)
-    @closeButton.click ->
-      slideshow.close()
-      return false
+      @closeButton = $("<a/>").addClass('close').
+        attr('href', '#').
+        attr('title', $.h(I18n.t('titles.close', 'Close'))).
+        html(' ')
+      @navigation.append(@closeButton)
+      @closeButton.click ->
+        slideshow.close()
+        return false

-    @slideObjects = []
-    @slideShown = null
+      @slideObjects = []
+      @slideShown = null

-  addSlide: (name, callback) ->
-    slide = new Slide(name, this)
-    @slideObjects.push(slide)
-    callback(slide)
+    addSlide: (name, callback) ->
+      slide = new Slide(name, this)
+      @slideObjects.push(slide)
+      callback(slide)

-  start: ->
-    @showSlide(@slideObjects[0])
-    @dialog = @dom.dialog
-      dialogClass: 'slideshow_dialog'
-      height: 529
-      width: 700
-      modal: true
-      draggable: false
-      resizable: false
+    start: ->
+      @showSlide(@slideObjects[0])
+      @dialog = @dom.dialog
+        dialogClass: 'slideshow_dialog'
+        height: 529
+        width: 700
+        modal: true
+        draggable: false
+        resizable: false

-  showSlide: (slide) ->
-    if slide
-      unless @slideShown and slide == @slideShown
-        @slideShown?.hide()
-        @slideShown = slide
-        @slideShown.show()
-      if @slideShown.prevSlide
-        @backButton.removeClass('inactive')
-      else
-        @backButton.addClass('inactive')
-      if @slideShown.nextSlide
-        @forwardButton.removeClass('inactive')
-      else
-        @forwardButton.addClass('inactive')
+    showSlide: (slide) ->
+      if slide
+        unless @slideShown and slide == @slideShown
+          @slideShown?.hide()
+          @slideShown = slide
+          @slideShown.show()
+        if @slideShown.prevSlide
+          @backButton.removeClass('inactive')
+        else
+          @backButton.addClass('inactive')
+        if @slideShown.nextSlide
+          @forwardButton.removeClass('inactive')
+        else
+          @forwardButton.addClass('inactive')

-  showPrevSlide: ->
-    @showSlide(@slideShown?.prevSlide)
+    showPrevSlide: ->
+      @showSlide(@slideShown?.prevSlide)

-  showNextSlide: ->
-    @showSlide(@slideShown?.nextSlide)
+    showNextSlide: ->
+      @showSlide(@slideShown?.nextSlide)

-  close: ->
-    @dom.dialog('close')
-    @dom.hide()
-    @slideShown?.hide()
-    @slideShown = null
+    close: ->
+      @dom.dialog('close')
+      @dom.hide()
+      @slideShown?.hide()
+      @slideShown = null

-window.Slideshow = Slideshow
+  window.Slideshow = Slideshow


Toggle flipbook mode via @state.inFlipbookMode #1901

--- app/components/subject-viewer.cjsx
+++ app/components/subject-viewer.cjsx
@@ -51,6 +51,7 @@
     frame: @props.frame ? 0
     playbackRate: 1
     frameDimensions: {}
+    inFlipbookMode: false

   componentDidMount: ->
     @refs.videoScrubber?.value = 0
@@ -59,26 +60,18 @@
     @refs.videoPlayer?.playbackRate = @state.playbackRate

   render: ->
-    FrameWrapper = @props.frameWrapper
-
-    mainDisplay = for frame of @props.subject.locations
-      {type, format, src} = getSubjectLocation @props.subject, frame
-
-      frameDisplay = switch type
-        when 'image'
-
-        when 'video'
-          <video src={src} type={"#{type}/#{format}"} controls onLoad={@handleLoad}>
-            Your browser does not support the video format. Please upgrade your browser.
-          </video>
-
-      <FrameWrapper frame={frame} naturalWidth={@state.frameDimensions[src]?.width} naturalHeight={@state.frameDimensions[src]?.height} workflow={@props.workflow} subject={@props.subject} classification={@props.classification} annotation={@props.annotation}>
-        {frameDisplay}
-      </FrameWrapper>
+    mainDisplay = ''
+    if @state.inFlipbookMode
+      {type, format, src} = getSubjectLocation @props.subject, @state.frame
+      mainDisplay = @renderFrame type, format, src, @state.frame
+    else
+      mainDisplay = for frame of @props.subject.locations
+        {type, format, src} = getSubjectLocation @props.subject, frame
+        @renderFrame type, format, src, frame

     tools = switch type
       when 'image'
-        if @props.subject?.locations.length < 2 or subjectHasMixedLocationTypes @props.subject
+        if not @state.inFlipbookMode or @props.subject?.locations.length < 2 or subjectHasMixedLocationTypes @props.subject
           null
         else

@@ -154,6 +147,17 @@

+  renderFrame: (type, format, src, frame) ->
+    FrameWrapper = @props.frameWrapper
+    <FrameWrapper frame={frame} naturalWidth={@state.frameDimensions[src]?.width} naturalHeight={@state.frameDimensions[src]?.height} workflow={@props.workflow} subject={@props.subject} classification={@props.classification} annotation={@props.annotation}>
+      {switch type
+        when 'image'
+
+        when 'video'
+          <video src={src} type={"#{type}/#{format}"} controls onLoad={@handleLoad}>
+            Your browser does not support the video format. Please upgrade your browser.
+          </video>}
+    </FrameWrapper>

   hiddenPreloadedImages: ->
     # Render this to ensure that all a subject's location images are cached and ready to display.


Default allowSeparateFrames to false Prevents the toggle button from showing on Talk. Closes #2169

--- app/components/subject-viewer.cjsx
+++ app/components/subject-viewer.cjsx
@@ -43,7 +43,7 @@
     linkToFullImage: false
     frameWrapper: null
     allowFlipbook: true
-    allowSeparateFrames: true
+    allowSeparateFrames: false

   getInitialState: ->
     loading: true


Forgot validation before adding a business.

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -90,38 +90,41 @@

     addBusiness: () ->
         self = this;
-        request = $.ajax "/businesses/add", {type: "POST", data: {
-        	name: $("#businessName").val(),
-        	address: $("#businessAddress").text(),
-        	latitude: this._place.geometry.location.lat(),
-        	longitude: this._place.geometry.location.lng(),
-        	pin_enabled: $("#pinEnabled").prop("checked")
-        }}
-        request.done (data) ->
-            self._infoWindow.close()
+        if $("#businessName").val() == ""
+            alert "Name must be filled in before you can add this business to the map."
+        else
+            request = $.ajax "/businesses/add", {type: "POST", data: {
+        	    name: $("#businessName").val(),
+        	    address: $("#businessAddress").text(),
+        	    latitude: this._place.geometry.location.lat(),
+                longitude: this._place.geometry.location.lng(),
+                pin_enabled: $("#pinEnabled").prop("checked")
+            }}
+            request.done (data) ->
+                self._infoWindow.close()

-            # TODO: ick. Use views for this.
-            checked = ""
-            if data.pin_enabled == "true"
-                checked = "checked"
-            windowContents = '
' +
-                '
' + data.name + '
' +
-                '
<address>' + data.address + '</address>
' +
-                '
<input type="checkbox" id="pinEnabled" value="true" ' + checked + ' disabled />business has PIN pad
' +
-                '
report errors

'
+                # TODO: ick. Use views for this.
+                checked = ""
+                if data.pin_enabled == "true"
+                    checked = "checked"
+                windowContents = '
' +
+                    '
' + data.name + '
' +
+                    '
<address>' + data.address + '</address>
' +
+                    '
<input type="checkbox" id="pinEnabled" value="true" ' + checked + ' disabled />business has PIN pad
' +
+                    '
report errors

'

-            newMarker = {
-        	    marker: new google.maps.Marker({
-        	        position: new google.maps.LatLng(data.lat, data.lng),
-        	        map: self._map
-        	    }),
-        	    infoWindow: new google.maps.InfoWindow({
-        	        content: windowContents
-        	    })
-            }
-            google.maps.event.addListener newMarker.marker, "click", () ->
-                newMarker.infoWindow.open self._map, newMarker.marker
-            self._locations.push newMarker
+                newMarker = {
+        	        marker: new google.maps.Marker({
+        	            position: new google.maps.LatLng(data.lat, data.lng),
+        	            map: self._map
+        	        }),
+        	        infoWindow: new google.maps.InfoWindow({
+        	            content: windowContents
+        	        })
+                }
+                google.maps.event.addListener newMarker.marker, "click", () ->
+                    newMarker.infoWindow.open self._map, newMarker.marker
+                self._locations.push newMarker

     @route "loc/:lat/:long", (lat, lng) ->
         mapOptions = {


protect against missing

protect against missing

--- api/apps/articles/routes.coffee
+++ api/apps/articles/routes.coffee
@@ -17,6 +17,7 @@
 @show = (req, res, next) ->
   Article.find req.params.id, (err, article) ->
     return next err if err
+    return res.err 404, 'Article not found.' unless article
     res.send present article

 # POST /api/articles


removed symbols XTS and BTS and instead using asset_id 0 to enabling a voting transfer #531

--- app/js/controllers/transfer.coffee
+++ app/js/controllers/transfer.coffee
@@ -147,7 +147,7 @@
                         trx: -> trx
                         action: -> yesSend
                         transfer_type: ->
-                            if $scope.transfer_info.symbol == 'XTS' || $scope.transfer_info.symbol == 'BTS' then 'xts' else ''
+                            if $scope.transfer_asset.id is 0 then 'xts' else ''

     $scope.newContactModal = ->
         $modal.open


End responses in express.js to avoid infinite loading.

--- app/js/loader/loader_common.coffee
+++ app/js/loader/loader_common.coffee
@@ -43,7 +43,7 @@
     document.getElementsByTagName('head')[0].appendChild style_tag

     #### Create iframe so jslib's namespace (window) doesn't collide with any content window.
-    jslib_jail_iframe = mkEl 'iframe'
+    jslib_jail_iframe = mkEl 'iframe', 'factlink_jail_iframe'
     jslib_jail_iframe.style.display = 'none';
     document.body.appendChild(jslib_jail_iframe)


Remove dev code.

Remove dev code.

--- app/client/js/errorreporter.coffee
+++ app/client/js/errorreporter.coffee
@@ -30,7 +30,6 @@
     @param {*} reason
   ###
   report: (action, reason) ->
-    console.log action, reason, @userName
     return if !@isWorthReporting_ reason

     # TODO: Show something more beautiful then alert. Add button to reload app.


Fix menu-data bug

Fix menu-data bug

--- app/coffeescripts/menu-data.coffee
+++ app/coffeescripts/menu-data.coffee
@@ -36,11 +36,6 @@
     Identity: noconfig
     Contact: noconfig
     Legal: noconfig
-
-context_menu_item(label, event) ->
-  className: 'context_menu_item',
-  label: label,
-  event: event

 # Main window context menu
 $a.main_context_menu = [