Fixed not clearing search input when searching

--- core/app/backbone/initializers/site_initializer.coffee
+++ core/app/backbone/initializers/site_initializer.coffee
@@ -8,21 +8,7 @@
       showComponent ReactFeedSelection()
       mp_track 'Viewed feed'

-    'search': (params={}) ->
-      Backbone.history.once 'route', (router, executed_route_name) ->
-        return if executed_route_name == 'showSearch'
-
-        $('.js-navbar-search-box').val('')
-
-      query = params['s']
-      $('.js-navbar-search-box').val(query)
-      results = new SearchResults [], search: query
-
-      showComponent ReactSearchResults model: results
-
-      results.fetch()
-      mp_track 'Search: Top bar search',
-        query: query
+    'search': 'search' # must be named

     ':username/edit': ->
       showComponent ReactProfileEdit model: currentSession.user()
@@ -38,6 +24,23 @@
       user.fetch()

       showComponent ReactProfile model: user
+
+  search: (params={}) ->
+    @once 'route', (route) ->
+      return if route == 'search'
+
+      $('.js-navbar-search-box').val('')
+
+    query = params['s']
+    $('.js-navbar-search-box').val(query)
+
+    results = new SearchResults [], search: query
+    results.fetch()
+
+    showComponent ReactSearchResults model: results
+
+    mp_track 'Search: Top bar search',
+      query: query

 Factlink.siteInitializer = ->


instant jump instead of animated scroll bounce

--- assets/lib/calendar.coffee
+++ assets/lib/calendar.coffee
@@ -31,7 +31,7 @@
       else
         el.parentElement

-      parent.scrollIntoView({behavior: 'smooth', block: 'start'})
+      parent.scrollIntoView({behavior: 'instant', block: 'start'})
   , 0)

 ## Make IDs slugged


refactored code

refactored code

--- src/api/certificateAuthority.coffee
+++ src/api/certificateAuthority.coffee
@@ -9,10 +9,12 @@
   options = this.request.body
   if options.type is 'server'
     logger.info 'Generating server cert'
-    yield generateServerCert options
+    result = yield generateServerCert options
   else
     logger.info 'Generating client cert'
-    yield generateClientCert options
+    result = yield generateClientCert options
+  this.status = 201
+  this.body = result

 generateClientCert = (options) ->
   keystoreDoc = yield Keystore.findOne().exec()
@@ -37,6 +39,7 @@
     logger.info 'Client certificate created'
   catch err
     utils.logAndSetResponse this, 'internal server error', "Could not create a client cert via the API: #{err}", 'error'
+  this.body

 generateServerCert = (options) ->
   keystoreDoc = yield Keystore.findOne().exec()
@@ -47,13 +50,16 @@
     certInfo = yield readCertificateInfo this.body.certificate
     certInfo.data = this.body.certificate
     keystoreDoc.cert = certInfo
+    keystoreDoc.key = this.body.key
     yield Q.ninvoke keystoreDoc, 'save'
-
     #Add the new certficate to the keystore
     this.status = 201
     logger.info 'Server certificate created'
+
   catch err
     utils.logAndSetResponse this, 'internal server error', "Could not create a client cert via the API: #{err}", 'error'
+
+  this.body

 createCertificate = (options) ->
@@ -63,7 +69,7 @@
   promises.push deferred.promise
   Q.denodeify pem.createCertificate options, (err, cert) ->
     if (err)
-      console.log err
+
       response =
         err : err
       deferred.resolve()


Run module unit tests with main tests

Run module unit tests with main tests

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -280,7 +280,7 @@

 	grunt.registerTask 'install', "Compile everything when installing as an npm module", ['compile']

-	grunt.registerTask 'test:unit', 'Run the unit tests (use --grep=<regex> or --feature=<feature> for individual tests)', ['compile:server', 'compile:unit_tests', 'mochaTest:unit']
+	grunt.registerTask 'test:unit', 'Run the unit tests (use --grep=<regex> or --feature=<feature> for individual tests)', ['compile:server', 'compile:modules:server', 'compile:unit_tests', 'compile:modules:unit_tests', 'mochaTest:unit'].concat(moduleUnitTestTasks)
 	grunt.registerTask 'test:smoke', 'Run the smoke tests', ['compile:smoke_tests', 'mochaTest:smoke']

 	grunt.registerTask 'test:modules:unit', 'Run the unit tests for the modules', ['compile:modules:server', 'compile:modules:unit_tests'].concat(moduleUnitTestTasks)


Support customized compiled dir

Support customized compiled dir

--- lib/bundle.coffee
+++ lib/bundle.coffee
@@ -8,6 +8,7 @@
   constructor: (@options) ->
     @options.staticRoot = path.normalize(@options.staticRoot)
     @files = []
+    @defaultCompiledDir = 'c'
     @defaultNamespace = 'global'

   # Gets relative path from staticRoot. If the
@@ -24,8 +25,8 @@
         break
     return relativePath

-  #
-  # Determines based on file extension
+  #
+  # Determines based on file extension
   # if the file needs to get compiled
   #
   _needsCompiling: (file) ->
@@ -98,7 +99,7 @@
     # Determine if we need to copy/compile
     # the file into the staticRoot folder
     if (file.indexOf(@options.staticRoot) == -1 or @_needsCompiling(file))
-      writeTo = path.normalize(@_convertFilename(@options.staticRoot + '/generated/' + relativeFile))
+      writeTo = path.normalize(@_convertFilename("#{@options.staticRoot}/#{@defaultCompiledDir}/#{relativeFile}"))
       needsCompiling = true
       file = writeTo
       relativeFile = @_getRelativePath(file)
@@ -123,7 +124,7 @@

       str = @minify(str)
       hash = crypto.createHash('md5').update(str).digest('hex')
-      filepath = "#{@options.staticRoot}/generated/bundle/#{hash.substring(0, 7)}_#{namespace}#{@fileExtension}"
+      filepath = "#{@options.staticRoot}/#{@defaultCompiledDir}/bundle/#{hash.substring(0, 7)}_#{namespace}#{@fileExtension}"

       writeToFile(filepath, str)


fix(log): remove empty registry entry

fix(log): remove empty registry entry

--- packages/log/src/register.coffee
+++ packages/log/src/register.coffee
@@ -5,7 +5,6 @@

 module.exports =
   log:
-    '': handler: (->)
     cli: '@nikitajs/log/src/cli'
     csv: '@nikitajs/log/src/csv'
     fs: '@nikitajs/log/src/fs'


Remove censoring. Twas far too silly.

Remove censoring. Twas far too silly.

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -53,12 +53,7 @@
 specials = [
   { pattern: /^\/(?!\/)/, replacement: 'say ' },
   { pattern: /^\/\//, replacement: 'emote ' },
-  { pattern: /^\?/, replacement: 'eval ' },
-  { pattern: /\b(ass|tit|fag|vag)\b/g, replacement: '***' },
-  { pattern: /\b(shit|fuck|tits|cock|dick|boob|fags|cunt)\b/g, replacement: '****' },
-  { pattern: /\b(shits|fucks|bitch|penis|cocks|dicks|boobs|cunts)\b/g, replacement: '*****' },
-  { pattern: /\b(faggot|vagina)\b/g, replacement: '******' },
-  { pattern: /\b(faggots|bitches)\b/g, replacement: '*******' },
+  { pattern: /^\?/, replacement: 'eval ' }
 ]

 replaceSpecials = (text) ->


Adding a close fn.

Adding a close fn.

--- lib/client.coffee
+++ lib/client.coffee
@@ -122,6 +122,9 @@
     @blocking = false
     @emit "unblocked"

+  close: =>
+    @ws.close()
+
   _onClose: =>
     @emit "close"


remove TableGateway _query()

remove TableGateway _query()

--- src/access/table-gateway.coffee
+++ src/access/table-gateway.coffee
@@ -29,12 +29,12 @@
         return ar

     selectOne: (predicate, cb) -> @doOne(@_select, arguments, "select")
-    selectMany: (predicate, cb) -> @_query(predicate, "allRows", cb)
-    _select: (predicate, cb) -> @_query(predicate, "oneRow", cb)
+    selectMany: (predicate, cb) -> @where(predicate).tryCall("allRows", cb)
+    _select: (predicate, cb) -> @where(predicate).tryCall("oneRow", cb)

     findOne: () -> @doOne(@_find, arguments, "find")
-    findMany: (predicate, cb) -> @_query(predicate, "allObjects", cb)
-    _find: (predicate, cb) -> @_query(predicate, "oneObject", cb)
+    findMany: (predicate, cb) -> @where(predicate).tryCall("allObjects", cb)
+    _find: (predicate, cb) -> @where(predicate).tryCall("oneObject", cb)

     deleteOne: () -> @doOne(@_delete, arguments, 'delete')

@@ -152,8 +152,7 @@

     all: (cb) -> @newSelect().tryCall('allObjects', cb)

-    where: (clause, cb) -> @_query(clause, 'allObjects', cb)
-    _query: (predicate, fnName, cb) -> @newSelect().where(predicate).tryCall(fnName, cb)
+    where: (clause, cb) -> @newSelect().where(clause).tryCall('allObjects', cb)

     newSelect: () ->
         q = new @selectClass(@)


Improved error message

Improved error message

--- lib/oauth1-response-parser.coffee
+++ lib/oauth1-response-parser.coffee
@@ -7,7 +7,7 @@
 		@_unparsedBody = body
 		@_format = format
 		@_contentType = @_response.headers['content-type']
-		@_errorPrefix = 'Error while parsing response for ' + tokenType
+		@_errorPrefix = 'Error while parsing \'' + tokenType + '\''

 		if not @_isResponseOk
 			@_setError('HTTP status code: ' + @_response.statusCode)
@@ -75,7 +75,7 @@
 		!!@body.oauth_token_secret

 	_setError: (message) ->
-		@error = new check.Error(@_errorPrefix + ' ' + message)
+		@error = new check.Error(@_errorPrefix + ' (' + message + ')')
 		@error.body = @_unparsedBody

 module.exports = OAuth1ResponseParser


better method names

better method names

--- lib/scroll.coffee
+++ lib/scroll.coffee
@@ -10,13 +10,18 @@
     super(@editorView, @editor)

   execute: (count=1) ->
+    @keepCursorOnScreen(count)
+    @scrollUp(count)
+
+  keepCursorOnScreen: (count) ->
     {row, column} = @editor.getCursorBufferPosition()
-    lastRow = @editorView.getLastVisibleScreenRow() - @scrolloff
-    firstRow = @editorView.getFirstVisibleScreenRow() + @scrolloff
+    firstScreenRow = @editorView.getFirstVisibleScreenRow() + @scrolloff
+    if row - count <= firstScreenRow
+      @editor.setCursorBufferPosition([firstScreenRow + count, column])

-    if row - count <= firstRow
-      @editor.setCursorBufferPosition([firstRow + count, column])
-    @editorView.scrollToBufferPosition([lastRow + count, column])
+  scrollUp: (count) ->
+    lastScreenRow = @editorView.getLastVisibleScreenRow() - @scrolloff
+    @editorView.scrollToBufferPosition([lastScreenRow + count, 0])

 class ScrollUp extends Scroll
@@ -24,12 +29,18 @@
     super(@editorView, @editor)

   execute: (count=1) ->
+    @keepCursorOnScreen(count)
+    @scrollDown(count)
+
+  keepCursorOnScreen: (count) ->
     {row, column} = @editor.getCursorBufferPosition()
-    lastRow = @editorView.getLastVisibleScreenRow() - @scrolloff
-    firstRow = @editorView.getFirstVisibleScreenRow() + @scrolloff
+    lastScreenRow = @editorView.getLastVisibleScreenRow() - @scrolloff
+    if row + count >= lastScreenRow
+        @editor.setCursorBufferPosition([lastScreenRow - count, column])

-    if row + count >= lastRow
-      @editor.setCursorBufferPosition([lastRow - count, column])
-    @editorView.scrollToBufferPosition([firstRow - count, column])
+  scrollDown: (count) ->
+    firstScreenRow = @editorView.getFirstVisibleScreenRow() + @scrolloff
+    @editorView.scrollToBufferPosition([firstScreenRow - count, 0])
+

 module.exports = { ScrollDown, ScrollUp }


remove property that was being serialized.

--- js/src/model/object/KryptnosticObject.coffee
+++ js/src/model/object/KryptnosticObject.coffee
@@ -33,7 +33,6 @@
     # construct using a raw json object
     constructor : (raw) ->
       _.extend(this, raw)
-      @blockEncryptionService = new BlockEncryptionService()
       @validate()

     # validate json properies
@@ -64,12 +63,13 @@
       if @isDecrypted()
         return this
       else
-        chunks                = @blockEncryptionService.decrypt(@body.data, cryptoService)
-        chunkingStrategyUri   = @body.data.chunkingStrategy
-        chunkingStrategyClass = ChunkingStrategyRegistry.get(chunkingStrategyUri)
-        chunkingStrategy      = new chunkingStrategyClass()
-        data                  = chunkingStrategy.join(chunks)
-        raw                   = _.extend({}, _.cloneDeep(this), {body: {data}})
+        blockEncryptionService = new BlockEncryptionService()
+        chunks                 = blockEncryptionService.decrypt(@body.data, cryptoService)
+        chunkingStrategyUri    = @body.data.chunkingStrategy
+        chunkingStrategyClass  = ChunkingStrategyRegistry.get(chunkingStrategyUri)
+        chunkingStrategy       = new chunkingStrategyClass()
+        data                   = chunkingStrategy.join(chunks)
+        raw                    = _.extend({}, _.cloneDeep(this), {body: {data}})
         return new KryptnosticObject(raw)

     # chunk and encrypt using a cryptoService
@@ -77,12 +77,13 @@
       if @isEncrypted()
         return this
       else
-        chunkingStrategyUri   = @body.data.chunkingStrategy
-        chunkingStrategyClass = ChunkingStrategyRegistry.get(chunkingStrategyUri)
-        chunkingStrategy      = new chunkingStrategyClass()
-        chunks                = chunkingStrategy.split(@body.data)
-        data                  = @blockEncryptionService.encrypt(chunks, cryptoService)
-        raw                   = _.extend({}, _.cloneDeep(this), {body: {data}})
+        blockEncryptionService = new BlockEncryptionService()
+        chunkingStrategyUri    = @body.data.chunkingStrategy
+        chunkingStrategyClass  = ChunkingStrategyRegistry.get(chunkingStrategyUri)
+        chunkingStrategy       = new chunkingStrategyClass()
+        chunks                 = chunkingStrategy.split(@body.data)
+        data                   = blockEncryptionService.encrypt(chunks, cryptoService)
+        raw                    = _.extend({}, _.cloneDeep(this), {body: {data}})
         return new KryptnosticObject(raw)

   return KryptnosticObject


agregando punto

agregando punto

--- lib/server.coffee
+++ lib/server.coffee
@@ -476,7 +476,7 @@
 						include_docs: true
 					}, (bulkerr, bulkbody) ->
 						elapsed = process.hrtime(start)[1] / 1000000;
-						elapsed_getDocsCargo = (if process.hrtime(start)[0] > 0 then "#{process.hrtime(start)[0]}." else "0") + "#{elapsed.toFixed(0)}s"
+						elapsed_getDocsCargo = (if process.hrtime(start)[0] > 0 then "#{process.hrtime(start)[0]}." else "0.") + "#{elapsed.toFixed(0)}s"
 						#console.timeEnd label
 						if bulkerr
 							console.log 'error while getting DB data', bulkerr


handle phantom process exits

handle phantom process exits

--- lib/stream.coffee
+++ lib/stream.coffee
@@ -123,7 +123,7 @@
       cleanupAndExit = (err) ->
         if err
           logger.error?("[phantom-system] exiting due to error:", err)
-        if daemonExited
+        if phantomExited
           process.exit()
         else
           phantomProcess.on("exit", -> process.exit())
@@ -132,6 +132,18 @@
       process.on("uncaughtException", cleanupAndExit)
       process.on("SIGINT", cleanupAndExit)
       process.on("SIGTERM", cleanupAndExit)
+
+      # Watch for phantomjs exits.
+      # TODO: auto-restart?
+      phantomExited = false
+      phantomProcess.on "exit", (exitCode, signal) ->
+        phantomExited = true
+        if signal and exitCode isnt null and exitCode isnt 0
+          logger.error?("[phantom-system] exited with code #{exitCode} due to signal #{signal}")
+        else if signal
+          logger.error?("[phantom-system] exited due to signal #{signal}")
+        else if exitCode isnt 0
+          logger.error?("[phantom-system] exited with code #{exitCode}")

   # http://nodejs.org/api/stream.html#stream_readable_stream
   # TODO: implement other events


Changed update method as public

Changed update method as public

--- src/Task.coffee
+++ src/Task.coffee
@@ -1,5 +1,5 @@
-class DivSugar.Task
-  constructor: (@name = null) ->
+class DivSugar._Task
+  constructor: (@id) ->
     @active = true
     @onUpdate = null
     @onDestroy = null
@@ -7,12 +7,12 @@
     @_parent = null
     @_children = []

-  _update: (frameCount) =>
+  update: (frameCount) =>
     if @active
       @onUpdate? frameCount

       for child in @_children
-        child._update frameCount
+        child.update frameCount

   destroy: ->
     @onDestroy?()
@@ -32,7 +32,4 @@
       @_children.splice i, 1
       task._parent = null

-  toString: ->
-    # TBD
-
-DivSugar.rootTask = new DivSugar.Task
+DivSugar.rootTask = DivSugar.createTask('root')


Avoid saving state when closing windows due to auto-update

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -186,7 +186,10 @@
     @on 'application:report-issue', -> require('shell').openExternal('https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues')
     @on 'application:search-issues', -> require('shell').openExternal('https://github.com/issues?q=+is%3Aissue+user%3Aatom')

-    @on 'application:install-update', => @autoUpdateManager.install()
+    @on 'application:install-update', =>
+      @quitting = true
+      @autoUpdateManager.install()
+
     @on 'application:check-for-update', => @autoUpdateManager.check()

     if process.platform is 'darwin'


Show Preference opens a new window (when none are already open)

Closed #990

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -124,7 +124,6 @@
     @on 'application:about', -> Menu.sendActionToFirstResponder('orderFrontStandardAboutPanel:')
     @on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
     @on 'application:run-benchmarks', -> @runBenchmarks()
-    @on 'application:show-settings', -> (@focusedWindow() ? this).openPath("atom://config")
     @on 'application:quit', -> app.quit()
     @on 'application:hide', -> Menu.sendActionToFirstResponder('hide:')
     @on 'application:hide-other-applications', -> Menu.sendActionToFirstResponder('hideOtherApplications:')
@@ -137,6 +136,11 @@
     @on 'application:zoom', -> Menu.sendActionToFirstResponder('zoom:')
     @on 'application:bring-all-windows-to-front', -> Menu.sendActionToFirstResponder('arrangeInFront:')
     @on 'application:inspect', ({x,y}) -> @focusedWindow().browserWindow.inspectElement(x, y)
+    @on 'application:show-settings', ->
+      if @focusedWindow()
+        @focusedWindow().openPath("atom://config")
+      else
+        @openPath(pathToOpen: "atom://config")

     app.on 'will-quit', =>
       fs.unlinkSync socketPath if fs.existsSync(socketPath) # Clean the socket file when quit normally.


Restore-state through CODAP works.

Restore-state through CODAP works.

Pallette is restored to its default.
File name is not handled properly, but may not be meaningful in this context.

--- src/code/utils/codap-connect.coffee
+++ src/code/utils/codap-connect.coffee
@@ -38,7 +38,8 @@

       when 'restoreState'
         log.info 'Received restoreState request from CODAP.'
-#        @linkManager.
+        @linkManager.deleteAll()
+        @linkManager.loadData args.state
         iCallback
           success: true
       else


Unwrap object.

Unwrap object.

--- src/coffee/utils/order-utils.coffee
+++ src/coffee/utils/order-utils.coffee
@@ -79,39 +79,42 @@
   ###
   actionsMapReturnInfo: (diff, old_obj) ->
     actions = []
-    returnInfoDiffs = diff['returnInfo']
-    if returnInfoDiffs
+    returnInfoDeltas = diff['returnInfo']
+    if returnInfoDeltas
       # iterate over returnInfo instances
-      _.each _.keys(returnInfoDiffs), (returnInfoIndex) ->
-        if returnInfoIndex isnt '_t'
-          returnInfoDiff = returnInfoDiffs[returnInfoIndex]
-          if _.isArray returnInfoDiff
-            # returnInfo was added
-            returnInfo = _.last returnInfoDiff
+      _.chain returnInfoDeltas
+        .filter (item, key) -> key isnt '_t'
+        .map (returnInfoDelta, returnInfoDeltaKey) ->
+          if _.isArray returnInfoDelta
+            # get last added item
+            returnInfo = _.last returnInfoDelta
             action =
               action: 'addReturnInfo'
-            _.each _.keys(returnInfo), (key) ->
-              action[key] = returnInfo[key]
+            _.each returnInfo, (value, key) ->
+              action[key] = value
             actions.push action

             # TODO: split into multiple actions (addReturnInfo + setReturnShipmentState/setReturnPaymentState)
             #   in case shipmentState/paymentState already transitioned to a non-initial state
           else
-            returnInfo = returnInfoDiff
-            # iterate over returnInfo items instances
-            _.each _.keys(returnInfo.items), (returnInfoItemIndex) ->
-              if returnInfoItemIndex isnt '_t'
-                returnInfoItem = returnInfo.items[returnInfoItemIndex]
+            returnInfo = returnInfoDelta
+            # iterate over returnItem instances
+            actions = _.chain returnInfo.items
+              .filter (item, key) -> key isnt '_t'
+              .map (item, itemKey) ->
                 # iterate over all returnInfo status actions
-                _.each actionsListReturnInfoState(), (actionDefinition) ->
-                  returnItemState = returnInfoItem[actionDefinition.key]
-                  if returnItemState
-                    action = {}
-                    action['action'] = actionDefinition.action
-                    action['returnItemId'] = old_obj.returnInfo[returnInfoIndex].items[returnInfoItemIndex].id
-                    action[actionDefinition.key] = helper.getDeltaValue returnItemState
-                    actions.push action
-    actions
+                _.chain actionsListReturnInfoState()
+                  .filter (actionDefinition) -> _.has(item, actionDefinition.key)
+                  .map (actionDefinition) ->
+                    action =
+                      action: actionDefinition.action
+                      returnItemId: old_obj.returnInfo[returnInfoDeltaKey].items[itemKey].id
+                    action[actionDefinition.key] = helper.getDeltaValue item[actionDefinition.key]
+                    action
+                  .value()
+              .value()
+       .value()
+    _.flatten actions

 ###
 Exports object


Fixed command parsing for new regex

Fixed command parsing for new regex

Also added ability to limit nominations (whoops)

--- src/aotw.coffee
+++ src/aotw.coffee
@@ -66,8 +66,8 @@
             msg.send "No current album of the week"

     printHistory: (msg) ->
-        if msg.match[1] != ""
-            limit = msg.match[2]
+        if msg.match[1] != "history"
+            limit = msg.match[1].split(" ")[1]
         else
             limit = 9999

@@ -82,19 +82,19 @@
             msg.send "No previous AOTWs"

     nominate: (msg) ->
-        if msg.match[1] != ""
-            url = msg.match[2]
+        if msg.match[1] != "nominate"
+            url = msg.match[1].split(" ")[1]
             spotify = /^https?:\/\/(open|play)\.spotify\.com\/(album|track|user\/[^\/]+\/playlist)\/([a-zA-Z0-9]+)$/
             googlePlay = /^https?:\/\/(music|play)\.google\.com\/music\/m\/([a-zA-Z0-9]+)$/
             youtube = /^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=.*v=\w+)(?:\S+)?$/
-            soundCloud = /^https?:\/\/(soundcloud.com)\/(.*)\/(sets)\/(.*)$/
+            soundCloud = /^https?:\/\/(soundcloud.com)\/(.*)\/(sets)\/(.*)$/
             if url.match(spotify) or url.match(googlePlay) or url.match(youtube) or url.match(soundCloud)
                 user = msg.message.user.name.toLowerCase()
                 try
-                    @storage.nominations.push(user: user, url: msg.match[2])
+                    @storage.nominations.push(user: user, url: url)
                 catch
                     @storage.nominations = []
-                    @storage.nominations.push(user: user, url: msg.match[2])
+                    @storage.nominations.push(user: user, url: url)
                 finally
                     @save
                     msg.send "Nomination saved"
@@ -104,15 +104,15 @@
             msg.send "Invalid nomination: missing url"

     printNominations: (msg) ->
-        if msg.match[1] != ""
-            limit = msg.match[2]
+        if msg.match[1] != "nominations"
+            limit = msg.match[1].split(" ")[1]
         else
             limit = 9999

         if @storage.nominations && @storage.nominations.length > 0
             msg.send "Total of #{@storage.nominations.length} nominations"
             i = 0
-            while i < @storage.nominations.length
+            while i < @storage.nominations.length && i < limit
                 nomination = @storage.nominations[i]
                 msg.send "#{i + 1} - #{nomination["user"]} - #{nomination["url"]}"
                 i++
@@ -139,9 +139,9 @@
         msg.send "All AOTW data has been reset"

     select: (msg) ->
-        if msg.match[1] != ""
-            if msg.match[2] <= @storage.nominations.length && msg.match[2] > 0
-                i = msg.match[2]
+        if msg.match[1] != "select"
+            if msg.match[1].split(" ")[1] <= @storage.nominations.length && msg.match[1].split(" ")[1] > 0
+                i = msg.match[1].split(" ")[1]
                 selected = @storage.nominations[i - 1]
                 try
                     @storage.history.push selected
@@ -182,7 +182,7 @@
         msg.send "Invalid command, say \"aotw help\" for help"

     robot.hear /^\s*aotw (.*)/i, (msg) ->
-        cmd = msg.match[1]
+        cmd = msg.match[1].split(" ")[0]
         switch cmd
             when "current" then checkMessage msg, aotw.printCurrentAotw
             when "help" then aotw.printHelp msg


removed unused file

removed unused file

--- sample/collectorInit.coffee
+++ sample/collectorInit.coffee
@@ -1,22 +0,0 @@
-define ['vendor/particle', 'vendor/pulsar', 'vendor/vein'], (particle, Pulsar, Vein) ->
-  $ ->
-
-    data =
-      sessionId: 'foo'
-
-    pulse = Pulsar.createClient {port: 4001}
-
-    rpc = Vein.createClient {}
-    rpc.ready ->
-
-      options =
-        register: rpc['particle/register']
-        activateDelta: (listener) ->
-          channel = pulse.channel "particle:session:#{data.sessionId}"
-          channel.on 'delta', listener
-
-      collector = new particle.Collector options
-
-      collector.ready ->
-        window.dataRoot = collector.data
-        $('.displayUser').html collector.data.you.id


minor fix for the phrasing

minor fix for the phrasing

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -71,7 +71,7 @@
         selector = selector or "English" # coded default
         assert messages = cache[selector] or new Object
         amount = _.keys(messages).length # of messages
-        banner = "Got %s messages for %s in %s".grey
+        banner = "Loaded %s messages for %s in %s".grey
         assert identify = try @constructor.identify()
         logger.debug banner, amount, identify, selector
         assert _.isFunction(callback), "invalid callback"


Fix window level commands by putting an atom-workspace back

--- src/atom.coffee
+++ src/atom.coffee
@@ -727,7 +727,7 @@
     @deserializeTimings.workspace = Date.now() - startTime

     SheetContainer = require './sheet-container'
-    @item = document.createElement("div")
+    @item = document.createElement("atom-workspace")
     @item.setAttribute("id", "sheet-container")
     @item.setAttribute("class", "sheet-container")


fix jquery require

fix jquery require

--- source/javascripts/app.coffee
+++ source/javascripts/app.coffee
@@ -1,7 +1,7 @@
 #= require 'prism/prism'
 #= require 'prism/plugins/autolinker/prism-autolinker'
 #= require 'prism-markdown'
-#= require 'jquery/dist/jquery'
+#= require 'jquery/dist/jquery.js'

 addLineNumbers = ->
   # works only for <code> wrapped inside <pre data-line-numbers> (not inline)


Fix options not working from Atom UI and config file

--- src/beautifiers/prettier.coffee
+++ src/beautifiers/prettier.coffee
@@ -33,12 +33,14 @@
       else
         reject(new Error("Unknown language for Prettier"))

+      filePath = atom.workspace.getActiveTextEditor().getPath()
+
       try
-        result = prettier.format(text, {
-          options
-          parser
-        })
-        resolve result
+        prettier.resolveConfig(filePath).then((configOptions) ->
+          result = prettier.format(text, configOptions or options, parser)
+          prettier.clearConfigCache()
+          resolve result
+        )
       catch err
         reject(err)
     )


Analytics: Update ES index mapping to use doc_values and disable _all

Addresses #30

* Turn on `doc_values` for most fields
* Turn off `_all` for our indices
* Store `client.ua` and `client.path` as `not_analyzed`

--- src/streammachine/master/analytics.coffee
+++ src/streammachine/master/analytics.coffee
@@ -476,31 +476,42 @@
         time:
             type:   "date"
             format: "date_time"
+            doc_values: true
         stream:
             type:   "string"
             index:  "not_analyzed"
+            doc_values: true
         session_id:
             type:   "string"
             index:  "not_analyzed"
+            doc_values: true
         client:
             type:   "object"
             properties:
                 session_id:
                     type:   "string"
                     index:  "not_analyzed"
+                    doc_values: true
                 user_id:
                     type:   "string"
                     index:  "not_analyzed"
+                    doc_values: true
                 output:
                     type:   "string"
                     index:  "not_analyzed"
+                    doc_values: true
                 ip:
                     type:   "string"
                     index:  "not_analyzed"
+                    doc_values: true
                 ua:
                     type:   "string"
+                    index:  "not_analyzed"
+                    doc_values: true
                 path:
                     type:   "string"
+                    index:  "not_analyzed"
+                    doc_values: true

     @EStemplates:
         sessions:
@@ -510,17 +521,17 @@
                     number_of_replicas: 1
             mappings:
                 session:
+                    "_all": { enabled: false }
                     properties: _.extend {}, @ESobjcore,
                         duration:
                             type:   "float"
-                            include_in_all: false
                         kbytes:
                             type:   "long"
-                            include_in_all: false
                         ips:
                             type:   "string"
                             index:  "not_analyzed"
                             index_name: "ip"
+                            doc_values: true
         listens:
             settings:
                 index:
@@ -528,20 +539,21 @@
                     number_of_replicas: 1
             mappings:
                 start:
+                    "_all": { enabled: false }
                     properties: _.extend {}, @ESobjcore

                 listen:
+                    "_all": { enabled: false }
                     properties:
                         _.extend {}, @ESobjcore,
                             duration:
                                 type:   "float"
-                                include_in_all: false
                             kbytes:
                                 type:   "long"
-                                include_in_all: false
                             offsetSeconds:
                                 type:   "integer"
-                                include_in_all: false
+                                doc_values: true
                             contentTime:
                                 type:   "date"
-                                format: "date_time"+                                format: "date_time"
+                                doc_values: true


DateTime: Fixed "@format(moment)", this only worked for Strings.

--- src/elements/DateTime/DateTime.coffee
+++ src/elements/DateTime/DateTime.coffee
@@ -273,17 +273,18 @@
 	format: (_s, type="display", output_type=null) ->
 		assert(type in DateTime.formatTypes, "DateTime.format", "type must be on of \"#{DateTime.formatTypes.join(',')}\".", parm1: _s, type: type)

-		s = _s.trim()
-
-		if isEmpty(s)
-			return null
-
-		if moment.isMoment(s)
-			mom = s
-		else
+		if moment.isMoment(_s)
+			mom = _s
+		else
+			s = _s.trim()
+
+			if isEmpty(s)
+				return null
+
 			mom = @parse(s, @__input_formats_known)
-			if mom.isValid() and isNull(output_type)
-				output_type = @getCurrentFormat().type
+
+		if mom.isValid() and isNull(output_type)
+			output_type = @getCurrentFormat().type

 		if not mom.isValid()
 			formats_tried = []


re-set the dirty bit on initialize

re-set the dirty bit on initialize

since the AuthView is reinstantiated many, many times

--- scripts/gh-book/auth.coffee
+++ scripts/gh-book/auth.coffee
@@ -25,6 +25,10 @@
       # Bind a function to the window if the user tries to navigate away from this page
       $(window).on 'beforeunload', () ->
         return 'You have unsaved changes. Are you sure you want to leave this page?' if @isDirty
+
+      # Since this View is reloaded all the time (whenever a route change occurs)
+      # re-set the `isDirty` bit.
+      @isDirty = true if allContent.some (model) -> model.isDirty()

     templateHelpers: () ->
       return {


move for to htmlFor on labels

move for to htmlFor on labels

--- src/components/teacher/reading.cjsx
+++ src/components/teacher/reading.cjsx
@@ -62,12 +62,13 @@
     <BS.Panel bsStyle="default" className="create-reading" footer={footer}>

{headerText}

-        Name
+        Name
         <input id="title" type="text" value={@props.title}/>

-        Due Date
+        Due Date
         <Datepicker
+          id="due-date"
           format="MMM dd, yyyy"
           time={false}
           calendar={true}


:couple::clock530: Updated at https://danielx.net/editor/

--- pixie.cson
+++ pixie.cson
@@ -3,6 +3,9 @@
   Record sounds directly from your browser and download them to you local PC.
 """
 version: "0.3.1"
+publish:
+  s3:
+    basePath: "public/danielx.net"
 remoteDependencies: [
 ]
 dependencies:


:clock5::mountain_railway: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -4,7 +4,6 @@
 ]
 dependencies:
   analytics: "distri/google-analytics:v0.1.0"
-  bindable: "distri/bindable:v0.1.0"
   byte_array: "distri/byte_array:v0.1.1"
   cornerstone: "distri/cornerstone:v0.2.6"
   eval: "distri/eval:v0.1.0"


:bug: Fix a duplicate variable issue

:bug: Fix a duplicate variable issue

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -12,11 +12,11 @@
     @EditorLinters = new Map # A map of Editor <--> Linter
     @Linters = new Set # I <3 ES6
     @Subscriptions.add atom.workspace.observeTextEditors (Editor) =>
-      EditorLinter = new EditorLinter @, Editor
-      @Emitter.emit 'linters-observe', EditorLinter
+      CurrentEditorLinter = new EditorLinter @, Editor
+      @Emitter.emit 'linters-observe', CurrentEditorLinter
       Editor.onDidDestroy =>
-        EditorLinter.destroy()
-        @EditorLinters.delete EditorLinter
+        CurrentEditorLinter.destroy()
+        @EditorLinters.delete CurrentEditorLinter

   getActiveEditorLinter:->
     ActiveEditor = atom.workspace.getActiveEditor()


#780 Don't set scrollTop if component was destroyed on smoothScroll

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -786,12 +786,15 @@
     point = new Point(row, 0)
     @editor.element.pixelRectForScreenRange(new Range(point, point)).top

-  smoothScroll: (fromRow, toRow, options={}) ->
+  smoothScroll: (fromRow, toRow, done) ->
     topPixelFrom = {top: @getPixelRectTopForSceenRow(fromRow)}
     topPixelTo = {top: @getPixelRectTopForSceenRow(toRow)}
-    options.step = (newTop) => @editor.element.setScrollTop(newTop)
-    options.duration = @getSmoothScrollDuation()
-    @vimState.requestScrollAnimation(topPixelFrom, topPixelTo, options)
+    # [NOTE]
+    # intentionally use `element.component.setScrollTop` instead of `element.setScrollTop`.
+    # SInce element.setScrollTop will throw exception when element.component no longer exists.
+    step = (newTop) => @editor.element.component?.setScrollTop(newTop)
+    duration = @getSmoothScrollDuation()
+    @vimState.requestScrollAnimation(topPixelFrom, topPixelTo, {duration, step, done})

   getAmountOfRows: ->
     Math.ceil(@amountOfPage * @editor.getRowsPerPage() * @getCount())
@@ -815,10 +818,10 @@
         @editor.setFirstVisibleScreenRow(newFirstVisibileScreenRow)
         # [FIXME] sometimes, scrollTop is not updated, calling this fix.
         # Investigate and find better approach then remove this workaround.
-        @editor.element.component.updateSync()
+        @editor.element.component?.updateSync()

       if @isSmoothScrollEnabled()
-        @smoothScroll(firstVisibileScreenRow, newFirstVisibileScreenRow, {done})
+        @smoothScroll(firstVisibileScreenRow, newFirstVisibileScreenRow, done)
       else
         done()


Making the development server to serve any static content

--- cli/commands/server.coffee
+++ cli/commands/server.coffee
@@ -21,7 +21,7 @@
   server.get '/main.js', (req, res) ->
     src  = if minify then source.minify() else source.compile()
     size = Math.round(src.length/102.4)/10
-    console.log(" Compiling: "+ "/main.js (#{size}Kb #{if minify then 'minified' else 'source'})".grey)
+    console.log(" Compiling: ".cyan+ "/main.js (#{size}Kb #{if minify then 'minified' else 'source'})".grey)
     res.charset = 'utf-8'
     res.header('Cache-Control', 'no-cache')
     res.contentType('text/javascript')
@@ -29,9 +29,9 @@

   server.get '/lovely.io/:module.js', (req, res) ->
     module = req.params.module
-    script = "#{base}/#{module}/current/build/#{module}-src.js"
+    script = "#{base}/#{module}/current/build/#{module}#{if minify then '' else '-src'}.js"

-    console.log(" Serving:   "+ "'#{module}' module from #{script}".grey)
+    console.log(" Serving:   ".magenta+ "'#{module}' module from #{script}".grey)

     fs.readFile script, (err, data)->
       res.contentType('text/javascript')
@@ -40,24 +40,35 @@
   server.get '/favicon.ico', (req, res) ->
     res.send '' # just keeping it happy

-  # listening all the pages in the user project
+  # listening all the static content in the user project
   server.get /^\/(.*?)$/, (req, res) ->
-    minify = req.query.minify is 'true'
-    filename = req.params[0] || 'index'
-    filename.substr(filename.length - 5) == '.html' || filename += '.html'
+    minify    = req.query.minify is 'true'
+    filename  = req.params[0] || 'index'
+    extension = filename.split('.')
+    extension = extension[extension.length - 1]

-    console.log(" Sending:   "+ "/#{filename}".grey)
+    unless extension in ['css', 'html', 'js']
+      filename += '.html'
+      extension = 'html'
+
+    switch extension
+      when 'css' then content_type = 'text/css'
+      when 'js'  then content_type = 'text/javascript'
+      else            content_type = 'text/html'

     fs.readFile "#{process.cwd()}/#{filename}", (err, data)->
+      console.log("") if extension is "html"
+      console.log(" Sending:   "+ "/#{filename} (#{content_type})".grey)
+
       res.charset = 'utf-8'
-      res.contentType('text/html')
+      res.contentType(content_type)
       res.send data

   server.listen(port)

   print "Listening at: http://127.0.0.0:#{port}\n"+
-    "Press Ctrl+C to hung up\n".grey
+    "Press Ctrl+C to hung up".grey

 exports.help = (args) ->


CLI/server making it serve fonts

CLI/server making it serve fonts

--- cli/commands/server.coffee
+++ cli/commands/server.coffee
@@ -75,14 +75,17 @@
     content_type = (name)->
       extension = (name || '').split('.')
       switch extension[extension.length - 1]
-        when 'css' then return 'text/css'
-        when 'js'  then return 'text/javascript'
-        when 'ico' then return 'image/icon'
-        when 'png' then return 'image/png'
-        when 'jpg' then return 'image/jpg'
-        when 'gif' then return 'image/gif'
-        when 'swf' then return 'application/x-shockwave-flash'
-        else            return 'text/html'
+        when 'css'  then return 'text/css'
+        when 'js'   then return 'text/javascript'
+        when 'ico'  then return 'image/icon'
+        when 'png'  then return 'image/png'
+        when 'jpg'  then return 'image/jpg'
+        when 'gif'  then return 'image/gif'
+        when 'swf'  then return 'application/x-shockwave-flash'
+        when 'eot'  then return "application/vnd.ms-fontobject"
+        when 'ttf'  then return "application/x-font-ttf"
+        when 'woff' then return "application/x-font-woff"
+        else             return 'text/html'

     if req.method is 'POST'
       console.log("\n POST:     ", JSON.stringify(req.body).grey)


rollbar: on logout reduce no. of errors logged to rollbar

--- client/Main/rollbar.coffee
+++ client/Main/rollbar.coffee
@@ -24,3 +24,7 @@
     id       : user.nickname or "unknown"
     name     : KD.utils.getFullnameFromAccount()
     username : user.nickname
+
+# on logout reduce no. of errors logged to rollbar
+KD.getSingleton('mainController').on "AccountChanged", (account) ->
+  _rollbar.itemsPerMinute = 1  if KD.isGuest()


Small changes.

Small changes.

--- src/core.coffee
+++ src/core.coffee
@@ -140,7 +140,7 @@
         newTreema.select()
         @integrateChildTreema(newTreema)
         newTreema.flushChanges()
-      true
+      newTreema

     open: ->
       @data.sort(@sortFunction) if @sort
@@ -266,7 +266,7 @@
       @deselectAll()
       properties = @childPropertiesAvailable()
       keyInput = $(@newPropertyTemplate)
-      keyInput.blur @cleanupAddNewChild
+#      keyInput.blur @cleanupAddNewChild
       keyInput.keydown (e) =>
         @originalTargetValue = $(e.target).val()
       keyInput.autocomplete?(source: properties, minLength: 0, delay: 0, autoFocus: true)


Remove pause from requireLogin router hook, so error messages are shown on login screen.

--- lib/router.coffee
+++ lib/router.coffee
@@ -78,7 +78,6 @@
       pause()
     else
       @render "signin"
-      pause()

 Router.onBeforeAction requireLogin,
     except: [


errors while authorize => callback err

errors while authorize => callback err

Signed-off-by: Arnaud Richard <a.richard@webshell.io>

Conflicts:
	lib/server.coffeeit

--- lib/server.coffee
+++ lib/server.coffee
@@ -129,6 +129,24 @@
 			res.send r
 			next()

+clientCallback = (data, res, next) -> (e, r) -> #data:state,provider,redirect_uri,origin
+	body = formatters.build e || r
+	body.state = data.state if data.state
+	body.provider = data.provider.toLowerCase() if data.provider
+	view = '<script>(function() {\n'
+	view += '\t"use strict";\n'
+	view += '\tvar msg=' + JSON.stringify(JSON.stringify(body)) + ';\n'
+	if data.redirect_uri
+		view += '\tdocument.location.href = "' + data.redirect_uri + '#oauthio=" + encodeURIComponent(msg);\n'
+	else
+		view += '\tvar opener = window.opener || window.parent.window.opener;\n'
+		view += '\tif (opener)\n'
+		view += '\t\topener.postMessage(msg, "' + data.origin + '");\n'
+		view += '\twindow.close();\n'
+	view += '})();</script>'
+	res.send view
+	next()
+
 # oauth: handle callbacks
 server.get config.base + '/', (req, res, next) ->
 	if Object.keys(req.params).length == 0
@@ -140,7 +158,9 @@
 		return next new check.Error 'state', 'must be present'
 	db.states.get req.params.state, (err, state) ->
 		return next err if err
-		return next new check.Error 'state', 'invalid or expired' if not state || state.step != "0"
+		return next new check.Error 'state', 'invalid or expired' if not state
+		callback = clientCallback state:state.options.state, provider:state.provider, redirect_uri:state.redirect_uri, origin:state.origin, res, next
+		return callback new check.Error 'state', 'code already sent, please use /access_token' if state.step != "0"
 		oauth[state.oauthv].access_token state, req, (e, r) ->
 			status = if e then 'error' else 'success'

@@ -154,39 +174,33 @@
 					r.code = req.params.state
 				if state.options.response_type == 'token'
 					db.states.del req.params.state, (->)
-			body = formatters.build e || r
-			body.state = state.options.state
-			body.provider = state.provider.toLowerCase()
-			view = '<script>(function() {\n'
-			view += '\t"use strict";\n'
-			view += '\tvar msg=' + JSON.stringify(JSON.stringify(body)) + ';\n'
-			if state.redirect_uri
-				redirect_infos = Url.parse fixUrl(state.redirect_uri), true
-				view += '\tdocument.location.href = "' + state.redirect_uri + '#oauthio=" + encodeURIComponent(msg);\n'
-			else
-				view += '\tvar opener = window.opener || window.parent.window.opener;\n'
-				view += '\tif (opener)\n'
-				view += '\t\topener.postMessage(msg, "' + state.origin + '");\n'
-				view += '\twindow.close();\n'
-			view += '})();</script>'
-			res.send view
-			next()
+			callback e, r

 # oauth: popup or redirection to provider's authorization url
 server.get config.base + '/auth/:provider', (req, res, next) ->
 	res.setHeader 'Content-Type', 'text/html'
+
+	domain = null
+	origin = null
+	ref = fixUrl(req.headers['referer'] || req.headers['origin'] || req.params.d || req.params.redirect_uri || "")
+	urlinfos = Url.parse ref
+	if not urlinfos.hostname
+		return next new restify.InvalidHeaderError 'Missing origin or referer.'
+	origin = urlinfos.protocol + '//' + urlinfos.host
+
+	options = {}
+	if req.params.opts
+		try
+			options = JSON.parse(req.params.opts)
+			return cb new check.Error 'Options must be an object' if typeof options != 'object'
+		catch e
+			return cb new check.Error 'Error in request parameters'
+
+	callback = clientCallback state:options.state, provider:req.params.provider, origin:origin, redirect_uri:req.params.redirect_uri, res, next
+
 	key = req.params.k
 	if not key
-		return next new restify.MissingParameterError 'Missing oauthd public key.'
-
-	domain = null
-	origin = null
-	ref = fixUrl(req.headers['referer'] || req.headers['origin'] || req.params.d || req.params.redirect_uri)
-	if ref
-		urlinfos = Url.parse ref
-		if not urlinfos.hostname
-			return next new restify.InvalidHeaderError 'Missing origin or referer.'
-		origin = urlinfos.protocol + '//' + urlinfos.host
+		return callback new restify.MissingParameterError 'Missing OAuthd public key.'

 	oauthv = req.params.oauthv && {
 		"2":"oauth2"
@@ -215,20 +229,14 @@
 		(keyset, provider, cb) ->
 			return cb new check.Error 'This app is not configured for ' + provider.provider if not keyset
 			{parameters, response_type} = keyset
-			options = {}
-			if req.params.opts
-				try
-					options = JSON.parse(req.params.opts)
-					return cb new check.Error 'Options must be an object' if typeof options != 'object'
-				catch e
-					return cb new check.Error 'Error in request parameters'
+			plugins.data.emit 'connect.auth', key:key, provider:provider.provider, parameters:parameters
 			if response_type != 'token' and (not options.state or options.state_type)
 				return cb new check.Error 'You must provide a state when server-side auth'
 			options.response_type = response_type
 			opts = oauthv:oauthv, key:key, origin:origin, redirect_uri:req.params.redirect_uri, options:options
 			oauth[oauthv].authorize provider, parameters, opts, cb
 	], (err, url) ->
-		return next err if err
+		return callback err if err
 		res.setHeader 'Location', url
 		res.send 302
 		next()


Revert "Simplify drawing shape in progress"

This reverts commit 32e89182d10bb4fffe8fadc5d76563911641d8bf.

--- src/core/LiterallyCanvas.coffee
+++ src/core/LiterallyCanvas.coffee
@@ -299,8 +299,13 @@
   # without doing a full repaint.
   # The context is restored to its original state before returning.
   drawShapeInProgress: (shape) ->
-    @setShapesInProgress [shape]
     @repaintLayer('main', false)
+    @clipped (=>
+      @transformed (=>
+        renderShapeToContext(
+          @ctx, shape, {@bufferCtx, shouldOnlyDrawLatest: true})
+      ), @ctx, @bufferCtx
+    ), @ctx, @bufferCtx

   # Draws the given shapes translated and scaled to the given context.
   # The context is restored to its original state before returning.


refactoring / new implementation in progress

--- 360.coffee
+++ 360.coffee
@@ -698,3 +698,123 @@
         res.end()

+#{{{1 Version 2
+if !isNodeJs then do ->
+  #{{{2 utility
+  #{{{3 extend
+  #{{{2 Model
+  #
+  # The model is just a json object that is passed around. This has all the state for the onetwo360 viewer
+  #
+  defaultModel = ->
+    frames:
+      current: 0
+      normal:
+        width: undefined
+        height: undefined
+        urls: []
+      zoom:
+        width: undefined
+        height: undefined
+        urls: []
+    fullscreen:
+      false
+    zoom:
+      lensSize: 200
+      enabled: false
+      # x/y-position on image normalised in [0;1]
+      x: undefined
+      y: undefined
+    domElem:
+      width: undefined
+      height: undefined
+      domId: undefined
+
+
+  #{{{2 View
+  #
+  # The html of the view is static, only updated through css-changes.
+  #
+  #{{{3 `View` constructor, - create a viw and bind it to a dom element
+  #
+  # Create the view, - and bind it to a dom element
+  #
+  View = (model, domId) ->
+    @model = model
+
+    @elems = {}
+    @elems.root = document.createElement "div"
+    @elems.root.innerHTML = """<
+      div class="onetwo360-zoom-lens>
<
+      div class="onetwo360-logo>
<
+      div class="onetwo360-fullscreen-button>
<
+      div class="onetwo360-zoom-button>
<
+      div>
"""
+    elemNames = ["zoomLens", "logo", "btnFull", "btnZoom"]
+    for i in [0..elemNames.length]
+      @elems.root.getChildren
+      @elems[key]
+
+    @elemStyle = {}
+    @style = {}
+    @styleCache = {}
+    for key, _ of @elems
+      @style[key] = {}
+      @elemStyle[key] = @elems[key].style
+      @styleCache[key] = {}
+
+    domElem = document.getElementById(domId)
+    domElem.addChild @elems.root
+    @defaultWidth = domElem.offsetWidth
+    @defaultHeigh = domElem.offsetHeight
+
+    @width = undefined
+    @height = undefined
+
+    @update()
+
+  #{{{3 `View#update()` draw the view based on current content of the model
+  View.prototype.update = ->
+    @_fullscreen()
+    @_root()
+    @_zoomLens()
+    @_applyStyle()
+
+  #{{{3 private utility functions for updating the view
+  View.prototype._fullscreen= -> #{{{4
+    if @model.fullscreen
+      extend @style.root,
+        position: "absolute"
+        top: 0
+        left: 0
+        width: (@width = window.innerWidth)
+        height: (@height = window.innerHeight)
+    else
+      extend @style.root,
+        position: "relative"
+        top: 0
+        left: 0
+        width: (@width = @defaultWidth)
+        height: (@height = @defaultHeight)
+
+  View.prototype._root= -> #{{{4
+    extend @style.root,
+      backgroundImage: "url(#{@model.frames.normal.urls[@model.frames.current]})"
+      backgroundSize: "#{@width}px #{@height}px"
+
+  View.prototype._zoomLens = -> #{{{4
+    undefined
+
+  View.prototype._applyStyle = -> #{{{4
+    for elemId, css of @style
+      for key, val of css
+        if @styleCache[key] != val
+          if typeof val == "number"
+            @elemStyle[elemId][key] = "#{val}px"
+          else
+            @elemStyle[elemId][key] = val
+          @styleCache[key] = val
+
+
+  #{{{2 main
+  window.newOneTwo360 = (cfg) ->


fix(destroy-draft): Do not throw exception deleting non-existent draft

--- src/flux/tasks/destroy-draft.coffee
+++ src/flux/tasks/destroy-draft.coffee
@@ -25,6 +25,7 @@
         return reject(new Error("Attempt to call DestroyDraftTask.performLocal without @draftLocalId"))

       DatabaseStore.findByLocalId(Message, @draftLocalId).then (draft) =>
+        return resolve() unless draft
         @draft = draft
         DatabaseStore.unpersistModel(draft).then(resolve)


Remove levitate ability activation

Remove levitate ability activation

--- server/bw/data/abilities.coffee
+++ server/bw/data/abilities.coffee
@@ -576,9 +576,7 @@

 makeAbility 'Levitate', ->
   this::isImmune = (type) ->
-    if type == 'Ground'
-      @pokemon.activateAbility()
-      return true
+    return true  if type == 'Ground'

 makeAbility 'Light Metal', ->
   this::calculateWeight = (weight) ->


Remove mathvariable classes

Remove mathvariable classes

--- src/javascripts/math_buttons.coffee
+++ src/javascripts/math_buttons.coffee
@@ -1,7 +1,11 @@
 ttm = thinkthroughmath

+# math_var = (name)->
+#   "#{name}"
+
+# Temp
 math_var = (name)->
-  "#{name}"
+  "#{name}"

 class ButtonBuilder
   initialize: (@opts={})->


fix bulk_docs permissions

fix bulk_docs permissions

--- server/middlewares/utils.coffee
+++ server/middlewares/utils.coffee
@@ -65,7 +65,7 @@

     else if req.url.indexOf('/replication/_changes') is 0
         next()
-    else if req.url is '/replication/_bulk_docs'
+    else if req.url.indexOf('/replication/_bulk_docs') is 0
         # Use to add/update/delete a document in replication
         async.forEach req.body.docs, (doc, cb) ->
             if doc._deleted


Bugfix in exposed properties (window.Hull.*) when debug:false

--- src/hull.coffee
+++ src/hull.coffee
@@ -18,10 +18,10 @@

     afterAppStart: (app)->
       sb = app.createSandbox();
-      Hull = _.extend(HullDef, sb);
+      _.extend(HullDef, sb);
       if !app.config.debug
         props = ['widget', 'templates', 'emit', 'on', 'version', 'track']
-        props.concat(config.expose || [])
+        props.concat(app.config.expose || [])
         _h = {}
         _.map props, (k)->
           _h[k] = window.Hull[k]


main: require async

main: require async

--- src/main.coffee
+++ src/main.coffee
@@ -1,3 +1,5 @@
+async = require('async')
+
 #process.on 'uncaughtException', (err) ->
 #    console.error "uncaughtException: #{err.stack || err.message || err.toString()}"


add refs to error messages

add refs to error messages

--- src/main.coffee
+++ src/main.coffee
@@ -74,7 +74,7 @@
   #   # ### experimental: accept only 'namespaced' event names a la 'foo/bar' and known names
   #   # so as to prevent accidental usage of bogus event names like `end`, `close`, `finish` etc: ###
   #   # unless ( '/' in name ) or ( name is 'stop' )
-  #   #   throw new Error "unknown event name #{rpr name}"
+  #   #   throw new Error "µ1763 unknown event name #{rpr name}"
   #   emitter.on name, P...
   #.........................................................................................................
   return R
@@ -85,10 +85,10 @@
     when 0 then null
     when 2
       unless ( type = CND.type_of for_event_name ) is 'text'
-        throw new Error "expected a text, got a #{type}"
+        throw new Error "µ2528 expected a text, got a #{type}"
       unless ( type = CND.type_of method ) is 'function'
-        throw new Error "expected a function, got a #{type}"
-    else throw new Error "expected 0 or 2 arguments, got #{arity}"
+        throw new Error "µ3293 expected a function, got a #{type}"
+    else throw new Error "µ4058 expected 0 or 2 arguments, got #{arity}"
   #.........................................................................................................
   R               = @_new_event_emitter()
   R._event_counts = {}
@@ -103,8 +103,8 @@
   R.add = ( emitter ) ->
     ### TAINT only works with PipeStreams event emitters; could overwrite `emit` method otherwise ###
     unless ( type = CND.type_of emitter.on ) is 'function'
-      throw new Error "expected an event emitter with an `on` method, got a #{type}"
-    throw new Error "got duplicate emitter" if R._emitters.has emitter
+      throw new Error "µ4823 expected an event emitter with an `on` method, got a #{type}"
+    throw new Error "µ5588 got duplicate emitter" if R._emitters.has emitter
     R._emitters.set emitter, 1
     R._source_count += +1
     emitter.on '*', aggregator
@@ -188,9 +188,9 @@
   else
     path    = path_or_stream.path ? '<UNKNOWN PATH>'
     unless @_isa_njs_stream path_or_stream
-      throw new Error "expected a path or a stream, got a #{CND.type_of path_or_stream}"
+      throw new Error "µ6353 expected a path or a stream, got a #{CND.type_of path_or_stream}"
     unless path_or_stream.writable
-      throw new Error "expected a path or a stream, got a #{CND.type_of path_or_stream}"
+      throw new Error "µ7118 expected a path or a stream, got a #{CND.type_of path_or_stream}"
     stream  = path_or_stream
   ### TAINT intermediate solution ###
   R       = STPS.sink stream, ( error ) => throw error if error?
@@ -211,7 +211,7 @@

 #-----------------------------------------------------------------------------------------------------------
 @_new_file_sink_using_pwf = ( path, options = null ) ->
-  throw new Error "not implemented"
+  throw new Error "µ7883 not implemented"
   # TAINT errors with "DeprecationWarning: Calling an asynchronous function without callback is deprecated." (???)
   options ?= {}
   return new_file_sink path, options, ( error ) ->
@@ -223,15 +223,15 @@
 ### TAINT refactor: `PS.new_source.from_path`, `PS.new_source.from_text`..., `PS.new_sink.as_text` (???) ###
 @new_text_source = ( text ) -> $values [ text, ]
 @new_text_sink = ->
-  throw new Error "not implemented"
+  throw new Error "µ8648 not implemented"

 #-----------------------------------------------------------------------------------------------------------
 @new_value_source = ( values ) -> $values values

 #-----------------------------------------------------------------------------------------------------------
 @map_start = ( method ) ->
-  throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
-  throw new Error "method arity #{arity} not implemented" unless ( arity = method.length ) is 0
+  throw new Error "µ9413 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+  throw new Error "µ10178 method arity #{arity} not implemented" unless ( arity = method.length ) is 0
   is_first = yes
   return @_map_errors ( data ) =>
     if is_first
@@ -241,16 +241,16 @@

 #-----------------------------------------------------------------------------------------------------------
 @map_stop = ( method ) ->
-  throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
-  throw new Error "method arity #{arity} not implemented" unless ( arity = method.length ) is 0
+  throw new Error "µ10943 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+  throw new Error "µ11708 method arity #{arity} not implemented" unless ( arity = method.length ) is 0
   return $pass_through return_id, ( abort ) ->
     method()
     return abort

 #-----------------------------------------------------------------------------------------------------------
 @map_first = ( method ) ->
-  throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
-  throw new Error "method arity #{arity} not implemented" unless ( arity = method.length ) is 1
+  throw new Error "µ12473 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+  throw new Error "µ13238 method arity #{arity} not implemented" unless ( arity = method.length ) is 1
   is_first = yes
   return @_map_errors ( data ) =>
     if is_first
@@ -260,25 +260,25 @@

 #-----------------------------------------------------------------------------------------------------------
 @map_last = ( method ) ->
-  throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
-  throw new Error "method arity #{arity} not implemented" unless ( arity = method.length ) is 1
+  throw new Error "µ14003 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+  throw new Error "µ14768 method arity #{arity} not implemented" unless ( arity = method.length ) is 1
   throw new Error 'meh'

 #-----------------------------------------------------------------------------------------------------------
 @filter = ( method ) ->
-  throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+  throw new Error "µ15533 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
   switch arity = method.length
     when 1 then null
-    else throw new Error "method arity #{arity} not implemented"
+    else throw new Error "µ16298 method arity #{arity} not implemented"
   #.........................................................................................................
   return pull.filter method

 #-----------------------------------------------------------------------------------------------------------
 @map = ( method ) ->
-  throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+  throw new Error "µ17063 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
   switch arity = method.length
     when 1 then null
-    else throw new Error "method arity #{arity} not implemented"
+    else throw new Error "µ17828 method arity #{arity} not implemented"
   #.........................................................................................................
   return @_map_errors method

@@ -289,14 +289,14 @@
       method  = hint
       hint    = null
     when 2
-      throw new Error "unknown hint #{rpr hint}" unless hint is 'null'
-    else throw new Error "expected 1 or 2 arguments, got #{arity}"
+      throw new Error "µ18593 unknown hint #{rpr hint}" unless hint is 'null'
+    else throw new Error "µ19358 expected 1 or 2 arguments, got #{arity}"
   #.........................................................................................................
   switch client_arity = method.length
     when 2 then null
-    else throw new Error "method arity #{client_arity} not implemented"
-  #.........................................................................................................
-  throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+    else throw new Error "µ20123 method arity #{client_arity} not implemented"
+  #.........................................................................................................
+  throw new Error "µ20888 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
   #.........................................................................................................
   self    = null
   send    = ( data ) => self.queue data
@@ -324,8 +324,8 @@

 # #-----------------------------------------------------------------------------------------------------------
 # @$async = @remit_async = ( method ) ->
-#   throw new Error "expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
-#   throw new Error "### MEH ###" unless ( arity = method.length ) is 2
+#   throw new Error "µ21653 expected a function, got a #{type}" unless ( type = CND.type_of method ) is 'function'
+#   throw new Error "µ22418 ### MEH ###" unless ( arity = method.length ) is 2

 #===========================================================================================================
@@ -341,7 +341,7 @@
 @$push_to_list    = ( collector ) -> @_map_errors ( data ) => collector.push  data; return data
 @$add_to_set      = ( collector ) -> @_map_errors ( data ) => collector.add   data; return data
 #...........................................................................................................
-@$count           = -> throw new Error "not implemented"
+@$count           = -> throw new Error "µ23183 not implemented"
 @$take            = $take

 #-----------------------------------------------------------------------------------------------------------
@@ -369,9 +369,9 @@

 #-----------------------------------------------------------------------------------------------------------
 @$name_fields = ( names ) ->
-  throw new Error "expected a list, got a #{type}" unless ( type = CND.type_of names ) is 'list'
+  throw new Error "µ23948 expected a list, got a #{type}" unless ( type = CND.type_of names ) is 'list'
   return @_map_errors ( fields ) =>
-    throw new Error "expected a list, got a #{type}" unless ( type = CND.type_of fields ) is 'list'
+    throw new Error "µ24713 expected a list, got a #{type}" unless ( type = CND.type_of fields ) is 'list'
     R = {}
     for value, idx in fields
       name      = names[ idx ] ?= "field_#{idx}"
@@ -399,12 +399,12 @@
   return @$pass() if methods.length is 0
   for method, idx in methods
     continue if ( type = CND.type_of method ) is 'function'
-    throw new Error "expected a function, got a #{type} for argument # #{idx + 1}"
+    throw new Error "µ25478 expected a function, got a #{type} for argument # #{idx + 1}"
   return pull methods...

 #-----------------------------------------------------------------------------------------------------------
 @$split = ( settings ) ->
-  throw new Error "MEH" if settings?
+  throw new Error "µ26243 MEH" if settings?
   R         = []
   matcher   = null
   mapper    = null
@@ -429,12 +429,12 @@
           when 'text'
             joiner ?= ''
           when 'buffer'
-            throw new Error "joiner not supported for buffers, got #{rpr joiner}" if joiner?
+            throw new Error "µ27008 joiner not supported for buffers, got #{rpr joiner}" if joiner?
           else
-            throw new Error "expected a text or a buffer, got a #{type}"
+            throw new Error "µ27773 expected a text or a buffer, got a #{type}"
       else
         unless ( this_type = CND.type_of data ) is type
-          throw new Error "expected a #{type}, got a #{this_type}"
+          throw new Error "µ28538 expected a #{type}, got a #{this_type}"
       length += data.length
       collector.push data
     else
@@ -445,14 +445,14 @@

 #-----------------------------------------------------------------------------------------------------------
 @$pluck = ( settings ) ->
-  throw new Error "need settings 'keys', got #{rpr settings}" unless settings?
+  throw new Error "µ29303 need settings 'keys', got #{rpr settings}" unless settings?
   { keys, } = settings
-  throw new Error "need settings 'keys', got #{rpr settings}" unless keys?
+  throw new Error "µ30068 need settings 'keys', got #{rpr settings}" unless keys?
   keys      = keys.split /,\s*|\s+/ if CND.isa_text keys
-  throw new Error "need settings 'keys', got #{rpr settings}" unless keys.length > 0
+  throw new Error "µ30833 need settings 'keys', got #{rpr settings}" unless keys.length > 0
   as        = settings[ 'as' ] ? 'object'
   unless as in [ 'list', 'object', 'pod', ]
-    throw new Error "expected 'list', 'object' or 'pod', got #{rpr as}"
+    throw new Error "µ31598 expected 'list', 'object' or 'pod', got #{rpr as}"
   if as is 'list'
     return @map ( data ) => ( data[ key ] for key in keys )
   return @map ( data ) =>
@@ -462,7 +462,7 @@

 #-----------------------------------------------------------------------------------------------------------
 @$gliding_window = ( width, method ) ->
-  throw new Error "expected a number, got a #{type}" unless ( CND.type_of width ) is 'number'
+  throw new Error "µ32363 expected a number, got a #{type}" unless ( CND.type_of width ) is 'number'
   section = []
   send    = null
   #.........................................................................................................
@@ -486,7 +486,7 @@

 #-----------------------------------------------------------------------------------------------------------
 @$collect = ( settings ) ->
-  throw new Error "API changed" if settings?
+  throw new Error "µ33128 API changed" if settings?
   collector = []
   return @$ 'null', ( data, send ) =>
     if data? then collector.push data
@@ -512,10 +512,10 @@
       direction = settings[ 'direction' ] ? 'ascending'
       sorter    = settings[ 'sorter'    ] ? null
       key       = settings[ 'key'       ] ? null
-    else throw new Error "expected 0 or 1 arguments, got #{arity}"
+    else throw new Error "µ33893 expected 0 or 1 arguments, got #{arity}"
   #.........................................................................................................
   unless direction in [ 'ascending', 'descending', ]
-    throw new Error "expected 'ascending' or 'descending' for direction, got #{rpr direction}"
+    throw new Error "µ34658 expected 'ascending' or 'descending' for direction, got #{rpr direction}"
   #.........................................................................................................
   unless sorter?
     #.......................................................................................................
@@ -526,13 +526,13 @@
     #.......................................................................................................
     validate_type = ( type_a, type_b, include_list = no ) =>
       unless type_a is type_b
-        throw new Error "unable to compare a #{type_a} with a #{type_b}"
+        throw new Error "µ35423 unable to compare a #{type_a} with a #{type_b}"
       if include_list
         unless type_a in [ 'number', 'date', 'text', 'list', ]
-          throw new Error "unable to compare values of type #{type_a}"
+          throw new Error "µ36188 unable to compare values of type #{type_a}"
       else
         unless type_a in [ 'number', 'date', 'text', ]
-          throw new Error "unable to compare values of type #{type_a}"
+          throw new Error "µ36953 unable to compare values of type #{type_a}"
       return null
     #.......................................................................................................
     if key?
@@ -613,7 +613,7 @@
         when 'stdout'   then  stdout.push value
         when 'stderr'   then  stderr.push value
         when 'exit'     then  return handler null, Object.assign { command, stdout, stderr, }, value
-        else throw new Error "internal error 2201991"
+        else throw new Error "µ37718 internal error 2201991"
       return null
   #.........................................................................................................
   source    = @spawn P...
@@ -634,9 +634,9 @@
   #.........................................................................................................
   switch arity = arguments.length
     when 1, 2 then null
-    else throw new Error "expected 1 or 2 arguments, got #{arity}"
-  #.........................................................................................................
-  # throw new Error "deprecated setting: error_to_exit" if ( pluck settings, 'error_to_exit',  null )?
+    else throw new Error "µ38483 expected 1 or 2 arguments, got #{arity}"
+  #.........................................................................................................
+  # throw new Error "µ39248 deprecated setting: error_to_exit" if ( pluck settings, 'error_to_exit',  null )?
   # stderr_target     = pluck settings, 'stderr', 'stderr'
   settings          = Object.assign { shell: yes, }, settings
   stdout_is_binary  = pluck settings, 'binary',         no
@@ -650,9 +650,9 @@
       cp = CP.spawn command, settings
     when 'list'
       unless command.length > 0
-        throw new Error "expected a list with at least one value, got #{rpr command}"
+        throw new Error "µ40013 expected a list with at least one value, got #{rpr command}"
       cp = CP.spawn command[ 0 ], command[ 1 .. ], settings
-    else throw new Error "expected a text or a list for command, got #{command_type}"
+    else throw new Error "µ40778 expected a text or a list for command, got #{command_type}"
   #.........................................................................................................
   stdout            = STPS.source cp.stdout
   stderr            = STPS.source cp.stderr
@@ -749,7 +749,7 @@
               value.error = error.join '\n'
               value.error = null if value.error.length is 0
               send event
-            else throw new Error "internal error 110918"
+            else throw new Error "µ41543 internal error 110918"
   #.........................................................................................................
   source = pull funnel...
   return [ cp, source, ]
@@ -774,7 +774,7 @@
 @$sample = ( p = 0.5, options ) ->
   #.........................................................................................................
   unless 0 <= p <= 1
-    throw new Error "expected a number between 0 and 1, got #{rpr p}"
+    throw new Error "µ42308 expected a number between 0 and 1, got #{rpr p}"
   #.........................................................................................................
   ### Handle trivial edge cases faster (hopefully): ###
   return ( @map     ( record ) => record  ) if p == 1


Smaller horizontal edges

Smaller horizontal edges

--- src/main.coffee
+++ src/main.coffee
@@ -50,6 +50,8 @@
 		W = rectangle.width
 		H = rectangle.height

+		E = H / 8
+
 		puzzle = new P.Path()
 		puzzle.strokeWidth = 0

@@ -63,18 +65,18 @@
 		if pieceEdges[i].right is 1

 			# Outwards
-			puzzle.lineTo new P.Point W, H / 4
-			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W + H / 4, H / 4)
-			puzzle.cubicCurveTo new P.Point(W + H / 2, 0), new P.Point(W + H / 2, H), new P.Point(W + H / 4, H * 3 / 4)
-			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W, H * 3 / 4)
+			puzzle.lineTo new P.Point W, 3 * E
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W + E, 3 * E)
+			puzzle.cubicCurveTo new P.Point(W + 2 * E, 2 * E), new P.Point(W + 2 * E, 6 * E), new P.Point(W + E, 5 * E)
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W, 5 * E)

 		else if pieceEdges[i].right is -1

 			# Inwards
-			puzzle.lineTo new P.Point W, H / 4
-			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W - H / 4, H / 4)
-			puzzle.cubicCurveTo new P.Point(W - H / 2, 0), new P.Point(W - H / 2, H), new P.Point(W - H / 4, H * 3 / 4)
-			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W, H * 3 / 4)
+			puzzle.lineTo new P.Point W, 3 * E
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W - E, 3 * E)
+			puzzle.cubicCurveTo new P.Point(W - 2 * E, 2 * E), new P.Point(W - 2 * E, 6 * E), new P.Point(W - E, 5 * E)
+			puzzle.quadraticCurveTo new P.Point(W, H / 2), new P.Point(W, 5 * E)

 		# Straight
 		puzzle.lineTo new P.Point W, H
@@ -108,18 +110,18 @@
 		if pieceEdges[i].left is 1

 			# Outwards
-			puzzle.lineTo new P.Point 0, H * 3 / 4
-			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0 - H / 4, H * 3 / 4)
-			puzzle.cubicCurveTo new P.Point(0 - H / 2, H), new P.Point(0 - H / 2, 0), new P.Point(0 - H / 4, H / 4)
-			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0, H / 4)
+			puzzle.lineTo new P.Point 0, 5 * E
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(-E, 5 * E)
+			puzzle.cubicCurveTo new P.Point(-2 * E, 6 * E), new P.Point(-2 * E, 2 * E), new P.Point(-E, 3 * E)
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0, 3 * E)

 		else if pieceEdges[i].left is -1

 			# Inwards
-			puzzle.lineTo new P.Point 0, H * 3 / 4
-			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0 + H / 4, H * 3 / 4)
-			puzzle.cubicCurveTo new P.Point(0 + H / 2, H), new P.Point(0 + H / 2, 0), new P.Point(0 + H / 4, H / 4)
-			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0, H / 4)
+			puzzle.lineTo new P.Point 0, 5 * E
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(E, 5 * E)
+			puzzle.cubicCurveTo new P.Point(2 * E, 6 * E), new P.Point(2 * E, 2 * E), new P.Point(E, 3 * E)
+			puzzle.quadraticCurveTo new P.Point(0, H / 2), new P.Point(0, 3 * E)

 		# Close
 		puzzle.closePath()
@@ -192,7 +194,7 @@
 			if piece.zone.contains event.point
 				piece.zone.fillColor = 'gray'

-		actualPiece.group.strokeWidth = 10
+		actualPiece.group.strokeWidth = 3
 		actualPiece.group.bringToFront()

 	tool.onMouseUp = (event) ->


Increasing intervalIntersects performance by 50%.

Only perform array lookups once and don't call a function
to test in-between-ness.

--- src/math.coffee
+++ src/math.coffee
@@ -12,18 +12,16 @@
     return Math.random() * (upper - lower + 1) + lower

 # Return true if the given interval intersects, false otherwise.
-#
-# @param i1 {Array} an interval
-# @param i2 {Array} an interval
-# @return {Boolean}
 wolf.intervalIntersects = (i1, i2) ->

-    ib = wolf.isBetween
+    [i10, i11] = i1
+    [i20, i21] = i2

-    return ib(i1[0], i1[1], i2[0]) or
-           ib(i1[0], i1[1], i2[1]) or
-           ib(i2[0], i2[1], i1[0]) or
-           ib(i2[0], i2[1], i1[1])
+    return i10 <= i20 <= i11 or
+           i10 <= i21 <= i11 or
+           i20 <= i10 <= i21 or
+           i20 <= i11 <= i21
+

 class wolf.Point


CNS-556: Editing a Calendar Assignment for a second time is not working


cosmetics.

cosmetics.


Correct Life Orb message

Correct Life Orb message

--- server/bw/data/items.coffee
+++ server/bw/data/items.coffee
@@ -391,7 +391,7 @@
   this::afterAllHits = (move) ->
     return  if move.isNonDamaging()
     if @pokemon.damage(Math.floor(@pokemon.stat('hp') / 10))
-      @battle.message "#{@pokemon.name} hurt itself with its Life Orb."
+      @battle.message "#{@pokemon.name} lost some of its HP!"

 makeItem 'Light Clay' # Hardcoded in Attachment.Screen


Make instructions easier to follow.

Make instructions easier to follow.

--- src/hubot-fitbit-leaders.coffee
+++ src/hubot-fitbit-leaders.coffee
@@ -89,7 +89,7 @@
       unless user.fullName
         user.fullName = 'the bot'

-      msg.send "Friend #{user.fullName} at http://fitbit.com/user/#{user.encodedId}"
+      msg.send "1) Add #{user.fullName} as a friend - http://fitbit.com/user/#{user.encodedId}\n2) Type `#{robot.name} fitbit approve`"

   # Approve existing friend requests
   robot.respond /fitbit approve/i, (msg) ->


always subscribe using an object

always subscribe using an object

--- lib/assets/javascripts/cable_bindings.coffee
+++ lib/assets/javascripts/cable_bindings.coffee
@@ -14,11 +14,10 @@
         subscriptionOptions = JSON.parse(subscriptionParams)
         subscriptionOptions.channel = @getAttribute('channel')
       else
-        subscriptionOptions = @getAttribute('channel')
+        subscriptionOptions = {channel: @getAttribute('channel')}
       @subscription = cable.subscriptions.create subscriptionOptions,
         received: (data)=>
           $(this).trigger("cable:received", data)
-
       $(this).on 'cable:perform', (e, action, params)=>
         @subscription.perform action, params


Disable auxclick

Disable auxclick

--- src/main-process/atom-window.coffee
+++ src/main-process/atom-window.coffee
@@ -32,6 +32,10 @@
         # local development experience when running specs through the UI (which
         # now won't pause when e.g. minimizing the window).
         backgroundThrottling: not @isSpec
+        # Disable the `auxclick` feature so that `click` events are triggered in
+        # response to a middle-click.
+        # (Ref: https://github.com/atom/atom/pull/12696#issuecomment-290496960)
+        disableBlinkFeatures: 'Auxclick'

     # Don't set icon on Windows so the exe's ico will be used as window and
     # taskbar's icon. See https://github.com/atom/atom/issues/4811 for more.


Fix room names to not include a space.

Fix room names to not include a space.

Adding the space is unreliable without making exceptions for certain
building abbreviations.

Some examples:
- M31006 would be split to "M 31006" instead of "M3 1006". We could
solve this one by ensuring all building names are two or more
characters (and allow the second character to be a number).
- EV34412 would be split into "EV 34412" instead of "EV3 4412", which
isn't possible to split without making an exception for the EV[1-3]
buildings.

I don't really feel it's worth making special cases at this point, so
we'll just do without the space for now.

--- routes/room.coffee
+++ routes/room.coffee
@@ -92,9 +92,7 @@
       ))

       day.hasClasses = day.classes?.length > 0
-
-      roomComps = req.params.room.match(/([A-Z]*)([0-9]*)/)
-      day.room = "#{roomComps[1]} #{roomComps[2]}"
+      day.room = req.params.room

       res.render('room', day)
     )


Browser compatibility

Browser compatibility

--- src/lib/LoggingComponent.coffee
+++ src/lib/LoggingComponent.coffee
@@ -1,9 +1,13 @@
-
 { Component } = require "./Component"
 { Port } = require "./Port"
-util = require "util"
+if typeof process is 'object' and process.title is 'node'
+  util = require "util"
+else
+  util =
+    inspect: (data) -> data

-# This class should not be put into a flow. It is intended to be a parent class to real components.
+# This class should not be put into a flow. It is intended to be a
+# parent class to real components.
 # You might use it in your own classes like this:
 #
 # noflo = require "noflo"
@@ -37,4 +41,3 @@
       @outPorts.log.send message
     else
       console.log util.inspect message, 4, true, true
-


facebook canvas test. Don't track item sales.  Too spammy.

--- src/adventure/itemView.coffee
+++ src/adventure/itemView.coffee
@@ -208,15 +208,7 @@
   processResult: (param) =>
     item = @items[@selected]
     if param is "SELL_YES"
-      oldGold = @gurk.game.gold
       @gurk.game.gold += item.getShopValue(@sellRate)
-      eburp.track 'Sell Item',
-        name: item.name,
-        id: item.id,
-        type: item.template.type,
-        value: item.template.baseValue
-        playerBefore:oldGold
-        playerAfter:@gurk.game.gold
       @player.dropItem(item)
       @doLayout()
     else if (param == "DROP_YES")


Added the ability to delete a highlight

Added the ability to delete a highlight

--- src/annotator.readmill.coffee
+++ src/annotator.readmill.coffee
@@ -120,11 +120,12 @@
     @error "Unable to create reading for this book"

   _onCreateHighlight: (annotation, data) ->
-    annotation.highlightUrl = data.location
-
     # Now try and get a permalink for the comment by fetching the first
     # comment for the newly created highlight.
     @client.request(url: data.location).done (highlight) =>
+      # Need to store this rather than data.location in order to be able to
+      # delete the highlight at a later date.
+      annotation.highlightUrl = highlight.uri
       @client.request(url: highlight.comments).done (comments) ->
         annotation.commentUrl = comments[0].uri if comments.length

@@ -150,7 +151,9 @@
       request.error (xhr) => @error "Unable to update annotation in Readmill"

   _onAnnotationDeleted: (annotation) =>
-
+    if annotation.highlightUrl
+      @client.deleteHighlight(annotation.highlightUrl).error =>
+        @error "Unable to update annotation in Readmill"

 utils =
   serializeQueryString: (obj, sep="&", eq="=") ->
@@ -271,7 +274,8 @@
   createHighlight: (url, highlight, comment) ->
     @request type: "POST", url: url, data: {highlight, comment}

-  deleteHighlight: ->
+  deleteHighlight: (url) ->
+    @request type: "DELETE", url: url

   updateComment: (url, comment) ->
     # Need to provide a data filter to trim the re


Fix typo

Fix typo

--- src/push.coffee
+++ src/push.coffee
@@ -67,7 +67,7 @@
     runTest 'gulp jshint'
     runTest 'gulp lint-css:style-names'
     runTest 'gulp lint-css:doiuse'
-    runTest 'gulp translations-update-fail-on-incomplet'
+    runTest 'gulp translations-update-fail-on-incomplete'
     runTest 'gulp protractor:single-run'
     runTest 'gulp karma:single-run'


Minor updates in service_call(...)

Minor updates in service_call(...)

--- main/static/src/script/common/service.coffee
+++ main/static/src/script/common/service.coffee
@@ -16,14 +16,14 @@
     accepts: 'application/json'
     dataType: 'json'
     data: if data then JSON.stringify(data) else undefined
-    success: (response) ->
-      if response.status == 'success'
+    success: (data, textStatus, jqXHR) ->
+      if data.status == 'success'
         more = undefined
-        if response.more_url
-          more = (callback) -> service_call(method, response.more_url, {}, callback)
-        callback? undefined, response.result, more
+        if data.more_url
+          more = (callback) -> service_call(method, data.more_url, {}, callback)
+        callback? undefined, data.result, more
       else
-        callback? response
+        callback? data
     error: (jqXHR, textStatus, errorThrown) ->
       error =
         error_code: 'ajax_error'


:fire: Delete obsolete code

:fire: Delete obsolete code

--- src/lines-tile-component.coffee
+++ src/lines-tile-component.coffee
@@ -193,7 +193,7 @@
   screenRowForNode: (node) -> parseInt(node.dataset.screenRow)

   buildLineNode: (id) ->
-    {screenRow, decorationClasses} = @newTileState.lines[id]
+    {lineText, tagCodes, screenRow, decorationClasses} = @newTileState.lines[id]

     lineNode = @domElementPool.buildElement("div", "line")
     lineNode.dataset.screenRow = screenRow
@@ -202,57 +202,7 @@
       for decorationClass in decorationClasses
         lineNode.classList.add(decorationClass)

-    @currentLineTextNodes = []
-    # if words.length is 0
-    #   @setEmptyLineInnerNodes(id, lineNode)
-
-    @setLineInnerNodes(id, lineNode)
-    @textNodesByLineId[id] = @currentLineTextNodes
-
-    # lineNode.appendChild(@domElementPool.buildElement("span", "fold-marker")) if fold
-    lineNode
-
-  setEmptyLineInnerNodes: (id, lineNode) ->
-    {indentGuidesVisible} = @newState
-    {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
-
-    if indentGuidesVisible and indentLevel > 0
-      invisibleIndex = 0
-      for i in [0...indentLevel]
-        indentGuide = @domElementPool.buildElement("span", "indent-guide")
-        for j in [0...tabLength]
-          if invisible = endOfLineInvisibles?[invisibleIndex++]
-            invisibleSpan = @domElementPool.buildElement("span", "invisible-character")
-            textNode = @domElementPool.buildText(invisible)
-            invisibleSpan.appendChild(textNode)
-            indentGuide.appendChild(invisibleSpan)
-
-            @currentLineTextNodes.push(textNode)
-          else
-            textNode = @domElementPool.buildText(" ")
-            indentGuide.appendChild(textNode)
-
-            @currentLineTextNodes.push(textNode)
-        lineNode.appendChild(indentGuide)
-
-      while invisibleIndex < endOfLineInvisibles?.length
-        invisible = endOfLineInvisibles[invisibleIndex++]
-        invisibleSpan = @domElementPool.buildElement("span", "invisible-character")
-        textNode = @domElementPool.buildText(invisible)
-        invisibleSpan.appendChild(textNode)
-        lineNode.appendChild(invisibleSpan)
-
-        @currentLineTextNodes.push(textNode)
-    else
-      unless @appendEndOfLineNodes(id, lineNode)
-        textNode = @domElementPool.buildText("\u00a0")
-        lineNode.appendChild(textNode)
-
-        @currentLineTextNodes.push(textNode)
-
-  setLineInnerNodes: (id, lineNode) ->
-    {lineText, tagCodes} = @newTileState.lines[id]
-
+    textNodes = []
     lineLength = 0
     startIndex = 0
     openScopeNode = lineNode
@@ -268,100 +218,15 @@
         textNode = @domElementPool.buildText(lineText.substr(startIndex, tagCode))
         startIndex += tagCode
         openScopeNode.appendChild(textNode)
-        @currentLineTextNodes.push(textNode)
+        textNodes.push(textNode)

     if startIndex is 0
       textNode = @domElementPool.buildText(' ')
       lineNode.appendChild(textNode)
-      @currentLineTextNodes.push(textNode)
-
-  appendTokenNodes: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters, scopeNode) ->
-    if isHardTab
-      textNode = @domElementPool.buildText(tokenText)
-      hardTabNode = @domElementPool.buildElement("span", "hard-tab")
-      hardTabNode.classList.add("leading-whitespace") if firstNonWhitespaceIndex?
-      hardTabNode.classList.add("trailing-whitespace") if firstTrailingWhitespaceIndex?
-      hardTabNode.classList.add("indent-guide") if hasIndentGuide
-      hardTabNode.classList.add("invisible-character") if hasInvisibleCharacters
-      hardTabNode.appendChild(textNode)
-
-      scopeNode.appendChild(hardTabNode)
-      @currentLineTextNodes.push(textNode)
-    else
-      startIndex = 0
-      endIndex = tokenText.length
-
-      leadingWhitespaceNode = null
-      leadingWhitespaceTextNode = null
-      trailingWhitespaceNode = null
-      trailingWhitespaceTextNode = null
-
-      if firstNonWhitespaceIndex?
-        leadingWhitespaceTextNode =
-          @domElementPool.buildText(tokenText.substring(0, firstNonWhitespaceIndex))
-        leadingWhitespaceNode = @domElementPool.buildElement("span", "leading-whitespace")
-        leadingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide
-        leadingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
-        leadingWhitespaceNode.appendChild(leadingWhitespaceTextNode)
-
-        startIndex = firstNonWhitespaceIndex
-
-      if firstTrailingWhitespaceIndex?
-        tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
-
-        trailingWhitespaceTextNode =
-          @domElementPool.buildText(tokenText.substring(firstTrailingWhitespaceIndex))
-        trailingWhitespaceNode = @domElementPool.buildElement("span", "trailing-whitespace")
-        trailingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
-        trailingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
-        trailingWhitespaceNode.appendChild(trailingWhitespaceTextNode)
-
-        endIndex = firstTrailingWhitespaceIndex
-
-      if leadingWhitespaceNode?
-        scopeNode.appendChild(leadingWhitespaceNode)
-        @currentLineTextNodes.push(leadingWhitespaceTextNode)
-
-      if tokenText.length > MaxTokenLength
-        while startIndex < endIndex
-          textNode = @domElementPool.buildText(
-            @sliceText(tokenText, startIndex, startIndex + MaxTokenLength)
-          )
-          textSpan = @domElementPool.buildElement("span")
-
-          textSpan.appendChild(textNode)
-          scopeNode.appendChild(textSpan)
-          startIndex += MaxTokenLength
-          @currentLineTextNodes.push(textNode)
-      else
-        textNode = @domElementPool.buildText(@sliceText(tokenText, startIndex, endIndex))
-        scopeNode.appendChild(textNode)
-        @currentLineTextNodes.push(textNode)
-
-      if trailingWhitespaceNode?
-        scopeNode.appendChild(trailingWhitespaceNode)
-        @currentLineTextNodes.push(trailingWhitespaceTextNode)
-
-  sliceText: (tokenText, startIndex, endIndex) ->
-    if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
-      tokenText = tokenText.slice(startIndex, endIndex)
-    tokenText
-
-  appendEndOfLineNodes: (id, lineNode) ->
-    {endOfLineInvisibles} = @newTileState.lines[id]
-
-    hasInvisibles = false
-    if endOfLineInvisibles?
-      for invisible in endOfLineInvisibles
-        hasInvisibles = true
-        invisibleSpan = @domElementPool.buildElement("span", "invisible-character")
-        textNode = @domElementPool.buildText(invisible)
-        invisibleSpan.appendChild(textNode)
-        lineNode.appendChild(invisibleSpan)
-
-        @currentLineTextNodes.push(textNode)
-
-    hasInvisibles
+      textNodes.push(textNode)
+
+    @textNodesByLineId[id] = textNodes
+    lineNode

   updateLineNode: (id) ->
     oldLineState = @oldTileState.lines[id]


Add HTTP Error Test

Add HTTP Error Test

--- test/unit/list_detail_resource_spec.coffee
+++ test/unit/list_detail_resource_spec.coffee
@@ -35,6 +35,8 @@
     $httpBackend.whenGET(messageDetailUrl).respond (method, url, data, headers) ->
       id = +messageDetailUrl.exec(url)[1]
       log "GET /messages/#{id}"
+      if id is 42
+        return [403, {err: 'Access Denied', msg: 'Classified'}, {}]
       [200, _.findWhere(messages, id: id), {}]

     $httpBackend.whenPOST('/messages').respond (method, url, data) ->
@@ -208,3 +210,13 @@
       done new Error JSON.stringify err

     tick()
+
+  it "should reject promise on HTTP error", (done) ->
+    Messages.get(id: 42).$promise
+    .then (data) ->
+      done new Error "HTTP error did not reject promise."
+    .then null, (err) ->
+      expect(err).to.exist
+      done(null)
+
+    tick()


we should not test against the live environment

--- nodejs/test/fraisr-api-sdk.coffee
+++ nodejs/test/fraisr-api-sdk.coffee
@@ -1,7 +1,7 @@
 FraisrAPI = require "../index"
 key = process.env.FRAISR_API_KEY ? null
 secret = process.env.FRAISR_API_SECRET ? null
-mode = process.env.FRAISR_API_MODE ? "sandbox"
+mode = "sandbox"
 url = process.env.FRAISR_API_URL ? null
 version = 1


Finish rotation

Finish rotation

--- coffee/models/piece.coffee
+++ coffee/models/piece.coffee
@@ -55,18 +55,41 @@
           @storage[(x + y * @width) * 4 + 3] = (if pieces[piecenum][y][x] then (if peer then 100 else 255) else 0)

   rotate: (times) ->
+    newstorage = (x for x in @storage)
+    newwidth = @width
+    newheight = @height
     for i in [0...times] by 1
-      newstorage = new Array(@storage.length)
-      for x in [0...@width] by 1
-        for y in [0...@height] by 1
-          newstorage[(@height - y - 1 + x * @width) * 4] = @storage[(x + y * @width) * 4]
-          newstorage[(@height - y - 1 + x * @width) * 4 + 1] = @storage[(x + y * @width) * 4 + 1]
-          newstorage[(@height - y - 1 + x * @width) * 4 + 2] = @storage[(x + y * @width) * 4 + 2]
-          newstorage[(@height - y - 1 + x * @width) * 4 + 3] = @storage[(x + y * @width) * 4 + 3]
+      tmpstorage = new Array(newstorage.length)
+      for x in [0...newwidth] by 1
+        for y in [0...newheight] by 1
+          tmpstorage[(newheight - y - 1 + x * newwidth) * 4] = newstorage[(x + y * newwidth) * 4]
+          tmpstorage[(newheight - y - 1 + x * newwidth) * 4 + 1] = newstorage[(x + y * newwidth) * 4 + 1]
+          tmpstorage[(newheight - y - 1 + x * newwidth) * 4 + 2] = newstorage[(x + y * newwidth) * 4 + 2]
+          tmpstorage[(newheight - y - 1 + x * newwidth) * 4 + 3] = newstorage[(x + y * newwidth) * 4 + 3]
+      newstorage = (x for x in tmpstorage)
+      tmp = newwidth
+      newwidth = newheight
+      newheight = tmp
+
+    deltaX = Math.floor(@width / 2 - newwidth / 2)
+    deltaY = Math.floor(@height / 2 - newheight / 2)
+
+    collide = Tetrus.get('controllers.game').game.collide
+
+    unless collide(newstorage, @position.x + deltaX, @position.y + deltaY)
       @storage = newstorage
-      tmp = @width
-      @width = @height
-      @height = tmp
+      @position.x += deltaX
+      @position.y += deltaY
+    else
+      radius = 2
+      for dy in [-radius..radius] by 1
+        for dx in [-radius..radius] by 1
+          continue if dx == 0 && dy == 0
+          unless collide(newstorage, @position.x + deltaX + dx, @position.y + deltaY + dy)
+            @storage = newstorage
+            @position.x += deltaX + dx
+            @position.y += deltaY + dy
+            return

   apply: (piece) ->
     @storage = piece.storage


Remove console logging.

Remove console logging.

--- coffeescripts/cards.coffee
+++ coffeescripts/cards.coffee
@@ -1270,8 +1270,6 @@
         points: 2
         text: """(Unconfirmed) After an enemy ship executes a maneuver that causes it to overlap your ship, roll 1 attack die.  On a  or  result, the enemy ship suffers 1 damage."""
         restriction_func: (ship) ->
-            console.log "Check restriction against ship"
-            console.dir ship
             ship.large ? false


Fix bug where translations prevented aliased unique claims.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -855,7 +855,7 @@
                 # Multiple upgrades have the same name but different slots
                 for upgrade_alias in unique.aka ? []
                     #console.log "Also claiming #{upgrade_alias} in use"
-                    @uniques_in_use['Upgrade'].push exportObj.upgradesByLocalizedName[upgrade_alias]
+                    @uniques_in_use['Upgrade'].push exportObj.upgrades[upgrade_alias]
             @uniques_in_use[type].push unique
         else
             throw new Error("Unique #{type} '#{unique.name}' already claimed")
@@ -884,7 +884,7 @@
                 # Release any aliases
                 for upgrade_alias in unique.aka ? []
                     #console.log "Also releasing #{upgrade_alias}"
-                    alias_idx = @uniqueIndex(exportObj.upgradesByLocalizedName[upgrade_alias], 'Upgrade')
+                    alias_idx = @uniqueIndex(exportObj.upgrades[upgrade_alias], 'Upgrade')
                     @uniques_in_use['Upgrade'].splice alias_idx, 1
         else
             throw new Error("Unique #{type} '#{unique.name}' not in use")


:racehorse: Only call _.min when there are multiple results

--- src/rule.coffee
+++ src/rule.coffee
@@ -78,10 +78,14 @@
         if result = scanner.findNextMatch(lineWithNewline, position)
           results.push(result)

-    if results.length > 0
+    if results.length > 1
       _.min results, (result) =>
         @normalizeCaptureIndices(line, result.captureIndices)
         result.captureIndices[0].start
+    else if results.length is 1
+      result = results[0]
+      @normalizeCaptureIndices(line, result.captureIndices)
+      result

   getNextTokens: (ruleStack, line, position, firstLine) ->
     result = @findNextMatch(ruleStack, line, position, firstLine)


Fix broken unit test from merge

Fix broken unit test from merge

--- services/document-updater/test/unit/coffee/RangesManager/RangesManagerTests.coffee
+++ services/document-updater/test/unit/coffee/RangesManager/RangesManagerTests.coffee
@@ -94,6 +94,7 @@
 					op: [{
 						c: "one"
 						p: 0
+						t: "thread-id-1"
 					}]
 				}]
 				@entries = {
@@ -101,12 +102,14 @@
 						op:
 							c: "three "
 							p: 4
+							t: "thread-id-2"
 						metadata:
 							user_id: @user_id
 					}, {
 						op:
 							c: "four "
 							p: 10
+							t: "thread-id-3"
 						metadata:
 							user_id: @user_id
 					}]
@@ -115,7 +118,6 @@
 				@RangesManager.applyUpdate @project_id, @doc_id, @entries, @updates, @newDocLines, @callback

 			it "should return an error", ->
-				# Save space in redis and don't store just {}
 				@callback.called.should.equal true
 				[error, entries] = @callback.args[0]
 				expect(error).to.not.be.null


Fix rank URL at nabal_battle

Fix rank URL at nabal_battle

--- server/app/assets/javascript/naval_battle.coffee
+++ server/app/assets/javascript/naval_battle.coffee
@@ -49,12 +49,14 @@
         @page = parseInt(obj.page)
         @bossOnly = obj.bossOnly != 'false'
         @dropOnly = obj.dropOnly != 'false'
-        for str, _ of @ranks.key
+        for str, _ of @ranks
           @ranks[str] = obj.rank.indexOf(str) != -1
     created: ->
       @restoreHash()
       @getData()
     ready: ->
-      values = ['page', 'bossOnly', 'dropOnly', 'ranks']
-      values.map (v) =>
-        @$watch v, () -> @getData()
+      @$watch 'ranks', @getData, true # Deep Watch
+    watch:
+      'page': -> @getData()
+      'bossOnly': -> @getData()
+      'dropOnly': -> @getData()