Add a test to check that datetime type returned from a query is plausible.

--- test/integration/connection-test.coffee
+++ test/integration/connection-test.coffee
@@ -125,6 +125,30 @@
     #console.log(message)
   )

+exports.datetimeType = (test) ->
+  test.expect(3)
+
+  connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) ->
+    test.ok(!err)
+
+    connection.execSql("select getdate()", (err, rowCount) ->
+      test.ok(!err)
+      test.done()
+    )
+  )
+
+  connection.on('row', (columns) ->
+    dbDate = columns[0].value.getTime()
+    now = new Date().getTime()
+    difference = Math.abs(now - dbDate)
+
+    test.ok(difference < 100)
+  )
+
+  connection.on('debug', (message) ->
+    #console.log(message)
+  )
+
 exports.execBadSql = (test) ->
   test.expect(6)


Adding test for code printing in the wiki tests

--- test/unit/plugins/wikiPluginSpec.coffee
+++ test/unit/plugins/wikiPluginSpec.coffee
@@ -55,6 +55,12 @@
             expectedResult = '

\n'
             expect(gmWiki.render('![test](test.png)')).to.be.equal(expectedResult)

+        it 'should allow to render code in the wiki', inject (gmWiki, $routeParams, $rootScope) ->
+            expectedResult = '
print("test")\n
\n'
+            expect(gmWiki.render('```python\nprint("test")\n```')).to.be.equal(expectedResult)
+            expectedResult = '
print("test")\n
'
+            expect(gmWiki.render('```\nprint("test")\n```')).to.be.equal(expectedResult)
+
     describe 'wiki filter', ->
         it 'should allow to render in wiki format', inject (wikiFilter) ->
             expect(wikiFilter('**test**')).to.be.equal('
test
\n')


remove fdescribe

remove fdescribe

--- angular/test/protractor/poll_spec.coffee
+++ angular/test/protractor/poll_spec.coffee
@@ -1,4 +1,4 @@
-fdescribe 'Polls', ->
+describe 'Polls', ->
   page = require './helpers/page_helper.coffee'

   it 'can start a poll in a group', ->


codepen: fixed coffee tab issue

codepen: fixed coffee tab issue

--- app/_src/components/codepen-embed.coffee
+++ app/_src/components/codepen-embed.coffee
@@ -50,7 +50,7 @@
 			# after it's loaded (ei.js)
 			@injectCodePenScriptTag()
 		else if typeof CodePenEmbed is 'object'
-				CodePenEmbed.init()
+			CodePenEmbed.init()

 	getTemplate: (params) ->
 		"

"


OX-103 selected node is now centered when selected in 3d-view and when jumping to a branch point

--- app/assets/javascripts/controller.coffee
+++ app/assets/javascripts/controller.coffee
@@ -185,7 +185,7 @@
         @model.Route.putBranch()
       "j" : => @model.Route.popBranch().done(
         (id) =>
-          @setActiveNode(id)
+          @setActiveNode(id, true)
         )

       #Zoom in/out
@@ -298,18 +298,15 @@
       intersectsCoord = [intersects[0].point.x, intersects[0].point.y, intersects[0].point.z]
       globalPos = @flycam.getGlobalPos()

-      # console.log clickCoords[2 - plane]
-      # console.log intersectsCoord[2 - plane]
-      # console.log @cameraController.getRouteClippingDistance()
-      # console.log Math.abs(clickCoords[2 - plane] - intersectsCoord[2 - plane])
-
       # make sure you can't click nodes, that are clipped away (one can't see)
       ind = @flycam.getIndices(plane)
       if plane == VIEW_3D or (Math.abs(globalPos[ind[2]] - intersectsCoord[ind[2]]) < @cameraController.getRouteClippingDistance())
       # intersects[0].object.material.color.setHex(Math.random() * 0xffffff)
         vertex = intersects[0].object.geometry.vertices[intersects[0].vertex]
       # set the active Node to the one that has the ID stored in the vertex
-        @setActiveNode(vertex.nodeId)
+      # center the node if click was in 3d-view
+        centered = plane == VIEW_3D
+        @setActiveNode(vertex.nodeId, centered)

   ########### Model Interaction

@@ -321,10 +318,10 @@
     @gui.updateRadius()
     @sceneController.setWaypoint()

-  setActiveNode : (nodeId) =>
+  setActiveNode : (nodeId, centered) =>
     @model.Route.setActiveNode(nodeId)
-    #### isn't centered anymore, was distracting in the viewports, wrote a mail to kevin to ask for his favourite behaviour
-    #@flycam.setGlobalPos(@model.Route.getActiveNodePos())
+    if centered
+      @flycam.setGlobalPos(@model.Route.getActiveNodePos())
     @flycam.hasChanged = true
     @gui.update()
     @sceneController.skeleton.setActiveNode()


changed ?= to = in user.coffee

changed ?= to = in user.coffee

--- app/assets/javascripts/model/user.coffee
+++ app/assets/javascripts/model/user.coffee
@@ -3,7 +3,7 @@
 ###

 # This takes care of the userdate.
-User ?= {}
+User = {}

 # Debounce for POST User.Configuration
 DEBOUNCE_TIME = 3000


prevent delete role xss

prevent delete role xss

--- app/coffee/modules/common/confirm.coffee
+++ app/coffee/modules/common/confirm.coffee
@@ -95,22 +95,23 @@
         el = angular.element(lightboxSelector)

         # Render content
-        el.find(".title").html(title)
-        el.find(".subtitle").html(subtitle)
+        el.find(".title").text(title)
+        el.find(".subtitle").text(subtitle)

         if replacement
-            el.find(".replacement").html(replacement)
+            el.find(".replacement").text(replacement)
         else
             el.find(".replacement").remove()

         if warning
-            el.find(".warning").html(warning)
+            el.find(".warning").text(warning)
         else
             el.find(".warning").remove()

         choicesField = el.find(".choices")
         choicesField.html('')
         _.each choices, (value, key) ->
+            value = _.escape(value)
             choicesField.append(angular.element("<option value='#{key}'>#{value}</option>"))

         # Assign event handlers


fix #1483

fix #1483

--- app/coffee/modules/common/wisiwyg.coffee
+++ app/coffee/modules/common/wisiwyg.coffee
@@ -96,18 +96,24 @@
             onEnter:
                 keepDefault: false
                 replaceWith: (data) =>
-                    lines = data.textarea.value[0..(data.caretPosition - 1)].split("\n")
-                    lastLine = lines[lines.length - 1]
+                    lines = data.textarea.value.split("\n")
+                    cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
+                    newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
+                    lastLine = lines[cursorLine - 1]

                     # unordered list -
                     match = lastLine.match /^(\s*- ).*/
+
                     if match
                         emptyListItem = lastLine.match /^(\s*)\-\s$/

                         if emptyListItem
                             markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
                         else
-                            return "\n#{match[1]}" if match
+                            breakLineAtBeginning = newLineContent.match /^(\s*)\-\s/
+
+                            if !breakLineAtBeginning
+                                return "\n#{match[1]}" if match

                     # unordered list *
                     match = lastLine.match /^(\s*\* ).*/
@@ -118,7 +124,10 @@
                         if emptyListItem
                             markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
                         else
-                            return "\n#{match[1]}" if match
+                            breakLineAtBeginning = newLineContent.match /^(\s*)\*\s/
+
+                            if !breakLineAtBeginning
+                                return "\n#{match[1]}" if match

                     # ordered list
                     match = lastLine.match /^(\s*)(\d+)\.\s/
@@ -129,7 +138,10 @@
                         if emptyListItem
                             markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
                         else
-                            return "\n#{match[1] + (parseInt(match[2], 10) + 1)}. "
+                            breakLineAtBeginning = newLineContent.match /^(\s*)(\d+)\.\s/
+
+                            if !breakLineAtBeginning
+                                return "\n#{match[1] + (parseInt(match[2], 10) + 1)}. "

                     return "\n"


Refined the code that automatically opens/closes folders in the sidebar when moving feeds around.

It's still a little messy though. The Bootstrap "collapsible" plugin was clashing with angularjs code. Also the accordion-like behavior got broken after programatically collapsing folders; according to the documentation this should be avoided by passing an options object with the correct "parent" key, but I haven't been able to make it work.

Anyway, it seems that manually toggling the "in" and "collapse" classes gets the job done. There is no open/close animation (change is instantaneous) but it does what is intended and the accordion-like behavior is not broken. Also I've had to introduce a small delay to give time for angularjs to "stabilize" after model changes.

I know, it's messy. I might still remove all this behavior if it gives me more trouble. It's even a bit uglier than I'd like for the end user, but hopefully moving feeds around, unsubscribing etc are actions that are not performed often, so the user won't have to see this hack moving but rarely.

Don't judge me.

--- app/assets/javascripts/ng-services/ng-OpenFolderSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-OpenFolderSvc.js.coffee
@@ -3,7 +3,7 @@
 ########################################################

 angular.module('feedbunch').service 'openFolderSvc',
-['$rootScope', ($rootScope)->
+['$rootScope', '$timeout', ($rootScope, $timeout)->

   #---------------------------------------------
   # Set the currently open folder in the root scope.
@@ -26,11 +26,16 @@
   #---------------------------------------------
   open: (folder)->
     $rootScope.current_open_folder = folder
-    $rootScope.$apply()
-    $("#feeds-#{folder.id}").collapse 'show'
-    # close all other folders
-    for f in $rootScope.folders
-      $("#feeds-#{f.id}").collapse 'hide' if f != folder
+    $rootScope.$apply() if $rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest'
+    $timeout ->
+      # open the passed folder
+      $("#feeds-#{folder.id}").addClass("in").removeClass("collapse")
+    , 250
+    $timeout ->
+      # close all other folders
+      for f in $rootScope.folders
+        $("#feeds-#{f.id}").removeClass("in").addClass("collapse") if f != folder
+    , 350

   #---------------------------------------------
   # Unset the currently open folder in the root scope, and also
@@ -41,10 +46,12 @@
   #---------------------------------------------
   close_all: ->
     $rootScope.current_open_folder = null
-    $rootScope.$apply()
-    # close all folders
-    for f in $rootScope.folders
-      $("#feeds-#{f.id}").collapse 'hide'
+    $rootScope.$apply() if $rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest'
+    $timeout ->
+      # close all folders
+      for f in $rootScope.folders
+        $("#feeds-#{f.id}").removeClass("in").addClass("collapse")
+    , 250

   #---------------------------------------------
   # Return the folder object which is currently open


When opening a folder, the check to see if any action is necessary is based in comparing folder IDs, not folder objects.

The reason is that folders may be reloaded from the server (e.g. when a feed is moved to a new folder) which creates a new collection of folder objects in memory. In this circumstances we must compare IDs to see if two objects represent the same folder; comparing the objects themselves could give a false negative if one folder object was loaded on page load, and the other one was loaded from the server at a later time.

--- app/assets/javascripts/ng-services/ng-OpenFolderSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-OpenFolderSvc.js.coffee
@@ -9,7 +9,7 @@
   # Set the currently open folder in the root scope.
   #---------------------------------------------
   set: (folder)->
-    if folder != $rootScope.current_open_folder
+    if folder.id != $rootScope.current_open_folder?.id
       animationsSvc.open_folder folder
       # When opening a folder, close any other open folder
       if $rootScope.current_open_folder?


more voxcharta scrape!

more voxcharta scrape!

--- scripts/voxcharta.coffee
+++ scripts/voxcharta.coffee
@@ -4,14 +4,14 @@
 cheerio = require('cheerio')

 module.exports = (robot) ->
-    robot.hear /voxcharta/i, (msg) ->
+    robot.listen /voxcharta/i, (msg) ->
         msg.http('http://moca.voxcharta.org').get() (error, response, body) ->
             # Load page
             page = cheerio.load(body)
             # Get papers on the agenda
             papers = page('.votemicrotext')
             text = []
-            text.push '*Papers on the Journal Club agenda this week:*'
+            text.push '*Papers on the discussion agenda this week:*'
             text.push ''
             # Iterate over papers
             papers.each (elem) ->
@@ -19,7 +19,9 @@
                 title = page(this).children().first().text()
                 link = page(this).children().first().attr('href')
                 text.push '-- ' + title
-                text.push '   ' + link
+                text.push link
                 text.push ''
-            text.push 'Vote for a paper here: http://moca.voxcharta.org'
+            details = page('.disc-details p')
+            details.each (elem) ->
+                text.push page(this).text()
             msg.send text.join('\n')


Removed gomi

Removed gomi

--- source/javascripts/scena-spec.js.coffee
+++ source/javascripts/scena-spec.js.coffee
@@ -1 +0,0 @@
-#= require_tree ./spec


insertLine now inserts a line after the current line, this is to make it possible to put the new line on the correct indentation

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -19,17 +19,18 @@
             Sets internal variables, the default text and buttons
             and their event handlers.
         ###
+        @editor = ace.edit @editorDivId
+        @editSession = @editor.getSession()
+        @editSession.setMode "ace/mode/java"
+        @editSession.setUseSoftTabs true
+
+        # mode = @editSession.getMode()
         codePrefix = "class StudentCode {\n    public static void main(String[] args) {\n"
         codeSuffix = "\n    }\n}"
         unless @codeText.startsWith codePrefix
             @codeText = codePrefix + @codeText
         unless @codeText.endsWith codeSuffix
             @codeText = @codeText + codeSuffix
-
-        @editor = ace.edit @editorDivId
-        @editSession = @editor.getSession()
-        @editSession.setMode "ace/mode/java"
-        @editSession.setUseSoftTabs true

         @buildNeededParsers()
         @resetState()
@@ -308,10 +309,12 @@

     insertLine: ({text, line, currentRow}) ->
         inputsDiv = jQuery('#insertButtons')
-        text.insertLines currentRow, [line]
-
-        if text.getLength() == 2 and text.getLine(currentRow + 1) == ""
+        text.insertLines currentRow + 1, [line]
+
+        if text.getLength() == 2 and text.getLine(currentRow) == ""
             text.removeNewLine currentRow
+
+        @editor.gotoLine currentRow + 2, 0, false

         return


explicitel make codeText a string object

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -31,6 +31,7 @@
         codeSuffix = '    }\n}'
         @codePrefixLength = codePrefix.split('\n').length - 1
         @codeSuffixLength = codeSuffix.split('\n').length
+        codeText = new String codeText
         unless codeText.startsWith codePrefix
             tab = @editSession.getTabString()
             tab += tab


:bug:Fix admin trial requests page

:bug:Fix admin trial requests page

Server refactoring exposed incorrect server parameters.

--- app/views/admin/TrialRequestsView.coffee
+++ app/views/admin/TrialRequestsView.coffee
@@ -30,7 +30,7 @@
           -1
         else
           1
-      @trialRequests = new CocoCollection([], { url: '/db/trial.request?conditions[sort]=-created&conditions[limit]=1000', model: TrialRequest, comparator: sortRequests })
+      @trialRequests = new CocoCollection([], { url: '/db/trial.request?conditions[sort]="-created"&conditions[limit]=1000', model: TrialRequest, comparator: sortRequests })
       @supermodel.loadCollection(@trialRequests, 'trial-requests', {cache: false})

   getRenderData: ->


call node-pool callback with (err, null)

--- src/adapters/tedious/index.coffee
+++ src/adapters/tedious/index.coffee
@@ -29,7 +29,8 @@
         conn = new Connection(@config)
         conn.on('connect', (err) =>
             if err?
-                callback(err)
+                # node-pool breaks if you send only err, must send null
+                callback(err, null)
             else
                 # MUST: find a real solution instead of this godawful workaround
                 set = 'SET ANSI_PADDING ON SET ANSI_WARNINGS ON ' +


update require paths

update require paths

--- lib/tabs-view.coffee
+++ lib/tabs-view.coffee
@@ -1,9 +1,9 @@
 $ = require 'jquery'
 SortableList = require 'sortable-list'
-Tab = require 'tabs/src/tab'
+Tab = require 'tabs/lib/tab'

 module.exports =
-class Tabs extends View
+class Tabs extends SortableList
   @activate: ->
     rootView.eachEditor (editor) =>
       @prependToEditorPane(rootView, editor) if editor.attached


Check if entries still exist before pasting them

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -541,7 +541,7 @@

     for initialPath in initialPaths ? []
       initialPathIsDirectory = fs.isDirectorySync(initialPath)
-      if entry and initialPath
+      if entry and initialPath and fs.existsSync(initialPath)
         basePath = atom.project.getDirectories()[0].resolve(entry.getPath())
         basePath = path.dirname(basePath) if entry instanceof FileView
         newPath = path.join(basePath, path.basename(initialPath))


coordinator: Clear @processes on clearGraph

--- src/coordinator.coffee
+++ src/coordinator.coffee
@@ -122,6 +122,7 @@
     @graphName = graphName
     # XXX: should we clear the @participants with the roles that we started?
     setup.killProcesses @processes, 'SIGTERM', (err) =>
+      @processes = {}
       return callback err

   start: (callback) ->


sidebar: minor

sidebar: minor

--- client/app/lib/activity/sidebar/channelsidebarmorelink.coffee
+++ client/app/lib/activity/sidebar/channelsidebarmorelink.coffee
@@ -8,7 +8,7 @@

     return  if err

-    { totalCount }      = res
+    { totalCount } = res

     if totalCount > 8
     then @show()


Fix nameIsInArray utility. Array elements were escaped, even if the regexp had already been formed. To put a backslash in front of the dot a double backslash was needed.

--- lib/utilities.coffee
+++ lib/utilities.coffee
@@ -26,6 +26,10 @@
     isFile(name+extensions[i],looper)
   looper(false) #initiate the process

+createArrayRegexp= (arr)->
+  a= (RegExp.escape(n) for n in arr)
+  arr.regexp ?= new RegExp('((\\.' + a.join(')|(\\.') + '))$')
+
 module.exports=
   # is the given name that of a regular file?
   isFile: isFile
@@ -48,7 +52,6 @@
   #
   nameIsInArray: (name,arr)->
     return if arr
-      a= (RegExp.escape(n) for n in arr)
-      (arr.regexp ?= new RegExp('((\.' + a.join(')|(\.') + '))$')).test(name)
+      (arr.regexp ?= createArrayRegexp(arr)).test(name)
     else
       false


usage comments

usage comments

--- server/Translator.coffee
+++ server/Translator.coffee
@@ -2,6 +2,8 @@

   constructor: ( server ) ->
+    #Default values were not being set when these fields were declared as
+    #  properties for some reason.  This is probably not the most elegant fix.
     @_id = ''
     @author_id = ''
     @parent_id = ''


change to console.warn for errors

change to console.warn for errors

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -215,7 +215,7 @@
 app.get '/api/:user/datasets/?', checkUserRights, (req, resp) ->
   Dataset.findAllByUserShortName req.user.effective.shortName, (err, datasets) ->
     if err?
-      console.log err
+      console.warn err
       return resp.send 500, error: 'Error trying to find datasets'
     else
       return resp.send 200, datasets
@@ -225,10 +225,10 @@
   console.log "GET /api/#{req.params.user}/datasets/#{req.params.id}"
   Dataset.findOneById req.params.id, req.user.effective.shortName, (err, dataset) ->
     if err?
-      console.log err
+      console.warn err
       return resp.send 500, error: 'Error trying to find datasets'
     else if not dataset
-      console.log "Could not find a dataset with {box: '#{req.params.id}', user: '#{req.user.effective.shortName}'}"
+      console.warn "Could not find a dataset with {box: '#{req.params.id}', user: '#{req.user.effective.shortName}'}"
       return resp.send 404
     else
       return resp.send 200, dataset
@@ -237,7 +237,7 @@
   console.log "PUT /api/#{req.params.user}/datasets/#{req.params.id}"
   Dataset.findOneById req.params.id, req.user.effective.shortName, (err, dataset) ->
     if err?
-      console.log err
+      console.warn err
       return resp.send 500, error: 'Error trying to find datasets'
     else if not dataset
       console.log "Could not find a dataset with {box: '#{req.params.id}', user: '#{req.user.effective.shortName}'}"
@@ -254,9 +254,9 @@
   data = req.body
   dataset = new Dataset req.user.effective.shortName, data.name, data.displayName, data.box
   dataset.save (err) ->
-    console.log err if err?
+    console.warn err if err?
     Dataset.findOneById dataset.box, req.user.effective.shortName, (err, dataset) ->
-      console.log err if err?
+      console.warn err if err?
       resp.send 200, dataset

 app.post '/api/:user/?', checkStaff, (req, resp) ->


use req.connection.remoteAddress instead of req.ip because req.ip doesnt seem to work

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -368,7 +368,7 @@
     phone: req.body.phone
     email: req.body.email
     description: req.body.description
-    ip: req.ip
+    ip: req.connection.remoteAddress
   dataRequest.send (err) ->
     if err?
       console.warn "Error trying submit data request", err


Renamed region variables and render subviews of SubCommentsView immediately.

--- core/app/backbone/views/sub_comments/sub_comments_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_view.coffee
@@ -98,8 +98,8 @@
     """

   regions:
-    subCommentsList: '.js-region-sub-comments-list'
-    subCommentsForm: '.js-region-sub-comments-form'
+    subCommentsListRegion: '.js-region-sub-comments-list'
+    subCommentsFormRegion: '.js-region-sub-comments-form'

   events:
     'click .js-sub-comments-link': 'toggleList'
@@ -108,28 +108,23 @@
     @updateLink()
     # Bind to model change event here when returning a comment count

-    @subCommentsForm.show new SubCommentsAddView addToCollection: @subComments()
-
-  toggleList: ->
-    if @listOpen then @closeList()
-    else @openList()
+  toggleList: -> if @listOpen then @closeList() else @openList()

   openList: ->
     @listOpen = true
     @$('.js-sub-comments-list-container').removeClass('hide')
-    @subCommentsList.close()

-    @subComments().fetch
-      success: =>
-        @subCommentsList.show new SubCommentsListView collection: @subComments()
+    subComments = new SubComments([], parentModel: @model)
+    subComments.fetch()
+
+    @subCommentsFormRegion.show new SubCommentsAddView addToCollection: subComments
+    @subCommentsListRegion.show new SubCommentsListView collection: subComments

   closeList: ->
     @listOpen = false
     @$('.js-sub-comments-list-container').addClass('hide')
-    @subCommentsList.close()
-
-  subComments: ->
-    @_subComments ?= new SubComments([], parentModel: @model)
+    @subCommentsFormRegion.close()
+    @subCommentsListRegion.close()

   updateLink: ->
     @$(".js-sub-comments-link").text "Comments" # Add comment count here


Remove an errant console.log

Remove an errant console.log

--- assets/javascripts/mailcatcher.js.coffee
+++ assets/javascripts/mailcatcher.js.coffee
@@ -298,7 +298,6 @@
     url = new URL("messages", document.baseURI)
     url.protocol = if secure then "wss" else "ws"
     @websocket = new WebSocket(url.toString())
-    console.log(@websocket)
     @websocket.onmessage = (event) =>
       @addMessage $.parseJSON event.data


failing tests for 2D props nested in expressions


Fix spec: line ending is '\r\n' on Windows.

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -234,7 +234,8 @@
         filePath = path.join(repo.getWorkingDirectory(), 'a.txt')
         fs.writeFileSync(filePath, 'changing a.txt', 'utf8')
         expect(repo.checkoutHead('a.txt')).toBe true
-        expect(fs.readFileSync(filePath, 'utf8')).toBe 'first line\n'
+        lineEnding = if process.platform is 'win32' then '\r\n' else '\n'
+        expect(fs.readFileSync(filePath, 'utf8')).toBe "first line#{lineEnding}"

     describe 'when the path is undefined', ->
       it 'returns false', ->


Add downvotes fetch

Add downvotes fetch

--- server/routes/get.coffee
+++ server/routes/get.coffee
@@ -1,12 +1,19 @@
 # GET Routes
 User = require '../models/user'
 Product = require '../models/product'
+Downvotes = require '../models/downvote'

 getRoutes =
   index: (req, res) ->
     # Get the products that are supposed to display today
     now = new Date()
     startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())
+
+    # downvotedProducts = []
+    # if req.user
+    #   Downvotes.find user: req.user._id, (err, downvotes) ->
+    #     for downvote in downvotes
+    #       downvotedProducts.push downvote.product

     # Do the product query
     Product.find(displayDay: $gte: startOfDay)


Fix temporario dos avatares nas guias.

Fix temporario dos avatares nas guias.

--- src/models/user.coffee
+++ src/models/user.coffee
@@ -97,8 +97,8 @@
 	toJSON: 	{ virtuals: true }
 }

-UserSchema.statics.APISelect = 'id name username profile path -profile.serie -profile.birthday avatarUrl'
-UserSchema.statics.APISelectSelf = 'name username profile path -profile.serie -profile.birthday avatarUrl profile meta preferences.interests'
+UserSchema.statics.APISelect = 'id name username profile path -profile.serie -profile.birthday avatar_url avatarUrl'
+UserSchema.statics.APISelectSelf = 'name username profile path -profile.serie -profile.birthday avatar_url avatarUrl profile meta preferences.interests'

 ##########################################################################################
 ## Virtuals ##############################################################################


Updated the Surface Pro 3 device image size

The actual size of the device image is different, but these dimensions result in the actual screen dimensions fitting the device image.

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -1317,8 +1317,8 @@
 	minStudioVersion: iPhoneXReleaseVersion

 MicrosoftSurfacePro3 =
-	deviceImageWidth: 2196
-	deviceImageHeight: 1514
+	deviceImageWidth: 2472
+	deviceImageHeight: 1704
 	deviceImageCompression: true
 	screenWidth: 2160
 	screenHeight: 1440


explain new shortcut

explain new shortcut

--- src/art/engine/core/element_base.coffee
+++ src/art/engine/core/element_base.coffee
@@ -165,7 +165,13 @@

        @currentLocation isn't actually stored as a point. It is derived from @elementToParentMatrix and @axis.

-    virtual property options:
+    As a short-cut, you can supply just a getterNew function instead of options to define a
+    virtual property. See getterNew below for details. Here is how to use this shortcut:
+
+      @virtualProperty
+        foo: (pending) -> ...
+
+    Virtual property options (when isPlainObject options)

       getterNew: (pending) ->
         REQUIRED


Hotkeys typo fixed

Hotkeys typo fixed

--- lib/foundry-xray/app/assets/javascripts/application.js.coffee
+++ lib/foundry-xray/app/assets/javascripts/application.js.coffee
@@ -127,8 +127,8 @@
     @slider.on 'mouseenter', '.label', -> $(this).find('span').show()
     @slider.on 'mouseout', '.label', -> $(this).find('span').hide()

-    next = => @draw @currentStep-1
-    prev = => @draw @currentStep+1
+    next = => @draw @currentStep+1
+    prev = => @draw @currentStep-1

     fnext = =>
       step = @input.transforms.find (x) => x.id > @currentStep
@@ -138,8 +138,8 @@
       step = @input.transforms.findAll((x) => x.id < @currentStep).last()
       @draw step.id if step

-    @sliderPrev.click next
-    @sliderNext.click prev
+    @sliderPrev.click prev
+    @sliderNext.click next
     @sliderFNext.click fnext
     @sliderFPrev.click fprev


Add call to super in QueryModel parse() method

--- src/coffee/cilantro/models/query.coffee
+++ src/coffee/cilantro/models/query.coffee
@@ -5,6 +5,8 @@

     class QueryModel extends base.Model
         parse: (attrs) ->
+            super
+
             if attrs? and not attrs.shared_users?
                 attrs.shared_users = []


HACK - defensive coding to prevent a bug I was having that I didn't have time to teas out. It's only about in reporting detailed compile-error info

--- source/BabelBridge/Parser.coffee
+++ source/BabelBridge/Parser.coffee
@@ -362,7 +362,7 @@

       expecting = {}
       for pmp in partialMatchingParents
-        for child in pmp.matches when child.isNonMatch
+        for child in pmp.matches when child.isNonMatch && child.nonMatchingLeaf
           couldMatchRuleNames.push ruleName if ruleName = child.nonMatchingLeaf.ruleNameOrNull
           expecting[child.nonMatchingLeaf.ruleNameOrPattern] =
             "to-continue": pmp.ruleName


moved logic for determining whether it is a YomTob that we can pray at plag into a separate function

--- source/javascripts/mincha.coffee
+++ source/javascripts/mincha.coffee
@@ -30,6 +30,7 @@
     @sunset = moment(@zmanim.sunset).subtract(30, 'second')
   today: -> moment(@sunset)
   tonight_is_yom_tob: -> @hebrew_date.isErebYomTob() || @hebrew_date.is1stDayOfYomTob()
+  yom_tob_that_we_can_pray_at_plag: -> @hebrew_date.is7thDayOfPesach() || @hebrew_date.is1stDayOfShabuot()
   last_time_for_shema: -> @_last_time_for_shema ?= moment.min(
       moment(@zmanim.magen_abraham_dawn).add((@zmanim.magen_abraham_dusk - @zmanim.magen_abraham_dawn) / 4000, 'seconds'),
       moment(@zmanim.sunrise).subtract(36, 'minutes').add((@zmanim.sunset - @zmanim.sunrise) / 4000, 'seconds'),
@@ -168,7 +169,7 @@
     when @tonight_is_yom_tob() && @hebrew_date.isShabbat() then minutes_before_event(@sunset, 23)
     when @hebrew_date.is6thDayOfPesach() then @today().hour(18).minute(30)
     when @hebrew_date.isShabbat() then @mincha_on_shabbat()
-    when @hebrew_date.is7thDayOfPesach() || @hebrew_date.is1stDayOfShabuot() then minutes_before_event(@plag(), 30)
+    when @yom_tob_that_we_can_pray_at_plag() then minutes_before_event(@plag(), 30)
     when @hebrew_date.isYomTob() then minutes_before_event(@sunset, if @hebrew_date.isErebShabbat() then 40 else 25)
     when @hebrew_date.isErebYomTob() then minutes_before_event(@sunset, if @hebrew_date.isErebShabbat() then 33 else  25)
     when @hebrew_date.isErebShabbat() then moment.min(moment(@sunset).subtract(33, 'minutes'), @today().hour(18).minute(30))
@@ -177,14 +178,14 @@
     when @hebrew_date.isErebYomKippur() then minutes_before_event(@sunset, 55)
     when @hebrew_date.isErebShabbat() || (@tonight_is_yom_tob() && !@hebrew_date.isShabbat()) then null
     when @hebrew_date.isYomKippur() || (@hebrew_date.isShabbat() && !@tonight_is_yom_tob()) then @arbit_on_mosaei_shabbat()
-    when @hebrew_date.is7thDayOfPesach() || @hebrew_date.is1stDayOfShabuot() then @plag()
+    when @yom_tob_that_we_can_pray_at_plag() then @plag()
     when @hebrew_date.is2ndDayOfYomTob() && !@hebrew_date.isErebShabbat() then minutes_before_event(@zmanim.set_hakochabim, 10)
     else @sunset
   hadlakat_nerot: -> if @has_hadlakat_nerot_after_set_hakochabim() then moment(@zmanim.set_hakochabim) else moment(@sunset).subtract(19, 'minutes')
   has_hadlakat_nerot_after_set_hakochabim: -> (@hebrew_date.isErebYomTob() && @hebrew_date.isShabbat()) || (@hebrew_date.is1stDayOfYomTob() && !@hebrew_date.isErebShabbat())
   has_hadlakat_nerot_before_sunset: -> @hebrew_date.isErebShabbat() || @hebrew_date.isErebYomKippur() || @hebrew_date.isErebYomTob()
   hadlakat_nerot_text: ->
-    if (@hebrew_date.is7thDayOfPesach() || @hebrew_date.is1stDayOfShabuot()) && !@hebrew_date.isErebShabbat() && !@hebrew_date.isShabbat()
+    if @yom_tob_that_we_can_pray_at_plag() && !@hebrew_date.isErebShabbat() && !@hebrew_date.isShabbat()
       "Before Kiddush
Eat from all cooked
foods before #{time_format(@sunset)}"
     else if @has_hadlakat_nerot_after_set_hakochabim()
       "After #{if @hebrew_date.isShabbat() then 'שַׁבָּת ends' else time_format(@hadlakat_nerot())}"


replace itemView by childView for Marionette 2.x

--- client/marionette/controller/file.coffee
+++ client/marionette/controller/file.coffee
@@ -635,7 +635,7 @@

 	onRender: ->
 		@create.show new AuthCreateView collection: @collection
-		@list.show new Marionette.CollectionView itemView: AuthView, collection: @collection
+		@list.show new Marionette.CollectionView childView: AuthView, collection: @collection

 module.exports =
 	NavBar:				NavBar


Revert "Experimental Java Object changes...."

This reverts commit c9eadcb5906267310bfaff143142bac26dab8c71.

--- src/java_object.coffee
+++ src/java_object.coffee
@@ -29,9 +29,22 @@
 class root.JavaObject
   constructor: (@type, rs, obj={}) ->
     @ref = rs.high_oref++
+    @fields = {}
     # init fields from this and inherited ClassFiles
-    cls = rs.class_lookup @type, true
-    @fields = cls.construct_fields(rs, @type)
+    t = @type
+    while t?
+      cls = rs.class_lookup t
+      for f in cls.fields when not f.access_flags.static
+        val = util.initial_value f.raw_descriptor
+        slot_val = @fields[f.name]
+        if typeof slot_val isnt 'undefined'
+          # Field shadowing.
+          unless slot_val?.$first?
+            @fields[f.name] = slot_val = {$first: slot_val}
+          slot_val[t.toClassString()] = val
+        else
+          @fields[f.name] = val
+      t = cls.super_class
     # init fields from manually given object
     for k,v of obj
       slot_val = @fields[k]
@@ -92,26 +105,6 @@
     @fields = {}
     @init_fields unless defer_init

-  construct_fields: (rs, t) ->
-    if @fields_proto? then return Object.seal(_.clone(@fields_proto))
-    @fields_proto = {}
-    # init fields from this and inherited ClassFiles
-    while t?
-      cls = rs.class_lookup t
-      for f in cls.fields when not f.access_flags.static
-        val = util.initial_value f.raw_descriptor
-        slot_val = @fields_proto[f.name]
-        if typeof slot_val isnt 'undefined'
-          # Field shadowing.
-          unless slot_val?.$first?
-            @fields_proto[f.name] = slot_val = {$first: slot_val}
-          slot_val[t.toClassString()] = val
-        else
-          @fields_proto[f.name] = val
-      t = cls.super_class
-    Object.seal(@fields_proto)
-    return @construct_fields(rs, t)
-
   init_fields: (rs) ->
     cls = rs.class_lookup @type
     for f in cls.fields when not f.access_flags.static


changing the action method to allow a complet callback this will allow devs to use the pipeline and not have the app quit

--- lib/docpad.coffee
+++ lib/docpad.coffee
@@ -361,7 +361,7 @@

 	# Handle
-	action: (action) ->
+	action: (action, complete = -> process.exit()) ->
 		# Clear
 		if @actionTimeout
 			clearTimeout(@actionTimeout)
@@ -372,7 +372,7 @@
 			logger.log 'notice', 'Action received, but we still loading... waiting...'
 			@actionTimeout = setTimeout(
 				=>
-					@action action
+					@action action, complete
 				500
 			)
 			return
@@ -382,12 +382,12 @@
 			when 'skeleton', 'scaffold'
 				@skeletonAction (err) ->
 					return @error(err)  if err
-					process.exit()
+					complete()

 			when 'generate'
 				@generateAction (err) ->
 					return @error(err)  if err
-					process.exit()
+					complete()

 			when 'watch'
 				@watchAction (err) ->


Regex match up-front, hook up xhr funcs.

--- mixirc/mixirc.coffee
+++ mixirc/mixirc.coffee
@@ -24,16 +24,22 @@
 	return if from is ircBot.nick  # abort if talking to self
 	info = userList[from]
 	return unless info?  # abort if this is a nobody
+	heartMatch = /^\.([0-9]+)$/.exec message
+	mixMsgMatch = /^\[\[ (.+)$/.exec message
+	mixHrtMatch = /^\]\] ([0-9]+)$/.exec message
 	switch
 		when message.toLowerCase() is "sup " + ircBot.nick
 			ircBot.say to, "OH YOU KNOW JUST ENSLAVING THE HUMAN RACE"
 		# NOTE: The Regex below only returns true if the message is ".#"
 		# (# = number of any size)
-		when (/^(\.[0-9]+)$/.test message) and info.canHeart
-			commentId = message.replace ".", ""
-			console.log "IRC => postAddCommentHeart: ", from, commentId,
+		when info.canHeart and heartMatch?
+			console.log "IRC => postAddCommentHeart: ", from, heartMatch[1],
 				message, channelId, info.mixlrUserLogin, info.mixlrAuthSession
-			postAddCommentHeart commentId, info, userAgent
+			postAddCommentHeart heartMatch[1], info, userAgent
+		when info.canHeart and mixHrtMatch?
+			xhrComm mixHrtMatch[1], info
+		when mixMsgMatch?
+			xhrComm mixMsgMatch[1], channelId, info
 		else
 			console.log "IRC => postComm: ", from, message, channelId,
 				info.mixlrUserLogin, info.mixlrAuthSession


fix (sorry master)

fix (sorry master)

--- vue/tests/e2e/helpers/pageHelper.coffee
+++ vue/tests/e2e/helpers/pageHelper.coffee
@@ -71,6 +71,7 @@

   clearField: (selector) ->
     test.getValue selector, (result) ->
+      return unless (result || {}).value
       length = result.value.length
       count = 0
       while count < length


changed comment headings in install.coffee

--- src/lib/install.coffee
+++ src/lib/install.coffee
@@ -4,7 +4,8 @@
 # For all details and documentation:
 # <http://neocotic.com/template>

-#### Private variables
+# Private variables
+# -----------------

 # Names of the classes to be removed from the targeted elements.
 classes     = [
@@ -14,9 +15,10 @@
 # Extension ID of Template.
 extensionId = 'dcjnfaoifoefmnbhhlbppaebgnccfddf'
 # "Install" links to be modified.
-links       = document.querySelectorAll("a.#{classes[0]}[href$=#{extensionId}]")
+links       = document.querySelectorAll "a.#{classes[0]}[href$=#{extensionId}]"

-#### Functionality
+# Functionality
+# -------------

 # Disable all "Install" links on the homepage for Template.
 for link in links


Docco run through.

Docco run through.

--- src/lib/strata_.coffee
+++ src/lib/strata_.coffee
@@ -1,34 +1,32 @@
-# A Streamline.js friendly evented I/O b-tree for node.js.
-#
-# TK Our I/O page storage an in memory page structures are inherently sparse.
+# A Streamline.js friendly evented I/O b-tree for Node.js.
 #
 # ## Purpose
 #
 # Strata stores JSON objects on disk, according to a sort order of your
-# choosing, indexed for fast retrieval. Faster than a flat file. Lighter than a
-# database. More capacity than an in memory tree.
+# choosing, indexed for fast retrieval.
+#
+# Strata is Faster than a flat file, lighter than a database, with more capacity
+# than an in memory tree.
 #
 # Strata is a [b-tree](http://en.wikipedia.org/wiki/B-tree) implementation for
-# [Node.js](http://nodejs.org/) that **evented**, **concurrent**, **perstant**
+# [Node.js](http://nodejs.org/) that **evented**, **concurrent**, **persistant**
 # and **durable**.
 #
-# Strata is **evented** because it uses asynchronous I/O to read and write
-# b-tree pages, allowing your CPU to continue to do work while Strata waits on
-# I/O.
+# Strata is **evented**. It uses asynchronous I/O to read and write b-tree
+# pages, allowing your CPU to continue to do work while Strata waits on I/O.
 #
 # Strata is **concurrent**. Strata will satisfy any queries from its in memory
-# cache that it can, even while waiting on evented I/O to load. It queues the
-# I/O, but keeps the main thread of execution busy while the I/O completes.
-# Strata keeps your CPU and I/O busy when requests are heavy.
+# cache when it can, so requests can be satisifed, even when there are evented
+# I/O requests outstanding.
 #
 # Strata is **persistant**. It stores your tree in page files. The page files
-# are plain old JSON. You can open them up in a `vim` or EMACS, back them up
-# using `textutils`, check them into `git`, and munge them with JavaScript. It's
-# your data, you ought to be able to read it.
+# are plain old JSON, text files that are easy to manage. You can open them up
+# in a `vim` or EMACS, back them up using `tar`, check them into `git`, and
+# munge them with JavaScript.
 #
 # Strata is **durable**. It only appends records to to file, so a hard shutdown
-# will only ever lose the few last records added. When pages jornaled when they
-# are vaccumed and rewritten.
+# will only ever lose the few last records added. Pages are journaled when they
+# are vacuumed or rewritten.
 #
 # Strata is a b-tree. A b-tree is a database primiative. Using Strata, you can
 # start to experiment with database designs of your own. You can use Strata to
@@ -36,10 +34,10 @@
 # to create relationship indexes. You can use Strata to store what ever form of
 # JSON suits your needs like NoSQL databases.
 #
-# It runs anywhere that Node.js runs, in pure JavaScript.
-#
-# Maybe you need a database server, or maybe Strata is all you need to get your
-# next application growing.
+# Strata is two database primatives in one, because with a time series index,
+# you can use Strata as a write ahead-log.
+#
+# Strata runs anywhere that Node.js runs, in pure JavaScript.
 #
 # ## Terminology
 #
@@ -50,10 +48,15 @@
 # out of memory when they have not been recently referenced. These are behaviors
 # that conjure an image of a page of values.
 #
-# Otherwise, we use common teriminology for *order*, *depth*, *parent*, *child*,
-# *split* and *merge*. There is no hard and fast definition for all the terms. A
-# leaf is a fuzzy concept in b-tree literature, for example. We call a page that
-# contains records a *leaf page*. We call a non-leaf page a *branch page*.
+# Otherwise, we use common teriminology for *height*, *depth*, *parent*,
+# *child*, *split* and *merge*.
+#
+# There is no hard and fast definition for all the terms. A leaf is a fuzzy
+# concept in b-tree literature, for example. We call a page that contains
+# records a *leaf page*. We call a non-leaf page a *branch page*. The term
+# *order* means different things to different people.  We define the order of a
+# branch page to be the maximum number of child pages, while the order of a leaf
+# page to be the maximum number of records.
 #
 # The term *b-tree* itself may not be correct. There are different names for
 # b-tree that reflect the variations of implementation, but those distinctions
@@ -67,37 +70,66 @@
 #
 # This documentation assumes that you understand the theory behind the b-tree,
 # and know the variations of implementation. If you are interested in learning
-# about b-trees, this document could be a learning aid for you, but you should
-# start with the Wikipedia articles on
+# about b-trees you should start with the Wikipedia articles on
 # [B-trees](http://en.wikipedia.org/wiki/B-tree) and
-# [B+trees](http://en.wikipedia.org/wiki/B%2B_tree).
+# [B+trees](http://en.wikipedia.org/wiki/B%2B_tree). I was introduced to b-trees
+# while reading [Algorithms in C](http://www.amazon.com/dp/0201314525), quite
+# some time ago.
 #
 # ## What flavor of b-tree is this?
 #
 # Strata is a b-tree with leaf pages that contain records ordered by the
-# collation order of the tree, indexed for retrieval in constant time, so that
-# they can be found using binary search. Branch pages contain links to other
-# pages, and do not store records themselves. Leaf pages are linked in ascending
-# order to ease the implementation of cursors.
-#
-# There is a maximum size for a page, that limits the number of links, in the
-# case of branch pages, or records in the case of leaf pages. When the maximum
-# size is reached which point a node is split. When two sibling pages next to
-# each other contain keys or records that combined are the less than than
-# maximum size they are merged.
-#
-# The tree always has a root branch page. The order of the tree increases when
-# the root branch is split. It decreases when the root branch is merged. The
-# split of the root branch is a different operation from the split of a non-root
-# branch, because the root branch does not have siblings.
-#
-# ## Implementation
+# collation order of the tree. Records are stored for retrieval in constant
+# time, addressed by an integer index, so that they can be found using binary
+# search.
+#
+# Branch pages contain links to other pages, and do not store records
+# themselves.
+#
+# Leaf pages are linked in ascending order for traversal by cursors. Brance
+# pages are singly linked in ascending order to simplify implementation of
+# branch pages merges.
+#
+# The page order is the maximum number of children for a branch page, or the
+# maximum number of records for a leaf page. When a page exceeds its capacity it
+# is split into two pages. When two sibling pages next to each other can be
+# combined to create a page less than than maximum size they are merged.
+#
+# The b-tree always has a root branch page. The order of the tree increases when
+# the root branch page is split. It decreases when the root branch page is
+# merged. The split of the root branch is a different operation from the split
+# of a non-root branch, because the root branch does not have siblings.
+#
+# ## Storage
+#
+# A database is a directory on the file system. Each page in the b-tree is
+# stored in a file in the file system.
+#
+# Our page implementations use JavaScript arrays of references to objects on
+# disk. The arrays sort the referneces according to the collation order of the
+# pages or records they reference. When we read records and keys off of disk, we
+# store them in an object that acts as a cache for the page.
+#
+# On disk and in memory, this arrangement is inheriently sparse. Pages are
+# stored in their own file, so they are no larger than they need to be on disk.
+#
+# JavaScript array implemenations are [clever about memory
+# usage](http://stackoverflow.com/questions/614126/why-is-array-push-sometimes-faster-than-arrayn-value/614255#614255)
+# and the per-page record cache is a hash table.
+#
+# ## Pages
+#
+# Every page has a page address. The page address is an integer. We reference
+# pages by this address. We write a page on disk into a file whose filename is
+# a prefix with the integer address appended. When we defererence a page we
+# either read it from a cache, or else we format the filename for the page and
+# read it from disk into the cache. Then we read it from cache.
 #
 # The b-tree has two types of pages. Leaf pages and branch pages.
 #
 # ### Leaf Pages
 #
-# Leaf pages contain records.
+# A leaf page contain records.
 #
 # In the abstract, a leaf page is an array data structure with zero based
 # integer index. The elements of the structure contain records. Given an
@@ -108,43 +140,30 @@
 # b-tree. Because of this, and because a lookup takes constant time, we can search
 # for a record in a leaf page using binary search in logorithmic time.
 #
-# The first record of every leaf page must be unique in relation to the first
-# record of every other leaf page. The b-tree will accept records that are
-# dupcliates according to the collation. When we split a page, we cannot cannot
-# choose a partition that has the same value as the first record, since that
-# would violate the unique first record constraint.
-#
-# If a page reaches the maximum leaf page size, filled with records that have
-# the same value according to the collation, the leaf page is cannot split,
-# since no suitable partition exists. The page will be allowed to grow beyond
-# the maximum page size.
+# Leaf pages cannot contain duplicate records. Therefore, the b-tree cannot
+# contain duplicate records. You can simulate duplicate records by adding a
+# series value to your key. The cursor implementation is designed faciliate
+# psuedo-duplicates in this fashion.
+#
+# The first record of every leaf page is the key value of the leaf page. When we
+# delete records from the leaf page, if we delete the first reord, we keep a
+# ghost of the record around, so we will know the key value of the leaf page.
+#
+# If a leaf page exceeds the leaf order, the leaf page is split.
 #
 # ### Branch Pages
 #
-# Branch pages contain links to other pages. To find the a record in the b-tree,
-# we first use branch pages to find the leaf page that contains our record.
-#
-# All pages are identified by a unique page address. The page address is an
-# integer assigned to the page when the page is created. Branch pages link to
-# other pages by storing the address of the referenced page in an array of page
-# addresses. This array of page addresses is essentially an *array of children*.
-#
-# A branch page orders its children according to the b-tree collation using a
-# record obtained from a leaf page as a *key*. A branch page always has one more
-# children than the number of keys. Keys are unique. A key used in a branch page
-# will not equal another key used in any branch page according to the collation.
-#
-# There are two special types of branch pages, the root page, and penultimate
-# pages.
-#
-# #### Root Branch Page
-#
-# The root page is the first page we consult to find the desired leaf page. Our
-# b-tree always contains a root page. The b-tree is never so empty that the root
-# page disappears. The root page always has the same address.
-#
-# TK move. Until the root branch page is split, it is both the root branch page
-# and a penultimate branch page.
+# To find the a record in the b-tree, we first use a tree of branch pages to
+# find the leaf page that contains our record.
+#
+# A branch page contains the addresses of child pages. This array of page
+# addresses is essentially an *array of children*.
+#
+# The child addresses are ordered according to the b-tree collation of the keys
+# of the directly or indirectly referenced leaf pages.
+#
+# There are three types of branch pages, penultimate branch pages, interior
+# branch pages, and the root branch page.
 #
 # #### Penultimate Branch Pages
 #
@@ -162,9 +181,8 @@
 # its key from the search. We search the subsequent keys to find the first key
 # that is grater than or equal to the record sought. Essentially, when we
 # encouter a key that is greater than our sought record, we know that the record
-# is contained in the leaf page child associated with the key before it.
-# Although it sounds linear, we are able to perform this search using binary
-# search in logorithmic time.
+# is contained in the leaf page child associated with the key before it. We are
+# able to perform this search using binary search in logorithmic time.
 #
 # By ignoring the key of the first leaf page, the penultimate branch page has a
 # number of children that is one greater than the number of keys.
@@ -222,6 +240,15 @@
 # When we visit the root interior page, to obtain the key to associate with the
 # right penultimate page, we visit the right penultimate page, then we visit its
 # left child, the leaf page whose first record is the key.
+#
+# #### Root Branch Page
+#
+# The root page is the first page we consult to find the desired leaf page. Our
+# b-tree always contains a root page. The b-tree is never so empty that the root
+# page disappears. The root page always has the same address.
+#
+# TK move. Until the root branch page is split, it is both the root branch page
+# and a penultimate branch page.
 #
 # ### Keys and First Records
 #
@@ -1187,38 +1214,51 @@

 # ## Descent
 #
-# A *decent* is a b-tree operation. It describes a traversal of the b-tree that
-# results in the creation of a cursor and cursor actions, or else balance
-# operation such as a merge or split. We use the term descent to describe both
-# top down and left right traversal of our b-tree.
-#
-# When we navigate to leaf pages of a search b-tree to obtain records, we
+# We use the term *descent* to describe b-tree operations, because all b-tree
+# operations require a descent of the b-tree, a traversal of the b-tree starting
+# from the root. Whenever we are search the tree, insert or delete records, or
+# balance the tree with a page splits and merges, we first begin with a descent
+# of the b-tree, from the root, to find the page we want to act upon.
+#
+# #### Descent as Unit of Work
+#
+# We use the term descent to describe the both traversal of the b-tree and the
+# subsequent actions performed when when the desired page is found.
+#
+# The descent is the unit of work in our concurrency model.  A descent is
+# analogous to a thread, because when a descent waits on I/O, other descents can
+# make progress.
+#
+# Descents can make progress concurrently, even though Node.js only has a single
+# thread of execution. Descents do not actually make progress in parallel, but
+# their progress can be interleaved. When we descend the tree, we may have to
+# wait for evented I/O to read or write a page. While we wait, we can make
+# progress on another descent in the main thread of execution.
+#
+# Because descents can make interleaved progress, we need to synchronize access
+# to b-tree pages, just as we would with a multi-threaded b-tree implementation.
+# When we descend the b-tree we need to make sure that we do not alter pages
+# that another waiting descent needs to complete its descent when it awakes, nor
+# read pages that a waiting descent had begun to alter before it had to wait.
+#
+# These are race conditions. We use the shared read/exclusive write locks
+# described in the `IO` class above to guard against these race conditions.
+#
+# #### Classes of Descent
+#
+# When we descend to leaf pages of a search b-tree to obtain records, we
 # *search* the b-tree. When we change the size of the b-tree by adding or
 # deleting records we *edit* the b-tree. When we change the structure of the
 # b-tree by splitting or merging pages, we *balance* the b-tree.
 #
+# We talk about search descents, edit descents, and balance descents we we
+# describe the interaction of b-tree operations.
+#
 # We use these terms in this document to save the chore of writing, and the
 # confustion of reading; insert or delete, or split or merge. We also want to
 # draw a distinction between changing the count of records stored in the b-tree,
-# editing, and changing the height of the b-tree, the count of pages, or the
-# choice of keys, balancing.
-#
-# A descent is analogous to a thread, because one can make progress while
-# another descent is waiting on I/O.
-#
-# A decent an make progress while other decents make progress, even though
-# Node.js only has a single thread of execution. When we descend the tree, we
-# may have to wait for evented I/O to read or write a page. While we wait, we
-# can make progress on another descent in the main thread of execution.
-#
-# Because descents can make progress in parallel, we need to synchronize access
-# to b-tree pages, as we would with a multi-threaded b-tree implementation. When
-# we descend the b-tree we need to make sure that we do not alter pages that
-# another waiting descent needs to complete its descent when it awakes, nor read
-# pages that a waiting descent had begun to alter before it had to wait.
-#
-# These are race conditions. We use the shared read/exclusive write locks
-# described in the `IO` class above to guard against these race conditions.
+# *editing*, and changing the height of the b-tree, the count of pages, or the
+# choice of keys, *balancing*.
 #
 # #### Locking on Descent
 #


:art: Update LinterRegistry::{add, delete}Linter specs to include specs of deactivated property

--- spec/linter-registry-spec.coffee
+++ spec/linter-registry-spec.coffee
@@ -19,6 +19,10 @@
       linter = getLinter()
       linterRegistry.addLinter(linter)
       expect(linterRegistry.linters.length).toBe(1)
+    it 'set deactivated to false on linter', ->
+      linter = getLinter()
+      linterRegistry.addLinter(linter)
+      expect(linter.deactivated).toBe(false)

   describe '::hasLinter', ->
     it 'returns true if present', ->
@@ -36,6 +40,11 @@
       expect(linterRegistry.hasLinter(linter)).toBe(true)
       linterRegistry.deleteLinter(linter)
       expect(linterRegistry.hasLinter(linter)).toBe(false)
+    it 'sets deactivated to true on linter', ->
+      linter = getLinter()
+      linterRegistry.addLinter(linter)
+      linterRegistry.deleteLinter(linter)
+      expect(linter.deactivated).toBe(true)

   describe '::lint', ->
     it "doesn't lint if textEditor isn't active one", ->


:memo: better comment for scrollIntoViewIfNeeded

--- src/notational/setup-panel.coffee
+++ src/notational/setup-panel.coffee
@@ -130,7 +130,7 @@
       # Scroll item into the view if outside the visible border and was triggered by selectItem change
       selectedRow = el.querySelector('.is-selected')
       if selectedRow
-        selectedRow.scrollIntoViewIfNeeded(false) # false=only scroll the minimal necessary
+        selectedRow.scrollIntoViewIfNeeded(false) # centerIfNeeded=false => croll minimal possible to avoid jumps
       el.querySelector('.atom-notational-search').focus()
     [resetStream, elementProp], (..., el) ->
       # TODO: getModel/setText are really internals of atom-text-editor.. can be done in a better way?


- slight changes to wrapper around THREE.js base maths

--- src/maths/maths.coffee
+++ src/maths/maths.coffee
@@ -1,4 +1,11 @@
 use 'strict'
+THREE = require 'THREE'
+
+###*
+  *All classes , functions etc defined here are simple wrappers, to make "logical" namespacing
+  *easier (ie Maths.Vector2 instead of THREE.Vector2), and to add a level of indirection between api and implementation
+  *
+###

 ###*
 * 3d vector class: simple wrapper around THREE.Vector3 + some additional params parsing helpers
@@ -7,4 +14,11 @@
   constructor:( options )->
     super(options)

-module.exports = Vector3+class Vector2 extends THREE.Vector2
+  constructor:( options )->
+    super(options)
+
+module.exports =
+  Vector2: Vector2
+  Vector3: Vector3
+


feat(core): reply with thanks after all 5 answers

--- src/model/scrum.coffee
+++ src/model/scrum.coffee
@@ -138,6 +138,10 @@
                     that._recentMessage = true
                     num = answerPattern.exec(message)[1]
                     that._scrumLog[userid].answers[num-1] = message
+                    if that._scrumLog[userid].answers.indexOf('') < 0
+                        that._robot.send
+                            room: that._room,
+                            'Thanks ' + displayname.split(' ')[0]

         activityCheck = () ->
             if !that._recentMessage


User scheme reworked

User scheme reworked

--- src/models/User.coffee
+++ src/models/User.coffee
@@ -3,12 +3,18 @@
   sequelize.define 'User',
     username:
       type: DataTypes.STRING
+      unique: true
+      validate:
+        isAlphanumeric: true
     password:
-      type: DataTypes.STRING
+      type: DataTypes.STRING(64)
       allowNull: false
     email:
       type: DataTypes.STRING
+      unique: true
       validate:
         isEmail: true
-    first_name: DataTypes.STRING
-    last_name: DataTypes.STRING
+    isActive:
+      type: DataTypes.BOOLEAN
+      allowNull: false
+      defaultValue: false


修复修改素材关系会累加的问题

修复修改素材关系会累加的问题

批量添加素材没有删除旧关系，造成多次修改会累加

--- src/biz/issue.coffee
+++ src/biz/issue.coffee
@@ -12,15 +12,21 @@
   #重载save
   save: (member, data, callback)->
     #提取素材列表，并删除原来键值
-    assets = (data.assets || []).slice(0)
+    assets = ((data.assets instanceof Array and data.assets) || [])[..]
     delete data.assets

     data.creator = member.member_id
     super member, data, (err, issue_id)->
+      _relation.replaceAll member, assets, data.id || issue_id, ()->
+        callback err, issue_id
+    ###
+      return done() if not data.id
+    console.log 'error'
+    #如果是更新的前题下，则需要删除所有的关系
+    _relation.unlinkAll member, data.id, done
       count = 0
       _async.whilst(
-        ()->
-          count < assets.length
+        ()-> count < assets.length
         ,
         (done)->
           relation_data = issue_id: issue_id, asset_id: assets[count++]
@@ -28,7 +34,7 @@
         ()->
           callback(err, issue_id)
       )
-
+    ###

   #改变issue的状态
   changeStatus: (member, req, res, next)->
@@ -41,7 +47,7 @@
     }

     #修改状态
-    this.save data, (err)->
+    this.save member, data, (err)->
       res.end()


Changed variable names

Changed variable names

--- app/assets/javascripts/ci/build.coffee
+++ app/assets/javascripts/ci/build.coffee
@@ -41,9 +41,9 @@
     $("html,body").scrollTop $("#build-trace").height()  if "enabled" is $("#autoscroll-button").data("state")

   initScrollButtonAffix: ->
-    buildScroll = $('#js-build-scroll')
-    body = $('body')
-    buildTrace = $('#build-trace')
+    $buildScroll = $('#js-build-scroll')
+    $body = $('body')
+    $buildTrace = $('#build-trace')

     buildScroll.affix(
       offset:


fix chat

fix chat

--- app/assets/javascripts/myrtc.js.coffee
+++ app/assets/javascripts/myrtc.js.coffee
@@ -1,5 +1,5 @@
 showAlertUnsupported = ->
-  $('.js-no-webrtc').removeClass('hide').show()
+  $('.js-no-webrtc').removeClass('hide').show()
   $('.js-accept-webrtc').hide()

 checkWebRTCSupport = ->
@@ -12,8 +12,22 @@
 checkBrowserSupport = ->
   is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
   is_android = navigator.userAgent.toLowerCase().indexOf('android') > -1
-  if (is_firefox and is_android)
+  if (is_firefox and is_android)
     showAlertUnsupported()
+
+appendDIV = (data, parent) ->
+  div = document.createElement('div')
+  div.innerHTML = data
+  chatOutput = document.getElementById('chat-output')
+  fileProgress = document.getElementById('file-progress')
+  if (!parent)
+    chatOutput.insertBefore(div, chatOutput.firstChild)
+  else
+    fileProgress.insertBefore(div, fileProgress.firstChild)
+  div.tabIndex = 0
+  div.focus()
+  chatInput.focus()
+

 $ ->
   checkWebRTCSupport()
@@ -25,6 +39,8 @@
         session: "audio-video-data"
         direction: "many-to-many"
       )
+      connection.onmessage = (e) ->
+        appendDIV(e)
       connection.onstream = (stream) ->
         $(".js-accept-webrtc").hide()
         video = getVideo(stream)
@@ -54,3 +70,12 @@
         initConnection()
         connection.open()
         window.isRoomInitiator = true
+    chatInput = document.getElementById('chat-input')
+    chatInput.onkeypress = (e) ->
+      if (e.keyCode != 13 || !this.value)
+        return
+      appendDIV(this.value);
+      connection.send(this.value)
+
+      this.value = ''
+      this.focus()


After comment is added, button should go back to Close button.

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -26,6 +26,7 @@
     # Reopen and close actions for Issue/MR combined with note form submit
     $(document).on "click", ".js-note-target-reopen", @targetReopen
     $(document).on "click", ".js-note-target-close", @targetClose
+    $(document).on "click", ".js-comment-button", @updateCloseButton
     $(document).on "keyup", ".js-note-text", @updateTargetButtons

     # remove a note (in general)
@@ -496,6 +497,11 @@
     if noteText.trim().length > 0
       form.submit()

+  updateCloseButton: (e) =>
+    textarea = $(e.target)
+    form = textarea.parents('form')
+    form.find('.js-note-target-close').text('Close')
+
   updateTargetButtons: (e) =>
     textarea = $(e.target)
     form = textarea.parents('form')


Add getNextPage and getPreviousPage methods

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -109,20 +109,30 @@
       if not page.loaded
         @fetchPage()

+    getNextPage: () ->
+      page = @get('page')
+      if page < @get('pages') then ++page
+      return page
+
+    getPreviousPage: () ->
+      page = @get('page')
+      if page isnt 1 then --page
+      return page
+
     nextPage: () ->
       page = @get('page')
+      nextPage = @getNextPage()

       # Show the next page if there is one
-      if page < @get('pages')
-        @setPage(++page)
+      @setPage(nextPage) if page isnt nextPage

-      return page
+      return nextPage

     previousPage: () ->
       page = @get('page')
+      previousPage = @getPreviousPage()

       # Show the previous page if there is one
-      if page isnt 1
-        @setPage(--page)
+      @setPage(previousPage) if page isnt previousPage

-      return page
+      return previousPage


Fix error handlers in kpi.svc not rejecting promises

--- src/services/kpis/kpis.svc.coffee
+++ src/services/kpis/kpis.svc.coffee
@@ -115,8 +115,9 @@

                   $log.info("Impac! - KpisSvc: loaded (force=#{force})")

-                (error) ->
-                  $log.error('Impac! - KpisSvc: Cannot retrieve kpis templates list', error)
+                (err) ->
+                  $log.error('Impac! - KpisSvc: Cannot retrieve kpis templates list', err)
+                  $q.reject(err)
               ).finally(-> _self.locked = false )

             else
@@ -255,7 +256,7 @@

             url = formatShowQuery(host, kpi.endpoint, kpi.element_watched, params)

-            $http.get(url).then(
+            return $http.get(url).then(
               (response) ->
                 # When no target has been defined
                 if response.data.error && response.data.error.code == 422
@@ -281,12 +282,12 @@

               (err) ->
                 $log.error 'Impac! - KpisSvc: Could not retrieve KPI (show) at: ' + kpi.endpoint, err
-                err
+                $q.reject(err)
             )
           )
         ->
           $log.error 'Impac! - KpisSvc: Service not initialized'
-          {error: { message: 'Impac! - KpisSvc: Service is not initialized' }}
+          $q.reject({error: { message: 'Impac! - KpisSvc: Service is not initialized' }})
       ).finally ( -> kpi.isLoading = false )

     @create = (source, endpoint, elementWatched, opts={}) ->
@@ -314,10 +315,10 @@
               kpi
             (err) ->
               $log.error("Impac! - KpisSvc: Unable to create kpi endpoint=#{endpoint}", err)
-              err
+              $q.reject(err)
           )
         ->
-          { error: {message: 'Impac! - KpisSvc: Service is not initialized'} }
+          $q.reject({ error: {message: 'Impac! - KpisSvc: Service is not initialized'} })
       )

     @update = (kpi, params) ->
@@ -355,7 +356,7 @@
             res
           (err)->
             $log.error("Impac! KpisSvc: Unable to delete KPI #{kpi.id}", err)
-            err
+            $q.reject(err)
         )
       )


compute server: allow network access for "monitoring" user

--- src/smc-hub/compute-server.coffee
+++ src/smc-hub/compute-server.coffee
@@ -982,7 +982,7 @@
             if err
                 opts.cb(err)
             else
-                opts.cb(undefined, ['root','salvus','dd-agent'].concat((x.project_id.replace(/-/g,'') for x in results)))
+                opts.cb(undefined, ['root','salvus','monitoring'].concat((x.project_id.replace(/-/g,'') for x in results)))

 NO_OUTGOING_FIREWALL = false
 firewall = (opts) ->


Заменил bind/unbind на on/off

Заменил bind/unbind на on/off

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -45,11 +45,11 @@
     ###
      Добавляем события по-умолчанию для контейнеров
     ###
-    @overlay.find('.choco-close').bind 'click', => @close()
+    @overlay.find('.choco-close').on 'click', => @close()

     @_prepareActionFor container for container in ['overlay', 'container', 'leftside', 'rightside']

-    $(window).bind 'keyup', (event) =>
+    $(window).on 'keyup', (event) =>
       if @overlay.hasClass 'show'
         switch event.keyCode
           when 27    # ESC
@@ -74,10 +74,10 @@
           else
             @open cid, false

-      $(window).bind 'load', -> onHistory()
+      $(window).on 'load', -> onHistory()

       if 'onhashchange' of window
-        $(window).bind 'hashchange', -> onHistory()
+        $(window).on 'hashchange', -> onHistory()

     ###
      Получаем параметры отступов и другие неизменяемые размеры
@@ -122,7 +122,7 @@

     @_hideLess() if @length is 1

-    $(window).bind 'resize', =>
+    $(window).on 'resize', =>
       image = @images[@current]
       @updateDimensions image.width, image.height

@@ -135,9 +135,9 @@

       if @options.thumbnails
         @thumbnails.html ''
-        @overlay.find('.choco-thumbnails-toggle').unbind 'click'
-
-      $(window).unbind 'resize'
+        @overlay.find('.choco-thumbnails-toggle').off 'click'
+
+      $(window).off 'resize'

       @current = null
       @overlay.removeClass 'show'
@@ -291,10 +291,10 @@
                          .replace('{{thumbnail}}', image.thumbnail)
                          .replace('{{title}}', if image.title then ' title="' + image.title + '"' else '')

-    @thumbnails.html(content).find('.choco-thumbnail').bind 'click', ->
+    @thumbnails.html(content).find('.choco-thumbnail').on 'click', ->
       _this.open toInt $(@).attr('data-cid')

-    @overlay.find('.choco-thumbnails-toggle').bind 'click', ->
+    @overlay.find('.choco-thumbnails-toggle').on 'click', ->
       current = _this.images[_this.current]
       status  = _this.thumbnails.hasClass 'hide'
       method  = if status then 'removeClass' else 'addClass'
@@ -372,7 +372,7 @@
         event.preventDefault()
         @show cid

-      image.addClass('choco-item').bind 'click', (event) ->
+      image.addClass('choco-item').on 'click', (event) ->
         showFirstImage event, cid

       preload        = new Image()
@@ -385,8 +385,8 @@
           'height':     image.height()
           'margin-top': '-' + image.height() + 'px'

-        image.bind 'hover', (event) -> popover.toggleClass 'hover'
-        popover.bind 'click', (event) -> showFirstImage event, cid
+        image.on 'hover', (event) -> popover.toggleClass 'hover'
+        popover.on 'click', (event) -> showFirstImage event, cid

   ###
    Private method
@@ -397,11 +397,11 @@
     if method
       verify = @[container].attr 'class'

-      @[container].bind 'click', (event) =>
+      @[container].on 'click', (event) =>
         @[method]() if $(event.target).hasClass verify

       if @options.actions[container] is 'close'
-        @[container].bind 'mouseenter mouseleave', =>
+        @[container].on 'mouseenter mouseleave', =>
           @overlay.find('.choco-close').toggleClass 'hover'
     @


Removeing some jQuery

Removeing some jQuery

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -145,10 +145,9 @@

       if @options.thumbnails
         @thumbnails.innerHTML = ''
-        $(@overlay.querySelector('.choco-thumbnails-toggle')).off 'click'
-
-      # jQuery
-      $(window).off 'resize'
+        removeHandler @overlay, 'click', '.choc-thumbnails-toggle'
+
+      removeHandler window, 'resize'

       @current = null

@@ -319,12 +318,12 @@
                          .replace('{{image}}', image.thumbnail)
                          .replace('{{title}}', if image.title then " title=\"#{image.title}\"" else '')

-    $(@thumbnails).html(content).find('.choco-thumbnail').each ->
-      thumbnail = $ @
-      image = @getAttribute 'data-image'
-
-      thumbnail.on 'click', -> _this.open toInt thumbnail.attr 'data-cid'
-      setStyle @, "background-image": "url(#{image})"
+    @thumbnails.innerHTML = content
+    [].forEach.call @thumbnails.querySelectorAll('.choco-thumbnail'), (thumbnail) ->
+      image = thumbnail.getAttribute 'data-image'
+
+      addHandler thumbnail, 'click', -> _this.open toInt @getAttribute 'data-cid'
+      setStyle thumbnail, "background-image": "url(#{image})"

     addHandler @overlay, 'click', ->
       current = _this.images[_this.current]
@@ -355,17 +354,20 @@
   updateThumbnails: ->
     return @ if not @options.thumbnails or @length <= 1

-    before = $(@thumbnails).find('.selected').removeClass('selected').attr 'data-cid'
+    before = @thumbnails.querySelector '.selected'
+    if before?
+      removeClass before, 'selected'
+      before = before.getAttribute 'data-cid'
     after  = @thumbnails.querySelector "[data-cid='#{@current}']"
     addClass after, 'selected'

     if not @dimensions.thumbnail
       @dimensions.thumbnail = toInt @_outerWidth after

-    width     = $(@thumbnails).width()
-    element   = $(after).get(0).offsetLeft
+    width     = @thumbnails.offsetWidth
+    element   = after.offsetLeft
     thumbnail = @dimensions.thumbnail
-    container = $(@thumbnails).get(0).scrollLeft or 0
+    container = @thumbnails.scrollLeft or 0

     if before
       offset = if @current > before then container + thumbnail else container - thumbnail
@@ -381,7 +383,7 @@
       else if offset > element
         offset = 1

-    $(@thumbnails).get(0).scrollLeft = offset
+    @thumbnails.scrollLeft = offset
     @

@@ -419,7 +421,7 @@

       preload = new Image()
       preload.onload = ->
-        $(image).after templates['image-hover'].replace '{{cid}}', cid
+        image.insertAdjacentHTML 'afterend', templates['image-hover'].replace '{{cid}}', cid

         popover = document.querySelector "[data-pid=\"#{cid}\"]"
         setStyle popover,
@@ -428,9 +430,9 @@
           'margin-top': "#{-1 * image.offsetHeight}px"

         addHandler image, ['mouseenter', 'mouseleave'], ->
-          toggleClass popover[0], 'choco-hover'
-
-        addHandler popover[0], 'click', (event) ->
+          toggleClass popover, 'choco-hover'
+
+        addHandler popover, 'click', (event) ->
           showFirstImage event, cid

       preload.src = data.thumbnail
@@ -528,6 +530,18 @@
     for ev in event
       target.addEventListener ev, listener, false

+removeHandler = (element, event, selector) ->
+  target = getTarget element, selector
+  ###
+  if target?
+    listeners = getEventListeners target
+    unless event instanceof Array
+      event = [event]
+    for ev in event
+      for listener in listeners[ev]
+        target.removeEventListener ev, listener, false
+  ###
+
 hasClassList = (element, selector) ->
   target = getTarget element, selector
   if target? and target.hasOwnProperty 'classList'


fix subtle (?) bug in which which everything broke when clicking the i-->share (etc.) buttons from a file when last listing of that directory didn't include the file being edited); this was caused by a race condition and false assumptions

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1132,11 +1132,11 @@

     render_share : ->
         # currently only works for a single selected file
-
         single_file = @props.checked_files.first()
         single_file_data = @props.file_map[misc.path_split(single_file).tail]
         if not single_file_data?
-            console.warn("BUG: render_share -- please report")
+            # directory listing not loaded yet... (will get re-rendered when loaded)
+            return <Loading />
         else
             if single_file_data.is_public and single_file_data.public?.path isnt single_file
                 parent_is_public = true


Detect if a model has actually changed

Detect if a model has actually changed

--- scripts/views/workspace/menu/auth.coffee
+++ scripts/views/workspace/menu/auth.coffee
@@ -32,7 +32,10 @@

     initialize: () ->
       @listenTo(session, 'login logout', @render)
-      @listenTo(content, 'change add remove', @changed)
+      @listenTo(content, 'add remove', @changed)
+      @listenTo content, 'change', (model, options) =>
+        # A change event can occur (ie setting a title during parsing but the changed set is still empty)
+        @changed() if model.hasChanged()

       # Bind a function to the window if the user tries to navigate away from this page
       $(window).on 'beforeunload', () ->


updated incorrect coments

updated incorrect coments

--- app/scripts/controllers/compare.coffee
+++ app/scripts/controllers/compare.coffee
@@ -7,9 +7,9 @@
 ######################

     # these were defined in AppCtrl and $scope will delegate to $rootScope
-      # $rootScope.reps
-      # $rootScope.selected
-      # $rootScope.reps_list
+      # $scope.reps
+      # $scope.selected
+      # $scope.reps_list

     # init local variables
     $scope.comparison =


Faster dialog and drones attacking

Faster dialog and drones attacking

--- app/scripts/lazerscripts/stage1.coffee
+++ app/scripts/lazerscripts/stage1.coffee
@@ -153,11 +153,13 @@

   droneTakeover: ->
     @sequence(
-      @say('General', 'What are those drones doing there!?')
-      @placeSquad Game.Scripts.CrewShooters,
-        amount: 4
-        delay: 750
-        drop: 'pool'
+      @parallel(
+        @say('General', 'What are those drones doing there!?')
+        @placeSquad Game.Scripts.CrewShooters,
+          amount: 4
+          delay: 750
+          drop: 'pool'
+      )
       @say('General', 'They do not respond to our commands anymore!\nOur defence AI has been hacked!')
       @async @chapterTitle(1, 'Hacked')
       @setSpeed 150


Check for divisionmember

Check for divisionmember

--- src/persistence.coffee
+++ src/persistence.coffee
@@ -719,7 +719,7 @@
   wrapMethod sdk, 'saveBroadcastAlert', (saveBroadcastAlert) ->
     (broadcastAlert, callback) ->
       saveBroadcastAlert.call(this, broadcastAlert).then((result) ->
-        if result.member?
+        if result.member? or result.divisionMember?
           params = {memberId: result.memberId}
         else
           params = {contactId: result.contactId}


took out dev tools

took out dev tools

--- src/browser/chizel-window.coffee
+++ src/browser/chizel-window.coffee
@@ -23,7 +23,7 @@
         #global.ChizelApplication.addWindow(this)

         @mainWindow = new browserWindow {}
-        @mainWindow.openDevTools()
+        #@mainWindow.openDevTools()
         @mainWindow.loadURL url.format
             protocol: 'file'
             pathname:  path + "/static/index.html"


Update: バージョンを自動取得するように(android) close #10

--- src/core/util.coffee
+++ src/core/util.coffee
@@ -40,7 +40,16 @@
       else
         reject("Error: Version Regexp Error (#{text})")
       return
-    , (err) ->
+    ).catch( ->
+      return readFile(path.join(require("./config").get("blitzPath"), "Data/version", "resources.txt"), "utf-8").then( (text) ->
+        reg = /^(\d+\.\d+\.\d+)$/.exec(text)
+        if reg?
+          resolve(reg[1])
+        else
+          reject("Error: Version Regexp Error (#{text})")
+        return
+      )
+    ).catch( (err) ->
       reject(err)
       return
     )


updated comment

updated comment

--- src/scripts/models/contents/node.coffee
+++ src/scripts/models/contents/node.coffee
@@ -38,7 +38,7 @@
       #if @get('title') then delete response.title
       response.title = @get('title') or response.title

-      # extract abstract from cnxml wrapper
+      # extract abstract from wrapper
       if response.abstract?
         response.abstract = $(response.abstract).unwrap().html()


[FIX] load fs module

[FIX] load fs module

--- src/postinstall.coffee
+++ src/postinstall.coffee
@@ -1,4 +1,5 @@
 prompt = require('prompt')
+fs = require('fs')

 schema = {
   properties: {


fixed the issue with the custom object sorting

--- src/coffee/sphere_service.coffee
+++ src/coffee/sphere_service.coffee
@@ -239,23 +239,7 @@
     new Pagger
       pageSize: @customObjectsPageSize
       onNextPage: (offset, limit) =>
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        # FIXME: here is the correct sort criteria: ["lastModifiedAt asc"]. In code is just a workeround
-        @_get @_pathWhere("/custom-objects", "container=\"#{@getMessagesStateContainer()}\"#{where}", ["container asc"], null, limit, offset)
+        @_get @_pathWhere("/custom-objects", "container=\"#{@getMessagesStateContainer()}\"#{where}", ["lastModifiedAt asc"], null, limit, offset)
     .page()

   getLastProcessedSequenceNumber: (resource) ->


add some secret shortcuts for working around ipad bugs...

--- src/smc-webapp/mobile/codemirror.coffee
+++ src/smc-webapp/mobile/codemirror.coffee
@@ -12,23 +12,25 @@

 exports.extra_alt_keys = (extraKeys, editor, opts) ->
     misc.merge extraKeys,
-        "Shift-Alt-L"  : (cm) => cm.align_assignments()
-        'Alt-Z'        : (cm) => cm.undo()
-        'Shift-Alt-Z'  : (cm) => cm.redo()
-        'Alt-A'        : (cm) => cm.execCommand('selectAll')
-        'Shift-Alt-A'  : (cm) => cm.execCommand('selectAll')
-        'Alt-K'        : (cm) => cm.execCommand('killLine')
-        'Alt-D'        : (cm) => cm.execCommand('selectNextOccurrence')
-        'Alt-F'        : (cm) => cm.execCommand('find')
-        'Shift-Alt-F'  : (cm) => cm.execCommand('replace')
-        'Shift-Alt-R'  : (cm) => cm.execCommand('replaceAll')
-        'Shift-Alt-D'  : (cm) => cm.execCommand('duplicateLine')
-        'Alt-G'        : (cm) => cm.execCommand('findNext')
-        'Shift-Alt-G'  : (cm) => cm.execCommand('findPrev')
-        'Alt-Up'       : (cm) => cm.execCommand('goPageUp')
-        'Alt-Down'     : (cm) => cm.execCommand('goPageDown')
-        'Alt-P'        : (cm) => cm.execCommand('goLineUp')
-        'Alt-N'        : (cm) => cm.execCommand('goLineDown')
+        "Shift-Alt-L" : (cm) => cm.align_assignments()
+        'Alt-Z'       : (cm) => cm.undo()
+        'Shift-Alt-Z' : (cm) => cm.redo()
+        'Alt-A'       : (cm) => cm.execCommand('selectAll')
+        'Shift-Alt-A' : (cm) => cm.execCommand('selectAll')
+        'Alt-K'       : (cm) => cm.execCommand('killLine')
+        'Alt-D'       : (cm) => cm.execCommand('selectNextOccurrence')
+        'Alt-F'       : (cm) => cm.execCommand('find')
+        'Shift-Alt-F' : (cm) => cm.execCommand('replace')
+        'Shift-Alt-R' : (cm) => cm.execCommand('replaceAll')
+        'Shift-Alt-D' : (cm) => cm.execCommand('duplicateLine')
+        'Alt-G'       : (cm) => cm.execCommand('findNext')
+        'Shift-Alt-G' : (cm) => cm.execCommand('findPrev')
+        'Alt-Up'      : (cm) => cm.execCommand('goPageUp')
+        'Alt-Down'    : (cm) => cm.execCommand('goPageDown')
+        'Alt-K'       : (cm) => cm.execCommand('goPageUp')
+        'Alt-J'       : (cm) => cm.execCommand('goPageDown')
+        'Alt-P'       : (cm) => cm.execCommand('goLineUp')
+        'Alt-N'       : (cm) => cm.execCommand('goLineDown')

     if editor?.goto_line?
         extraKeys['Alt-L'] = (cm) => editor.goto_line(cm)