Update spec names

Update spec names

--- spec/text-editor-component-spec.coffee
+++ spec/text-editor-component-spec.coffee
@@ -1240,7 +1240,7 @@
         expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'

     describe "when the marker is not empty", ->
-      it "renders in the correct position", ->
+      it "renders at the head of the marker", ->
         marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], invalidate: 'never')
         decoration = editor.decorateMarker(marker, {type: 'overlay', item})
         nextAnimationFrame()
@@ -1251,7 +1251,7 @@
         expect(overlay.style.left).toBe position.left + 'px'
         expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px'

-      it "renders in the correct position when the marker is reversed", ->
+      it "renders at the head of the marker when the marker is reversed", ->
         marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], invalidate: 'never', reversed: true)
         decoration = editor.decorateMarker(marker, {type: 'overlay', item})
         nextAnimationFrame()


:art: Better test naming

:art: Better test naming

--- spec/text-editor-component-spec.coffee
+++ spec/text-editor-component-spec.coffee
@@ -68,7 +68,7 @@
       expect(nextAnimationFrame).not.toThrow()

   describe "line rendering", ->
-    expectLineRender = (tileNode, {screenRow, top}) ->
+    expectTileContainsRow = (tileNode, screenRow, {top}) ->
       lineNode = tileNode.querySelector("[data-screen-row='#{screenRow}']")
       tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)

@@ -78,7 +78,7 @@
       else
         expect(lineNode.textContent).toBe(tokenizedLine.text)

-    it "renders the currently-visible lines into a tiled fashion", ->
+    it "renders the currently-visible lines in a tiled fashion", ->
       wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
       tileHeight = tileSize * lineHeightInPixels
       component.measureDimensions()
@@ -90,21 +90,21 @@

       expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
       expect(tilesNodes[0].children.length).toBe(tileSize)
-      expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)

       expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
       expect(tilesNodes[1].children.length).toBe(tileSize)
-      expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)

       expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
       expect(tilesNodes[2].children.length).toBe(tileSize)
-      expectLineRender(tilesNodes[2], screenRow: 6, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[2], screenRow: 7, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[2], screenRow: 8, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)

       expect(component.lineNodeForScreenRow(9)).toBeUndefined()

@@ -119,21 +119,21 @@

       expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, -5px, 0px)"
       expect(tilesNodes[0].children.length).toBe(tileSize)
-      expectLineRender(tilesNodes[0], screenRow: 3, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 4, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 5, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 3, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 4, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 5, top: 2 * lineHeightInPixels)

       expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight - 5}px, 0px)"
       expect(tilesNodes[1].children.length).toBe(tileSize)
-      expectLineRender(tilesNodes[1], screenRow: 6, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 7, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 8, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 6, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 7, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 8, top: 2 * lineHeightInPixels)

       expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight - 5}px, 0px)"
       expect(tilesNodes[2].children.length).toBe(tileSize)
-      expectLineRender(tilesNodes[2], screenRow: 9, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[2], screenRow: 10, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[2], screenRow: 11, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 9, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 10, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 11, top: 2 * lineHeightInPixels)

     it "updates the top position of subsequent tiles when lines are inserted or removed", ->
       wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
@@ -145,14 +145,14 @@
       tilesNodes = componentNode.querySelectorAll(".tile")

       expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
-      expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)

       expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
-      expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)

       editor.getBuffer().insert([0, 0], '\n\n')
       nextAnimationFrame()
@@ -160,19 +160,19 @@
       tilesNodes = componentNode.querySelectorAll(".tile")

       expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
-      expectLineRender(tilesNodes[0], screenRow: 0, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 1, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[0], screenRow: 2, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 1, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[0], 2, top: 2 * lineHeightInPixels)

       expect(tilesNodes[1].style['-webkit-transform']).toBe "translate3d(0px, #{1 * tileHeight}px, 0px)"
-      expectLineRender(tilesNodes[1], screenRow: 3, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 4, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[1], screenRow: 5, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 3, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 4, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[1], 5, top: 2 * lineHeightInPixels)

       expect(tilesNodes[2].style['-webkit-transform']).toBe "translate3d(0px, #{2 * tileHeight}px, 0px)"
-      expectLineRender(tilesNodes[2], screenRow: 6, top: 0 * lineHeightInPixels)
-      expectLineRender(tilesNodes[2], screenRow: 7, top: 1 * lineHeightInPixels)
-      expectLineRender(tilesNodes[2], screenRow: 8, top: 2 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 6, top: 0 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 7, top: 1 * lineHeightInPixels)
+      expectTileContainsRow(tilesNodes[2], 8, top: 2 * lineHeightInPixels)

     it "updates the lines when lines are inserted or removed above the rendered row range", ->
       wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'


cleanup

cleanup


add log level filtering to Console

add log level filtering to Console

--- scripts/console.coffee
+++ scripts/console.coffee
@@ -167,6 +167,8 @@
 class Console
   COFFEESCRIPT_URI = 'https://raw.github.com/jashkenas/coffee-script/1.1.3/extras/coffee-script.js'

+  LEVELS = ['silent', 'error', 'warn', 'info', 'debug']
+
   @instance =
     dump:  -> console?.dir   arguments...
     log:   -> console?.log   arguments...
@@ -203,6 +205,8 @@
     @history.index = -1

     @bufferLimit = 20
+
+    @level = 'error'

   processInput: ->
     if CoffeeScript?
@@ -260,6 +264,12 @@
     @root.hide arguments...
     return

+  shouldLog: (testLevel) ->
+    logLevel  = @level
+    logLevel  = LEVELS.indexOf(logLevel)  unless typeof logLevel  is 'number'
+    testLevel = LEVELS.indexOf(testLevel) unless typeof testLevel is 'number'
+    testLevel <= logLevel
+
   log: (message, options) ->
     $('<div>')
     .addClass(options?.class ? 'ui-consoleMessage')
@@ -268,10 +278,10 @@
     @cullBuffer()
     return

-  debug: (message) -> @log message, class: 'ui-consoleDebug'
-  info:  (message) -> @log message, class: 'ui-consoleInfo'
-  warn:  (message) -> @log message, class: 'ui-consoleWarning'
-  error: (message) -> @log message, class: 'ui-consoleError'
+  debug: (message) -> @log(message, class: 'ui-consoleDebug')   if this.shouldLog 'debug'
+  info:  (message) -> @log(message, class: 'ui-consoleInfo')    if this.shouldLog 'info'
+  warn:  (message) -> @log(message, class: 'ui-consoleWarning') if this.shouldLog 'warn'
+  error: (message) -> @log(message, class: 'ui-consoleError')   if this.shouldLog 'error'

   @valueToHtml: (object, options) ->
     type = typeof object


no message

no message

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -2,6 +2,9 @@
 #   Slackで新しいチャンネルができたら通知するやつ
 # Author:
 #   hiraoka
+
+
+_ = require('hubot:new-channel-bot')

 module.exports = (robot) ->
   robot.hear /疲れた/i, (msg) ->
@@ -23,4 +26,7 @@
     return unless msg.item.message.permalink
     user = robot.adapter.client.getUserByID msg.user
     text = ":star: @#{user.name} added star #{msg.item.message.permalink}"
-    msg.send text+    msg.send text
+
+  robot.adapter.client?.on? 'hello', (msg) ->
+    msg.send "おはよう"


Implement seeking

Implement seeking

--- scripts/players.coffee
+++ scripts/players.coffee
@@ -31,6 +31,31 @@
         if video.readyState > 0
             updateVideo()
     , false) for event in ['progress', 'timeupdate', 'pause', 'playing', 'seeked', 'ended']
+
+    seeking = false
+    beginSeek = (e) ->
+        e.preventDefault()
+        seeking = true
+        video.pause()
+        seekProgress(e)
+    seekProgress = (e) ->
+        e.preventDefault()
+        return if not seeking
+        if e.offsetX?
+            amount = e.offsetX / seek.clientWidth
+        else
+            amount = e.layerX / seek.clientWidth
+        video.currentTime = video.duration * amount
+    endSeek = (e) ->
+        e.preventDefault()
+        video.play()
+        seeking = false
+
+    seekClick = seek.querySelector('.clickable')
+    seekClick.addEventListener('mousedown', beginSeek, false)
+    seekClick.addEventListener('mouseup', endSeek, false)
+    seekClick.addEventListener('mousemove', seekProgress, false)
+    seekClick.addEventListener('mouseleave', endSeek, false)

     debounce = true
     document.addEventListener(prefix + 'fullscreenchange', (e) ->


exit on nextTick to allow other SIGTERM and SIGINT handlers to run

--- app/lib/process.coffee
+++ app/lib/process.coffee
@@ -2,14 +2,16 @@
 logger = log4js.getLogger 'process'

 process.on 'uncaughtException', (err) ->
-  logger.fatal 'Uncaught Exception', err
-  console.log err.stack
-  process.exit 1
+  logger.fatal "Uncaught Exception [#{err.toString()}]\n#{err.stack}"
+  process.nextTick ->
+    process.exit 1

 process.on 'SIGTERM', ->
   logger.info 'caught SIGTERM'
-  process.exit 0
+  process.nextTick ->
+    process.exit 0

 process.on 'SIGINT', ->
   logger.info 'caught SIGINT'
-  process.exit 0
+  process.nextTick ->
+    process.exit 0


wrong variable name

wrong variable name

--- scripts/tonypai.coffee
+++ scripts/tonypai.coffee
@@ -12,7 +12,7 @@
       .get() (err, res, body) ->

         if err
-          res.send res.toString()
+          res.send err.toString()
         else
           data = JSON.parse body
           if data.cod is "200"


Don't try to fetch lastBuild if there's no id

--- app/models/repo.coffee
+++ app/models/repo.coffee
@@ -28,9 +28,9 @@
   ).property('lastBuildId', 'lastBuildNumber')

   lastBuild: (->
-    id = @get('lastBuildId')
-    @store.find('build', id)
-    @store.recordForId('build', id)
+    if id = @get('lastBuildId')
+      @store.find('build', id)
+      @store.recordForId('build', id)
   ).property('lastBuildId')

   withLastBuild: ->


Twit is passing back a nice Error object, not a stringified JSON object.

--- scripts/tweeter.coffee
+++ scripts/tweeter.coffee
@@ -78,13 +78,8 @@
     twit.post "statuses/update",
       status: update
     , (err, reply) ->
-      console.log "===== Twitter error:"
-      console.log err
-      console.log "===== Twitter response:"
-      console.log reply
       if err
-        data = JSON.parse(err.data).errors[0]
-        msg.reply "I can't do that. #{data.message} (error #{data.code})"
+        msg.reply "I can't do that. #{err.message} (error #{err.statusCode})"
         return
       if reply['text']
         return msg.send "#{reply['user']['screen_name']} just tweeted: #{reply['text']}"


Created ability to create lists

Created ability to create lists

--- src/trello.coffee
+++ src/trello.coffee
@@ -18,7 +18,7 @@
 #   hubot trello description <shortlink> "<discription>"
 #   hubot trello search "<criteria>"
 #   hubot trello get <name>'s cards
-#
+#   hubot trello create list "<name>" (optional: top' or bottom)
 #
 # Author:
 #   jared barboza <jared.m.barboza@gmail.com>
@@ -101,12 +101,12 @@
       for cards in data.cards
          msg.send " * #{cards.name} | #{cards.url}"

-createList = (msg, list_name, postion = top) ->
+createList = (msg, list_name, position = "top") ->
   msg.reply "I'll get right on that!"
   if position is "top" or position is "bottom" or position > 0
-    trello.post "/1/lists", {name: list_name, idBoard: board.id} (err, data) ->
-    msg.reply "I'm sorry I was unable to add that list. Please try again later." if err
-    msg.reply "That list has been created and be viewed here: #{data.url}" unless err
+    trello.post "/1/lists", {name: list_name, idBoard: board.id, pos:position}, (err, data) ->
+      msg.reply "I'm sorry I was unable to add that list. Please try again later." if err
+      msg.reply "That list has been created and be viewed here: #{board.url}" unless err
   else msg.reply "I'm sorry I can't create a list in that position"

@@ -195,7 +195,7 @@
   robot.respond /trello add member (\w+) (\w+)/i, (msg) ->
     addMember msg, msg.match[1], msg.match[2]

-  robot.respond /^trello create list ["“'‘]((.+|\n)+)["”'’] (.+)?$/i, (msg) ->
+  robot.respond /trello create list ["“'‘]((.+|\n)+)["”'’] ?(.+)?$/i, (msg) ->
     createList msg, msg.match[1], msg.match[3]

   robot.respond /trello help/i, (msg) ->
@@ -211,3 +211,4 @@
     msg.send " *  trello comment <card.shortLink> <Comment>"
     msg.send " *  trello description <card.shortLink> <Description>"
     msg.send " *  trello search <criteria>"
+    msg.send " *  trello create list \"<name>\" (optional:top or bottom)"


Better "packages using hook" logic

Better "packages using hook" logic

--- generate-docs.coffee
+++ generate-docs.coffee
@@ -32,11 +32,9 @@
 		commentLines: commentLines

 # } Get the type and name of this package (if any).
-packageInformation = {}
-packageLookup = {}
-for filename, {commentLines} of sources
-
-	packageTypeAndName = ///
+packageTypeAndName = (filename) ->
+
+	regex = ///

 ^(?:
 	(server)\/packages\/([^/]+)
@@ -45,11 +43,18 @@
 )

 ///
-
-	continue unless (matches = filename.match packageTypeAndName)?
-
-	type = matches[1] ? matches[3]
-	name = matches[2] ? matches[4]
+
+	return unless (matches = filename.match regex)?
+
+	type: matches[1] ? matches[3]
+	name: matches[2] ? matches[4]
+
+packageInformation = {}
+packageLookup = {}
+for filename, {commentLines} of sources
+
+	continue unless (typeAndName = packageTypeAndName filename)?
+	{type, name} = typeAndName

 	packageInformation[type] ?= {}
 	packageLookup[filename] = name: name, type: type
@@ -92,9 +97,14 @@
 		packages = []

 		for filename, {commentLines} of sources
-			continue unless (package_ = packageLookup[filename])?
-
-			packages.push package_ if commentLines.some (line) ->
+
+			continue unless (typeAndName = packageTypeAndName filename)?
+
+			matches = filename.match /packages\/(.*)\./
+			typeAndName.name = matches[1]
+			typeAndName.filename = filename
+
+			packages.push typeAndName if commentLines.some (line) ->
 				line.match implementsPattern

 		packages
@@ -156,18 +166,14 @@

 			for package_ in packages

-				{filename} = packageInformation[type][package_.name]
-
-				matches = filename.match /packages\/(.*)\./
-
 				markdown += """

 \t* #{
-	matches[1].replace /\/index$/, ''
+	package_.name.replace /\/index$/, ''
 } (#{
 	package_.type
 })


fixed how original content is handled

fixed how original content is handled

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -457,20 +457,21 @@
     # Show the left stage to start with.
     @stages.left.style.display = 'block'
     # Empty the target element.
-    @el.innerHTML = ''
+    @stageEl = document.createElement 'div'

     # Append each stage to the target element.
     for anchor in @anchors
-      @el.appendChild @stages[anchor]
+      @stageEl.appendChild @stages[anchor]

     # Show the target if applicable.
     if @settings.showOnStart
       @el.style.display = 'block'
       @el.style.visibility = 'visible'

-    # Hide the original element and insert the oriDomi version.
-    @cleanEl.style.display = 'none'
-    @cleanEl.parentNode.insertBefore @el, @cleanEl
+    # Hide the original content and insert the oriDomi version.
+    @el.innerHTML = ''
+    @el.appendChild @cleanEl
+    @el.appendChild @stageEl

     # Cache a jQuery object of the element if applicable.
     @$el = $ @el if $


reorganized bootstrap process

reorganized bootstrap process

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -15,7 +15,15 @@
 oriDomiSupport = true
 testEl = document.createElement 'div'
 prefixList = ['Webkit', 'Moz', 'O', 'ms', 'Khtml']
-
+css =
+  transform: 'transform'
+  origin: 'transformOrigin'
+  transformStyle: 'transformStyle'
+  transitionProp: 'transitionProperty'
+  transitionDuration: 'transitionDuration'
+  transitionEasing: 'transitionTimingFunction'
+  perspective: 'perspective'
+  backface: 'backfaceVisibility'

 testProp = (prop) ->
   return prop if testEl.style[prop]?
@@ -26,7 +34,14 @@
   false

-getGradientProp = ->
+for key, value of css
+  css[key] = testProp value
+  if !css[key]
+    console?.warn 'oriDomi: Browser does not support oriDomi'
+    oriDomiSupport = false
+    break
+
+css.gradientProp = do ->
   for prefix in prefixList
     hyphenated = "-#{ prefix.toLowerCase() }-linear-gradient"
     testEl.style.backgroundImage = "#{ hyphenated }(left, #000, #fff)"
@@ -34,16 +49,14 @@
       return hyphenated
   'linear-gradient'

-
-getTransformProp = ->
+css.transformProp = do ->
   prefix = css.transform.match(/(\w+)Transform/i)[1]
   if prefix
     "-#{ prefix.toLowerCase() }-transform"
   else
     'transform'

-
-getTransitionEndProp = ->
+css.transitionEnd = do ->
   switch css.transitionProp
     when 'transitionProperty'
       'transitionEnd'
@@ -55,6 +68,7 @@
       'oTransitionEnd'
     when 'MSTransitionProperty'
       'msTransitionEnd'
+

 # one dimensional:


position stage holder absolutely

position stage holder absolutely

--- oridomi.coffee
+++ oridomi.coffee
@@ -303,7 +303,7 @@
     width:          '100%'
     height:         '100%'
     position:       'absolute'
-    transform:      'translateY(-100%)'
+    top:            '0'
     transformStyle: p3d

   addStyle elClasses.stage,
@@ -1011,7 +1011,7 @@
       @isFrozen = false
       # Swap the visibility of the elements.
       hideEl @_cloneEl
-      @_stageHolder.style[css.transform] = 'translateY(-100%)'
+      showEl @_stageHolder
       @_setCursor()
       # Set `lastAngle` to 0 so an immediately subsequent call to `freeze` triggers the callback.
       @_lastOp.angle = 0


improve test

improve test

--- spec/specs/main.coffee
+++ spec/specs/main.coffee
@@ -149,10 +149,10 @@
     describe "No glitches in a complex scenario", ->
       expectPropertyEvents(
         ->
-          changes = series(1, [{a:0,b:0},{a:1,b:1}])
+          changes = series(1, [{a:0,b:0},{a:1,b:10}])

           a = changes.map((x) -> x.a)
-          b = changes.map((x) -> x.a)
+          b = changes.map((x) -> x.b)

           ab = Bacon.combineAsArray a, b

@@ -160,7 +160,7 @@
             Bacon.once 'f' + values

           Bacon.combineAsArray(f, b).map((x) -> x[0])
-        ["f0,0","f1,1"], semiunstable)
+        ["f0,0","f1,10"], semiunstable)
     it "Works with flatMap source spawning fromArrays", ->
       result = []
       array = [1,2,3]


Track transitions, not jobViews

Track transitions, not jobViews

--- javascripts/application.js.coffee
+++ javascripts/application.js.coffee
@@ -96,41 +96,41 @@
     @$el.html "
#{@model.id}

"
     this

-class View.Transition extends Backbone.View
+class View.Transition
   distance = (from, to)->
     from.offset()[dimension] - to.offset()[dimension] for dimension in ['left', 'top']

   translate = (x, y)->
     {translateX: "#{x}px", translateY: "#{y}px"}

-  initialize: (options)->
-    @target = options.target
+  constructor: (options)-> @$el = $ options.el

-  render: ->
-    [x, y] = distance @target, @$el
+  render: (target)->
+    [x, y] = distance target, @$el
     @$el.animate translate(x, y),
       duration: 250
       complete: =>
         @$el.remove()
           .animate translate(0, 0), duration: 0, complete: =>
-            @$el.prependTo @target
+            @$el.prependTo target
+    this

 class View.Flatware extends Backbone.View
   initialize: ->
     @finished = @$ '#finished'
-    @jobViews = {}
+    @transitions = {}
     @listenTo @model.jobs, 'add', @addJob
     @listenTo @model.workers, 'add', @addWorker

   addJob: (job)=>
     jobView = new View.Job(model: job)
-    @jobViews[job.id] = jobView
     jobView.render().$el.appendTo '#waiting'
+    @transitions[job.id] = transition = new View.Transition(el: jobView.el)
     @listenTo job, 'completed', =>
-      new View.Transition(el: jobView.el, target: @finished).render()
+      transition.render @finished

   addWorker: (worker)=>
     workerView = new View.Worker(model: worker).render()
     workerView.$el.appendTo '#workers'
     @listenTo worker, 'assigned', (_, job)=>
-      new View.Transition(el: @jobViews[job.id].el, target: workerView.jobList()).render()
+      @transitions[job.id].render workerView.$el


fix error catching to work with latest version of gulp-if

--- main.coffee
+++ main.coffee
@@ -72,18 +72,21 @@
     if notests
         config.testtasks = ["notests"]

-    error_handler = (e) ->
-        error = gutil.colors.bold.red
-        if e.fileName?
-            gutil.log(error("#{e.plugin}:#{e.name}: #{e.fileName} +#{e.lineNumber}"))
-        else
-            gutil.log(error("#{e.plugin}:#{e.name}"))
-        gutil.log(error(e.message))
-        gutil.beep()
-        @.end()
-        @emit("end")
-        if not dev
-            throw e
+    catch_errors = (s) ->
+        s.on "error", (e) ->
+            error = gutil.colors.bold.red
+            if e.fileName?
+                gutil.log(error("#{e.plugin}:#{e.name}: #{e.fileName} +#{e.lineNumber}"))
+            else
+                gutil.log(error("#{e.plugin}:#{e.name}"))
+            gutil.log(error(e.message))
+            gutil.beep()
+            s.end()
+            s.emit("end")
+            if not dev
+                throw e
+            return null
+        s

     # if coverage, we need to put vendors and templates apart
     if coverage
@@ -107,17 +110,17 @@
     gulp.task 'scripts', false, ->
         if coverage and config.coffee_coverage
             return gulp.src script_sources
-                .pipe(ngClassify(config.ngclassify(config)).on('error', error_handler))
+                .pipe(catch_errors(ngClassify(config.ngclassify(config))))
                 .pipe gulp.dest path.join(config.dir.coverage, "src")

         gulp.src script_sources
             .pipe gif(dev or config.sourcemaps, sourcemaps.init())
             .pipe cached('scripts')
             # coffee build
-            .pipe(gif("*.coffee", ngClassify(config.ngclassify(config)).on('error', error_handler)))
-            .pipe(gif("*.coffee", coffee().on('error', error_handler)))
+            .pipe(catch_errors(gif("*.coffee", ngClassify(config.ngclassify(config)))))
+            .pipe(catch_errors(gif("*.coffee", coffee())))
             # jade build
-            .pipe(gif("*.jade", jade().on('error', error_handler)))
+            .pipe(catch_errors(gif("*.jade", jade())))
             .pipe gif "*.html", rename (p) ->
                 if config.name? and config.name isnt 'app'
                     p.dirname = path.join(config.name, "views")
@@ -154,7 +157,7 @@
             return
         gulp.src config.files.templates
             # jade build
-            .pipe(gif("*.jade", jade().on('error', error_handler)))
+            .pipe(catch_errors(gif("*.jade", jade())))
             .pipe gif "*.html", rename (p) ->
                 if config.name? and config.name isnt 'app'
                     p.dirname = path.join(config.name, "views")
@@ -173,8 +176,8 @@
             .pipe cached('tests')
             .pipe gif(dev, sourcemaps.init())
             # coffee build
-            .pipe(gif("*.coffee", ngClassify(config.ngclassify)))
-            .pipe(gif("*.coffee", coffee().on('error', error_handler)))
+            .pipe(catch_errors(gif("*.coffee", ngClassify(config.ngclassify))))
+            .pipe(catch_errors(gif("*.coffee", coffee())))
             .pipe remember('tests')
             .pipe concat("tests.js")
             .pipe gif(dev, sourcemaps.write("."))
@@ -198,7 +201,7 @@
     gulp.task 'styles', false, ->
         gulp.src config.files.less
             .pipe cached('styles')
-            .pipe less().on('error', error_handler)
+            .pipe catch_errors(less())
             .pipe remember('styles')
             .pipe concat("styles.css")
             .pipe gif(prod, cssmin())
@@ -219,7 +222,7 @@
     # index.jade build
     gulp.task 'index', false, ->
         gulp.src config.files.index
-            .pipe jade().on('error', error_handler)
+            .pipe catch_errors(jade())
             .pipe gulp.dest config.dir.build

     # Run server.


Adding callbacks on set_auto_hide to add a new log entry if necessary.

--- main.coffee
+++ main.coffee
@@ -168,7 +168,7 @@

 auto_hide_time = Infinity
 last_auto_hide_interval = null
-set_auto_hide_time = (time=10)->
+set_auto_hide_time = (time=10, callback=->)->
     clearInterval last_auto_hide_interval
     auto_hide_time = time
     if auto_hide_time == Infinity
@@ -180,6 +180,7 @@
         if auto_hide_time == 0
             set_auto_hide_time Infinity
             set_dialog(0)
+            callback?()
             hide_window()
         render_all()

@@ -272,7 +273,9 @@
                         useHighlight:true
                         onClick: =>
                             @setState dialog: 1
-                            set_auto_hide_time 10
+                            set_auto_hide_time 10, ->
+                                if not activity_state.active
+                                    add_log_entry {active: true, date: Date.now()}

                     button.ui
                         label:'no'


filefinder uses Modal instead of facebox

--- extensions/filefinder/filefinder-pane.coffee
+++ extensions/filefinder/filefinder-pane.coffee
@@ -4,8 +4,9 @@
 File = require 'fs'
 Pane = require 'pane'

-jQuery  = $
-facebox = eval File.read require.resolve 'filefinder/facebox'
+jQuery = $
+Modal  = require 'modal'
+
 require 'filefinder/stringscore'

 module.exports =
@@ -14,14 +15,7 @@

   constructor: (@filefinder) ->
     $('#filefinder input').live 'keydown', @onKeydown
-
-    css   = File.read require.resolve 'filefinder/facebox.css'
-    head  = $('head')[0]
-    style = document.createElement 'style'
-    rules = document.createTextNode css
-    style.type = 'text/css'
-    style.appendChild rules
-    head.appendChild style
+    @modal = new Modal @html

   onKeydown: (e) =>
     keys = up: 38, down: 40, enter: 13
@@ -36,11 +30,10 @@
       @filterFiles()

   toggle: ->
-    if @showing
-      $.facebox.close()
+    if @modal.showing
+      @modal.hide()
     else
       @showFinder()
-    @showing = not @showing

   paths: ->
     _paths = []
@@ -50,7 +43,7 @@
     _.reject _.flatten(_paths), (dir) -> File.isDirectory dir

   showFinder: ->
-    $.facebox @html
+    @modal.show()
     @files = []
     for file in @paths()
       @files.push file.replace "#{window.path}/", ''


Added safeguard to rendering shell credentials.

--- web/coffee/shell.coffee
+++ web/coffee/shell.coffee
@@ -3,5 +3,5 @@
 $ ->
   apiCall "GET", "/api/user/shell", {}
   .done (data) ->
-    console.log data
-    $("#shell-account-credentials").html renderShellAccountCredentials({account: data.data})
+    if data.data
+      $("#shell-account-credentials").html renderShellAccountCredentials({account: data.data})


more on airplane password reset WIP

more on airplane password reset WIP

--- src/scripts/controllers/dapiControllers.coffee
+++ src/scripts/controllers/dapiControllers.coffee
@@ -68,7 +68,6 @@

     @do_login = () =>
       @service.login(@login, @password).then((results) =>
-        @error = null
         @location.path("/") # Back to top of app
       , (reason) =>
         @log.debug("Login failed due to #{reason.statusText}")
@@ -77,10 +76,25 @@

     @do_password_reset = () =>
       @service.password_reset(@login).then((results) =>
-        @error = null
         @add_success('Password reset email sent...')
       , (reason) =>
-        @log.debug("Login failed due to #{reason.statusText}")
+        @log.debug("Password reset failed due to #{reason.statusText}")
+        @set_http_error(reason)
+      )
+
+    @do_password_reset_confirm = () =>
+      @service.password_reset_confirm(@routeParams.id, @routeParams.verification).then((results) =>
+        @add_success('Your password has been reset')
+      , (reason) =>
+        @log.debug("Password reset failed due to #{reason.statusText}")
+        @set_http_error(reason)
+      )
+
+    @do_email_confirm = () =>
+      @service.email_confirm(@routeParams.id, @routeParams.verification).then((results) =>
+        @add_success('Your email address is now confirmed')
+      , (reason) =>
+        @log.debug("Email confirm failed due to #{reason.statusText}")
         @set_http_error(reason)
       )

@@ -103,7 +117,6 @@
       @service.logout().then (results) =>
         # Redirect to root
         @location.path("/")
-        @error = null

   isLoggedIn: =>
     @getUser().loggedIn


--allow-unrelated-histories everywhere!

--allow-unrelated-histories everywhere!

--- webpack/dist/tag.coffee
+++ webpack/dist/tag.coffee
@@ -20,5 +20,5 @@
 git 'push', '--tags', '--force', 'origin'
 git 'fetch'
 git 'checkout', '-f', 'dist'
-git 'merge', '-X', 'theirs', vX, '-m', vX #, '--allow-unrelated-histories'
+git 'merge', '-X', 'theirs', vX, '-m', vX, '--allow-unrelated-histories'
 git 'push'


fix export bug

fix export bug

--- views/importTab.cjsx
+++ views/importTab.cjsx
@@ -133,7 +133,7 @@
       selectTitle: selectTitle
   handleExportClick: ->
     title = @props.henseiData.titles[@state.selectTitle]
-    code = JSON.stringify @props.henseiData.title.ships
+    code = JSON.stringify @props.henseiData[title].ships
     code = '[' + code + ']'
     @setState
       code: code


ready to try this out

ready to try this out

--- src/dsa.iced
+++ src/dsa.iced
@@ -22,10 +22,6 @@

   #----------------

-  verify : ([r,s], cb) ->
-
-  #----------------
-
   serialize : () ->
     Buffer.concat [
       @n.to_mpi_buffer()
@@ -44,6 +40,19 @@
     if err then [ err, null ]
     else [ null, new Pub(d), (orig_len - raw.length) ]

+  #----------------
+
+  verify : ([r, s], h, cb) ->
+    err = null
+    hi = bi_from_left_n_bits h, @q.bitLength()
+    w = s.modInverse @q
+    u1 = hi.multiply(w).mod(@q)
+    u2 = r.multiply(w).moq(@q)
+    v = @g.modPow(u1, @p).multiply(@y.modPow(u2, @p)).mod(@p).mod(@q)
+    if not v.equals(s)
+      err = new Error "hash mismatch"
+    cb err
+
 #=================================================================

 class Pair
@@ -59,7 +68,11 @@
     err = null
     [err, sig] = Pair.read_sig_from_buf(sig) if Buffer.isBuffer(sig)
     hash = hasher data
-    await @verify sig, defer v
+    if sig.length isnt 2
+      err = new Error "Expected 2 Bigints in the signature"
+    else
+      await @verify sig, hash, defer err, v
+    cb err

   #----------------


:fire: padding

:fire: padding

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -66,12 +66,12 @@
   '+':     'vim-mode-plus:move-to-first-character-of-line-down'
   'enter': 'vim-mode-plus:move-to-first-character-of-line-down'

-  'ctrl-u':  'vim-mode-plus:scroll-half-screen-up'
-  'ctrl-b':  'vim-mode-plus:scroll-full-screen-up'
-  'ctrl-d':  'vim-mode-plus:scroll-half-screen-down'
-  'ctrl-f':  'vim-mode-plus:scroll-full-screen-down'
-  'ctrl-e':  'vim-mode-plus:scroll-down'
-  'ctrl-y':  'vim-mode-plus:scroll-up'
+  'ctrl-u': 'vim-mode-plus:scroll-half-screen-up'
+  'ctrl-b': 'vim-mode-plus:scroll-full-screen-up'
+  'ctrl-d': 'vim-mode-plus:scroll-half-screen-down'
+  'ctrl-f': 'vim-mode-plus:scroll-full-screen-down'
+  'ctrl-e': 'vim-mode-plus:scroll-down'
+  'ctrl-y': 'vim-mode-plus:scroll-up'

   'z enter': 'vim-mode-plus:scroll-cursor-to-top'
   'z t':     'vim-mode-plus:scroll-cursor-to-top-leave'


improved search bar query parsing

improved search bar query parsing

--- app/assets/javascripts/admin/models/pagination_collection.coffee
+++ app/assets/javascripts/admin/models/pagination_collection.coffee
@@ -115,8 +115,9 @@
       @state.filterQuery = ""
       @state.filter = null
     else
-      words = _.map(query.match(/\w+/ig), (element) -> element.toLowerCase())
-      pattern = "(" + _.uniq(words).join("|") + ")"
+      words = _.map(query.split(" "), (element) -> element.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"))
+      uniques = _.filter(_.uniq(words), (element) -> element != '')
+      pattern = "(" + uniques.join("|") + ")"
       regexp = new RegExp(pattern, "igm")

       @state.filterQuery = query


Get categories from store on init; define user categories filtered list

--- app/assets/javascripts/controllers/category-controller.js.coffee
+++ app/assets/javascripts/controllers/category-controller.js.coffee
@@ -1 +1,7 @@
-App.CategoriesController = Em.ArrayController.extend()
+App.CategoriesController = Em.ArrayController.extend
+  init: ->
+  	@_super()
+  	@set 'content', App.Category.findAll()
+  userCategories: (->
+    @get('content').filterBy('system_type', null)
+  ).property('content.@each.system_type')


cleanup

cleanup

--- app/assets/javascripts/inventory/views/getting_started.js.coffee
+++ app/assets/javascripts/inventory/views/getting_started.js.coffee
@@ -16,6 +16,3 @@
         setTimeoutR 0, ->
           e.target.select()
     }
-
-    # after_render: ->
-    #   @app.router.changeURL @state.create_url()


Fixed bug causing import cancel to fail.

--- src/EntityImporter.coffee
+++ src/EntityImporter.coffee
@@ -481,7 +481,7 @@
         Logger.info('Import request started', importId)
       Promises.runSync ->
         promise = EntityImporter.fromAsset(args)
-        if df? then df.resolve(promise)
+        promise.then(df.resolve, df.reject)
         promise

     'entities/from/asset/cancel': (args) ->
@@ -489,7 +489,9 @@
       Logger.info('Cancelling import request...', importId)
       @unblock()
       df = importRequestMap[importId]
-      unless df
-        throw new Meteor.Error(500, 'Import cancel request not found with ID: ' + importId)
-      df.reject('Import cancelled')
-      Logger.info('Import request cancelled', importId)
+      if df
+        df.reject('Import cancelled')
+        Logger.info('Import request cancelled', importId)
+      else
+        Logger.warn('Import request cannot be cancelled', importId)
+


Handle some special cases for initialization

--- src/main/client/admin/admin-study-labels-editor-controller.coffee
+++ src/main/client/admin/admin-study-labels-editor-controller.coffee
@@ -43,7 +43,7 @@

     ## Safely propogate rules back into the study if they are different
     ruleWatcher = (newRules, oldRules) ->
-      if typeof newRules != 'undefined'
+      if typeof newRules != 'undefined' and $scope.study? and ! angular.equals newRules, oldRules
         $scope.study.study.options.stateRules = newRules
         $scope.$emit 'admin:modified'


Use commmon Fira Mono while Atom has ligatures issue

--- atom.cson
+++ atom.cson
@@ -1,6 +1,6 @@
 '*':
   'editor':
-    'fontFamily': 'Fira Code'
+    'fontFamily': 'Fira Mono OT'
     'showIndentGuide': true
     'tabLength': 4
     'softWrap': true


Single quotes can't do interpolation

Single quotes can't do interpolation

--- scripts/card-fetcher.coffee
+++ scripts/card-fetcher.coffee
@@ -36,7 +36,7 @@

 getCardImageUrl = (cardName, cardDB) ->
   verifiedCard = verifyCard(cardName, cardDB)
-  fullUrl = '#{urlBase}#{verifiedCard}'
+  fullUrl = urlBase + verifiedCard

 module.exports = (robot) ->
   robot.hear /\[\[(.*)\]\]/i, (msg) ->


Adding FIXME note.

Adding FIXME note.

--- src/timber.coffee
+++ src/timber.coffee
@@ -205,6 +205,8 @@

     # Create a new environment.
     constructor: () ->
+        #FIXME: add arguments for gravity and whatnot.
+
         @logger = new timber.Logger("timber.Environment")
         @logger.debug("Initializing")


Comment typo.

Comment typo.

--- src/timber.coffee
+++ src/timber.coffee
@@ -44,6 +44,7 @@
     # @param message {String} the message to log.
     info : (message) ->
         @_write("INFO", message)
+

 #
 # The timber canvas.
@@ -76,6 +77,7 @@
     clear: () ->
         @context.clearRect(0, 0, @canvas.width, @canvas.height)

+
 class timber.Point

     # Create a point.
@@ -103,7 +105,7 @@
     # vector.
     #
     # @param vector {Object}
-    # @return {Objec} the resultant point
+    # @return {Object} the resultant point
     sum : (vector) ->
         return new timber.Vector(@x + vector.x, @y + vector.y)


Account: account deletion modal survey added

--- client/Account/views/deleteaccountview.coffee
+++ client/Account/views/deleteaccountview.coffee
@@ -39,10 +39,18 @@
     data = KD.nick()

     options.title     or= 'Please confirm account deletion'
-    options.content   or= "

CAUTION! This will destroy everything you have on Koding, including your data on your VM(s). This action CANNOT be undone.

Please enter #{data} into the field below to continue:

"
+    options.content   or= """
+

+

+          CAUTION! This will destroy everything you have on Koding, including your data on your VM(s). This action CANNOT be undone.
+

+

+
Please enter #{data} into the field below to continue:

+

+      """
     options.callback   ?= -> log "#{options.action} performed"
     options.overlay    ?= yes
-    options.width      ?= 500
+    options.width      ?= 520
     options.height     ?= 'auto'
     options.tabs       ?=
       forms                  :
@@ -56,12 +64,25 @@
             JUser.unregister username.getValue(), (err)->
               if err then new KDNotificationView title : 'There was a problem, please try again!'
               else
-                new KDNotificationView title : 'Thank you for trying Koding!'
+                surveyLink = "https://docs.google.com/forms/d/1fiC6wSThfXxtLpdRlQ7qnNvJrClqdUrmOT_L-_cu1tw/viewform"
+                @setTitle 'Account successfully deleted'
+                @setContent """
+

+

+                      Thanks for trying out Koding. We'd appreciate if you tell us why.
+

+

+
Click to take the 1 minute survey.

+

+                  """
+                  # Loading...
+                @_windowDidResize()
                 KD.mixpanel "Delete account, success"
-                KD.utils.wait 2000, ->
+                KD.utils.wait 30000, ->
                   $.cookie 'clientId', erase : yes
                   location.replace '/'
               confirmButton.hideLoader()
+
           buttons            :
             confirmButton    :
               title          : 'Delete Account'


Don't conclude "winner is clear" using z-stats until the distribution is reasonably Gaussian.

--- web/scoreTracker.coffee
+++ web/scoreTracker.coffee
@@ -46,9 +46,12 @@
   updateScores: =>
     @proportions = (score / @games for score in @scores)

+  # This should be using a binomial distribution,
+  # but N>=40 is probably Gaussian enough
   decisiveWinner: =>
     for i in [0...@players.length]
-      if @proportions[i] - this.errorMargin() > 1 / @scores.length
+      if @proportions[i] - this.errorMargin() > 1 / @scores.length \
+      and @scores.length >= 40
         return @players[i]
     return null


Make solid archive

Make solid archive

--- webpack/dist/sfx.coffee
+++ webpack/dist/sfx.coffee
@@ -46,7 +46,7 @@
         return
       if !relative and skipFile file
         return
-      zip.addFile full, short
+      zip.addFile full, short, compress: false
   zip.end()

 zipTree PACKAGE.mingzi, require './repo'


Add href to avatar link

Add href to avatar link

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -26,7 +26,7 @@
         @span outlet: 'packageDescription', class: 'package-description', description

       @div class: 'meta', =>
-        @a outlet: 'avatarLink', =>
+        @a outlet: 'avatarLink', href: "https://atom.io/users/#{owner}", =>
           @img outlet: 'avatar', class: 'avatar', src: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' # A transparent gif so there is no "broken border"
         @a outlet: 'loginLink', class: 'author', href: "https://atom.io/users/#{owner}", owner
         @div class: 'meta-right', =>


collaboration: assign rtm to panes on collab active

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -648,6 +648,9 @@
     @bindRealtimeEvents()
     @bindSocialChannelEvents()

+    # attach RTM instance to already in-screen panes.
+    @forEachSubViewInIDEViews_ @bound 'setRealtimeManager'
+

   transitionViewsToActive: ->


ace: Removed compile button reference.

ace: Removed compile button reference.

--- client/landing/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Ace.kdapplication/AppView.coffee
@@ -79,7 +79,6 @@
     @getSingleton('kodingAppsController').compileApp manifest.name, (err) =>
       @ace.notify "Trying to run old version..." if err
       @getSingleton('kodingAppsController').runApp manifest
-      @compileAndRunButton.hideLoader()

   viewAppended:->


landing: reset-password: fix reference error on error state

--- client/landing/site.landing/coffee/team/tabs/teamresettab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamresettab.coffee
@@ -48,7 +48,7 @@
       type      : 'POST'
       error     : (xhr) =>
         { responseText } = xhr
-        @resetForm.button.hideLoader()
+        @form.button.hideLoader()
         new kd.NotificationView { title : responseText }
       success   : ({ username }) =>
         @form.button.hideLoader()


feat(quotes): Split quotes into three arrays

Quotes arranged by tense (sort of) so hubot can respond better
to "tucker me" and "tucker <name>" requests.
Written respond method for these newly arranged quotes.

--- src/tucker.coffee
+++ src/tucker.coffee
@@ -18,67 +18,82 @@
 #   ocean
 #   with thanks to arsenio and danriti

-quotes = [
-  'He\'s useless. He\'s absolutely useless. He\'s as useless as a marzipan dildo.',
-  'You don\'t seem to understand that I\'m going to have to have to mop up a fucking hurricane of piss from all these neurotics.',
-  'Feet off the furniture, you Oxbridge twat. You\'re not in a punt now.',
-  'NOMFuP. N-O-M-F-P. Not My Fucking Problem. I quite like that. Did you like that? I\'ll use that quite a lot today.',
-  '*knock* *knock* ... Come the fuck in or fuck the fuck off.',
+secondPerson = [
+  'You don\'t seem to understand that I\'m going to have to mop up a fucking hurricane of piss from all these neurotics.',
   'All these hands all over the place! You were like a sweaty octopus trying to unhook a bra.',
   'Shut it! You\'re using all the minutes on my "Talk until you get head cancer" tariff.',
-  'I mean the guy is an epic fuck-up. He\'s so dense that light bends around him.',
   'Do you know ninety percent of household dust is made of dead human skin? That\'s what you are, to me.',
   'What\'s up with you? You look like you\'ve shat a LEGO garage.',
   'Jesus Christ, see you, you\'re a fucking omnishambles, that\'s what you are. You\'re like that coffee machine, you know: from bean to cup, you fuck up.',
-  'I think we should use the \'carrot and stick\' approach. You take a carrot, you stick it up his fucking arse, followed by the stick, followed by an even bigger, rougher carrot.',
   'I\'d love to stop and chat to you but I\'d rather have Type 2 diabetes.',
   'I will tear your fucking skin off, I will wear it to your mother\'s birthday party, and rub your nuts up and down her leg whilst whistling Bohemian fucking Rhapsody. Right?',
-  'I could always go and fire a warning shot across his throat.',
   'Stop fucking blinking! Or I will take your optic nerve and strangle you with it!',
   'Are you sure you\'re working as hard as I am, \'cause I\'m sweating fucking spinal fluid here?!',
+  'I\'ve come across a lot of psychos, but none as fucking boring as you.',
+  'You\'re so back-bench, you\'ve actually fucking fallen off. You\'re out by the fucking bins where I put you.',
   'Don\'t get sarcastic with me, son. We burned this tight-arsed city to the ground in 1814. And I\'m all for doing it again, starting with you, you frat fuck. You get sarcastic with me again and I will stuff so much cotton wool down your fucking throat it\'ll come out your arse like the wee tail on a Playboy bunny.',
   'I will marshal all the forces of darkness to hound you into an assisted suicide.',
   'How dare you! How dare you! Don\'t you ever, EVER, call me a bully! I\'m so much worse than that.',
   'I will rain down on you so hard, you will have to be reassembled by fucking air crash investigators.',
   'Okay, I\'m just going to explain what I\'m gonna fucking to do to you! I\'m gonna take your bollocks, I\'m gonna fucking rip them off, I\'m gonna fucking paint eyeballs on them! And then stitch them to a FUCKING SOCK and use THAT as a mouthpiece!',
   'Go stand in that fucking corner! STAND OVER THERE, RIGHT?! And do not move! Or I will perform a fucking, LIVING fucking autopsy on you! With a fucking rusty spade and I\'ll have your kidneys for fucking cufflinks!',
-  'We are in a prison drama. This is like The Shawshank Redemption, only with more tunneling through shit and no fucking redemption.',
-  'She\'s going to have to fall on her sword. Which means that we have to stick one in the ground, trip her onto it and get someone to jump up and down on her back for ten minutes.',
   'You\'ve got all the charm of a rotting teddy bear by a graveside.',
   'Christ on a bendy bus son, don\'t be such a fucking faff arse.',
+  'I\'m gonna come over there, I\'m gonna lock you in a fucking flotation tank and pump it full of sewage until you fucking drown.',
   'Within your \'purview\'? Where do you think you are, some fucking regency costume drama? This is a government department, not some fucking Jane fucking Austen novel! Allow me to pop a jaunty little bonnet on your purview and ram it up your shitter with a lubricated horse cock.',
-  'Fuckity bye!',
-  'I\'m gonna come over there, I\'m gonna lock you in a fucking flotation tank and pump it full of sewage until you fucking drown.',
-  "I've got a to-do list here that's longer than a fucking Leonard Cohen song.",
-  'Do you know what\'s really fucking sad here, is that I don\'t have the energy to pretend I already knew. Which is for the best, because I\'m gonna need all of my fucking energy to fucking rip all of your bodies to bits with my bare hands and sell off, yes, sell off your fucking flayed skin, as a sleeping bag, to a fucking NORMAL PERSON!',
   'I wouldn\'t fucking piss on you if you were fucking allergic to piss, right?',
   'Get over here. Now. Might be advisable to wear brown trousers and a shirt the colour of blood.',
   'Enough! E-fucking-nough. You need to learn to shut your fucking cave. Right? Today, you have laid your first big fat egg of solid fuck. You took the data loss media strategy and you ate with a lump of E.coli. And then you sprayed it our of your arse at 300 mph.',
-  'Fuck me! This is like a clown running across a minefield!',
   'You know fuck all about me! I am totally beyond the realms of your fuckin\' tousle-haired fuckin\' dim-witted compre-fucking-hension. I don\'t just take this fucking job home, you know! I take this job home, it fucking ties me to the bed, and it fuckin\' fucks me from arsehole to breakfast! Then it wakes me up in the morning with a cup full of piss slammed in my face, slaps me about the chops to make sure I\'m awake enough so it can kick me in the fucking bollocks! This job has taken me in every hole in my fucking body. "Malcolm!", it\'s gone, you can\'t know Malcolm because Malcolm is not here! Malcolm fucking left the building fucking years ago! This is a fucking husk, I am a fucking host for this fucking job. Do you want this job? Yes? You do fucking want this job? Then you\'re gonna have to swallow this whole fucking life and let it grow inside you like a parasite, getting bigger and bigger and bigger until it fucking eats your insides alive and it stares out of your eyes and tells you what to do.',
   'How fucking dare you? Have you any idea of the amount of pressure that has been exerted on my skull, huh? It feels like my brain has been fucking emptied into little packets, into fucking crisp packets. Cheese and onion fucking crisp packets that contain my living, breathing fucking brain.',
-  'The exchange of private information - that is what drives our economy. But, you come after me because you can\'t arrest a landmass, can you? You can\'t cuff a country. You can\'t lynch that guy there, can you? But you decide that you can sit there, that you can judge and can ogle me like a Page 3 girl. You don\'t like it? Well, you don\'t like yourself! You don\'t like your species. And you know what? Neither do I, but how dare you come and lay this at my door! How dare you blame me for this! Which is the result of a political class, which has given up on morality and simply pursues popularity at all costs. I am you and you are me.',
-    'How much fucking shit is there on the menu and what fucking flavour is it?',
-    'You\'re so back-bench, you\'ve actually fucking fallen off. You\'re out by the fucking bins where I put you.',
-    'Please could you take this note, ram it up his hairy inbox and pin it to his fucking prostate.',
-    'I\'ve come across a lot of psychos, but none as fucking boring as you.',
-    'I\'ve never seen anyone look so fucking ugly with only one head.',
-    'I need you lot to make like a tree... and go fuck yourselves.',
-    'Kiss my sweaty balls, you fat fuck.',
-    'Yeah, ok. Fuckity bye!'
 ]

+thirdPerson = [
+  'He\'s useless. He\'s absolutely useless. He\'s as useless as a marzipan dildo.',
+  'I mean the guy is an epic fuck-up. He\'s so dense that light bends around him.',
+  'I think we should use the \'carrot and stick\' approach. You take a carrot, you stick it up his fucking arse, followed by the stick, followed by an even bigger, rougher carrot.',
+  'I could always go and fire a warning shot across his throat.',
+  'I\'ve never seen anyone look so fucking ugly with only one head.',
+  'Please could you take this note, ram it up his hairy inbox and pin it to his fucking prostate.',
+  'She\'s going to have to fall on her sword. Which means that we have to stick one in the ground, trip her onto it and get someone to jump up and down on her back for ten minutes.',
+]
+
+restOfQuotes = [
+  'Feet off the furniture, you Oxbridge twat. You\'re not in a punt now.',
+  'NOMFuP. N-O-M-F-P. Not My Fucking Problem. I quite like that. Did you like that? I\'ll use that quite a lot today.',
+  '*knock* *knock* ... Come the fuck in or fuck the fuck off.',
+  'I need you lot to make like a tree... and go fuck yourselves.',
+  'Kiss my sweaty balls, you fat fuck.',
+  'Yeah, ok. Fuckity bye!',
+  'I am you and you are me.',
+  'How much fucking shit is there on the menu and what fucking flavour is it?',
+  'We are in a prison drama. This is like The Shawshank Redemption, only with more tunneling through shit and no fucking redemption.',
+  'Fuckity bye!',
+  'I\'ve got a to-do list here that\'s longer than a fucking Leonard Cohen song.',
+  'Do you know what\'s really fucking sad here, is that I don\'t have the energy to pretend I already knew. Which is for the best, because I\'m gonna need all of my fucking energy to fucking rip all of your bodies to bits with my bare hands and sell off, yes, sell off your fucking flayed skin, as a sleeping bag, to a fucking NORMAL PERSON!',
+  'Fuck me! This is like a clown running across a minefield!',
+]
+
+mostOfQuotes = secondPerson.concat thirdPerson
+
+allQuotes = mostOfQuotes.concat restOfQuotes
+
 module.exports = (robot) ->
-  robot.respond /tucker/i, (res) ->
-    quote = res.random quotes
-    res.send "\"#{quote}\""
+  robot.respond /tucker (.*)/i, (res) ->
+    person = res.match[1]
+    if person is "me"
+      quote = res.random thirdPerson
+      res.send "Malcolm was talking about you earlier, he said \"#{quote}\""
+    else
+      quote = res.random secondPerson
+      res.send "I heard Malcolm talking to #{person}, he was saying \"#{quote}\""

   robot.hear /tucker/i, (res) ->
-      quote = res.random quotes
+      quote = res.random allQuotes
       res.send "\"#{quote}\""

   unless process.env.HUBOT_LESS_MALCOLM
     robot.hear /malc(\s+|olm)/i, (res) ->
-      quote = res.random quotes
+      quote = res.random allQuotes
       res.send "\"#{quote}\""


Disable atom-ternjs keybindings

Disable atom-ternjs keybindings

--- .config/atom/config.cson
+++ .config/atom/config.cson
@@ -23,6 +23,7 @@
       "refactor"
       "sort-lines"
       "csslint"
+      "atom-ternjs"
     ]
     telemetryConsent: "limited"
   csslint:


Battle: Fixed teleport crashing the server

--- server/bw/data/moves.coffee
+++ server/bw/data/moves.coffee
@@ -806,8 +806,8 @@
     halfHP = Math.floor(target.currentHP / 2)
     Math.max(1, halfHP)
 makeWeatherRecoveryMove 'Synthesis'
-extendMove 'Teleport', (battle) ->
-  @execute = -> @fail(battle)
+extendMove 'Teleport', ->
+  @execute = (battle) -> @fail(battle)
 makeThiefMove 'Thief'

 extendMove 'Thunder', ->


the best javascript color library

the best javascript color library

--- test/color.coffee
+++ test/color.coffee
@@ -139,3 +139,7 @@
 test "Color should output proper toString", ->
   color = Color(5, 25, 125, 0.73)
   equals color.toString(), "rgba(5, 25, 125, 0.73)"
+
+test "Color should output proper toHex", ->
+  color = Color(255, 255, 255)
+  equals color.toHex(), "#ffffff"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -458,13 +458,5 @@
   equalEnough piPink.b, 153
   equalEnough piPink.a, 1.0

-test "fromHue", ->
-  red = Color.fromHue(60)
-
-  equalEnough red.r, 255
-  equalEnough red.g, 0
-  equalEnough red.b, 0
-  equalEnough red.a, 1
-
 module()


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -94,6 +94,7 @@
         #d.innerRadius = 0 #(radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
         #d.outerRadius = radiuso
         "translate(" + arc.centroid(d) + ")")
+
         #c = arc.centroid(d)
         #x = c[0]
         #y = c[1]
@@ -105,7 +106,8 @@
         #    r = labelRadius
         #return "translate(" + (x/h * labelRadius) + "," + (y/h * labelRadius) + ")"
         #return "translate(" + r + "," + y + ")"#(y/h * labelRadius) + ")"
-        )
+        #)
+
         .attr('fill', "#fff")
         .attr("text-anchor", "middle").text((d, i) ->
           if data[i].value != 0


Agent pie testing

Agent pie testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -248,6 +248,7 @@
             maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
             return
         )
+        .attr("x",0),attr("y",0)
         .transition()
             .duration(1000)
             .attr('x', (d) ->


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -585,8 +585,8 @@
       bbox = @getBBox()
       #bbox.width and bbox.height will
       #describe the size of the label text
-      labelRadius = radius - 20
-      #labelRadius = radiuso * 1.3
+      #labelRadius = radius - 20
+      labelRadius = radiuso * 1.3
       d.x = Math.sin(a) * labelRadius
       d.l = d.x - bbox.width / 2 - 2
       d.r = d.x + bbox.width / 2 + 2


More debug output

More debug output

--- server/models/submit.coffee
+++ server/models/submit.coffee
@@ -62,10 +62,15 @@
         s1 = @source.replace("\r", "")
         s2 = other.source.replace("\r", "")
         logger.log "s1,s2.length=", s1.length, s2.length, @_id, other._id
+        ll = ""
+        ll += "s1,s2.length=#{s1.length}, #{s2.length} "
         for i in [0..s1.length - 1]
             logger.log i, s1.charCodeAt(i), s2.charCodeAt(i), @_id, other._id
+            ll += "[#{i}, #{s1.charCodeAt(i)}, #{s2.charCodeAt(i)}]"
             if s1.charAt(i) != s2.charAt(i)
-                logger.log "!!!", i, s1.charCodeAt(i), s2.charCodeAt(i), @_id, other._id
+                logger.log "bad", i, s1.charCodeAt(i), s2.charCodeAt(i), @_id, other._id
+                ll += "^"
+        logger.log "ll=", ll
     if @comments.length > 0
         return false
     if @outcome == "AC" or @outcome == "IG" or @outcome == "DQ"


new MathJax root specification thanks to https://github.com/mathjax/MathJax/pull/1525

--- client/lib/mathjax.coffee
+++ client/lib/mathjax.coffee
@@ -23,9 +23,10 @@
     ignoreClass: 'nojax'
     processClass: 'tex2jax'
   skipStartupTypeset: true
+  root: '/mathjax'
   ## This is a workaround until https://github.com/mathjax/MathJax/issues/1403
   ## gets solved (see https://github.com/mathjax/MathJax/issues/1399)
-  AuthorInit: -> MathJax.Ajax.config.root = '/mathjax'
+  #AuthorInit: -> MathJax.Ajax.config.root = '/mathjax'

 Meteor.startup ->
   $('body').addClass 'nojax'


Add hook to substitution

Add hook to substitution

--- annotran/static/scripts/widget-controller.coffee
+++ annotran/static/scripts/widget-controller.coffee
@@ -33,6 +33,7 @@
 eventsa = require('./events')
 events = require('../../../../h/h/static/scripts/events.js');

+substitution = require('./annotator/scripts/substitution)

 widgetcontroller =  require('../../../../h/h/static/scripts/widget-controller.coffee');

@@ -74,6 +75,7 @@
         if offset < total
           _loadAnnotationsFrom query, offset

+        substitution.multipleSubstitution(results.rows)
         annotationMapper.loadAnnotations(results.rows, results.replies)

     loadAnnotations = (frames) ->


Changed precision of value labels to single decimal

Closes #1451

--- app/assets/javascripts/d3/waterfall.coffee
+++ app/assets/javascripts/d3/waterfall.coffee
@@ -115,7 +115,7 @@
           else
             @y(d.bottom) + 10
         )
-        .text((d) => Metric.autoscale_value d.value, @model.get('unit'))
+        .text((d) => Metric.autoscale_value d.value, @model.get('unit'), 1)

     # The final label is not defined in the chart attributes, so we must add
     # add it manually. This is ugly!


add client-side DownloadModal class

add client-side DownloadModal class

--- app/assets/javascripts/downloads.js.coffee
+++ app/assets/javascripts/downloads.js.coffee
@@ -1,3 +1,46 @@
-# 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/
+class @DownloadModal
+  DEFAULT_TYPES = ['csv', 'shp', 'kml']
+  BASE_DOWNLOAD_PATH = '/downloads'
+
+  @template: """
+

+

+        Select a download type
+         Last updated on <%= @protected_area.created_at.strftime('%e %B %Y') %>
+

+

+

+
…or use the ESRI web service

+

+

+        Cancel
+

+

+  """
+
+  constructor: ->
+    @$el = $(@constructor.template)
+    @$el.find('#close-modal').on('click', (e) =>
+      @hide()
+      e.preventDefault()
+    )
+
+  buildLinksFor: (objectName, types) ->
+    types ||= DEFAULT_TYPES
+    linkContainer = @$el.find('#link-container')
+
+    newLinks = types.map((type) ->
+      typeLinkText = type.toUpperCase()
+      typeLinkHref = "#{BASE_DOWNLOAD_PATH}/#{objectName}?type=#{type}"
+
+      newLink = "#{typeLinkText}"
+      newLink
+    )
+
+    linkContainer.html(newLinks)
+
+  show: ->
+    @$el.addClass('opened')
+
+  hide: ->
+    @$el.removeClass('opened')


Fixed spacing issue.

Fixed spacing issue.

--- lib/bibtex-provider.coffee
+++ lib/bibtex-provider.coffee
@@ -24,9 +24,9 @@
         for citation in bibtex
           citation.entryTags.prettyTitle =
             @prettifyTitle citation.entryTags.title
+
           citation.entryTags.authors =
             @cleanAuthors citation.entryTags.author?.split ' and '
-
           citation.entryTags.prettyAuthors =
             @prettifyAuthors citation.entryTags.authors


quote values in form field selectors

quote values in form field selectors

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -154,7 +154,7 @@

   setSelectFields: (formModel) ->
     this.setFieldValues formModel, "select", ($select, value) =>
-      $select.find("option[value=#{value}]").attr("selected", "selected")
+      $select.find("option[value=\"#{value}\"]").attr("selected", "selected")

   setCheckboxes: (formModel) ->
     this.setFieldValues formModel, "input:checkbox", ($checkbox, value) =>
@@ -162,7 +162,7 @@

   setRadioButtons: (formModel) ->
     this.setFieldValues formModel, "input:radio", ($radio, value) =>
-      $radio.find("[value=#{value}]").attr("checked", "checked")
+      $radio.find("[value=\"#{value}\"]").attr("checked", "checked")

   setFormFields: (formModel) ->
     this.fillTextFields(formModel)


tutorial fix

tutorial fix

--- app/assets/javascripts/tag_building.coffee
+++ app/assets/javascripts/tag_building.coffee
@@ -284,17 +284,17 @@
 						}
 						{
 							element: "#no-button"
-							intro: "Press NO."
-							position: "top"
-						}
-						{
-							element: "#map-highlight"
-							intro: "Laugh at the poor computer and move on."
+							intro: "Teach that computer a lesson. Press NO."
+							position: "top"
+						}
+						{
+							element: "#map-highlight"
+							intro: "Yikes! It really screwed that one up."
 							position: "top"
 						}
 						{
 							element: "#no-button"
-							intro: "Press NO."
+							intro: "Laugh at the poor computer and move on. Press NO."
 							position: "top"
 						}
 						{


When clicking a tag suggestion, prevent event propagation.

If we don't do this, then we'll go to #, which plays quite badly with
Ember's history tracking and manipulation code.

--- app/assets/javascripts/views/tagger.coffee
+++ app/assets/javascripts/views/tagger.coffee
@@ -82,8 +82,9 @@
     mouseEnter: ->
       @set 'parentView.currentIndex', @get('contentIndex')

-    click: ->
+    click: (e) ->
       @get('parentView').willAddSelection()
+      e.preventDefault()

   willAddSelection: ->
     @get('parentView').willAddSelection()


Added read/write exclusivity tests to basic Lock unit Tests

--- test/index.coffee
+++ test/index.coffee
@@ -197,6 +197,13 @@
           assert.equal lock2.update, null
           assert.equal lock2.heldLock, null

+      describe 'obtainWriteLock', () ->
+        it "should fail to return a valid write lock", (done) ->
+          lock2.obtainWriteLock (e, ld) ->
+            assert.ifError e
+            assert.equal ld, null
+            done()
+
     describe 'obtainWriteLock', () ->

       lock1 = null
@@ -238,8 +245,14 @@
           assert.throws (() -> throw e), /Cannot obtain an already held lock/
           done()

-      it "should fail to return a valid second write lock on a different lock object", (done) ->
+      it "should fail to return a valid second write lock", (done) ->
         lock2.obtainWriteLock (e, ld) ->
+          assert.ifError e
+          assert.equal ld, null
+          done()
+
+      it "should fail to return a valid read lock", (done) ->
+        lock2.obtainReadLock (e, ld) ->
           assert.ifError e
           assert.equal ld, null
           done()


[test] RedisModel: early return callbacks that don't throw errors

--- test/index.coffee
+++ test/index.coffee
@@ -323,6 +323,14 @@
           done()

   describe "##fullKeysForList", ->
+    it "should return early and empty if not provided keys", (done) ->
+      ideally = errify done
+
+      await instance.fullKeysForList null, ideally defer fullKeys
+      expect(fullKeys).to.be.empty
+
+      done()
+
     it "should find queues for unknown ids", (done) ->
       ideally = errify done
       data    = {name: "testjob"}
@@ -348,6 +356,13 @@
       done()

   describe "##unknownKeysForIds", ->
+    it "should return early and empty if not provided ids", (done) ->
+      ideally = errify done
+
+      await instance.unknownKeysForIds null, ideally defer fullKeys
+      expect(fullKeys).to.be.empty
+
+      done()

   describe "##formatJobs", ->
     queues   = []
@@ -372,6 +387,14 @@
         await qCleaner(queue).asCallback ideally defer _
       queues  = []
       jobIds  = []
+
+      done()
+
+    it "should return early and empty if not provided keys", (done) ->
+      ideally = errify done
+
+      await instance.formatJobs null, null, ideally defer jobs
+      expect(jobs).to.be.empty

       done()

@@ -392,6 +415,14 @@
       done()

   describe "##remove", ->
+    it "should return early and empty if not provided jobs", (done) ->
+      ideally = errify done
+
+      await instance.remove null, ideally defer result
+      expect(result).to.be.empty
+
+      done()
+
     it "should remove jobs by id", (done) ->
       ideally = errify done
       data    = {name: "testjob"}


Added default to calculate_range call

Added default to calculate_range call

--- app/assets/javascripts/models/accounts.js.coffee
+++ app/assets/javascripts/models/accounts.js.coffee
@@ -19,6 +19,8 @@
               })

     @calculateRange: (fromDate, toCurrent, callback = ->) ->
+      fromDate = @time unless fromDate.month
+
       if toCurrent
           if @time.year < fromDate.year and @time.month < fromDate.month
               return callback()


grey icon a bit

grey icon a bit

--- app/assets/javascripts/mtl/document_modal.coffee
+++ app/assets/javascripts/mtl/document_modal.coffee
@@ -37,7 +37,7 @@
 MTL.templates.preview_unknown = """

-      <%= MTL.icon('visibility_off', { size: 'large' }) %>
+      <%= MTL.icon('visibility_off', { size: 'large grey-text' }) %>

     <a href="<%- url %>" target="_blank" class="btn btn-primary truncate">
       <%= MTL.icon('file_download', { class: 'left' }) %>


HOPER-3184 Require jquery-ui in widget for correct integration with third party services

--- hbw/app/assets/javascripts/hbw_dependencies/noconflict.js.coffee
+++ hbw/app/assets/javascripts/hbw_dependencies/noconflict.js.coffee
@@ -1,6 +1,7 @@
 #= require_self
 #= require jquery
 #= require jquery.formatter
+#= require jquery-ui
 #= require moment
 #= require moment/ru
 #= require bootstrap-datetimepicker


Adding test case for type of query that failed due to a bug in underscore-query.

It passed without any changes in query-predicate :-)

--- test/suite.coffee
+++ test/suite.coffee
@@ -623,6 +623,18 @@
     a = create()
     result = _query a, {$nor: [{likes: { $gt: 2}}, {likes: { $lt: 20}}]}
     assert.equal result.length, 0
+
+  it "compound $ands (underscore-query #29)", ->
+    a = create()
+
+    res = _query(a, {
+      $and: [{
+        $and: [ likes: {$gt: 5 } ]
+      }]
+    })
+
+    assert.equal(res.length, 2)
+

 #  This query is not a valid MongoDB query, but if it were one would expect it to yield an empty set
   #  it "$nor combination of $gt and $lt  - values", ->


Making coffee script optional

Making coffee script optional

--- test/suite.coffee
+++ test/suite.coffee
@@ -1,6 +1,7 @@
 Queue = require 'backbone-orm/lib/queue'

 option_sets = require 'backbone-orm/test/option_sets'
+option_sets = option_sets.slice(0, 5)

 test_queue = new Queue(1)
 for options in option_sets


Fix whitespace

Fix whitespace

--- project.coffee
+++ project.coffee
@@ -105,21 +105,19 @@
         label: translate 'div', 'notAllMarked'
       }]

-  tutorialSteps: [
-    {
-      image: './tutorial/example.jpg'
-      content: translate 'p', 'tutorial.welcome'
-    }, {
-      image: './tutorial/mark-penguins.jpg'
-      content: translate 'p', 'tutorial.marking'
-    }, {
-      image: './tutorial/delete-mark.jpg'
-      content: translate 'p', 'tutorial.removing'
-    }, {
-      image: './tutorial/other.jpg'
-      content: translate 'p', 'tutorial.other'
-    }, {
-      image: './tutorial/talk.jpg'
-      content: translate 'p', 'tutorial.talk'
-    }
-  ]
+  tutorialSteps: [{
+    image: './tutorial/example.jpg'
+    content: translate 'p', 'tutorial.welcome'
+  }, {
+    image: './tutorial/mark-penguins.jpg'
+    content: translate 'p', 'tutorial.marking'
+  }, {
+    image: './tutorial/delete-mark.jpg'
+    content: translate 'p', 'tutorial.removing'
+  }, {
+    image: './tutorial/other.jpg'
+    content: translate 'p', 'tutorial.other'
+  }, {
+    image: './tutorial/talk.jpg'
+    content: translate 'p', 'tutorial.talk'
+  }]


PROJECT NAME!

PROJECT NAME!

--- project.coffee
+++ project.coffee
@@ -2,7 +2,7 @@
 upload_form = new UploadForm

 module.exports =
-  id: 'orchids'
+  id: 'orchid'
   background: 'background.jpg'
   subjectGroup: false


factoid debug

factoid debug

--- scripts/factoid.coffee
+++ scripts/factoid.coffee
@@ -16,6 +16,8 @@
 #   ~~<user> <factoid> - Same as ~tell, less typing
 #   <factoid>? - Same as ~<factiod> except for there is no response if not found
 #   hubot no, <factoid> is <some phrase, link, whatever> - Replaces the full definition of a factoid
+#   hubot factoids list - List all factoids
+#   hubot factoid delete "<factoid>" - delete a factoid
 #
 # Author:
 #   arthurkalm
@@ -57,7 +59,7 @@
     @cache[key]

   list: ->
-    Object.keys(@cache)
+    Object.keys(@cache).join('\n')

   tell: (person, key) ->
     factoid = this.get key
@@ -93,8 +95,8 @@
   robot.respond /no, (.+) is (.+)/i, (msg) ->
     msg.reply factoids.setFactoid msg.match[1], msg.match[2]

-  robot.respond /factoids lsit/i, (msg) ->
-    msg.send factoids.list.join('\n')
+  robot.respond /factoids? list/i, (msg) ->
+    msg.send factoids.list

-  robot.respond /factoid delete "(.*)"$/i, (msg) ->
+  robot.respond /factoids? delete "(.*)"$/i, (msg) ->
     msg.reply factoids.delFactoid msg.match[1]


Fix `separately` typo.

Fix `separately` typo.

--- js/coffeescript/fitness_state_machine.coffee
+++ js/coffeescript/fitness_state_machine.coffee
@@ -93,7 +93,7 @@
         pre_action: ->
         post_action: ->
         question: ->
-          "Wow, that sounds complicated. Any chance that #{machine.noun} can be broken into smaller parts that you could test seperately?"
+          "Wow, that sounds complicated. Any chance that #{machine.noun} can be broken into smaller parts that you could test separately?"
         answer_type: 'short'
       },
       {
@@ -104,7 +104,7 @@
         pre_action: ->
         post_action: ->
         question: ->
-          "So does it do just one thing? Any chance that #{machine.noun}, or parts of it, can be isolated and test seperately?"
+          "So does it do just one thing? Any chance that #{machine.noun}, or parts of it, can be isolated and test separately?"
         answer_type: 'short'
       },
       {


code formatting fix

code formatting fix

--- scripts/standup.coffee
+++ scripts/standup.coffee
@@ -45,8 +45,8 @@
     standupDate.setUTCHours(standupHours + utcOffset)

     result = (standupDate.getUTCHours() == now.getUTCHours()) and
-    (standupDate.getUTCMinutes() == now.getUTCMinutes()) and
-    (standupDayOfWeek == -1 or (standupDayOfWeek == standupDate.getDay() == now.getUTCDay()))
+      (standupDate.getUTCMinutes() == now.getUTCMinutes()) and
+      (standupDayOfWeek == -1 or (standupDayOfWeek == standupDate.getDay() == now.getUTCDay()))

     if result then true else false

@@ -219,4 +219,4 @@
       else sendHelp msg
     return

-return+return


Those should be assignments.

Those should be assignments.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -33,15 +33,15 @@
     robot.respond /raffle start (\d+)$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
             cost = msg.match[1]
-            raffle is on
+            raffle = on
             save(robot)
             msg.send "A raffle has now started and costs " + cost + " points to enter. If you have enough, use !ticket to enter. You can only enter once per raffle."

     # Close raffle, drawn winner and reset.
     robot.respond /raffle draw$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
-            raffle is off
-            entered[username] is false
+            raffle = off
+            entered[username] = false
             save(robot)
             msg.send "The raffle is now closed. A winner will be announced in this message once finished."

@@ -51,7 +51,7 @@
         points[username] ?= 0

         if points[username] >= cost and raffle is on and entered[username] is false
-            entered[username] is true
+            entered[username] = true
             points[username] -= cost
             save(robot)
             msg.send "Test: raffle entered."


added null check for email property #ugly

--- services/web/app/coffee/Features/Email/EmailHandler.coffee
+++ services/web/app/coffee/Features/Email/EmailHandler.coffee
@@ -1,6 +1,11 @@
 settings = require("settings-sharelatex")
 EmailBuilder = require "./EmailBuilder"
 EmailSender = require "./EmailSender"
+
+if !settings.email?
+	settings.email =
+		lifecycleEnabled:false
+

 module.exports =


Sort the project entities by path

Sort the project entities by path

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -163,6 +163,7 @@
 					ProjectEntityHandler.getAllEntitiesFromProject project, (err, docs, files) ->
 						return next(err) if err?
 						entities = docs.concat(files)
+							.sort (a, b) -> a.path > b.path  # Sort by path ascending
 							.map (e) -> {
 								path: e.path,
 								type: if e.doc? then 'doc' else 'file'


Increase rollout to 5%

Increase rollout to 5%

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -276,7 +276,7 @@
 				# Extract data from user's ObjectId
 				timestamp = parseInt(user_id.toString().substring(0, 8), 16)

-				rolloutPercentage = 3 # Percentage of users to roll out to
+				rolloutPercentage = 5 # Percentage of users to roll out to
 				if !ProjectController._isInPercentageRollout('autocompile', user_id, rolloutPercentage)
 					# Don't show if user is not part of roll out
 					return cb(null, { enabled: false, showOnboarding: false })


Increase rollout to 40%

Increase rollout to 40%

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -253,7 +253,7 @@
 				# Extract data from user's ObjectId
 				timestamp = parseInt(user_id.toString().substring(0, 8), 16)

-				rolloutPercentage = 20 # Percentage of users to roll out to
+				rolloutPercentage = 40 # Percentage of users to roll out to
 				if !ProjectController._isInPercentageRollout('autocompile', user_id, rolloutPercentage)
 					# Don't show if user is not part of roll out
 					return cb(null, { enabled: false, showOnboarding: false })


altering accounts logging

altering accounts logging

--- server/accounts.coffee
+++ server/accounts.coffee
@@ -386,7 +386,8 @@
     srch = if u? then ' get ID' else '_id:"' + val + '" OR username.exact:"' + val + '" OR emails.address.exact:"' + val + '"'
   u ?= Users.find srch
   if u
-    API.log msg: 'Retrieved account for val ' + JSON.stringify(val) + ' with ' + JSON.stringify(srch), retrieved: u._id if u
+    srch = 'apikey' if val.apikey?
+    API.log msg: 'Retrieved account by ' + JSON.stringify(srch), retrieved: u?._id
     return u
   else
     return undefined


Fix tab toggle on activity

Fix tab toggle on activity

Signed-off-by: Dmitriy Zaporozhets <be23d75b156792e5acab51b196a2deb155d35d6a@gmail.com>

--- app/assets/javascripts/activities.js.coffee
+++ app/assets/javascripts/activities.js.coffee
@@ -12,7 +12,7 @@

   toggleFilter: (sender) ->
-    sender.parent().toggleClass "active"
+    sender.toggleClass "active"
     event_filters = $.cookie("event_filter")
     filter = sender.attr("id").split("_")[0]
     if event_filters


Fixed unmemoization.

Fixed unmemoization.

--- src/SchemaUtils.coffee
+++ src/SchemaUtils.coffee
@@ -31,9 +31,6 @@
       if field.collectionType
         refFields[fieldId] = field
     refFields
-
-  getSchemaReferenceFieldsMemoized: _.memoize(
-      SchemaUtils.getSchemaReferenceFields, Collections.getName.bind(Collections))

   getRefModifier: (model, collection, idMaps) ->
     modifier = {}
@@ -237,6 +234,11 @@
     units: 'm^2'
     calc: -> @calcArea(@model._id)

+# Define the memoized function separately, and use the unmemoized function by default. This allows
+# collection schemas to change at runtime and not receive stale output.
+SchemaUtils.getSchemaReferenceFieldsMemoized = _.memoize(@getSchemaReferenceFields,
+    Collections.getName.bind(Collections))
+
 ####################################################################################################
 # SCHEMA OPTIONS
 ####################################################################################################


updated variable names

updated variable names

--- app/coffee/drm-calendar/drm-calendar.coffee
+++ app/coffee/drm-calendar/drm-calendar.coffee
@@ -607,14 +607,17 @@
         daysInFirstWeek = @daysPerWeek - dayShift

         i = 1
+        # get the number of dates in each week
         while i <= numberWeeks
             dates = []

             if i is 1
+            # first week of the month
                 j = 0
                 while j < daysInFirstWeek
                     j = j + 1
                     dates.push j
+            # middle weeks
             else if i < numberWeeks
                 if i is 2 then date = daysInFirstWeek
                 j = 0
@@ -623,11 +626,12 @@
                     date = date + 1
                     dates.push date
             else if i is numberWeeks
-                # last week in month
+            # last week in month
                 while date < numberDays
                     date = date + 1
                     dates.push date

+            # get the week number
             if newDate in dates
                 weekInfo.weekNum = i - 1
                 weekInfo.datesInWeek = dates
@@ -662,7 +666,7 @@
     createEvent: (newEvent) =>
         _id = @events.length
         obj =
-            _id: _id
+            id: _id
             name: if newEvent.name? then newEvent.name else null
             recurrance: if newEvent.recurrance? then newEvent.recurrance.toLowerCase() else 'none'
             month: if newEvent.month? then newEvent.month else null
@@ -675,7 +679,7 @@
             notes: if newEvent.notes? then newEvent.notes else null

         @events.push obj
-        @addEventsToCalendar @events[obj._id]
+        @addEventsToCalendar @events[obj.id]

     removeCalendarEvent: (eventId, index) =>
         events = @calendar.find "ul.#{@eventClass} a[data-event=#{eventId}]"
@@ -683,9 +687,10 @@
         @events.splice index, 1

     getEventIndex: (eventId) =>
+        # gets the index of an event so we can keep track after events are removed
         index = null
         $.each @events, (key, value) ->
-            if value._id is eventId then index = key
+            if value.id is eventId then index = key
             index
         index

@@ -730,12 +735,12 @@
             type: 'button'
         editButton = $ "<button></button>",
             class: 'drm-event-edit'
-            'data-event': events._id
+            'data-event': events.id
             text: 'Edit'
             type: 'button'
         deleteButton = $ "<button></button>",
             class: 'drm-event-delete'
-            'data-event': events._id
+            'data-event': events.id
             text: 'Delete'
             type: 'button'
         eventDetailList = $ '

',
@@ -888,7 +893,7 @@

             eventHtml = $ '',
                 href: '#'
-                'data-event': events._id
+                'data-event': events.id
                 html: eventContent

             if length is 0


Fix default image of invitations

Fix default image of invitations

--- app/coffee/modules/admin/memberships.coffee
+++ app/coffee/modules/admin/memberships.coffee
@@ -236,7 +236,7 @@
             ctx = {
                 full_name: if member.full_name then member.full_name else ""
                 email: member.email
-                imgurl: if member.photo then member.photo else "/image/unnamed.png"
+                imgurl: if member.photo then member.photo else "/images/unnamed.png"
             }

             html = template(ctx)


fix block lightbox

fix block lightbox

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -31,6 +31,15 @@
             lightbox.addClass('open')
         ), 70

+        $(document)
+            .on 'keydown.lightbox', (e) =>
+                code = if e.keyCode then e.keyCode else e.which
+
+                if code == 27
+                    @close(lightbox)
+                    $(document).off('lightbox')
+
+
     close: (lightbox) ->
         lightbox
             .one "transitionend", () ->
@@ -62,7 +71,7 @@
         title = $attrs.title
         $el.find("h2.title").text(title)
         $scope.$on "block", ->
-            lightboxService.close($el)
+            lightboxService.open($el)

         $scope.$on "unblock", ->
             $model.$modelValue.is_blocked = false


Adds Space.eventSourcing to required modules

--- source/server/module.coffee
+++ source/server/module.coffee
@@ -2,6 +2,8 @@

   @publish this, 'Space.accounts'

+  RequiredModules: ['Space.eventSourcing']
+
   Singletons: [
     'Space.accounts.UsersRouter'
   ]


for html5validation; input[type=file] doesn't set any value but instead the value is placed as hidden value. input.file 'require' is removed if upload has succeeded

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -192,8 +192,10 @@
         @$wrapper.removeClass 'disabled' if @$wrapper?
         @$input.prop('disabled', false)

-    maximumReached: ->
-      @options.maximum && @files.length >= @options.maximum
+      if @files.length > 0
+        @$input.prop('required', false)
+      else if @$input.hasClass('required')
+        @$input.prop('required', true)

     maximumReached: ->
       @options.maximum && @files.length >= @options.maximum && not @isUpdateOperation()


Fix confirm bug when dismissing the modal by clicking outside. Now there is a new feature to intentionally allow or not a modal to be disposable by clicking outside.

--- sources/tuktuk.modal.coffee
+++ sources/tuktuk.modal.coffee
@@ -1,12 +1,14 @@
 window.TukTuk.Modal = do (tk = TukTuk) ->

-  lock  = undefined
-  modal = undefined
+  lock       = undefined
+  modal      = undefined
+  disposable = true

   ###
       @todo: Describe method
   ###
-  show = (modal_id) ->
+  show = (modal_id, allow_dismiss=true) ->
+    disposable = allow_dismiss
     lock.addClass("active").show()
     @_hideAnyModal()
     modal = tk.dom("[data-tuktuk=modal]##{modal_id}").addClass "active"
@@ -24,6 +26,7 @@
     @

   alert = (message = "") ->
+    disposable = true
     modal = tk.dom("[data-tuktuk=modal][data-modal=alert]")
     text  = modal.find "#text"
     text.html message
@@ -37,6 +40,7 @@
     @

   confirm = (message = "", true_cb, false_cb) ->
+    disposable = false
     modal = tk.dom("[data-tuktuk=modal][data-modal=confirm]")
     text  = modal.find "#text"
     accept_button = modal.find "button.success"
@@ -58,6 +62,7 @@
     @

   prompt = (message = "", callback) ->
+    disposable = false
     modal = tk.dom("[data-tuktuk=modal][data-modal=prompt]")
     text  = modal.find "#text"
     content = modal.find "#content"
@@ -167,6 +172,11 @@
       #{loading_template}
       """

+    tk.dom("[data-tuktuk=lock]").on "click", (event) ->
+      loading = lock.attr("data-loading")
+      unless event.target is modal or loading is "true" or not disposable
+        TukTuk.Modal.hide()
+
     lock = tk.dom("[data-tuktuk=lock]").first()

   show    : show


Rename tests for clarity

Rename tests for clarity

--- test/encrypt.coffee
+++ test/encrypt.coffee
@@ -172,7 +172,7 @@
     assert @simpleTestDoc2.encrypt.calledBefore @simpleTestDoc2.sign, 'encrypted before signed'
     assert @simpleTestDoc2.sign.calledBefore @simpleTestDoc2.decryptSync, 'signed before decrypted'

-describe 'document.save() on nested document', ->
+describe 'document.save() on encrypted document which contains nesting', ->
   before ->
     @schemaWithNest = mongoose.Schema
       nest:
@@ -221,7 +221,7 @@
       assert.propertyVal docs[0].nest, 'areBirdsPretty', true
       done()

-describe 'document.save() on simple nested document', ->
+describe 'document.save() on encrypted nested document', ->
   before ->
     @schema = mongoose.Schema
       birdColor: type: String


Indentation fix

Indentation fix

--- app/locale/de-CH.coffee
+++ app/locale/de-CH.coffee
@@ -639,7 +639,7 @@
 #    material_premium_server: "Approximately 50 hours of gameplay spread over 190+ subscriber-only levels so far."
 #    material_1: "Approximately 25 hours of free content and an additional 15 hours of subscriber content."
 #    concepts_title: "What concepts are covered?"
-     how_much_title: "Wievil choschtet es monatlichs Abo?"
+    how_much_title: "Wievil choschtet es monatlichs Abo?"
 #    how_much_1: "A"
 #    how_much_2: "monthly subscription"
 #    how_much_3: "costs $9.99, and can be cancelled anytime."


Uncommented a couple headers

Uncommented a couple headers

--- app/locale/es-ES.coffee
+++ app/locale/es-ES.coffee
@@ -774,7 +774,7 @@
     social_hipchat: "Habla con nosotros en el chat publico de CodeCombat HipChat room"
     contribute_to_the_project: "Contribuye al proyecto"

-#  clans:
+  clans:
     clan: "Clan"
     clans: "Clanes"
 #    new_name: "New clan name"
@@ -1188,7 +1188,7 @@
     poll: "Encuesta"
 #    user_polls_record: "Poll Voting History"

-#  concepts:
+  concepts:
 #    advanced_strings: "Advanced Strings"
 #    algorithms: "Algorithms"
     arguments: "Argumentos"