Index finding bug fixed

Index finding bug fixed

--- style.coffee
+++ style.coffee
@@ -220,6 +220,12 @@
 		@file.readyToUse = false
 		stylesToRevert.push @
 		updateWhenPossible @
+
+		# clean index finding data
+		tmpNode = @node
+		while (tmpNode = tmpNode._parent) && not tmpNode.style
+			if tmpNode.name is 'neft:use'
+				tmpNode._documentStyle = null

 		for child in @children
 			child.revert()
@@ -603,7 +609,8 @@
 					if style.node isnt tmpNode
 						item = item._parent

-				tmpNode._documentStyle ?= @
+				if tmpNode.name isnt 'neft:fragment'
+					tmpNode._documentStyle ?= @

 				if item
 					@item.parent = item
@@ -618,12 +625,6 @@

 		for child in @children
 			child.findItemParent()
-
-		# HACK
-		if item
-			while (tmpNode = tmpNode._parent) && not tmpNode.style
-				if tmpNode.name is 'neft:use'
-					tmpNode._documentStyle = tmpNode.children[0]._documentStyle

 		return


Set the `decorations` and `previousDecorations` vars before loops

Both loops use both vars, so both need to be available before the
diffing!

--- src/lines-component.coffee
+++ src/lines-component.coffee
@@ -207,11 +207,14 @@
     {editor, lineHeightInPixels, lineDecorations} = @props
     lineNode = @lineNodesByLineId[line.id]

-    if previousDecorations = @renderedDecorationsByLineId[line.id]
+    decorations = lineDecorations[screenRow]
+    previousDecorations = @renderedDecorationsByLineId[line.id]
+
+    if previousDecorations?
       for decoration in previousDecorations
         lineNode.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(decorations, decoration)

-    if decorations = lineDecorations[screenRow]
+    if decorations?
       for decoration in decorations
         if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(previousDecorations, decoration)
           lineNode.classList.add(decoration.class)


Add off method for Tracker class

Add off method for Tracker class

--- src/tracker.coffee
+++ src/tracker.coffee
@@ -45,6 +45,9 @@
         @on 'start', ->
             VASTClient.lastSuccessfullAd = +new Date()
             return
+
+    @off: (eventName, cb) ->
+        @removeListener eventName, cb

     setDuration: (duration) ->
         @assetDuration = duration


Replace translated address without breaking back.

--- src/control.coffee
+++ src/control.coffee
@@ -368,7 +368,8 @@
                             # command cycle including URL navigation to change the URL language.
                             # For example in Finland, the slug should be in Swedish if the UI is in Swedish,
                             # otherwise in Finnish (the default).
-                            app.commands.execute 'selectPosition', position
+                            @selectPosition(position).done =>
+                                @router.navigate "address/#{slug}", replace: true
                         else
                             @selectPosition position
                     catch err


change how auto-launch works on mac

change how auto-launch works on mac

--- src/ui/util.coffee
+++ src/ui/util.coffee
@@ -198,14 +198,17 @@
         range.select()

 # AutoLaunch requires a path unless you are running in electron/nw
-vesrions = process?.versions
+versions = process?.versions
 if versions? and (versions.nw? or versions['node-webkit']? or versions.electron?)
     autoLaunchPath = undefined
 else
     autoLaunchPath = process.execPath
 autoLauncher = new AutoLaunch({
     name: 'YakYak',
-    path: autoLaunchPath
+    path: autoLaunchPath,
+    mac: {
+        useLaunchAgent: true
+    }
 });

 module.exports = {nameof, initialsof, nameofconv, linkto, later,


Adds bulkDeleteTeamMedia

Adds bulkDeleteTeamMedia

--- src/collections/teamMedia.coffee
+++ src/collections/teamMedia.coffee
@@ -40,6 +40,19 @@
         '`teamMedium` must be provided'

     @deleteItem teamMedium, callback
+
+
+exports.bulkDeleteTeamMedia = (teamMediumIds, callback) ->
+  unless teamMediumIds
+    throw new TSArgsError 'teamsnap.bulkDeleteTeamMedia',
+      "`teamMediumIds` must be provided"
+  unless Array.isArray teamMediumIds
+    throw new TSArgsError 'teamsnap.bulkDeleteTeamMedia',
+      "`teamMediumIds` must include 'teamMediumIds' as type Array"
+
+  params =
+    teamMediumIds: teamMediumIds
+  @collections.teamMedia.exec('bulkDeleteTeamMedia', params).callback callback

 exports.assignMediaToGroup = (teamMediumId, teamMediaGroupId, callback) ->


[grunt] proper way to handle domains

[grunt] proper way to handle domains

--- modules/gettext.coffee
+++ modules/gettext.coffee
@@ -30,8 +30,15 @@

           @gettext.addTextdomain(domain, messages)

+    resolveDomain: (domain) ->
+      if domain.charAt(0) == ':' then @gettext.textdomain() + domain else domain
+
+    textdomain: (domain) ->
+      @gettext.textdomain(@resolveDomain(domain))
+
     installNunjucksGlobals: (env, currentLocale) ->
-      gettext = @gettext
+
+      @textdomain(currentLocale)

       # --------------
       # i18n functions
@@ -42,8 +49,8 @@
       * @param {string} string          String, which should be loaded
       * @return {string} Translated string into current locale
       ###
-      env.addGlobal 'gettext', (string) ->
-        gettext.dgettext(currentLocale, string)
+      env.addGlobal 'gettext', (string) =>
+        @gettext.gettext(string)

       ###*
        * Load string from specified domain
@@ -53,9 +60,8 @@
        * @param {string} string                 String, which should be loaded
        * @return {string} Translated string into specified locale
       ###
-      env.addGlobal 'dgettext', (domain = currentLocale, string) ->
-        domain = if domain.charAt(0) == ':' then currentLocale + domain else domain
-        gettext.dgettext(domain, string)
+      env.addGlobal 'dgettext', (domain, string) =>
+        @gettext.dgettext(@resolveDomain(domain), string)

       ###*
       * Load plural string from current locale
@@ -64,8 +70,8 @@
       * @param {number} count           Count for detecting correct plural form
       * @return {string} Pluralized and translated into current locale string
       ###
-      env.addGlobal 'ngettext', (string, pluralString, count) ->
-        gettext.dngettext(currentLocale, string, pluralString, count)
+      env.addGlobal 'ngettext', (string, pluralString, count) =>
+        @gettext.ngettext(string, pluralString, count)

       ###*
        * Load plural string from specified domain
@@ -77,9 +83,8 @@
        * @param {number} count                  Count for detecting correct plural form
        * @return {string} Pluralized and translated into specified loca stringle
       ###
-      env.addGlobal 'dngettext', (domain = currentLocale, string, pluralString, count) ->
-        domain = if domain.charAt(0) == ':' then currentLocale + domain else domain
-        gettext.dngettext(domain, string, pluralString, count)
+      env.addGlobal 'dngettext', (domain, string, pluralString, count) =>
+        @gettext.dngettext(@resolveDomain(domain), string, pluralString, count)

       ###*
       * Load string of specific context from current locale
@@ -87,8 +92,8 @@
       * @param {string} string          String, which should be loaded
       * @return {string} Translated string into current locale
       ###
-      env.addGlobal 'pgettext', (context, string) ->
-        gettext.dpgettext(currentLocale, context, string)
+      env.addGlobal 'pgettext', (context, string) =>
+        @gettext.pgettext(context, string)

       ###*
        * Load string of specific context from specified domain
@@ -99,9 +104,8 @@
        * @param {string} string                 String, which should be loaded
        * @return {string} Translated string into specified locale
       ###
-      env.addGlobal 'dpgettext', (domain = currentLocale, context, string) ->
-        domain = if domain.charAt(0) == ':' then currentLocale + domain else domain
-        gettext.dpgettext(domain, context, string)
+      env.addGlobal 'dpgettext', (domain, context, string) =>
+        @gettext.dpgettext(@resolveDomain(domain), context, string)

       ###*
       * Load plural string of specific context from current locale
@@ -111,8 +115,8 @@
       * @param {number} count           Count for detecting correct plural form
       * @return {string} Pluralized and translated into current locale string
       ###
-      env.addGlobal 'npgettext', (context, string, pluralString, count) ->
-        gettext.dnpgettext(currentLocale, context, string, pluralString, count)
+      env.addGlobal 'npgettext', (context, string, pluralString, count) =>
+        @gettext.npgettext(context, string, pluralString, count)

       ###*
        * Load plural string of specific context from specified domain
@@ -125,8 +129,7 @@
        * @param {number} count                  Count for detecting correct plural form
        * @return {string} Pluralized and translated into specified loca stringle
       ###
-      env.addGlobal 'dnpgettext', (domain = currentLocale, context, string, pluralString, count) ->
-        domain = if domain.charAt(0) == ':' then currentLocale + domain else domain
-        gettext.dnpgettext(domain, context, string, pluralString, count)
+      env.addGlobal 'dnpgettext', (domain, context, string, pluralString, count) =>
+        @gettext.dnpgettext(@resolveDomain(domain), context, string, pluralString, count)

       return


Scrollbars TODO

Scrollbars TODO

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -1,4 +1,6 @@
 {my} = require('../my')
+
+#TODO: Add SVG scrollbars a la http://www.carto.net/svg/gui/scrollbar/

 COMMANDS = 'commands'
 EXECUTING = 'executing'


added option parameter

added option parameter

--- src/sloc.coffee
+++ src/sloc.coffee
@@ -169,11 +169,8 @@
     sum += d
   sum

-X = null
-
-slocModule = (code, lang) ->
-
-  X = lang
+slocModule = (code, lang, opt={}) ->
+
   unless typeof code is "string"
     throw new TypeError "'code' has to be a string"

@@ -192,6 +189,8 @@
   blockEmpty = 0
   blockEmpty =+ x.empty for x in res.block
   source  = total - comment - empty + blockEmpty + mixed
+
+  console.log res if opt.debug

   # result
   { total, source, comment, single, block, mixed, empty }


refactor(macDatepicker): Removed calling digest cycle when setting datepicker date

--- src/directives/datepicker.coffee
+++ src/directives/datepicker.coffee
@@ -85,7 +85,7 @@
             if initialized and value?
               $timeout ->
                 inputElement.datepicker "setDate", value
-              , 0
+              , 0, false

         if attrs.macDatepickerDefaultDate?
           $scope.$watch attrs.macDatepickerDefaultDate, (value) ->


fix delete outcome/group action. fixes #11503

as an account admin, deleting an imported outcome or outcome
group immediately after import (i.e. before refreshing the page)
would delete the outcome or group from the account and not just
from the course.

now, the delete button inside of a course is properly scoped
to a course.

test plan:
  * as an account admin on a course outcomes page, import both
    an account-level outcome and an account-level outcome group;
  * without refreshing the page, delete both of the newly imported
    items;
  * verify that both items delete from the course successfully;
  * from the account's outcomes page, verify that both items are
    not deleted.

Change-Id: I3d017155ff9739e97320d01496cf243a08f9d677
Reviewed-on: https://gerrit.instructure.com/14968
Reviewed-by: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Marc LeGendre <d33c80bc45d65303e33ca83108a9952b745af9ef@instructure.com>

--- app/coffeescripts/views/outcomes/FindDialog.coffee
+++ app/coffeescripts/views/outcomes/FindDialog.coffee
@@ -91,8 +91,16 @@
             outcome_id: model.get 'id'
         @$el.disableWhileLoading dfd
         $.when(dfd)
-          .done =>
-            @trigger 'import', model
+          .done (response, status, deferred) =>
+            importedModel = model.clone()
+            if importedModel instanceof OutcomeGroup
+              importedModel.set(response)
+            else
+              importedModel.outcomeLink     = _.extend({}, model.outcomeLink)
+              importedModel.outcomeGroup    = response.outcome_group
+              importedModel.outcomeLink.url = response.url
+              importedModel.set(context_id: response.context_id, context_type: response.context_type)
+            @trigger 'import', importedModel
             @close()
             $.flashMessage I18n.t('flash.importSuccess', 'Import successful')
           .fail =>


Published API to compare string slices, with lev. distance and visual diff

--- app/lib/dom_text_matcher/text_match_engines.coffee
+++ app/lib/dom_text_matcher/text_match_engines.coffee
@@ -158,6 +158,16 @@

     [result]

+  # Compare two string slices, get Levenhstein and visual diff
+  compare: (text1, text2) ->
+    result = {}
+    result.diff = @dmp.diff_main text1, text2
+    result.lev = @dmp.diff_levenshtein result.diff
+    @dmp.diff_cleanupSemantic result.diff
+    result.diffHTML = @dmp.diff_prettyHtml result.diff
+    result
+
+
   # ============= Private part ==========================================
   # You don't need to call the functions below this point manually


Use coffeescript `=>` functions, rather than `self=this`.

--- services/web/public/coffee/ide/labels/LabelsManager.coffee
+++ services/web/public/coffee/ide/labels/LabelsManager.coffee
@@ -14,29 +14,25 @@
 			@periodicLoadInterval = null

 			setTimeout(
-				(self) ->
+				() =>
 					# set up a regular re-load
 					setTimeout(
-						(self) ->
-							self.periodicLoadInterval = setInterval(
-								(self) ->
-									self.loadLabelsFromOpenDoc()
+						() =>
+							@periodicLoadInterval = setInterval(
+								() =>
+									@loadLabelsFromOpenDoc()
 								, AUTOMATIC_REFRESH_PERIOD
-								, self
 							)
 						, AUTOMATIC_REFRESH_PERIOD
-						, self
 					)
 					# listen for document open
-					self.$scope.$on 'document:opened', (e, doc) ->
+					@$scope.$on 'document:opened', (e, doc) =>
 						setTimeout(
-							(self) ->
-								self.scheduleLoadLabelsFromOpenDoc()
+							() =>
+								@scheduleLoadLabelsFromOpenDoc()
 							, 1000
-							, self
 						)
 				, 0
-				this
 			)

 		loadLabelsFromOpenDoc: () ->
@@ -53,8 +49,8 @@
 			if @loadLabelsTimeout
 				clearTimeout(@loadLabelsTimeout)
 			@loadLabelsTimeout = setTimeout(
-				(self) ->
-					self.loadLabelsFromOpenDoc()
+				() =>
+					@loadLabelsFromOpenDoc()
 				, 1000
 				, this
 			)


Add a TODO

Add a TODO

--- admin/static/coffee/app.coffee
+++ admin/static/coffee/app.coffee
@@ -39,7 +39,7 @@
 apply_to_collection = (collection, collection_data) ->
     for id, data of collection_data
         if data isnt null
-            if data.protocol? and data.protocol is 'memcached'  # We check that the machines in the blueprint do exist
+            if data.protocol? and (data.protocol is 'memcached' or data.protocol is 'rdb')  # We check that the machines in the blueprint do exist
                 if collection_data[id].blueprint? and collection_data[id].blueprint.peers_roles?
                     for machine_uuid of collection_data[id].blueprint.peers_roles
                         if !machines.get(machine_uuid)?
@@ -55,6 +55,7 @@
             else
                 data.id = id
                 collection.add(new collection.model(data))
+            #TODO remove not found object
         else
             if collection.get(id)
                 collection.remove(id)


Restored old date format. Fixes feedbin/support#504.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -85,7 +85,18 @@
       feedbin.preloadedImageIds.push(id)

   localizeTime: (container) ->
-    $("time.timeago").timeago()
+    now = new Date()
+    $("time.timeago").each ->
+      datePublished = $(@).attr('datetime')
+      datePublished = new Date(datePublished)
+      if datePublished > now
+        $(@).text('the future')
+      else if (now - datePublished) < feedbin.ONE_DAY * 7
+        $(@).timeago()
+      else if datePublished.getFullYear() == now.getFullYear()
+        $(@).text(datePublished.format("%e %b"))
+      else
+        $(@).text(datePublished.format("%e %b %Y"))

   entryTime: ->
     $(".post-meta time").each ->
@@ -516,6 +527,10 @@
   dragOwner: null

   preloadedImageIds: []
+
+  ONE_HOUR: 60 * 60 * 1000
+
+  ONE_DAY: 60 * 60 * 1000 * 24

 $.extend feedbin,
   init:
@@ -874,7 +889,6 @@
         numbers: []
       jQuery.timeago.settings.strings = strings
       jQuery.timeago.settings.allowFuture = true
-      feedbin.localizeTime()
       return

     updateReadability: ->


lintering in app.js

lintering in app.js

--- app/assets/javascripts/base/app.coffee
+++ app/assets/javascripts/base/app.coffee
@@ -13,8 +13,8 @@
       NProgress.done()
       response
     responseError: (rejection) ->
-      NProgress.done();
-      $q.reject(rejection);
+      NProgress.done()
+      $q.reject(rejection)
   ]
   spinnerFunction = (data, headersGetter) ->
     NProgress.start()


correct some bugs

correct some bugs

--- app/assets/javascripts/nodes.js.coffee
+++ app/assets/javascripts/nodes.js.coffee
@@ -315,7 +315,13 @@
         if id
           @node_objs_by_id[id] = null
           delete @node_objs_by_id[id]
-        # TODO TODO TODO Remove node_obj from @node_objs
+        # Remove node_obj from @node_objs
+        index = -1
+        for nd_obj, ind in @node_objs
+          if nd_obj == node_obj
+            index = ind
+        if (index > -1)
+          @node_objs.splice(index, 1)

       # [ Called before register
       fetch: (id) ->
@@ -363,8 +369,8 @@
           @on_map = true

         @resize_marker()
-        # TODO TODO TODO correct that shit
-        @marker.on('dragging', (e) =>
+
+        @marker.on('drag', (e) =>
           closest = node_collection.closest(this)
           if closest
             @select_target(closest)
@@ -450,11 +456,11 @@
         b = @node.longitude - node_obj.node.latitude
         a*a+b*b

-      select_target = (node_obj)->
+      select_target: (node_obj)->
         @target = node_obj
         node_obj.target()

-      deselect_target = (node_obj)->
+      deselect_target: (node_obj)->
         @target = null
         node_obj.untarget()


Remove broken code

Remove broken code

--- app/assets/javascripts/rooms.js.coffee
+++ app/assets/javascripts/rooms.js.coffee
@@ -25,7 +25,6 @@

 class Users
-  users = {}
   constructor: (@room_id, @container) ->
     @_subscribeChannels()

@@ -35,12 +34,10 @@

   _subscribeJoin: =>
     window.FAYE_CLIENT.subscribe "/rooms/#{@room_id}/users/join", (data) =>
-      users[data.user.id] = data.user
       @container.append(@_renderUser(data))

   _subscribeLeave: =>
     window.FAYE_CLIENT.subscribe "/rooms/#{@room_id}/users/leave", (data) =>
-      delete users[data.user.id]
       @container.find("a[data-id=\"#{data.user.id}\"]").closest('li').remove()

   _renderUser: (data) ->


Removed unused self-closing list

Removed unused self-closing list

--- source/compiler.coffee
+++ source/compiler.coffee
@@ -1,17 +1,5 @@
 CoffeeScript = require "coffee-script"
 styl = require 'styl'
-
-selfClosing =
-  area: true
-  base: true
-  br: true
-  col: true
-  hr: true
-  img: true
-  input: true
-  link: true
-  meta: true
-  param: true

 indentText = (text, indent="  ") ->
   indent + text.replace(/\n/g, "\n#{indent}")
@@ -25,10 +13,7 @@

 keywordsRegex = RegExp("\\s*(#{keywords.join('|')})\\s+")

-# JST Renderer Util
 util =
-  selfClosing: selfClosing
-
   indent: indentText

   filters:


CollectionView no need to be observable

CollectionView no need to be observable

--- src/view/iterator_view.coffee
+++ src/view/iterator_view.coffee
@@ -30,7 +30,7 @@
     iv.init node, $marker, $parent, obj

   init: (@node, @$marker, @$parent, @obj) ->
-    @collectionViews = new Leaf.ObservableArray()
+    @collectionViews = []

     binder = new Leaf.Template.Binder @obj
     bindingObj = binder.getBindingObject @node.localeBindings
@@ -41,7 +41,7 @@
       throw new NonIteratableObjectError @node.iterator, @collection

     @collection.forEach @addOne
-    @collection.observe @update
+    @collection.observe => @applyPatch @collection.getPatch()

   addOne: (item) =>
     view = @createView item
@@ -63,9 +63,6 @@
         model: item
         collection: @collection

-  update: =>
-    @applyPatch @collection.getPatch()
-
   applyPatch: (patch) ->
     for p in patch
       switch p.method
@@ -73,11 +70,11 @@
           view = @createView p.element
           $idx = @collectionViews[p.index]?.$view ? @$marker
           view.render $idx
-          @collectionViews.insertAt p.index, [view]
+          @collectionViews.splice p.index, -1, view
         when 'removeAt'
           if view = @collectionViews[p.index]
             view.detach()
-            @collectionViews.removeAt p.index
+            @collectionViews.splice p.index, 1

 #  Iterator item


Allow forward/back thumbnail animations

Allow forward/back thumbnail animations

--- src/views/level-select.coffee
+++ src/views/level-select.coffee
@@ -9,14 +9,14 @@
   events: ->
     if ENV.mobile
       events =
-        'touchend .back': 'back'
+        'touchend .back': 'back'
         'touchend .previous': 'previous'
         'touchend .next': 'next'
         'touchend .play': 'play'
         'touchend canvas': 'select'
     else
       events =
-        'click .back': 'back'
+        'click .back': 'back'
         'click .previous': 'previous'
         'click .next': 'next'
         'click .play': 'play'
@@ -47,7 +47,7 @@

       @trigger 'sfx:play', 'button'

-      @animateThumbnails()
+      @animateThumbnails("-")

   next: (e) ->
     e.preventDefault()
@@ -74,7 +74,7 @@
   back: (e) ->
     e.preventDefault()
     @undelegateEvents() # Prevent multiple clicks
-
+
     @trigger 'sfx:play', 'button'
     @trigger 'scene:change', 'difficulty'

@@ -85,7 +85,7 @@
       string = '0' + string while string.length < length
       return string

-    if @stats[@selectedLevel]?.time
+    if @stats[@selectedLevel]?.time
       minutes = pad Math.floor(@stats[@selectedLevel].time / 60), 2
       seconds = pad @stats[@selectedLevel].time % 60, 2
       time = "#{minutes}:#{seconds}"
@@ -93,7 +93,7 @@
       time = '--:--'

     attempts = @stats[@selectedLevel]?.attempts || "0"
-
+
     @$('.attempts').html "Attempts: #{attempts}"
     @$('.best-time').html "Best Time: #{time}"

@@ -105,16 +105,18 @@
     else
       @$('.level-number').html "#{@difficulty.charAt(0).toUpperCase() + @difficulty.slice(1)} ##{@selectedLevel + 1}: ????"

-  animateThumbnails: (direction) ->
+  animateThumbnails: (direction = "") ->
+    opposite = if direction == "-" then "" else "-"
+
     # Move existing thumbnails off
     @canvases.parent('.group').css('z-index': 0)
     @canvases.each (i, element) =>
       canvas = $(element)

       _.delay =>
-        # Animate offscreen
-        # "cubic-bezier(.5,-0.5,.5,1.5)"
-        canvas.animate({ transform: "translateX(-#{@width}px)" }, "fast", "ease-in-out")
+        canvas.animate({
+          transform: "translateX(#{direction}#{@width}px)"
+        }, "fast", "ease-in-out")
       , i * 100

     # switch thumbnail groups
@@ -130,13 +132,15 @@
     @canvases.each (i, element) =>
       canvas = $(element)

-      # Move offscreen
-      canvas.animate({ transform: "translateX(#{@width}px)" }, 0)
+      # Start offscreen
+      canvas.animate({
+        transform: "translateX(#{opposite}#{@width}px)"
+      }, 0)

       _.delay ->
-        # Animate onscreen
-        # "cubic-bezier(.5,-0.5,.5,1.5)"
-        canvas.animate({ transform: "translateX(0)" }, "fast", "ease-in-out")
+        canvas.animate({
+          transform: "translateX(0)"
+        }, "fast", "ease-in-out")
       , i * 100 + 250

     @selectedLevel = @page * @perPage
@@ -155,7 +159,7 @@
         canvas.hide()
       else
         canvas.show()
-        clues = if @stats[index]?.time
+        clues = if @stats[index]?.time
                   levelData.clues
                 else
                   levels.incomplete.clues
@@ -211,7 +215,7 @@
     # Determine the last viewed level for this difficulty
     @selectedLevel = localStorage.getObject('lastViewedLevel')[@difficulty] || 0
     @page = Math.floor(@selectedLevel / @perPage)
-
+
     # disable/enable buttons
     @$('.previous, .next').addClass 'disabled'
     if @page > 0 then @$('.previous').removeClass 'disabled'
@@ -223,6 +227,10 @@
     # Move alt canvases off-screen
     @altCanvases.animate({ transform: "translateX(-#{@width}px)" }, 0)

+    # Ensure on-screen group is clickable
+    @altCanvases.parent('.group').css('z-index': 0)
+    @canvases.parent('.group').css('z-index': 1)
+
     @drawThumbnails()
     @highlightThumbnail()


tell: allow username to be proceeded with @

--- src/tell.coffee
+++ src/tell.coffee
@@ -32,7 +32,7 @@
   commands = ['tell'].concat(config.aliases)
   commands = commands.join('|')

-  REGEX = ///(#{commands})\s+([\w,.-]+):?\s+(.*)///i
+  REGEX = ///(#{commands})\s+@?([\w,.-]+):?\s+(.*)///i

   robot.respond REGEX, (msg) ->
     localstorage = JSON.parse(robot.brain.get 'hubot-tell') or {}


better fix for #5

better fix for #5

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -85,16 +85,18 @@
     (interval) ->
       sub?.dispose?()
       sub = atom.workspace.observeTextEditors (editor) ->
-        editorPresenter = atom.views.getView(editor)?.component?.presenter
-        return unless editorPresenter
-        editorPresenter.stopBlinkingCursors true
-        if interval > 0
-          editorPresenter.cursorBlinkPeriod = interval
-          atom.config.set 'block-cursor.secondaryColor', 'transparent'
-        else
-          editorPresenter.cursorBlinkPeriod = -1 + Math.pow 2, 31
-          atom.config.set 'block-cursor.secondaryColor', atom.config.get 'block-cursor.primaryColor'
-        editorPresenter.startBlinkingCursors()
+        setTimeout ->
+          editorPresenter = atom.views.getView(editor).component.presenter
+          # return unless editorPresenter
+          editorPresenter.stopBlinkingCursors true
+          if interval > 0
+            editorPresenter.cursorBlinkPeriod = interval
+            atom.config.set 'block-cursor.secondaryColor', 'transparent'
+          else
+            editorPresenter.cursorBlinkPeriod = -1 + Math.pow 2, 31
+            atom.config.set 'block-cursor.secondaryColor', atom.config.get 'block-cursor.primaryColor'
+          editorPresenter.startBlinkingCursors()
+        , 0
       @subs.add sub

   applyPulseDuration: (duration) =>


fix homepage to google

fix homepage to google

--- lib/browser-plus.coffee
+++ lib/browser-plus.coffee
@@ -17,7 +17,7 @@
     homepage:
       title: 'HomePage'
       type: 'string'
-      default: 'http://localhost:3000/' #www.google.com
+      default: 'http://www.google.com'
     preview:
       title: 'Allow Preview'
       type: 'boolean'


fix zoom error on image_url

fix zoom error on image_url

--- bokehjs/src/coffee/renderer/glyph/image_url.coffee
+++ bokehjs/src/coffee/renderer/glyph/image_url.coffee
@@ -43,7 +43,7 @@
         img.src = url[i]
         need_load[i] = false
       else if @loaded[i]
-        @_render_image(ctx, i, image, sx, sy, sw, sh, angle)
+        @_render_image(ctx, i, image[i], sx, sy, sw, sh, angle)

   _final_sx_sy: (anchor, sx, sy, sw, sh) ->
     switch anchor


fix missing users dependency

fix missing users dependency

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -61,6 +61,7 @@
   "doubtfire.units"
   "doubtfire.tasks"
   "doubtfire.projects"
+  "doubtfire.users"
 ])
 .config(($urlRouterProvider, $httpProvider) ->


Emitters are Essential

Emitters are Essential

--- src/emitter.coffee
+++ src/emitter.coffee
@@ -1,6 +1,6 @@
 Disposable = require './disposable'

-# Public: Utility class to be used when implementing event-based APIs that
+# Essential: Utility class to be used when implementing event-based APIs that
 # allows for handlers registered via `::on` to be invoked with calls to
 # `::emit`. Instances of this class are intended to be used internally by
 # classes that expose an event-based API.


Only replace .git segment if trailing

Only replace .git segment if trailing

--- src/app/git.coffee
+++ src/app/git.coffee
@@ -64,7 +64,7 @@
     @unsubscribe()

   getWorkingDirectory: ->
-    @getPath()?.replace(/\/\.git\/?/, '')
+    @getPath()?.replace(/\/\.git\/?$/, '')

   getHead: ->
     @getRepo().getHead() ? ''


Format code

Format code

--- lib/dcd-provider.coffee
+++ lib/dcd-provider.coffee
@@ -7,7 +7,7 @@
   disableForSelector: ".source.d .comment, .source.d .string"
   inclusionPriority: 1
   excludeLowerPriority: true
-  server: null,
+  server: null

   startServer: ->
     @server = childProcess.spawn("dcd-server", stdio: ["ignore", "ignore", "ignore"])
@@ -87,7 +87,6 @@
   createFunctionContext: (text) ->
     fakeText = ""
     reg = /import [^;]+;/g
-    res

     while (res = reg.exec(text)) != null
       fakeText += res[0]


FIX: Fix trimming nullable student ID

FIX: Fix trimming nullable student ID

--- src/app/home/states/new-user-wizard/new-user-wizard.coffee
+++ src/app/home/states/new-user-wizard/new-user-wizard.coffee
@@ -116,7 +116,7 @@
       when $scope.steps.nicknameStep, $scope.steps.targetGradeStep, $scope.steps.avatarStep
         state = true
       when $scope.steps.studentIdStep
-        state = $scope.user.student_id.trim().length > 0
+        state = $scope.user.student_id?.trim().length > 0
       when $scope.steps.emailStep
         state = $scope.user.email?.trim().length > 0
         if $scope.projects.length > 0


Fix typo with UNATPPD_API_ACCESS_TOKEN

Fix typo with UNATPPD_API_ACCESS_TOKEN

UNATPPD_API_ACCESS_TOKEN should be UNTAPPD_API_ACCESS_TOKEN

--- src/hubot-untappd-friends.coffee
+++ src/hubot-untappd-friends.coffee
@@ -8,7 +8,7 @@
 # Configuration:
 #  UNTAPPD_API_KEY - Your Untappd API Key
 #  UNTAPPD_API_SECRET - Your Untappd API Secret
-#  UNATPPD_API_ACCESS_TOKEN - A valid OAuth 2 token
+#  UNTAPPD_API_ACCESS_TOKEN - A valid OAuth 2 token
 #  UNTAPPD_MAX_COUNT - (optional) Number of results to return
 #
 # Commands:
@@ -33,7 +33,7 @@
   untappd = new UntappdClient(false);
   untappd.setClientId process.env.UNTAPPD_API_KEY
   untappd.setClientSecret process.env.UNTAPPD_API_SECRET
-  untappd.setAccessToken process.env.UNATPPD_API_ACCESS_TOKEN
+  untappd.setAccessToken process.env.UNTAPPD_API_ACCESS_TOKEN

   ##
   # Default command


Return package if its already loaded

Return package if its already loaded

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -52,7 +52,7 @@
     @activatePackage(pack.name) for pack in @getLoadedPackages() when not pack.isTheme()

   activatePackage: (name, options) ->
-    return if @getActivePackage(name)
+    return pack if pack = @getActivePackage(name)
     if pack = @loadPackage(name, options)
       @activePackages[pack.name] = pack
       pack.activate(options)


Abstracted show functionality to method

Abstracted show functionality to method

--- src/js/views/notification.coffee
+++ src/js/views/notification.coffee
@@ -44,22 +44,20 @@
     @$el.fadeOut(@options.fade_time, => @$el.remove())

 Factlink.Views.Notifications.showFactlinkCreated = ->
-  created_message = new Factlink.Views.Notification
+  Factlink.Views.Notifications.show
     message: 'Factlink loaded!'
     type_classes: 'fl-message-success fl-message-icon-time'

-  created_message.render()
-
 Factlink.Views.Notifications.showShouldSelectText = ->
-  select_text_message = new Factlink.Views.Notification
+  Factlink.Views.Notifications.show
     message: 'To create a Factlink, select a statement and click the Factlink button.'
     type_classes: 'fl-message-icon-add'

-  select_text_message.render()
-
 Factlink.Views.Notifications.showLoaded = ->
-  loaded_message = new Factlink.Views.Notification
+  Factlink.Views.Notifications.show
     message: 'Factlink is loaded!'
     type_classes: 'fl-message-success fl-message-icon-time'

-  loaded_message.render()
+Factlink.Views.Notifications.show (options) = ->
+  message = new Factlink.Views.Notification options
+  message.render()


Nicer error message for uneditable sprite

--- src/layout/mixins/actions.coffee
+++ src/layout/mixins/actions.coffee
@@ -34,7 +34,7 @@
         button.send 'click'
         button.send 'brick'
       else
-        button.send 'error', "#{sprite} not editable"
+        button.send 'error', "#{sprite.get('name')} is not editable"
   )

   _.extend buttons, {


Simplify mouse events, fix bug on drag-end when button is released outside minimap

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -37,8 +37,6 @@

     @computeScale()
     @miniScrollView = @miniEditorView.scrollView
-    # dragging's status
-    @isPressed = false
     @offsetLeft = 0
     @offsetTop = 0
     @indicator = new MinimapIndicator()
@@ -58,8 +56,6 @@
     @on 'mousedown', (e) =>
       @onMouseDown(e)
       @onDragStart(e)
-
-    @on 'mousedown', '.minimap-visible-area', @onDragStart

     @subscribe @paneView.model.$activeItem, @onActiveItemChanged
     # Fix items movin to another pane.
@@ -257,6 +253,8 @@
       @editorView.scrollTop(@editorView.scrollTop() - wheelDeltaY)

   onMouseDown: (e) =>
+    # Handle left-click only
+    return if e.which isnt 1
     @isClicked = true
     e.preventDefault()
     e.stopPropagation()
@@ -273,18 +271,15 @@
   onScrollViewResized: => @updateMinimapView()

   onDragStart: (e) =>
-    # only supports for left-click
+    # Handle left-click only
     return if e.which isnt 1
-    @isPressed = true
     @on 'mousemove.visible-area', @onMove
-    @on 'mouseup.visible-area', @onDragEnd

   onMove: (e) =>
-    @onMouseDown e if @isPressed
-
-  onDragEnd: (e) =>
-    @isPressed = false
-    @off '.visible-area'
+    if e.which is 1
+      @onMouseDown e
+    else
+      @off '.visible-area'

   # OTHER PRIVATE METHODS


use transform instead of top

use transform instead of top

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -199,7 +199,7 @@

     scrollRange = totalHeight - height

-    @miniScroller.css top: @indicator.ratioY * scrollRange
+    @transform @miniScroller[0], @translate(0, @indicator.ratioY * scrollRange)

   # EVENT CALLBACKS


Add some delegations to the editor methods

The purpose here is to be able to pass the minimap view
as an editor wherever decorations are added. It’s a poor man solution
to avoid duplicating code from other plugin.

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -12,6 +12,8 @@
   Delegato.includeInto(this)

   @delegatesMethods 'getLineHeight', 'getCharHeight', 'getCharWidth', 'getLinesCount', 'getMinimapHeight', 'getMinimapScreenHeight', 'getMinimapHeightInLines', 'getFirstVisibleScreenRow', 'getLastVisibleScreenRow', 'addLineClass', 'removeLineClass', 'removeAllLineClasses', 'pixelPositionForScreenPosition', 'decorateMarker', 'removeDecoration', 'decorationsForScreenRowRange', 'removeAllDecorationsForMarker', toProperty: 'miniEditorView'
+
+  @delegatesMethods 'getSelection', 'getSelections', 'getLastSelection', 'bufferRangeForBufferRow', 'getTextInBufferRange', 'getEofBufferPosition', 'scanInBufferRange', 'markBufferRange', toProperty: 'editor'

   @delegatesProperty 'lineHeight', toMethod: 'getLineHeight'
   @delegatesProperty 'charWidth', toMethod: 'getCharWidth'


:beers: Improve cursor placement when occurrence and  when setToStartOnUndo=true

- Since checkpoint is created before multiplying cursor, multiple
cursor is not restored but changed range is usable as alternative for
this.

- Another approach to this is creating checkpoint AFTER occurrence
selected(at this timing multi-cursor is created). But I want keep it
simple for now.

--- lib/misc-command.coffee
+++ lib/misc-command.coffee
@@ -67,11 +67,11 @@

     allRanges = sortRanges(newRanges.concat(oldRanges))

-    if @editor.hasMultipleCursors()
+    if allRanges.length > 0
       point = @editor.getCursorBufferPosition()
-      allRanges = allRanges.filter (range) -> range.containsPoint(point)
-
-    if changedRange = allRanges[0]
+      cursorContainedRanges = allRanges.filter (range) -> range.containsPoint(point)
+
+    if changedRange = cursorContainedRanges[0] ? allRanges[0]
       @vimState.mark.setRange('[', ']', changedRange)
       if settings.get('setCursorToStartOfChangeOnUndoRedo')
         if isLinewiseRange(changedRange)


'Updating for version 0.12.4'

'Updating for version 0.12.4'

--- bokehjs/src/coffee/version.coffee
+++ bokehjs/src/coffee/version.coffee
@@ -1 +1 @@
-export version = '0.12.4rc1'
+export version = '0.12.4'


a little collision animation

a little collision animation

--- dwim.coffee
+++ dwim.coffee
@@ -107,10 +107,13 @@
     @available_mappings = level.available_mappings

   requestBotMove: (dir) ->
-    @bot.x += dir.dx
-    @bot.y += dir.dy
-
-    return true
+    dest = {x: @bot.x + dir.dx, y: @bot.y + dir.dy}
+    if @level[dest.x][dest.y].type != 'obstacle'
+      @bot.x = dest.x
+      @bot.y = dest.y
+      return true
+    else
+      return false

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

@@ -145,7 +148,7 @@
       @bot_sprite.dir = anim.dir
       if not anim.start_t?
         anim.start_t = absolute_t
-        anim.start_t += @bot_sprite.leftover_t
+        anim.start_t -= @bot_sprite.leftover_t
         @bot_sprite.leftover_t = 0
       t = (absolute_t - anim.start_t) / anim.duration

@@ -155,11 +158,11 @@
         do_next = true
         @bot_sprite.x = anim.x1
         @bot_sprite.y = anim.y1
-        @bot_sprite.t = 0
+        @bot_sprite.t = anim.t1
       else
         @bot_sprite.x = (anim.x1 - anim.x0) * t + anim.x0
         @bot_sprite.y = (anim.y1 - anim.y0) * t + anim.y0
-        @bot_sprite.t = t
+        @bot_sprite.t = (anim.t1 - anim.t0) * t + anim.t0

     @bot_sprite.leftover_t = 0
     @gfx.render()
@@ -172,9 +175,42 @@
         new_pos = @bot_sprite.computePos()
         @bot_sprite.animations.push(
           duration: 100
+          t0: 0, t1: 1
           x0: old_pos.x, y0: old_pos.y
           x1: new_pos.x, y1: new_pos.y
           dir: dir
         )
+      else
+        howfar = .15*@gfx.block
+        new_pos = x: old_pos.x+dir.dx*howfar, y: old_pos.y+dir.dy*howfar
+        @bot_sprite.animations.push(
+          duration: 10
+          t0: 0, t1: .10
+          x0: old_pos.x, y0: old_pos.y
+          x1: new_pos.x, y1: new_pos.y
+          dir: dir
+        )
+        @bot_sprite.animations.push(
+          duration: 50
+          t0: .10, t1: 0
+          x0: new_pos.x, y0: new_pos.y
+          x1: new_pos.x, y1: new_pos.y
+          dir: dir
+        )
+        @bot_sprite.animations.push(
+          duration: 25
+          t0: 0, t1: .1
+          x0: new_pos.x, y0: new_pos.y
+          x1: (new_pos.x+old_pos.x)/2, y1: (new_pos.y+old_pos.y)/2
+          dir: dir
+        )
+        @bot_sprite.animations.push(
+          duration: 25
+          t0: .1, t1: 0
+          x0: (new_pos.x+old_pos.x)/2, y0: (new_pos.y+old_pos.y)/2
+          x1: old_pos.x, y1: old_pos.y
+          dir: dir
+        )
+

 window.Dwim = Dwim


connect timeout to ask for reload

connect timeout to ask for reload

--- src/scripts/channel.coffee
+++ src/scripts/channel.coffee
@@ -126,13 +126,18 @@
     url = @base + @id
     console.log 'connect', url
     @socket = sio = @io.connect url # connect to server
+    _timeout = @wait 3000, -> # 3s
+      _timeout = null
+      location.reload() if confirm 'Connection Timeout!\n Click OK to retry.'
     # listen connect
     sio.on 'connect', =>
+      _timeout = clearTimeout _timeout if _timeout
       @connected = yes
       @trigger 'connected', @ # call connected
       @socket.on 'disconnect', => @trigger 'disconnected', @ # bind disconnect
       @_bind 'system' # bind system msg
     sio.on 'connect_failed', =>
+      _timeout = clearTimeout _timeout if _timeout
       @trigger 'connectfailed', type, attempts
       alert 'connect failed'
     sio.on 'connecting', (t) ->


disable trackRateLimit if we don't have a configuration for it

--- src/remote/services/track.coffee
+++ src/remote/services/track.coffee
@@ -10,11 +10,6 @@
 MarketingProps = ['campaign', 'source', 'medium', 'term', 'content']
 TopLevelProps = ['hull_ship_id'].concat(StructuredEventProps)

-DEFAULT_RATE_LIMIT_CONFIG = {
-  capacity: 25,
-  interval: 60000
-}
-
 Identity = (o)-> o

 class HullTrackService extends GenericService
@@ -24,7 +19,11 @@
     super(config, gateway)

     @_request = @wrappedRequest
-    @rateLimitter = new LeakyBucket(config.trackRateLimit || DEFAULT_RATE_LIMIT_CONFIG)
+
+    if (config.trackRateLimit && config.trackRateLimit.capacity)
+      @rateLimitter = new LeakyBucket(config.trackRateLimit)
+    else
+      @rateLimitter = false

     RemoteUserStore.addChangeListener (change)=>
       currentUser = RemoteUserStore.getState().user
@@ -32,7 +31,10 @@

   request: (opts, callback, errback) =>
-    @rateLimitter.throttle().then =>
+    if @rateLimitter
+      @rateLimitter.throttle().then =>
+        @_sendRequest(opts, callback, errback)
+    else
       @_sendRequest(opts, callback, errback)

   _sendRequest: (opts, callback, errback) =>


Fix bug in process.coffee

Fix bug in process.coffee

--- src/scripts/tests/process.coffee
+++ src/scripts/tests/process.coffee
@@ -55,7 +55,7 @@
                 log.e(error.toString())
                 redo(task._id)
             else
-                log.i "Experiment #{styler.id name}: Test ##{times+1}: Finished #{styler.cmd command}"
+                log.i "Experiment #{styler.id name}: Test ##{task.data.test}: Finished #{styler.cmd command}"

                 result = JSON.parse(stderr)
                 result.neural_network = fs.readFileSync(NN_PATH + "/#{result.neural_network}.txt", 'utf-8')


Update process.coffee

Update process.coffee

--- src/scripts/tests/process.coffee
+++ src/scripts/tests/process.coffee
@@ -22,7 +22,7 @@
 available_threads = os.cpus().length

 METERS = ['wins', 'completion']
-URL = "http://mspacman.ml/methods/"
+URL = "http://localhost:3000/methods/"

 # Bom's: n_rounds_powerpill=55 initial_scatter_cycle_rounds=40 initial_chase_cycle_rounds=10

@@ -42,13 +42,14 @@
     catch e
         log.e "Error while performing redo request for #{taskid}: #{e.toString()}"

-done = (taskid, result) ->
-    currentTasks.splice(currentTasks.indexOf(taskid), 1)
+done = (task, result) ->
+    console.log task
+    currentTasks.splice(currentTasks.indexOf(task._id), 1)
     try
-        res = request('POST', URL + 'finishTask', { json: { _id: taskid, result } })
-        log.i "Finish request for #{taskid} handed:\n#{JSON.stringify(res, null, 2)}"
+        res = request('POST', URL + 'finishTask', { json: { _id: task._id, result, index: task.data.index, experimentId: task.data.experiment._id } })
+        log.i "Finish request for #{task._id} handed:\n#{JSON.stringify(res, null, 2)}"
     catch e
-        log.e "Error while performing done request for #{taskid}: #{e.toString()}"
+        log.e "Error while performing done request for #{task._id}: #{e.toString()}"

 perform = (task) ->
     argsString = _.toPairs(task.data.args).map((arg) -> if arg[1]? then arg.join('=') else '').join(' ')
@@ -68,7 +69,7 @@
                 result = JSON.parse(stderr)
                 result.neural_network = fs.readFileSync(NN_PATH + "/#{result.neural_network}.txt", 'utf-8')

-                done(task._id, result)
+                done(task, result)

             processTask()

@@ -82,13 +83,12 @@
         res = JSON.parse(res.getBody('utf8'))
     catch e
         log.e "Error while performing process request: #{e.toString()}"
+        return no

     if res?
         currentTasks.push(res._id)
         perform(res)
         yes
-    else
-        no

 setInterval(->


Interposing on setProperty method of CSSStyleDeclaration

--- src/server/browser/advice.coffee
+++ src/server/browser/advice.coffee
@@ -208,6 +208,16 @@
     adviseMethod html.HTMLFrameElement, 'setAttribute', createFrameAttrHandler(false)
     adviseMethod html.HTMLFrameElement, 'setAttributeNS', createFrameAttrHandler(true)

+    adviseMethod html.CSSStyleDeclaration, 'setProperty', (elem, args, rv) ->
+      parent = elem._parentElement
+      return if !parent
+      browser = getBrowser(parent)
+      if isVisibleOnClient(parent, browser)
+        browser.emit 'DOMStyleChanged',
+          target:parent
+          attribute:args[0]
+          value:args[1]
+
     # Advice for: HTMLElement.style
     #
     # JSDOM level2/style.js uses the style getter to lazily create the
@@ -218,73 +228,3 @@
     adviseProperty html.HTMLElement, 'style',
         getter : (elem, rv) ->
             rv._parentElement = elem
-
-    # This list is from:
-    #   http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
-    cssAttrs = [
-        'azimuth', 'background', 'backgroundAttachment', 'backgroundColor',
-        'backgroundImage', 'backgroundPosition', 'backgroundRepeat', 'border',
-        'borderCollapse', 'borderColor', 'borderSpacing', 'borderStyle',
-        'borderTop', 'borderRight', 'borderBottom', 'borderLeft',
-        'borderTopColor', 'borderRightColor', 'borderBottomColor',
-        'borderLeftColor', 'borderTopStyle', 'borderRightStyle',
-        'borderBottomStyle', 'borderLeftStyle', 'borderTopWidth',
-        'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth',
-        'borderWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color',
-        'content', 'counterIncrement', 'counterReset', 'cue', 'cueAfter',
-        'cueBefore', 'cursor', 'direction', 'display', 'elevation',
-        'emptyCells', 'cssFloat', 'font', 'fontFamily', 'fontSize',
-        'fontSizeAdjust', 'fontStretch', 'fontStyle', 'fontVariant',
-        'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
-        'listStyle', 'listStyleImage', 'listStylePosition', 'listStyleType',
-        'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
-        'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight',
-        'minWidth', 'orphans', 'outline', 'outlineColor', 'outlineStyle',
-        'outlineWidth', 'overflow', 'padding', 'paddingTop', 'paddingRight',
-        'paddingBottom', 'paddingLeft', 'page', 'pageBreakAfter',
-        'pageBreakBefore', 'pageBreakInside', 'pause', 'pauseAfter',
-        'pauseBefore', 'pitch', 'pitchRange', 'playDuring', 'position',
-        'quotes', 'richness', 'right', 'size', 'speak', 'speakHeader',
-        'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
-        'tableLayout', 'textAlign', 'textDecoration', 'textIndent',
-        'textShadow', 'textTransform', 'top', 'unicodeBidi', 'verticalAlign',
-        'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows', 'width',
-        'wordSpacing', 'zIndex'
-    ]
-
-    # Advice for: Element.style.*
-    # For each possible style property, add a setter to emit advice.
-    do () ->
-        proto = html.CSSStyleDeclaration.prototype
-        cssAttrs.forEach (attr) ->
-            proto.__defineSetter__ attr, (val) ->
-                # cssom seems to use some CSSStyleDeclaration objects
-                # internally, so we only want to emit instructions if there
-                # is a parent element pointer, meaning this CSSStyleDeclaration
-                # belongs to an element.
-                parent = this._parentElement
-                prop = "_#{attr}"
-
-                # To explain this if statement, see cssom's
-                #    CSSStyleDeclaration.js.
-                # CSSStyleDeclaration treats itself like an array, with each
-                # index containing a string for a property that has been set
-                # on the object.  See CSSStyleDeclaration#setProperty.
-                # The array indices are used by the cssText getter to iterate
-                # over the CSS properties that have been set, so not doing this
-                # was causing cssText to miss properties, and therefore our
-                # serializer to miss CSS attributes (due to JSDOM's StyleAttr
-                # object, which uses cssText internally).
-                if !this[prop]
-                    this[this.length++] = attr
-                rv = this[prop] = val
-                if parent
-                    browser = getBrowser(parent)
-                    if isVisibleOnClient(parent, browser)
-                        browser.emit 'DOMStyleChanged',
-                            target    : parent
-                            attribute : attr
-                            value     : val
-                return rv
-            proto.__defineGetter__ attr, () ->
-                return @["_#{attr}"]


put in some logging related to COCALC_EXTRA_ENV

--- src/smc-project/local_hub.coffee
+++ src/smc-project/local_hub.coffee
@@ -375,10 +375,13 @@

 # Contains additional environment variables. Base 64 encoded JSON of {[key:string]:string}.
 set_extra_env = ->
-    return if not process.env.COCALC_EXTRA_ENV
+    if not process.env.COCALC_EXTRA_ENV
+        winston.debug("set_extra_env: nothing provided")
+        return
     try
         env64 = process.env.COCALC_EXTRA_ENV
         raw = Buffer.from(env64, 'base64').toString('utf8')
+        winston.debug("set_extra_env: #{raw}")
         data = JSON.parse(raw)
         if typeof data == 'object'
             for k, v of data


make sure project id and username are set for kucalc

--- src/smc-project/local_hub.coffee
+++ src/smc-project/local_hub.coffee
@@ -113,9 +113,13 @@
 init_info_json = (cb) ->
     winston.debug("Writing 'info.json'")
     filename = "#{SMC}/info.json"
-    v = process.env.HOME.split('/')
-    project_id = v[v.length-1]
-    username   = project_id.replace(/-/g,'')
+    if process.env.COCALC_PROJECT_ID? and process.env.COCALC_USERNAME?
+        project_id = process.env.COCALC_PROJECT_ID
+        username   = process.env.COCALC_USERNAME
+    else
+        v = process.env.HOME.split('/')
+        project_id = v[v.length-1]
+        username   = project_id.replace(/-/g,'')
     if process.env.SMC_HOST?
         host = process.env.SMC_HOST
     else if os.hostname() == 'sagemathcloud'


fixing watch folder method

fixing watch folder method

--- src/toaster/utils/fs-util.coffee
+++ src/toaster/utils/fs-util.coffee
@@ -45,7 +45,7 @@
 		filepath = pn filepath

 		if dispatch_create
-			onchange?( {type:"file", path:filepath, action:"create"} )
+			onchange?( {type:"file", path:filepath, action:"created"} )

 		onchange?( {type:"file", path:filepath, action:"watching"} )

@@ -97,7 +97,7 @@
 									continue

 							onchange?( info )
-							FsUtil.watch_file info.path, onchange, true
+							FsUtil.watch_file info.path, onchange

 						else if info.type == "folder"


Uniqueness validator fix

Uniqueness validator fix

--- src/tower/model/validator.coffee
+++ src/tower/model/validator.coffee
@@ -54,6 +54,8 @@
         new @Format(name, value, attributes, options)
       when 'in', 'except', 'only', 'notIn', 'values', 'accepts'
         new @Set(name, value, attributes, options)
+      when 'uniqueness', 'unique'
+        new @Uniqueness(name, value, attributes, options)

   constructor: (name, value, attributes, options = {}) ->
     @name       = name


Switch topic bookmarking test to wait for selector

--- frontend/topic_bookmarking.coffee
+++ frontend/topic_bookmarking.coffee
@@ -58,11 +58,17 @@
 casper.then ->
   @click utils.xpath(utils.a_text_xpath('Remove from bookmarks'))

-  @waitForResource /viewtopic\.php.*bookmark=1/
+  # This is iffy because the same url was loaded previously.
+  # On freebsd this works, on linux it does not.
+  # Wait for the selector instead
+  #@waitForResource /viewtopic\.php.*bookmark=1/
+
+  @waitForSelector utils.a_text_xs('Bookmark topic')

 casper.then ->
   @test.assertNotExists utils.a_text_xs('Remove from bookmarks')
-  @test.assertExists utils.a_text_xs('Bookmark topic')
+  # We now wait for this, therefore no point in checking it
+  #@test.assertExists utils.a_text_xs('Bookmark topic')

 casper.run ->
   @test.done()


:memo: doc parameters in directory.coffee

--- src/directory.coffee
+++ src/directory.coffee
@@ -7,7 +7,7 @@

 File = require './file'

-# Public: Represents a directory using {File}s.
+# Public: Represents a directory on disk.
 #
 # ## Requiring in packages
 #
@@ -18,15 +18,12 @@
 class Directory
   Emitter.includeInto(this)

-  path: null
   realPath: null

   # Public: Configures a new Directory instance, no files are accessed.
   #
-  # * path:
-  #   A String containing the absolute path to the directory.
-  # + symlink:
-  #   A Boolean indicating if the path is a symlink (defaults to false).
+  # path - A {String} containing the absolute path to the directory.
+  # symlink - A {Boolean} indicating if the path is a symlink (default: false).
   constructor: (@path, @symlink=false) ->
     @on 'first-contents-changed-subscription-will-be-added', =>
       # Triggered by emissary, when a new contents-changed listener attaches
@@ -36,7 +33,7 @@
       # Triggered by emissary, when the last contents-changed listener detaches
       @unsubscribeFromNativeChangeEvents()

-  # Public: Returns the basename of the directory.
+  # Public: Returns the {String} basename of the directory.
   getBaseName: ->
     path.basename(@path)

@@ -108,8 +105,8 @@

   # Public: Reads file entries in this directory from disk asynchronously.
   #
-  # * callback: A function to call with an Error as the first argument and
-  #   an {Array} of {File} and {Directory} objects as the second argument.
+  # callback - A {Function} to call with an {Error} as the 1st argument and
+  #            an {Array} of {File} and {Directory} objects as the 2nd argument.
   getEntries: (callback) ->
     fs.list @path, (error, entries) ->
       return callback(error) if error?


:art: Remove whitespace

:art: Remove whitespace

--- spec/about-spec.coffee
+++ spec/about-spec.coffee
@@ -196,8 +196,6 @@
           runs ->
             expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled()

-
-
 describe "the status bar", ->
   workspaceElement = null


escaping HTML tags is better than stripping them

--- js/libs/string-helpers.coffee
+++ js/libs/string-helpers.coffee
@@ -8,12 +8,17 @@
       start_slice = Math.ceil((goal_len - 3) / 3)
       str.slice(0, start_slice) + "..." + str.slice(start_slice + over)

-  stripHTMLTagsExcept: (html, allowedTags) ->
+  # escapes all HTML tags in html except for those explicity allowed
+  escapeHTMLTagsExcept: (html, allowedTags) ->
+    escape = (html) ->
+      $('<textarea>').html(html).html()   # see http://stackoverflow.com/a/9251169/2608804
+
     container = $("
#{html}
")
     container.find("*:not(#{allowedTags})").each (i, elt) ->
-      $(elt).replaceWith(elt.innerText)
+      $(elt).replaceWith(escape(elt.outerHTML))
     container.html()

+  # strips all attributes from all HTML tags in html except for those explicitly allowed
   stripHTMLAttributesExcept: (html, allowedAttributes) ->
     container = $("
#{html}
")
     container.find("*").each (i, elt) ->
@@ -56,7 +61,7 @@
                  .replace(issuePattern, "#$1")

     # sanitize HTML to protect against potential XSS exploits
-    text = @stripHTMLTagsExcept(text, ['a'])
+    text = @escapeHTMLTagsExcept(text, ['a'])
     text = @stripHTMLAttributesExcept(text, ['href', 'target'])

     text


Atom font size

Atom font size

--- states/atom/.atom/config.cson
+++ states/atom/.atom/config.cson
@@ -1,6 +1,6 @@
 "*":
   editor:
-    fontSize: 14
+    fontSize: 12
     showInvisibles: true
     showIndentGuide: true
     tabLength: 4


Simplified MessageService._searchToParams

--- static/coffee/services.coffee
+++ static/coffee/services.coffee
@@ -197,16 +197,16 @@
     # Convert search object to URL params
     #----------------------------------------------------------------------------
     _searchToParams: (search) ->
-      params = {}
-      params.folder = search.folder
-      params.text = search.text
-      params.after = formatIso8601(search.after)
-      params.before = formatIso8601(search.before)
-      params.groups = (g.uuid for g in search.groups).join(',')
-      params.contact = if search.contact then search.contact.uuid else null
-      params.label = if search.label then search.label.id else null
-      params.archived = if search.archived then 1 else 0
-      return params
+      return {
+        folder: search.folder,
+        text: search.text,
+        after: formatIso8601(search.after),
+        before: formatIso8601(search.before),
+        groups: (g.uuid for g in search.groups).join(','),
+        contact: if search.contact then search.contact.uuid else null,
+        label: if search.label then search.label.id else null,
+        archived: if search.archived then 1 else 0
+      }

     #----------------------------------------------------------------------------
     # POSTs to the messages action endpoint


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -43,7 +43,7 @@
   ###*
   Get the object closest to this one.

-  @param
+  @param @param {Object|Array|String} groupB An object or set of objects to check collisions with
   ###
   closest: (selector) ->
     if Object.isString(selector)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -42,9 +42,9 @@

   closest: (selector) ->
     if Object.isString(selector)
-        groupA = engine.find(selector)
+        selector = engine.find(selector)
       else
-        groupA = [].concat(groupA)
+        selector = [].concat(groupA)

   ###*
   Distance between two objects. Proxies to Point.distance.


prepare parent._browser.done before used

--- src/browser.coffee
+++ src/browser.coffee
@@ -122,7 +122,7 @@
             @animation.push(cb)

         ecb = el._browser.done.callback()
-        pcb = parent._browser.done.callback()
+        pcb = prepare_deferred_done(parent).callback()
         if el is el.builder then ecb() else el.ready(ecb)
         if parent is parent.builder then pcb() else parent.ready(pcb)

@@ -133,7 +133,7 @@
         el._browser.parent_done.replace?(el).callback ?= ->
             return if removed this
             that.parent_done_callback(this)
-        prepare_deferred_done(parent).call(el._browser.parent_done.call)
+        parent._browser.done.call(el._browser.parent_done.call)

     onreplace: (oldtag, newtag) ->
         return if removed(oldtag) or removed(newtag)


Update viewport size according to orientation changes

http://stackoverflow.com/questions/12102235/how-to-fit-to-screen-after-changing-viewport-width-on-orientation-change

See #26

--- static/script/settlers.coffee
+++ static/script/settlers.coffee
@@ -168,6 +168,25 @@
         url:			'/logout/'
       return false

+  # needed for iOS mobile devices (well, afaik...)
+  adapt_to_orientation = () ->
+    if window.orientation == 0 or window.orientation == 180
+      content_width = 758
+      screen_dimension = screen.width * 0.98
+
+    else if window.orientation == 90 or window.orientation == -90
+      content_width = 1024
+      screen_dimension = screen.height
+
+    viewport_scale = screen_dimension / content_width
+
+    $('meta[name=viewport]').attr 'content', ('width=' + content_width + ', minimum-scale=' + viewport_scale + ', maximum-scale=' + viewport_scale)
+
+  adapt_to_orientation()
+
+  window.addEventListener 'orientationchange', () ->
+    adapt_to_orientation()
+
 $(window).bind 'hlib_poststartup', () ->
   $('a[rel=tooltip]').tooltip()
   $('button[rel=tooltip]').tooltip()


Simplify some logic

Simplify some logic

--- src/bugsnag.coffee
+++ src/bugsnag.coffee
@@ -65,7 +65,7 @@

   # The request middleware for express/connect. Ensures next(err) is called when there is an error, and
   # tracks the request for manual notifies.
-  @requestHandler: (req, res, next) =>
+  @requestHandler: (req, res, next) ->
     dom = domain.create()
     dom._bugsnagOptions =
       req: req
@@ -105,7 +105,7 @@
   shouldNotify = ->
     ( Configuration.notifyReleaseStages == null || Configuration.notifyReleaseStages.indexOf(Configuration.releaseStage) != -1 ) && Configuration.apiKey

-  autoNotifyCallback = (notifiedError, uncaught = false) ->
+  autoNotifyCallback = (notifiedError, uncaughtError = notifiedError.domain) ->
     (error) ->
       Configuration.logger.error "Bugsnag: error notifying bugsnag.com - #{error}" if error
-      Configuration.onUncaughtError(notifiedError) if Configuration.onUncaughtError && (notifiedError.domain || uncaught)+      Configuration.onUncaughtError(notifiedError) if Configuration.onUncaughtError && uncaughtError


ngTemplates fix

ngTemplates fix

--- app/back/lib/ngtemplate.coffee
+++ app/back/lib/ngtemplate.coffee
@@ -1,4 +1,7 @@
-Template = require('mincer/lib/mincer/template')
+
+# Template = require('mincer/lib/mincer/template')
+Mincer = require 'mincer'
+Template = Mincer.Template
 prop     = require('mincer/lib/mincer/common').prop
 templatecache = require('ng-templatecache')


Get dirname.

Get dirname.

--- src/bundler.coffee
+++ src/bundler.coffee
@@ -1,4 +1,5 @@
 fs         = require 'fs'
+path       = require 'path'

 convert    = require 'convert-source-map'
 postmortem = require 'postmortem'
@@ -27,7 +28,7 @@
     return cb null, cached.sourceMapSupport

   opts =
-    base:      require.resolve 'source-map-support'
+    base:      path.dirname require.resolve 'source-map-support'
     prelude:   false
     sourceMap: false


storage: minor improvements to logging and snapshot mounting

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -879,15 +879,17 @@
         (cb) ->
             dbg("ensure all local snapshots are mounted (should take at most 30s) -- due to sftp chroot we need to do this, since zfs automount doesn't work")
             misc_node.execute_code
-                command : 'ls'
-                args    : ["/#{server}/.zfs/snapshot/*/NO_SUCH_FILE"]
+                bash    : true  # important to use bash shell so that * below works
+                command : "ls /#{server}/.zfs/snapshot/*/NO_SUCH_FILE"
                 cb      : (err) ->
                     cb()  # explicitly ignore the error we get due to NO_SUCH_FILE
         (cb) ->
             mnt = "/mnt/snapshots/#{server}/"
+            remote = "fusermount -u #{mnt}; mkdir -p #{mnt}/; chmod a+rx /mnt/snapshots/ #{mnt}; sshfs -o ro,allow_other,default_permissions #{server}:/ #{mnt}/; ls #{mnt}/"
+            dbg("run this on #{opts.host}:   #{remote}")
             misc_node.execute_code
                 command : 'ssh'
-                args    : [opts.host, "fusermount -u #{mnt}; mkdir -p #{mnt}/; chmod a+rx /mnt/snapshots/ #{mnt}; sshfs -o ro,allow_other,default_permissions #{server}:/ #{mnt}/; ls #{mnt}/"]
+                args    : [opts.host, remote]
                 cb      : (err, output) ->
                     if err
                         cb(err)
@@ -895,6 +897,7 @@
                         numfiles = misc.split(output.stdout).length
                         cb()
         (cb) ->
+            dbg("verify that number of snapshot files is about the same (just a double check)")
             fs.readdir "/#{server}/.zfs/snapshot", (err, files) ->
                 if err
                     cb(err)


fix #2130, hopefully.

fix #2130, hopefully.

--- src/smc-util/db-doc.coffee
+++ src/smc-util/db-doc.coffee
@@ -660,9 +660,10 @@
     # change (or create) exactly *one* database entry that matches
     # the given where criterion.
     set: (obj, save=true) =>
-        if not @_doc?
-            return
-        @_doc.set_doc(new Doc(@_doc.get_doc()._db.set(obj)))
+        doc = @_doc?.get_doc()
+        if not doc?   # see https://github.com/sagemathinc/cocalc/issues/2130
+            return
+        @_doc.set_doc(new Doc(doc._db.set(obj)))
         if save
             @_doc.save()
         @_on_change()


allow absolute paths

allow absolute paths

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -1013,8 +1013,7 @@

     handle_click: (e) ->
         e.preventDefault()
-        path_head = 'files'
-        path_head += '/' if @props.path[0] != '/'
+        path_head = 'files/'
         @actions('projects').open_project
             project_id : @props.project_id
             target     : path_head + @props.path


Typo I guess

Typo I guess

--- src/channel.coffee
+++ src/channel.coffee
@@ -47,7 +47,7 @@

       when "channel_leave", "group_leave"
         index = @members.indexOf message.user
-        if index not -1
+        if index isnt -1
           @members.splice index

         @_history[message.ts] = message


Fix the average value used for parameters

--- src/gui/scene.coffee
+++ src/gui/scene.coffee
@@ -27,9 +27,9 @@
             when 'tolerance'
               [id,meta,defaultValue] = attr[0] # unwrap tolerance tag
               if Array.isArray defaultValue.min
-                defaultValue = (defaultValue.min[i] + defaultValue.max[i] for i in [0...defaultValue.min.length])
+                defaultValue = ((defaultValue.min[i] + defaultValue.max[i]) * 0.5 for i in [0...defaultValue.min.length])
               else
-                defaultValue = defaultValue.min + defaultValue.max
+                defaultValue = (defaultValue.min + defaultValue.max) * 0.5
             when 'range'
               throw "TODO: Range not yet implemented"
             else


Add test to ensure onDestroy callback fires.

--- tests/specs/SuperfishTest.coffee
+++ tests/specs/SuperfishTest.coffee
@@ -116,10 +116,24 @@
       $menu.superfish
         pathClass: 'current'
       expect($menu.data('sf-options').$path.length).toEqual 1
-
+
+
   describe "pathClass feature", ->
     it "should show 'current' submenu", ->
       $menu.superfish('destroy')
       $menu.superfish
         pathClass: 'current'
       expect($li_with_sub).toBe '.sfHover'
+
+
+  describe "callbacks", ->
+
+    describe "onDestroy", ->
+      it "should fire", ->
+        destroy_count = 0
+        $menu.superfish('destroy')
+        $menu.superfish
+          onDestroy: -> destroy_count++
+        $menu.superfish('destroy')
+        console.log destroy_count
+        expect(destroy_count).toEqual 1


Tests:IDE:Workspace: fixed createWorkspace helper.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -353,8 +353,8 @@
     workspaceName = paragraph.split(' ')[0]

     browser
-      .waitForElementVisible   '.kdscrollview li a.more-link', 20000
-      .click                   '.kdscrollview li a.more-link'
+      .waitForElementVisible   '.activity-sidebar .workspaces-link', 20000
+      .click                   '.activity-sidebar .workspaces-link'
       .waitForElementVisible   '.kdmodal-inner', 20000
       .click                   '.kdmodal-inner button'
       .pause                   3000 # required


use regexes to detect valid avatar_src urls vs paths (#5866)

--- app/partials/project-card.cjsx
+++ app/partials/project-card.cjsx
@@ -25,9 +25,10 @@
     if !!@props.imageSrc
       conditionalStyle.backgroundImage = "url('#{ @props.imageSrc }')"
     else if !!@props.project.avatar_src
-      try
-        backgroundImageSrc = new URL(@props.project.avatar_src)
-      catch error
+      urlRegex = new RegExp('^https?:\/\/[^\s]+');
+      if urlRegex.test(@props.project.avatar_src)
+        backgroundImageSrc = @props.project.avatar_src
+      else
         backgroundImageSrc = "//#{ @props.project.avatar_src }"

       conditionalStyle.backgroundImage = "url('#{ backgroundImageSrc }')"


fix bug api v1 & v2 add restangularV2

fix bug api v1 & v2 add restangularV2

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -468,7 +468,7 @@
 )

 .controller('LoginController', (
-                                  $rootScope, $scope, Restangular, $state,
+                                  $rootScope, $scope, Restangular, RestangularV2, $state,
                                   Authentification, authManager, jwtHelper
                                 ) ->

@@ -488,6 +488,7 @@
               localStorage.setItem('user_id', tokenDecode.user_id)
               # set header
               Restangular.setDefaultHeaders({Authorization: "JWT "+ auth.token})
+              RestangularV2.setDefaultHeaders({Authorization: "JWT "+ auth.token})

               $state.go("candidature.resume")

@@ -569,7 +570,7 @@
 )

-.controller('CivilStatusController', ($rootScope, $scope, $state, $filter, ISO3166, Restangular, Upload) ->
+.controller('CivilStatusController', ($rootScope, $scope, $state, $filter, ISO3166, Restangular, RestangularV2, Upload) ->

   if(!$scope.isAuthenticated)
     $state.go("candidature")
@@ -581,7 +582,7 @@
     # console.log($scope.form)

     # method 1 - copie du model et changement de valeurs sur des données "dirty"
-    model_copy =  Restangular.copy(model)
+    model_copy =  RestangularV2.copy(model)

     if model_copy.profile.photo
       delete model_copy.profile.photo
@@ -606,7 +607,7 @@

 .controller('CivilStatusAdressController', ($rootScope, $q,
-            $scope, $state, $filter, ISO3166, Restangular, Upload) ->
+            $scope, $state, $filter, ISO3166, Restangular, RestangularV2, Upload) ->

     $rootScope.loadInfos($rootScope)
@@ -644,7 +645,7 @@

     $scope.save = (model) ->
       # console.log($scope.form)
-      model_copy =  Restangular.copy(model)
+      model_copy =  RestangularV2.copy(model)

       if model.profile.birthdate
         model_copy.profile.birthdate = $filter('date')(model.profile.birthdate, 'yyyy-MM-dd')
@@ -689,7 +690,8 @@

 )

-.controller('CivilStatusLanguageController', ($rootScope, $scope, $state, $filter, ISO3166, Restangular, Upload) ->
+.controller('CivilStatusLanguageController', ($rootScope, $scope, $state, $filter, ISO3166,
+    Restangular, RestangularV2, Upload) ->

     $rootScope.loadInfos($rootScope)

@@ -739,8 +741,8 @@

     $scope.save = (model) ->
-        console.log($scope.form)
-        user_copy = Restangular.copy($scope.user)
+
+        user_copy = RestangularV2.copy($scope.user)

         if user_copy.profile.photo
           delete user_copy.profile.photo
@@ -785,7 +787,7 @@
 #cursus
 .controller('CursusController', (
         $rootScope, $scope, $q, $state, $filter
-        Users, ArtistsV2, Restangular, Candidatures, Media, Galleries,
+        Users, ArtistsV2, Restangular, RestangularV2, Candidatures, Media, Galleries,
         ISO3166, Upload,
       ) ->

@@ -805,7 +807,7 @@
       $scope.save = (model) ->
         console.log($scope.form)
         # save medium
-        model_copy = Restangular.copy(model)
+        model_copy = RestangularV2.copy(model)

         if model_copy.picture
           delete model_copy.picture
@@ -820,7 +822,7 @@
           cursus += "\n"

         $scope.user.profile.cursus = cursus
-        user_copy = Restangular.copy($scope.user)
+        user_copy = RestangularV2.copy($scope.user)

         if user_copy.profile.birthdate
           user_copy.profile.birthdate = $filter('date')(user_copy.profile.birthdate, 'yyyy-MM-dd')


Set back to beginning

Set back to beginning

--- app/scripts/scenes/Game.coffee
+++ app/scripts/scenes/Game.coffee
@@ -12,7 +12,7 @@
   level.start()

   options =
-    startAtCheckpoint: data.checkpoint ? 6
+    startAtCheckpoint: data.checkpoint ? 0

   if data.checkpoint
     label = "Checkpoint #{data.checkpoint}"


remove crypto options

remove crypto options

--- app/services/currencies.coffee
+++ app/services/currencies.coffee
@@ -9,6 +9,4 @@
     { code: 'GBP', symbol: '£' },
     { code: 'EUR', symbol: '€' },
     { code: 'CHF', symbol: 'CHF' },
-    { code: 'JPY', symbol: '¥' },
-    { code: 'XBT', symbol: 'Ƀ' },
-    { code: 'ETH', symbol: 'Ξ' }]
+    { code: 'JPY', symbol: '¥' }]


Fix typo in template in grunt file

Fix typo in template in grunt file

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -108,7 +108,7 @@

     open:
       server:
-        path: "http://localhost:<%%= connect.options.port %>"
+        path: "http://localhost:<%= connect.options.port %>"

     uglify:
       app:


Add check/map for node module concat

Add check/map for node module concat

--- src/codecat.coffee
+++ src/codecat.coffee
@@ -1,6 +1,6 @@
 {EOL} = require 'os'
 Readline = require 'readline'
-{readFileSync, createReadStream, createWriteStream} = require 'fs'
+{readFileSync, createReadStream, createWriteStream, statSync} = require 'fs'
 {Writable} = require 'stream'
 Path = require 'path'

@@ -26,8 +26,7 @@
 			Relative:
 				value: class extends CodeCat
 					constructor: (src) ->
-						sourceDir = Path.dirname(source)
-						relSrc = Path.join(sourceDir, src)
+						relSrc = getRelativePath(source, src)
 						super(relSrc, options)

 	findConcats: (options, callback) ->
@@ -59,7 +58,7 @@
 		ensureStream dest, defaultEncoding: @encoding, (stream, end) =>
 			@findConcats (concats) =>
 				error = null
-				concats = mapConcats(concats, newRelative, this) if recursive
+				concats = mapConcats(concats, mapRelative, this) if recursive
 				paths = [concats.prepend..., @source, concats.append...]
 				joinFiles paths, @encoding, stream, options, -> callback?(error)

@@ -75,8 +74,21 @@
 		commenter = CodeCat.Commenters[ext]
 		if commenter? then regexpEscape(commenter) else ''

-	newRelative = (source) ->
-		new @Relative(source)
+	mapRelative = (source) ->
+		stats =
+			try
+				statSync getRelativePath(@source, source)
+			catch
+				null
+		if stats?.isFile()
+			new @Relative(source)
+		# else if stats?.isDirectory()
+		else
+			try
+				require.resolve(source)
+			catch
+				console.error("Invalid file or node module \"#{source}\"")
+				null

 	joinFiles = (files, encoding, stream, options = {}, finishedFn) ->
 		{separator = EOL} = options
@@ -103,10 +115,16 @@
 	mapConcats = (concats, map, thisArg) ->
 		mapped = {}
 		for own type, files of concats
-			mapped[type] = files.map(map, thisArg)
+			mapped[type] = files
+				.map(map, thisArg)
+				.filter (c) -> c?
 		return mapped

 	# General utility functions
+
+	getRelativePath = (src, rel) ->
+		srcDir = Path.dirname(src)
+		Path.join(srcDir, rel)

 	regexpEscape = (str) ->
 		str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')


change upgrade notice

change upgrade notice

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -274,9 +274,9 @@
         ###

         if host and internet
-            mesg = Upgrade this project, since it is on an unpaid trial server and has no network access.
+            mesg = Time to upgrade this project, since it is on an unpaid trial server and has no network access.
         else if host
-            mesg = Upgrade this project, since it is on an unpaid trial server.
+            mesg = Time to upgrade this project, since it is on an unpaid trial server.
         else if internet
             mesg = WARNING: this project does not have network access.


adding a third test for flush-top header

--- test/vs-sticky_spec.coffee
+++ test/vs-sticky_spec.coffee
@@ -24,8 +24,6 @@

header3

-
header4

-

       """
@@ -37,10 +35,19 @@
     header2PositionTopOld = $('#header2').position().top
     chai.expect(header2PositionTopOld).to.equal 200

-  it "wraps sticky header upon scroll", (done) ->
+  it "sets position on top of document upon scrolling to next section", (done) ->
+    vsSticky = new VsSticky($('.sticky-header-container'))
+    $(window).scrollTop(250);
+    _.defer ->
+      header2PositionTop = $('#header2').position().top
+      chai.expect(header2PositionTop).to.equal 0
+      done()
+
+  it "sets position on top of document and moves out sticky header upon partial scrolling to next section", (done) ->
     vsSticky = new VsSticky($('.sticky-header-container'))
     $(window).scrollTop(350);
     _.defer ->
       header2PositionTop = $('#header2').position().top
       chai.expect(header2PositionTop).to.equal -50
       done()
+


wktreader: another test for the wktreader

--- test/wktreader-test.coffee
+++ test/wktreader-test.coffee
@@ -36,6 +36,13 @@
         assert.instanceOf e, Error
         assert.equal e.message, "IllegalArgumentException: point array must contain 0 or >1 elements\n"

+    "should read a wellformed WKT which represents a invalid geometry": (reader) ->
+      multipolygon = "MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),
+        ((3 3,6 2,6 4,3 3)),
+        ((78 45,65 34,100 54,78 45),(4 65, 54 23, 544 346, 2 1, 4 65)))"
+      geom = reader.read multipolygon
+      assert.isFalse geom.isValid()
+
   "A WKTReader with non standard GeometryFactory":
     topic: ->
       new WKTReader new GeometryFactory new PrecisionModel "FIXED"


syntax hl for : after :Class to specify inheritance

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -58,11 +58,14 @@
       oi: 0 #   oi: outer indent -- the indent of the opening token's line
       ii: 0 #   ii: inner indent -- the indent of the block's body; it can be adjusted later
     ]
+    # kw:   # current keyword
     # vars: # local names in a tradfn

   token: (stream, h) -> # h:state
     {a} = h; la = last a; n = stream.indentation()
-    if stream.sol() && !stream.match /^\s*(:|∇|$)/, false then a[a.length - 1] = $.extend {ii: n}, la
+    if stream.sol()
+      delete h.kw
+      if !stream.match /^\s*(:|∇|$)/, false then a[a.length - 1] = $.extend {ii: n}, la
     if h.hdr
       delete h.hdr; stream.match /[^⍝\n\r]*/; s = stream.current()
       if /^\s*:/.test(s) || dfnHeader.test s
@@ -77,6 +80,7 @@
       switch c
         when ' ' then stream.eatSpace(); null
         when '⍝' then stream.skipToEnd(); 'apl-com'
+        when '⋄' then delete h.kw; la.t !in ['(', '['] && 'apl-diam' || 'apl-err'
         when '←' then 'apl-asgn'
         when "'" then (if stream.match /^(?:[^'\r\n]|'')*'/ then 'apl-str' else stream.skipToEnd(); 'apl-err')
         when '⍬' then 'apl-zld'
@@ -87,7 +91,6 @@
         when ']' then (if la.t == '[' then a.pop(); 'apl-sqbr' else 'apl-err')
         when '}' then (if la.t == '{' then a.pop(); "apl-dfn#{1 + dfnDepth a} apl-dfn" else 'apl-err')
         when ';' then la.t == '[' && 'apl-semi' || 'apl-err'
-        when '⋄' then la.t !in ['(', '['] && 'apl-diam' || 'apl-err'
         when '⎕' then stream.match(/[áa-z0-9]*/i)?[0].toLowerCase() in quadNames && 'apl-quad' || 'apl-err'
         when '⍞' then 'apl-quad'
         when '#' then 'apl-ns'
@@ -129,7 +132,8 @@
                   for y in ['constructor', 'destructor', 'method', 'trigger'] when x == y[...x.length] then ok = 1; break
                 else
                   ok = 1
-            ok && 'apl-kw' || 'apl-err'
+              when '' then ok = h.kw == 'class' # ":" is allowed after ":Class" to specify inheritance
+            if ok then h.kw = kw; 'apl-kw' else delete h.kw; 'apl-err'
         else
           if name0.test c
             stream.match name1; x = stream.current(); dd = dfnDepth a


Remove alert from checkins

Remove alert from checkins

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -65,10 +65,6 @@
             <p>
             Не забудьте с собой паспорт (если паспорта еще нет, то свидетельство о рождении) -- его могут спросить охранники на входе!
             </p>
-
-            <Alert bsStyle="danger">
-                Занятия 9 декабря не будет. Ниже — запись на 16 декабря.
-            </Alert>

             {
             if @state.result?.error


Move Impromptu constructor before internal methods.

--- src/impromptu.coffee
+++ src/impromptu.coffee
@@ -12,6 +12,28 @@

   compiledPrompt: "#{@CONFIG_DIR}/.compiled/prompt.js"
   paths: "#{@CONFIG_DIR}/prompt.#{ext}" for ext in ['coffee', 'js']
+
+  constructor: (@options = {}) ->
+    @color = new Impromptu.Color @
+    @repository = new Impromptu.RepositoryFactory @
+    @db = new Impromptu.DB @
+
+    @module = new Impromptu.ModuleFactory @
+    @prompt = new Impromptu.Prompt @
+
+    # Make sure we have a source prompt
+    return unless sourcePrompt = _.find @paths, (path) ->
+      fs.existsSync path
+
+    # Regenerate the prompt if it's been changed or doesn't exist
+    @_compilePrompt sourcePrompt if @_isPromptStale sourcePrompt
+
+    # Load a new Impromptu module from a file.
+    prompt = require @compiledPrompt
+    return unless typeof prompt == 'function'
+
+    # Go!
+    prompt.call @, Impromptu, @prompt.section

   _ensureCompiledDirExists: ->
     compiledDir = path.dirname @compiledPrompt
@@ -38,28 +60,6 @@
       fs.writeFileSync @compiledPrompt, compiledJs
     else
       return
-
-  constructor: (@options = {}) ->
-    @color = new Impromptu.Color @
-    @repository = new Impromptu.RepositoryFactory @
-    @db = new Impromptu.DB @
-
-    @module = new Impromptu.ModuleFactory @
-    @prompt = new Impromptu.Prompt @
-
-    # Make sure we have a source prompt
-    return unless sourcePrompt = _.find @paths, (path) ->
-      fs.existsSync path
-
-    # Regenerate the prompt if it's been changed or doesn't exist
-    @_compilePrompt sourcePrompt if @_isPromptStale sourcePrompt
-
-    # Load a new Impromptu module from a file.
-    prompt = require @compiledPrompt
-    return unless typeof prompt == 'function'
-
-    # Go!
-    prompt.call @, Impromptu, @prompt.section

 # Create custom errors by extending `Impromptu.Error`.


Add screen Mixin ns

Add screen Mixin ns

--- client/lanes/lib/namespace.coffee
+++ client/lanes/lib/namespace.coffee
@@ -8,5 +8,5 @@
     target = target[item] or= {} for item in name.split '.'
     block( target, top ) if typeof block == 'function'

-for ns in ['Components', 'Models.Mixins', 'Views.Mixins', 'Screens', 'Vendor', 'Templates', 'Extensions','lib']
+for ns in ['Components', 'Models.Mixins', 'Views.Mixins', 'Screens.Mixins', 'Vendor', 'Templates', 'Extensions','lib']
     Lanes.namespace(ns)


:green_heart:

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -560,6 +560,7 @@
   handleStylingChange: =>
     @sampleFontStyling()
     @sampleBackgroundColors()
+    @presenter.characterWidthsChanged()

   handleDragUntilMouseUp: (dragHandler) =>
     dragging = false
@@ -708,8 +709,6 @@
     oldFontFamily = @fontFamily
     oldLineHeight = @lineHeight

-    @presenter.characterWidthsChanged()
-
     {@fontSize, @fontFamily, @lineHeight} = getComputedStyle(@getTopmostDOMNode())

     if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
@@ -824,6 +823,7 @@
   setFontSize: (fontSize) ->
     @getTopmostDOMNode().style.fontSize = fontSize + 'px'
     @sampleFontStyling()
+    @presenter.characterWidthsChanged()

   getFontFamily: ->
     getComputedStyle(@getTopmostDOMNode()).fontFamily
@@ -831,10 +831,12 @@
   setFontFamily: (fontFamily) ->
     @getTopmostDOMNode().style.fontFamily = fontFamily
     @sampleFontStyling()
+    @presenter.characterWidthsChanged()

   setLineHeight: (lineHeight) ->
     @getTopmostDOMNode().style.lineHeight = lineHeight
     @sampleFontStyling()
+    @presenter.characterWidthsChanged()

   setShowIndentGuide: (showIndentGuide) ->
     atom.config.set("editor.showIndentGuide", showIndentGuide)


Remove unused getGlobal function

Remove unused getGlobal function

--- src/util.coffee
+++ src/util.coffee
@@ -68,8 +68,6 @@

   jq.map -> getTextNodes(this)

-Util.getGlobal = -> (-> this)()
-
 # Public: decides whether node A is an ancestor of node B.
 #
 # This function purposefully ignores the native browser function for this,


fix: get either `window.jQuery or window.$`, not always `jQuery`

--- src/util.coffee
+++ src/util.coffee
@@ -2,6 +2,7 @@
 # -----------

 if window['jQuery'] or window['$']
+  jQuery = window['jQuery'] or window['$']
   [bindMethod, unbindMethod] = if 'on' of jQuery.prototype then ['on', 'off'] else ['bind', 'unbind']

   Rivets.Util =


reverting commit 4572244c4cad960a09193418b898cfd20e47e59f

we use both "type strings" and "type descriptors", so we can't rely on
having the L...; format. TODO: move to only using type descriptors

--- src/util.coffee
+++ src/util.coffee
@@ -261,7 +261,7 @@
   convert_if_prim type_str[1...]
 root.is_array_type = (type_str) -> type_str[0] == '['
 root.is_primitive_type = (type_str) -> type_str of external2internal
-root.is_reference_type = (type_str) -> type_str[0] == 'L'
+root.is_reference_type = (type_str) -> not (root.is_array_type type_str or root.is_primitive_type type_str)

 # Parse Java's pseudo-UTF-8 strings. (spec 4.4.7)
 root.bytes2str = (bytes, null_terminate=false) ->


Track personalisation settings changes to analytics by every click

--- src/views/personalisation.coffee
+++ src/views/personalisation.coffee
@@ -47,9 +47,7 @@
                 @setActivations()
                 @renderIconsForSelectedModes()
             @listenTo p13n, 'user:open', -> @personalisationButtonClick()
-            @_triggerProfileAnalytics = _.debounce () =>
-                @_sendCurrentProfile()
-            , 3000
+
         serializeData: ->
             lang: p13n.getLanguage()

@@ -113,17 +111,21 @@
                     $li.removeClass 'selected'
                 $button.attr 'aria-pressed', activated

-        _sendCurrentProfile: ->
-            _.each p13n.getCities(), (city) ->
-                Analytics.trackCommand 'setProfileCity', [city, 1]
+        _sendProfileClickToAnalytics:(group, type) ->
+            category = null
+            name = type
+            value = 0
+            if group == 'city'
+                category = "setProfileCity"
+                value += type in p13n.getCities()
+            else if group == 'senses'
+                category = "setProfileSenses"
+                value += p13n.getAccessibilityMode(type)
+            else if group == 'mobility'
+                category = "setProfileMobility"
+                value += type == p13n.getAccessibilityMode(group)

-            accessibility = p13n.getAccessibilityModes()
-            _.each _.keys(accessibility), (key) ->
-                if key == 'mobility'
-                    Analytics.trackCommand 'setProfileMobility', [accessibility[key], 1]
-                else
-                    if !!accessibility[key]
-                        Analytics.trackCommand 'setProfileSenses', [key, 1]
+            Analytics.trackCommand category, [name, value]

         switchPersonalisation: (ev) =>
             ev.preventDefault()
@@ -151,7 +153,7 @@
                 p13n.toggleCity type
             else if group == 'language'
                 window.location.href = getLangURL type
-            @_triggerProfileAnalytics()
+            @_sendProfileClickToAnalytics(group, type)

         onDomRefresh: ->
             @renderIconsForSelectedModes()


options.url consistency

options.url consistency

--- src/kite/kite.coffee
+++ src/kite/kite.coffee
@@ -11,7 +11,7 @@
   # ready states:
   [ NOTREADY, READY, CLOSED ] = [0,1,3]

-  uniqueId = "#{ Math.random() }"
+  uniqueId = "#{ Math.random() }"

   constructor: (options) ->
     return new Kite options  unless this instanceof Kite
@@ -35,19 +35,19 @@

   # connection state:
   connect: ->
-    addr = @options.url
-    @ws = new WebSocket addr
+    { url } = @options
+    @ws = new WebSocket url
     @ws.onopen    = @bound 'onOpen'
     @ws.onclose   = @bound 'onClose'
     @ws.onmessage = @bound 'onMessage'
     @ws.onerror   = @bound 'onError'
-    @emit 'info', "Trying to connect to #{ addr }"
+    @emit 'info', "Trying to connect to #{ url }"
     return this

   disconnect: (reconnect = true) ->
     @autoReconnect = !!reconnect  if reconnect?
     @ws.close()
-    @emit 'info', "Disconnecting from #{ addr }"
+    @emit 'info', "Disconnecting from #{ @options.url }"
     return this

   # event handlers:


Don't open up chosen on option keyup.

Don't open up chosen on option keyup.

When embedding chosen in a chrome extension, the keyboard shortcut
includes the option key. If a chosen instance happens to be focused at the
time, releasing the option key opens it, but shouldn't.

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -245,7 +245,7 @@
       when 27
         this.results_hide() if @results_showing
         return true
-      when 9, 38, 40, 16, 91, 17
+      when 9, 38, 40, 16, 91, 17, 18
         # don't do anything on these keys
       else this.results_search()


Still need to set canonical name initially.

fixed #434

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -7424,6 +7424,7 @@

     for ship_name, ship_data of basic_cards.ships
         ship_data.english_name ?= ship_name
+        ship_data.canonical_name ?= ship_data.english_name.canonicalize()

     # Set sources from manifest
     for expansion, cards of exportObj.manifestByExpansion


Added slot replacement

Added slot replacement

--- src/element.coffee
+++ src/element.coffee
@@ -58,6 +58,16 @@
     else
       root = document.createElement('_shadow_')
       root.appendChild(node) while node = nodes[0]
+
+      # slot replacement algorithm
+      for node in root.querySelectorAll('slot')
+        nodes = @childNodes
+        nodes = @querySelectorAll("[slot='#{selector}']") if selector = node.getAttribute('name')
+        nodes = Array::slice.call(nodes)
+        if nodes.length
+          node.removeChild(child) while child = node.firstChild
+          node.appendChild(child) while child = nodes?.shift()
+      @removeChild(child) while child = @firstChild
       @appendChild(root)
     return


Options can be passed to change state.

Options can be passed to change state.

These get passed to transitions and also state functions, alongside any triggered events.

--- src/view.coffee
+++ src/view.coffee
@@ -123,26 +123,35 @@
         Then changes the state, calling the state action method if it exists
         Fires the changestate event
         ###
-        changeState: (state) ->
+        changeState: (state, options) ->
             # Transition
             tran = @calcTransition(@state, state)

             if tran and _.isFunction(@[tran.func])
-                success = @[tran.func]()
+                success = @[tran.func](@state, state, options)
             else
                 success = true

             if sucess is false then return false
-            @root.trigger(@eventPrefix + 'transition', from: @state, to: state)
+            @root.trigger(@eventPrefix + 'transition', {
+                from: @state
+                to: state
+                options: options
+            })

             # Change state
             @priorState = @state
             @state = state
-            if _.isFunction(@[@states[state]]) then @[@states[state]]()
-            @root.trigger(@eventPrefix + 'changestate', state: @state)
+            if _.isFunction(@[@states[state]]) then @[@states[state]](options)
+            @root.trigger(@eventPrefix + 'changestate', {
+                state: @state
+                options: options
+            })

             @undelegateEvents()
             @delegateEvents(@calcEvents(state))
+
+            return true

         become: (state) -> @changeState(state)


removeNode messing up groups, fixes #135

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -126,7 +126,7 @@

     for group in @groups
       continue unless group
-      index = group.nodes.indexOf(id) is -1
+      index = group.nodes.indexOf(id)
       continue if index is -1
       group.nodes.splice index, 1


Remove accidental change to pod action trigger success callback in pod controller

--- static/coffee/controllers.coffee
+++ static/coffee/controllers.coffee
@@ -869,7 +869,6 @@
     $scope.$emit('podActionFailure', payload)

   onTriggerSuccess = () ->
-    $scope.$emit('podActionSuccess')
     $scope.update()

   updateAction = (type, props) ->


Unused click handler removed

Unused click handler removed

--- static/script/pages/admin.coffee
+++ static/script/pages/admin.coffee
@@ -225,7 +225,3 @@
   window.settlers.setup_forms()

   window.settlers.refresh_maintenance_access_list()
-
-  $('#test_error').click () ->
-    window.hlib.WORKING.show()
-    false


Move implementation under the cleanup option

--- src/xslt.coffee
+++ src/xslt.coffee
@@ -82,18 +82,14 @@
         throw new Error("Failed to load document from string:\r\n#{d.documentElement.textContent}")
     return d

-  docToStr = (doc, opt) ->
+  docToStr = (doc) ->
     return null unless doc?
-    xml = if typeof doc == 'string'
+    xml = if (typeof doc) == 'string'
       doc
     else
       doc?.xml || new XMLSerializer?()?.serializeToString?(doc)
     if xml?.indexOf?("<transformiix::result") >= 0
       xml = xml.substring(xml.indexOf(">") + 1, xml.lastIndexOf("<"))
-    if opt.xmlHeaderInOutput
-      xml = prependHeader(xml) if needsHeader(xml)
-    else
-      xml = stripHeader(xml)
     return xml

   arrayContains = (arr, val) ->
@@ -185,8 +181,12 @@
       xslProc.transform()
       trans = xslProc.output

-    outStr = docToStr(trans, opt)
+    outStr = docToStr(trans)
     if opt.cleanup
+      outStr = if opt.xmlHeaderInOutput and needsHeader(outStr)
+        prependHeader(outStr)
+      else
+        stripHeader(outStr)
       outStr = cleanupXmlNodes(outStr, opt)
       outStr = stripRedundantNamespaces(outStr) if opt.removeDupNamespace
     return outStr


fix: an unhandled error

fix: an unhandled error

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -590,7 +590,12 @@
 				return

 			if isFunction xthen
-				x._pre = p if x instanceof Yaku
+				# On this point a new node is inserted in to the tree.
+				# To full understand this, you have to read the `docs/lazyTree.md`.
+				if x instanceof Yaku
+					x._pre = p._pre
+					p._pre = x
+
 				settleXthen p, x, xthen
 			else
 				# 2.3.3.4


+ LRUQueue.push method as add method alias.

--- src/lru-queue.coffee
+++ src/lru-queue.coffee
@@ -17,6 +17,7 @@
     ++@_mru
     result = @pop() if @length > @maxCapacity
     result
+  push: @::add
   pop: ()->
     @shiftLU()#@_lru++ while @_lru < @_mru and not @queue[@_lru]
     result = @queue[@_lru]


Time zone example

Time zone example

--- src/grafana.coffee
+++ src/grafana.coffee
@@ -11,6 +11,7 @@
 #   - `hubot graf db graphite-carbon-metrics now-12hr` - Get a dashboard with a window of 12 hours ago to now
 #   - `hubot graf db graphite-carbon-metrics now-24hr now-12hr` - Get a dashboard with a window of 24 hours ago to 12 hours ago
 #   - `hubot graf db graphite-carbon-metrics:3 now-8d now-1d` - Get only the third panel of a particular dashboard with a window of 8 days ago to yesterday
+#   - `hubot graf db graphite-carbon-metrics:3 tz=Europe/Amsterdam` - Get only the third panel of a particular dashboard and render in the time zone Europe/Amsterdam
 #
 # Configuration:
 #   HUBOT_GRAFANA_HOST - Host for your Grafana 2.0 install, e.g. 'http://play.grafana.org'
@@ -42,7 +43,7 @@
 #
 # Commands:
 #   hubot graf set `[host|api_key]` <value> - Setup Grafana host or API key
-#   hubot graf db <dashboard slug>[:<panel id>][ <template variables>][ <from clause>][ <to clause>][ <tz clause>] - Show grafana dashboard graphs
+#   hubot graf db <dashboard slug>[:<panel id>][ <template variables>][ <from clause>][ <to clause>] - Show grafana dashboard graphs
 #   hubot graf list <tag> - Lists all dashboards available (optional: <tag>)
 #   hubot graf search <keyword> - Search available dashboards by <keyword>
 #   hubot graf alerts[ <state>] - Show all alerts (optional: <state>)


:bug: Fix wrong scopes applied to lines following a very long line.

Make sure the rule stack is copied, so that modifying it does not modify the
original. This was causing the stack to be 'restored' to the wrong state when
the max tokens per line limit was reached and there were no initial rules.

See https://github.com/atom/atom/issues/1667#issuecomment-69776121

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -107,7 +107,7 @@
       ruleStack = ruleStack.slice()
     else
       ruleStack = [@getInitialRule()]
-    originalRuleStack = ruleStack
+    originalRuleStack = ruleStack.slice()

     tokens = []
     position = 0


multianewarray optimization

multianewarray optimization

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -4,6 +4,7 @@
 types = require './types'
 {java_throw,BranchException,ReturnException,JavaException} = require './exceptions'
 {c2t} = types
+{JavaArray} = require './java_object'

 root = exports ? window.opcodes = {}

@@ -223,14 +224,18 @@

   execute: (rs) ->
     counts = rs.curr_frame().stack.splice(-@dim,@dim)
-    def = util.initial_value @class[@dim..]
+    default_val = util.initial_value @class[@dim..]
+    types = (c2t(@class[d..]) for d in [0...@dim] by 1)
     init_arr = (curr_dim) =>
-      return def if curr_dim == @dim
       len = counts[curr_dim]
       if len < 0 then java_throw(rs, 'java/lang/NegativeArraySizeException',
         "Tried to init dimension #{curr_dim} of a #{@dim} dimensional #{@class.toString()} array with length #{len}")
-      typestr = @class[curr_dim..]
-      rs.init_object typestr, (init_arr(curr_dim+1) for [0...len])
+      type = types[curr_dim]
+      if curr_dim+1 == @dim
+        array = (default_val for i in [0...len] by 1)
+      else
+        array = (init_arr(curr_dim+1) for i in [0...len] by 1)
+      new JavaArray type, rs, array
     rs.push init_arr 0

 class root.ArrayLoadOpcode extends root.Opcode


fix imul, fmul

fix imul, fmul

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -232,9 +232,9 @@
   101: new root.Opcode 'lsub', { execute: (rs) -> rs.push(-rs.pop2()+rs.pop2(), null) }
   102: new root.Opcode 'fsub', { execute: (rs) -> rs.push(-rs.pop()+rs.pop()) }
   103: new root.Opcode 'dsub', { execute: (rs) -> rs.push(-rs.pop2()+rs.pop2(), null) }
-  104: new root.Opcode 'imul', { execute: (rs) -> rs.push(rs.pop2()*rs.pop2()) }
+  104: new root.Opcode 'imul', { execute: (rs) -> rs.push(rs.pop()*rs.pop()) }
   105: new root.Opcode 'lmul', { execute: (rs) -> rs.push(rs.pop2()*rs.pop2(), null) }
-  106: new root.Opcode 'fmul', { execute: (rs) -> rs.push(rs.pop2()*rs.pop2()) }
+  106: new root.Opcode 'fmul', { execute: (rs) -> rs.push(rs.pop()*rs.pop()) }
   107: new root.Opcode 'dmul', { execute: (rs) -> rs.push(rs.pop2()*rs.pop2(), null) }
   108: new root.Opcode 'idiv'
   109: new root.Opcode 'ldiv'
@@ -301,12 +301,12 @@
   169: new root.Opcode 'ret', { byte_count: 1 }
   170: new root.Opcode 'tableswitch'
   171: new root.Opcode 'lookupswitch'
-  172: new root.Opcode 'ireturn'
-  173: new root.Opcode 'lreturn'
-  174: new root.Opcode 'freturn'
-  175: new root.Opcode 'dreturn'
-  176: new root.Opcode 'areturn'
-  177: new root.Opcode 'return'
+  172: new root.Opcode 'ireturn', { execute: (rs) -> }  # explicitly make these NOPs
+  173: new root.Opcode 'lreturn', { execute: (rs) -> }
+  174: new root.Opcode 'freturn', { execute: (rs) -> }
+  175: new root.Opcode 'dreturn', { execute: (rs) -> }
+  176: new root.Opcode 'areturn', { execute: (rs) -> }
+  177: new root.Opcode 'return', { execute: (rs) -> }
   178: new root.FieldOpcode 'getstatic'
   179: new root.FieldOpcode 'putstatic'
   180: new root.FieldOpcode 'getfield'