Added many more features to class-complete

--- lib/class-complete.coffee
+++ lib/class-complete.coffee
@@ -7,42 +7,191 @@
     complete: ->
         buffer = ""
         editor = atom.workspace.getActivePaneItem()
-        cursor = editor.cursors[0];
+        cursor = editor.cursors[0]
         text = cursor.getCurrentBufferLine().trim()
-        split = text.split ":"
-        className = split[0]

-        parameters = []
-        if split[1]
-            for param in split[1].split ","
-                parameters.push param.trim() if param.trim()
+        classdef = module.exports.parseClassdef(text)

         cursor.moveToEndOfLine()
         editor.selectToBeginningOfLine()

-        buffer += "#{className} = (function() {\n"
-        buffer += "\tfunction #{className}("
+        buffer += "#{classdef.name} = (function() {\n"

-        if parameters
-            for param in [0..parameters.length - 1]
-                if param == parameters.length - 1
-                    buffer += parameters[parameters.length - 1] if parameters[parameters.length - 1]
-                else
-                    buffer += parameters[param] + ", " if parameters[param]
+        buffer += module.exports.indent(module.exports.generateMethod(classdef.name, null, classdef.parameters, "constructor", {
+            "before": "#{classdef.extends}.apply(this, arguments);" if classdef.extends
+        }), 1)

-        buffer += ") {\n"
-        if split.length > 2
-            buffer += "\t\t#{split[2]}.apply(this, arguments);\n"
+        if classdef.extends
+            buffer += "\n"
+            buffer += "\t#{classdef.name}.prototype = Object.create(#{classdef.extends}.prototype);\n"
+            buffer += "\t#{classdef.name}.prototype.constructor = #{classdef.name};\n"

-        buffer += "\t}\n"
+        if classdef.methods.length > 0
+            for method in [0..classdef.methods.length - 1]
+                if method >= 0
+                    buffer += "\n"

-        if split.length > 2
-            buffer += "\n"
-            buffer += "\t#{className}.prototype = Object.create(#{split[2]}.prototype);\n"
-            buffer += "\t#{className}.prototype.constructor = #{className};\n"
+                method = classdef.methods[method]
+                buffer += module.exports.indent(module.exports.generateMethod(classdef.name, method.name, method.parameters, method.type), 1)

         buffer += "\n"
-        buffer += "\treturn #{className};"
+        buffer += "\treturn #{classdef.name};"
         buffer += "\n}());"

         editor.insertText buffer
+
+    parseClassdef: (string) ->
+        # <class_name>:<arguments>,...;<methods>,...:<extend>
+
+        def = {
+            name: null,
+            parameters: [],
+            methods: [],
+            extends: null
+        }
+
+        string = string.split(".")
+        string = string[string.length - 1]
+        string = string.replace(/\s/g, "")
+        parts = string.split(":")
+
+        if parts.length > 0
+            def.name = parts[0]
+            if parts.length > 1
+                parameth = parts[1].split(";")
+                parameters = parameth[0]
+                methods = parameth[1]
+
+                if parameters
+                    def.parameters = module.exports.parseParameters(parameters)
+
+                if methods
+
+                    methods2 = methods.split("/")
+                    for method in methods2
+                        meth = {
+                            name: null,
+                            parameters: []
+                            type: "method"
+                        }
+
+                        if method[0] == "@"
+                            meth.type = "static"
+                            method = method.substr(1, method.length - 1)
+
+                        methsplit = method.split("+")
+                        meth.name = methsplit[0] if methsplit.length > 0
+
+                        meth.parameters = module.exports.parseParameters(methsplit[1]) if methsplit.length > 1
+
+                        def.methods.push(meth) if meth.name
+
+            if parts.length > 2
+                def.extends = parts[2]
+
+        return def
+
+    parseParameters: (string) ->
+        params = []
+
+        string = string.replace(/\s/g, "").split(",")
+
+        for part in string
+            param = {
+                name: null
+                type: null
+                instance: null
+                member: false
+                modes: []
+            }
+
+            parts = part.split("~")
+            if parts.length > 1
+                param.modes = parts[1].split("")
+                part = parts[0]
+
+            if part[0] == "@"
+                param.member = true
+                part = part.substr(1, part.length - 1)
+
+            if part.split("!").length > 1
+                split = part.split("!")
+
+                param.name = split[0]
+                param.type = split[1]
+            else if part.split("^").length > 1
+                split = part.split("^")
+
+                param.name = split[0]
+                param.instance = split[1]
+            else
+                param.name = part
+
+            params.push(param)
+
+        return params
+
+    generateMethod: (className, method, parameters, type = "member", body = {}) ->
+        if type == "static"
+            buffer = "#{className}.#{method} = function("
+        else if type == "method"
+            buffer = "#{className}.prototype.#{method} = function("
+        else if type == "constructor"
+            buffer = "function #{className}("
+
+        if parameters.length > 0
+            for param in [0..parameters.length - 1]
+                if param == parameters.length - 1
+                    buffer += parameters[parameters.length - 1].name + ") {\n"
+                else
+                    buffer += parameters[param].name + ", "
+        else
+            buffer += ") {\n"
+
+        buffer += "#{module.exports.indent(body.before, 1)}" if body.before
+
+        checks = false
+        members = false
+
+        if parameters.length > 0
+            buffer += "\n" if body.before
+            for param in [0..parameters.length - 1]
+                param = parameters[param]
+
+                if param.member
+                    members = true
+
+                if param.type
+                    checks = true
+                    buffer += "\tif (typeof #{param.name} !== \"#{param.type}\") throw new Error(\"Parameter '#{param.name}' expects to be type '#{param.type}'\");\n"
+                if param.instance
+                    checks = true
+                    buffer += "\tif (#{param.name} instanceof #{param.instance}) throw new Error(\"Parameter '#{param.name}' expects to be instance of '#{param.instance}'\");\n"
+
+
+        if parameters.length > 0 && members
+            buffer += "\n" if checks
+            for param in [0..parameters.length - 1]
+                param = parameters[param]
+                if param.member
+                    buffer += "\tthis.#{param.name} = #{param.name};\n"
+
+        if body.after
+            buffer += "\n" if checks || members
+            buffer += "#{module.exports.indent(body.after, 1)}"
+
+        buffer += "};"
+
+        return buffer
+
+    indent: (text, indent) ->
+        buffer = ""
+        lines = text.split(/\n|\r\n/)
+
+        prefix = ""
+        prefix += "\t" for i in [0..indent - 1]
+
+        for line in lines
+            buffer += prefix + line + "\n"
+
+        return buffer


Protect against an undefined command target

see https://github.com/atom/notifications/pull/142

--- lib/command-logger.coffee
+++ lib/command-logger.coffee
@@ -72,6 +72,7 @@
     {type: name, target, time} = command
     return if command.detail?.jQueryTrigger
     return if name of ignoredCommands
+    return unless target?

     event = @latestEvent()


Be able to target individual units in the clock template

--- components/clock/view.coffee
+++ components/clock/view.coffee
@@ -50,7 +50,7 @@
         false
       else
         """
-

+

             #{if diff < 10 then '0' + diff else diff}
             #{label}


fake link

fake link

--- lib/file-info-view.coffee
+++ lib/file-info-view.coffee
@@ -4,7 +4,7 @@
   initialize: ->
     @classList.add('file-info', 'inline-block')

-    @currentPath = document.createElement('span')
+    @currentPath = document.createElement('a')
     @currentPath.classList.add('current-path')
     @appendChild(@currentPath)


Change the original settings of Documentation Browser

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -226,14 +226,14 @@
                 title: 'Default location of Documentation Browser Pane'
                 type: 'string'
                 enum: ['center', 'left', 'bottom', 'right']
+                default: 'center'
+                radio: true
+                order: 1
+              split:
+                title: 'Splitting rule of Documentation Browser Pane'
+                type: 'string'
+                enum: ['no split', 'left', 'up', 'right', 'down']
                 default: 'right'
-                radio: true
-                order: 1
-              split:
-                title: 'Splitting rule of Documentation Browser Pane'
-                type: 'string'
-                enum: ['no split', 'left', 'up', 'right', 'down']
-                default: 'no split'
                 radio: true
                 order: 2
           plotPane:


Renamed '_destroy' to '__destroy'.

Renamed '_destroy' to '__destroy'.

This avoids a naming clash with Knockout.js 2.0 where a "foreach"
binding would be broken. Foreach will (by default) only create DOM
elements for view models where "vm._destroy" evaluates to false.

--- src/knockback_view_model.coffee
+++ src/knockback_view_model.coffee
@@ -58,7 +58,7 @@
   constructor: ->
     @ref_count = 1

-  _destroy: ->
+  __destroy: ->
     kb.vmReleaseObservables(this)

   # reference counting
@@ -70,7 +70,7 @@
   release: ->
     throw new Error("ViewModel: ref_count is corrupt: " + @ref_count) if (@ref_count <= 0)
     @ref_count--
-    @_destroy() unless @ref_count
+    @__destroy() unless @ref_count
     @

   refCount: -> return @ref_count
@@ -104,7 +104,7 @@
     missing = _.difference(missing, _.keys(@_kb_vm.model.attributes)) if not @_kb_vm.model_ref or @_kb_vm.model_ref.isLoaded()
     @_updateAttributeObservor(@_kb_vm.model, key) for key in missing

-  _destroy: ->
+  __destroy: ->
     (@_kb_vm.model.unbind('change', @_kb_vm_onModelChange); @_kb_vm.model = null) if @_kb_vm.model
     view_model = @_kb_vm.view_model; @_kb_vm.view_model = null
     kb.vmReleaseObservables(view_model, @_kb_vm.observables)


register dummy user with valid email

register dummy user with valid email

--- core/utils.coffee
+++ core/utils.coffee
@@ -458,7 +458,7 @@
     uniqueness = (Date.now()+"").slice(6)
     formData   =
       agree           : "on"
-      email           : "#{uniqueness}@sinanyasar.com"
+      email           : "sinanyasar+#{uniqueness}@gmail.com"
       firstName       : gp(gr(10), yes)
       inviteCode      : "twitterfriends"
       lastName        : gp(gr(10), yes)


utils: markdown imageproxy fixed

utils: markdown imageproxy fixed

--- core/utils.coffee
+++ core/utils.coffee
@@ -109,9 +109,8 @@

   proxifyUrl:(url="")->
     if url is ""
-      "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
-    else
-      '/-/imageProxy?url=' + encodeURIComponent(url)
+    then "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+    else "#{location.protocol}//#{location.host}/-/imageProxy?url=#{encodeURIComponent(url)}"

   applyMarkdown: (text)->
     # problems with markdown so far:
@@ -133,11 +132,9 @@

     # Proxify images

-    proxify = (str, p1, offset, s)=>
-      @proxifyUrl str
-
     sanitizeText.find("img").each (i,element) =>
-      $(element).attr("src", $(element).attr("src")?.replace(/.*/, proxify))
+      src = element.getAttribute 'src'
+      element.setAttribute "src", src?.replace /.*/, @proxifyUrl

     # Give all outbound links a target blank
     sanitizeText.find("a").each (i,element) =>


improve mockup message entry with basic formatting and auto linking

--- mockup/scripts/mockup.coffee
+++ mockup/scripts/mockup.coffee
@@ -9,7 +9,8 @@
   $.get 'Status.html', (text) -> templates.status = text

   $('#messageInput').bind 'keydown', (event) ->
-    if event.which is 13
+    if event.which is 13 and not event.altKey
+      event.preventDefault()
       sendMessage $(this).val()
       $(this).val ''

@@ -23,6 +24,12 @@
 formatTime = (date = new Date) ->
   [ date.getHours(), date.getMinutes() ].join(':')

+uriPattern = /[^:\/?\#]+:(?:\/\/(?:(?:[^:@\/]*(?::[^:@\/]*)?)?@)?[^:\/?\#]*(?::\d*)?)?(?:[^?\#]*)*(?:\?[^\#]*)?(?:\#.*)?/g
+markup = (text) ->
+  $('
').text(text).html() # escape HTML characters
+  .replace(/\n/g, '
') # convert line breaks
+  .replace(uriPattern, (uri) -> """#{uri}""") # convert URIs to links
+
 sendMessage = (text, options = {}) ->
   type = options.type ? 'message'

@@ -35,7 +42,7 @@
     sender: user.displayName
     senderScreenName: user.screenName
     messageClasses: classNames.join(' ')
-    message: text
+    message: markup text

   html = templates.message.replace /%([A-Za-z]+)%/g, (m, key) -> data[key]


Sends url, path and referrer to analytics tracking calls

--- src/remote/services/hull.coffee
+++ src/remote/services/hull.coffee
@@ -16,6 +16,10 @@
     getSessionId = ->
       createOrRefreshUuid('_sid', 30 * 60)

+    initInfo =
+      url: app.config.data.request.url.href
+      path: app.config.data.request.url.path
+      referrer: app.config.data.request.referrer?.href
     identify = (me) ->
       return unless me
       identified = !!me.id?
@@ -53,6 +57,7 @@
       return unless event
       params.hull_app_id    = app.config?.appId
       params.hull_app_name  = app.config?.data?.app?.name
+      _.defaults params, initInfo
       require('analytics').track(event, params)

     trackAction = (response)->


search: publications for dataset

search: publications for dataset

--- src/routes/simple-search.coffee
+++ src/routes/simple-search.coffee
@@ -1,17 +1,18 @@
 CONFIG = require '../config'
 Async  = require 'async'
 log = require('../log')(module)
+Accepts    = require 'accepts'

 _denoise_id = (s) ->
 	sold = s
 	s = s.replace(/doi:/i, '')
 	s = s.replace(new RegExp("https?://(dx\\.)?doi.org/?"), '')
-	log.debug("_denoise_id: #{sold} -> #{s}")
+	# log.debug("_denoise_id: #{sold} -> #{s}")
 	return s

 _get_id = (s) ->
 	snew = s.substr(s.lastIndexOf('/') + 1)
-	log.debug("_get_id: #{s} -> #{snew}")
+	# log.debug("_get_id: #{s} -> #{snew}")
 	return snew

 #publication -> entityLink -> dataset
@@ -43,34 +44,48 @@
 module.exports = (app, done) ->
 	{Entity, EntityLink} = app.schemo.models

-	_find_links_from_publication = (fromEntity, cb) ->
+	_find_links_to_dataset = (toEntity, limit, cb) ->
 		FOUND_LINKS = []
-		#
-		# publication -> LINKPC
-		#
-		EntityLink.find({fromEntity: fromEntity.uri()}).limit(100).exec (err, links1) ->
+		EntityLink.find({toEntity: toEntity.uri()}).limit(limit).exec (err, links1) ->
 			return cb err if err
+			Async.eachLimit links1, 100, (link, cbLinks1) ->
+				Entity.findOne {_id: _get_id link.fromEntity}, (err, citedData) ->
+					return cbLinks1 err if err or not citedData
+					EntityLink.findOne {toEntity: citedData.uri()}, (err, link2) ->
+						return cbLinks1 err if err or not link2
+						Entity.findOne {_id: _get_id link2.fromEntity}, (err, fromEntity) ->
+							return cbLinks1 err if err or not fromEntity
+							if fromEntity.entityType is 'publication'
+								toPush = link2.toJSON()
+								toPush.fromEntity = fromEntity.toJSON()
+								toPush.toEntity = toEntity.toJSON()
+								FOUND_LINKS.push toPush
+							return cbLinks1()
+			, (err) ->
+				return cb err if err
+				return cb null, FOUND_LINKS
+
+	_find_links_from_publication = (fromEntity, limit, cb) ->
+		FOUND_LINKS = []
+		EntityLink.find({fromEntity: fromEntity.uri()}).limit(limit).exec (err, links1) ->
+			return cb err if err
+			# publication -> LINKPC
 			Async.eachSeries links1, (link, cbLinks1) ->
-				#
 				# LINKPC -> citedData
-				#
 				Entity.findOne {_id: _get_id link.toEntity}, (err, citedData) ->
 					return cbLinks1 err if err or not citedData
+					# citedData -> LINKCD
 					# TODO same_as
-					# TODO same_as
-					# TODO same_as
-					#
-					# citedData -> LINKCD
-					#
 					EntityLink.findOne {fromEntity: citedData.uri()}, (err, link2) ->
 						return cbLinks1 err if err or not link2
-						#
+						console.log(link.entityRelations)
+						console.log(link2.entityRelations)
 						# LINKCD -> dataset
-						#
 						Entity.findOne {_id: _get_id link2.toEntity}, (err, toEntity) ->
 							return cbLinks1 err if err or not toEntity
 							# jsonify and replace fromEntity/toEntity with populated documents
 							toPush = link2.toJSON()
+							# toPush.confidence = (link.confidence || -1)
 							toPush.fromEntity = fromEntity.toJSON()
 							toPush.toEntity = toEntity.toJSON()
 							FOUND_LINKS.push toPush
@@ -79,19 +94,24 @@
 				return cb err if err
 				return cb null, FOUND_LINKS

-	_find_links = (needleQuery, cb) ->
+	_find_links = (needleQuery, limit, cb) ->
 		log.debug("Entity query:". needleQuery)
 		FOUND_LINKS = []
-		Entity.find(needleQuery).limit(1000).exec (err, entities) ->
+		Entity.find(needleQuery).limit(limit).exec (err, entities) ->
 			return cb err if err
 			return cb null, [] if entities.length == 0
-			Async.eachSeries entities, (fromEntity, cbEntities1) ->
+			Async.eachSeries entities, (needleEntity, cbEntities1) ->
 				#
 				# Query1 (publication -> dataset)
 				#
-				# log.debug "fromEntity", fromEntity.uri()
-				if fromEntity.entityType is 'publication'
-					_find_links_from_publication fromEntity, (err, links) ->
+				# log.debug "needleEntity", needleEntity.uri()
+				if needleEntity.entityType is 'publication'
+					_find_links_from_publication needleEntity, limit, (err, links) ->
+						return cbEntities1 err if err
+						FOUND_LINKS.push x for x in links
+						return cbEntities1()
+				else
+					_find_links_to_dataset needleEntity, limit, (err, links) ->
 						return cbEntities1 err if err
 						FOUND_LINKS.push x for x in links
 						return cbEntities1()
@@ -99,29 +119,33 @@
 				return cb err if err
 				return cb null, FOUND_LINKS

-	# Swagger interface
 	app.get '/search', (req, res, next) ->
 		locals = links: [], query: req.query
-		if not req.query.id
+		if not req.query.id and not req.query.title
 			return res.render 'simple-search', locals
-		req.query.id = _denoise_id(req.query.id)
-		# Enable regex search with "?regex=on"
-		if not req.query.regex
-			needle = req.query.id
-		else
-			needle = '$regex': req.query.id
 		needleQuery = $or: []
-		needleQuery.$or.push _id: needle
-		needleQuery.$or.push identifier: needle
-		needleQuery.$or.push identifiers: needle
-		needleQuery.entityType = req.query.from_type || 'publication'
+		if req.query.id
+			req.query.id = _denoise_id(req.query.id)
+			# Enable regex search with "?regex=on"
+			if not req.query.regex
+				needle = req.query.id
+			else
+				needle = '$regex': req.query.id
+			needleQuery.$or.push _id: needle
+			needleQuery.$or.push identifier: needle
+			needleQuery.$or.push identifiers: needle
+		if req.query.title
+			needleQuery.$or.push name: '$regex': req.query.title
+		needleQuery.entityType = req.query.type || 'publication'
 		unless needleQuery.entityType in ['dataset', 'publication']
-			return next "from_type must be dataset or publication"
-		_find_links needleQuery, (err, links) ->
-			if err
-				next err
-			else
+			return next "type must be dataset or publication"
+		limit = parseInt(req.query.limit or 1000)
+		_find_links needleQuery, limit, (err, links) ->
+			return next err if err
+			if Accepts(req).type('text/html')
 				locals.links = links
 				return res.render 'simple-search', locals
+			else
+				return res.send links

 	done()


Added comments

Added comments

--- src/scripts/basicContext.coffee
+++ src/scripts/basicContext.coffee
@@ -106,6 +106,8 @@

 		# Build context
 		$('body').append basicContext._build(data)
+
+		# Block scrolling of site
 		$('body').css	'overflow', 'hidden'

 		# Get info to calculate position
@@ -154,6 +156,10 @@

 	close: ->

+		# Remove context
 		basicContext._dom().parent().remove()
+
+		# Reactivate scrolling of site
 		$('body').css 'overflow', 'scroll'
+
 		return true


Improved DOM wrapper advice.

Improved DOM wrapper advice.

Changed the way we specify and inject the advice.
Added wrappers for level2/html DOM API.
Added wrappers for properties.

--- src/server/jsdom_wrapper.coffee
+++ src/server/jsdom_wrapper.coffee
@@ -26,9 +26,14 @@
             ProcessExternalResources : ['script']#, 'frame', 'iframe'] # TODO:
             MutationEvents : '2.0'
             QuerySelector : false
-        @wrapDOMMethods(@jsdom.dom.level3.html)
+        toWrap = @createWrappedObjectList(@jsdom.dom.level3.html)
+        @wrapDOM(toWrap)
         @addDefaultHandlers(@jsdom.dom.level3.core)
         @setLanguageProcessor(@jsdom.dom.level3.core)
+        @on 'DOMPropertyUpdate', (update) ->
+            console.log "DOMPropertyUpdate"
+        @on 'DOMUpdate', (update) ->
+            console.log "DOMUpdate"

     addDefaultHandlers : (core) ->
         browser = @browser
@@ -50,26 +55,10 @@
                     console.log "Script succeeded"
                 catch e
                     console.log "Script failed: #{e}"
-
-    # Manually testing this with innerHTML for now.
-    # There is also some special case code in the client side
-    wrapDOMGettersSetters : (dom) ->
-        nodes = @nodes
-        propName = @nodes.propName
-        self = this
-        old = dom.Element.prototype.__lookupSetter__ 'innerHTML'
-        dom.Element.prototype.__defineSetter__ 'innerHTML', (html) ->
-            rv = old.call this, html
-            params =
-                targetID : this[propName]
-                rvID : null
-                method : 'innerHTML'
-                args : [html]
-            self.emit 'DOMUpdate', params
-            return rv
-
-    # BIG TODO FIXME XXX : Add wrappers for mutators like nodeValue() etc
-    wrapDOMMethods : (dom) ->
+                    console.log e
+                    console.log e.stack
+
+    wrapDOM : (toWrap) ->
         isDOMNode = (node) ->
             (typeof node.ELEMENT_NODE == 'number') &&
             (node.ELEMENT_NODE == 1)               &&
@@ -79,34 +68,47 @@
         propName = @nodes.propName
         self = this

-        wrapper = (info) ->
-            parent = info[0]
-            methods = info[1]
-            for method in methods
-                do (parent, method) ->
-                    console.log "Wrapping #{method}"
-                    oldStr = method + '__original'
-                    originalMethod = if parent[oldStr] then parent[oldStr] else parent[oldStr] = parent[method]
-                    parent[method] = ->
-                        rv = originalMethod.apply(this, arguments)
-                        if isDOMNode(rv) && rv[propName] == undefined
-                            nodes.add(rv)
-                        params =
-                            targetID : this[propName]
-                            rvID : rv[propName]
-                            method : method
-                            args : nodes.scrub(arguments)
-                        self.emit 'DOMUpdate', params
-                        #printMethodCall(this, method, argumentss)
-                        return rv
-
-        [[dom.Node.prototype, ['insertBefore', 'replaceChild',
-                               'appendChild', 'removeChild']],
-        [dom.Element.prototype, ['setAttribute', 'removeAttribute',
-                                 'setAttributeNode', 'removeAttributeNode']],
-        [dom.Document.prototype, ['createElement', 'createTextNode',
-                                  'createDocumentFragment', 'createComment',
-                                  'createAttribute']]].forEach(wrapper)
+        wrapProperty = (parent, prop) ->
+            console.log "Wrapping #{prop}"
+            originalMethod = parent.__lookupSetter__(prop)
+            if !originalMethod?
+                throw new Error "Missing a setter for #{prop}"
+            parent.__defineSetter__ prop, (value) ->
+                console.log "Setter for #{prop} called."
+                rv = originalMethod.call this, value
+                rvID = if rv? then rv[propName] else null
+                params =
+                    targetID : this[propName]
+                    rvID : rv
+                    prop : prop
+                    args : nodes.scrub([value])
+                self.emit 'DOMPropertyUpdate', params
+                return rv
+
+        wrapMethod = (parent, method) ->
+            console.log "Wrapping #{method}"
+            originalMethod = parent[method]
+            parent[method] = ->
+                console.log "#{method} called"
+                rv = originalMethod.apply(this, arguments)
+                if isDOMNode(rv) && rv[propName] == undefined
+                    nodes.add(rv)
+                rvUd = if rv? then rv[propName] else null
+                params =
+                    targetID : this[propName]
+                    rvID : rv[propName]
+                    method : method
+                    args : nodes.scrub(arguments)
+                # TODO: Change event name to show that this is a method, not prop
+                self.emit 'DOMUpdate', params
+                return rv
+
+        toWrap.forEach (info) ->
+            obj = info.object
+            info.methods?.forEach (method) ->
+                wrapMethod(obj, method)
+            info.properties?.forEach (prop) ->
+                wrapProperty(obj, prop)

     printMethodCall : (node, method, args) ->
         parentName = node.name || node.tagName
@@ -127,4 +129,239 @@
         argStr = argStr.replace(/\s$/, '')
         console.log(parentName + '.' + method + '(' + argStr + ')')

+    createWrappedObjectList : (dom) ->
+        list = []
+        # Level 2 Core:
+        list.push
+            object     : dom.Node.prototype
+            methods    : ['insertBefore', 'replaceChild', 'appendChild',
+                          'removeChild', 'cloneNode']
+            properties : ['nodeValue']
+        list.push
+            object     : dom.Element.prototype
+            methods    : ['setAttribute', 'setAttributeNS', 'removeAttribute',
+                          'removeAttributeNS', 'setAttributeNode',
+                          'setAttributeNodeNS', 'removeAttributeNode',
+                          'removeAttributeNodeNS']
+        list.push
+            object     : dom.Document.prototype
+            methods    : ['createElement', 'createTextNode',
+                          'createDocumentFragment', 'createComment',
+                          'createAttribute', 'createCDATASection', 'importNode',
+                          'createElementNS', 'createAttributeNS']
+        list.push
+            object     : dom.CharacterData.prototype
+            methods    : ['appendData', 'insertData', 'deleteData', 'replaceData']
+            properties : ['data']
+        list.push
+            object     : dom.Attr.prototype
+            properties : ['value']
+        list.push
+            object     : dom.Text.prototype
+            methods    : ['splitText']
+            properties : ['value']
+        # TODO: entity reference, notationnode?
+        # Level 2 HTML
+        list.push
+            object     : dom.HTMLDocument.prototype
+            properties : ['title', 'cookie']
+            # TODO: JSDOM is missing 'body' setter for HTMLDocument
+        list.push
+            object     : dom.HTMLElement.prototype
+            # TODO: will wrapping these cause things to fire twice?  Do these
+            #       manipulate Attr nodes behind the scenes?
+            properties : ['id', 'title', 'lang', 'dir', 'className']
+        list.push
+            object     : dom.HTMLHtmlElement.prototype
+            properties : ['version']
+        list.push
+            object     : dom.HTMLLinkElement.prototype
+            properties : ['disabled', 'charset', 'href', 'hreflang', 'media',
+                          'rel', 'rev', 'target', 'type']
+        list.push
+            object     : dom.HTMLTitleElement.prototype
+            properties : ['text']
+        list.push
+            object     : dom.HTMLMetaElement.prototype
+            properties : ['content', 'httpEquiv', 'name', 'scheme']
+        list.push
+            object     : dom.HTMLBaseElement.prototype
+            properties : ['href', 'target']
+        list.push
+            object     : dom.HTMLIsIndexElement.prototype
+            properties : ['prompt']
+        list.push
+            object     : dom.HTMLStyleElement.prototype
+            properties : ['disabled', 'media', 'type']
+        list.push
+            object     : dom.HTMLBodyElement.prototype
+            properties : ['aLink', 'background', 'bgColor', 'link', 'text',
+                          'vLink']
+        list.push
+            object     : dom.HTMLFormElement.prototype
+            # TODO: This might get wacky with submit/reset
+            methods    : ['submit', 'reset']
+            properties : ['name', 'acceptCharset', 'action', 'enctype',
+                          'method', 'target']
+        list.push
+            object     : dom.HTMLSelectElement.prototype
+            methods    : ['add', 'remove', 'blur', 'focus']
+            properties : ['selectedIndex', 'value', 'disabled', 'multiple',
+                          'name', 'size', 'tabIndex']
+        list.push
+            object     : dom.HTMLOptGroupElement.prototype
+            properties : ['disabled', 'label']
+        list.push
+            object     : dom.HTMLOptionElement.prototype
+            properties : ['disabled', 'label', 'selected',
+                          'value']
+            #TODO: Why doesn't defaultSelected property work?
+        list.push
+            object     : dom.HTMLInputElement.prototype
+            methods    : ['blur', 'focus', 'select', 'click']
+            properties : ['accept',
+                          'accessKey', 'align', 'alt', 'checked', 'disabled',
+                          'maxLength', 'name', 'readOnly', 'size', 'src',
+                          'tabIndex', 'type', 'useMap', 'value']
+            #TODO: JSDOM is missing defaultValue and defaultChecked setters for HTMLInputElement
+        list.push
+            object     : dom.HTMLTextAreaElement.prototype
+            methods    : ['blur', 'focus', 'select']
+            properties : ['accessKey', 'cols', 'disabled',
+                          'name', 'readOnly', 'rows', 'tabIndex', 'value']
+            #TODO: JSDOM is missing defaultValue for HTMLTextAreaElement
+        list.push
+            object     : dom.HTMLButtonElement.prototype
+            properties : ['accessKey', 'disabled', 'name', 'tabIndex', 'value']
+        list.push
+            object     : dom.HTMLLabelElement.prototype
+            properties : ['accessKey', 'htmlFor']
+        list.push
+            object     : dom.HTMLLegendElement.prototype
+            properties : ['accessKey', 'align']
+        list.push
+            object     : dom.HTMLUListElement.prototype
+            properties : ['compact', 'type']
+        list.push
+            object     : dom.HTMLOListElement.prototype
+            properties : ['compact', 'start', 'type']
+        list.push
+            object     : dom.HTMLDListElement.prototype
+            properties : ['compact']
+        list.push
+            object     : dom.HTMLDirectoryElement.prototype
+            properties : ['compact']
+        list.push
+            object     : dom.HTMLMenuElement.prototype
+            properties : ['compact']
+        list.push
+            object     : dom.HTMLLIElement.prototype
+            properties : ['type', 'value']
+        list.push
+            object     : dom.HTMLDivElement.prototype
+            properties : ['align']
+        list.push
+            object     : dom.HTMLParagraphElement.prototype
+            properties : ['align']
+        list.push
+            object     : dom.HTMLHeadingElement.prototype
+            properties : ['align']
+        list.push
+            object     : dom.HTMLQuoteElement.prototype
+            properties : ['cite']
+        list.push
+            object     : dom.HTMLPreElement.prototype
+            properties : ['width']
+        list.push
+            object     : dom.HTMLBRElement.prototype
+            properties : ['clear']
+        list.push
+            object     : dom.HTMLBaseFontElement.prototype
+            properties : ['color', 'face', 'size']
+        list.push
+            object     : dom.HTMLFontElement.prototype
+            properties : ['color', 'face', 'size']
+        list.push
+            object     : dom.HTMLHRElement.prototype
+            properties : ['align', 'noShade', 'size', 'width']
+        list.push
+            object     : dom.HTMLModElement.prototype
+            properties : ['cite', 'dateTime']
+        list.push
+            object     : dom.HTMLAnchorElement.prototype
+            methods    : ['blur', 'focus']
+            properties : ['accessKey', 'charset', 'coords', 'href', 'hreflang',
+                          'name', 'rel', 'rev', 'shape', 'tabIndex', 'target',
+                          'type']
+        list.push
+            object     : dom.HTMLImageElement.prototype
+            properties : ['name', 'align', 'alt', 'border', 'height', 'hspace',
+                          'isMap', 'longDesc', 'src', 'useMap', 'vspace',
+                          'width']
+        list.push
+            object     : dom.HTMLObjectElement.prototype
+            properties : ['code', 'align', 'archive', 'border', 'codeBase',
+                          'codeType', 'data', 'declare', 'height', 'hspace',
+                          'name', 'standby', 'tabIndex', 'type', 'useMap',
+                          'vspace', 'width']
+        list.push
+            object     : dom.HTMLParamElement.prototype
+            properties : ['name', 'type', 'value', 'valueType']
+        list.push
+            object     : dom.HTMLAppletElement.prototype
+            properties : ['align', 'alt', 'archive', 'code', 'codeBase',
+                          'height', 'hspace', 'name', 'object', 'vspace',
+                          'width']
+        list.push
+            object     : dom.HTMLMapElement.prototype
+            properties : ['name']
+        list.push
+            object     : dom.HTMLAreaElement.prototype
+            properties : ['accessKey', 'alt', 'coords', 'href', 'noHref',
+                          'shape', 'tabIndex', 'target']
+        list.push
+            object     : dom.HTMLScriptElement.prototype
+            properties : ['text', 'htmlFor', 'event', 'charset', 'defer',
+                          'src', 'type']
+        list.push
+            object     : dom.HTMLTableElement.prototype
+            methods    : ['createTHead', 'deleteTHead', 'createTFoot',
+                          'deleteTFoot', 'createCaption', 'deleteCaption',
+                          'insertRow', 'deleteRow']
+            properties : ['align', 'bgColor', 'border', 'cellPadding',
+                          'cellSpacing', 'frame', 'rules', 'summary', 'width']
+        list.push
+            object     : dom.HTMLTableCaptionElement.prototype
+            properties : ['align']
+        list.push
+            object     : dom.HTMLTableColElement.prototype
+            properties : ['align', 'chOff', 'span', 'vAlign', 'width']
+        list.push
+            object     : dom.HTMLTableSectionElement.prototype
+            methods    : ['insertRow', 'deleteRow']
+            properties : ['align', 'ch', 'chOff', 'vAlign']
+        list.push
+            object     : dom.HTMLTableRowElement.prototype
+            methods    : ['insertCell', 'deleteCell']
+            properties : ['align', 'bgColor', 'ch', 'chOff', 'vAlign']
+        list.push
+            object     : dom.HTMLTableCellElement.prototype
+            properties : ['abbr', 'align', 'axis', 'bgColor', 'ch', 'chOff',
+                          'colSpan', 'headers', 'height', 'noWrap', 'rowSpan',
+                          'scope', 'vAlign', 'width']
+        list.push
+            object     : dom.HTMLFrameSetElement.prototype
+            properties : ['cols', 'rows']
+        list.push
+            object     : dom.HTMLFrameElement.prototype
+            properties : ['frameBorder', 'longDesc', 'marginHeight',
+                          'marginWidth', 'name', 'noResize', 'scrolling']
+            #NOTE: JSDOM uses setAttribute for setting 'src'.
+        list.push
+            object     : dom.HTMLIFrameElement.prototype
+            properties : ['align', 'frameBorder', 'height', 'longDesc',
+                          'marginHeight', 'marginWidth', 'name', 'scrolling',
+                          'src', 'width']
+        return list
+
 module.exports = JSDOMWrapper


project log -- preserve scroll position better on loading all.

--- src/smc-webapp/project_log.cjsx
+++ src/smc-webapp/project_log.cjsx
@@ -24,6 +24,7 @@
 underscore = require('underscore')
 immutable  = require('immutable')
 lodash = require('lodash')
+{delay} = require('awaiting')

 {React, ReactDOM, rtypes, rclass, Redux, redux}  = require('./app-framework')
 {Col, Row, Button, ButtonGroup, ButtonToolbar, FormControl, FormGroup, InputGroup, Panel, Well} = require('react-bootstrap')
@@ -450,14 +451,12 @@
         if not immutable.is(@props.project_log, next.project_log) or not immutable.is(@props.project_log_all, next.project_log_all) or @props.search != next.search
             delete @_log

-    previous_page: ->
-        if @props.page > 0
-            @reset_cursor()
-            @actions(name).setState(page: @props.page-1)
-
-    next_page: ->
-        @reset_cursor()
-        @actions(name).setState(page: @props.page+1)
+    componentDidUpdate: ->
+        if @_next_cursor_pos? and @_log? and (@props.project_log_all? or @props.project_log?)
+            n = @_next_cursor_pos
+            delete @_next_cursor_pos
+            await delay(1)
+            @window_list_ref.current?.scrollToRow(n)

     get_log: () ->
         if @_log?
@@ -508,19 +507,8 @@
     reset_cursor: ->
         @move_cursor_to(0)

-    render_paging_buttons: (num_pages, cur_page) ->
-        <ButtonGroup>
-            <Button onClick={@previous_page} disabled={@props.page<=0} >
-                <Icon name='angle-double-left' /> Newer
-            </Button>
-            <Button disabled>{"#{cur_page + 1}/#{num_pages}"}</Button>
-            <Button onClick={@next_page} disabled={@props.page>=num_pages-1} >
-                Older <Icon name='angle-double-right' />
-            </Button>
-        </ButtonGroup>
-
     load_all: ->
-        @reset_cursor()
+        @_next_cursor_pos = @get_log().size - 1
         delete @_last_project_log
         delete @_last_user_map
         delete @_loading_table
@@ -560,7 +548,7 @@
         <WindowedList
             ref = {@window_list_ref}
             overscan_row_count = {10}
-            estimated_row_size={20}
+            estimated_row_size={22}
             row_count={@get_log().size + 1}
             row_renderer = {(x) => @row_renderer(x.index)}
             row_key = {@row_key}


implements budget items calculation

implements budget items calculation

--- app/assets/javascripts/backend/budget.js.coffee
+++ app/assets/javascripts/backend/budget.js.coffee
@@ -17,6 +17,18 @@
             link_to_add_budget_item.click()
             new_item = link_to_add_budget_item.closest("td").prev()
             new_item.attr('data-support-destroy', $(this).attr('id'))
+    # calculations
+    # item calculation
+    $("tr.budget_nested_fields").each ->
+      $(this).find("[data-budget-item-value]").each (index) ->
+        quantity = $(this).parent().find("input").val()
+        unit_amount         = $(this).closest("tr.budget_nested_fields").find("input[type='number'][id$='unit_amount']").val()
+        computation_method  = $(this).closest("tr.budget_nested_fields").find("select[id$='computation_method']").val()
+        working_indicator_value = 1
+        if computation_method == 'per_working_unit'
+          working_indicator_value = $("[data-working-indicator-value]")[index]
+          working_indicator_value = parseFloat($(working_indicator_value).text())
+        $(this).text(quantity * unit_amount * working_indicator_value)
     #total per budget
     $("tr.budget_nested_fields").each ->
       sum = 0.0


Prevent accidentally zeroing last_src in image_questions

[#60268936] Retaking a snapshot copies `current_src` into `last_src` twice due to different method calls. After the first time, `current_src` is set to ''. This meant the second time, last_src became ''. That prevented the snapshot retake from being properly canceled. This change checks to see if `current_src` is an empty string before allowing it to be copied into `last_src`.

https://www.pivotaltracker.com/story/show/60268936

--- app/assets/javascripts/image_question.js.coffee
+++ app/assets/javascripts/image_question.js.coffee
@@ -191,7 +191,7 @@
     @$content.dialog("open");

   set_image_source: (src) ->
-    @last_src = @current_src
+    @last_src = @current_src unless @current_src == ""
     @current_src = src
     @$current_src_field.val(src)
     @update_display()


unicode euro sign

unicode euro sign

--- app/assets/javascripts/lib/models/metric.coffee
+++ app/assets/javascripts/lib/models/metric.coffee
@@ -59,7 +59,7 @@
       when 'MW'
         return "#{value}#{@scaling_in_words(pow, 'watt')}"
       when 'euro'
-        return "€#{value}"
+        return "€#{value}"
       when 'man_years'
         return "#{@round_number x, 0}#{@scaling_in_words(0, 'man_years')}"
       when 'FTE'


Dropped pushState to use #, as it wasn't falling back gracefully for IE

--- app/assets/javascripts/species/router.js.coffee
+++ app/assets/javascripts/species/router.js.coffee
@@ -1,7 +1,3 @@
-Species.Router.reopen
-  location: 'history'
-  rootURL: '/species'
-
 Species.Router.map (match) ->
   @route 'search', { path: 'search/:params' }
   @resource 'taxon_concept', { path: 'taxon_concepts/:taxon_concept_id' }, () ->


AS#101967871059519, Bundle NavBar components to be used by tc-site

-- Updated CDN prefix.

--- navbar.webpack.config.coffee
+++ navbar.webpack.config.coffee
@@ -12,7 +12,7 @@

 # exports javascript as library
 config.output.filename = '[name].js';
-config.output.publicPath = 'http://local.topcoder.com/mf/js/app/header/partials/'
+config.output.publicPath = 'http://components.topcoder-dev.com/'
 config.output.library = '[name]';
 config.output.libraryTarget = 'var'


Add WIP Backbone.Model tests

Add WIP Backbone.Model tests

--- test/elvis-backbone-test.coffee
+++ test/elvis-backbone-test.coffee
@@ -62,3 +62,24 @@
     element.value = 'quux'
     element.dispatchEvent(createEvent('change'))
     expect(model.get('foo')).to.equal('quux')
+#    model.set(foo: 'bar')
+#    expect(element.value).to.equal('bar')
+
+#   it 'can handle short-hand input binding', ->
+#     model = new Backbone.Model(foo: 'bar')
+#     element = el('input', model.bindTo('foo'))
+#     expect(element.value).to.equal('bar')
+#     element.value = 'quux'
+#     element.dispatchEvent(createEvent('change'))
+#     expect(model.get('foo')).to.equal('quux')
+#     model.set(foo: 'bar')
+#     expect(element.value).to.equal('bar')
+
+#  it 'can transform value on input change', ->
+#    model = new Backbone.Model()
+#    element = el('input', type: 'text',
+#    value: model.bindTo('firstName lastName', parseName))
+#    element.value = 'John Doe'
+#    element.dispatchEvent(createEvent('change'))
+#    expect(model.get('firstName')).to.equal('John')
+#    expect(model.get('lastName')).to.equal('Doe')


Scan split save, edit with ctrl-s, ctrl-e

--- scanblog/static/js/moderation/scans.coffee
+++ scanblog/static/js/moderation/scans.coffee
@@ -147,7 +147,7 @@
         @split = new btb.ScanSplit {scan: id: parseInt(options.scanId)}
         @split.fetch
             success: @initSplit
-        $(window).keyup @keyUp
+        $(window).keydown @keyDown
         @imgScale = 1
         this

@@ -158,12 +158,22 @@
         @checkFinished()
         @selectPage(0)

-    keyUp: (event) =>
+    keyDown: (event) =>
         # Don't capture events if we're in an input.
         if $("input:focus, textarea:focus").length > 0
             return
-        return if event.ctrlKey or event.metaKey
-        return if event.shiftKey and event.keyCode != 61 # let "+" through
+        # Let meta-e and meta-s through; exclude other meta-modifier events.
+        if event.metaKey or event.ctrlKey or event.altKey
+          switch event.keyCode
+            when 83 # ctrl-s
+              event.preventDefault()
+              @save()
+            when 69 # ctrl-e
+              event.preventDefault()
+              @switchToDocumentView()
+          return
+        # Let shift-= ("+") through, exclude oher shift modifiers.
+        return if event.shiftKey and event.keyCode != 61
         switch event.keyCode
             when 32,78,39  then @nextPage(event) # spacebar, n, right
             when 8,80,37 then @prevPage(event) # backspace, p, left


Tests: Disable paid account tests.

Tests: Disable paid account tests.

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -82,9 +82,9 @@
     { name : 'environments vm' }
   ]

-  [
-    { name : 'environments paidaccount' }
-  ]
+  # [
+  #   { name : 'environments paidaccount' }
+  # ]

   [
     { name : 'environments domain' }


feat(web): сheck messages to an empty string before send;

--- apps/actor-web/app/services/actorService.coffee
+++ apps/actor-web/app/services/actorService.coffee
@@ -107,7 +107,6 @@
           @$rootScope.$broadcast 'actorAuthCode'
     , (tag, message, canTryAgain, state) ->
       console.log '[AW]ActorService requestSms: error: state:', state
-
   sendCode: (code) ->
     console.log '[AW]ActorService sendCode'
     @messenger.sendCode code, (state) =>
@@ -121,12 +120,14 @@
       console.log '[AW]ActorService sendCode: error: state:', state

-
   sendMessage: (peer, message) ->
     console.log '[AW]ActorService sendMessage'
-    # console.log '[AW]ActorService sendMessage: message:', message
+    console.log '[AW]ActorService sendMessage: message:', message
+    message = message.replace /^\s+|\s+$/g, ''
+    if message.length > 0
+      console.log '[AW]ActorService sendMessage: message:', message.length
     # console.log '[AW]ActorService sendMessage: peer:', peer
-    @messenger.sendMessage peer, message
+      @messenger.sendMessage peer, message

   # Events


Alter the slider behavior such that the throttle is works as desired and there is a positive feedback on where the slider is in the associated input.

--- bokehjs/src/coffee/models/widgets/slider.coffee
+++ bokehjs/src/coffee/models/widgets/slider.coffee
@@ -17,6 +17,14 @@
     @$el.empty()
     html = @template(@model.attributes)
     @$el.html(html)
+    @callbackWrapper = null
+    if @mget('callback_policy') == 'continious'
+      @callbackWrapper = () ->
+        @mget('callback')?.execute(@model)
+    if @mget('callback_policy') == 'throttle' and @mget('callback')
+      @callbackWrapper = _.throttle(() ->
+        @mget('callback')?.execute(@model)
+      , @mget('callback_throttle'))
     @render()

   render: () ->
@@ -30,20 +38,11 @@
       value: @mget('value'),
       min: min,
       max: max,
-      step: step
-      start: @slidestart
-      stop: @slidestop
+      step: step,
+      start: @slidestart,
+      stop: @slidestop,
+      slide: @slide
     }
-    switch @mget('callback_policy')
-      when 'continious'
-        opts.slide = @slide
-      when 'throttle'
-        opts.slide = _.throttle(@slide, @mget('callback_throttle'))
-      when 'mouseup'
-        opts.stop = @slide
-      else
-        opts.stop = @slide
-        logger.debug("slider render: ERROR: do not know how to handle the callback policy")
     @$('.slider').slider(opts)
     @$( "##{ @mget('id') }" ).val( @$('.slider').slider('value') )
     return @
@@ -52,14 +51,16 @@
     @$( "##{ @mget('id') }" ).css('color', '#ffceab')

   slidestop: (event, ui) =>
-      @$( "##{ @mget('id') }" ).css('color', '#f6931f')
+    @$( "##{ @mget('id') }" ).css('color', '#f6931f')
+    if @mget('callback_policy') == 'mouseup' or @mget('callback_policy') == 'throttle'
+      @mget('callback')?.execute(@model)

   slide: (event, ui) =>
     value = ui.value
     logger.debug("slide value = #{value}")
     @$( "##{ @mget('id') }" ).val( ui.value )
     @mset('value', value)
-    @mget('callback')?.execute(@model)
+    if @callbackWrapper then @callbackWrapper()

 class Slider extends InputWidget.Model
   type: "Slider"


remove dead link

remove dead link

--- readme-src.coffee
+++ readme-src.coffee
@@ -17,7 +17,6 @@

 - [Homepage](http://baconjs.github.io/)
 - [CoffeeScript source](https://github.com/baconjs/bacon.js/tree/master/src)
-- [assembled CoffeeScript source](https://github.com/baconjs/bacon.js/blob/master/dist/Bacon.coffee)
 - [Generated javascript](https://github.com/baconjs/bacon.js/blob/master/dist/)
 - [Specs](https://github.com/baconjs/bacon.js/blob/master/spec/BaconSpec.coffee)
 - [Examples](https://github.com/baconjs/bacon.js/blob/master/examples/examples.html)


Made it possible to override the offsetParent of a tooltip.

--- core/app/backbone/factlink/tooltip_mixin.coffee
+++ core/app/backbone/factlink/tooltip_mixin.coffee
@@ -11,8 +11,12 @@

     view = new HelptextPopoverView(model: new Backbone.Model(title: title, text: text), side: side)

-    @_tooltips[selector] = new Backbone.Factlink.PositionedRegion _.extend(options, side: side)
-    @_tooltips[selector].show view
+    positionedRegion = new Backbone.Factlink.PositionedRegion _.extend({}, options, side: side)
+    positionedRegion.show view
+
+    offsetParent = options.offsetParent || @$el
+
+    @_tooltips[selector] = { positionedRegion, offsetParent, view }

     unless @isClosed
       @tooltipBindAll()
@@ -21,24 +25,24 @@
     @on 'close', @tooltipResetAll

   tooltipRemove: (selector) ->
-    tooltip = @tooltip(selector)
-    tooltip.reset()
+    tooltip = @_tooltips[selector]
+    tooltip.positionedRegion.reset()
     delete @_tooltips[selector]

   tooltipBindAll: ->
-    for selector, tooltipHandler of @_tooltips
+    for selector, tooltip of @_tooltips
       $bindEl = @$(selector).first()
-      tooltipHandler.bindToElement($bindEl, @$el)
+      tooltip.positionedRegion.bindToElement($bindEl, tooltip.offsetParent)

     @tooltipUpdateAll()

   tooltipUpdateAll: ->
-    for selector, tooltipHandler of @_tooltips
-      tooltipHandler.updatePosition()
+    for selector, tooltip of @_tooltips
+      tooltip.positionedRegion.updatePosition()

   tooltipResetAll: ->
-    for selector, tooltipHandler of @_tooltips
-      tooltipHandler.reset()
+    for selector, tooltip of @_tooltips
+      tooltip.positionedRegion.reset()

   tooltip: (selector) ->
     @_tooltips?[selector]


Fixed showing of "show more" link on profile when navigating to this page directly.

--- core/app/backbone/views/top_channel_view.coffee
+++ core/app/backbone/views/top_channel_view.coffee
@@ -24,5 +24,5 @@
       @$(".no-channels").hide()
       @$(".top-channels").show()

-  onRender: ->
+  onRenderCollection: ->
     @$(".top-channels .show-more").hide() if @collection.length < 6


kill stale projects after 6 hours now; too many users.

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -5291,7 +5291,8 @@
     winston.debug("closing stale projects...")
     storage.close_stale_projects
         dry_run : false
-        ttl     : 60*60*12  # every 12 hours for now.
+        ttl     : 60*60*6  # every 6 hours for now.
+        limit   : 40
         cb : (err) -> winston.debug("finished closing stale projects (err=#{err})")

 #############################################


Remove project specific route in searchable mixin

--- app/assets/javascripts/mixins/searchable.coffee
+++ app/assets/javascripts/mixins/searchable.coffee
@@ -44,7 +44,7 @@
       @getController('Tab').set('search', Ember.copy(@get('defaultSearchFields'), true))

       if @constructor.toString().match("#{@baseRouteName.capitalize()}SearchController")
-        @transitionToRoute('projects.tab', 'online')
+        @transitionToRoute("#{@baseRouteName}")

   getController: (name)->
     Dashboard.__container__.lookup("controller:#{@baseRouteName}#{name}")


Fixed bug #706 and #679

Fixed bug #706 and #679

--- app/coffee/modules/resources/userstories.coffee
+++ app/coffee/modules/resources/userstories.coffee
@@ -33,7 +33,7 @@
         return $repo.queryOne("userstories", usId, params)

     service.listUnassigned = (projectId, filters) ->
-        params = {"project": projectId, "milestone": null}
+        params = {"project": projectId, "milestone": "null"}
         params = _.extend({}, params, filters or {})
         service.storeQueryParams(projectId, params)
         return $repo.queryMany("userstories", params)


Restoring global version flag

Restoring global version flag

--- bin/run.coffee
+++ bin/run.coffee
@@ -15,9 +15,9 @@
   RallyAppBuilder.build {templates}, errorHandler

 init = (args) ->
-  {name, version, server, templates} = args
+  {name, sdk, server, templates} = args
   name = args._[1] || name
-  sdk_version = args._[2] || version
+  sdk_version = args._[2] || sdk
   server = args._[3] || server
   console.log 'Creating a new App.'
   RallyAppBuilder.init(
@@ -63,8 +63,8 @@
     'init',
     'Creates a new Rally App project template.',
     name: {alias: 'n', describe: 'The name of the app'}
-    version: {alias: 'v', describe: 'The SDK version to target', default: '2.0'}
-    server: {alias: 's', describe: 'The server to target'}
+    sdk: {alias: 's', describe: 'The SDK version to target', default: '2.0'}
+    server: {alias: 'r', describe: 'The server to target'}
     templates: {alias: 't', describe: 'The path containing custom html output templates (advanced)'}
     , init
   )
@@ -94,6 +94,6 @@
     port: {alias: 'p', default: 1337, describe: 'The port on which to start the local http server'}
     , run
   )
-  .help()
-  .version()
+  .help().alias('h', 'help')
+  .version().alias('v', 'version')
   .argv


only encode query string if no hash

only encode query string if no hash

--- app/app.coffee
+++ app/app.coffee
@@ -277,7 +277,11 @@

   if location.search isnt ''
     uri = URI location.href
-    uri.hash("#{uri.hash()}?#{encodeURIComponent(uri.query())}")
+    if uri.hash() == ''
+      query = encodeURIComponent(uri.query())
+    else
+      query = uri.query()
+    uri.hash("#{uri.hash()}?#{query}")
     uri.query(null)
     window.history.replaceState null, document.title, uri.toString()


aliased rememberToken

aliased rememberToken

--- addon/singletons/user-session.coffee
+++ addon/singletons/user-session.coffee
@@ -20,6 +20,7 @@
   isLoggedIn: ifPresent "account.id"
   namespace: alias "account.namespace"
   host: alias "account.host"
+  simwmsAccountSession: alias "rememberToken"
   hasErrors: alias "errors.hasErrors"
   errors: Errors.create()
   p: Ember.computed -> new Ember.RSVP.Promise (r) => r @


Add TODO for database migrations

Add TODO for database migrations

--- bin/www.coffee
+++ bin/www.coffee
@@ -42,5 +42,6 @@

 # Listen on provided port, on all network interfaces.

+# TODO: replace sync with migration files
 models.sequelize.sync().then ->
   server.listen config.port


no rotation in network mode

no rotation in network mode

--- app/vis.coffee
+++ app/vis.coffee
@@ -389,7 +389,7 @@

     _addNetwork: (network) =>
         radius = 700
-        rotation = Math.PI / 2.2
+        rotation = 0 # Math.PI / 2.2

         network_object = new THREE.Object3D()
         network_object.scale.multiplyScalar(@scaleText)
@@ -520,10 +520,8 @@
             .map positionLines

         chainObject = new THREE.Object3D()
-        # chainObject.add.apply(chainObject, lineObjects)
         chainObject.scale.multiplyScalar(@scaleText)

-        # NOTE: Adding all objects at once – not efficient!
         @scene.add(chainObject)

         ########################
@@ -545,8 +543,6 @@
                 z = bbox.center().z + @getZoomDistanceFromBox bbox, 1.3
                 @panCameraToPosition3 new THREE.Vector3(x,y,z), 1000, true
                 @fadeToArray(1, 1000) curr.children
-            # .then =>
-            #     @fadeToArray(1, 1000) curr.children

         first = Promise.resolve()
         lineObjects.reduce reducer, first


child positioning for lines mode

child positioning for lines mode

--- app/vis.coffee
+++ app/vis.coffee
@@ -28,7 +28,7 @@

         document.body.appendChild( @renderer.domElement )

-        @camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 )
+        @camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 )
         _x = 1
         _y = 2
         @camera.position.z = CAMERA_Z
@@ -326,10 +326,10 @@
         .domain([0, 360])
         .range([0, Math.PI * 2])

-    zoomToBoundingBox: (box, duration) ->
+    zoomToBoundingBoxWidth: (box, duration) ->
         width = Math.abs(box.min.x - box.max.x)

-        buffer = 0.3
+        distance_scale = 1.2

         v_fov = _radianScale @camera.fov

@@ -337,11 +337,24 @@
         h_fov = Math.atan( Math.tan(v_fov/2) * @camera.aspect )

         # See: stackoverflow.com/questions/2866350/move-camera-to-fit-3d-scene
-        distance = (width / 2) / Math.tan(h_fov) + buffer
+        distance = (width / 2) / Math.tan(h_fov) * distance_scale

         target = new THREE.Vector3(0, 0, - distance)

         @zoomCameraToPosition target, duration
+
+    LINE_SPACING = 40
+
+    alignToNode = (parent) ->
+        (child) ->
+            word = parent.word
+            regex = new RegExp word, "i"
+            offset = [ parent, child ]
+                .map (_) ->
+                    idx = _.line.search regex
+                    _._text_object.children[idx].position.x
+                .reduce (a, b) -> a - b
+            child._text_object.position.x += offset

     addLines: (lines) =>
         lines_object = new THREE.Object3D()
@@ -351,18 +364,50 @@

         root = linesToTree lines

-        root._text_object ?= @getLineObject(root.line)
-        root._text_object.children.forEach (mesh) ->
-            mesh.material.uniforms.opacity.value = 1
-        lines_object.add root._text_object
-
-        bbox = new THREE.BoundingBoxHelper( root._text_object, 0xff0000 )
-        do bbox.update
-
-        @scene.add bbox
-
-        @panCameraToPosition bbox.box.center(), 1000
-            .then => @zoomToBoundingBox(bbox.box, 1000)
+        traverse = (parent) =>
+            parent._text_object ?= @getLineObject(parent.line)
+            parent._text_object.children.forEach (mesh) ->
+                mesh.material.uniforms.opacity.value = 1
+            lines_object.add parent._text_object
+
+            return if ! parent.children?
+
+            parent.children.forEach (child) =>
+                child._text_object ?= @getLineObject(child.line)
+                child._text_object.children.forEach (mesh) ->
+                    mesh.material.uniforms.opacity.value = 1
+                lines_object.add child._text_object
+
+            positions_array = parent.children.concat parent
+            d3.shuffle positions_array
+            parent_index = positions_array.indexOf parent
+
+            positions_array.forEach (node, index) =>
+                obj = node._text_object
+                offset_from_parent = index - parent_index
+                height = obj._layout.height
+                obj.position.y = offset_from_parent * (height + LINE_SPACING)
+
+            parent.children.forEach alignToNode(parent)
+
+            # debugger
+
+            # debugger
+
+        traverse root
+
+        # root._text_object ?= @getLineObject(root.line)
+        # root._text_object.children.forEach (mesh) ->
+        #     mesh.material.uniforms.opacity.value = 1
+        # lines_object.add root._text_object
+        #
+        # bbox = new THREE.BoundingBoxHelper( root._text_object, 0xff0000 )
+        # do bbox.update
+        #
+        # @scene.add bbox
+        #
+        # @panCameraToPosition bbox.box.center(), 1000
+        #     .then => @zoomToBoundingBoxWidth(bbox.box, 1000)

     animate: =>


more warnings, while still falling to fatal errors.

--- wrapper/actions/index.coffee
+++ wrapper/actions/index.coffee
@@ -55,6 +55,8 @@
 			for k in [0..tds.length-1] # hours in day
 				isHavingClassNow = -1==['#C6DCFC', '#A1C6FC'].indexOf(tds.eq(k).attr('bgcolor'))
 				if isHavingClassNow
+					if not classesByTimeTable[i]
+						log.warning("classesByTimeTable[#{i}] not set", classesByTimeTable)
 					classesByTimeTable[i].hour = (new Date((startDate+30*k)*60000)).toGMTString().substr(17, 5);
 					break

@@ -67,7 +69,6 @@
 			if not details
 				log.warning("Could not match tutorInfo details", ti[0], ti)
 				#console.log c
-				details={}
 			classNumber = parseInt(details[3])
 			classes[classNumber]= {
 				class: classNumber


refactor: extract scroll sync and enml renderer to separate ng services

--- app/assets/javascripts/app-ng.coffee
+++ app/assets/javascripts/app-ng.coffee
@@ -1,8 +1,8 @@
 angular.module('markever', ['ui.ace'])

 angular.module('markever')
-.controller "EditorController", ['$scope','$window', '$http', '$sce',
-($scope, $window, $http, $sce) ->
+.controller "EditorController", ['$scope','$window', '$http', '$sce', 'enmlRenderer', 'scrollSyncor'
+($scope, $window, $http, $sce, enmlRenderer, scrollSyncor) ->
     # ------------------------------------------------------------------------------------------------------------------
     # define frequently-used jquery elements
     # ------------------------------------------------------------------------------------------------------------------
@@ -25,34 +25,7 @@
     # refer: http://stackoverflow.com/questions/18952623/synchronized-scrolling-using-jquery
     # ------------------------------------------------------------------------------------------------------------------
     $scope.aceLoaded = (editor) =>
-        editor.setShowPrintMargin(false)
-        # sync scroll: md -> html
-        editor_scroll_handler = (scroll) =>
-            percentage = scroll / (editor.session.getScreenLength() * \
-                editor.renderer.lineHeight - ($(editor.renderer.getContainerElement()).height()))
-            if percentage > 1 or percentage < 0 then return
-            percentage = Math.floor(percentage * 1000) / 1000;
-            # detach other's scroll handler first
-            $html_div.off('scroll', html_scroll_handler)
-            md_html_div = $html_div.get(0)
-            md_html_div.scrollTop = percentage * (md_html_div.scrollHeight - md_html_div.offsetHeight)
-            # re-attach other's scroll handler at the end, with some delay
-            setTimeout (-> $html_div.scroll(html_scroll_handler)), 10
-        editor.session.on('changeScrollTop', editor_scroll_handler)
-
-        # sync scroll: html -> md
-        html_scroll_handler = (e) =>
-            md_html_div = $html_div.get(0)
-            percentage = md_html_div.scrollTop / (md_html_div.scrollHeight - md_html_div.offsetHeight)
-            if percentage > 1 or percentage < 0 then return
-            percentage = Math.floor(percentage * 1000) / 1000;
-            # detach other's scroll handler first
-            editor.getSession().removeListener('changeScrollTop', editor_scroll_handler);
-            editor.session.setScrollTop((editor.session.getScreenLength() * \
-                editor.renderer.lineHeight - $(editor.renderer.getContainerElement()).height()) * percentage)
-            # re-attach other's scroll handler at the end, with some delay
-            setTimeout (-> editor.session.on('changeScrollTop', editor_scroll_handler)), 20
-        $html_div.scroll(html_scroll_handler)
+        scrollSyncor.syncScroll(editor, $html_div)

     $scope.aceChanged = (e) ->
         $html_div = $("#md_html_div")
@@ -65,15 +38,39 @@
     # ------------------------------------------------------------------------------------------------------------------
     vm.save_note = ->
         # fill the hidden html div
-        $html_div_hidden = $("#md_html_div_hidden")
+        html_div_hidden = $("#md_html_div_hidden")
+        final_note_xml = enmlRenderer.getEnmlFromElement(html_div_hidden, vm.markdown)
+        # set title to content of first H1 tag
+        title = "New Note - Markever"
+        if html_div_hidden.find("h1").size() > 0
+            title = html_div_hidden.find("h1").text()
+        else if html_div_hidden.find("h2").size() > 0
+            title = html_div_hidden.find("h2").text()
+        else if html_div_hidden.find("h3").size() > 0
+            title = html_div_hidden.find("h3").text()
+        else if html_div_hidden.find("p").size() > 0
+            title = html_div_hidden.find("p").text()
+        # invoke the api
+        $.post("/api/v1/notes", {title: title, contentXmlStr: final_note_xml})
+            .done (data) ->
+                alert("create note succeed: \n" + JSON.stringify(data))
+            .fail (data) ->
+                alert("create note failed: \n" + JSON.stringify(data))
+]

+
+# ----------------------------------------------------------------------------------------------------------------------
+# Service: enmlRenderer
+# ----------------------------------------------------------------------------------------------------------------------
+angular.module('markever').factory 'enmlRenderer', ->
+    getEnmlFromElement = (jq_element, markdown) ->
         # highlight code & style Latex
-        $html_div_hidden.find('pre code').each (i, block) ->
+        jq_element.find('pre code').each (i, block) ->
             hljs.highlightBlock(block)
-        MathJax.Hub.Queue(["Typeset", MathJax.Hub, $html_div_hidden.get(0)])
+        MathJax.Hub.Queue(["Typeset", MathJax.Hub, jq_element.get(0)])

         # remove all script tags
-        $html_div_hidden.find("script").remove()
+        jq_element.find("script").remove()

         # add inline style
         # refer: https://github.com/Karl33to/jquery.inlineStyler
@@ -104,10 +101,10 @@
                 'border-matters' : ['HR', 'BLOCKQUOTE', 'SPAN', 'PRE', 'CODE'],
             }
         }
-        $html_div_hidden.inlineStyler(inline_styler_option)
+        jq_element.inlineStyler(inline_styler_option)

         # set href to start with "http" is no protocol assigned
-        $html_div_hidden.find("a").attr "href", (i, href) ->
+        jq_element.find("a").attr "href", (i, href) ->
             if not href.toLowerCase().match("(^http)|(^https)|(^file)")
                 return "http://" + href
             else
@@ -125,14 +122,14 @@
             allowedAttributes: [["href", ["a"]], ["style"]],
             removeAttrs: ["id", "class", "onclick", "ondblclick", "accesskey", "data", "dynsrc", "tabindex",],
         }
-        cleaned_html = $.htmlClean($html_div_hidden.html(), html_clean_option);
+        cleaned_html = $.htmlClean(jq_element.html(), html_clean_option);
         # FIXME hack for strange class attr not removed
         cleaned_html = cleaned_html.replace(/class="[^"]*"/g, "")

         # embbed raw markdown content into the html
         cleaned_html = cleaned_html +
             "" +
-            $('
').text(vm.markdown).html() +
+            $('
').text(markdown).html() +
             ""

         # add XML header & wrap with <en-note></en-note>
@@ -143,22 +140,48 @@
                          "</en-note>"
         # FIXME debugging info
         console.log(final_note_xml)
+        return final_note_xml

-        # set title to content of first H1 tag
-        title = "New Note - Markever"
-        if $html_div_hidden.find("h1").size() > 0
-            title = $html_div_hidden.find("h1").text()
-        else if $html_div_hidden.find("h2").size() > 0
-            title = $html_div_hidden.find("h2").text()
-        else if $html_div_hidden.find("h3").size() > 0
-            title = $html_div_hidden.find("h3").text()
-        else if $html_div_hidden.find("p").size() > 0
-            title = $html_div_hidden.find("p").text()
+    return {
+        getEnmlFromElement : getEnmlFromElement
+    }

-        # invoke the api
-        $.post("/api/v1/notes", {title: title, contentXmlStr: final_note_xml})
-            .done (data) ->
-                alert("create note succeed: \n" + JSON.stringify(data))
-            .fail (data) ->
-                alert("create note failed: \n" + JSON.stringify(data))
-]
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Service: enmlRenderer
+# ----------------------------------------------------------------------------------------------------------------------
+angular.module('markever').factory 'scrollSyncor', ->
+    syncScroll = (ace_editor, jq_div) ->
+        ace_editor.setShowPrintMargin(false)
+        # sync scroll: md -> html
+        editor_scroll_handler = (scroll) =>
+            percentage = scroll / (ace_editor.session.getScreenLength() * \
+                ace_editor.renderer.lineHeight - ($(ace_editor.renderer.getContainerElement()).height()))
+            if percentage > 1 or percentage < 0 then return
+            percentage = Math.floor(percentage * 1000) / 1000;
+            # detach other's scroll handler first
+            jq_div.off('scroll', html_scroll_handler)
+            md_html_div = jq_div.get(0)
+            md_html_div.scrollTop = percentage * (md_html_div.scrollHeight - md_html_div.offsetHeight)
+            # re-attach other's scroll handler at the end, with some delay
+            setTimeout (-> jq_div.scroll(html_scroll_handler)), 10
+        ace_editor.session.on('changeScrollTop', editor_scroll_handler)
+
+        # sync scroll: html -> md
+        html_scroll_handler = (e) =>
+            md_html_div = jq_div.get(0)
+            percentage = md_html_div.scrollTop / (md_html_div.scrollHeight - md_html_div.offsetHeight)
+            if percentage > 1 or percentage < 0 then return
+            percentage = Math.floor(percentage * 1000) / 1000;
+            # detach other's scroll handler first
+            ace_editor.getSession().removeListener('changeScrollTop', editor_scroll_handler);
+            ace_editor.session.setScrollTop((ace_editor.session.getScreenLength() * \
+                ace_editor.renderer.lineHeight - $(ace_editor.renderer.getContainerElement()).height()) * percentage)
+            # re-attach other's scroll handler at the end, with some delay
+            setTimeout (-> ace_editor.session.on('changeScrollTop', editor_scroll_handler)), 20
+        jq_div.scroll(html_scroll_handler)
+
+    return {
+        syncScroll : syncScroll
+    }
+


also see running stuff

also see running stuff

--- app/assets/javascripts/deploy.coffee
+++ app/assets/javascripts/deploy.coffee
@@ -8,6 +8,6 @@
         if data.chunks.length > 0
           data.chunks.map (chunk) ->
             $("code").append(chunk.text)
-        if data.deploy.status != 'pending'
+        if data.deploy.status != 'pending' && data.deploy.status != 'running'
           clearTimeout(tid)
     tid = setInterval func, 500


Reduce pan rate

Reduce pan rate

--- app/assets/javascripts/mix.js.coffee
+++ app/assets/javascripts/mix.js.coffee
@@ -141,7 +141,7 @@
     loaderColor: 'purple'
     cursorColor: 'navy'
     minimap: true
-    minPxPerSec: 100
+    minPxPerSec: 50
   wavesurfer.initMinimap
     height: 30
     waveColor: '#ddd'


Fix guards not quite working as expected

--- app/assets/javascripts/orders.coffee
+++ app/assets/javascripts/orders.coffee
@@ -60,14 +60,15 @@
 populateQuantity = (selected, element) ->
   if selected.val() == ""
     element.attr("placeholder", "Select an Item...")
+    element.removeAttr("data-guard data-guard-int-min data-guard-int-max");
   else
     available_quantity = selected.data("current-quantity") - selected.data("requested-quantity")
     element.attr("placeholder", "#{available_quantity} available")
+    element.attr("data-guard", "required int")
+    element.attr("data-guard-int-min", "1").data("guard-int-min", 1)
+    element.attr("data-guard-int-max", available_quantity).data("guard-int-max", available_quantity)

-  element.val("")
-  element.attr("data-guard", "required int")
-  element.attr("data-guard-int-min", "1")
-  element.attr("data-guard-int-max", available_quantity)
+  element.val("").clearErrors()

 addNewOrderRow = ->
   currentNumRows = $("#new-order-table tbody").find("tr").length


not search similar name with current user id

--- app/assets/javascripts/people.coffee
+++ app/assets/javascripts/people.coffee
@@ -19,7 +19,7 @@

   $('.js-person-name').on 'keyup', ->
     if $(this).val().length > 1
-      $.ajax(url: "/people.json?q[name_cont]=#{$(this).val()}")
+      $.ajax(url: "/people.json?q[name_cont]=#{$(this).val()}&q[id_not_eq]=#{$(this).data('current-id')}")
         .done (response)->
           $('.js-similar-people-container').html('').append("Found #{response.length} similar people")
           if response.length < 10


Uses $.get for styles loader

Uses $.get for styles loader

--- app/assets/javascripts/styles.coffee
+++ app/assets/javascripts/styles.coffee
@@ -1,57 +1,56 @@
-#
+#
 # styles.coffee: Load CSS styles manually to be sure when they're loaded.
-#
+#
 # Copyright 2013 Canopy Canopy Canopy, Inc.
-#
+#

 class window.Styles
   urls: []

   # Break open HTML snippet and store href attributes of contained <link>s.
-  #
+  #
   loadLater: (snippet) ->
     $(snippet).filter('link').each (index, link) =>
       @urls.push $(link).attr('href')

   # Load all CSS files, then execute callback.
-  #
+  #
   # For now, load synchronously, to make sure CSS is loaded in the correct
   # order. Async loading is a possible optimization, but would have to do
   # more legwork to keep track of all requests.
-  #
+  #
   doLoad: (callback) ->
     styles = $('<style type="text/css"/>').appendTo('body')
     $.each @urls, (index, url) =>
-      $.ajax url,
-        async: false
-        success: (data, textStatus, jqXHR) =>
-          styles.append data
+      $.get(url)
+        .success (data) -> styles.append data
+        .error (response) -> console.error response
     callback()

   # Util: CSS formatter
-  #
+  #
   formatPercentages: (selector, values) ->
     declarations = (_.map values, (value, key) => "#{key}: #{@formatPercentageValue(value)}").join(';')
     return "#{selector} { #{declarations} }"

-  #
-  #
+  #
+  #
   formatPercentageValues: (values) ->
-    _.object _(values).map (percent, key) => [key, @formatPercentageValue percent]
+    _.object _(values).map (percent, key) => [key, @formatPercentageValue percent]

   # Util.
-  #
+  #
   formatPercentageValue: (value) ->
     "#{value * 100}%"

   # Util: CSS formatter
-  #
+  #
   formatPixels: (selector, values) ->
     declarations = (_.map values, (value, key) -> "#{key}: #{value}px").join(';')
     return "#{selector} { #{declarations} }"

   # Util: set CSS by writing <style> to the DOM.
-  #
+  #
   write: (id, styles) ->
     $("style##{id}").remove()
     $('<style type="text/css" id="'+id+'"/>').append(styles).appendTo('body')


Updated the date time format of the year in 'store' attribute, to force 4 digits when saving. See #34589

--- src/elements/DateTime/DateTimeFormats.coffee
+++ src/elements/DateTime/DateTimeFormats.coffee
@@ -107,7 +107,7 @@
 		display_short: "Y"
 		display_attribute: "year"
 		display_short_attribute: "year"
-		store: "Y"
+		store: "YYYY"
 		type: "year"
 		# digi_clock: false
 		clock: false
@@ -214,7 +214,7 @@
 		display_short: "Y"
 		display_attribute: "year"
 		display_short_attribute: "year"
-		store: "Y"
+		store: "YYYY"
 		type: "year"
 		# digi_clock: false
 		clock: false
@@ -321,7 +321,7 @@
 		display_short: "Y"
 		display_attribute: "year"
 		display_short_attribute: "year"
-		store: "Y"
+		store: "YYYY"
 		type: "year"
 		# digi_clock: false
 		clock: false
@@ -431,7 +431,7 @@
 		display_short: "Y"
 		display_attribute: "year"
 		display_short_attribute: "year"
-		store: "Y"
+		store: "YYYY"
 		type: "year"
 		# digi_clock: false
 		clock: false


#3 работает команда копирования

#3 работает команда копирования

--- builder.coffee
+++ builder.coffee
@@ -7,7 +7,7 @@
 {deepExtend} = require './helpers'
 CopyFiles    = require './copy-files'

-{normalize, basename, dirname, extname, join, existsSync} = path
+{normalize, basename, dirname, extname, join, existsSync, relative} = path

 _.templateSettings = interpolate : /\$\(([\S]+?)\)/g

@@ -17,12 +17,15 @@
     builder.execConfig(key, val)

 copy = (builder, name, options) ->
-  console.log 'copy', options
   builder.lock()
   cp = new CopyFiles options.source, options.destination,
-    replaceStrategy: 2
-    on_complete: () ->
+    replaceStrategy: CopyFiles.REPLACE_OLDER
+    on_complete: (stat) ->
       builder.unlock()
+      console.log "#{name}: #{stat.filesCopied} files copied".green
+    on_progress: (ctx) ->
+      if builder.verbose and not ctx.skipped
+        console.log "#{relative(process.cwd(), ctx.src)} -> #{relative(process.cwd(), ctx.dst)}".grey

 remove = (builder, name, options) ->
   console.log 'remove', options


fix watching us filter, is_closed

fix watching us filter, is_closed

--- app/modules/home/home.service.coffee
+++ app/modules/home/home.service.coffee
@@ -103,9 +103,14 @@
             watchers: userId
         }

+        params_us = {
+            is_closed: false
+            watchers: userId
+        }
+
         watching = Immutable.Map()

-        watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params).then (userstories) ->
+        watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params_us).then (userstories) ->
             watching = watching.set("userStories", userstories)

         watchingTasksPromise = @rs.tasks.listInAllProjects(params).then (tasks) ->


Remove debug logs

Remove debug logs

--- app/scripts/views/preferences.coffee
+++ app/scripts/views/preferences.coffee
@@ -69,10 +69,8 @@
     $body = $ "body"
     if @$components.outerHeight() is parseInt(@$components.css("max-height"), 10)
       $body.addClass "preferences-overflown"
-      console.log "add"
     else
       $body.removeClass "preferences-overflown"
-      console.log "remove"

   getBounceObject: =>


Editor: select current file on fileTree when user changes tab views.

--- client/Ace/aceappview.coffee
+++ client/Ace/aceappview.coffee
@@ -159,9 +159,16 @@

     @tabView.addPane pane
     pane.addSubView aceView
+    pane.on "KDTabPaneActive", => @selectCurrentFileAtFinder aceView

     # save opened file to localStorage, so that we can open same files on refresh.
     KD.singletons.localSync.addToOpenedFiles file.path
+
+  selectCurrentFileAtFinder: (aceView)->
+    {treeController}  = KD.singletons.appManager.get("Finder").controller
+    treeController.deselectAllNodes()
+    nodeName = FSHelper.getFullPath aceView.data # we need filepath with VM address
+    treeController.selectNode treeController.nodes[nodeName]

   setViewListeners: (view) ->
     @setFileListeners view.getData()


fixed uncertainties

fixed uncertainties

--- coffeequate/src/nodes.coffee
+++ coffeequate/src/nodes.coffee
@@ -11,19 +11,25 @@
 			# Return an array of children.
 			return []

+		getAllVariables: ->
+			return []
+
 		getUncertainty: ->
-			require ["operators/Add, operators/Mul, operators/Pow, terminals"], (Add, Mul, Pow, terminals) ->
+			Add = require("operators/Add")
+			Mul = require("operators/Mul")
+			Pow = require("operators/Pow")
+			terminals = require("terminals")

-				Uncertainty = terminals.Uncertainty
-				Constant = terminals.Constant
+			Uncertainty = terminals.Uncertainty
+			Constant = terminals.Constant

-				variables = @getAllVariables()
-				out = []
-				for variable in variables
-					stuff = new Mul(new Uncertainty(variable), @differentiate(variable))
-					out.push(new Pow(stuff, 2))
+			variables = @getAllVariables()
+			out = []
+			for variable in variables
+				stuff = new Mul(new Uncertainty(variable), @differentiate(variable))
+				out.push(new Pow(stuff, 2))

-				return new Pow(new Add(out...), new Constant(1,2)).expandAndSimplify()
+			return new Pow(new Add(out...), new terminals.Constant(1,2)).expandAndSimplify()

 	return {


Implement back button. Wasn't that hard.

--- assets/scripts/mandrill_merge.coffee
+++ assets/scripts/mandrill_merge.coffee
@@ -11,7 +11,7 @@
     caller.parent().prev('form').submit()

   go_back: (caller)->
-    alert 'watch this space'
+    caller.parents('.accordion-navigation').prev().find('a').click()

 $ ->
   window.MandrillMerge.app = new MandrillMergeApp()


(- fix) Handle resizing crash

(- fix) Handle resizing crash

--- Paws/Source/additional.coffee
+++ Paws/Source/additional.coffee
@@ -17,7 +17,7 @@
          # Patching Tput's column measurement
          if @tput.columns == 80
             @tput.columns = process.stdout.columns
-         process.stdout.on 'resize', -> @tput.columns = process.stdout.columns
+         process.stdout.on 'resize', => @tput.columns = process.stdout.columns

          @tput.sgr = (flags...)-> @csi flags.join(';') + 'm'
          @tput.csi = (text)->  '\x1b[' + text


:lipstick:

:lipstick:

--- spec/app/project-spec.coffee
+++ spec/app/project-spec.coffee
@@ -148,7 +148,7 @@

           expect(matches[0]).toEqual
             path: project.resolve('a')
-            match: '\$bill'
+            match: '$bill'
             range: [[2, 6], [2, 11]]

       it "works on evil filenames", ->


Use internal getPath() method

Use internal getPath() method

--- src/extensions/tree-view/src/file-view.coffee
+++ src/extensions/tree-view/src/file-view.coffee
@@ -10,7 +10,7 @@
   file: null

   initialize: (@file) ->
-    @addClass('ignored') if new Git(@file.getPath()).isPathIgnored(@file.getPath())
+    @addClass('ignored') if new Git(@getPath()).isPathIgnored(@getPath())

   getPath: ->
     @file.path


PR feedbacks

PR feedbacks

--- src-public/project/states/main/controller.coffee
+++ src-public/project/states/main/controller.coffee
@@ -56,24 +56,14 @@
         $scope.project = project
     ]

-
   if $scope.project.boardId?
     fetchBoardData $scope.project.boardId
-
-  # Get board colums and members when board is set
-  $scope.$watch 'project.boardId', (next, prev) ->
-    return unless next? and next != prev
-    fetchBoardData next
-
-  $scope.$watch 'project.team', (next, prev) ->
-    return unless next? and  not angular.equals next, prev
-  , true

   $scope.clearTeam = ->
     $scope.project.team.rest = []
     $scope.project.team.dev = []
     $scope.save()
-  #
+
   saveFeedback = $mdToast.simple()
     .hideDelay(1000)
     .position('top right')
@@ -86,5 +76,5 @@
       user.project = p
       user.save().then ->
         $mdToast.show saveFeedback
-        $location.path '/sprint/current'
+        $state.go 'current-sprint'
   return


Don't use temp for integration tests socket creation

--- spec/integration/helpers/start-atom.coffee
+++ spec/integration/helpers/start-atom.coffee
@@ -1,6 +1,7 @@
 path = require 'path'
 http = require 'http'
 temp = require('temp').track()
+os = require('os')
 remote = require 'remote'
 async = require 'async'
 {map, extend, once, difference} = require 'underscore-plus'
@@ -10,7 +11,7 @@
 AtomPath = remote.process.argv[0]
 AtomLauncherPath = path.join(__dirname, "..", "helpers", "atom-launcher.sh")
 ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'electron', 'chromedriver', 'chromedriver')
-SocketPath = path.join(temp.mkdirSync("socket-dir"), "atom-#{process.env.USER}.sock")
+SocketPath = path.join(os.tmpdir(), "atom-integration-test-#{Date.now()}.sock")
 ChromedriverPort = 9515
 ChromedriverURLBase = "/wd/hub"
 ChromedriverStatusURL = "http://localhost:#{ChromedriverPort}#{ChromedriverURLBase}/status"


Fix quick guide

Fix quick guide

--- app/controllers/quick_guide.coffee
+++ app/controllers/quick_guide.coffee
@@ -21,12 +21,13 @@
     @show = not @show

   onClick: (e) =>
+    window.location.hash = '/guide'
+
     type = e.target.dataset.type
-    @navigate('/guide')

     if type in ['lensing-clusters', 'lensed-galaxies', 'lensed-quasars']
       sectionType = 'real-lenses'
-    if type in ['noise', 'offsets']
+    else if type in ['noise', 'offsets']
       sectionType = 'artifacts'
     else
       sectionType = 'false-positives'


test(macModal): Fixed broken e2e test

test(macModal): Fixed broken e2e test

--- test/e2e/modal.spec.coffee
+++ test/e2e/modal.spec.coffee
@@ -38,7 +38,8 @@
     button = element(By.id("open-overlay-btn"))
     button.click()

-    modal = element(By.id("overlay-modal"))
-    modal.click()
+    driver = protractor.getInstance().driver
+    modal  = driver.findElement(By.id("overlay-modal"))
+    driver.executeScript("arguments[0].click()", modal).then ->
+      expect(modal.isDisplayed()).toBeFalsy()

-    expect(modal.isDisplayed()).toBeFalsy()


fix sendToOrga mail function

fix sendToOrga mail function

--- server/methods/mail/sendMail/sendToOrga.coffee
+++ server/methods/mail/sendMail/sendToOrga.coffee
@@ -2,6 +2,9 @@

 	sendToOrga: (projectId, type, shiftId, teamId) ->
 		project = Projects.findOne projectId, fields: name: 1, email: 1, language: 1
+
+		check projectId, isExistingProject
+
 		if shiftId? and teamId?
 			shift = Shifts.findOne shiftId

@@ -13,14 +16,12 @@
 			for team in shift.teams when team._id == teamId
 				teamName = team.name

-		check projectId, isExistingProject
-
 		Meteor.call 'sendMail',
 			recipient: project.email
 			sender: project.name + ' <' + project.email + '>'
 			subject: TAPi18n.__('mail.toOrga.subject.' + type, '', project.language)
 			template: 'toOrga'
-			language: user.profile.language
+			language: project.language
 			data:
 				project: project.name
 				text: TAPi18n.__('mail.toOrga.text.' + type, {date: date, time: time, team: team.name} , project.language)


Default to Date.now (#3993)

Default to Date.now (#3993)

--- angular/core/services/attachment_service.coffee
+++ angular/core/services/attachment_service.coffee
@@ -14,7 +14,7 @@
         data = event.clipboardData
         return unless item = _.first _.filter(data.items, (item) -> item.getAsFile())
         event.preventDefault()
-        file = new File [item.getAsFile()], data.getData('text/plain'),
+        file = new File [item.getAsFile()], data.getData('text/plain') || Date.now(),
           lastModified: moment()
           type:         item.type
         scope.$broadcast 'attachmentPasted', file


Remove unused js

Remove unused js

--- app/assets/javascripts/admin/comments.js.coffee
+++ app/assets/javascripts/admin/comments.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/


Fixed client id bug

Fixed client id bug

--- app/assets/javascripts/models/message.js.coffee
+++ app/assets/javascripts/models/message.js.coffee
@@ -9,7 +9,7 @@

   timestamp: -> new Date(@get('timestamp'))

-  toJSON: -> { content: @get("content"), client_id: @get("client_id") }
+  toJSON: -> { content: @get("content"), client_id: session.get('client_id') }

   selfReference: -> if @get('content').match(/^\/me/i) then true else false


Fix an issue where the extension's story reducer incorrectly filtered out local story records

--- src/chromeExtension/reducers/storyReducer.coffee
+++ src/chromeExtension/reducers/storyReducer.coffee
@@ -118,12 +118,12 @@
 _rxStory = (state, record, options) ->
   {fPastRecords, fDiscardRemoteClientLogs} = options
   {storyId, fServer, hubId} = record
+  {localHubId} = state
   newStoryPathStr = null

   # We ignore root stories (beginning by '*') when they are server-side
   # OR they belong to our local hub
   if (storyId[0] is '*')
-    {localHubId} = state
     if fServer or (hubId is localHubId)
       return [state, newStoryPathStr]
     title = record.title.replace 'ROOT STORY', 'REMOTE CLIENT'


fixed broken controller

fixed broken controller

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -56,7 +56,6 @@
     @model.fetch()
       .then(
         (error) => @modelFetchDone(error)
-        (response) => @modelFetchFail(response)
       )

@@ -149,11 +148,6 @@
           @zoomStepWarningToast = null

-  modelFetchFail : (response) ->
-
-    Toast.error(response.responseJSON.messages)
-
-
   initKeyboard : ->

     $(document).keypress (event) ->


fixed warning message modal

fixed warning message modal

--- app/assets/javascripts/oxalis/view/modal.coffee
+++ app/assets/javascripts/oxalis/view/modal.coffee
@@ -9,13 +9,22 @@
   show : (text, buttons) ->
     # buttons: [{id:..., label:..., callback:...}, ...]

-    html =  "

" + text + "

"
+    html =  """
+

+

+

+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+
Ups...

+

+

+
#{text}

+
"""

     html += "
"
     for button in buttons
       html += "" +
                     button.label + ""
-    html += "
"
+    html += "

"

     $("#modal").html(html)


Use Zooniverse logo component on organizations page

--- app/pages/about/organizations.cjsx
+++ app/pages/about/organizations.cjsx
@@ -1,4 +1,5 @@
 React = require 'react'
+ZooniverseLogo = require '../../partials/zooniverse-logo'

 module.exports = React.createClass
   displayName: 'Organizations'
@@ -25,7 +26,7 @@

Zooniverse

-

+
<ZooniverseLogo width="100%" height="100%" />

The Zooniverse and the suite of projects it contains is produced, maintained, and developed by the Citizen Science Alliance (CSA). The member institutions of the CSA work with many academic and other partners around the world to produce projects that use the efforts and ability of volunteers to help scientists and researchers deal with the flood of data that confronts them.


@className + @debug usage for widget's compatibility.

--- app/assets/javascripts/ultimate/flash.js.coffee
+++ app/assets/javascripts/ultimate/flash.js.coffee
@@ -11,6 +11,8 @@

 # TODO customizable show() and hide()
 # TODO improve English
+# TODO settings by call
+# TODO jGrowl features

 ###
  * Invoke Ultimate Flash functionality for the first element in the set of matched elements.
@@ -38,6 +40,7 @@

 class Ultimate.Plugins.Flash extends Ultimate.Proto.Widget
+  @className: 'Flash'

   @selector: '.l-page__flashes'

@@ -209,16 +212,16 @@
       if jqXHR.status >= 400 and jqXHR.responseText
         # try detect Rails raise message
         if raiseMatches = jqXHR.responseText.match /<\/h1>\n<pre>(.+?)<\/pre>/
-          cout "replace thrownError = '#{thrownError}' with raiseMatches[1] = '#{raiseMatches[1]}'"
+          @debug "replace thrownError = '#{thrownError}' with raiseMatches[1] = '#{raiseMatches[1]}'"
           thrownError = raiseMatches[1]
         else
           # try detect short text message as error
           if jqXHR.responseText.length <= @settings.detectPlainTextMaxLength
-            cout "replace thrownError = '#{thrownError}' with jqXHR.responseText = '#{jqXHR.responseText}'"
+            @debug "replace thrownError = '#{thrownError}' with jqXHR.responseText = '#{jqXHR.responseText}'"
             thrownError = jqXHR.responseText
       else
         if _.isString thrownError and not $.isEmptyString thrownError
-          cout "replace thrownError = '#{thrownError}' with @settings.translations.defaultThrownError = '#{@settings.translations.defaultThrownError}'"
+          @debug "replace thrownError = '#{thrownError}' with @settings.translations.defaultThrownError = '#{@settings.translations.defaultThrownError}'"
           thrownError = @settings.translations.defaultThrownError
       text += ': '  if text
       text += "#{thrownError} [#{jqXHR.status}]"


Initialize JavaScript date pickers.

Initialize JavaScript date pickers.

--- app/assets/javascripts/voluntary/base.js.coffee
+++ app/assets/javascripts/voluntary/base.js.coffee
@@ -31,4 +31,7 @@
           $($(this).attr('id_element')).val(ui.item.id)

         return false;
-    });+    });
+
+  $( ".datepicker" ).each (k, v) ->
+    $(v).datepicker({ dateFormat: "yy-mm-dd", changeYear: true, yearRange: "c-100:c+10" });


Fix theme initial generation

Fix theme initial generation

--- packages/rocketchat-theme/server/server.coffee
+++ packages/rocketchat-theme/server/server.coffee
@@ -73,20 +73,21 @@

 		@compileDelayed = _.debounce Meteor.bindEnvironment(@compile.bind(@)), 100

-		RocketChat.settings.onAfterInitialLoad =>
+		Meteor.startup =>
+			RocketChat.settings.onAfterInitialLoad =>

-			RocketChat.settings.get '*', Meteor.bindEnvironment (key, value, initialLoad) =>
-				if key is 'theme-custom-css'
-					if value?.trim() isnt ''
-						@customCSS = value
-				else if /^theme-.+/.test(key) is true
-					name = key.replace /^theme-[a-z]+-/, ''
-					if @variables[name]?
-						@variables[name].value = value
-				else
-					return
+				RocketChat.settings.get '*', Meteor.bindEnvironment (key, value, initialLoad) =>
+					if key is 'theme-custom-css'
+						if value?.trim() isnt ''
+							@customCSS = value
+					else if /^theme-.+/.test(key) is true
+						name = key.replace /^theme-[a-z]+-/, ''
+						if @variables[name]?
+							@variables[name].value = value
+					else
+						return

-				@compileDelayed()
+					@compileDelayed()

 	compile: ->
 		content = [
@@ -159,7 +160,7 @@

 	addPackageAsset: (cb) ->
 		@packageCallbacks.push cb
-		# @compileDelayed()
+		@compileDelayed()

 	getCss: ->
 		return RocketChat.settings.get('css') or ''


Fix checkMountPoint, remove trailing whitespaces

--- kites/sharedHosting/mounter.coffee
+++ kites/sharedHosting/mounter.coffee
@@ -107,7 +107,7 @@
       else
         uid = res.trim()
         log.info "[OK] user ID for user #{username} is #{uid}"
-        ftpfsopts = "#{config.ftpfs.opts},uid=#{uid},gid=#{uid},fsname=#{remotehost},user=#{remoteuser}:#{remotepass}"
+        ftpfsopts = "#{config.ftpfs.opts},uid=#{uid},gid=#{uid},fsname=#{remotehost},user=#{remoteuser}:#{remotepass}"
         @createMountpoint options, (err,res)=>
           if err
             callback err
@@ -122,20 +122,19 @@
                     callback err
                   else
                     @updateMountCfg options,(err,res)->
-                    callback null,res
-
-  checkMountpoint : (options, callback)->
+                    callback null,res
+
+  checkMountPoint : (options, callback)->
     #
     # this method checks if remount host mounted (it doesn't check for availability)
     #
     #
-
+
     #
     # return =
     #   mounted: Bool # true/false
     #   remotehost: String # remote FTP/SFTP hostname
     #   mountpoint: Strint # full path to the mountpoint
-

     {username, remotehost} = options

@@ -159,15 +158,14 @@
           mounted: true
           remotehost: remotehost
           mountpoint: options.mountpoint
-        callback res
+        callback null, res
       else
         console.log error = "[ERROR] mountpoing #{options.mountpoint} is not mounted"
         res =
           mounted: false
           remotehost: remotehost
           mountpoint: options.mountpoint
-        callback res
-
+        callback null, res

   # Safe
   umountDrive : (options, callback)->


Support last location of killed components

--- app/scripts/lib/LazerScript.coffee
+++ app/scripts/lib/LazerScript.coffee
@@ -177,11 +177,15 @@
     @component.attr
       x: @component.x - Crafty.viewport.x
       y: @component.y - Crafty.viewport.y
-    @component.bind 'Destroyed', => @enemy.alive = no
+    @component.bind 'Destroyed', =>
+      @enemy.location.x = (@component.x + Crafty.viewport.x)
+      @enemy.location.y = (@component.y + Crafty.viewport.y)
+      @enemy.alive = no

     @enemy =
       moveState: 'air'
       alive: yes
+      location: {}

     super.then(-> console.log 'success').catch(-> console.log 'fail').finally(-> console.log 'ended')

@@ -361,6 +365,6 @@

   location: (settings = {}) ->
     =>
-      x: @component.x + Crafty.viewport.x
-      y: @component.y + Crafty.viewport.y
-
+      x: @enemy.location.x ? (@component.x + Crafty.viewport.x)
+      y: @enemy.location.y ? (@component.y + Crafty.viewport.y)
+


Updating the serializer to deal with the correct FHIR bundle format

--- app/serializers/application.coffee
+++ app/serializers/application.coffee
@@ -8,20 +8,22 @@
     return Ember.String.capitalize(key)

   extract: (store, type, payload, id, requestType) ->
-
     return [] if payload == null
-    if payload.Type == "Bundle"
+    if payload.resourceType == "Bundle"
       console.log "Bundle came in"
-      payload = payload.Entries || []
+      payload = payload.entry || []
     payload.id ?= id || Em.generateGuid({}, type.typeKey)
     @_super(store, type, payload, id, requestType)

   normalize: (type, hash, prop) ->
     # console.log "#{type.typeKey} #{hash.id}"
     # Because FHIR resources have embedded stuff without IDs we're going to generate them
-    # debugger if type.typeKey == "patient"
-    hash.id ?= hash.Identifier || Em.generateGuid({}, type.typeKey)
-    @_super(type, hash, prop)
+    if hash.content
+      hash.content.id ?= hash.Identifier || Em.generateGuid({}, type.typeKey)
+      @_super(type, hash.content, prop)
+    else
+      hash.id ?= hash.Identifier || Em.generateGuid({}, type.typeKey)
+      @_super(type, hash, prop)
   # serializeAttribute: (record, json, key, attribute) ->
     # Because of the way FHIR handles values we have to munge the JSON a little
     # if key =="value"


Remove incorrect comment

Remove incorrect comment

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -137,7 +137,6 @@
   return resp.send 403, error: "Unauthorised"

 app.get '/api/:user/datasets/?', checkUserRights, (req, resp) ->
-  # note: this ignores :user !?
   Dataset.findAllByUserShortName req.user.effective.shortName, (err, datasets) ->
     if err?
       console.log err