menu: link logo

menu: link logo

--- src/components/menu/index.coffee
+++ src/components/menu/index.coffee
@@ -121,8 +121,16 @@
         onclick: @toggle
       z '.container',
         z '.menu',
-          z.server.link \
-            z 'a.logo', href: '/', 'Zorium'
+          z 'a.logo',
+            href: '/'
+            onclick: z.ev (e, $$el) ->
+              if util.isRegularClickEvent e
+                e.preventDefault()
+                # Manually scroll because we don't want people to lose
+                # their scoll position on page refresh
+                window.scrollTo(0, 0)
+                z.server.go $$el.href
+            'Zorium'
           z '.break'
           z '.list',
             _.map links, (link) =>


Emit status messages for loading/empty states

--- src/components/qa/exercises.cjsx
+++ src/components/qa/exercises.cjsx
@@ -38,9 +38,17 @@
     <Question key={exercise.id} model={exercise.content.questions[0]}/>

   render: ->
+    content = if ExerciseStore.isLoaded([@state.pageId])
+      exercises = ExerciseStore.allForPage(@state.pageId)
+      if _.isEmpty(exercises)
+
No exercises found

+      else _.map exercises, @renderExercise
+    else
+
Loading...

+

-        {_.map ExerciseStore.allForPage(@state.pageId), @renderExercise}
+        {content}


force new Finder instance

force new Finder instance

--- lib/browse.coffee
+++ lib/browse.coffee
@@ -124,7 +124,7 @@
     # Default file manager
     switch process.platform
       when "darwin"
-        exec "open -R #{path}"
+        exec "open -n -R #{path}"
         fileManager = "Finder"
       when "win32"
         exec "explorer /select,#{path}"


[master] filter

[master] filter

--- translator/run.coffee
+++ translator/run.coffee
@@ -51,7 +51,7 @@
               process.stdout.write '\rskip'
               return
             else
-              src = (entry.title + '\n' + entry.content).replace(/
|\u001d|\u2586|\u2587|\u258c|\u00b7|\u0000/g, ' ').trim()
+              src = (entry.title + '\n' + entry.content).replace(/
|\u001d|\u2586|\u2587|\u258c|\u00b7|\u0000|\uff0b|\u002e/g, ' ').trim()
               currentTranslator = round.get()
               process.stdout.write '\r'
               process.stdout.write JSON.stringify(round.status())


added width tweak to table wrapper to fix visual issues

--- src/directives/table_view.coffee
+++ src/directives/table_view.coffee
@@ -73,7 +73,7 @@
       # Update the width and height of the whole table
       element.css
         height: cellOuterHeight * opts.numDisplayRows
-        width:  opts.width
+        width:  opts.width - 2

       ($scope, element, attrs) ->
         # Make sure columns are defined


Drop unused code

Drop unused code

--- src/gui/tabs/remote/click.coffee
+++ src/gui/tabs/remote/click.coffee
@@ -22,9 +22,9 @@
   ask.x "Install OpenSSL", "openssl", (is64)->
     require '../journal'
       .T()
+      # Performs also `defer ask` to remove all popups
     echo "Install:", "OpenSSL"
     node = remotes.x node, is64
-    defer ask
     remotes.O node, (success)->
       echo if success then "OpenSSL installed" else "Failed to install OpenSSL"
       return


draw colission crash

draw colission crash

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -140,7 +140,7 @@
     @color = '#00FFFF'
     @padding = 1
     @buildings = []
-    @f = 10
+    @f = 0

   draw_scene: ->
     unless @empty
@@ -206,22 +206,35 @@
     player = this['player_' + player]
     player.grab_banana(force, angle)
     @timeout = setTimeout (=>
-      @start_time = 0
-      @animate_banana(player)
+      @start_time = new Date()
+
+      @animate_banana(player, @start_time)
       ),
       @f

   animate_banana:(player) ->
     @timeout = setTimeout (=>
       @draw_scene()
+      if @banana_hit_gorilla(player) == true
+        x = @colission[0]
+        y = @colission[1]
+        @star(x+o, y) for o in [ -3...3 ]
+        @star(x-o, y) for o in [ -1...3 ]
+        @star(x, y+o) for o in [ -1...3 ]
+        @star(x, y-o) for o in [ -3...3 ]
+        return
+        @next_player_turn(player)
       if @banana_has_collided(player) == true
         @next_player_turn(player)
         return
       if @within_boundaries(player.banana.x(), player.banana.y()) == false
         @next_player_turn(player)
         return
-      player.throw_banana()
-
+
+      now = new Date()
+      time = now - @start_time
+
+      player.throw_banana(time/1000)
       @animate_banana(player)
       ),
       @f
@@ -230,11 +243,15 @@
     x = player.banana.x()
     y = player.banana.y()
     for building in @buildings
-      return true if @player_2.check_colission x, y
-      return true if @player_1.check_colission x, y
       return true if building.check_colission x, y
     false

+  banana_hit_gorilla:(player) ->
+    x = player.banana.x()
+    y = player.banana.y()
+    if @player_2.check_colission(x, y) || @player_1.check_colission(x, y)
+      @colission = [x, y]
+      return true

   next_player_turn:(player) ->
     next_player = if player.player_number == 2 then 1 else 2
@@ -242,6 +259,26 @@

   within_boundaries:(x, y) ->
     return false if x < 0 || x > @width || y > @height || y < 0
+
+  star:(x, y) ->
+    color = '#FFFF00'
+    @context.strokeStyle = color
+    @context.beginPath()
+    @context.lineWidth = 1
+    for z in [0...5]
+      @draw_ray(x, y, (360*z/72))
+      @context.stroke()
+
+
+  draw_ray:(x, y, a) ->
+    @context.moveTo x, y
+    coords = @coordinates(x, y, 20, a)
+    @context.lineTo coords.x, coords.y
+
+  coordinates:(x, y, d, a) ->
+    x: x + d * Math.cos(a),
+    y: y + d * Math.sin(a)
+

 class Gorilla
   constructor:(@context, @player_number) ->
@@ -264,12 +301,11 @@
   grab_banana:(force, angle) ->
     @banana = new Banana(@context, @x+@width, @y-@height, force, angle)

-  throw_banana: ->
-    @banana.draw_frame()
+  throw_banana:(time) ->
+    @banana.draw_frame(time)

   check_colission:(x, y) ->
     if (@y < y+(@height) || @y < y-(@height)) && (x > @x-@width/2 && x < @x+@width/2)
-      alert "BOOM"
       return true
     false

@@ -291,12 +327,12 @@
   draw: ->
     @context.drawImage @image(), @x(), @y()

-  draw_frame: ->
+  draw_frame:(time) ->
+    @start_time = time
     @draw()
     @calculate_projection()

   calculate_initial_position: ->
-    @start_time = @start_time + 0.1
     radian = @angle*Math.PI/180
     @dx = @force*Math.cos(radian)
     @dy = @force*Math.sin(radian)-@g*@start_time


Compat fix

Compat fix

--- framer/Compat.coffee
+++ framer/Compat.coffee
@@ -3,13 +3,13 @@
 compatWarning = (msg) ->
 	console.warn msg

-compatProperty = (name) ->
+compatProperty = (name, originalName) ->
 	exportable: false
 	get: ->
-		compatWarning "#{name} is a deprecated property"
+		compatWarning "#{originalName} is a deprecated property"
 		@[name]
 	set: (value) ->
-		compatWarning "#{name} is a deprecated property"
+		compatWarning "#{originalName} is a deprecated property"
 		@[name] = value

 class CompatLayer extends Layer
@@ -21,9 +21,9 @@

 		super options

-	@define "superView", compatProperty "superLayer"
-	@define "subViews", compatProperty "subLayers"
-	@define "siblingViews", compatProperty "siblingLayers"
+	@define "superView", compatProperty "superLayer", "superView"
+	@define "subViews", compatProperty "subLayers", "subViews"
+	@define "siblingViews", compatProperty "siblingLayers", "siblingViews"

 	addSubView = (layer) -> @addSubLayer layer
 	removeSubView = (layer) -> @removeSubLayer layer


Fix tests

Fix tests

--- test/gateblu-container-spec.coffee
+++ test/gateblu-container-spec.coffee
@@ -1,3 +1,3 @@
-GatebluContainer = require '../gateblu-container'
+Container = require '../container'

-describe 'GatebluContainer', ->
+describe 'Container', ->


disable force in send -- we will investigate "merge conflicts" by hand, rather than deleting user data!

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -1549,7 +1549,7 @@
         project_id : required
         source     : required    # {host:ip_address, version:snapshot_name}
         dest       : required    # {host:ip_address, version:snapshot_name}
-        force      : true
+        force      : false       # TODO: this may make things slam to a halt... but we need to nail this down.
         cb         : undefined

     dbg = (m) -> winston.debug("send(#{opts.project_id},#{misc.to_json(opts.source)}-->#{misc.to_json(opts.dest)}): #{m}")


forgot get route

forgot get route

--- config/routes.coffee
+++ config/routes.coffee
@@ -13,10 +13,6 @@
     map.get '/tasks/tags/:tag/todo', 'tasks#tagTodo'
     map.get '/tasks/tags/:tag/archives', 'tasks#tagArchives'

-    # routes for tasks interaction (non-prefixed)
-    map.put '/tasks/:id', 'tasks#update'
-    map.del '/tasks/:id', 'tasks#destroy'
-
     # routes for todolists interactions
     map.get  '/todolists', 'todolists#all'
     map.post '/todolists', 'todolists#create'
@@ -29,5 +25,10 @@
     # routes for tag interactions
     map.get '/tasks/tags', 'tasks#tags'

+    # routes for tasks interaction (non-prefixed)
+    map.get '/tasks/:id', 'tasks#show'
+    map.put '/tasks/:id', 'tasks#update'
+    map.del '/tasks/:id', 'tasks#destroy'
+
     # routes for tree interactions
     map.get  '/tree', 'tree#tree'


extra quotes

extra quotes

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -17,10 +17,12 @@

 POLISH_QUOTE = [
   "Żyj tak, jakby jutro już wczoraj wyruchało Cię w dupę.",
-  "Wczoraj koks sypał się jak konfident na przesłuchaniu.",
+  "Koks sypał się jak konfident na przesłuchaniu.",
   "Mężczyźni w którymś momencie zaczynają przedkładać cellulit mózgowy nad brak cellulitu dupnego.",
   "Promieniować blaskiem bardziej niż elektrownia w Czarnobylu.",
-  "Wyglądasz jakby Ci ktoś w wargę strzykawką z botoksem nie trafił."
+  "Wyglądasz jakby Ci ktoś w wargę strzykawką z botoksem nie trafił.",
+  "Rzucil sie jak rozlew Dunaju na przybrzezne domki",
+  "Niebo gwiazdziste nade mna a koniak grzejacy perorował apologie"
 ]

 MATT_QUOTE = [


add more future

add more future

--- scripts/future.coffee
+++ scripts/future.coffee
@@ -13,7 +13,8 @@
       'http://stayornay.com/tech/wp-content/uploads/2010/07/LaserDisc-DVD-Sizes-web.jpg',
       'http://netdna.webdesignerdepot.com/uploads/2009/01/newton.jpg',
       'http://distilleryimage2.ak.instagram.com/083fe09c405711e3bb0822000a1fbacd_8.jpg',
-      'http://media.tumblr.com/d44062e2b0f30fdce7615c90c982b587/tumblr_inline_mkfsczbiUo1qz4rgp.gif'
+      'http://media.tumblr.com/d44062e2b0f30fdce7615c90c982b587/tumblr_inline_mkfsczbiUo1qz4rgp.gif',
+      'https://lh5.googleusercontent.com/-lXVUj0-i8GA/UL90muHh8WI/AAAAAAAAEow/yX90zMQmqSY/w1149-h862-no/IMG_20121205_111949.jpg'
     ]

     msg.send msg.random images


I don t see the right panel when the node is published

--- BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
+++ BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
@@ -64,7 +64,7 @@
     @addPreviewLink()
     if @node.attributes.status.published
       $('.ui-model *', @el).unbind()
-      $('.js-widget-panel').hide()
+      $('.js-widget-blockpanel', @$el).hide()
       $('span.action', @el).hide()
     else
       $("ul.ui-model-areas, ul.ui-model-blocks", @$el).each ->


Check for android version

Check for android version

--- client.coffee
+++ client.coffee
@@ -22,6 +22,13 @@
 # ========== Events ==========
 exports.render = ->
 	log '8: FULL RENDER'
+
+	Server.call 'log', Plugin.userId(), Plugin.agent().android
+	version = Plugin.agent().android
+	if version?
+		if version <2.4
+			Dom.text 'Sorry this game is unavailable for android version 2.3 or lower'
+			return 0
 	if not (window.inRangeCheckinRunning?)
 		window.inRangeCheckinRunning = {}
 	if not (inRangeCheckinRunning[Plugin.groupId()]?)
@@ -1206,10 +1213,11 @@
 	result = ''
 	Db.shared.iterate 'game', 'beacons', (beacon) !->
 		location = L.latLng(beacon.peek('location', 'lat'), beacon.peek('location', 'lng'))
-		#log 'location='+location+', lat='+beacon.peek('location', 'lat')+', lng='+beacon.peek('location', 'lng')+', beacon=', beacon, ', key='+beacon.key()
-		if event.latlng.distanceTo(location) < beaconRadius*2 and !tooClose
-			tooClose = true;
-			result = 'Beacon is placed too close to other beacon'
+		if location?
+			#log 'location='+location+', lat='+beacon.peek('location', 'lat')+', lng='+beacon.peek('location', 'lng')+', beacon=', beacon, ', key='+beacon.key()
+			if event.latlng.distanceTo(location) < beaconRadius*2 and !tooClose
+				tooClose = true;
+				result = 'Beacon is placed too close to other beacon'
 	#Check if marker area is passing the game border
 	if !tooClose
 		outsideGame = !boundaryRectangle.getBounds().contains(event.latlng)


Enable high-res tiles for retina displays

--- client.coffee
+++ client.coffee
@@ -1080,6 +1080,7 @@
 			# Default OpenStreetMap tiles: maxZoom = 21
 			L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
 				attribution: '© OpenStreetMap contributors'
+				detectRetina: true
 			}).addTo(map);
 			# CartoDB dark tiles: maxZoom = 18
 			###


Calculate score in GUI from percs

Calculate score in GUI from percs

--- client.coffee
+++ client.coffee
@@ -367,6 +367,7 @@
             Dom.style padding: '5px 5px 0'
             userCnt = Plugin.users.count().get()
             results = round.get('results')
+            percs = round.get('percs')
             if !results
                 Dom.div tr("Not enough votes")
             else
@@ -415,7 +416,7 @@
                         Dom.style textAlign: 'center', color: '#aaa', padding: '12px 8px', fontSize: '85%', borderBottomStyle: 'none'
                         Dom.addClass 'b_vertical'
                         if myRank in [1..3]
-                            if results[myRanking['self'] - 1][1] is results[myRank - 1][1]
+                            if percs[myRanking['self']] is percs[myRank]
                                 diff = 0
                         if not diff?
                             diff = Math.min(3, Math.abs(myRank - myRanking['self']))


Add EB Garamond

Add EB Garamond

--- examples/Controls.cjsx
+++ examples/Controls.cjsx
@@ -74,6 +74,7 @@
   "Fira Sans"
   "Jura"
   "Bitter"
+  "EB Garamond"
 ]

 fonts = fonts.sort()


JS tweak.

JS tweak.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -1047,7 +1047,6 @@
         content = $('[data-behavior~=settings_modal]').html()
         feedbin.modalBox(content);
         event.preventDefault()
-        return

     fuzzyFilter: ->
       feeds = $('.feed-form li')


disallow opening of clicker during association of question

--- app/assets/javascripts/clickers.coffee
+++ app/assets/javascripts/clickers.coffee
@@ -38,10 +38,12 @@
     $('#cancelSearch').show().data('change', $(this).data('change'))
     $('#clickerAlternativeSelection').hide()
     $('#clickerAssociatedQuestion').hide()
+    $('#openClickerButton').hide()
     return

   $(document).on 'click', '#cancelSearch', ->
     $('#clickerSearchForm').hide()
+    $('#openClickerButton').show()
     if $(this).data('change')
       $('#clickerAssociatedQuestion').show()
     else


Beginnings of script to make form elements appear

--- app/assets/javascripts/meetings.coffee
+++ app/assets/javascripts/meetings.coffee
@@ -1,3 +1,7 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
+
+$('document').ready ->
+  $('a#add_talk').on 'click', (e) ->
+    # Show the talk-adding form


Scroll chat log to bottom on page load

Scroll chat log to bottom on page load

--- app/assets/javascripts/rooms.js.coffee
+++ app/assets/javascripts/rooms.js.coffee
@@ -11,8 +11,9 @@

 class ChatLog
   constructor: (@room_id, @container) ->
+    @_subscribe()
+    @scrollToBottom()
     @container.scroll(@_onChatScroll)
-    @_subscribe()

   _subscribe: ->
     window.FAYE_CLIENT.subscribe "/rooms/#{@room_id}/message", (data) =>
@@ -23,7 +24,10 @@
   addRawMessage: (html) ->
     $row = $(html)
     $row.appendTo(@container).hide().fadeIn()
-    @container.stop(true, false).animate({scrollTop: @container.prop('scrollHeight')})
+    @scrollToBottom()
+
+  scrollToBottom: ->
+    @container.stop(true).animate(scrollTop: @container.prop('scrollHeight'))

 class CurrentQuestion


Minor changes

Minor changes

--- source/keychain.coffee
+++ source/keychain.coffee
@@ -147,13 +147,13 @@
       if profile?
         @loadProfile(profile)
       else
-        throw new Error 'Couldn\'t find profile.js'
+        callback(new Error 'Couldn\'t find profile.js')

       if folders? then @loadFolders(folders)
       if bands.length > 0 then @loadBands(bands)
       if attachments.length > 0 then @loadAttachment(attachments)

-      if callback? then callback()
+      callback(null)

     return this

@@ -300,17 +300,17 @@
   ###*
    * Lock the keychain. This discards all currently decrypted keys, overview
    * data and any decrypted item details.
-   * - autolock {Boolean} : Whether the keychain was locked automatically.
-   * > this
-  ###
-  lock: (autolock) ->
-    @event.emit('lock:before', autolock)
+   * - _autolock {Boolean} : Whether the keychain was locked automatically.
+   * > this
+  ###
+  lock: (_autolock) ->
+    @event.emit('lock:before', _autolock)
     @super = undefined
     @master = undefined
     @overview = undefined
     @items = {}
     @unlocked = false
-    @event.emit('lock:after', autolock)
+    @event.emit('lock:after', _autolock)
     return this


update combo editor

update combo editor

--- app/classifier/tasks/combo/editor.cjsx
+++ app/classifier/tasks/combo/editor.cjsx
@@ -57,7 +57,7 @@
             {Object.keys(@props.workflow.tasks).map (taskKey) =>
               taskDescription = @props.workflow.tasks[taskKey]
               TaskComponent = tasks[taskDescription.type]
-              <option key={taskKey} value={taskKey} disabled={taskDescription.type is 'drawing'}>{TaskComponent.getTaskText taskDescription}</option>}
+              <option key={taskKey} value={taskKey} disabled={taskDescription.type is 'crop'}>{TaskComponent.getTaskText taskDescription}</option>}
           </select>
         </label>
       </p>


removed min size for quadtree ROI in collision.detect for improved computational efficiency (was using it for debugging and forgot to remove last commit)

--- src/physics/modules/collision.coffee
+++ src/physics/modules/collision.coffee
@@ -17,9 +17,8 @@
     return unless @list.length > 0
     @update_quadtree() # update the quadtree for collision detection after all moveable elements have been moved
     quadtree = @quadtree # copy for access inside inner function
-    minsize = 100
     _.each(_.filter(@list, (d) -> d.active), (d) ->
-      size = Math.max(4 * (d.size + d.tol), minsize) # define size of selection box using the size of this element
+      size = 2 * (d.size + d.tol) # define size of selection box using the size of this element
       # define the selection box to use for searching the quadtree:
       x0 = d.r.x - size
       x3 = d.r.x + size


Disable logs

Disable logs

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -28,9 +28,9 @@
     if listeners?.length
       listener.call null, params for listener in listeners

-  window.addEventListener 'popstate', (event)->
-    console.log event
-    console.log location
+  # window.addEventListener 'popstate', (event)->
+  #   log event
+  #   log location

   window.addEventListener 'hashchange', (event)->
     changeRoute getHashFragment(event.newURL)


Fix voting

Fix voting

--- app/js/controllers/account_vote.coffee
+++ app/js/controllers/account_vote.coffee
@@ -42,7 +42,7 @@

     $scope.updateVotes = ->
         myBal=$scope.balances[Info.symbol]
-        balMinusFee=myBal.amount / myBal.precision - Info.info.priority_fee / myBal.precision
+        balMinusFee=myBal.amount / myBal.precision - Wallet.info.priority_fee.amount / myBal.precision
         $modal.open
             templateUrl: "dialog-confirmation.html"
             controller: "DialogConfirmationController"


Was not resolving a relative root name. Tests always used __dirname to form an absolute path.t

--- lib/malifi.coffee
+++ lib/malifi.coffee
@@ -29,7 +29,7 @@
   unless root?
     throw new Error('malifi site root path required')
   options?= {}
-  baseSiteStack= [normalize(root), normalize(join(__dirname,'../base-site'))]
+  baseSiteStack= [path.resolve(root), normalize(join(__dirname,'../base-site'))]
   loader.init(baseSiteStack,options)

   malifiConnectHandler= (req, res, next)->


Need to set the Editor height to view all lines in sample file

--- spec/highlight-selected-spec.coffee
+++ spec/highlight-selected-spec.coffee
@@ -55,6 +55,8 @@
         jasmine.attachToDOM(workspaceElement)
         editor = atom.workspace.getActiveTextEditor()
         editorElement = atom.views.getView(editor)
+        editorElement.setHeight(250)
+        editorElement.component.measureDimensions()

     describe "updates debounce when config is changed", ->
       beforeEach ->


Revert "fixed typo/bug in router#all"

Revert "fixed typo/bug in router#all"

This reverts commit d5ecff1ff5aae7dee0a10f3482b169f781fbf1a9.

--- lib/router.coffee
+++ lib/router.coffee
@@ -132,7 +132,7 @@
     params = false

     for route in @routes
-      params = route.parse.apply path, arguments
+      params = route.parse.apply route, arguments
       if params
         ret.push params
     ret


Let's try this.

Let's try this.

--- spec/about-spec.coffee
+++ spec/about-spec.coffee
@@ -59,6 +59,9 @@
     waitsForPromise ->
       atom.packages.activatePackage('about')

+    waitsForPromise ->
+      atom.workspace.open('sample.js')
+
   afterEach ->
     atom.packages.deactivatePackage('about')
     atom.packages.deactivatePackage('status-bar')
@@ -77,6 +80,6 @@
         triggerUpdate()
         expect(workspaceElement).toContain('.about-release-notes')

-        dispatchCall = spyOn(atom.commands, 'dispatch')
-        $(workspaceElement).find('.about-release-notes').trigger('click')
-        expect(dispatchCall.mostRecentCall.args[1]).toBe 'about:view-release-notes'
+        # dispatchCall = spyOn(atom.commands, 'dispatch')
+        # $(workspaceElement).find('.about-release-notes').trigger('click')
+        # expect(dispatchCall.mostRecentCall.args[1]).toBe 'about:view-release-notes'


hackily fix setting fields?

hackily fix setting fields?

--- lib/sunset.coffee
+++ lib/sunset.coffee
@@ -7,25 +7,25 @@
       description: 'What syntax theme should I use during daylight?'
       default: ''
       type: 'string'
-      enum: atom.config.settings.core.themes
+      enum: ['']
     daytime_ui_theme:
       title: 'Daytime Theme (UI)'
       description: 'What UI theme should I use during daylight?'
       default: ''
       type: 'string'
-      enum: atom.config.settings.core.themes
+      enum: ['']
     nighttime_syntax_theme:
       title: 'Nighttime Theme (Syntax)'
       description: 'What syntax theme should I use during nighttime?'
       default: ''
       type: 'string'
-      enum: atom.config.settings.core.themes
+      enum: ['']
     nighttime_ui_theme:
       title: 'Nighttime Theme (UI)'
       description: 'What UI theme should I use during nighttime?'
       default: ''
       type: 'string'
-      enum: atom.config.settings.core.themes
+      enum: ['']
     when_does_it_get_dark:
       title: 'When does nighttime start?'
       description: 'When (in 24hr format) does the sun set?'


Fix typo in var name

Fix typo in var name

--- app/assets/javascripts/api-helpers/location_service.js.coffee
+++ app/assets/javascripts/api-helpers/location_service.js.coffee
@@ -1,13 +1,13 @@
 FM.LocationService = Ember.Object.extend
-  _aChache: Ember.Map.create({})
+  _aCache: Ember.Map.create({})
   find: (latitude, longitude) ->
     rr = (n) -> (Math.round(n * 100) / 100)
     [lat, lon] = [rr(latitude), rr(longitude)]
     id = "#{lat},#{lon}"
-    addr = @_aChache.get(id)
+    addr = @_aCache.get(id)
     unless addr
       addr = FM.Address.create {id,latitude,longitude}
-      @_aChache.set(id, addr)
+      @_aCache.set(id, addr)
     addr

   loadSelected: (complete_callback) ->


Fix reviewer_report_task spec

Fix reviewer_report_task spec

--- app/assets/javascripts/controllers/index_controller.js.coffee
+++ app/assets/javascripts/controllers/index_controller.js.coffee
@@ -12,7 +12,8 @@

   tasksByPaper:(->
     assignedTasks = @get('viewableAssignedTasks')
-    tasksByPaper = @get('submissions').map (litePaper) ->
-      tasks = assignedTasks.filterBy('litePaper', litePaper)
+    tasksByPaperId = _.groupBy(assignedTasks, (task) -> task.get('litePaper.id'))
+    _(tasksByPaperId).map (tasks, litePaperId) =>
+      litePaper = @store.getById('litePaper', litePaperId)
       { shortTitle: litePaper.get('shortTitle'), id: litePaper.get('id'), tasks: tasks }
   ).property('viewableAssignedTasks')


Tweak uploader to work with attachment-add-to

--- app/assets/javascripts/effective_assets/s3_uploader.js.coffee
+++ app/assets/javascripts/effective_assets/s3_uploader.js.coffee
@@ -209,8 +209,9 @@
       async: true
       success: (data) ->
         limit = asset_box.data('limit')
-
-        if limit == 10000 # Guard value for no limit
+        direction = asset_box.data('attachment-add-to')  # bottom or top.  bottom is default append behaviour
+
+        if limit == 10000 && direction != 'top' # Guard value for no limit.  There is no limit
           asset_box.find('.attachments').append($(data))
         else
           asset_box.find('.attachments').prepend($(data))


filter deleted users out of ClassroomView

--- app/views/courses/ClassroomView.coffee
+++ app/views/courses/ClassroomView.coffee
@@ -79,6 +79,7 @@
   onLoaded: ->
     @teacherMode = me.isAdmin() or @classroom.get('ownerID') is me.id
     userSessions = @sessions.groupBy('creator')
+    @users.remove(@users.where({ deleted: true }))
     for user in @users.models
       user.sessions = new CocoCollection(userSessions[user.id], { model: LevelSession })
       user.sessions.comparator = 'changed'


Adding correct precision

Adding correct precision

--- app/assets/javascripts/lib/views/html_table_chart_view.coffee
+++ app/assets/javascripts/lib/views/html_table_chart_view.coffee
@@ -55,7 +55,7 @@

       raw_value = if graph == 'future' then serie.future_value() else serie.present_value()
       raw_value = 0 unless _.isNumber(raw_value)
-      value     = @main_formatter(maxFrom: 5, maxPrecision: 5, scaledown: false)(raw_value)
+      value     = @main_formatter(maxFrom: 5, precision: default_decimals, maxPrecision: 5, scaledown: false)(raw_value)

       # some gqueries need a special treatment if they're 0
       on_zero = $(cell).data('on_zero')


Translate level wrapper code

Translate level wrapper code

--- app/views/play/level/tome/Spell.coffee
+++ app/views/play/level/tome/Spell.coffee
@@ -94,6 +94,7 @@
         fallingBack = true
     try
       @originalSource = _.template @originalSource, context
+      @wrapperCode = _.template @wrapperCode, context
     catch e
       console.error "Couldn't create example code template of", @originalSource, "\nwith context", context, "\nError:", e


fix messaging to all teachers/students in course

test plan:
  * from the new conversations ui, attempt to send a
    message to all teachers or all students in a course;
  * verify that the message sends.

Change-Id: I64d782b10fc932e24bf4df223c93dfd3117e6c32
Reviewed-on: https://gerrit.instructure.com/24637
Reviewed-by: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>
Product-Review: Matt Goodwin <7c47e89894930304a2cdfa1d2ccca1e1eddb893d@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>

--- app/coffeescripts/views/conversations/AutocompleteView.coffee
+++ app/coffeescripts/views/conversations/AutocompleteView.coffee
@@ -298,7 +298,7 @@
       return if @currentContext.id.match(/course_\d+_(group|section)/)

       tag =
-        id:       "#{@currentContext.id}_all"
+        id:       @currentContext.id
         name:     name
         everyone: true
         people: @currentContext.peopleCount


security: /Logout route should not log the user out when invoked with GET

This closes a security hole discovered by our user Nathan Malcolm

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -164,8 +164,8 @@
         res.send 200, authTemplate data

-app.get "/Logout", (req, res)->
-  res.clearCookie 'clientId'
+app.all "/Logout", (req, res)->
+  res.clearCookie 'clientId'  if req.method is 'POST'
   res.redirect 302, '/'

 app.get "/humans.txt", (req, res)->


Add redeemers.userID index to prepaids collection

--- server/prepaids/Prepaid.coffee
+++ server/prepaids/Prepaid.coffee
@@ -3,6 +3,7 @@
 PrepaidSchema = new mongoose.Schema {}, {strict: false, minimize: false,read:config.mongo.readpref}

 PrepaidSchema.index({code: 1}, { unique: true })
+PrepaidSchema.index({'redeemers.userID': 1})

 PrepaidSchema.statics.generateNewCode = (done) ->
   tryCode = ->


Change Facebook user albums request

Change Facebook user albums request

--- app/templates/app/scripts/coffee/packages/social/FBApi.coffee
+++ app/templates/app/scripts/coffee/packages/social/FBApi.coffee
@@ -160,33 +160,32 @@
       async = $.Deferred()
       @getFB()
         .done (FB)->
-          FB.api {
-            method: "fql.multiquery"
-            queries:
-              query1: "select object_id, owner, created, modified, aid,name,link,photo_count,cover_object_id from album where owner = me()"
-              query2: "SELECT pid,src,src_big FROM photo WHERE object_id  IN (SELECT cover_object_id FROM #query1)"
-          }, (response)->
+          FB.api "me/albums?fields=created_time,updated_time,id,photos.limit(1),count", (response)->
             parsed = new Array()
-            _.each response[0].fql_result_set, (value,index)->
-              thumb_src = response[1].fql_result_set[index].src_big or response[1].fql_result_set[index].src
+            _.each response.data, (value, index)->
+              thumb = _.chain value.photos.data[0].images
+              .filter (item)-> item.height < 500 and item.width < 500
+              .max (item)-> item.width
+              .value()
+              thumb_src = thumb.source
+              # debugger
               parsed.push
                 id: value.object_id
                 title: value.name
-                owner_id: value.owner
-                size: parseInt(value.photo_count)
-                thumb_id: response[0].fql_result_set[index].cover_object_id
+                # owner_id: value.owner
+                size: parseInt(value.count)
+                thumb_id: value.photos.data[0].id
                 thumb_src: thumb_src
-                created: new Date value.created
-                updated: new Date value.modified
+                created: new Date value.created_time
+                updated: new Date value.updated_time
                 #aid: value.aid
-                aid: value.object_id
-                album_id: value.object_id
+                aid: value.id
+                album_id: value.id
                 link: value.link
             async.resolve(parsed)
         .fail (err)->
           async.reject(err)
       async.promise()
-
     getPhotos:(album_id)->
       async = $.Deferred()
       @getFB()


Added functionality to decimal export for precision and size.

--- lib/ModelExport.coffee
+++ lib/ModelExport.coffee
@@ -128,7 +128,10 @@
               typeOutStr += if length then length else ''

               if field.Type.match('unsigned')
-                typeOutStr += '.UNSIGNED'
+                typeOutStr += '.UNSIGNED'
+            else if type.value is 'DECIMAL'
+              length = field.Type.match(/\(\d+,\d+\)/)
+              typeOutStr += if length then length else ''

         if type is 'coffee'
           if exportDefaultValue is true


Fixed crashing when filepath had a space in it

--- lib/atom-tracer.coffee
+++ lib/atom-tracer.coffee
@@ -131,7 +131,7 @@

     parseData = @parseScripts[fileType]
     #If found, please call the parse script to check if it's a real variable
-    command = parseData.runCommand.replace("[file]",parseData.parseScript) + " " + filePath + " " + varName + " " + line
+    command = parseData.runCommand.replace("[file]",parseData.parseScript) + " \"" + filePath + "\" " + varName + " " + line
     child_process.exec(command, (error, stdout, stderr) ->
       #If all good, send the scope info the inject
       if(error || stderr)
@@ -148,7 +148,7 @@
         return

       #Run the inject script if no error
-      command = parseData.runCommand.replace("[file]",parseData.injectScript) + " " + filePath + " " + varName + " " + btoa(JSON.stringify(scopeInfo))
+      command = parseData.runCommand.replace("[file]",parseData.injectScript) + " \"" + filePath + "\" " + varName + " " + btoa(JSON.stringify(scopeInfo))
       child_process.exec(command, (error, stdout, stderr ) ->
         if(error || stderr)
           atom.notifications.addError("Injection failed!", {dismissable:DismissableErrors,detail: error || stderr})


display variant images on admin product taxon page

this is coherent with the image logic on the products index page

--- backend/app/assets/javascripts/spree/backend/taxons.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxons.js.coffee
@@ -45,13 +45,18 @@
         id: e.val,
         token: Spree.api_key
       success: (data) ->
-        el.empty();
+        el.empty()
         if data.products.length == 0
           $('#taxon_products').html("
" + Spree.translations.no_results + "
")
         else
           for product in data.products
             if product.master.images[0] != undefined && product.master.images[0].small_url != undefined
               product.image = product.master.images[0].small_url
+            else
+              for variant in product.variants
+                if variant.images[0] != undefined && variant.images[0].small_url != undefined
+                  product.image = variant.images[0].small_url
+                  break
             el.append(productTemplate({ product: product }))

   $('#taxon_products').on "click", ".js-delete-product", (e) ->


Added fix for EmailTemplates, added fixes for other subfolder items

--- lib/build.coffee
+++ lib/build.coffee
@@ -193,6 +193,8 @@
           params.metaDataType = 'AuraDefinitionBundle'
         when 'documents'
           params.metaDataType = 'Document'
+        when 'email'
+          params.metaDataType = 'EmailTemplate'
         else
           params.metaDataType = null
     return params
@@ -212,7 +214,7 @@
         if(fileParams != null)
           if(fileParams.metaDataType != null)
             params = [fileParams.fileNameParsed, fileParams.metaDataType, fileParams.folderName[0]]
-            if fileParams.metaDataType == 'AuraDefinitionBundle' || fileParams.metaDataType == 'Document'
+            if fileParams.metaDataType == 'AuraDefinitionBundle' || fileParams.metaDataType == 'Document' || fileParams.metaDataType == 'EmailTemplate'
                 params.push(fileParams.folderName[1])
             if @child then @abort(=> @startNewBuild(optype+'-single-file', params, 'buildSingle')) else @startNewBuild(optype+'-single-file', params, 'buildSingle')
           else
@@ -236,12 +238,23 @@
           paths = atom.packages.getActivePackage('tree-view').mainModule.createView().selectedPaths()
           for key, path of paths
               fileParams = @getFileDetails(isWin, projectPath, path)
-              if (fileParams? && fileParams.metaDataType?)
+              if (fileParams? && fileParams.metaDataType? && fileParams.fileNameParsed?)
                   if !params[fileParams.metaDataType]?
                       params[fileParams.metaDataType] = {"fld" : fileParams.folderName[0],"items" : []}
-                  if fileParams.metaDataType == 'AuraDefinitionBundle' || fileParams.metaDataType == 'Document'
+                  if fileParams.metaDataType == 'AuraDefinitionBundle'
                       if fileParams.folderName[1] not in params[fileParams.metaDataType].items && fileParams.folderName[1].length > 0
                           params[fileParams.metaDataType].items.push(fileParams.folderName[1])
+                  else if fileParams.metaDataType == 'Document' || fileParams.metaDataType == 'EmailTemplate'
+                      subFld = null;
+                      for subItem in params[fileParams.metaDataType].items
+                          if subItem.subfld == fileParams.folderName[1] && fileParams.folderName[1].length > 0
+                              subFld = subItem;
+                              break;
+                      if (subFld == null && fileParams.folderName[1].length > 0)
+                          params[fileParams.metaDataType].items.push({"subfld":fileParams.folderName[1], "files":[]});
+                          subFld = params[fileParams.metaDataType].items[params[fileParams.metaDataType].items.length-1];
+                      if fileParams.fileNameParsed not in subFld.files
+                          subFld.files.push(fileParams.fileNameParsed);
                   else
                       if fileParams.fileNameParsed not in params[fileParams.metaDataType].items && fileParams.fileNameParsed.length > 0
                           params[fileParams.metaDataType].items.push(fileParams.fileNameParsed)


Add util folder to grunt watch task.

Add util folder to grunt watch task.

--- grunt/config/watch.coffee
+++ grunt/config/watch.coffee
@@ -13,6 +13,7 @@
       files: [
         '<%= src %>/*.coffee'
         '<%= src %>/views/*.coffee'
+        '<%= src %>/util/*.coffee'
       ]
       tasks: ['copy:app', 'publish']
     coffee2css:


Add extra snippets

Add extra snippets

--- snippets/snippets.cson
+++ snippets/snippets.cson
@@ -152,6 +152,14 @@
 		descriptionMoreURL: "http://linux.die.net/man/1/tbl"

+	# Debugging message
+	log:
+		prefix: "log"
+		body: ".tm ${1:Text}"
+		leftLabel: ".tm"
+		description: "Trace message to STDERR"
+
+
 ".source.pic":

 	# DFORMAT block
@@ -162,3 +170,22 @@
 	dformat:
 		prefix: "df"
 		body: ".begin dformat\nstyle $1\n.end"
+
+
+	# CHEM: Dextroamphetamine molecule
+	dextroamphetamine:
+		prefix: "dex"
+		body: """
+			.cstart
+				.ps 26
+				size 28
+			R1:
+				ring double 1,2 3,4 5,6
+				bond 60 from R1.V2
+				bond 120
+			A1:
+				front bond down ; CH3
+				bond 60 from A1 ; NH2
+				.ps
+			.cend
+			"""


tikz comment character setting

tikz comment character setting

--- settings/language-latex2e.cson
+++ settings/language-latex2e.cson
@@ -10,6 +10,10 @@
   'editor':
     'commentStart': '% '

+'.source.embedded.tikz':
+  'editor':
+    'commentStart': '% '
+
 '.text.tex.latex.log':
   'whitespace':
     'ensureSingleTrailingNewline': false


add test for canceling `r`

add test for canceling `r`

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -1214,6 +1214,13 @@
       commandModeInputKeydown('x')
       expect(editor.getText()).toBe 'x2\nx4\n\n'

+    it "does nothing when cancelled", ->
+      keydown('r')
+      expect(editorElement.classList.contains('operator-pending-mode')).toBe(true)
+      keydown('escape')
+      expect(editor.getText()).toBe '12\n34\n\n'
+      expect(editorElement.classList.contains('command-mode')).toBe(true)
+
     it "replaces a single character with a line break", ->
       keydown('r')
       editor.commandModeInputView.editor.trigger 'core:confirm'


Migrate more specs

Migrate more specs

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -1345,7 +1345,7 @@
           addDialog.cancel()
           root.click()
           root.removeClass('selected')
-          expect(treeView.selectedEntry()).toBeUndefined()
+          expect(treeView.selectedEntry()).toBeNull()
           treeView.trigger "tree-view:add-file"
           addDialog = atom.workspaceView.find(".tree-view-dialog").view()

@@ -1386,7 +1386,7 @@
               expect(atom.workspace.getActivePaneItem().getPath()).not.toBe newPath
               expect(treeView.find(".tree-view")).toMatchSelector(':focus')
               expect(atom.workspaceView.getActiveView().isFocused).toBeFalsy()
-              expect(dirView.find('.directory.selected:contains(new)').length).toBe(1)
+              expect(dirView.find('.directory.selected:contains(new)').length).toBe 1

         describe "when the path with a trailing '/' is changed and confirmed", ->
           describe "when no directory exists at the given path", ->
@@ -1479,7 +1479,7 @@
               runs ->
                 dirView = $(treeView.root.entries).find('.directory:contains(test-dir)')
                 dirView[0].expand()
-                expect(dirView.entries.children().length).toBe 0
+                expect($(dirView[0].entries).children().length).toBe 0

           describe "when the directories along the new path don't exist", ->
             it "creates the target directory before moving the file", ->
@@ -1593,9 +1593,9 @@
                 expect(fs.existsSync(newPath)).toBeTruthy()
                 expect(fs.existsSync(filePath)).toBeTruthy()
                 expect(copyDialog.parent()).not.toExist()
-                dirView = treeView.root.entries.find('.directory:contains(test-dir)')
-                dirView.expand()
-                expect(dirView.entries.children().length).toBe 1
+                dirView = $(treeView.root.entries).find('.directory:contains(test-dir)')
+                dirView[0].expand()
+                expect($(dirView[0].entries).children().length).toBe 1
                 expect(atom.workspace.getActiveEditor().getPath()).toBe(newPath)

           describe "when the directories along the new path don't exist", ->
@@ -1648,8 +1648,8 @@
         beforeEach ->
           dotFilePath = path.join(dirPath, ".dotfile")
           fs.writeFileSync(dotFilePath, "dot")
-          dirView.collapse()
-          dirView.expand()
+          dirView[0].collapse()
+          dirView[0].expand()
           dotFileView = treeView.find('.file:contains(.dotfile)')

           waitsForFileToOpen ->


Add camera count to road list.

Add camera count to road list.

--- views/roads.coffee
+++ views/roads.coffee
@@ -20,4 +20,6 @@
                             for direction in road.directions
                                 a 'data-theme': 'c',
                                   href: "/roads/#{road.roadId}/#{direction}",
-                                  -> "#{road.name} #{direction}"
+                                  ->
+                                    h1 "#{road.name} #{direction}"
+                                    p "#{road.cameraCount} Cameras"


Listen keydown event onload

Listen keydown event onload

--- views/tabarea.cjsx
+++ views/tabarea.cjsx
@@ -176,6 +176,7 @@
       @setState
         activeMainTab: 'mainView'
   componentDidMount: ->
+    @handleKeyDown()
     window.addEventListener 'game.start', @handleKeyDown
     window.addEventListener 'tabarea.reload', @forceUpdate
     window.addEventListener 'PluginManager.PLUGIN_RELOAD', (e) => @cachePluginList()


Made the API docs a bit more... API-docy.

Most of the meat that used to be in the API docs moved to the readme.

--- lib/graft.coffee
+++ lib/graft.coffee
@@ -38,6 +38,13 @@
         '.author': authorName,
         '.link': $('a').text(authorName).href('link-to-author')
       }) // as above, but searches the top-level object instead of a sub-object
+    .graft({
+      '.author': author.name,
+      '.link[href]': linkFor(author) // sets the href attribute
+      '.link[class+] ': author.category // adds the category to the class attribute
+      '.other-link[target] ': 'EXTERNAL' // targets other-links with a target attribute (note space at the end)
+      '.other-link[target=_blank]': 'new window' // targets other-links with a target attribute _blank
+    })

 If the graft call is invoked on an element with class `template', it
 will clone that element and remove the template class, add the resulting
@@ -45,7 +52,6 @@
 on the other hand, the graft call is invoked on an element without that class,
 it will operate directly on the element and return it.

--------------
 Collections
 -------------

@@ -61,46 +67,6 @@
           '.link': $('a').text(author.name).href(linkFor(author)),
         }
       })
-
-The mechanism is straightforward: if you pass an array as the second parameter,
-each object in the array is passed to graft in turn, each with a new clone of
-the matched element(s). Then, these elements are gathred up and put into the
-DOM instead of the matched element. This means you provide an array of
-functions, if you prefer, or an object, as above, or a string, etc.
-
-The goodness is good, no?
-
---------------------------
-Easily setting attributes
---------------------------
-
-Graft adds just a little bit of bam-bam-bam to selectors:
-
-  $('li.template')
-    .graft({
-      '.author': author.name,
-      '.link[href]': linkFor(author)
-    })
-
-This lets us set the href with zero effort. The downside is, the above selector
-also means `elements with class link that have an attribute href'. I've found
-that is fairly rarely used, but if you still want to use it, just put a space
-at the end of the selector:
-
-  $('li.template')
-    .graft({
-      '.author': author.name,
-      '.link[href] ': function($elt) { /* do stuff with the link */ }
-    })
-
-We're not *quite* done yet, however. There is also a dash of goodness for
-adding to an attribute (particularly handy for classes):
-
-  $('li.template')
-    .graft({
-      '.author': author.name,
-      '.link[class+] ': author.category
-    })

 ###
 addGraft = (jQuery) ->


Better k-for-kill.

Better k-for-kill.

--- lib/models/repo.coffee
+++ lib/models/repo.coffee
@@ -51,7 +51,14 @@
     @git.git "reset HEAD #{@current_file().filename()}", @error_callback

   kill: ->
-    @git.git "checkout #{@current_file().filename()}", @error_callback
+    if @current_file().unstaged()
+      @git.git "checkout #{@current_file().filename()}", @error_callback
+    else if @current_file().untracked()
+      @git.add @current_file().filename(), =>
+        @git.git "rm -f #{@current_file().filename()}", @error_callback
+    else if @current_file().staged()
+      @git.git "reset HEAD #{@current_file().filename()}", =>
+        @git.git "checkout #{@current_file().filename()}", @error_callback

   open: ->
     filename = @current_file().filename()


Update index.coffee

Update index.coffee

--- lib/index.coffee
+++ lib/index.coffee
@@ -51,7 +51,7 @@
 	return if schema._useFakeRemove is true
 	schema.add {deletedAt: Date}
 #	schema.path('deletedAt').index {sparse: true}
-	schema.path('deletedAt').index {sparse: false}
+	schema.path('deletedAt').index(true)

 	schema.plugin queryHook,


handle ordering error by doing fail()

handle ordering error by doing fail()

--- lib/index.coffee
+++ lib/index.coffee
@@ -11,9 +11,27 @@

 # order the array before a chain run executes
 ensureOrdered = (event) ->
+
   unless event.chain.__isOrdered is true
-    order event.chain.array
-    event.chain.__isOrdered = true
+    result = order event.chain.array
+
+    if result?.error?
+      #event.chain.emit 'order error', result
+      event.chain.__orderError = result
+
+    else
+      event.chain.array = result.array
+      event.chain.__isOrdered = true
+
+# when the chain runs, first, check if there was an error during order()
+checkOrderedError = (control) ->
+  # get error from chain
+  error = control._chain.__orderError
+  # if it exists, then we fail()
+  if error? then control.fail 'Unable to order the chain', error
+
+# ensure if ordering occurs then this is first...?
+checkOrderedError.options = id:'CheckOrderedError', before:['*']

 class Cio
@@ -24,9 +42,10 @@

   # add the listeners to a chain which provide ordering
   _addOrdering: (chain) ->
-    chain.on 'add', markChanged
-    chain.on 'remove', markChanged
-    chain.on 'start', ensureOrdered
+    chain.on 'add', markChanged      # only do ordering when we add something
+    chain.on 'remove', markChanged   # or if we remove something
+    chain.on 'start', ensureOrdered  # do the ordering once before starting
+    chain.add checkOrderedError      # if there's an error then fail()
     return chain

@@ -96,14 +115,14 @@
   # build a new client socket, return the socket or the build chain failure
   client: (options) ->
     result = @_run @getClientChain(), options
-    if result.failed? then result
+    if result.failed? or result.error? then result
     else result.context.client

   # build a new server socket, return the socket or the build chain failure
   server: (options) ->
     result = @_run @getServerChain(), options
-    if result.failed? then result
+    if result.failed? or result.error? then result
     else result.context.server


Require helpers first

Require helpers first

--- lib/index.coffee
+++ lib/index.coffee
@@ -8,5 +8,8 @@

 projectPaths = window.location.hash.substr(1).split(':')
 for projectPath in projectPaths
-  specPaths = glob.sync(path.join(projectPath, 'spec', '**', '*-spec.@(js|coffee)'))
-  require(specPath) for specPath in specPaths
+  for helperPath in glob.sync(path.join(projectPath, 'spec', '**', '*-helper?(s).@(js|coffee)'))
+    require(helperPath)
+
+  for specPath in glob.sync(path.join(projectPath, 'spec', '**', '*-spec.@(js|coffee)'))
+    require(specPath)


add missing error checks, return server/client objects

--- lib/index.coffee
+++ lib/index.coffee
@@ -6,6 +6,10 @@
 class Cio

   constructor: (@_options) ->
+
+    # TODO:
+    #   check for error results from all of these.
+    #   set it on _error for the builder function to find and return

     @_clientChain = buildChain()
     @_serverChain = buildChain()
@@ -42,8 +46,18 @@

     if @_options?.noRelisten then @_serverChain.disable 'cio/relistener'

-  client: (options) -> @_run @_clientChain, options
-  server: (options) -> @_run @_serverChain, options
+    return
+
+
+  client: (options) ->
+    result = @_run @_clientChain, options
+    if result?.failed? then result
+    else result?.context?.client
+
+  server: (options) ->
+    result = @_run @_serverChain, options
+    if result?.failed? then result
+    else result?.context?.server

   _run: (chain, options) ->
     # # build options for chain.run(), choose the options which exists
@@ -95,6 +109,8 @@
 module.exports = (options) ->

   # if the certs are provided then try to read them now
-  readCerts options if options?
+  if options?
+    result = readCerts options
+    if result?.error? then return result

   new Cio options


fix bug where paste does not overwrite

fix bug where paste does not overwrite

--- src/modules/paste-manager.coffee
+++ src/modules/paste-manager.coffee
@@ -31,6 +31,7 @@
       # Need to remove trailing newline so paste is inline, losing format is expected and observed in Word
       delta.compose(new Delta().retain(lengthAdded).delete(1))
       delta.ops.unshift({ retain: range.start }) if range.start > 0
+      delta.delete(range.end - range.start)
       @quill.updateContents(delta, 'user')
       @quill.setSelection(range.start + lengthAdded, range.start + lengthAdded)
       [line, offset] = @quill.editor.doc.findLineAt(range.start + lengthAdded)


support displayname and error handling

support displayname and error handling

--- lib/remote-atom.coffee
+++ lib/remote-atom.coffee
@@ -15,13 +15,15 @@

     constructor: (socket) ->
         @socket = socket
-        @remoteAddress = socket.remoteAddress
+        @online = true
         socket.on "readable", =>
             chunk = socket.read()
             @parse_chunk(chunk)
+        socket.on "close", =>
+            @online = false

     make_tempfile: ()->
-        @tempfile = path.join(os.tmpdir(), randomstring(10), @token)
+        @tempfile = path.join(os.tmpdir(), randomstring(10), @basename)
         console.log "[ratom] create #{@tempfile}"
         dirname = path.dirname(@tempfile)
         mkdirp.sync(dirname)
@@ -53,10 +55,14 @@
                 switch m[1]
                     when "token"
                         @token = m[2]
-                        @make_tempfile()
                     when "data"
                         @datasize = parseInt(m[2],10)
                         @should_parse_data = true
+                    when "display-name"
+                        @displayname = m[2]
+                        @remoteAddress = @displayname.split(":")[0]
+                        @basename = @displayname.split(":")[1]
+                        @make_tempfile()

     open_in_atom: ->
@@ -78,6 +84,10 @@
         @socket.write cmd+"\n"

     save: ->
+        if not @online
+            console.log "[ratom] Error saving #{path.basename @tempfile} to #{@remoteAddress}"
+            message.display "Error saving #{path.basename @tempfile} to #{@remoteAddress}", 2000
+            return
         console.log "[ratom] saving #{path.basename @tempfile} to #{@remoteAddress}"
         message.display "Saving #{path.basename @tempfile} to #{@remoteAddress}", 2000
         @send "save"
@@ -88,6 +98,7 @@
         @send ""

     close: ->
+        @online = false
         @send "close"
         @send ""
         @socket.end()


Don't check for identical values in _set

--- lib/pages.coffee
+++ lib/pages.coffee
@@ -289,9 +289,9 @@
       # Set the parameter on this instance (client)

       oldV = @get(k, opts?.cid)
-      if !@valuesEqual(oldV, v)
+      if Meteor.isClient
         ch = 1
-        @[k] = v if Meteor.isClient
+        @[k] = v

       if Meteor.isClient and !opts.init
         # Change the setting for the corresponding instance on the server


Avoid non-standard String.prototype.trimRight

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/TrimRight

--- src/rules/max_line_length.coffee
+++ src/rules/max_line_length.coffee
@@ -31,7 +31,7 @@
         max = lineApi.config[@rule.name]?.value
         limitComments = lineApi.config[@rule.name]?.limitComments

-        lineLength = line.trimRight().length
+        lineLength = line.replace(/\s+$/, '').length
         if lineApi.isLiterate() and regexes.literateComment.test(line)
             lineLength -= 2


Updated comment on  to include info about new argument which prevents the method from throwing an Error if SessionIndex does not exist.

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -309,7 +309,9 @@
   nameid[0].firstChild?.data

 # Takes in an xml @dom of an object containing a SAML Assertion and returns the SessionIndex. It will throw an error
-# if there is no SessionIndex, no Assertion, or the Assertion does not appear to be valid.
+# if there is no SessionIndex, no Assertion, or the Assertion does not appear to be valid. Optionally you can pass a
+# second argument `false` making SessionIndex optional. Doing so returns `null` instead of throwing an Error if the
+# SessionIndex attribute does not exist.
 get_session_index = (dom, index_required=true) ->
   assertion = dom.getElementsByTagNameNS(XMLNS.SAML, 'Assertion')
   throw new Error("Expected 1 Assertion; found #{assertion.length}") unless assertion.length is 1


Starts to clean up this file

Starts to clean up this file

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -17,18 +17,10 @@
   ADMIN_SLUGS
 } = require '../config'

-_ = require 'underscore'
 express = require 'express'
 Backbone = require 'backbone'
 sharify = require 'sharify'
-arenaPassport = require './passport'
 bodyParser = require 'body-parser'
-localsMiddleware = require './middleware/locals'
-ensureSSL = require './middleware/ensure_ssl'
-viewMode = require './middleware/view_mode'
-checkSession = require './middleware/check_session'
-isInverted = require '../components/night_mode/middleware'
-splitTestMiddleware = require '../components/split_test/middleware'
 cookieParser = require 'cookie-parser'
 session = require 'cookie-session'
 path = require 'path'
@@ -36,134 +28,121 @@
 multipart = require 'connect-multiparty'
 artsyError = require 'artsy-error-handler'
 bucketAssets = require 'bucket-assets'
-cache = require './cache'
 favicon = require 'serve-favicon'
 blocker = require 'express-spam-referral-blocker'

-# Inject some constant data into sharify
-sharify.data =
-  NODE_ENV: NODE_ENV
-  API_URL: API_URL
-  APP_URL: APP_URL
-  JS_EXT: (if ("production" is NODE_ENV ) then ".min.js.cgz" else ".js")
-  CSS_EXT: (if ("production" is NODE_ENV) then ".min.css.cgz" else ".css")
-  ASSET_PATH: ASSET_PATH
-  IMAGE_PATH: IMAGE_PATH
-  PUSHER_KEY: PUSHER_KEY
-  GOOGLE_ANALYTICS_ID: GOOGLE_ANALYTICS_ID
-  IMAGE_PROXY_URL: IMAGE_PROXY_URL
-  STRIPE_PUBLISHABLE_KEY: STRIPE_PUBLISHABLE_KEY
-  BLOG_URL: BLOG_URL
-  ADMIN_SLUGS: ADMIN_SLUGS
+localsMiddleware = require './middleware/locals'
+ensureSSL = require './middleware/ensure_ssl'
+viewMode = require './middleware/view_mode'
+checkSession = require './middleware/check_session'
+isInverted = require '../components/night_mode/middleware'
+splitTestMiddleware = require '../components/split_test/middleware'
+cache = require './cache'
+arenaPassport = require './passport'

-# current user management
+sharify.data = {
+  NODE_ENV
+  API_URL
+  APP_URL
+  ASSET_PATH
+  IMAGE_PATH
+  PUSHER_KEY
+  GOOGLE_ANALYTICS_ID
+  IMAGE_PROXY_URL
+  STRIPE_PUBLISHABLE_KEY
+  BLOG_URL
+  ADMIN_SLUGS
+  JS_EXT: if 'production' is NODE_ENV then '.min.js.cgz' else '.js'
+  CSS_EXT: if 'production' is NODE_ENV then '.min.css.cgz' else '.css'
+}
+
 CurrentUser = require '../models/current_user'

 module.exports = (app) ->
-
-  Backbone.sync = require "backbone-super-sync"
+  Backbone.sync = require 'backbone-super-sync'
   Backbone.sync.cacheClient = cache.client

-  # Mount sharify
   app.use sharify

-  # Referral spam blocker
-  blocker.addToReferrers ['tkpassword.com', 'lifehacĸer.com']
+  blocker.addToReferrers [
+    'tkpassword.com'
+    'lifehacĸer.com'
+  ]
+
   app.use blocker.send404

-  # Development only
-  if "development" is NODE_ENV
-    nib = require "nib"
-    stylus = require "stylus"
-    rupture = require 'rupture'
-    # Compile assets on request in development
-    app.use require("stylus").middleware
-      src: path.resolve(__dirname, "../")
-      dest: path.resolve(__dirname, "../public")
-      compile: (str, path) ->
-        stylus(str)
-        .set('filename', path)
-        .use(rupture())
-        .use(require("nib")())
+  switch NODE_ENV
+    when 'development'
+      app
+        .use (stylus = require 'stylus').middleware
+          src: path.resolve(__dirname, '../')
+          dest: path.resolve(__dirname, '../public')
+          compile: (str, path) ->
+            stylus(str)
+              .set('filename', path)
+              .use(require('rupture')())
+              .use(require('nib')())

-    app.use require("browserify-dev-middleware")
-      src: path.resolve(__dirname, "../")
-      transforms: [require("jadeify"), require('caching-coffeeify')]
-      debug: true
+        .use require('browserify-dev-middleware')
+          src: path.resolve(__dirname, '../')
+          transforms: [require('jadeify'), require('caching-coffeeify')]
+          debug: true

-  # Test only
-  if "test" is NODE_ENV
-    # Mount fake API server
-    app.use "/__api", require("../test/helpers/integration.coffee").api
+    when 'test' # lol
+      app.use '/__api', require('../test/helpers/integration.coffee').api

-  # Assets
-  app.use bucketAssets()
+  app
+    .use bucketAssets()
+    .use express.static(path.resolve __dirname, '../public')
+    .use favicon(path.resolve __dirname, '../public/images/favicon.ico')
+    .use logger('dev')
+    .use bodyParser.json()
+    .use multipart()
+    .use bodyParser.urlencoded(extended: true)
+    .use cookieParser()
+    .use session
+      secret: SESSION_SECRET
+      domain: COOKIE_DOMAIN
+      key: SESSION_COOKIE_KEY
+      maxAge: SESSION_COOKIE_MAX_AGE
+    .use artsyError.helpers
+    .use arenaPassport({ CurrentUser })
+    .use checkSession
+    .use localsMiddleware
+    .use splitTestMiddleware
+    .use ensureSSL
+    .use isInverted
+    .use viewMode

-  # More general middleware
-  app.use express.static(path.resolve __dirname, "../public")
-  app.use favicon(path.resolve __dirname, '../public/images/favicon.ico')
+  app
+    # Normal routing: In order
+    .use require '../apps/feed'
+    .use require '../apps/home'
+    .use require '../apps/blog'
+    .use require '../apps/authentication'
+    .use require '../apps/getting_started'
+    .use require '../apps/tools'
+    .use require '../apps/util'
+    .use require '../apps/about'
+    .use require '../apps/search'
+    .use require '../apps/manage'
+    .use require '../apps/share'
+    .use require '../apps/marklet'
+    .use require '../apps/import'
+    .use require '../apps/settings'
+    .use require '../apps/onboarding'
+    .use require '../apps/ui'
+    # Dynamic routing: In order
+    .use require '../apps/user'
+    .use require '../apps/block'
+    .use require '../apps/channel'
+    .use require '../apps/rss'

-  # session management
-  app.use logger('dev')
-  app.use bodyParser.json()
-  app.use multipart()
-  app.use bodyParser.urlencoded(extended: true)
-  app.use cookieParser()
-  app.use session
-    secret: SESSION_SECRET
-    domain: COOKIE_DOMAIN
-    key: SESSION_COOKIE_KEY
-    maxage: SESSION_COOKIE_MAX_AGE
+  switch NODE_ENV
+    when 'development'
+      app.use require '../apps/statuses'

-  app.use artsyError.helpers
-  app.use arenaPassport({ CurrentUser })
-  app.use checkSession
-  app.use localsMiddleware
-  app.use splitTestMiddleware
-  app.use ensureSSL
-  app.use isInverted
-  app.use viewMode
-
-  app.get "/robots.txt", (req, res) ->
-    res.set 'Content-Type', 'text/plain'
-    robotsText = """
-      User-agent: *
-    """
-    res.send switch NODE_ENV
-      when 'production'
-        robotsText
-      else
-        "User-agent: *\nNoindex: /"
-
-  # Mount apps
-  app.use require '../apps/feed'
-  app.use require '../apps/home'
-  app.use require '../apps/blog'
-  app.use require '../apps/authentication'
-  app.use require '../apps/getting_started'
-  app.use require '../apps/tools'
-  app.use require '../apps/util'
-  app.use require '../apps/about'
-  app.use require '../apps/search'
-  app.use require '../apps/manage'
-  app.use require '../apps/share'
-  app.use require '../apps/marklet'
-  app.use require '../apps/import'
-  app.use require '../apps/settings'
-  app.use require '../apps/onboarding'
-  app.use require '../apps/ui'
-
-  # Dev only routes
-  if 'development' is NODE_ENV
-    app.use require '../apps/statuses'
-
-  # Apps that use dynamic routes
-  app.use require '../apps/user'
-  app.use require '../apps/block'
-  app.use require '../apps/channel'
-  app.use require '../apps/rss'
-
-  # Finally 404 and error handling middleware when the request wasn't handled
-  # successfully by anything above.
+  # Drop down to error handling middleware if nothing else catches it
+  # TODO: Kill this/replace with something that's not a Node module
   artsyError.handlers app,
     template: path.resolve(__dirname, '../components/layout/templates/error.jade')


Better Redis keys naming.

Better Redis keys naming.

--- lib/state-redis.coffee
+++ lib/state-redis.coffee
@@ -9,6 +9,8 @@
 # @private
 asyncLimit = 16

+# @private
+namespace = 'chatservice'

 # @private
 initState = (redis, state, values, cb) ->
@@ -24,7 +26,11 @@

   # @private
   makeDBListName : (listName) ->
-    "#{@prefix}_#{listName}_#{@name}"
+    "#{namespace}:#{@prefix}:#{listName}:#{@name}"
+
+  # @private
+  makeDBHashName : (hashName) ->
+    "#{namespace}:#{@prefix}:#{hashName}"

   # @private
   checkList : (listName, cb) ->
@@ -58,11 +64,13 @@
   # @private
   whitelistOnlySet : (mode, cb) ->
     whitelistOnly = if mode then true else false
-    @redis.hset "#{@prefix}_whitelistmodes", @name, whitelistOnly, @withTE cb
+    @redis.hset @makeDBHashName('whitelistmodes'), @name, whitelistOnly
+    , @withTE cb

   # @private
   whitelistOnlyGet : (cb) ->
-    @redis.hget "#{@prefix}_whitelistmodes", @name, @withTE cb, (data) ->
+    @redis.hget @makeDBHashName('whitelistmodes'), @name, @withTE cb
+    , (data) ->
       cb null, JSON.parse data

@@ -99,19 +107,19 @@
           @redis.lpush @makeDBListName('history'), msgs, fn
       , (fn) =>
         unless whitelistOnly then return fn()
-        @redis.hset "#{@prefix}_whitelistmodes", @name, whitelistOnly, fn
+        @redis.hset @makeDBHashName('whitelistmodes'), @name, whitelistOnly, fn
       , (fn) =>
         unless owner then return fn()
-        @redis.hset "#{@prefix}_owners", @name, owner, fn
+        @redis.hset @makeDBHashName('owners'), @name, owner, fn
     ] , @withTE cb

   # @private
   ownerGet : (cb) ->
-    @redis.hget "#{@prefix}_owners", @name, @withTE cb
+    @redis.hget @makeDBHashName('owners'), @name, @withTE cb

   # @private
   ownerSet : (owner, cb) ->
-    @redis.hset "#{@prefix}_owners", @name, owner, @withTE cb
+    @redis.hset @makeDBHashName('owners'), @name, owner, @withTE cb

   # @private
   messageAdd : (msg, cb) ->
@@ -160,7 +168,7 @@
         initState @redis, @makeDBListName('blacklist'), blacklist, fn
       , (fn) =>
         unless whitelistOnly then return fn()
-        @redis.hset "#{@prefix}_whitelistmodes", @name, whitelistOnly, fn
+        @redis.hset @makeDBHashName('whitelistmodes'), @name, whitelistOnly, fn
     ] , @withTE cb

@@ -170,6 +178,7 @@

   # @private
   constructor : (@server, @username) ->
+    @name = @username
     @prefix = 'user'
     @redis = @server.chatState.redis
     @errorBuilder = @server.errorBuilder
@@ -177,7 +186,7 @@

   # @private
   makeDBListName : (listName) ->
-    "#{@prefix}_#{listName}_#{@username}"
+    "#{namespace}:#{@prefix}:#{listName}:#{@name}"

   # @private
   socketAdd : (id, cb) ->
@@ -218,8 +227,12 @@
     @directMessagingState = DirectMessagingStateRedis

   # @private
+  makeDBHashName : (hashName) ->
+    "#{namespace}:#{hashName}"
+
+  # @private
   getRoom : (name, cb) ->
-    @redis.sismember 'rooms', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('rooms'), name, @withTE cb, (data) =>
       unless data
         error = @errorBuilder.makeError 'noRoom', name
         return cb error
@@ -229,25 +242,25 @@
   # @private
   addRoom : (room, cb) ->
     name = room.name
-    @redis.sismember 'rooms', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('rooms'), name, @withTE cb, (data) =>
       if data
         return cb @errorBuilder.makeError 'roomExists', name
-      @redis.sadd 'rooms', name, @withTE cb
+      @redis.sadd @makeDBHashName('rooms'), name, @withTE cb

   # @private
   removeRoom : (name, cb) ->
-    @redis.sismember 'rooms', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('rooms'), name, @withTE cb, (data) =>
       unless data
         return cb @errorBuilder.makeError 'noRoom', name
-      @redis.srem 'rooms', name, @withTE cb
+      @redis.srem @makeDBHashName('rooms'), name, @withTE cb

   # @private
   listRooms : (cb) ->
-    @redis.smembers 'rooms', @withTE cb
+    @redis.smembers @makeDBHashName('rooms'), @withTE cb

   # @private
   getOnlineUser : (name, cb) ->
-    @redis.sismember 'users_online', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('usersOnline'), name, @withTE cb, (data) =>
       unless data
         return cb @errorBuilder.makeError 'noUserOnline', name
       user = new @server.User name
@@ -255,44 +268,44 @@

   # @private
   getUser : (name, cb) ->
-    @redis.sismember 'users_online', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('usersOnline'), name, @withTE cb, (data) =>
       if data then return cb null, user, true
-      @redis.sismember 'users', name, @withTE cb, (data) =>
+      @redis.sismember @makeDBHashName('users'), name, @withTE cb, (data) =>
         unless data
           return cb @errorBuilder.makeError 'noUser', name
         cb null, user, false

   # @private
   loginUser : (name, socket, cb) ->
-    @redis.sismember 'users_online', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('usersOnline'), name, @withTE cb, (data) =>
       if data
         user = new @server.User name
         user.registerSocket socket, (error) -> cb error, user
       else
-        @redis.sismember 'users', name, @withTE cb, (data) =>
+        @redis.sismember @makeDBHashName('users'), name, @withTE cb, (data) =>
           user = new @server.User name
           async.parallel [
             (fn) =>
-              @redis.sadd 'users', name, fn
+              @redis.sadd @makeDBHashName('users'), name, fn
             (fn) =>
-              @redis.sadd 'users_online', name, fn
+              @redis.sadd @makeDBHashName('usersOnline'), name, fn
           ], @withTE cb, ->
             user.registerSocket socket, cb

   # @private
   logoutUser : (name, cb) ->
-    @redis.sismember 'users_online', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('usersOnline'), name, @withTE cb, (data) =>
       unless data
         return cb @errorBuilder.makeError 'noUserOnline', name
-      @redis.srem 'users_online', name, @withTE cb
+      @redis.srem @makeDBHashName('usersOnline'), name, @withTE cb

   # @private
   addUser : (name, cb = (->), state = null) ->
-    @redis.sismember 'users', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('users'), name, @withTE cb, (data) =>
       if data
         return cb @errorBuilder.makeError 'userExists', name
       user = new @server.User name
-      @redis.sadd 'users', name, @withTE cb, ->
+      @redis.sadd @makeDBHashName('users'), name, @withTE cb, ->
         if state
           user.initState state, cb
         else
@@ -300,16 +313,16 @@

   # @private
   removeUser : (name, cb = ->) ->
-    @redis.sismember 'users_online', name, @withTE cb, (data) =>
+    @redis.sismember @makeDBHashName('usersOnline'), name, @withTE cb, (data) =>
       fn = =>
-        @redis.sismember 'users', name, @withTE cb, (data) =>
+        @redis.sismember @makeDBHashName('users'), name, @withTE cb, (data) =>
           unless data
             return cb @errorBuilder.makeError 'noUser', name
           async.parallel [
               (fn) =>
-                @redis.srem 'users', name, fn
+                @redis.srem @makeDBHashName('users'), name, fn
               (fn) =>
-                @redis.srem 'users_online', name, fn
+                @redis.srem @makeDBHashName('usersOnline'), name, fn
           ], @withTE cb
       if data
         user = new @server.User name


get stack back

get stack back

--- lib/unit_runner.coffee
+++ lib/unit_runner.coffee
@@ -49,7 +49,7 @@
       error = e

     promise.fail (err) ->
-      err.stack = e.stack
+      err.stack += "\n" + e.stack
       throw err

     promise.finally @taskDone


support multiple arg on debug util fund

support multiple arg on debug util fund

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -34,12 +34,12 @@
   for key, value of module
     klass::[key] = value

-debug = (message) ->
+debug = (message...) ->
   return unless settings.get('debug')
   message += "\n"
   switch settings.get('debugOutput')
     when 'console'
-      console.log message
+      console.log message...
     when 'file'
       filePath = fs.normalize settings.get('debugOutputFilePath')
       if fs.existsSync(filePath)


Replace any white space with new line check


Cleanup columnize function

Cleanup columnize function

--- app/lib/columnize.coffee
+++ app/lib/columnize.coffee
@@ -1,14 +1,8 @@
 columnize = (array, columns) ->
-  untouchedLength = array.length
-
-  sorted = []
-
-  while array.length > 0
-    sorted.push array.shift()
-    per = Math.floor array.length / columns
-    array = array.splice(per).concat array
-    console.log per, sorted, array
-
-  sorted
+  until array.length is 0
+    item = array.shift()
+    perColumn = Math.floor array.length / columns
+    array = array.splice(perColumn).concat array
+    item

 module.exports = columnize


Update es-419.coffee

Update es-419.coffee

Finished translation?!!! :D

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -905,7 +905,7 @@
     question1: "¿Cuál es la diferencia entre estos cursos y sus juegos de un solo jugador?"
     answer: "R:" # Like in 'Answer'
     answer1: "El juego de un solo jugador está diseñado para individuos, mientras que los cursos están diseñados para grupos."
-#    answer2: "El juego de un solo jugador tiene items, gemas, selección de héroe, aumento de niveles, y compras dentro de la aplicación. Los cursos tienen características de administración de salón y streamlined student-focused level pacing."
+    answer2: "El juego de un solo jugador tiene items, gemas, selección de héroe, aumento de niveles, y compras dentro de la aplicación. Los cursos tienen características de administración de salón y niveles ágiles enfocados al estudiante."
     teachers_click: "Maestros Click Aquí"
     students_click: "Estudiantes Click Aquí"
     courses_on_coco: "Curso en CodeCombat"
@@ -1163,7 +1163,7 @@
     tournament_blurb: "Escribe código, recolecta oro, arma ejercitos, aplasta adversarios, gana premios, y asciende en tu carrera por $40,000 en el Torneo Codicia! Echa un vistazo a los detalles"
     tournament_blurb_criss_cross: "Gana apuestas, construye caminos, burla tus oponentes, agarra gemas, y asciende tu perfil en nuestro torneo Cruzado! Echa un vistazo a los detalles"
     tournament_blurb_zero_sum: "Suelta tus habilidades de código en recolección de oro y tácticas de batalla en este partido espejo alpino entre el hechicero rojo y el hechicero azul. El torneo comenzó el Viernes, 27 de Marzo y se extenderá hasta el Lunes, 6 de Abril a las 5PM PDT. Compite por la diversión y la gloria ! Echa un vistazo a los detalles:"
-#    tournament_blurb_ace_of_coders: "¡Battle it out en el glaciar congelado en este encuentro domination-style mirror! The tournament began on Wednesday, 16 de Septiembre y will run until Wednesday, 14 de October a las 5PM PDT. Check out los detalles"
+    tournament_blurb_ace_of_coders: "¡Lucha en el glaciar congelado en este encuentro con estilo dominación en espejo! El torneo comienza el Miércoles 16 de Septiembre, y se ejecutará hasta el Miércoles 14 de Octubre hasta las 5PM PDT. Revisa los detalles"
     tournament_blurb_blog: "en nuestro blog"
     rules: "Reglas"
     winners: "Ganadores"


modified leaderboard rank logic

modified leaderboard rank logic

--- app/models/person.coffee
+++ app/models/person.coffee
@@ -28,7 +28,10 @@
     if losses < 1
       percentage = (wins * (wins + 1)) + 1
     else
-      percentage = (wins / losses) * ( @get('totalGames') )
+      if wins < 1
+        percentage = -1 * losses
+      else
+        percentage = (wins / losses) * ( @get('totalGames') )
     Math.round(percentage * 100) / 100
   ).property('wins', 'losses', 'totalGames')
   totalGames: (->


update graphql mutation snippet

update graphql mutation snippet

--- snippets/graphql.cson
+++ snippets/graphql.cson
@@ -23,14 +23,15 @@
   'Mutation function':
     'prefix': 'mutation'
     'body': """
-      @autobind
+      @a
       async $1 () {
         this.setState({loading: true})
         try {
           await this.props.$2({})
+          this.props.showMessage('Success')
         } catch (error) {
           console.log('Error:', error)
-          this.props.showMessage(error.message)
+          this.props.showMessage(error.message, {level: 'error'})
         }
         this.setState({loading: false})
       }


Added support for saving overridden titles

--- scripts/models/content/book.coffee
+++ scripts/models/content/book.coffee
@@ -42,8 +42,20 @@
     # Currently, a Book does not support full Tree editing
     # so generate a naive list.
     toHTML: () ->
-      listItems = @getChildren().map (child) ->
-        return "
#{child.get('title')}
"
+      children = @getChildren()
+
+      overriddenTitles = children.titles # Overridden titles are stored in an Object dangling off the Collection
+
+      listItems = children.map (child) ->
+        # If this node has an overridden title, look it up and use it here
+        if child.isNew()
+          console.warn 'BUG: Saving collections with new unsaved modules is not supported'
+        else
+          overriddenTitle = _.find overriddenTitles, (item) -> item.id == child.id and item.title
+          if overriddenTitle
+            return "
#{overriddenTitle.title}
"
+          else
+            return "
#{child.get('title')}
"

       return """


course: make backup files, as in the instructions

--- src/smc-webapp/course/actions.coffee
+++ src/smc-webapp/course/actions.coffee
@@ -876,6 +876,7 @@
                         target_project_id : store.get('course_project_id')
                         target_path       : target_path
                         overwrite_newer   : true
+                        backup            : true
                         delete_missing    : false
                         exclude_history   : false
                         cb                : cb
@@ -945,8 +946,9 @@
                         src_path          : src_path
                         target_project_id : student_project_id
                         target_path       : assignment.get('graded_path')
-                        overwrite_newer   : assignment.get('overwrite_newer')
-                        delete_missing    : assignment.get('delete_missing')
+                        overwrite_newer   : true
+                        backup            : true
+                        delete_missing    : false
                         exclude_history   : true
                         cb                : cb
                 (cb) =>


renamed card

renamed card

--- www/src/coffee/card.coffee
+++ www/src/coffee/card.coffee
@@ -1,20 +0,0 @@
-class MainView
-  constructor: ->
-    @layout = new Famous.Views.HeaderFooterLayout
-      headerSize: 100
-      footerSize: 50
-
-    # @createHeader()
-    @createContent()
-    # @createFooter()
-
-  createHeader: ->
-    # surface = new Famous.Surface()
-    #
-    # @layout.add(new Famous.Surface())
-
-  createContent: ->
-    content = new Famous.Surface
-      content: 'yeaaa'
-
-    @layout.content.add(content)


Closes #58 admin users can read all settings

--- server/authentication/index.coffee
+++ server/authentication/index.coffee
@@ -93,14 +93,30 @@
     exports._systemCheck req, res, next

   publicReadAction = (req, res, next) ->
-    if req.route.method is 'get'
+
       systemCheck req, res, () ->
-        if not req.query?
-          req.query = {}
-        req.query.acl = 'public-read'
-        next()
-    else
-      res.json 401, {msg: 'not authorized'}
+        if req.user?
+          isAdmin req.user, (admin) ->
+            if admin?
+              next()
+            else
+              if req.route.method is 'get'
+                #if we're not an admin, enforce public-read acl
+                if not req.query?
+                  req.query = {}
+                req.query.acl = 'public-read'
+                next()
+              else
+                res.json 401, {msg: 'not authorized'}
+        else
+          if req.route.method is 'get'
+            #if we're not an admin, enforce public-read acl
+            if not req.query?
+              req.query = {}
+            req.query.acl = 'public-read'
+            next()
+          else
+            res.json 401, {msg: 'not authorized'}

   publicRegisterAction = (req, res, next) ->
     systemCheck req, res, () ->
@@ -234,4 +250,4 @@
     _playAuth: playAuth
     _findUser: findUser

-  exports+  exports


remove useless query to set binary download headers

--- server/controllers/binaries.coffee
+++ server/controllers/binaries.coffee
@@ -132,21 +132,16 @@
             else if err
                 next new Error err.error
             else
-                # retrieve attachment infos
-                db.get id, (err, binDoc) ->
-                    return next err if err

-                    # Set response header from attachment infos
-                    length = binDoc._attachments[name].length
-                    type = binDoc._attachments[name]['content-type']
-                    res.setHeader 'Content-Length', length
-                    res.setHeader 'Content-Type', type
+            # Set response header from attachment infos
+            res.setHeader 'Content-Length', stream.headers['content-length']
+            res.setHeader 'Content-Type', stream.headers['content-type']

-                    if req.headers['range']?
-                        stream.setHeader 'range', req.headers['range']
+            if req.headers['range']?
+                stream.setHeader 'range', req.headers['range']

-                    # Use streaming to avoid high memory consumption.
-                    stream.pipe res
+            # Use streaming to avoid high memory consumption.
+            stream.pipe res

     # No binary found, error is returned.
     else


hot-patch for not show teambuilder for teachers

--- picoCTF-web/web/coffee/front-page.coffee
+++ picoCTF-web/web/coffee/front-page.coffee
@@ -248,7 +248,7 @@
         when 0
             apiNotify resp
         when 1
-          if document.location.hash == "#team-builder"
+          if document.location.hash == "#team-builder" and not resp.data.teacher
             @setPage "Team Management"
           else
             if resp.data.teacher


Only display scoreboard when things have gone successfully.

--- picoCTF-web/web/coffee/scoreboard.coffee
+++ picoCTF-web/web/coffee/scoreboard.coffee
@@ -4,10 +4,14 @@
 load_scoreboard = ->
   apiCall "GET", "/api/scoreboard", {}
   .done (data) ->
-    $("#scoreboard-tabs").html renderScoreboardTabs({
-      data: data.data
-      renderScoreboard: renderScoreboard
-    })
+    switch data["status"]
+      when 1
+        $("#scoreboard-tabs").html renderScoreboardTabs({
+          data: data.data
+          renderScoreboard: renderScoreboard
+        })
+      when 0
+        apiNotify(data)

 $ ->
   load_scoreboard()


add name= options to all FormControls on landing page, just in case.

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -102,12 +102,12 @@
                 <form onSubmit={@sign_in} className='form-inline'>
                     <Row>
                         <FormGroup>
-                            <FormControl ref='email' type='email' placeholder='Email address' autoFocus={@props.has_account} onChange={@remove_error} />
+                            <FormControl ref='email' type='email' placeholder='Email address' name='email' autoFocus={@props.has_account} onChange={@remove_error} />
                         </FormGroup>
                     </Row>
                     <Row>
                         <FormGroup>
-                            <FormControl style={width:'100%'} ref='password' type='password' placeholder='Password' onChange={@remove_error} />
+                            <FormControl style={width:'100%'} ref='password' type='password' name='password' placeholder='Password' onChange={@remove_error} />
                         </FormGroup>
                     </Row>
                     <Row>
@@ -137,12 +137,12 @@
                 <Row>
                     <Col xs={5}>
                         <FormGroup>
-                            <FormControl style={width:'100%'} ref='email' type='email' placeholder='Email address' autoFocus={true} onChange={@remove_error} />
+                            <FormControl style={width:'100%'} ref='email' type='email' name='email' placeholder='Email address' autoFocus={true} onChange={@remove_error} />
                         </FormGroup>
                     </Col>
                     <Col xs={4}>
                         <FormGroup>
-                            <FormControl style={width:'100%'} ref='password' type='password' placeholder='Password' onChange={@remove_error} />
+                            <FormControl style={width:'100%'} ref='password' type='password' name='password' placeholder='Password' onChange={@remove_error} />
                         </FormGroup>
                     </Col>
                     <Col xs={3}>
@@ -227,7 +227,7 @@

                 <form onSubmit={@forgot_password} style={marginTop:'1em'}>
                     <FormGroup>
-                        <FormControl ref='email' type='email' placeholder='Email address' autoFocus={true} onChange={@set_email} />
+                        <FormControl ref='email' type='email' placeholder='Email address' name='email' autoFocus={true} onChange={@set_email} />
                     </FormGroup>
                     {if @props.forgot_password_error then @display_error() else @display_success()}

@@ -280,7 +280,7 @@

                 <form onSubmit={@reset_password}>
                     <FormGroup>
-                        <FormControl ref='password' type='password' placeholder='New Password' />
+                        <FormControl name='password' ref='password' type='password' placeholder='New Password' />
                     </FormGroup>
                     {@display_error()}


Use better message.

Use better message.

--- src/spec/integration.spec.coffee
+++ src/spec/integration.spec.coffee
@@ -155,7 +155,7 @@
         expect(msg.message['No mastersku attribute!']).toBe 1
         expect(msg.message['Prices updated.']).toBe 1
         expect(msg.message['Price update postponed.']).toBe 2
-        expect(msg.message['There is no product in master for sku \'We add some content here in order to create the variant\'.']).toBe 1
+        expect(msg.message['There is no published product in master for sku \'We add some content here in order to create the variant\'.']).toBe 1
         expect(msg.message['There is no product in master for sku \'We add some content here in order to create another variant\'.']).toBe 1

         @priceSync.run (msg) =>
@@ -236,4 +236,4 @@
     .fail (error) ->
       console.log error
       done error
-    .done()+    .done()


add failing test case

add failing test case

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1713,6 +1713,20 @@
             value.onValue(->)
             value.take(1)
         [0, 1])
+  describe "case X", ->
+    expectStreamEvents(
+      ->
+        bus = new Bacon.Bus()
+        root = Bacon.later(1).toProperty()
+        root.onValue ->
+
+        Bacon.later(1).onValue ->
+          root.map(->).onValue ->
+            bus.push(1)
+            bus.end()
+        bus
+      [1])
+

 describe "Bacon.combineAsArray", ->
   describe "initial value", ->


Added google search test

Added google search test

--- spec/ahoy_spec.coffee
+++ spec/ahoy_spec.coffee
@@ -1,7 +1,15 @@
 ahoy = require("../dist/ahoy.js").ahoy
+
+checkSource = (referrer, name, category, extra) ->
+  source = ahoy.parseReferrer(referrer)
+  expect(source.name).toEqual(name)
+  expect(source.category).toEqual(category)
+  expect(source.extra).toEqual(extra)

 describe "ahoy", ->

-  it "should be direct with no referrer", ->
-    data = ahoy.parseReferrer("")
-    expect(data.name).toEqual("(direct)")
+  it "direct", ->
+    checkSource "", "(direct)", "(direct)", null
+
+  it "google organic", ->
+    checkSource "http://www.google.com/?q=test", "google.com", "search", "test"


Prevent text from leaving fixture modified

--- spec/pane-spec.coffee
+++ spec/pane-spec.coffee
@@ -1080,6 +1080,7 @@
       expect(eventCount).toBe 1

     it "only calls terminate handler once when text is modified twice", ->
+      originalText = editor1.getText()
       editor1.insertText('Some text')
       advanceClock(editor1.getBuffer().stoppedChangingDelay)

@@ -1090,6 +1091,10 @@

       expect(pane.getPendingItem()).toBeNull()
       expect(eventCount).toBe 1
+
+      # Reset fixture back to original state
+      editor1.setText(originalText)
+      editor1.save()

     it "only calls clearPendingItem if there is a pending item to clear", ->
       spyOn(pane, "clearPendingItem").andCallThrough()


more linting fixes

more linting fixes

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -447,7 +447,7 @@
         triggerMouseDownEvent(tabBar.tabForItem(item1), which: 3)
         expect(atom.workspace.getPanes().length).toBe 1

-        spyOn(atom,'open')
+        spyOn(atom, 'open')
         atom.commands.dispatch(tabBar.element, 'tabs:open-in-new-window')
         expect(atom.open).toHaveBeenCalled()


Fix regression where spacebar scrolled elements containing editors

/cc @hellendag

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -365,7 +365,12 @@

   onTextInput: (event) =>
     event.stopPropagation()
-    event.preventDefault()
+
+    # WARNING: If we call preventDefault on the input of a space character,
+    # then the browser interprets the spacebar keypress as a page-down command,
+    # causing spaces to scroll elements containing editors. This is impossible
+    # to test.
+    event.preventDefault() if event.data isnt ' '

     return unless @isInputEnabled()


incorrect textwrap container now errors correctly

--- src/textwrap/methods/draw.coffee
+++ src/textwrap/methods/draw.coffee
@@ -1,4 +1,5 @@
-print = require "../../core/console/print.coffee"
+print        = require "../../core/console/print.coffee"
+stringFormat = require "../../string/format.js"

 module.exports =
   accepted: [undefined]


Send profile analytics events with values -1 and 1

--- src/views/personalisation.coffee
+++ src/views/personalisation.coffee
@@ -125,6 +125,8 @@
                 category = "setProfileMobility"
                 value += type == p13n.getAccessibilityMode(group)

+            if value == 0
+                value = -1
             Analytics.trackCommand category, [name, value]

         switchPersonalisation: (ev) =>


fix stupid pan breakage

fix stupid pan breakage

--- static/coffee/common/plot.coffee
+++ static/coffee/common/plot.coffee
@@ -153,8 +153,8 @@

   update_range : (range_info) ->
     @pause()
-    @x_range.set(range_info.x)
-    @y_range.set(range_info.y)
+    @x_range.set(range_info.xr)
+    @y_range.set(range_info.yr)
     @unpause()

   build_tools: () ->


Fix activity scores bar display

Fix activity scores bar display

--- static/js/activity_scores.coffee
+++ static/js/activity_scores.coffee
@@ -104,12 +104,13 @@
             while data_idx + 1 < score_list.length
                 act = score_list[data_idx + 1].attributes

+                if act.time != time
+                    break
+
                 if @type_filter and @type_filter.length
                     if act.type not in @type_filter
-                        break
-
-                if act.time != time
-                    break
+                        data_idx += 1
+                        continue

                 score += act.score
                 data_idx += 1


STL/dom refactoring the element_test

STL/dom refactoring the element_test

--- stl/dom/test/element_test.coffee
+++ stl/dom/test/element_test.coffee
@@ -7,82 +7,86 @@

 describe 'Element', ->
+  $ = window = document = Element = MyElement = Table = null
+
+  before Test.load(module, (dom, win)->
+    $        = dom
+    window   = win
+    document = win.document
+    Element  = $.Element)
+

   describe "direct instance", ->
-    get = (callback)->
-      Test.load module, ($, window)->
-        callback($.Element, $, window)

-    it "should allow to create new elements", get (Element) ->
+    it "should allow to create new elements", ->
       element = new Element('div')

       element.should.be.instanceOf Element
       element._.tagName.should.eql 'DIV'

-    it "should bypass the properties in place", get (Element)->
+    it "should bypass the properties in place", ->
       element = new Element('div', id: 'my-id', html: 'my-html', class: 'my-class')

       element._.id.should.eql        'my-id'
       element._.innerHTML.should.eql 'my-html'
       element._.className.should.eql 'my-class'

-    it "should accept raw dom-elements as the first attribute", get (Element, $, window)->
-      raw_dom = window.document.createElement('div')
+    it "should accept raw dom-elements as the first attribute", ->
+      raw_dom = document.createElement('div')
       element = new Element(raw_dom)

       element._.should.be.equal raw_dom

   describe "inheritance usage", ->
-    get = (callback)->
-      Test.load module, ($, window)->
-        MyElement = new window.Lovely.Class $.Element,
-          constructor: (tag, id, html) ->
-            this.$super(tag, id: id, html: html)
-
-        callback(MyElement, $, window)
+    before (done)->
+      MyElement = new window.Lovely.Class $.Element,
+        constructor: (tag, id, html) ->
+          this.$super(tag, id: id, html: html)
+      done()

-    it "should inherit the Element", get (MyElement, $) ->
+    it "should inherit the Element", ->
       element = new MyElement('div')

       element.should.be.instanceOf MyElement
       element.should.be.instanceOf $.Element

-    it "should use correct tag name", get (MyElement) ->
+    it "should use correct tag name", ->
       element = new MyElement('div')

       element._.tagName.should.eql 'DIV'

-    it "should bypass the 'id' and 'html' properties", get (MyElement) ->
+    it "should bypass the 'id' and 'html' properties", ->
       element = new MyElement('div', 'my-id', 'my-html')

       element._.id.should.eql        'my-id'
       element._.innerHTML.should.eql 'my-html'

   describe "dynamic typecasting", ->
-    get = (callback)->
-      Test.load module, ($, window)->
-        $.Wrapper.set 'table', new window.Lovely.Class $.Element,
-          constructor: (one, two) ->
-            this.$super(one, two)
+    before (done)->
+      Table = new window.Lovely.Class $.Element,
+        constructor: (one, two) ->
+          this.$super(one, two)

-        callback($.Element, $, window)
+      $.Wrapper.set 'table', Table

-    it "should automatically typecast elements by tag name", get (Element, $)->
+      done()
+
+    it "should automatically typecast elements by tag name", ->
       table = new Element('table')

-      table.should.be.instanceOf $.Wrapper.get('table')
+      table.should.be.instanceOf Table
       table.should.be.instanceOf $.Element

-    it "should bypass the attributes in place", get (Element)->
+    it "should bypass the attributes in place", ->
       table = new Element('table', id: 'my-id', class: 'my-class')

       table._.id.should.eql        'my-id'
       table._.className.should.eql 'my-class'

-    it "should work with raw dom-elements as well", get (Element, $, window)->
-      raw_dom = window.document.createElement('table')
+    it "should work with raw dom-elements as well", ->
+      raw_dom = document.createElement('table')
       element = new Element(raw_dom)

       element.should.be.instanceOf $.Wrapper.get('table')


used vectorized mappers for performance improvement

--- static/coffee/glyph_renderers.coffee
+++ static/coffee/glyph_renderers.coffee
@@ -42,47 +42,34 @@
         console.log("Unknown glyph type: " + glyph.type)

   distance: (glyph, data, pt, span, position) ->
-    results = new Array(data.length)
-
     pt_units = glyph[pt].units
     span_units = glyph[span].units
-    if pt == "x"
-      mapper = @xmapper
-    else if pt == "y"
-      mapper = @ymapper
+
+    if      pt == "x" then mapper = @xmapper
+    else if pt == "y" then mapper = @ymapper
+
+    span = (glyph.select(span, x) for x in data)
+    if span_units == "screen"
+      return span

     if position == "center"
-
-      for i in [0..data.length-1]
-        halfspan = glyph.select(span, data[i]) / 2
-        if span_units == "screen"
-          results[i] = 2 * halfspan
-          continue
-        ptc = glyph.select(pt, data[i])
-        if pt_units == "screen"
-          ptc = mapper.map_data(ptc)
-        pt0 = ptc - halfspan
-        pt1 = ptc + halfspan
-        spt0 = mapper.map_screen(pt0)
-        spt1 = mapper.map_screen(pt1)
-        results[i] = spt1 - spt0
+      halfspan = (d / 2 for d in span)
+      ptc = (glyph.select(pt, x) for x in data)
+      if pt_units == "screen"
+        ptc = mapper.v_map_data(ptc)
+      pt0 = (ptc[i] - halfspan[i] for i in [0..ptc.length-1])
+      pt1 = (ptc[i] + halfspan[i] for i in [0..ptc.length-1])

     else
+      pt0 = (glyph.select(pt, x) for x in data)
+      if pt_units == "screen"
+        pt0 = mapper.v_map_data(pt0)
+      pt1 = (pt0[i] + span[i] for i in [0..pt0.length-1])

-      for i in [0..data.length-1]
-        halfspan = glyph.select(span, data[i])
-        if span_units == "screen"
-          results[i] = halfspan
-          continue
-        pt0 = glyph.select(pt, data[i])
-        if pt_units == "screen"
-          pt0 = mapper.map_data(pt0)
-        pt1 = pt0 + halfspan
-        spt0 = mapper.map_screen(pt0)
-        spt1 = mapper.map_screen(pt1)
-        results[i] = spt1 - spt0
+    spt0 = mapper.v_map_screen(pt0)
+    spt1 = mapper.v_map_screen(pt1)

-    return results
+    return (spt1[i] - spt0[i] for i in [0..spt0.length-1])

   map_to_screen : (x, x_units, y, y_units) ->
     sx = new Array(x.length)
@@ -91,14 +78,14 @@
     if x_units == "screen"
       sx = x
     else
-      for i in [0..x.length-1]
-        sx[i] = @plot_view.viewstate.xpos(@xmapper.map_screen(x[i]))
+      sx = @xmapper.v_map_screen(x)
+      sx = @plot_view.viewstate.v_xpos(sx)

     if y_units == "screen"
       sy = y
     else
-      for i in [0..y.length-1]
-        sy[i] = @plot_view.viewstate.ypos(@ymapper.map_screen(y[i]))
+      sy = @ymapper.v_map_screen(y)
+      sy = @plot_view.viewstate.v_ypos(sy)

     return [sx, sy]


Added how to link and button styling

Added how to link and button styling

--- app/pages/lab/index.cjsx
+++ app/pages/lab/index.cjsx
@@ -93,7 +93,8 @@
             Create a new project{' '}
             <LoadingIndicator off={not @state.creationInProgress} />
           </button>
-          <Link to="lab-policies">Project building policies</Link>
+          <Link className="standard-button" to="lab-policies">Project building policies</Link>
+          How to build a project
           {if @state.creationError?

{@state.creationError.message}
}


Don't include stuff you don't have

Don't include stuff you don't have

--- frontend/src/main.cjsx
+++ frontend/src/main.cjsx
@@ -1,17 +1,5 @@
-Data = require('./Data.coffee')
 Ajax = require('./Ajax.coffee')

+document.addEventListener 'DOMContentLoaded', ->
+	console.log "Hello, world"

-React.initializeTouchEvents(true)
-
-
-document.addEventListener 'DOMContentLoaded', ->
-
-	# appCache = window.applicationCache
-
-	# appCache.addEventListener 'updateready', (e) ->
-	# 	alert("There's a new version available. You will be updated.")
-	# 	window.location.reload()
-
-
-