Working on Add.solve

Working on Add.solve

--- coffeequate/src/operators.coffee
+++ coffeequate/src/operators.coffee
@@ -3,8 +3,8 @@
 	# Defines operator nodes of the expression tree.

 	class AlgebraError extends Error
-		constructor: (expr, variable) ->
-			super("Unsolvable: #{expr} for #{variable}")
+		constructor: (expr, variable, postscript=null) ->
+			super("Unsolvable: #{expr} for #{variable}#{if postscript then ";" + postscript else ""}")

 	prettyPrint = (array) ->
 		out = []
@@ -224,6 +224,155 @@

 		expandAndSimplify: ->
 			@expand().simplify()
+
+		solve: (variable) ->
+			expr = @expandAndSimplify()
+
+			# Sort the expression into terms containing v, and terms not containing v.
+			# Since we expanded, every term we deal with should be either a Mul with v as a direct
+			# child, a Pow with v as the left child, or the latter in a Mul.
+
+			termsContainingVariable = []
+			termsNotContainingVariable = []
+
+			for term in expr.children
+				if term.copy?
+					term = term.copy()
+
+				if term instanceof Pow
+					if term.children.left instanceof terminals.Variable and term.children.left.label == variable
+						termsContainingVariable.push(term)
+					else
+						termsNotContainingVariable.push(term)
+				else if term instanceof Mul
+					added = false
+					for subterm in term.children
+						if subterm instanceof terminals.Variable and subterm.label == variable
+							termsContainingVariable.push(term)
+							added = true
+							break
+						else if (
+							subterm instanceof Pow and
+							subterm.children.left instanceof terminals.Variable and
+							subterm.children.left.label == variable)
+							termsContainingVariable.push(term)
+							added = true
+							break
+
+					unless added
+						termsNotContainingVariable.push(term)
+				else if term instanceof terminals.Variable and term.label == variable
+					termsContainingVariable.push(term)
+				else
+					termsNotContainingVariable.push(term)
+
+			# The rest of the terms need to be manipulated to solve the equation.
+			# (a * v) + (b * v) -> ((a + b) * v)
+			# (a * v) + (b * v) + (c * (v ** 2)) -> ((a + b) * v) + (c * (v ** 2))
+			# If we detect a power > 2, reject the equation.
+
+			factorised = [] # (a + b) in the above example.
+			factorisedSquares = [] # c in the above example.
+
+			for term in termsContainingVariable
+				if term instanceof terminals.Variable
+					factorised.push(new terminals.Constant("1"))
+				else if term instanceof Pow
+					unless term.children.right instanceof terminals.Constant
+						throw new AlgebraError(expr.toString(), variable)
+					power = term.children.right.evaluate()
+					if power == 1
+						factorised.push(new terminals.Constant("1"))
+					else if power == 2
+						factorisedSquares.push(new terminals.Constant("1"))
+					else
+						throw new AlgebraError(expr.toString(), variable, "polynomials of degree > 2 not supported")
+				else if term instanceof Mul
+					subterms = [] # Non-variable terms.
+					quadratic = false
+					for subterm in term.children
+						if term instanceof terminals.Variable and term.label == variable then # pass
+						else if term instanceof Pow
+							unless term.children.right instanceof terminals.Constant
+								throw new AlgebraError(expr.toString(), variable)
+							power = term.children.right.evaluate()
+							if power == 1 then # pass
+							else if power == 2
+								quadratic = true # We operate on the assumption that there's only one term with our target variable in it here.
+								# This should be possibly due to expandAndSimplify.
+							else
+								throw new AlgebraError(expr.toString(), variable, "polynomials of degree > 2 not supported")
+						else
+							subterms.push(subterm)
+
+					factorisedTerm = new Mul(subterms...)
+					unless quadratic
+						factorised.push(factorisedTerm)
+					else
+						factorisedSquares.push(factorisedTerm)
+
+			negatedTerms = []
+			# Terms not containing the variable need to be negated. They will form part of the returned result.
+			for term in termsNotContainingVariable
+				newMul = new Mul("-1", term)
+				newMul = newMul.simplify()
+				negatedTerms.push(newMul)
+
+			negatedTermsEquatable = new Add(negatedTerms...) unless negatedTerms.length == 0
+			factorisedEquatable = new Add(factorised...) unless factorised.length == 0
+			factorisedSquaresEquatable = new Add(factorisedSquares...) unless factorisedSquares.length == 0
+
+			if factorisedSquares.length == 0
+				# We now have terms on the other side of the equation (negatedTermsEquatable) and
+				# terms factorised out from this side of the equation (factorisedEquatable).
+				# (factorisedEquatable * v) = negatedTermsEquatable
+				# Hence the solution is simply (negatedTermsEquatable * (factorisedEquatable ** -1)).
+				if factorised.length > 0
+					newPow = new Pow(factorisedEquatable, "-1")
+					newPow = newPow.simplify()
+					if negatedTermsEquatable?
+						newMul = new Mul(negatedTermsEquatable, newPow)
+						newMul = newMul.simplify()
+						return [newMul]
+					else
+						return [newPow]
+				else
+					throw new AlgebraError(expr.toString(), variable)
+			else if factorised.length == 0
+				# We now have terms on the other side of the equation (negatedTermsEquatable) and
+				# terms factorised out from this side of the equation (factorisedSquaresEquatable).
+				# (factorisedSquaresEquatable * (v ** 2)) = negatedTermsEquatable
+				# Hence the solution is simply \pm ((negatedTermsEquatable * (factorisedSquaresEquatable ** -1)) ** 1/2).
+				if factorisedSquares.length > 0
+					newPow = new Pow(factorisedSquaresEquatable, "-1")
+					newPow = newPow.simplify()
+					if negatedTermsEquatable?
+						newMul = new Mul(negatedTermsEquatable, newPow)
+						newerPow = new Pow(newMul, "1/2")
+						negatedNewerPow = new Mul("-1", newerPow)
+						negatedNewerPow = negatedNewerPow.simplify()
+						newerPow = newerPow.simplify()
+						return [newerPow, negatedNewerPow]
+					else
+						newerPow = new Pow(newPow, "1/2")
+						negatedNewerPow = new Mul("-1", newerPow)
+						negatedNewerPow = negatedNewerPow.simplify()
+						newerPow = newerPow.simplify()
+						return [newerPow, negatedNewerPow]
+				else
+					throw new AlgebraError(expr.toString(), variable)
+			else
+				# We have a quadratic equation.
+				# ((factorisedSquaresEquatable) * (v ** 2)) + (factorisedEquatable * v) + (nonNegatedTerms) = 0
+				nonNegatedTermsEquatable = new Add(termsNotContainingVariable...) unless termsNotContainingVariable.length == 0
+				if nonNegatedTermsEquatable?
+
+				else
+					# (((fSE * v) + fE) * v) = 0
+					# v = 0
+					# v = (-1 * (fE * (fSE ** -1)))
+					newPow = new Pow(factorisedSquaresEquatable, "-1")
+

 	class Mul extends nodes.RoseNode
 		# Represent multiplication.


Fixed header shown on Legal page

Fixed header shown on Legal page

--- client/landing/site.landing/coffee/legal/AppView.coffee
+++ client/landing/site.landing/coffee/legal/AppView.coffee
@@ -5,6 +5,7 @@
 PrivacyView    = require './privacy'
 TosView        = require './tos'
 CopyrightView  = require './copyright'
+MainHeaderView = require '../core/mainheaderview'

 module.exports = class LegalView extends JView

@@ -30,7 +31,9 @@
   constructor:->

     super
-
+
+    @addSubView @mainHeader = new MainHeaderView
+
     @setPartial @partial()

     @handles = {}


adding loginStatus and authenticated into info for .html

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -288,12 +288,19 @@
       if e then return res.e e
       if status is 404
         return res.send page, status
-      info = {}
-      info.pages = [
-          page: file
-          generated: """data-server-generated=true"""
-          story: wiki.resolveLinks(render(page))
-        ]
+      info = {
+      	pages: [
+      	  page: file
+      	  generated: """data-server-generated=true"""
+      	  story: wiki.resolveLinks(render(page))
+      	]
+      	authenticated: req.isAuthenticated()
+      	loginStatus: if owner
+      	  if req.isAuthenticated()
+      	    'logout'
+      	  else 'login'
+      	else 'claim'
+      }
       res.render('static.html', info)

   app.get ///system/factories.json///, (req, res) ->


Cleanup: minor explicit return

Cleanup: minor explicit return

--- components/tabs/KDTabView.coffee
+++ components/tabs/KDTabView.coffee
@@ -183,7 +183,7 @@
     handle.makeActive()
     pane.emit "PaneDidShow"
     @emit "PaneDidShow", pane
-    pane
+    return pane

   hideAllPanes:->


Social: export fetchGroupChannels function

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -119,4 +119,5 @@
   createChannel
   fetchMessage
   fetchChannelActivity
+  fetchGroupChannels
 }


Additional test for branch coverage.

Additional test for branch coverage.

--- test/suite/profile/NicknameProfile.spec.coffee
+++ test/suite/profile/NicknameProfile.spec.coffee
@@ -65,5 +65,8 @@

     describe 'validate()', ->

+        it 'allows non-empty strings', ->
+            assert.doesNotThrow => @subject.validate [0]
+
         it 'rejects empty strings', ->
             assert.throws (=> @subject.validate []), EmptyStringError


longer wait time for contested

longer wait time for contested

changed from 5 seconds to 10 seconds

--- scripts/vote.coffee
+++ scripts/vote.coffee
@@ -53,7 +53,7 @@
     # We're not done if it's only a two vote difference
     return if (Math.abs(@votes.yes.length - @votes.no.length) < 2)
     # Is this still being contested?
-    return if (Date.now() - @votes.prev) < 5000
+    return if (Date.now() - @votes.prev) < 10000
     @finish()


Add bot messages. Add more emojis being converted over.

--- packages/rocketchat-importer-slack/server/SlackImporter.coffee
+++ packages/rocketchat-importer-slack/server/SlackImporter.coffee
@@ -2,6 +2,7 @@
 	constructor: (name, descriptionI18N, fileTypeRegex) ->
 		super(name, descriptionI18N, fileTypeRegex)
 		@userTags = []
+		@bots = {}

 	prepare: (dataURI, sentContentType, fileName) =>
 		super(dataURI, sentContentType, fileName)
@@ -22,6 +23,10 @@
 					else if entry.entryName == 'users.json'
 						@updateProgress Importer.ProgressStep.PREPARING_USERS
 						tempUsers = JSON.parse entry.getData().toString()
+
+						for user in tempUsers when user.is_bot
+							@bots[user.profile.bot_id] = user
+
 					else if not entry.isDirectory and entry.entryName.indexOf('/') > -1
 						item = entry.entryName.split('/') #random/2015-10-04.json
 						channelName = item[0] #random
@@ -170,7 +175,7 @@
 				@collection.update { _id: @channels._id }, { $set: { 'channels': @channels.channels }}

 				missedTypes = {}
-				ignoreTypes = { 'bot_add': true, 'file_comment': true, 'file_mention': true }
+				ignoreTypes = { 'bot_add': true, 'file_comment': true, 'file_mention': true, 'channel_name': true }
 				@updateProgress Importer.ProgressStep.IMPORTING_MESSAGES
 				for channel, messagesObj of @messages
 					do (channel, messagesObj) =>
@@ -193,8 +198,26 @@
 															ts: new Date(parseInt(message.ts.split('.')[0]) * 1000)
 												else if message.subtype is 'me_message'
 													RocketChat.sendMessage @getRocketUser(message.user), { msg: '_' + @convertSlackMessageToRocketChat(message.text) + '_', ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) }, room
-												#else if message.subtype is 'bot_message'
-													#RocketChat.sendMessage
+												else if message.subtype is 'bot_message'
+													botUser = RocketChat.models.Users.findOneById 'rocket.cat', { fields: { username: 1 }}
+													botUsername = if @bots[message.bot_id] then @bots[message.bot_id]?.name else message.username
+													msgObj =
+														msg: if message.text then message.text else ''
+														ts: new Date(parseInt(message.ts.split('.')[0]) * 1000)
+														rid: room._id
+														bot: true
+														attachments: message.attachments
+														username: if botUsername then botUsername else undefined
+
+													if message.edited?
+														msgObj.ets = new Date(parseInt(message.edited.ts.split('.')[0]) * 1000)
+
+													if message.icons?
+														msgObj.emoji = message.icons.emoji
+
+													msgObj.msg = @convertSlackMessageToRocketChat(msgObj.msg)
+
+													RocketChat.sendMessage botUser, msgObj, room
 												else if message.subtype is 'channel_purpose'
 													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.purpose, @getRocketUser(message.user)
 												else if message.subtype is 'channel_topic'
@@ -259,6 +282,9 @@
 			message = message.replace /</g, '>'
 			message = message.replace /&/g, '&'
 			message = message.replace /:simple_smile:/g, ':smile:'
+			message = message.replace /:memo:/g, ':pencil:'
+			message = message.replace /:piggy:/g, ':pig:'
+			message = message.replace /:uk:/g, ':gb:'
 			for userReplace in @userTags
 				message = message.replace userReplace.slack, userReplace.rocket
 				message = message.replace userReplace.slackLong, userReplace.rocket


:bug:Fix slack tower message routing

:bug:Fix slack tower message routing

--- server/slack.coffee
+++ server/slack.coffee
@@ -4,6 +4,7 @@

 roomChannelMap =
   main: '#general'
+  tower: '#general'
   artisans: '#artisan'

 module.exports.sendChangedSlackMessage = (options) ->


GetMulti -> Get - muffin

GetMulti -> Get - muffin

--- server/src/Component/Builtin/Database/CouchMuffin.coffee
+++ server/src/Component/Builtin/Database/CouchMuffin.coffee
@@ -150,6 +150,7 @@
     # @param {function} callback Called when the operation is completed (error, result)
     insert: (params, callback) ->
         id = params.id || null
+        id = "#{@_keyPrefix}#{id}" if id?
         data = params.data || {}
         options = params.options || {}
         validate = options.validate ? true


moved calculation of mincha time on ereb shabbat into a separate function

--- source/javascripts/mincha.coffee
+++ source/javascripts/mincha.coffee
@@ -165,6 +165,7 @@
     $(".#{@chag()}.sunset .time").html(time_format(@sunset))
     $(".#{@chag()}.neilah .time").html(time_format(minutes_before_event(@sunset, 55)))
     @rabbenu_tam_schedule()
+  mincha_on_ereb_shabbat: -> moment.min(moment(@sunset).subtract(33, 'minutes'), @today().hour(18).minute(30))
   mincha: -> @_mincha ?= switch
     when @hebrew_date.isErebRoshHashana() then minutes_before_event(@sunset, if @hebrew_date.isErebShabbat() then 55 else 45)
     when @hebrew_date.isRoshHashana() then minutes_before_event(@sunset, @mincha_minutes_before_sunset_on_rosh_hashana())
@@ -178,7 +179,7 @@
     when @yom_tob_that_we_can_pray_at_plag() && !@hebrew_date.isErebShabbat() then minutes_before_event(@plag(), 30)
     when @hebrew_date.isYomTob() && !@yom_tob_that_we_can_pray_at_plag() then minutes_before_event(@sunset, if @hebrew_date.isErebShabbat() then 40 else 25)
     when @hebrew_date.isErebYomTob() then minutes_before_event(@sunset, if @hebrew_date.isErebShabbat() then 33 else  25)
-    when @hebrew_date.isErebShabbat() then moment.min(moment(@sunset).subtract(33, 'minutes'), @today().hour(18).minute(30))
+    when @hebrew_date.isErebShabbat() then @mincha_on_ereb_shabbat()
     else recent_hadlakat_nerot
   arbit: -> @_arbit ?= switch
     when @hebrew_date.isErebYomKippur() then minutes_before_event(@sunset, 55)


cleanup pass spec

cleanup pass spec

--- spec/quick-highlight-spec.coffee
+++ spec/quick-highlight-spec.coffee
@@ -37,8 +37,6 @@
     atom.commands.dispatch(element, command)

   beforeEach ->
-    spyOn(_._, "now").andCallFake -> window.now
-
     workspaceElement = atom.views.getView(atom.workspace)
     jasmine.attachToDOM(workspaceElement)

@@ -125,6 +123,9 @@
         ensureDecorations(editor3, "underline-01": ['orange', 'orange', 'orange'])

   describe "selection changed when highlightSelection", ->
+    beforeEach ->
+      spyOn(_._, "now").andCallFake -> window.now
+
     it "decorate selected keyword", ->
       dispatchCommand('editor:select-word')
       advanceClock(150)
@@ -199,30 +200,29 @@
       beforeEach ->
         setConfig('highlightSelectionDelay', 300)

-      xit "highlight selection after specified delay", ->
-        # FIXME: It delay works in interactive use, but I can't make it success in this spec.
-        # need investigation.
+      it "highlight selection after specified delay", ->
         dispatchCommand('editor:select-word')
         expect(editor.getSelectedText()).toBe "orange"
         expect(getDecorations(editor)).toHaveLength 0
-        advanceClock(150)
-        expect(getDecorations(editor)).toHaveLength 0
-        advanceClock(150)
+        advanceClock(100)
+        expect(getDecorations(editor)).toHaveLength 0
+        advanceClock(100)
+        expect(getDecorations(editor)).toHaveLength 0
+        advanceClock(100)
+        expect(getDecorations(editor)).toHaveLength 3
         ensureDecorations(editor, "box-selection": ['orange', 'orange', 'orange'])

     describe "displayCountOnStatusBar", ->
       [editor3, container, span] = []
       beforeEach ->
         editor.setText """
-        apple orange
-        orange lemon orange
-        apple
-        """
-        waitsForPromise ->
-          atom.packages.activatePackage("status-bar")
-
-        waitsFor ->
-          main.statusBarManager.tile?
+          apple orange
+          orange lemon orange
+          apple
+          """
+
+        waitsForPromise -> atom.packages.activatePackage("status-bar")
+        waitsFor -> main.statusBarManager.tile?

         runs ->
           container = workspaceElement.querySelector('#status-bar-quick-highlight')


Prettier transition between old and new preview

--- servers/www/assets/js/directives/previewer.coffee
+++ servers/www/assets/js/directives/previewer.coffee
@@ -31,8 +31,11 @@
       request = $http.post("#{url.api}/previews", json)

       request.then (response) ->
-        $preview.remove() if $preview
-        $preview = $("<iframe>", src: response.data.run_url, class: "plnk-runner", frameborder: 0, width: "100%", height: "100%", scrolling: "auto").appendTo(el).fadeIn()
+        $old = $preview
+        $preview = $("<iframe>", src: response.data.run_url, class: "plnk-runner", frameborder: 0, width: "100%", height: "100%", scrolling: "auto").appendTo(el)
+        $preview.ready ->
+          if $old then $old.fadeOut -> $old.remove()
+          $preview.fadeIn()


hold referer in window

hold referer in window

--- lib/assets/javascripts/angular-turbolinks.js.coffee
+++ lib/assets/javascripts/angular-turbolinks.js.coffee
@@ -3,7 +3,6 @@
   loadedAssets = null
   createDocument = null
   xhr_req = null
-  referer = null

   triggerEvent = (name, data) ->
     event = document.createEvent 'Events'
@@ -17,7 +16,7 @@
     value

   rememberReferer = ->
-    referer = document.location.href
+    window.referer = document.location.href

   processResponse = (responseText, status, headers)->
     clientOrServerError = ->
@@ -126,10 +125,12 @@
     xhr_req = $q.defer()

     triggerEvent 'page:fetch', url: url
-    $http.get(url, {
+    $http({
+      url: url,
+      method: 'GET',
       headers: {
-        'Accept' : 'text/html, application/xhtml+xml, application/xml'
-        'X-XHR-Referer' : referer
+        'Accept' : 'text/html, application/xhtml+xml, application/xml',
+        'X-XHR-Referer' : window.referer
       },
       timeout: xhr_req.promise
     }).success((data, status, headers)->


New method findOne

New method findOne

--- source/hamsa.coffee
+++ source/hamsa.coffee
@@ -54,14 +54,24 @@
       (record for uid, record of @records when not filter or filter record)

     ###
-    Finds a determinate instance with a field attribute.
+    Finds a determinate group of instances with a field attribute.
     @method findBy
     @param  {string}    Name of field to search.
     @param  {string}    Value to filter search.
-    @return {object}    Hamsa instance.
+    @return {object}    Array of Hamsa instances.
     ###
     @findBy: (name, value) ->
       (record for uid, record of @records when record[name] is value)
+
+    ###
+    Finds a determinate instance with a field attribute.
+    @method findOne
+    @param  {string}    Name of field to search.
+    @param  {string}    Value to filter search.
+    @return {object}    Hamsa instance.
+    ###
+    @findOne: (name, value) ->
+      @findBy(name, value)[0]

     ###
     Observe changes in instance repository.


--- lib/assets/javascripts/firehose/long_poll.js.coffee
+++ lib/assets/javascripts/firehose/long_poll.js.coffee
@@ -126,44 +126,32 @@
     lines.join '\n'
   xhr

-# if ( window.XDomainRequest ) {
-#   jQuery.ajaxTransport(function( s ) {
-#     if ( s.crossDomain && s.async ) {
-#       if ( s.timeout ) {
-#         s.xdrTimeout = s.timeout;
-#         delete s.timeout;
-#       }
-#       var xdr;
-#       return {
-#         send: function( _, complete ) {
-#           function callback( status, statusText, responses, responseHeaders ) {
-#             xdr.onload = xdr.onerror = xdr.ontimeout = jQuery.noop;
-#             xdr = undefined;
-#             complete( status, statusText, responses, responseHeaders );
-#           }
-#           xdr = new XDomainRequest();
-#           xdr.open( s.type, s.url );
-#           xdr.onload = function() {
-#             callback( 200, "OK", { text: xdr.responseText }, "Content-Type: " + xdr.contentType );
-#           };
-#           xdr.onerror = function() {
-#             callback( 404, "Not Found" );
-#           };
-#           if ( s.xdrTimeout ) {
-#             xdr.ontimeout = function() {
-#               callback( 0, "timeout" );
-#             };
-#             xdr.timeout = s.xdrTimeout;
-#           }
-#           xdr.send( ( s.hasContent && s.data ) || null );
-#         },
-#         abort: function() {
-#           if ( xdr ) {
-#             xdr.onerror = jQuery.noop();
-#             xdr.abort();
-#           }
-#         }
-#       };
-#     }
-#   });
-# }
+# Let's try to hack in support for IE8+ via the XDomainRequest object!
+if window.XDomainRequest?
+  jQuery.ajaxTransport (s) ->
+    if s.crossDomain and s.async
+      if s.timeout
+        s.xdrTimeout = s.timeout
+        delete s.timeout
+      xdr = undefined
+      return {
+        send: (_, complete) ->
+          callback = (status, statusText, responses, responseHeaders) ->
+            xdr.onload = xdr.onerror = xdr.ontimeout = jQuery.noop
+            xdr = undefined
+            complete status, statusText, responses, responseHeaders
+          xdr = new XDomainRequest()
+          xdr.open s.type, s.url
+          xdr.onload = ->
+            headers = "Content-Type: #{xdr.contentType}"
+            callback 200, "OK", {text: xdr.responseText}, headers
+          xdr.onerror = -> callback 404, "Not Found"
+          if s.xdrTimeout?
+            xdr.ontimeout = -> callback 0, "timeout"
+            xdr.timeout   = s.xdrTimeout
+          xdr.send (s.hasContent and s.data) or null
+        abort: ->
+          if xdr?
+            xdr.onerror = jQuery.noop()
+            xdr.abort()
+      }


Show test updated

Show test updated

--- lib/model/levelDB/tool/test/show.coffee
+++ lib/model/levelDB/tool/test/show.coffee
@@ -48,13 +48,13 @@
                       return next err if err
                       client.close (err) ->
                         return next err if err
-                        show "#{__dirname}/../../../../../db/tmp/user", 'user', (err, message) ->
+                        show "#{__dirname}/../../../../../db/tmp", 'user', (err, message) ->
                           return next err if err
                           console.log message
                           next()

   it 'Show user database without user', (next) ->
-    show "#{__dirname}/../../../../../db/tmp/user", 'user', (err, message) ->
+    show "#{__dirname}/../../../../../db/tmp", 'user', (err, message) ->
       return next err if err
       console.log message
       next()
@@ -75,7 +75,7 @@
           return next err if err
           client.close (err) ->
             return next err if err
-            show "#{__dirname}/../../../../../db/tmp/user", 'user', (err, message) ->
+            show "#{__dirname}/../../../../../db/tmp", 'user', (err, message) ->
               return next err if err
               console.log message
               next()


Fixing regexp.

Fixing regexp.

--- source/utils.coffee
+++ source/utils.coffee
@@ -4,7 +4,7 @@
 Utils =
   # Regexp for validating the tagname
   # probably cheaper then try / catch - createElement
-  TAGNAME_REGEXP: /^[a-zA-Z_:][-a-zA-Z0-9_:.]+$/
+  TAGNAME_REGEXP: /^[a-zA-Z_:]([-a-zA-Z0-9_:.]+)?$/

   # Async series minimal implementation
   #


Start preparing merging tests

Start preparing merging tests

--- spec/Scoping.coffee
+++ spec/Scoping.coffee
@@ -27,6 +27,24 @@
       callback()
     , 1

+wirePatternMerge = ->
+  c = new noflo.Component
+  c.inPorts.add 'in1',
+    datatype: 'string'
+  c.inPorts.add 'in2',
+    datatype: 'string'
+  c.outPorts.add 'out',
+    datatype: 'string'
+
+  noflo.helpers.WirePattern c,
+    in: ['in1', 'in2']
+    out: 'out'
+    async: true
+    forwardGroups: true
+  , (data, groups, out, callback) ->
+    out.send "1#{data['in1']}#{c.nodeId}2#{data['in2']}#{c.nodeId}"
+    callback()
+
 processAsync = ->
   c = new noflo.Component
   c.inPorts.add 'in',
@@ -39,6 +57,28 @@
     setTimeout ->
       output.sendDone data + c.nodeId
     , 1
+
+processMerge = ->
+  c = new noflo.Component
+  c.inPorts.add 'in1',
+    datatype: 'string'
+  c.inPorts.add 'in2',
+    datatype: 'string'
+  c.outPorts.add 'out',
+    datatype: 'string'
+
+  c.forwardGroups =
+    in1: ['out']
+
+  c.process (input, output) ->
+    return unless input.has 'in1', 'in2', (ip) -> ip.type is 'data'
+    second = input.get 'in2'
+    until second.type is 'data'
+      second = input.get 'in2'
+    first = input.getData 'in1'
+
+    output.sendDone
+      out: "1#{first}#{c.nodeId}2#{second.data}#{c.nodeId}"

 describe 'Scope isolation', ->
   loader = null
@@ -47,7 +87,9 @@
     loader.listComponents (err) ->
       return done err if err
       loader.registerComponent 'wirepattern', 'Async', wirePatternAsync
+      loader.registerComponent 'wirepattern', 'Merge', wirePatternMerge
       loader.registerComponent 'process', 'Async', processAsync
+      loader.registerComponent 'process', 'Merge', processMerge
       done()

   describe 'with WirePattern sending to Process API', ->
@@ -173,3 +215,70 @@
         scope: 'x'
       ins.post new noflo.IP 'closeBracket', 1,
         scope: 'x'
+
+  describe 'pure Process API merging two inputs', ->
+    c = null
+    in1 = null
+    in2 = null
+    out = null
+    before (done) ->
+      fbpData = "
+      INPORT=Pc1.IN:IN1
+      INPORT=Pc2.IN:IN2
+      OUTPORT=PcMerge.OUT:OUT
+      Pc1(process/Async) OUT -> IN1 PcMerge(process/Merge)
+      Pc2(process/Async) OUT -> IN2 PcMerge(process/Merge)
+      "
+      noflo.graph.loadFBP fbpData, (err, g) ->
+        return done err if err
+        loader.registerComponent 'scope', 'Merge', g
+        loader.load 'scope/Merge', (err, instance) ->
+          return done err if err
+          c = instance
+          in1 = noflo.internalSocket.createSocket()
+          c.inPorts.in1.attach in1
+          in2 = noflo.internalSocket.createSocket()
+          c.inPorts.in2.attach in2
+          done()
+    beforeEach ->
+      out = noflo.internalSocket.createSocket()
+      c.outPorts.out.attach out
+    afterEach ->
+      c.outPorts.out.detach out
+      out = null
+
+    it 'should forward new-style brackets as expected', (done) ->
+      expected = [
+        'CONN'
+        '< 1'
+        '< a'
+        'DATA bazWpPc'
+        '>'
+        '>'
+        'DISC'
+      ]
+      received = []
+
+      out.on 'connect', ->
+        received.push 'CONN'
+      out.on 'begingroup', (group) ->
+        received.push "< #{group}"
+      out.on 'data', (data) ->
+        received.push "DATA #{data}"
+      out.on 'endgroup', ->
+        received.push '>'
+      out.on 'disconnect', ->
+        received.push 'DISC'
+        chai.expect(received).to.eql expected
+        done()
+
+      in2.connect()
+      in2.send 'foo'
+      in2.disconnect()
+      in1.connect()
+      in1.beginGroup 1
+      in1.beginGroup 'a'
+      in1.send 'baz'
+      in1.endGroup()
+      in1.endGroup()
+      in1.disconnect()


Removed debug log.

Removed debug log.

--- app/assets/javascripts/app/controllers/navigation.js.coffee
+++ app/assets/javascripts/app/controllers/navigation.js.coffee
@@ -98,14 +98,11 @@

           # load user collection
           if data.load.organizations
-            console.log(999999999)
             for organization_id, organization of data.load.organizations
-              console.log(2222, organization)
               if organization.user_ids
                 organization.users = []
                 for user_id in organization.user_ids
                   user = App.Collection.find( 'User', user_id )
-                  console.log(111, user_id)
                   organization.users.push user
             App.Collection.load( type: 'Organization', data: data.load.organizations )


Let jQuery do the HTML entity encoding work

--- app/assets/javascripts/browse_everything/behavior.js.coffee
+++ app/assets/javascripts/browse_everything/behavior.js.coffee
@@ -23,7 +23,9 @@
       .split('&')
       .map (t) -> t.split('=',2)
     elements = $(fields).map () ->
-      "<input type='hidden' name='#{decodeURIComponent(this[0])}' value='#{decodeURIComponent(this[1])}'/>"
+      $("<input type='hidden'/>")
+        .attr('name',decodeURIComponent(this[0]))
+        .val(decodeURIComponent(this[1]))[0].outerHTML
     $(elements.toArray().join("\n"))

   $.fn.browseEverything = (options) ->
@@ -83,10 +85,10 @@
     event.preventDefault()
     target = $(this).closest('*[data-ev-location]')
     target_form = $('form.ev-submit-form')
-    file_location = target.data('ev-location').replace(/'/g, "'")
+    file_location = target.data('ev-location')
     target.toggleClass('ev-selected')
     if target.hasClass('ev-selected')
-      hidden_input = $("<input type='hidden' class='ev-url' name='selected_files[]' value='#{file_location}'>")
+      hidden_input = $("<input type='hidden' class='ev-url' name='selected_files[]'/>").val(file_location)
       target_form.append(hidden_input)
     else
       $("form.ev-submit-form input[value='#{file_location}']").remove()


dispose existing context

dispose existing context

otherwise we leak contexts, which keep rendering in the background,
and kill fps.

--- app/assets/javascripts/jax/dev/views/webgl_canvas.js.coffee
+++ app/assets/javascripts/jax/dev/views/webgl_canvas.js.coffee
@@ -9,6 +9,9 @@
   initialize: ->
     @render()
     # add to window to give dev a handle on jax context for console debugging
+    # - first dispose the context, if any, else it'll keep rendering in the
+    #   background
+    window.jax?.dispose()
     window.jax = @jax = new Jax.Context canvas: @el
     @startController 'jax'


:art: Lost -> Lots

:art: Lost -> Lots

--- spec/autocomplete-manager-integration-spec.coffee
+++ spec/autocomplete-manager-integration-spec.coffee
@@ -504,7 +504,7 @@

       it "keeps the suggestion list planted at the beginning of the prefix when typing", ->
         overlayElement = null
-        # Lost of x's to keep the margin offset away from the left of the window
+        # Lots of x's to keep the margin offset away from the left of the window
         # See https://github.com/atom-community/autocomplete-plus/issues/399
         editor.insertText('xxxxxxxxxx xx')
         editor.insertText(' ')


Simplify simulation node

Simplify simulation node

--- assets/javascripts/react/components/simulation_nodes.coffee
+++ assets/javascripts/react/components/simulation_nodes.coffee
@@ -18,7 +18,7 @@

   render: ->

-      <SimulationValueArea value={@state.notifications}/>
+      <SimulationValueArea values={@state.notifications}/>

 SimulationValueArea = React.createClass
@@ -26,12 +26,4 @@
   joinValues: R.compose(R.join(', '), R.pluck('value'), R.pluck('value'))

   render: ->
-    values = @props.value
-    if values.length == 0
-      {'No value yet'}
-    else if values.length == 1
-
-        {'Value:'}{values[0].value.value}
-
-    else
-      {'Values!'}{@joinValues(values)}
+     {@joinValues(@props.values)}


Set className property

Set className property

--- src/components/student-dashboard/all-events-by-week.cjsx
+++ src/components/student-dashboard/all-events-by-week.cjsx
@@ -14,6 +14,7 @@
     startAt = moment(week, "YYYYww")
     <EventsPanel
       key={week}
+      className="-weeks-events"
       courseId={@props.courseId}
       events={events}
       startAt={startAt}


Fixed missing initial link drawing when loading a saved file [#142963961]

When using sage.html outside of CODAP the links where not being drawn at startup if a file was loaded via the command line like this: sage.html#file=localStorage:node%20test

This change forces the graph to draw when it is mounted.

--- src/code/views/graph-view.coffee
+++ src/code/views/graph-view.coffee
@@ -35,6 +35,9 @@
       handleClick:          @handleLinkClick
       handleDoubleClick:    @handleLinkEditClick
       handleLabelEdit:      @handleLabelEdit
+
+    # force an initial draw of the connections
+    @_updateToolkit()

     @props.selectionManager.addSelectionListener (manager) =>
       [..., lastLinkSelection] = @state.selectedLink


TeamMembersCommonView: Make fetcher method optional.

--- client/admin/lib/views/members/teammemberscommonview.coffee
+++ client/admin/lib/views/members/teammemberscommonview.coffee
@@ -15,6 +15,7 @@

     options.cssClass                 = 'members-commonview'
     options.itemLimit               ?= 10
+    options.fetcherMethod          or= 'fetchMembers'
     options.noItemFoundWidget      or= new KDCustomHTMLView
     options.listViewItemClass      or= MemberItemView
     options.listViewItemOptions    or= {}
@@ -89,13 +90,14 @@
     @isFetching = yes

     group    = @getData()
+    method   = @getOption 'fetcherMethod'
     options  =
       limit  : @getOptions().itemLimit
       sort   : timestamp: -1 # timestamp is at relationship collection
       skip   : @skip

     # fetch members as jAccount
-    group.fetchMembers {}, options, (err, members) =>
+    group[method] {}, options, (err, members) =>

       return @handleError err  if err


Remove log

Remove log

--- client/home/lib/utilities/components/kdcli/container.coffee
+++ client/home/lib/utilities/components/kdcli/container.coffee
@@ -32,8 +32,6 @@
           "export KONTROLURL=#{KodingKontrol.getKontrolUrl()}; curl -sL https://sandbox.kodi.ng/c/d/kd | bash -s #{token}"
         else "curl -sL https://kodi.ng/d/kd | bash -s #{token}"

-      console.log {cmd}
-
       @setState
         key : key
         cmd : cmd


remove username column from publisher export

--- client/views/pages/publisherActions/publisherActions.coffee
+++ client/views/pages/publisherActions/publisherActions.coffee
@@ -103,7 +103,6 @@
 			i18next.t('profile.congregation')
 			i18next.t('profile.language')
 			i18next.t('profile.languages')
-			i18next.t('input.username')
 			i18next.t('role.role'))

 		csvContent = 'data:text/csv;charset=utf-8,\uFEFF' + head.join(';') + '\r\n'
@@ -124,7 +123,6 @@
 				user.profile.congregation
 				user.profile.language
 				user.profile.languages
-				user.username
 				'"' + Object.keys(user.roles).filter((r) -> user.roles[r][0]?).map((r) -> r + '=' + user.roles[r][0]).join(';') + '"')

 			csvContent += row.join(';') + '\r\n'


refactor HolidayTableFactory to generate HTML for individual rows of a table in a standalone function

--- source/javascripts/index/holidayTableFactory.coffee
+++ source/javascripts/index/holidayTableFactory.coffee
@@ -43,6 +43,12 @@

 rabbenuTamTime = (zmanim) -> moment(zmanim.sunset()).add(72, 'minutes').seconds(60).format('h:mm')

+generateRowHtml = (row) ->
+  if row.time?
+    "#{row.description}	#{row.time}	"
+  else
+    "#{row.description}	"
+
 class HolidayTableFactory
   constructor: (gregorianWeek, hebrewWeek, zmanimWeek, shaharitWeek, minchaWeek) ->
     [@gregorianWeek, @hebrewWeek, @zmanimWeek, @shaharitWeek, @minchaWeek] = [gregorianWeek, hebrewWeek, zmanimWeek, shaharitWeek, minchaWeek]
@@ -50,12 +56,7 @@
     rowData = @generateRows(iterator)
     rows = rowData.rows
     return if 0 == rows.length
-    tableSection = []
-    for row in rows
-      if row.time?
-        tableSection.push("#{row.description}	#{row.time}	")
-      else
-        tableSection.push("#{row.description}	")
+    tableSection = (generateRowHtml(row) for row in rows)
     """

         #{rowData.dayOfWeek}


prevent execution/failure of obsolete callbacks

callback() can modifie callbacks[keypath]
This will lead to an exception when callbacks unsubscribe other callbacks.
demo: http://jsfiddle.net/hpUTB/

--- src/adapters.coffee
+++ src/adapters.coffee
@@ -62,7 +62,7 @@
         set: (newValue) =>
           if newValue isnt value
             value = newValue
-            callback() for callback in callbacks[keypath]
+            callback() for callback in callbacks[keypath].slice() when callback in callbacks[keypath]
             @observeMutations newValue, obj[@id], keypath

     unless callback in callbacks[keypath]


Cache Host stats for 5 secs to avoid unnecessary requests.

--- src/api/host.coffee
+++ src/api/host.coffee
@@ -267,11 +267,15 @@

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

+points = {}

 stats = $coroutine ({host, granularity}) ->
   granularity = if granularity then granularity else 0
   # granularity: 0: every 5 sec along last 10 minutes, 1: every minute along last 2 hours, 2: every hour along past week, 3: everyday along past year
   # see http://xenserver.org/partners/developing-products-for-xenserver/18-sdk-development/96-xs-dev-rrds.html
+
+  if points[host.id] and (Date.now() - points[host.id].timestamp < 5000)
+    return points[host.id]

   # select the AVERAGE values
   granularity = {0:0, 1:1, 2:4, 3:7}[granularity]
@@ -341,9 +345,7 @@
       return
     return

-
-  # the final object
-  return {
+  points[host.id] = {
     memoryFree: memoryFree
     memoryUsed: memoryUsed
     memory: memory
@@ -351,7 +353,11 @@
     cpus: cpus
     pifs: pifs
     load: load
+    timestamp: Date.now()
   }
+
+  # the final object
+  return points[host.id]

 stats.description = 'returns statistic of the host'


Make atom.crashMainProcess and atom.crashRenderProcess work.

--- src/app/atom.coffee
+++ src/app/atom.coffee
@@ -250,10 +250,10 @@
     throw new Error('atom.getUpdateStatus is not implemented')

   crashMainProcess: ->
-    throw new Error('atom.crashMainProcess is not implemented')
+    remote.process.crash()

   crashRenderProcess: ->
-    throw new Error('atom.crashRenderProcess is not implemented')
+    process.crash()

   requireUserInitScript: ->
     userInitScriptPath = fsUtils.join(config.configDirPath, "user.coffee")


Corrige slug para não apagar "ã" e "õ"

Corrige slug para não apagar "ã" e "õ"

--- app/assets/javascripts/angular_app/controllers/app_controller.js.coffee
+++ app/assets/javascripts/angular_app/controllers/app_controller.js.coffee
@@ -52,8 +52,8 @@
       slug = slug.trim().toLowerCase()

       # remove accents, swap ñ for n, etc
-      from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;"
-      to   = "aaaaeeeeiiiioooouuuunc------"
+      from = "àáäâãèéëêìíïîòóöôõùúüûñç·/_,:;"
+      to   = "aaaaaeeeeiiiiooooouuuunc------"
       for char, i in from
         slug = slug.replace(new RegExp(char, 'g'), to.charAt(i))


added notification for deleting

added notification for deleting

--- app/assets/javascripts/application/presenters/contacts_presenter.coffee
+++ app/assets/javascripts/application/presenters/contacts_presenter.coffee
@@ -24,6 +24,7 @@
   destroy: ->
     $(@element).fadeOut 500, ->
       $(@element).remove()
+    toastr.info("Moved to Trash View Trashed Contacts")

   untrash: ->
     # remove from the trash page


bottom margin to match style change

bottom margin to match style change

--- src/components/scores/index.cjsx
+++ src/components/scores/index.cjsx
@@ -63,7 +63,7 @@
   tableHeight: ->
     windowEl = @_getWindowSize()
     table = React.findDOMNode(@refs.tableContainer)
-    bottomMargin = 40
+    bottomMargin = 140
     windowEl.height - table.offsetTop - bottomMargin


bind => on

bind => on

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -25,7 +25,7 @@
   bind: (args...) ->
     self.on(args...)

-  unbind: () ->
+  unbind: (args...) ->
     self.off(args...)

   ###*


print stripe error

print stripe error

--- src/controllers/discounts.coffee
+++ src/controllers/discounts.coffee
@@ -29,6 +29,7 @@
 				if err
 					# res.send {message: "Error with stripe charge."}
 					# return res.statusCode = 400
+					console.error err if err
 					return res.sendStatus(403)
 				generatedCouponCode = getRandomCode()
 				createCoupon data.couponId, generatedCouponCode, (err, result) ->


WindowController: keybindings are done through helper

--- src/core/windowcontroller.coffee
+++ src/core/windowcontroller.coffee
@@ -59,7 +59,8 @@

   bindEvents:->

-    $(window).bind @keyEventsToBeListened.join(' '), @bound "key"
+    for eventName in @keyEventsToBeListened
+      addEventListener eventName, @bound 'key'

     addEventListener 'resize', @bound 'notifyWindowResizeListeners'


changed jquery div names from -Wrap to -border

--- public/dreamcatcher/controllers/admin_controller.coffee
+++ public/dreamcatcher/controllers/admin_controller.coffee
@@ -95,8 +95,8 @@
     # the google.load command needs to be loaded in resources/google.charts.coffee so that it gets loaded before this controller

     $buttonParent = $("##{ev.currentTarget.id}").parent()
-    buttonType = 'user' if $buttonParent.hasClass('userButton-Wrap')
-    buttonType = 'entry' if $buttonParent.hasClass('entryButton-Wrap')
+    buttonType = 'user' if $buttonParent.hasClass('userButton-border')
+    buttonType = 'entry' if $buttonParent.hasClass('entryButton-border')

     if ev.currentTarget.id is 'users-1wk' then google.setOnLoadCallback @charts.getLineChartData('last 7 days in users', buttonType)
     if ev.currentTarget.id is 'users-8wks' then google.setOnLoadCallback @charts.getLineChartData('last 8 weeks in users', buttonType)
@@ -112,7 +112,7 @@
     if ev.currentTarget.id is 'entries-comments' then google.setOnLoadCallback @charts.getLineChartData('last 6 months in comments', buttonType)
     if ev.currentTarget.id is 'entries-tags' then google.setOnLoadCallback @charts.getPieChartData('top 32 tags', buttonType)

-    $(".#{buttonType}Button-Wrap").removeClass('select') # reset all buttons for this buttonType
+    $(".#{buttonType}Button-border").removeClass('select') # reset all buttons for this buttonType
     $buttonParent.addClass('select')

   '#bedsheetsHeader click': (el,ev) ->


ListView: Allow empty header columns.

ListView: Allow empty header columns.

--- src/elements/ListView/ListViewHeaderColumn.coffee
+++ src/elements/ListView/ListViewHeaderColumn.coffee
@@ -19,7 +19,6 @@
 			rotate_90:
 				check: Boolean
 			label:
-				mandatory: true
 				check: (v) ->
 					if CUI.isPlainObject(v) or v instanceof Label
 						true
@@ -32,7 +31,7 @@
 		super()
 		if @_label instanceof Label
 			@__label = @_label
-		else
+		else if @_label
 			@_label.rotate_90 = @_rotate_90
 			@__label = new CUI.defaults.class.Label(@_label)

@@ -68,6 +67,8 @@
 		else
 			arr = []

-		arr.push(@__label.DOM)
+		if @__label
+			arr.push(@__label.DOM)
+
 		arr


Render an undefined date as an empty string

--- src/main/client/grid/grid-directives-pages.coffee
+++ src/main/client/grid/grid-directives-pages.coffee
@@ -11,9 +11,13 @@
         dateFormat = scope.study?.options?.dateFormat || "YYYY-MM-DD"

         formatter = (v) ->
-          moment(v).format(dateFormat)
+          if v?
+            moment(v).format(dateFormat)
+          else
+            ""

         parser = (v) ->
+          return undefined if !v? or v == ""
           value = moment(v, dateFormat)
           if value.isValid()
             value.format("YYYY-MM-DD")


Added namespaces for referencing types and classes in macros

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -2826,17 +2826,37 @@
       (jsArray [serialized])

 quotedReference = (ctx, atom) ->
-  symbol = _symbol atom
+  [ns, symbol] = namespace atom
   compiled = token_ symbol
+
+  # These don't allow builtins to be overriden, for now
   if ctx.isBuiltIn symbol
     compiled.builtin = yes
   else if ctx.isBuiltInDefinition symbol
     compiled.builtInDefinition = yes
-  else if ctx.isDeclaredInTopScopeOrMacro symbol
+  else if (ctx.isDeclaredInTopScopeOrMacro symbol) or (isNotValNS ns)
     compiled.modulePath = ctx.typedModulePath.names
+    compiled.ns = ns
   else
     ctx.doDefer atom, symbol
   compiled
+
+# TODO: figure out combination of namespace and module access
+namespace = (atom) ->
+  symbol = _symbol atom
+  match = symbol.match /(\w+)\/(.+)/
+  if match and ([_, ns, name] = match) and (ns in NAMESPACES)
+    [ns, name]
+  else
+    ['none', symbol]
+
+isTypeNS = (ns) ->
+  ns is 'type'
+
+isNotValNS = (ns) ->
+  ns isnt 'val'
+
+NAMESPACES = ['type', 'class', 'val']

 matchAst = (ctx, ast) ->
   matched = ctx.assignTo()
@@ -3429,6 +3449,7 @@
     translation: malformed ctx, atom, 'Macros cannot be referenced'
   else
     validSymbol = validIdentifier symbol
+    ns = atom.ns
     # TODO: we probably need to defer anyway, because the top level might not be
     #       typed yet when we expand a macro referencing it
     #    more generally we should probably just combine this with nameCompile
@@ -3448,7 +3469,7 @@
         translation: (jsAccess '_', validSymbol)
     else
       # TODO: access from other module
-      throw new Error "NOT SUPPORTED YET"
+      throw new Error "References in macros from other modules not supported yet"

 isQuotedReference = (atom) ->
   atom.builtin or atom.builtInDefinition or atom.modulePath


Age in seconds

Age in seconds

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -40,7 +40,7 @@
     for name, cooldownOptions of I.cooldowns
       {approachBy, target} = cooldownOptions

-      I[name] = I[name].approach(target, approachBy)
+      I[name] = I[name].approach(target, approachBy * dt)

   cooldown: (name, options={}) ->
     {target, approachBy, value} = options


Sections work

Sections work

--- src/digester.coffee
+++ src/digester.coffee
@@ -14,41 +14,43 @@
       for filename, fileData of files
         @current.filename = filename
         @current.objects = fileData.objects
+        @current.sections = @extractSections(fileData.objects)
+
         for row, columnsObj of fileData.objects
           for column, object of columnsObj
             switch object.type
               when 'class'
                 klass = @digestClass(object)
                 classes.push klass if klass?
-              # when 'comment'

     {classes}

-  digestClass: (klass) ->
-    classDoc = @docFromDocString(klass.doc)
-    return unless classDoc and classDoc.visibility is 'Essential'
+  digestClass: (classEntity) ->
+    classDoc = @docFromDocString(classEntity.doc)
+    return unless classDoc

-    classMethods = @extractEntities(klass.classProperties, 'function')
-    instanceMethods = @extractEntities(klass.prototypeProperties, 'function')
+    sections = @filterSectionsForRowRange(classEntity.range[0][0], classEntity.range[1][0])
+    classMethods = @extractEntities(sections, classEntity.classProperties, 'function')
+    instanceMethods = @extractEntities(sections, classEntity.prototypeProperties, 'function')
+
+    # Only sections that are used should be in the output
+    filteredSections = []
+    for section in sections
+      for method in classMethods.concat instanceMethods
+        if section.name is method.sectionName
+          filteredSections.push(section)
+          break

     {
-      name: klass.name
+      name: classEntity.name
       filename: @current.filename
       visibility: classDoc.visibility
+      sections: filteredSections
       classMethods: classMethods
       instanceMethods: instanceMethods
     }

-  extractEntities: (entityPositions, entityType) ->
-    entities = []
-    for entityPosition in entityPositions
-      entityObject = @objectFromPosition(entityPosition)
-      if entityObject.type is entityType
-        entity = @digestEntity(entityObject, entityPosition)
-        entities.push entity if entity?
-    entities
-
-  digestEntity: (entity, entityPosition) ->
+  digestEntity: (sections, entity, entityPosition) ->
     doc = @docFromDocString(entity.doc)
     return unless doc?

@@ -56,12 +58,53 @@
       name: entity.name
       visibility: doc.visibility
       summary: doc.summary
+      sectionName: @sectionNameForRow(sections, entityPosition[0])
       srcUrl: @linkForPosition(entityPosition)
     }

   ###
   Section: Utils
   ###
+
+  extractEntities: (sections, entityPositions, entityType) ->
+    entities = []
+    for entityPosition in entityPositions
+      entityObject = @objectFromPosition(entityPosition)
+      if entityObject.type is entityType
+        entity = @digestEntity(sections, entityObject, entityPosition)
+        entities.push entity if entity?
+    entities
+
+  extractSections: (objects) ->
+    sections = []
+    for row, columnsObj of objects
+      for column, object of columnsObj
+        if object.type is 'comment'
+          section = @sectionFromCommentEntity(object)
+          sections.push section if section?
+    sections
+
+  sectionFromCommentEntity: (commentEntity) ->
+    doc = atomdoc.parse(commentEntity.doc)
+    if doc?.visibility is 'Section'
+      name: doc.summary
+      description: doc.description?.replace(doc.summary, '').trim() ? ''
+      startRow: commentEntity.range[0][0]
+      endRow: commentEntity.range[1][0]
+    else
+      null
+
+  filterSectionsForRowRange: (startRow, endRow) ->
+    sections = []
+    for section in @current.sections
+      sections.push section if section.startRow >= startRow and section.startRow <= endRow
+    sections.sort (sec1, sec2) -> sec1.startRow - sec2.startRow
+    sections
+
+  sectionNameForRow: (sections, row) ->
+    for section in sections
+      return section.name if row > section.startRow
+    null

   docFromDocString: (docString) ->
     classDoc = atomdoc.parse(docString) if docString?
@@ -78,7 +121,7 @@

     repo = @current.package.repository.replace(/\.git$/i, '')
     filePath = path.normalize "/blob/v#{@current.package.version}/#{@current.filename}"
-    "#{repo}#{filePath}#L#{position[0]}"
+    "#{repo}#{filePath}#L#{position[0] + 1}"

 module.exports =
   digest: (metadata) ->


When canceling an upload, processQueue() is called now.

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -697,6 +697,7 @@
     else if file.status in [ Dropzone.ADDED, Dropzone.ACCEPTED ]
       file.status = Dropzone.CANCELED
       @filesQueue = without(@filesQueue, file)
+    @processQueue()

   uploadFile: (file) ->
     xhr = new XMLHttpRequest()


Throw when backend responds with unexpected status

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -29,6 +29,10 @@
         'Cookie': @cookies.join("; ")

     @request = https.get options, (res) =>
+      if res.statusCode > 500
+        @emit "error", res.statusCode, "Backend connection failed"
+        return
+
       buffer = ""
       res.on "data", (data) =>
         chunk = data.toString("utf8")


Give out more info about the users.

Give out more info about the users.

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -145,12 +145,7 @@
       res.on "end", =>
         json = JSON.parse(data.toString("utf8"))
         json.users.forEach (flow_user) =>
-          flow = @flows.filter((flow) -> return flow.slug == flowSlug)[0]
-          user =
-            id: flow_user.user.id
-            name: flow_user.user.nick
-            flow: flow
-          @users.push(user)
+          @users.push(flow_user)
         callback(@users)
     request.end()


calc total time

calc total time

--- src/gittimer.coffee
+++ src/gittimer.coffee
@@ -3,14 +3,57 @@
 childProcess = require "child_process"
 spawn = childProcess.spawn

+defaultPauseTime = 20
+defaultInitTime = 10
+
+parsePauseTime = (val) ->
+	console.log "parse"
+	if val?
+		parseInt val
+	else
+		defaultPauseTime
+
+parseInitTime = (val) ->
+	console.log "parse"
+	if val?
+		parseInt val
+	else
+		defaultPauseTime
+
 program
 	.version('0.1.0')
-	.option('-d, --directory [dir]', 'directory to analyse', '.')
-	.option('-u, --user <email>', 'user email adress to filter')
-	.option('-t, --time [time]', 'git log since compatible time')
+	.option("-d, --directory [dir]", "directory to analyse", ".")
+	.option("-u, --user <email>", "user email adress to filter")
+	.option("-t, --time [time]", "git log since compatible time")
+	.option("-p, --pause <pause>", "max pause time in minutes (default: #{defaultPauseTime})", parsePauseTime)
+	.option("-i, --init <init>", "init time in minutes (default: #{defaultInitTime})", parseInitTime)
+
+
+formatTime = (sum) ->
+	[Math.floor( sum / 60 / 60), Math.floor(Math.floor(sum / 60) % 60), sum % 60]
+
+calcTime = (data) ->
+	pause = program.pause * 60
+	init = program.init * 60

-calcTime = (data) ->
-
+	sum = 0
+	for i in [0...data.length]
+		commit = data[i]
+		if not data[i+1]?
+			commit.effort = init
+		else
+			prev = data[i+1]
+			if prev.time + pause < commit.time
+				commit.effort = init
+			else
+				commit.effort = commit.time - prev.time
+		sum+=commit.effort
+		#console.log commit.hash, commit.time, commit.effort / 1000 / 60
+
+	[h,m,s] = formatTime sum
+	console.log "#{h}:#{m}:#{s}"
+
+	process.exit()

 parseLog = (log, user) ->
 	lines = log.split "\n"
@@ -22,7 +65,7 @@
 			data.push
 				hash: hash,
 				subject: subject
-				time: Date.parse(time) / 1000
+				time: Date.parse(time) / 1000
 	console.log "Found\t\t", data.length, "commits"
 	calcTime data

@@ -48,8 +91,12 @@
 	if err?
 		console.log "not a real path"
 		process.exit()
-	else
-		if not program.user
+	else
+		if not program.pause?
+			program.pause = defaultPauseTime
+		if not program.init?
+			program.init = defaultInitTime
+		if not program.user?
 			program.prompt 'Email: ', (email) ->
 				start email, path
 		else


insights: Download all current data

insights: Download all current data

--- src/insights.coffee
+++ src/insights.coffee
@@ -24,7 +24,7 @@
   end = end.toISOString()

   limit = 999
-  query = "SELECT role,jobs,workers,app from GuvScaled SINCE '#{start}' UNTIL '#{end}' LIMIT #{limit}"
+  query = "SELECT jobs,workers,drainrate,fillrate,consumers,role,app,timestamp from GuvScaled SINCE '#{start}' UNTIL '#{end}' LIMIT #{limit}"
   insights.query query, (err, body) ->
     return callback err if err


Update cpp.coffee

Update cpp.coffee

исправил опечатку

--- server/materials/data/topics/cpp.coffee
+++ server/materials/data/topics/cpp.coffee
@@ -8,7 +8,7 @@
 module24698 = () ->
     page("Про язык C++", String.raw"""

Про язык C++

-

Если вы добрались до сюда, но еще пишете на паскале или на питоне, то, возможно, вам стоит потихоньку переходить на C++. Это не обозначает, что надо прямо сейчас все бросать и изучать новый язык программирования; это сильно зависит, во-первых, от вас, во-вторых, от текущего времени года. Я советую переходить на новый язык программирования весной-летом, когда основные олимпиады одного года уже закончились, а олимпиады следующего года еще не начались, но это также сильно зависит от сам самих (11-классникам, возможно, нет большого смысла переходить на C++ в школе — вас ему научат в университете, и т.п.) Поэтому прежде чем переходить на C++, посоветуйтесь со мной.

+

Если вы добрались до сюда, но еще пишете на паскале или на питоне, то, возможно, вам стоит потихоньку переходить на C++. Это не обозначает, что надо прямо сейчас все бросать и изучать новый язык программирования; это сильно зависит, во-первых, от вас, во-вторых, от текущего времени года. Я советую переходить на новый язык программирования весной-летом, когда основные олимпиады одного года уже закончились, а олимпиады следующего года еще не начались, но это также сильно зависит от вас самих (11-классникам, возможно, нет большого смысла переходить на C++ в школе — вас ему научат в университете, и т.п.) Поэтому прежде чем переходить на C++, посоветуйтесь со мной.

На самом деле даже уровень 3Б — это немного рановато для перехода на C++, вполне можно и на паскале и на питоне заниматься и на более высоких уровнях, но в любом случае, если вы добрались до сюда, то поговорите про C++ со мной. Помимо уровня, у меня есть еще один критерий: переходить на C++ имеет смысл, если вы с довольно высокой вероятностью станете призерами областной олимпиады; но, опять-таки, этот критерий не строгий.

@@ -29,4 +29,4 @@
             module24698(),
         ]),
         count: false
-    }+    }


menus: Create Episodes and Seasons menus

--- src/js/menus.coffee
+++ src/js/menus.coffee
@@ -355,12 +355,21 @@
     super
     @menu = createDefaultMenu()
     @menu.on 'select', (e) =>
+      element = e.item
       data = e.item.data

+      displaySubtitle = (text) =>
+        element.subtitle = text
+        @menu.item e.sectionIndex, e.itemIndex, element
+
+      displaySubtitle "Loading..."
+
       show = i for i in @shows when i.show.ids.trakt == data.showID

-      seasonsMenu = createSeasonsMenu(data.showID, data.showTitle, show.seasons)
-      seasonsMenu.show()
+      seasonsMenu = new Seasons data.showID, data.showTitle, show.seasons
+      seasonsMenu.whenReady (err) =>
+        seasonsMenu.show()
+        displaySubtitle ""

   update: (@shows) ->
     console.log "Updating MyShows"
@@ -380,79 +389,83 @@

 menus.MyShows = MyShows

-createEpisodesMenu = (showTitle, episodes) ->
-  episodesMenu = createDefaultMenu(
-    sections: [{
-      items:
-        {
-          title: episode.title
-          subtitle: "Season #{episode.season} Ep. #{episode.number}"
-          data:
-            episodeTitle: episode.title
-            overview: episode.overview
-            seasonNumber: episode.season
-            episodeNumber: episode.number
-        } for episode in episodes
-    }]
-  )
-  episodesMenu.on 'select', (e) ->
-    data = e.item.data
-    detailedItemCard = createDefaultCard(
-      title: showTitle
-      subtitle: "Season #{data.seasonNumber} Ep. #{data.episodeNumber}"
-      body: "Title: #{data.episodeTitle}\n\
-             Overview: #{data.overview}"
+class Episodes extends Menu
+  constructor: (showTitle, episodes) ->
+    super
+    @menu = createDefaultMenu(
+      sections: [{
+        items:
+          {
+            title: episode.title
+            subtitle: "Season #{episode.season} Ep. #{episode.number}"
+            data:
+              episodeTitle: episode.title
+              overview: episode.overview
+              seasonNumber: episode.season
+              episodeNumber: episode.number
+          } for episode in episodes
+      }]
     )
-
-    detailedItemCard.show()
-
-  episodesMenu
-
-menus.createEpisodesMenu = createEpisodesMenu
-
-createSeasonsMenu = (showID, showTitle, seasons) ->
-  seasonsMenu = createDefaultMenu(
-    sections: [{
-      items:
-        {
-          title: "Season #{season.number}"
-          data:
-            seasonNumber: season.number
-        } for season in seasons
-    }]
-  )
-
-  seasonsMenu.on 'select', (e) ->
-    element = e.item
-    data = e.item.data
-
-    displaySubtitle = (text) =>
-      element.subtitle = text
-      seasonsMenu.item e.sectionIndex, e.itemIndex, element
-
-    originalSubtitle = element.subtitle
-    displaySubtitle "Loading..."
-
-    season = s for s in seasons when s.number == data.seasonNumber
-
-    async.map(
-      season.episodes,
-      (ep, cb) ->
-        trakttv.getEpisodeData showID, data.seasonNumber, ep.number,
-          (err, episode) ->
-            # TODO: maybe we can print something else?
-            if err?
-              episode = {}
-            cb(null, episode)
-      (err, episodes) ->
-        episodesMenu = createEpisodesMenu showTitle, episodes
-        episodesMenu.show()
-
-        displaySubtitle originalSubtitle
+    @menu.on 'select', (e) ->
+      data = e.item.data
+      detailedItemCard = createDefaultCard(
+        title: showTitle
+        subtitle: "Season #{data.seasonNumber} Ep. #{data.episodeNumber}"
+        body: "Title: #{data.episodeTitle}\n\
+               Overview: #{data.overview}"
+      )
+
+      detailedItemCard.show()
+
+    @readyEmitter.notify()
+
+menus.Episodes = Episodes
+
+class Seasons extends Menu
+  constructor: (showID, showTitle, seasons) ->
+    super
+    @menu = createDefaultMenu(
+      sections: [{
+        items:
+          {
+            title: "Season #{season.number}"
+            data:
+              seasonNumber: season.number
+          } for season in seasons
+      }]
     )
-  seasonsMenu
-
-menus.createSeasonsMenu = createSeasonsMenu
+
+    @menu.on 'select', (e) =>
+      element = e.item
+      data = e.item.data
+
+      displaySubtitle = (text) =>
+        element.subtitle = text
+        @menu.item e.sectionIndex, e.itemIndex, element
+
+      originalSubtitle = element.subtitle
+      displaySubtitle "Loading..."
+
+      season = s for s in seasons when s.number == data.seasonNumber
+
+      async.map(
+        season.episodes,
+        (ep, cb) =>
+          trakttv.getEpisodeData showID, data.seasonNumber, ep.number,
+            (err, episode) =>
+              # TODO: maybe we can print something else?
+              if err?
+                episode = {}
+              cb(null, episode)
+        (err, episodes) =>
+          episodesMenu = new Episodes showTitle, episodes
+          episodesMenu.whenReady (err) =>
+            episodesMenu.show()
+            displaySubtitle originalSubtitle
+      )
+    @readyEmitter.notify()
+
+menus.Seasons = Seasons

 class Main extends Menu
   constructor: (TimeFormatAccessor, initSettings, fetchData) ->


precompile regexen where possible

precompile regexen where possible

--- chrome/content/zotero-better-bibtex/BetterBibTeXPatternFormatter.coffee
+++ chrome/content/zotero-better-bibtex/BetterBibTeXPatternFormatter.coffee
@@ -8,6 +8,7 @@
     punct: Zotero.Utilities.XRegExp('\\p{Pc}|\\p{Pd}|\\p{Pe}|\\p{Pf}|\\p{Pi}|\\p{Po}|\\p{Ps}', 'g')
     caseNotUpperTitle: Zotero.Utilities.XRegExp('[^\\p{Lu}\\p{Lt}]', 'g')
     caseNotUpper: Zotero.Utilities.XRegExp('[^\\p{Lu}]', 'g')
+    word: Zotero.Utilities.XRegExp("[\\p{L}\\p{Nd}\\{Pc}\\p{M}]+", 'g')

   format: (item) ->
     @item = Zotero.BetterBibTeX.serialized.get(item)
@@ -58,7 +59,7 @@
     return safe

   words: (str) ->
-    return (@clean(word) for word in Zotero.Utilities.XRegExp.matchChain(@innerText(str), [Zotero.Utilities.XRegExp("[\\p{L}\\p{Nd}\\{Pc}\\p{M}]+", "g")]) when word != '')
+    return (@clean(word) for word in Zotero.Utilities.XRegExp.matchChain(@innerText(str), [@re.word]) when word != '')

   # three-letter month abbreviations. I assume these are the same ones that the
   # docs say are defined in some appendix of the LaTeX book. (i don't have the


added aliases for voidValue: toFromVoid, fromToVoid, void (I just can't deside which I like best. toFromVoid feels most natural right now.)

--- src/art/engine/animation/persistant_animator.coffee
+++ src/art/engine/animation/persistant_animator.coffee
@@ -9,6 +9,7 @@
   plainObjectsDeepEq
   isPlainObject
   eq
+  rubyOr
 } = Foundation
 {EventedObject} = Events

@@ -356,9 +357,9 @@
     @_element = null
     @_animate = options.animate
     @_continuous = options.continuous
-    @_voidValue = options.voidValue
-    @_toVoid = options.toVoid
-    @_fromVoid = options.fromVoid
+    @_voidValue = rubyOr options.voidValue, options.toFromVoid, options.fromToVoid, options.void
+    @_toVoid    = options.toVoid
+    @_fromVoid  = options.fromVoid
     @on options.on if options?.on

   @getter


documents removeData function

documents removeData function

	modified:   vendor/assets/javascripts/plinth/plugins/tbg-snowplow.js.coffee

--- vendor/assets/javascripts/plinth/plugins/tbg-snowplow.js.coffee
+++ vendor/assets/javascripts/plinth/plugins/tbg-snowplow.js.coffee
@@ -53,6 +53,11 @@
       evData = ['trackStructEvent'].concat data
       _snaq?.push evData

+  # Remove data and assoc attributes from DOM element
+  #
+  # @param $el [DOMElement] jQuery-wrapped DOM el
+  # @param ev [String] Data to be removed
+  #
   removeData = ($el, ev) ->
     $el.removeAttr('data-' + ev)
     $el.removeData(ev)


Update to AggregateRouter

Update to AggregateRouter

--- source/server/users/users-router.coffee
+++ source/server/users/users-router.coffee
@@ -1,4 +1,4 @@
-class Space.accounts.UsersRouter extends Space.eventSourcing.Router
+class Space.accounts.UsersRouter extends Space.eventSourcing.AggregateRouter

   aggregate: Space.accounts.User
   initializingCommand: Space.accounts.CreateUser


Clean up Listener

Clean up Listener

--- src/listener.coffee
+++ src/listener.coffee
@@ -1,6 +1,6 @@
+{inspect} = require 'util'
+
 {TextMessage} = require './message'
-
-inspect = require('util').inspect;

 class Listener
   # Listeners receive every message from the chat source and decide if they
@@ -21,15 +21,17 @@
   # Returns a boolean of whether the matcher matched.
   call: (message) ->
     if match = @matcher message
-      @robot.logger.debug "Message '#{message}' matched regex /#{inspect @regex}/" if @regex
+      @robot.logger.debug \
+        "Message '#{message}' matched regex /#{inspect @regex}/" if @regex
+
       @callback new @robot.Response(@robot, message, match)
       true
     else
       false

 class TextListener extends Listener
-  # TextListeners receive every message from the chat source and decide if they want
-  # to act on it.
+  # TextListeners receive every message from the chat source and decide if they
+  # want to act on it.
   #
   # robot    - A Robot instance.
   # regex    - A Regex that determines if this listener should trigger the


not really... ambiguates purpose of capsule._uuid

--- spec/notice/capsule/capsule_spec.coffee
+++ spec/notice/capsule/capsule_spec.coffee
@@ -14,10 +14,10 @@
     it 'can set the uuid at creation (and it sticks)', (done) ->

         Capsule  = capsule()
-        instance = new Capsule uuid: 'https://cluster.dc.city.domain.com/entity/:id:'
-        instance._uuid.should.equal 'https://cluster.dc.city.domain.com/entity/:id:'
+        instance = new Capsule uuid: 'assigned'
+        instance._uuid.should.equal 'assigned'
         instance._uuid = 'erg2q3'
-        instance._uuid.should.equal 'https://cluster.dc.city.domain.com/entity/:id:'
+        instance._uuid.should.equal 'assigned'
         done()


Decode HTML entities

Decode HTML entities

--- src/scripts/tweet-content.coffee
+++ src/scripts/tweet-content.coffee
@@ -2,16 +2,14 @@
 #   Detect tweet URL and send tweet content
 #
 # Dependencies:
-#   None
+#  "ntwitter": "0.2.10"
+#  "underscore": "1.5.1"
 #
 # Configuration:
 #   HUBOT_TWITTER_CONSUMER_KEY
 #   HUBOT_TWITTER_CONSUMER_SECRET
 #   HUBOT_TWITTER_ACCESS_TOKEN_KEY
 #   HUBOT_TWITTER_ACCESS_TOKEN_SECRET
-#
-# Dependencies:
-#  "ntwitter" : "0.2.10",
 #
 # Commands:
 #   None
@@ -20,6 +18,7 @@
 #   Vrtak-CZ, kdaigle

 ntwitter = require 'ntwitter'
+_ = require 'underscore'

 module.exports = (robot) ->
   auth =
@@ -42,7 +41,7 @@
         console.log err
         return

-      tweet_text = tweet.text
+      tweet_text = _.unescape(tweet.text)
       if tweet.entities.urls?
         for url in tweet.entities.urls
           tweet_text = tweet_text.replace(url.url, url.expanded_url)


Fix crash !

Fix crash !

--- src/server/middleware/api.coffee
+++ src/server/middleware/api.coffee
@@ -2,6 +2,7 @@
 restify = require("restify")
 nodemailer = require("nodemailer")
 mailgunApiTransport = require("nodemailer-mailgunapi-transport")
+User = require("../models/user")

 exports.activate = (customerId, plan, billingType, callback) ->
     User.findOne


Add inline help

Add inline help

--- lib/ide-haskell-repl-view.coffee
+++ lib/ide-haskell-repl-view.coffee
@@ -23,6 +23,7 @@
     @output.getDecorations(class: 'cursor-line', type: 'line')[0].destroy()
     @output.setGrammar \
       atom.grammars.grammarForScopeName 'text.tex.latex.haskell'
+    @element.appendChild @helpDiv = document.createElement 'div'
     @element.appendChild @errDiv = document.createElement 'pre'
     @element.appendChild @promptDiv = document.createElement 'div'
     @element.appendChild @editorDiv = document.createElement 'div'
@@ -33,6 +34,11 @@
     @editor.setLineNumberGutterVisible(false)
     @editor.setGrammar \
       atom.grammars.grammarForScopeName 'source.haskell'
+
+    @helpDiv.innerText = """
+    To submit command, use shift+enter
+    To navigate though command history, use shift+↑ and shift+↓
+    """

     setTimeout (=>@editorElement.focus()),100

@@ -51,6 +57,7 @@
       cwd: atom.project.getDirectories()[0].getPath()

     @ghci.onResponse (response) =>
+      @helpDiv.remove()
       @log response

     @ghci.onError (error) =>


hub: create indices concurrently -- such that a table does not end up being locked for minutes

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -983,7 +983,7 @@
         f = (info, cb) =>
             query = info.query
             # Shorthand index is just the part in parens.
-            query = "CREATE INDEX #{info.name} ON #{table} #{query}"
+            query = "CREATE INDEX CONCURRENTLY #{info.name} ON #{table} #{query}"
             @_query
                 query : query
                 cb    : cb
@@ -1128,7 +1128,7 @@
                     switch task.action
                         when 'create'
                             @_query
-                                query : "CREATE INDEX #{task.name} ON #{table} #{task.query}"
+                                query : "CREATE INDEX CONCURRENTLY #{task.name} ON #{table} #{task.query}"
                                 cb    : cb
                         when 'delete'
                             @_query


coffe script if

coffe script if

--- lib/assets/javascripts/excel-i-o/export/main.js.coffee.cjsx
+++ lib/assets/javascripts/excel-i-o/export/main.js.coffee.cjsx
@@ -98,9 +98,9 @@

{rulesViews}
-          { @availableFieldNames().length > 0 ?
+          { if (@availableFieldNames().length > 0) then
             Добавить столбец
-           : null}
+           else null}

{samples}


removed debug info

removed debug info

--- template/base/src/app/controllers/main_controller.coffee
+++ template/base/src/app/controllers/main_controller.coffee
@@ -6,5 +6,4 @@
     super

   home: ->
-    console.log "a"
     $('body').html app.views.home.render().el


Increase timeout for test.

Increase timeout for test.

--- test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee
+++ test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee
@@ -210,7 +210,7 @@
 				throw error if error?
 				DocUpdaterClient.sendUpdates @project_id, @doc_id, updates, (error) =>
 					throw error if error?
-					setTimeout done, 1000
+					setTimeout done, 2000

 		after ->
 			MockTrackChangesApi.flushDoc.restore()


Always subtract @scrollTop

Always subtract @scrollTop

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -977,11 +977,12 @@
           charLength = 1
           valueIndex++

-        return {top, left} if column is targetColumn
+        break if column is targetColumn

         left += characterWidths[char] ? baseCharacterWidth unless char is '\0'
         column += charLength
-    {top, left}
+
+    {top: top - @scrollTop, left}

   hasPixelRectRequirements: ->
     @hasPixelPositionRequirements() and @scrollWidth?
@@ -1218,7 +1219,7 @@

     if spannedRows is 1
       [
-        top: startPixelPosition.top - @scrollTop
+        top: startPixelPosition.top
         height: lineHeightInPixels
         left: startPixelPosition.left
         width: endPixelPosition.left - startPixelPosition.left
@@ -1228,7 +1229,7 @@

       # First row, extending from selection start to the right side of screen
       regions.push(
-        top: startPixelPosition.top - @scrollTop
+        top: startPixelPosition.top
         left: startPixelPosition.left
         height: lineHeightInPixels
         right: 0
@@ -1237,7 +1238,7 @@
       # Middle rows, extending from left side to right side of screen
       if spannedRows > 2
         regions.push(
-          top: startPixelPosition.top + lineHeightInPixels - @scrollTop
+          top: startPixelPosition.top + lineHeightInPixels
           height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels
           left: 0
           right: 0
@@ -1246,7 +1247,7 @@
       # Last row, extending from left side of screen to selection end
       if screenRange.end.column > 0
         regions.push(
-          top: endPixelPosition.top - @scrollTop
+          top: endPixelPosition.top
           height: lineHeightInPixels
           left: 0
           width: endPixelPosition.left


reference book self locator was wrong

reference book self locator was wrong

--- test-integration/helpers/ui/reference-book.coffee
+++ test-integration/helpers/ui/reference-book.coffee
@@ -19,7 +19,7 @@
 class ReferenceBook extends TestHelper
   constructor: (test, testElementLocator) ->

-    testElementLocator ?= css: '.page-wrapper .page.has-html'
+    testElementLocator ?= css: '.reference-book'
     super test, testElementLocator, COMMON_ELEMENTS, defaultWaitTime: 10000

   waitUntilExercisesLoaded: =>


fix: incorrect css path

fix: incorrect css path

--- lib/revealjs-preview-view.coffee
+++ lib/revealjs-preview-view.coffee
@@ -45,12 +45,19 @@
     @subscriptions.add @editor.onDidSave(changeHandler)
     #@subscriptions.add @editor.onDidChange(changeHandler)

+  getRevealjsPrevewDirPath: ->
+    filtered = atom.packages.getAvailablePackagePaths().filter (x) -> x.indexOf('revealjs-preview') > -1
+    _.first(filtered)
+
   renderSlide: ->
     @showLoading()
+
+    package_dir_path = @getRevealjsPrevewDirPath()
+
     text = @resolvePath(@editor.getText())
     css = """
-    <link rel="stylesheet" href="./css/reveal.css" type="text/css" />
-    <link rel="stylesheet" href="./css/theme/black.css" id="theme" />
+    <link rel="stylesheet" href="#{package_dir_path}/css/reveal.css" type="text/css" />
+    <link rel="stylesheet" href="#{package_dir_path}/css/theme/black.css" id="theme" />
     """
     @html """
 <div class="reveal">


settings.open for tinymce

settings.open for tinymce

--- app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
+++ app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
@@ -35,9 +35,6 @@
         $uploader = $(@)
         uploader = $uploader.data()

-        prefix = uploader.prefix
-        throw "prefix isn't defined" unless prefix?
-
         multiselect = settings.multiselect or uploader.multiselect

         uploaderId = $.md5(Math.random())[0..3]
@@ -45,6 +42,10 @@
         else $uploader.attr 'id', uploaderId

         getUrl = ->
+          # "Живой" префикс
+          prefix = $uploader.data('prefix')
+          throw "prefix isn't defined" unless prefix?
+
           url = "
             #{settings.host}/#
             v2/
@@ -55,36 +56,35 @@
           ".replace /\ /g, ''

         openUploader = ->
-          popupWindow = open getUrl(), 'NMD Media Lib', 'scrollbars=1, width=800, height=500'
-          popupWindow.focus()
+          $uploaderIframes = $uploader.find('.js-uploader-iframe')
+          if $uploaderIframes.length
+            iframeTemplate = _.template '''
+              <iframe width='<%= width %>' height='<%= height %>' src='<%= src %>' frameborder='0'>
+            '''

-        openUploader() if settings.open
+            $uploader.find('.js-uploader-open').on 'click', ->
+              $uploaderIframes.each ->
+                $iframe = $ @
+                iframeData = $iframe.data()
+                $iframe.html iframeTemplate
+                  src: getUrl()
+                  width: iframeData.width or '100%'
+                  height: iframeData.height or 500

-        # Вешаем открытие окна с загрузчиком на кнопку с классом js-uploader-open
-        $uploader.find('.js-uploader-open').on 'click', (e) ->
-          e.preventDefault()
-          e.stopPropagation()
-          openUploader()
+            # Для TinyMCE id модального окна передаётся в параметре
+            if settings.open
+              $("##{settings.open}").modal('show') if $.fn.modal?
+          else
+            popupWindow = open getUrl(), 'NMD Media Lib', 'scrollbars=1, width=800, height=500'
+            popupWindow.focus()

-        # Инициализируем iframe с загрузчиком
-        $uploaderIframe = $uploader.find('.js-uploader-iframe')
-        if $uploaderIframe.length
-          iframeTemplate = _.template '''
-            <iframe width='<%= width %>' height='<%= height %>' src='<%= src %>' frameborder='0'>
-          '''
-          initIframes = ($iframes) ->
-            $iframes.each ->
-              $iframe = $ @
-              iframeData = $iframe.data()
-              $iframe.html iframeTemplate
-                src: getUrl()
-                width: iframeData.width or '100%'
-                height: iframeData.height or 500
-          $uploaderIframeOpen = $uploader.find('.js-uploader-iframe-open')
-          if $uploaderIframeOpen.length
-            $uploaderIframeOpen.on 'click', -> initIframes($uploaderIframe)
-          else
-            initIframes($uploaderIframe)
+        if settings.open
+          openUploader()
+        else
+          $uploader.find('.js-uploader-open').on 'click', (e) ->
+            e.preventDefault()
+            e.stopPropagation()
+            openUploader()

     select: (options) ->
       settings = _.extend {}, options


minor payment change

minor payment change

--- app/assets/javascripts/views/payments/payments_form.js.coffee
+++ app/assets/javascripts/views/payments/payments_form.js.coffee
@@ -25,7 +25,7 @@

     if selected_opt.attr('data-arbitrary') > 0
       amount = selected_opt.find('input').val() * 100
-      desc = "#{selected_opt.attr('data-label')} $#{amount/100.00}"
+      desc = "charge $#{amount/100.00}"
     else
       amount = selected_opt.attr('data-amount')
       desc = selected_opt.find('h3').text()


cache 'everyone' search result in new conversations

fixes CNVS-12865

test plan
- as a teacher in new conversations, compose a new message
- select 'all in course' from the people picker drop down
- ensure that 'all in course' can be selected
- ensure that the message can be sent and is delivered to all
 users in the course

Change-Id: I6f2e0ad30ca2a09e52b4b5b764f3c21ecc0628f0
Reviewed-on: https://gerrit.instructure.com/34291
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Jon Willesen <eb5ce5e6a76c243326fecfa94534101600f87bd9@instructure.com>
QA-Review: Steven Shepherd <e794458448443812fb2685675aab3bbd1a3891bc@instructure.com>
Product-Review: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>

--- app/coffeescripts/views/conversations/AutocompleteView.coffee
+++ app/coffeescripts/views/conversations/AutocompleteView.coffee
@@ -271,9 +271,9 @@
     # Returns nothing.
     _onSearchResultLoad: =>
       @cache[@currentUrl] = @resultCollection.toJSON()
-      @resultCollection.each @_addToModelCache
       _.extend(@permissions, @_getPermissions())
       @_addEveryoneResult(@resultCollection) unless @excludeAll or !@_canSendToAll()
+      @resultCollection.each @_addToModelCache
       shouldDrawResults = @resultCollection.length
       isFinished        = !@nextRequest
       @_addBackResult(@resultCollection)


Adjust line thickness for "more and more" [#116538881]

--- src/code/models/relation-factory.coffee
+++ src/code/models/relation-factory.coffee
@@ -54,7 +54,7 @@
     text: tr "~NODE-RELATION-EDIT.MORE_AND_MORE"
     postfixIco: "more-and-more"
     formulaFrag: "min(exp(in/21.7)-1, maxOut)"
-    magnitude: 3
+    magnitude: 2
     func: (scope) ->
       return Math.min(Math.exp(scope.in/21.7)-1, scope.maxOut)


syntax convs in the document

syntax convs in the document

--- library/membrane/document.coffee
+++ library/membrane/document.coffee
@@ -50,8 +50,8 @@
         return @$notes if arguments.length is 0
         noNotes = "The notes is not a string"
         assert _.isString(notes), noNotes
-        @emit "notes", arguments...
-        @$notes = notes.toString()
+        @emit.call this, "notes", arguments...
+        return @$notes = notes.toString()

     # Either get or set the inputs of the method that is being
     # described by this document. If you do not supply inputs
@@ -61,8 +61,8 @@
         return @$inputs if arguments.length is 0
         noInputs = "The inputs is not a string"
         assert _.isString inputs, noInputs
-        @emit "inputs", arguments...
-        @$inputs = inputs.toString()
+        @emit.call this, "inputs", arguments...
+        return @$inputs = inputs.toString()

     # Either get or set the outputs of the method that is being
     # described by this document. If you do not supply outputs
@@ -72,7 +72,7 @@
         return @$outputs if arguments.length is 0
         noOutputs = "The outputs is not a string"
         assert _.isString(outputs), noOutputs
-        @emit "outputs", arguments...
+        @emit.call this, "outputs", arguments...
         @$outputs = outputs.toString()

     # Either get or set the description of the method that is being
@@ -83,8 +83,8 @@
         return @$synopsis if arguments.length is 0
         noSynopsis = "The synopsis is not a string"
         assert _.isString(synopsis), noSynopsis
-        @emit "synopsis", arguments...
-        @$synopsis = synopsis.toString()
+        @emit.call this, "synopsis", arguments...
+        return @$synopsis = synopsis.toString()

     # Either get or set the argument information of the method that
     # is being described by this document. If you do no supply any
@@ -98,8 +98,8 @@
         assert _.isString(description), noDescription
         assert _.isString(identify), noIdentify
         assert _.isString(typeable), noTypeable
-        @emit "argument", arguments...
-        (@$argument ?= []).push
+        @emit.call this, "argument", arguments...
+        return (@$argument ?= []).push
             description: description
             identify: identify
             typeable: typeable
@@ -110,12 +110,12 @@
     # Each failure consists of the expected code and reason for fail
     failure: (code, reasoning) ->
         return @$failure if arguments.length is 0
-        noCode = "The code is not a number"
-        noReasoning = "The reasoning is not a string"
+        noCode = "The supplied code is not a number"
+        noReasoning = "the reasoning is not a string"
         assert _.isString(reasoning), noReasoning
-        assert _.isNumber(code), noCode
-        @emit "failure", arguments...
-        (@$failure ?= []).push
+        assert  code and _.isNumber(code), noCode
+        @emit.call this, "failure", arguments...
+        return (@$failure ?= []).push
             reasoning: reasoning
             code: code

@@ -130,9 +130,9 @@
         noPath = "the path name must be a valid string"
         assert _.isString(username), noUsername
         assert _.isString(repository), noRepository
-        assert _.isString(path), noPath
-        @emit "github", arguments...
-        (@$github ?= []).push
+        assert path and _.isString(path), noPath
+        @emit.call this, "github", arguments...
+        return (@$github ?= []).push
             repository: repository
             username: username
             path: path
@@ -145,8 +145,8 @@
         return @$relevant if arguments.length is 0
         noRelevant = "the relevant should be a string"
         assert _.isString(relevant), noRelevant
-        @emit "relevant", arguments...
-        (@$relevant ?= []).push relevant
+        @emit.call this, "relevant", arguments...
+        return (@$relevant ?= []).push relevant

     # Either get or set the produces information of the method that
     # is being described by this document. If you do no supply any
@@ -157,7 +157,7 @@
         notString = "elements must be MIME type strings"
         assert _.all(produces, _.isString), notString
         @$produces = (@$produces or []).concat produces
-        @emit "produces", @$produces, arguments
+        @emit.call @, "produces", @$produces, arguments

     # Either get or set the consumes information of the method that
     # is being described by this document. If you do no supply any
@@ -168,7 +168,7 @@
         notString = "elements must be MIME type strings"
         assert _.all(consumes, _.isString), notString
         @$consumes = (@$consumes or []).concat consumes
-        @emit "consumes", @$consumes, arguments
+        @emit.call @, "consumes", @$consumes, arguments

     # Either get or set the markings information of the method that
     # is being described by this document. If you do no supply any
@@ -189,8 +189,8 @@
         return @$schemas if arguments.length is 0
         noSchemas = "the schemas should be a object"
         assert _.isObject(schemas), noSchemas
-        _.extend (@$schemas ?= {}), schemas
-        @emit "schemas", arguments...
+        assert _.extend (@$schemas ?= {}), schemas
+        @emit.call this, "schemas", arguments...

     # Either get or set the version information of the method that
     # is being described by this document. If you do no supply any
@@ -200,5 +200,5 @@
         return @$version if arguments.length is 0
         noVersion = "the version should be a string"
         assert _.isString(version), noVersion
-        @emit "version", arguments...
-        @$version = version
+        @emit.call this, "version", arguments...
+        return @$version = version


menu toggle logic

menu toggle logic

--- listrr/static/coffee/list.coffee
+++ listrr/static/coffee/list.coffee
@@ -39,21 +39,38 @@
         new_li_title = title
     init_miniform form, ajax_callback, submit_callback

+hit_detection = (x, y, tx, ty, width, height) ->
+    if tx <= x <= (tx + width)
+        if ty <= y <= (ty + height)
+            return true
+    return false
+
+many_hit = (elems, e) ->
+    x = e.pageX
+    y = e.pageY
+    for elem in elems
+        elem = $ elem
+        target = elem.offset()
+        if hit_detection x, y, target.left, target.top, elem.width(), elem.height()
+            return true
+    return false
+
 initialize_menu = (menu) ->
     toggle_elems = $ ".menu_item", menu
-    bind_window = ->
-        window_click = ->
-            $(".menu_icon", menu).toggleClass "selected"
-            toggle_elems.stop()
+    btn_menu = $(".menu_icon", menu)
+    window_click = (e) ->
+        if not many_hit toggle_elems, e
             toggle_elems.fadeToggle()
+            btn_menu.toggleClass 'selected'
             $(window).unbind 'click', window_click
+            initialize_menu menu
+
+    btn_menu.click (e) ->
+        btn_menu.toggleClass 'selected'
+        toggle_elems.fadeToggle()
         $(window).click window_click
-
-    $(".menu_icon", menu).click (e) ->
-        toggle_elems.fadeToggle()
-        $(@).toggleClass "selected"
         e.stopPropagation()
-        bind_window()
+        btn_menu.unbind()

 $ ->
     forms = $ "form.new_item_form"


Include api key for taxon reordering.

Include api key for taxon reordering.

Fixes #6206

--- backend/app/assets/javascripts/spree/backend/taxons.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxons.js.coffee
@@ -6,6 +6,7 @@
       url: Spree.routes.classifications_api,
       method: 'PUT',
       data:
+        token: Spree.api_key,
         product_id: ui.item.data('product-id'),
         taxon_id: $('#taxon_id').val(),
         position: ui.item.index()


filtering out associated events from suggested events

--- client/controllers/curatorInbox/curatorSuggestedEvents.coffee
+++ client/controllers/curatorInbox/curatorSuggestedEvents.coffee
@@ -64,6 +64,9 @@
         # set the initialEvents to the result and update the reactive array
         @initialEvents = res
         @events.set(res)
+        # filter out associated events from the suggested events
+        associated = @data.associatedEventIdsToArticles?.get()
+        @updateSuggestedEvents(associated)

   # reactively compute changes to associatedEventIdsToArticles object and
   # update the suggested events


Fix setting username from REST API

Fix setting username from REST API

--- packages/rocketchat-lib/server/functions/setUsername.coffee
+++ packages/rocketchat-lib/server/functions/setUsername.coffee
@@ -62,4 +62,4 @@
 	return user

 RocketChat.setUsername = RocketChat.RateLimiter.limitFunction RocketChat._setUsername, 1, 60000,
-	0: () -> return not Meteor.userId() or not RocketChat.authz.hasPermission(Meteor.userId(), 'edit-other-user-info') # Administrators have permission to change others usernames, so don't limit those
+	0: (userId) -> return not userId or not RocketChat.authz.hasPermission(userId, 'edit-other-user-info') # Administrators have permission to change others usernames, so don't limit those


fix direct room

fix direct room

--- packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.coffee
+++ packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.coffee
@@ -12,10 +12,10 @@
 		return 'status-' + (Session.get('user_' + this.name + '_status') or 'offline')

 	name: ->
-		console.log this
-		project_info = ChatRoom.findOne(this.rid)?.details
-		if project_info
-			return project_info?.name
+		if this.t != "d"
+			project_info = ChatRoom.findOne(this.rid)?.details
+			if project_info
+				return project_info?.name
 		else
 			user = Meteor.users.findOne({username:this.name})
 			return user?.name


add content in the link

add content in the link

--- src/components/task-step/all-steps.cjsx
+++ src/components/task-step/all-steps.cjsx
@@ -43,7 +43,9 @@
   renderBody: ->
     {id} = @props
     {content_html, content_url} = TaskStepStore.get(id)
-    <ArbitraryHtmlAndMath className="-video-content" html={content_html} />
-
+

+      <ArbitraryHtmlAndMath className="-video-content" html={content_html} />
+      video
+

 module.exports = {Reading, Interactive, Video, Exercise}


Identify periodic commits and autocommit.

--- community/browser/app/scripts/init/commandInterpreters.coffee
+++ community/browser/app/scripts/init/commandInterpreters.coffee
@@ -356,25 +356,34 @@
         # Return the function that handles the input
         (input, q) ->
           current_transaction = Cypher.transaction()
-
-          r = current_transaction.begin().then(
-            (begin_response) ->
-              current_transaction.commit(input).then(
-                (response) ->
-                  if response.size > Settings.maxRows
-                    response.displayedSize = Settings.maxRows
-                  q.resolve(
-                    table: response
-                    graph: extractGraphModel(response, CypherGraphModel)
-                  )
-                ,
-                (r) ->
-                  q.reject(r)
-              )
-            ,
-            (r) ->
-              q.reject(r)
-          )
+          commit_fn = () ->
+            current_transaction.commit(input).then(
+              (response) ->
+                if response.size > Settings.maxRows
+                  response.displayedSize = Settings.maxRows
+                q.resolve(
+                  table: response
+                  graph: extractGraphModel(response, CypherGraphModel)
+                )
+              ,
+              (r) ->
+                q.reject(r)
+            )
+
+          #Periodic commits cannot be sent to an open transaction.
+          if /^\s*USING\sPERIODIC\sCOMMIT/i.test(input)
+            commit_fn()
+          #All other queries should be sent through an open transaction
+          #so they can be canceled.
+          else
+            r = current_transaction.begin().then(
+              (begin_response) ->
+                commit_fn()
+              ,
+              (r) ->
+                q.reject(r)
+            )
+
           q.promise.transaction = current_transaction
           q.promise
       ]


Implemented issue 2326 - Add AccountedTime to tag selection for text modules or triggers.

--- app/assets/javascripts/app/models/ticket.coffee
+++ app/assets/javascripts/app/models/ticket.coffee
@@ -13,6 +13,7 @@
       { name: 'pending_time',             display: 'Pending till', tag: 'datetime', null: true, width: '130px' },
       { name: 'priority_id',              display: 'Priority',     tag: 'select',   multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', width: '12%', edit: true, customer: true },
       { name: 'article_count',            display: 'Article#',     readonly: 1, width: '12%' },
+      { name: 'time_unit',                display: 'Accounted Time',          readonly: 1, width: '12%' },
       { name: 'escalation_at',            display: 'Escalation',              tag: 'datetime', null: true, readonly: 1, width: '110px', class: 'escalation' },
       { name: 'last_contact_at',          display: 'Last contact',            tag: 'datetime', null: true, readonly: 1, width: '110px' },
       { name: 'last_contact_agent_at',    display: 'Last contact (agent)',    tag: 'datetime', null: true, readonly: 1, width: '110px' },


Forgot to inject the $logProvider dependency

--- app/assets/javascripts/sprangular/module.coffee
+++ app/assets/javascripts/sprangular/module.coffee
@@ -24,7 +24,7 @@
       newInstance

 # Default Headers
-Sprangular.config ["$httpProvider", "$locationProvider", ($httpProvider, $locationProvider, $logProvider) ->
+Sprangular.config ["$httpProvider", "$locationProvider", "$logProvider", ($httpProvider, $locationProvider, $logProvider) ->
   $httpProvider.defaults.headers.common['Accept'] = 'application/json'
   $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
   $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded'


Added event handler for $routeChangeStart to check route requirements

--- app/assets/javascripts/sprangular/module.coffee
+++ app/assets/javascripts/sprangular/module.coffee
@@ -22,7 +22,7 @@
       newInstance

 # Default Headers
-window.Sprangular.config ["$httpProvider", "$locationProvider", ($httpProvider, $locationProvider) ->
+Sprangular.config ["$httpProvider", "$locationProvider", ($httpProvider, $locationProvider) ->
   $httpProvider.defaults.headers.common['Accept'] = 'application/json'
   $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
   $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded'
@@ -31,3 +31,20 @@
     .html5Mode false
     .hashPrefix '!'
 ]
+
+Sprangular.run ($rootScope, $location, Account, Cart, Flash) ->
+
+  $rootScope.$on '$routeChangeStart', (event, next, current) ->
+    requirements = next.requires || {}
+
+    if requirements.user && !Account.isLogged
+      Flash.error('Please sign in or register to continue.')
+      $location.path('/sign-in')
+
+    else if requirements.guest && Account.isLogged
+      Flash.error("Sorry, that page is only available when you're signed out.")
+      $location.path('/')
+
+    else if requirements.cart && Cart.items.length == 0
+      Flash.error('Sorry, there are no items in your cart.')
+      $location.path('/')


mouse wheel loupe

mouse wheel loupe

--- app/assets/javascripts/static_editors.js.coffee
+++ app/assets/javascripts/static_editors.js.coffee
@@ -22,7 +22,8 @@
   normalMatrix = new THREE.Matrix3()
   ROLLOVERED = null
   voxelBase = new THREE.Object3D()
-  voxelData = {};
+  voxelData = {}
+  cameraR = 40
   #色選択時
   $(".color-palette").click ->
     colorVal = parseInt($(this).attr("data-colorval"))
@@ -58,6 +59,10 @@
     $canvas.attr('id', 'canvas_id')
     document.addEventListener( 'mousemove', onDocumentMouseMove, false )
     $canvas.click(onDocumentMouseDown)
+    #マウスホイール
+    if window.addEventListener
+      window.addEventListener "DOMMouseScroll", onMouseWheel, false #Mozilla
+      window.onmousewheel = document.onmousewheel = onMouseWheel #IE,Opera,Chrome

     #rollover cube
     rollOverCube = MYTHREE.createTransparentCube(new THREE.Vector3(0,0,0), MYTHREE.cubeColor)
@@ -206,14 +211,50 @@
         return intersector
     null

+  #マウスホイール
+  mouseWheelHandler = (delta) ->
+    magnifySpeed = 1
+    if delta < 0
+      cameraR += magnifySpeed
+    else
+      cameraR -= magnifySpeed
+    return
+  onMouseWheel = (event) ->
+    delta = 0
+    # For IE.
+    event = window.event  unless event
+    if event.wheelDelta # IE/Opera.
+      delta = event.wheelDelta / 120
+      ###
+      Mozilla case.
+      In Mozilla, sign of delta is different than in IE.
+      Also, delta is multiple of 3.
+      ###
+    else
+      delta = -event.detail / 3  if event.detail
+      ###
+      If delta is nonzero, handle it.
+      Basically, delta is now positive if wheel was scrolled up,
+      and negative, if wheel was scrolled down.
+      ###
+    mouseWheelHandler delta  if delta
+    ###
+    Prevent default actions caused by mouse wheel.
+    That might be ugly, but we handle scrolls somehow
+    anyway, so don't bother here..
+    ###
+    event.preventDefault()  if event.preventDefault
+    event.returnValue = false
+    return
+
   render = ->
     phi += mouse2D.x * 3  if isShiftDown
     if auto_rotate
       phi += 1
     R = 40
-    camera.position.x = R * Math.sin(theta * Math.PI / 360) * Math.cos(phi* Math.PI / 360)
-    camera.position.z = R * Math.sin(theta * Math.PI / 360) * Math.sin(phi * Math.PI / 360)
-    camera.position.y = R * Math.cos(theta * Math.PI / 360)
+    camera.position.x = cameraR * Math.sin(theta * Math.PI / 360) * Math.cos(phi* Math.PI / 360)
+    camera.position.z = cameraR * Math.sin(theta * Math.PI / 360) * Math.sin(phi * Math.PI / 360)
+    camera.position.y = cameraR * Math.cos(theta * Math.PI / 360)
     camera.lookAt origin
     raycaster = projector.pickingRay(mouse2D.clone(), camera) #ray from 2D to 3D
     #rollover pos