AutoComplete: log when no data found

AutoComplete: log when no data found

--- src/components/autocomplete/autocompletecontroller.coffee
+++ src/components/autocomplete/autocompletecontroller.coffee
@@ -356,6 +356,7 @@
         @refreshDropDown data
         @showDropdown()
       else
+        log 'no data found'
         @showNoDataFound()

   keyUpOnInputView:(event)->


Little bigger text and vertically center

--- framer/Extras/ErrorDisplay.coffee
+++ framer/Extras/ErrorDisplay.coffee
@@ -16,7 +16,7 @@
 else
 	Config.height = 40
 	Config.textInset = 12
-	Config.fontSize = 13
+	Config.fontSize = 14

 class ErrorDisplay extends BaseClass
@@ -57,7 +57,7 @@

 			error.text.style =
 				font: "#{Config.fontSize}px/1em #{Utils.deviceFont()}"
-				lineHeight: "#{error.text.height}px"
+				lineHeight: "#{parseInt(error.text.height - 2)}px"
 				textAlign: "center"
 				wordWrap: "break-word"
 				textOverflow: "ellipsis"


Use -p to create intermediate directories if needed.

So if user doesn't have Applications folder "mkdir -p ~/Applications/APP_NAME" will also creates Applications folder.

--- client/landing/app/MainApp/fs/fsitem.coffee
+++ client/landing/app/MainApp/fs/fsitem.coffee
@@ -19,7 +19,7 @@
       else
         KD.getSingleton('kiteController').run
           withArgs  :
-            command : "#{if type is 'file' then 'touch' else 'mkdir'} #{escapeFilePath response}"
+            command : "#{if type is 'file' then 'touch' else 'mkdir -p'} #{escapeFilePath response}"
         , (err, res)->
           if err then warn err
           else


Sending context to runner function

Sending context to runner function

--- src/processor.coffee
+++ src/processor.coffee
@@ -16,7 +16,7 @@
         context.done()
         return

-      Promise.method(@runner) notification
+      Promise.method(@runner) notification, context
       .tap => @emit "successful", { context, notification }
       .thenReturn()
       .tapCatch (err) => @emit "unsuccessful", { context, notification, err }


:art Simplify calculating selected scrollTop value

--- src/notational/adjust-scroll-top-for-selected-item.coffee
+++ src/notational/adjust-scroll-top-for-selected-item.coffee
@@ -1,10 +1,7 @@
 module.exports = (currentScrollTop, selectedItem, items, rowHeight, bodyHeight) ->
   return currentScrollTop unless selectedItem

-  for item, idx in items
-    if item is selectedItem
-      selectedScrollTop = idx * rowHeight
-      break
+  selectedScrollTop = items.indexOf(selectedItem) * rowHeight

   if currentScrollTop > selectedScrollTop
     # selected item is located before the visible bounds


Only 5 seconds.

Only 5 seconds.

--- packages/grow-app/devices/display.coffee
+++ packages/grow-app/devices/display.coffee
@@ -47,13 +47,13 @@
   onAcid: (event) ->
     event.preventDefault()

-    Meteor.call 'Device.sendCommand', @currentDeviceUuid(), 'acid', 30, (error) ->
+    Meteor.call 'Device.sendCommand', @currentDeviceUuid(), 'acid', 5, (error) ->
       console.log "Error", error if error

   onBase: (event) ->
     event.preventDefault()

-    Meteor.call 'Device.sendCommand', @currentDeviceUuid(), 'base', 30, (error) ->
+    Meteor.call 'Device.sendCommand', @currentDeviceUuid(), 'base', 5, (error) ->
       console.log "Error", error if error

   device: ->


tests that columns + gutters will be less than container width

--- components/artwork_columns/test/view.coffee
+++ components/artwork_columns/test/view.coffee
@@ -54,6 +54,14 @@
       @view.length().should.equal 4
       @view.appendArtworks _.times 4, (-> new Artwork fabricate 'artwork')
       @view.length().should.equal 8
+
+  describe '#_columnWidth',  ->
+
+    it 'total width of columns should be less than container width', ->
+      @view.$el.width 1000
+
+      totalWidth = (@view._columnWidth() * @view.numberOfColumns) + ((@view.numberOfColumns - 1) * @view.gutterWidth)
+      totalWidth.should.be.below @view.$el.width()

   describe '#setUserSavedArtworks', ->


Cleanup: minor

Cleanup: minor

--- client/Social/Topics/AppController.coffee
+++ client/Social/Topics/AppController.coffee
@@ -133,7 +133,7 @@
       width                       : 500
       overlay                     : yes
       tabs                        :
-        navigable              : yes
+        navigable                 : yes
         goToNextFormOnSubmit      : no
         forms                     :
           update                  :


Improve message list scrolling, refs #427

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -241,7 +241,7 @@
         return should

     render: ->
-        ul className: 'list-unstyled',
+        ul className: 'list-unstyled', ref: 'messageList',
             @props.messages.map((message, key) =>
                 id = message.get('id')
                 cid = message.get('conversationID')
@@ -276,9 +276,15 @@
         # If selected message has changed, scroll the list to put
         # current message into view
         if @state.messageID isnt @props.messageID
+            scrollable = @refs.messageList?.getDOMNode()?.parentNode
             active = document.querySelector("[data-message-id='#{@props.messageID}']")
             if active? and not DomUtils.isVisible(active)
+                scroll = scrollable?.scrollTop
                 active.scrollIntoView(false)
+                # display half of next message
+                if scroll isnt @refs.scrollable?.getDOMNode()?.scrollTop
+                    scrollable?.scrollTop += active.getBoundingClientRect().height / 2
+
             @setState messageID: @props.messageID


Fix conversations deletion, refs #413

Fix conversations deletion, refs #413

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -271,6 +271,7 @@
                                 ref: 'listToolboxActions'
                                 mailboxes: @props.mailboxes
                                 onMark: @onMark
+                                onConversationDelete: @onConversationDelete
                                 onConversationMark: @onConversationMark
                                 onConversationMove: @onConversationMove
                                 displayConversations: @props.displayConversations
@@ -386,7 +387,7 @@
             return {count, conversationIDs, applyToConversation}

     onConversationDelete: ->
-        onConversationDelete true
+        @onDelete true

     onDelete: (applyToConversation) ->
         return unless options = @_getSelectedAndMode(applyToConversation)


set className to include formGroupClassNames()

--- client/lanes/components/shared/Input.cjsx
+++ client/lanes/components/shared/Input.cjsx
@@ -12,7 +12,8 @@

     renderStyled: (props, handlers, label) ->
         colProps = _.omit(@props, 'name', 'label', 'type', 'editing', 'display')
-        colClassName = _.classnames("align-#{@props.align}": @props.align)
+
+        colClassName = _.classnames("align-#{@props.align}": @props.align, @formGroupClassNames(), 'field')
         <BS.Col {...colProps} className={colClassName}>
             <BS.Input {...props} {...handlers} />
         </BS.Col>


Expand sandbox tests to cover nested Aether calls and generators

--- test/global_scope_spec.coffee
+++ test/global_scope_spec.coffee
@@ -27,3 +27,44 @@
     aether.transpile(code)
     aether.run()
     expect((->).__proto__.constructor).not.toEqual 100
+
+  it 'should sandbox nested aether functions', ->
+    c1 = "arguments[0]();"
+    c2 = "(function(){}).__proto__.constructor('');"
+
+    aether = new Aether()
+    aether.transpile c1
+    f1 = aether.createFunction()
+
+    aether.transpile c2
+    f2 = aether.createFunction()
+
+    expect(->f1 f2).toThrow()
+
+  it 'shouldn\'t remove sandbox in nested aether functions', ->
+    c1 = "arguments[0]();(function(){}).__proto__.constructor('');"
+    c2 = ""
+
+    aether = new Aether()
+    aether.transpile c1
+    f1 = aether.createFunction()
+
+    aether.transpile c2
+    f2 = aether.createFunction()
+
+    expect(->f1 f2).toThrow()
+
+  it 'should sandbox generators', ->
+    code = "(function(){}).__proto__.constructor();"
+    aether = new Aether
+      yieldAutomatically: true
+
+    aether.transpile code
+    func = aether.sandboxGenerator aether.createFunction()()
+
+    try
+      while true
+        func.next()
+    catch e
+      # If we change the error message or whatever make sure we change it here too
+      expect(e.message).toEqual '[Sandbox] Function::constructor is disabled. If you are a developer, please make sure you have a reference to your builtins.'


Remove tests for depends: [...] syntax

Remove tests for depends: [...] syntax

--- coffeespec/property-accessors-spec.coffee
+++ coffeespec/property-accessors-spec.coffee
@@ -107,27 +107,6 @@
       expect(calls).toBe(3)
       expect(events).toBe(2)

-    it 'correctly works with dependencies', ->
-      class Person extends Base
-        @property 'firstName'
-        @property 'lastName'
-        @property 'fullName', depends: ['firstName', 'lastName'], -> @_fullName = "#{@firstName} #{@lastName}"
-      p = new Person()
-      n = 0
-      p.on 'change:fullName', -> ++n
-
-      p.fullName
-      expect(n).toBe(1)
-      expect(p.fullName).toBe('undefined undefined')
-
-      p.firstName = 'Yaroslav'
-      expect(n).toBe(2)
-      expect(p.fullName).toBe('Yaroslav undefined')
-
-      p.lastName = 'Volkov'
-      expect(n).toBe(3)
-      expect(p.fullName).toBe('Yaroslav Volkov')
-
     it 'returns actual value if value changes during get (memo: true)', ->
       class Person extends Base
         @property 'name', memo: true, -> 'Yaroslav'
@@ -171,30 +150,6 @@
         expect(n).toBe(3)
         c.count
         expect(n).toBe(3)
-
-      it 'correctly works when there are dependencies', ->
-        class Person extends Base
-          @property 'firstName'
-          @property 'lastName'
-          @property 'fullName', depends: ['firstName', 'lastName'], memo: yes, ->
-            "#{@firstName} #{@lastName}"
-        x = 0
-        y = 0
-        z = 0
-        p = new Person()
-        p.bind
-          'change:firstName': -> ++x
-          'change:lastName':  -> ++y
-          'change:fullName':  -> ++z
-        p.firstName = 'Yaroslav'
-        expect(x).toBe(1)
-        expect(y).toBe(0)
-        expect(z).toBe(1)
-        p.lastName = 'Volkov'
-        expect(p.fullName).toBe('Yaroslav Volkov')
-        expect(x).toBe(1)
-        expect(y).toBe(1)
-        expect(z).toBe(2)

   describe 'both readonly and computed property', ->
     it 'correctly gets, sets value and emits events', ->
@@ -246,4 +201,4 @@
     .not.toThrow()

     foo = new out.Foo()
-    expect(-> foo.bar = 'value').toThrow()+    expect(-> foo.bar = 'value').toThrow()


try a smaller (though less accurate) expanded route view

--- web/js/views/route_graph_view.coffee
+++ web/js/views/route_graph_view.coffee
@@ -1,6 +1,6 @@
 define ['underscore', 'backbone', 'utils', 'handlebars', 'hbs!template/route_graph'], (_, Backbone, Utils, Handlebars, template) ->

-  EXPANDED_HEIGHT = 230
+  EXPANDED_HEIGHT = 150
   MINIMUM_LEG_HEIGHT = 12

   class RouteGraphView extends Backbone.View


Add regression tests for map translation issue #212

--- test/json/builder.spec.coffee
+++ test/json/builder.spec.coffee
@@ -50,6 +50,17 @@
       params = { Items: { A: 'a', B: 'b' } }
       expect(build(rules, params)).toEqual('{"Items":{"A":"a","B":"b"}}')

+    it 'translates nested maps', ->
+      rules =
+        type: 'structure'
+        members:
+          Items: type: 'map', members: type: 'integer'
+      now = new Date()
+      now.setMilliseconds(100)
+      params = Items: MyKey: "5", MyOtherKey: "10"
+      str = '{"Items":{"MyKey":5,"MyOtherKey":10}}'
+      expect(build(rules, params)).toEqual(str)
+
     it 'traslates nested timestamps', ->
       rules =
         type: 'structure'


primeFactors

primeFactors

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -5,7 +5,7 @@
   equals (4.2).abs(), 4.2, "(4.2).abs() equals 4.2"
   equals (-1.2).abs(), 1.2, "(-1.2).abs() equals 1.2"
   equals (0).abs(), 0, "(0).abs() equals 0"
-
+
 test "#ceil", ->
   equals (4.9).ceil(), 5, "(4.9).floor() equals 5"
   equals (4.2).ceil(), 5, "(4.2).ceil() equals 5"
@@ -34,16 +34,44 @@
 test "#times called correct amount", ->
   n = 5
   count = 0
-
+
   n.times -> count++
-
+
   equals n, count, "returns n"

-
+
 test "#mod should have a positive result when used with a positive base and a negative number", ->
   n = -3

   equals n.mod(8), 5, "Should 'wrap' and be positive."

+test "#primeFactors", ->
+
+  factors = 15.primeFactors()
+
+  equals factors.length, 2
+  equals factors[0], 3
+  equals factors[1], 5
+  equals factors.product(), 15
+
+  factors = 256.primeFactors()
+
+  equals factors.product(), 256
+  equals factors.length, 8
+  equals factors.first(), 2
+  equals factors.last(), 2
+
+  factors = 997.primeFactors()
+
+  equals factors.length, 1
+  equals factors.first(), 997
+  equals factors.product(), 997
+
+  equals 0.primeFactors(), undefined
+
+  factors = (-3).primeFactors()
+  equals factors.first(), -1
+  equals factors.product(), -3
+
 module undefined


Fix -> price import - test name.

Fix -> price import - test name.

--- test/price-import.spec.coffee
+++ test/price-import.spec.coffee
@@ -407,7 +407,7 @@
       .catch (err) -> done(err)
       .finally -> done()

-    it 'should add publish action to update actions when product not published
+    it 'should not add publish action to update actions when product not published
        and has staged changes', (done) ->
       existingProduct =
         version: 1


SocialWorker: SocialChannel: remove cyclechannel method

--- workers/social/lib/social/models/socialapi/channel.coffee
+++ workers/social/lib/social/models/socialapi/channel.coffee
@@ -65,8 +65,6 @@
         updateLastSeenTime   :
           (signature Object, Function)
         glancePinnedPost     :
-          (signature Object, Function)
-        cycleChannel:
           (signature Object, Function)
         delete:
           (signature Object, Function)
@@ -118,25 +116,6 @@
       else callback null, "socialapi.channelsecret.#{secretName}",
         if oldSecretName then "socialapi.channelsecret.#{oldSecretName}"

-  @cycleChannel = do ->
-    cycleChannel = (options, callback = -> ) ->
-      JName = require '../name'
-      name = @generateChannelName options
-      JName.cycleSecretName name, (err, oldSecretName, newSecretName) =>
-        return callback err if err
-        routingKey = "socialapi.channelsecret.#{oldSecretName}.cycleChannel"
-        @emit 'broadcast', routingKey, null
-        return callback null
-    return throttle cycleChannel, 5000
-
-  cycleChannel:(callback) ->
-    options =
-      groupSlug     : @groupName
-      apiChannelType: @typeConstant
-      apiChannelName: @name
-
-    @constructor.cycleChannel options, callback
-
   @checkChannelParticipation = secureRequest
     fnName   : 'checkChannelParticipation'
     validate : ['name', 'type']


course: delete the todo list from source file

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -19,13 +19,13 @@
 #
 ###############################################################################

-# Course Management
-
 ###
-TODO:
-
-
-NEXT VERSION (after a release):
+Course Management
+
+AUTHORS:
+   - first version written by William Stein, July 13-25, 2015 while completely unemployed.
+
+IDEAS FOR NEXT VERSION (after a release):
 - [ ] (1:00?) ui -- maybe do a max-height on listing of student assignments or somewhere and overflow auto
 - [ ] (1:00?) provide a way to enable/disable tooltips on a per-application basis
 - [ ] (1:30?) #speed cache stuff/optimize for speed
@@ -35,85 +35,6 @@
 - [ ] (2:00?) #unclear automatically collect assignments on due date (?)
 - [ ] (5:00?) #unclear realtime chat for courses...
 - [ ] (8:00?) #unclear way to show other viewers that a field is being actively edited by a user (no idea how to do this in react)
-
-DONE:
-- [x] (0:45?) (0:20) re-assign/re-copy/re-collect need to have ... and confirm, since they are dangerous.
-- [x] (0:45?) (0:55) while doing any of three steps of workflow, set something in database and store, which locks things (with a minute limit and spinner) to prevent double click.
-- [x] (0:45?) (2:30) make confirm copies have nice text and look nice
-- [x] (0:45?) (0:37) xs mobile assignment looks bad -- need a fullscreen toggle thing.
-- [x] (1:00?) (1:17) make it so creating a project does not open it until user explicitly opens it.
-- [x] (1:30?) #now make the assign/collect/return all buttons have a confirmation and an option to only collect from students not already collected from already; this will clarify what happens on re-assign, etc.
-- [x] (1:00?) (1:21) typing times into the date picker doesn't work -- probably needs config -- see http://jquense.github.io/react-widgets/docs/#/datetime-picker
-- [x] (1:00?) (2:41) BUG: race: when changing all titles/descriptions, some don't get changed.  I think this is because
-      set of many titles/descriptions on table doesn't work.  Fix should be to only do the messages to the
-      backend doing the actual sync at most once per second (?).  Otherwise we send a flury of conflicting
-      sync messages.   Or at least wait for a response (?).
-- [x] (1:30?) (0:54) just create the student project when adding student -- FIXES: adding a non-collaborator student to a course makes it impossible to get their name -- see compute_student_list.  This is also a problem for project collaborators that haven't been added to all student projects.
-- [x] (1:00?) (0:40?) whenever owner opens the course file, update the collaborators/titles/descriptions for all projects.
-- [x] (1:30?) (0:30) BUG: search feels slow with 200 students; showing students for assignment feels slow.; also add grey alternating lines
-- [x] (1:00?) (0:30) bug fix -- "(student used project...") time doesn't update, probably due to how computed and lack of dependency on projects store.
-- [x] (1:00?) (1:07) save status info (so know if not saving due to network, etc.)
-- [x] (1:00?) (5:36) fix bugs in opening directories in different projects using actions -- completely busted right now due to refactor of directory listing stuff....
-- [x] (1:00?) (4:00) #now ensuring opening and closing a course doesn't leak memory
-- [x] (0:30?) (0:01) #now set_project_error/set_student_error -- implement or remove (x)
-- [x] (3:38) ensure actions don't return anything; clarify flux.
-    Problems:
-       - create_student_project returns project_id.
-       - copy_assignment_from_student, etc. returns error (?)
-    Creating student project:
-      - need to start the process and move it forward by watching for change events on stores rather than using callbacks.
-    When creating new projects need to wait until they are in the store before configuring them.
-- [x] (0:30?) bug -- border bottom vanishes upon toggle/untoggle of students or assignments
-- [x] (0:45?) make Help component page center better
-- [x] (1:00?) (4:07) add tooltips/help popups
-- [x] (0:45?) (1:13) ui button colors -- make the next button you should click related to workflow be blue.
-- [x] (0:45?) (0:42) error messages in assignment page -- make hidable and truncate-able (ability to clear ErrorDisplay's)
-- [x] (0:20?) (0:23) truncate long assignment titles in student displays
-- [x] (1:00?) (1:08) overall realtime status messages shouldn't move screen down; and should get maybe saved for session with scrollback
-- [x] (0:30?) (0:52) nicer space, etc., around "show/hide deleted [assignment|students] buttons"
-- [x] (1:30?) (0:41) #now date picker for assignment due date
-- [x] (1:30?) (1:57) make quick simple textarea component that renders using markdown and submits using shift+enter...
-- [x] (0:45?) (0:30) triangles for show/hide assignment info like for students, and make student triangle bigger.
-- [x] (2:00?) (2:46) make student-assignment info row look not totally horrible
-- [x] (0:30?) (0:31) escape to clear search boxes
-- [x] (0:15?) (0:05) uniformly sort assignments everywhere
-- [x] (1:30?) (0:45) add student/assignment note fields
-    - let enter/edit it in the students page
-- [x] (1:30?) (0:23) add due date as a field to assignments:
-    - way to edit it (date selector...?)
-    - use it to sort assignments
-- [x] (1:00?) (1:05) export all grades... to csv, excel file, python file, etc.?
-- [x] (0:30?) (0:03) course title should derive from filename first time.
-- [x] (1:00?) (0:55) grade: place to record the grade, display grade, etc.
-- [x] (1:30?) (0:19) show the last time a student opened their project...
-- [x] (1:00?) (1:25) help page -- integrate info
-- [x] (0:45?) (0:04) delete old course code
-- [x] (1:00?) (1:49) clean up after flux/react when closing the editor; clean up surrounding element
-- [x] (0:30?) (0:10) delete confirms
-- [x] (1:00?) (0:30) changing title/description needs to change it for all projects
-- [x] (0:45?) (0:07) delete assignment; show deleted assignments
-- [x] (0:45?) (0:37) delete student; show deleted students
-- [x] (0:30?) (0:18) when searching, show how many things are not being shown.
-- [x] (0:30?) (0:15) when adding assignments filter out folders contained in existing assignment folders
-- [x] (1:00?) (0:12) assignment collect; don't allow until after assigned, etc. -- FLOW
-- [x] (0:30?) (0:09) create function to render course in a DOM element with basic rendering; hook into editor.coffee
-- [x] (0:30?) (0:36) create proper 4-tab pages using http://react-bootstrap.github.io/components.html#tabs
-- [x] (0:45?) (1:35) create dynamically created store attached to a project_id and course filename, which updates on sync of file.
-- [x] (0:30?) (1:15) fill in very rough content components (just panels/names)
-- [x] (0:45?) settings: title & description
-- [x] (1:00?) (2:02) add student
-- [x] (1:00?) (0:22) render student row
-- [x] (0:45?) (0:30) search students
-- [x] (0:45?) (2:30+) create student projects
-- [x] (1:00?) nice error displays of error in the store.
-- [x] (1:00?) (1:21) add assignment
-- [x] (1:30?) (0:27) render assignment row; search assignments
-- [x] (1:30?) #now (4:00+) assign all... (etc.) button
-- [x] (1:30?) collect all... (etc.) button
-- [x] (1:00?) return all... button
-- [x] (0:45?) (0:20)  counter for each page heading (num students, num assignments)
-- [x] (2:00?) (0:59) links to grade each student; buttons to assign to one student, collect from one, etc.
-- [x] (1:30?) (1:03) display info about each student when they are clicked on (in students page) -- ugly but nicely refactored

 ###


Write CSS to a new `<style>` tag instead of using `<body>` attribute

So as not to conflict with other packages.

--- lib/fonts-cjk.coffee
+++ lib/fonts-cjk.coffee
@@ -17,11 +17,21 @@
       markdownPreviewFont = atom.config.get("fonts-cjk.markdownPreviewFont")
       workspaceFont = atom.config.get("fonts-cjk.workspaceFont")
       fonts = """
-        --fonts-cjk-editorFont: "#{editorFont}";
-        --fonts-cjk-markdownPreviewFont: "#{markdownPreviewFont}";
-        --fonts-cjk-workspaceFont: "#{workspaceFont}";
+        :root {
+          --fonts-cjk-editorFont: "#{editorFont}";
+          --fonts-cjk-markdownPreviewFont: "#{markdownPreviewFont}";
+          --fonts-cjk-workspaceFont: "#{workspaceFont}";
+        }
       """
-      setBodyAttribute("style", fonts)
+
+      if document.getElementById("fonts-cjk-style")
+        document.getElementById("fonts-cjk-style").textContent = fonts
+      else
+        style = document.createElement("style")
+        style.id = "fonts-cjk-style"
+        style.textContent = fonts
+        document.head.appendChild(style)
+
       setBodyAttribute("data-fonts-cjk-editorFont", editorFont)
       setBodyAttribute("data-fonts-cjk-markdownPreviewFont", markdownPreviewFont)
       setBodyAttribute("data-fonts-cjk-workspaceFont", workspaceFont)
@@ -38,6 +48,8 @@
     body.removeAttribute("data-fonts-cjk-editorFont")
     body.removeAttribute("data-fonts-cjk-markdownPreviewFont")
     body.removeAttribute("data-fonts-cjk-workspaceFont")
-    body.removeAttribute("style")
+
+    style = document.getElementById("fonts-cjk-style")
+    style?.parentNode.removeChild(style)

     @observer?.dispose()


Remove rpm dependency lsb-core-noarch

Remove rpm dependency lsb-core-noarch

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -185,7 +185,10 @@
         ]
       else
         depsList = [
-          'lsb-core-noarch'
+          # 'lsb-core-noarch'
+          # 'libappindicator'
+          # 'libappindicator1'
+          # 'libappindicator-gtk3'
         ]

       debRecommendsList = [


清理debugger

清理debugger

--- src/widget/edit/date-picker.coffee
+++ src/widget/edit/date-picker.coffee
@@ -404,14 +404,14 @@
 			doPost()
 			return
 		).on("focus", ()=>
-			debugger
+
 			@_inputFocused = true
 			@_refreshInputValue(@_value)
 			@addClass("focused") if not @_finalReadOnly
 			@fire("focus", @)
 			return
 		).on("blur", ()=>
-			debugger
+
 			@_inputFocused = false
 			@removeClass("focused")
 			@_refreshInputValue(@_value)


Do not precache all scripts unless data-precache is set

--- vendor/assets/javascripts/modal.js.coffee
+++ vendor/assets/javascripts/modal.js.coffee
@@ -51,7 +51,7 @@
 $ ->

   $('script[data-path]').each ->
-    $(this).modal 'precache'
+    $(this).modal 'precache' if $(this).attr("data-precache")

   $(document.body).on 'click', '*[data-open-modal]', (e) ->
     e.preventDefault()
@@ -143,7 +143,11 @@

     when 'show'
       req = modals[path]
-      if req? and req.readyState isnt 4
+      unless req
+        $("script[data-path='#{path}']").modal('precache')
+        req = modals[path]
+
+      if req? and req.readyState < 4
         $(this).tempText 'One sec...'
         waits[path] = this
         return this


add Rackup and Gemspec support

add Rackup and Gemspec support

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -81,7 +81,7 @@
     singleLineComment: ['#']

   Ruby:
-    nameMatchers:      ['.rb']
+    nameMatchers:      ['.rb', '.ru', '.gemspec']
     pygmentsLexer:     'ruby'
     singleLineComment: ['#']


Use style/margin to margin instead of space

--- views/services/modernization-delta.coffee
+++ views/services/modernization-delta.coffee
@@ -68,14 +68,13 @@
               #   2nd term: Something has been added
               if (remaining > 0 && maxDelta != 0) || delta != 0
                 upIcon = if remaining <= 0 || delta == maxDelta then 'angle-double-up' else 'angle-up'
-
-                    {nameStatuses[i]}
-                  <FontAwesome key={0} name={upIcon} />
-                   {delta}/
+
+                  {nameStatuses[i]}
+                  <FontAwesome key={0} name={upIcon} style={margin: '0 3px'} />
+                  {delta}/

                     {if remaining <= 0 then 'MAX' else "+#{remaining}"}

-

           }


Fix binary data test.

Fix binary data test.

--- test/hooks.coffee
+++ test/hooks.coffee
@@ -236,9 +236,7 @@
             done()

   it 'should correctly send room messages with binary data', (done) ->
-    data = new ArrayBuffer 1
-    view = new DataView data
-    view.setInt8 0, 5
+    data = new Buffer [5]
     message = { data : data }
     roomMessagesChecker = (msg, cb) ->
       nextTick cb
@@ -252,6 +250,7 @@
             expect(room).equal(roomName1)
             expect(msg).include.keys 'data', 'author'
             , 'timestamp', 'id'
+            expect(msg.data).deep.equal(data)
             expect(msg.author).equal(user1)
             expect(msg.timestamp).a('Number')
             expect(msg.id).equal(1)


Cleaned up tests

Cleaned up tests

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- test/items.coffee
+++ test/items.coffee
@@ -1,113 +1,123 @@
 items = require("../build/liblol.js").items

 describe "items", ->
+  find = items.find
+  findOne = items.findOne
+
+  withStats = items.filters.withStats
+  OR = items.filters.or
+  AND = items.filters.and
+  byProperty = items.sorts.byProperty
+
+
+  hextechGunblade = items._list["Hextech Gunblade"]

   describe "find", ->

     it "should return all items when nothing is passed to it", ->
-      items.find().should.include item for item in items._list
+      find().should.include item for item in items._list

     it "should get the item by name if a string is passed", ->
-      items.find("Hextech Gunblade").should.eql [items._list["Hextech Gunblade"]]
+      find("Hextech Gunblade").should.eql [hextechGunblade]

     it "should use the 'where' filter if an object is passed", ->
-      items.find(name: "Hextech Gunblade").should.eql [items._list["Hextech Gunblade"]]
+      find(name: "Hextech Gunblade").should.eql [hextechGunblade]

     it "should throw an error if the filter isn't a string, object, or function", ->
       try
-        items.find 3.14
+        find 3.14
       catch e
         e.message.should.equal "Invalid arguments for find!"

     it "should return a blank array if the filter is never true", ->
-      items.find(-> false).should.eql []
+      find(-> false).should.eql []

     describe "filters", ->

       describe "and", ->

         it "should include no cases if one filter has no results", ->
-          items.find(items.filters.and (-> false), (-> true)).should.eql []
+          find(AND (-> false), (-> true)).should.eql []

         it "should include only cases that fulfill both filters", ->
-          list = items.find(items.filters.and items.filters.withStats(["ap"]), items.filters.withStats(["ad"]))
+          list = find(AND withStats(["ap"]), withStats(["ad"]))

-          list.should.include items._list["Hextech Gunblade"] # ap and ad
-          list.should.not.include items._list["Hextech Revolver"] # ap
-          list.should.not.include items._list["Bilgewater Cutlass"] # ad
+          list.should.include hextechGunblade # ap and ad
+          list.should.not.include findOne("Hextech Revolver") # ap
+          list.should.not.include findOne("Bilgewater Cutlass") # ad

       describe "or", ->

         it "should include all cases if one filter has all results", ->
-          items.find(items.filters.or (-> false), (-> true)).length.should.equal items.find().length
+          find(OR (-> false), (-> true)).length.should.equal find().length

         it "should include cases that fulfill any filters", ->
-          list = items.find(items.filters.or items.filters.withStats(["ap"]), items.filters.withStats(["ad"]))
+          list = find(OR withStats(["ap"]), withStats(["ad"]))

-          list.should.include items._list["Hextech Gunblade"] # ap and ad
-          list.should.include items._list["Hextech Revolver"] # ap
-          list.should.include items._list["Bilgewater Cutlass"] # ad
-          list.should.not.include items._list["Warmog's Armor"] # none
+          list.should.include hextechGunblade # ap and ad
+          list.should.include findOne("Hextech Revolver") # ap
+          list.should.include findOne("Bilgewater Cutlass") # ad
+          list.should.not.include findOne("Warmog's Armor") # none

       describe "not", ->

         it "should include nothing if the filter includes everything", ->
-          items.find(items.filters.not -> true).should.eql []
+          find(items.filters.not -> true).should.eql []

         it "should include nothing with ap if the filter includes everything with ap", ->
-          list = items.find(items.filters.not items.filters.withStats(["ap"]))
+          list = find(items.filters.not withStats(["ap"]))
           item.ap.should.equal 0 for item in list

       describe "where", ->
         where = items.filters.where

         it "should check for equality if the parameter isn't a query object", ->
-          items.find(where name: "Hextech Gunblade").should.eql [items._list["Hextech Gunblade"]]
+          find(where name: "Hextech Gunblade").should.eql [hextechGunblade]

         it "should check for equality with the 'eq' filter", ->
-          items.find(where name: eq: "Hextech Gunblade").should.eql [items._list["Hextech Gunblade"]]
+          find(where name: eq: "Hextech Gunblade").should.eql [hextechGunblade]

         it "should check for >= with the 'gte' filter", ->
-          items.find(where name: "Hextech Gunblade", ap: gte: 65).should.eql [items._list["Hextech Gunblade"]]
-          items.find(where name: "Hextech Gunblade", ap: gte: 66).should.eql []
+          find(where name: "Hextech Gunblade", ap: gte: 65).should.eql [hextechGunblade]
+          find(where name: "Hextech Gunblade", ap: gte: 66).should.eql []

         it "should check for > with the 'gt' filter", ->
-          items.find(where name: "Hextech Gunblade", ap: gt: 64).should.eql [items._list["Hextech Gunblade"]]
-          items.find(where name: "Hextech Gunblade", ap: gt: 65).should.eql []
+          find(where name: "Hextech Gunblade", ap: gt: 64).should.eql [hextechGunblade]
+          find(where name: "Hextech Gunblade", ap: gt: 65).should.eql []

         it "should check for <= with the 'lte' filter", ->
-          items.find(where name: "Hextech Gunblade", ap: lte: 65).should.eql [items._list["Hextech Gunblade"]]
-          items.find(where name: "Hextech Gunblade", ap: lte: 64).should.eql []
+          find(where name: "Hextech Gunblade", ap: lte: 65).should.eql [hextechGunblade]
+          find(where name: "Hextech Gunblade", ap: lte: 64).should.eql []

         it "should check for < with the 'lt' filter", ->
-          items.find(where name: "Hextech Gunblade", ap: lt: 66).should.eql [items._list["Hextech Gunblade"]]
-          items.find(where name: "Hextech Gunblade", ap: lt: 65).should.eql []
+          find(where name: "Hextech Gunblade", ap: lt: 66).should.eql [hextechGunblade]
+          find(where name: "Hextech Gunblade", ap: lt: 65).should.eql []

         it "should work with multiple query filters applied at once", ->
-          items.find(where name: "Hextech Gunblade", ap: lt: 66, gt: 54).should.eql [items._list["Hextech Gunblade"]]
-          items.find(where name: "Hextech Gunblade", ap: lt: 69, gt: 65).should.eql []
+          find(where name: "Hextech Gunblade", ap: lt: 66, gt: 54).should.eql [hextechGunblade]
+          find(where name: "Hextech Gunblade", ap: lt: 69, gt: 65).should.eql []

       describe "withStats", ->

         it "should not include items without the stat", ->
-          items.find(items.filters.withStats(["ap"])).should.not.include items._list["B. F. Sword"]
+          find(withStats(["ap"])).should.not.include findOne("B. F. Sword")

         it "should include items with the stat", ->
-          items.find(items.filters.withStats(["ap"])).should.include items._list["Needlessly Large Rod"]
+          find(withStats(["ap"])).should.include findOne("Needlessly Large Rod")

         it "should include items with multiple stats", ->
-          items.find(items.filters.withStats(["ap", "ad"])).should.include items._list["Hextech Gunblade"]
+          find(withStats(["ap", "ad"])).should.include hextechGunblade

         it "should include items with a stat in a passive", ->
-          items.find(items.filters.withStats(["hp5"])).should.include items._list["Warmog's Armor"]
+          find(withStats(["hp5"])).should.include findOne("Warmog's Armor")

   describe "findOne", ->

     it "should find the first item and return it", ->
-      items.findOne("Hextech Gunblade").should.equal items._list["Hextech Gunblade"]
+      findOne("Hextech Gunblade").should.equal hextechGunblade

     it "should return null if no item was found", ->
-      true.should.equal items.findOne("Needlessly Small Rod") is null
+      true.should.equal findOne("Needlessly Small Rod") is null

   describe "sorts", ->

@@ -115,14 +125,14 @@

       it "should error if the property is not a number", ->
         try
-          items.find().sort items.sorts.byProperty("name")
+          find().sort byProperty("name")
         catch e
           e.message.should.equal "The property must be a number."

       it "should sort ascending by default", ->
-        res = items.find(items.filters.withStats(["ap"])).sort items.sorts.byProperty("ap")
+        res = find(withStats(["ap"])).sort byProperty("ap")
         (res[0].ap < res[res.length - 1].ap).should.be.true

       it "should sort descending if needed", ->
-        res = items.find(items.filters.withStats(["ap"])).sort items.sorts.byProperty("ap", no)
+        res = find(withStats(["ap"])).sort byProperty("ap", no)
         (res[0].ap > res[res.length - 1].ap).should.be.true


:fire: DRY

:fire: DRY

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -353,37 +353,28 @@
     @editor.moveToFirstCharacterOfLine()
     super

+# FIXME need support count
 class InsertAboveWithNewline extends Insert
   @extend()
-  # FIXME need support count
+  direction: 'above'
   execute: ->
     @vimState.setInsertionCheckpoint() unless @typedText?
-    @editor.insertNewlineAbove()
+    switch @direction
+      when 'above' then @editor.insertNewlineAbove()
+      when 'below' then @editor.insertNewlineBelow()
     @editor.getLastCursor().skipLeadingWhitespace()

     if @typedText?
       # We'll have captured the inserted newline, but we want to do that
       # over again by hand, or differing indentations will be wrong.
       @typedText = @typedText.trimLeft()
-      return super
-
-    @vimState.activateInsertMode()
-
-class InsertBelowWithNewline extends Insert
-  @extend()
-  # FIXME need support count
-  execute: ->
-    @vimState.setInsertionCheckpoint() unless @typedText?
-    @editor.insertNewlineBelow()
-    @editor.getLastCursor().skipLeadingWhitespace()
-
-    if @typedText
-      # We'll have captured the inserted newline, but we want to do that
-      # over again by hand, or differing indentations will be wrong.
-      @typedText = @typedText.trimLeft()
-      return super
-
-    @vimState.activateInsertMode()
+      super
+    else
+      @vimState.activateInsertMode()
+
+class InsertBelowWithNewline extends InsertAboveWithNewline
+  @extend()
+  direction: 'below'

 #
 # Delete the following motion and enter insert mode to replace it.


reverted clean up of unused functions

reverted clean up of unused functions

--- test/stubs.coffee
+++ test/stubs.coffee
@@ -51,15 +51,18 @@
           return dm if dm.name is name
       openDM: (user_id, callback) =>
         user = @stubs.client.dataStore.getUserById user_id
-        @stubs.client.dataStore.dms.push {
-          name: user.name,
+        @stubs.client.dataStore.dms.push
+          name: user.name
           id: 'D1234'
-        }
+          sendMessage: (msg) =>
+            @stubs._msg = if @stubs._msg then @stubs._msg + msg else msg
         callback?()
       users: [@stubs.user, @stubs.self]
       dms: [
         name: 'user2'
         id: 'D5432'
+        sendMessage: (msg) =>
+          @stubs._dmmsg = if @stubs._dmmsg then @stubs._dmmsg + msg else msg
       ]
   # Hubot.Robot instance
   @stubs.robot = do ->


Pass view.controller to added subviews.

Pass view.controller to added subviews.

--- src/view/view.coffee
+++ src/view/view.coffee
@@ -44,8 +44,11 @@
     super

   _addSubview: (subview) ->
+    subviewController = subview.controller
     subview.removeFromSuperview()

+
+    subview.set('controller', subviewController || @controller)
     subview.set('superview', this)
     subview.fire('viewDidMoveToSuperview')

@@ -73,6 +76,7 @@
     @destroyBindings() if destroy

     @set('superview', null)
+    @set('controller', null)

   removeFromSuperview: ->
     @superview?.subviews.remove(this)


Allow viewWillDisappear to fire correctly on die.

--- src/view/view.coffee
+++ src/view/view.coffee
@@ -237,8 +237,8 @@
   die: ->
     @fire('destroy')

+    @removeFromSuperview()
     Batman.DOM.destroyNode(@node)
-    @removeFromSuperview()

     @forget()
     @_batman.properties?.forEach (key, property) -> property.die()


Don't tick unless we have clients.

Don't tick unless we have clients.

--- lib/reflector.coffee
+++ lib/reflector.coffee
@@ -45,6 +45,8 @@
     @observers = []

   tick: =>
+    return if @observers.length == 0
+
     packets = []

     for element in @scene.childNodes when element.reflect


fixed script error

fixed script error

--- templates/web/test/directive_test.coffee
+++ templates/web/test/directive_test.coffee
@@ -7,7 +7,7 @@

   scope = {}

-  beforeEach inject ($rootScope,) ->
+  beforeEach inject ($rootScope) ->
     scope = $rootScope.$new()

   it 'should make hidden element visible', inject ($compile) ->


Document Workspace::onDidOpen

Document Workspace::onDidOpen

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -153,6 +153,18 @@
     callback(textEditor) for textEditor in @getTextEditors()
     @onDidAddTextEditor ({textEditor}) -> callback(textEditor)

+  # Essential: Invoke the given callback whenever an item is opened. Unlike
+  # ::onDidAddPaneItem, observers will be notified for items that are already
+  # present in the workspace when they are reopened.
+  #
+  # * `callback` {Function} to be called whenever an item is opened.
+  #   * `event` {Object} with the following keys:
+  #     * `uri` {String} representing the opened URI. Could be `undefined`.
+  #     * `item` The opened item.
+  #     * `pane` The pane in which the item was opened.
+  #     * `index` The index of the opened item on its pane.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidOpen: (callback) ->
     @emitter.on 'did-open', callback


Use promises instead of Q in Workspace

Use promises instead of Q in Workspace

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -2,7 +2,6 @@
 _ = require 'underscore-plus'
 path = require 'path'
 {join} = path
-Q = require 'q'
 Serializable = require 'serializable'
 {Emitter, Disposable, CompositeDisposable} = require 'event-kit'
 Grim = require 'grim'
@@ -448,17 +447,17 @@
     catch error
       switch error.code
         when 'CANCELLED'
-          return Q()
+          return Promise.resolve()
         when 'EACCES'
           atom.notifications.addWarning("Permission denied '#{error.path}'")
-          return Q()
+          return Promise.resolve()
         when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL'
           atom.notifications.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
-          return Q()
+          return Promise.resolve()
         else
           throw error

-    Q(item)
+    Promise.resolve(item)
       .then (item) =>
         if not pane
           pane = new Pane(items: [item])
@@ -488,7 +487,7 @@
     if uri = @destroyedItemURIs.pop()
       @open(uri)
     else
-      Q()
+      Promise.resolve()

   # Public: Register an opener for a uri.
   #
@@ -934,36 +933,33 @@
   #
   # Returns a `Promise`.
   replace: (regex, replacementText, filePaths, iterator) ->
-    deferred = Q.defer()
-
-    openPaths = (buffer.getPath() for buffer in atom.project.getBuffers())
-    outOfProcessPaths = _.difference(filePaths, openPaths)
-
-    inProcessFinished = not openPaths.length
-    outOfProcessFinished = not outOfProcessPaths.length
-    checkFinished = ->
-      deferred.resolve() if outOfProcessFinished and inProcessFinished
-
-    unless outOfProcessFinished.length
-      flags = 'g'
-      flags += 'i' if regex.ignoreCase
-
-      task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
-        outOfProcessFinished = true
-        checkFinished()
-
-      task.on 'replace:path-replaced', iterator
-      task.on 'replace:file-error', (error) -> iterator(null, error)
-
-    for buffer in atom.project.getBuffers()
-      continue unless buffer.getPath() in filePaths
-      replacements = buffer.replace(regex, replacementText, iterator)
-      iterator({filePath: buffer.getPath(), replacements}) if replacements
-
-    inProcessFinished = true
-    checkFinished()
-
-    deferred.promise
+    new Promise (resolve, reject) =>
+      openPaths = (buffer.getPath() for buffer in atom.project.getBuffers())
+      outOfProcessPaths = _.difference(filePaths, openPaths)
+
+      inProcessFinished = not openPaths.length
+      outOfProcessFinished = not outOfProcessPaths.length
+      checkFinished = ->
+        resolve() if outOfProcessFinished and inProcessFinished
+
+      unless outOfProcessFinished.length
+        flags = 'g'
+        flags += 'i' if regex.ignoreCase
+
+        task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
+          outOfProcessFinished = true
+          checkFinished()
+
+        task.on 'replace:path-replaced', iterator
+        task.on 'replace:file-error', (error) -> iterator(null, error)
+
+      for buffer in atom.project.getBuffers()
+        continue unless buffer.getPath() in filePaths
+        replacements = buffer.replace(regex, replacementText, iterator)
+        iterator({filePath: buffer.getPath(), replacements}) if replacements
+
+      inProcessFinished = true
+      checkFinished()

 if includeDeprecatedAPIs
   Workspace.properties


Use thin arrow where possible

Use thin arrow where possible

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -933,7 +933,7 @@
   #
   # Returns a `Promise`.
   replace: (regex, replacementText, filePaths, iterator) ->
-    new Promise (resolve, reject) =>
+    new Promise (resolve, reject) ->
       openPaths = (buffer.getPath() for buffer in atom.project.getBuffers())
       outOfProcessPaths = _.difference(filePaths, openPaths)


disable navigation controls if questions are unanswered

--- app/assets/javascripts/components/navigation_controls.cjsx
+++ app/assets/javascripts/components/navigation_controls.cjsx
@@ -86,7 +86,10 @@

   onClick: (pageIndex) ->
-    if pageIndex.constructor == Array
-      NavigationStore.setPage(pageIndex[0])
+    if NavigationStore.isCurrentPageCompleted()
+      if pageIndex.constructor == Array
+        NavigationStore.setPage(pageIndex[0])
+      else
+        NavigationStore.setPage(pageIndex)
     else
-      NavigationStore.setPage(pageIndex)
+      alert("Sorry! You can't move onto other pages yet, as there are some required questions without answers.")


clean console.logs

clean console.logs

--- app/assets/javascripts/lib/melbourne/melbourne_base.coffee
+++ app/assets/javascripts/lib/melbourne/melbourne_base.coffee
@@ -31,7 +31,6 @@
       AssetFetch.get "http://www.lonelyplanet.com/global-navigation", () ->
         if (window.jsonNavItems and (window.jsonNavItems isnt '') and (window.jsonNavItems isnt 'undefined'))
           dest = new DestinationNav(jsonNavItems.nav, "a.nav__item--destinations")
-          console.log(dest)

       lpLoggedInUsername = null
       auth = new Authentication()


bugfix

bugfix

--- app/assets/javascripts/mixins/item_on_form_mixin.js.coffee
+++ app/assets/javascripts/mixins/item_on_form_mixin.js.coffee
@@ -10,7 +10,7 @@
       serviceUrl: "/api/items.json"
       onSelect: $.proxy(@itemSelected, @)
       transformResult: (response) =>
-        response = JSON.parse response, Bandango.jsonCamelizedReviver
+        response = JSON.parse response, Bandango.jsonCamelizedReviverHelper
         {
           suggestions: response.items.map (itemObject) ->
             item = store.push "item", itemObject


Slightly improve the disabled attribute test coverage

--- test/spec/ng-infinite-scroll.spec.coffee
+++ test/spec/ng-infinite-scroll.spec.coffee
@@ -157,12 +157,21 @@
               element(By.id("force")).click()
               expect(getItems().count()).toBe 100

-            it "respects the disabled attribute and is then throttled when re-enabled", ->
+            it "respects the disabled attribute and is throttled when page loads", ->
               replaceIndexFile "infinite-scroll-disabled='busy'", throttle
               browser.get pathToDocument
               expect(getItems().count()).toBe 0
               element(By.id("action")).click()
+              expect(getItems().count()).toBe 0
               browser.sleep(throttle)
+              expect(getItems().count()).toBe 100
+
+            it "is not throttled when re-enabled if the throttle time has already elapsed", ->
+              replaceIndexFile "infinite-scroll-disabled='busy'", throttle
+              browser.get pathToDocument
+              expect(getItems().count()).toBe 0
+              browser.sleep(throttle)
+              element(By.id("action")).click()
               expect(getItems().count()).toBe 100

             it "respects the infinite-scroll-distance attribute", ->


twopoint: Use grid.

twopoint: Use grid.

--- Oghma/twopoint.coffee
+++ Oghma/twopoint.coffee
@@ -80,6 +80,7 @@

     move = ( x, y, event ) =>
       if group?
+        [ x, y ] = @_.O.grid.nearest( [ x, y ], A )
         if ! event.altKey
           B = [ x, y ]
         else
@@ -93,6 +94,7 @@
       null

     up = ( x, y, event ) =>
+      [ x, y ] = @_.O.grid.nearest( [ x, y ], A )
       B = [ x, y ]
       group?.destroy()
       group = null
@@ -103,6 +105,7 @@
       null

     down = ( x, y ) =>
+      [ x, y ] = @_.O.grid.nearest( [ x, y ], A )
       group = new Kinetic.Group()
       @_.layer.add( group )
       A = [ x, y ]


Use a WeakMap to track drag event counts

This removes the need to synthesize an identifier

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -39,7 +39,7 @@
     @selectedPath = null
     @ignoredPatterns = []

-    @dragEventCounts = {}
+    @dragEventCounts = new WeakMap

     @handleEvents()

@@ -789,24 +789,16 @@

   onDragEnter: (e) =>
     e.stopPropagation()
-
     entry = e.currentTarget.parentNode
-    identifier = "#{entry.constructor.name}:#{entry.getPath()}"
-
-    @dragEventCounts[identifier] ?= 0
-    @dragEventCounts[identifier]++
-
-    entry.classList.add('selected')
+    @dragEventCounts.set(entry, 0) unless @dragEventCounts.get(entry)
+    entry.classList.add('selected') if @dragEventCounts.get(entry) is 0
+    @dragEventCounts.set(entry, @dragEventCounts.get(entry) + 1)

   onDragLeave: (e) =>
     e.stopPropagation()
-
     entry = e.currentTarget.parentNode
-    identifier = "#{entry.constructor.name}:#{entry.getPath()}"
-
-    @dragEventCounts[identifier]--
-    if @dragEventCounts[identifier] is 0
-      entry.classList.remove('selected')
+    @dragEventCounts.set(entry, @dragEventCounts.get(entry) - 1)
+    entry.classList.remove('selected') if @dragEventCounts.get(entry) is 0

   # Handle entry name object dragstart event
   onDragStart: (e) ->


initialPaths is only top level

initialPaths is only top level

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -1069,11 +1069,13 @@
       # the drag image.
       dragImage.style.willChange = "transform"

+      initialPaths = []
       for target in @getSelectedEntries()
         entryPath = target.querySelector(".name").dataset.path
         parentSelected = target.parentNode.closest(".entry.selected")
         @dragPaths.push(entryPath)
         unless parentSelected
+          initialPaths.push(entryPath)
           newElement = target.cloneNode(true)
           if newElement.classList.contains("directory")
             newElement.querySelector(".entries").remove()
@@ -1088,7 +1090,7 @@

       e.dataTransfer.effectAllowed = "move"
       e.dataTransfer.setDragImage(dragImage, 0, 0)
-      e.dataTransfer.setData("initialPaths", @dragPaths)
+      e.dataTransfer.setData("initialPaths", initialPaths)

       window.requestAnimationFrame ->
         dragImage.remove()


Leave case unmodified in file manager warning messages

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -560,13 +560,13 @@
     return unless filePath = @selectedEntry()?.getPath()

     unless shell.showItemInFolder(filePath)
-      atom.notifications.addWarning("Unable to show #{filePath} in #{@getFileManagerName().toLowerCase()}")
+      atom.notifications.addWarning("Unable to show #{filePath} in #{@getFileManagerName()}")

   showCurrentFileInFileManager: ->
     return unless filePath = atom.workspace.getCenter().getActiveTextEditor()?.getPath()

     unless shell.showItemInFolder(filePath)
-      atom.notifications.addWarning("Unable to show #{filePath} in #{@getFileManagerName().toLowerCase()}")
+      atom.notifications.addWarning("Unable to show #{filePath} in #{@getFileManagerName()}")

   getFileManagerName: ->
     switch process.platform


Add locations collection

Add locations collection

--- _source/lib/collections/location.coffee
+++ _source/lib/collections/location.coffee
@@ -1,5 +1,5 @@

-# Apollos.locations = new Mongo.Collection "locations"
+Apollos.locations = new Mongo.Collection "locations"

 Apollos.generateSchema "location",
   locationId:


Update translations.coffee

Update translations.coffee

--- addon/locales/zh-tw/translations.coffee
+++ addon/locales/zh-tw/translations.coffee
@@ -303,14 +303,14 @@
     "expect_delivery": "物資大約何時到達？"
     "open_new_tab": "將在新視窗打開"
     "book_schedule": "預約時間"
-    "arrive_on_time": "Please arrive during our operating hours to ensure we can receive your kind donation."
-    "volunteer": "Our volunteers can help unload your vehicle."
+    "arrive_on_time": "為確保能收到閣下捐贈的物資，請在辦公時間光臨。"
+    "volunteer": "我們的義工團隊將協助搬運。"
     "see_map": "詳見地圖"
-    "delivery_to": "Deliver To:"
-    "crossroads_address": "Crossroads Village,
 2 Castle Peak Road, Tuen Mun, NT
 Drop off at Building 32"
-    "arrive": "Please arrive during our operating hours."
-    "message_us": "Message us via this app if you have any questions."
-    "thanks": "Thank you for arranging delivery of your items."
+    "delivery_to": "請送到："
+    "crossroads_address": "
新界屯門青山公路2號
 十字路會32號大樓"
+    "arrive": "請在辦公時間光臨。"
+    "message_us": "如有任何疑問，請通過好人好市應用程式向我們查詢。"
+    "thanks": "閣下安排遞送物資，我們衷心感激。"

   "crossroads_truck":
         "book_time": "預約時間"


combineAsArray: inline checkEnd function

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -159,16 +159,14 @@
     values = (None for s in streams)
     new Property (sink) =>
       ends = (false for s in streams)
-      checkEnd = (unsubAll) ->
-        if _.all(ends)
-          reply = sink end()
-          unsubAll() if reply == Bacon.noMore
       initialSent = false
       combiningSink = (index) => (unsubAll) ->
         streams[index].subscribeInternal (event) =>
           if (event.isEnd())
             ends[index] = true
-            checkEnd(unsubAll)
+            if _.all(ends)
+              reply = sink end()
+              unsubAll() if reply == Bacon.noMore
             Bacon.noMore
           else if event.isError()
             reply = sink event


remove unnecessary if

remove unnecessary if

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1355,13 +1355,12 @@
     unsub = ->
       unsubd = true
       doUnsub()
-    if !unsubd
-      doUnsub = obs.subscribeInternal (event) ->
-        afterTransaction ->
-          if not unsubd
-            reply = sink event
-            if reply == Bacon.noMore
-              unsub()
+    doUnsub = obs.subscribeInternal (event) ->
+      afterTransaction ->
+        if not unsubd
+          reply = sink event
+          if reply == Bacon.noMore
+            unsub()
     unsub

   { whenDoneWith, inTransaction, currentEventId, wrappedSubscribe }


JCredential: setPermissionFor: add support to provide role for relationship

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -245,7 +245,7 @@
             callback null, []

-  setPermissionFor: (target, { user, owner }, callback) ->
+  setPermissionFor: (target, { user, owner, role }, callback) ->

     Relationship.remove
       targetId : @getId()
@@ -253,8 +253,9 @@
     , (err) =>

       if user
-        as = if owner then 'owner' else 'user'
-        target.addCredential this, { as }, (err) -> callback err
+        options      = { as: if owner then 'owner' else 'user' }
+        options.data = { role }  if role
+        target.addCredential this, options, (err) -> callback err
       else
         callback err


Fix raw message concatenation

Fix raw message concatenation

--- lib/view/body.coffee
+++ lib/view/body.coffee
@@ -22,7 +22,7 @@

 # concatenate multiline judgements
 concatJudgements = (lines) ->
-    lineStartRegex = /^(?:Goal|Have|.+ )\:|Sort /
+    lineStartRegex = /^(?:Goal|Have|\S+ )\:|Sort /
     result = []
     currentLine = 0
     lines.forEach (item, i) ->


Fixed rerender issue with missing content.

--- app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee
+++ app/assets/javascripts/app/controllers/_dashboard/first_steps.coffee
@@ -7,14 +7,13 @@

   constructor: ->
     super
-    @interval(@load, 25000)
+    @interval(@load, 20000)

   load: =>
     return if @lastData && !@el.is(':visible')
     @ajax(
-      id:    'first_steps'
-      type:  'GET'
-      url:   @apiPath + '/first_steps'
+      type: 'GET'
+      url:  @apiPath + '/first_steps'
       success: (data) =>
         return if _.isEqual(@lastData, data)
         @lastData = data


Users can use semi colon in their javascript expression. Related to #21

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -781,7 +781,7 @@
                 @.$('.loading_query_img').css 'display', 'none'
                 @results_view.render_error(@query, err)

-        # Separate the queries so we can execute them in an synchronous order
+        # Separate the queries so we can execute them in a synchronous order. We use .run()\s{*}; to separate queries (and we make sure that the separator is not in a string)
         separate_queries: (query) =>
             start = 0
             count_dot = 0
@@ -789,14 +789,36 @@
             is_string = false
             char_used = ''
             queries = []
+
+            # Again because of strings, we cannot know use a pretty regex
+            is_parsing_function = false # Track if we are parsing a function (between a dot and a opening parenthesis)
+            is_parsing_args = false # Track if we are parsing the arguments of a function so we can match for .run( ) but not for .ru n()
+            last_function = '' # The last function used with its arguments
             for i in [0..query.length-1]
                 if is_string is false
                     if (query[i] is '"' or query[i] is '\'')
                         is_string = true
                         char_used = query[i]
                     else if query[i] is ';'
-                        queries.push query.slice start, i
-                        start = i+1
+                        if last_function is 'run()' # If the last function is run(), we have one query
+                            queries.push query.slice start, i
+                            start = i+1
+
+                    # Keep track of the last function used
+                    if query[i] is '.' # New function detected, let's reset last_function and switch on is_parsing_function
+                        last_function = ''
+                        is_parsing_function = true
+                    else if is_parsing_function is true
+                        if is_parsing_args is false or /\s/.test(query[i]) is false # If we are parsing arguments, we are not interested in white space
+                            last_function += query[i]
+
+                        if query[i] is '(' # We are going to parse arguments now
+                            is_parsing_args = true
+                        else if query[i] is ')' # End of the function
+                            is_parsing_function = false
+                            is_parsing_args = false
+
+
                 else if is_string is true
                     if query[i] is char_used
                         if query[i-1]? and query[i-1] is '\\'


Fix #1249

Fix #1249

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -505,10 +505,10 @@
                 @hide_suggestion()
             return @

-        # Callback used by the cursor
+        # Callback used by the cursor when the user hit 'more results'
         callback_query: (data) =>
             # If we get a run time error
-            if data instanceof rethinkdb.errors.RuntimeError
+            if data instanceof rethinkdb.errors.RuntimeError or data instanceof rethinkdb.errors.BadQuery or data instanceof rethinkdb.errors.ClientError or data instanceof rethinkdb.errors.ClientError
                 @.$('.loading_query_img').css 'display', 'none'
                 @.results_view.render_error(@query, data)
                 return false
@@ -536,29 +536,6 @@
                     @current_results.push data
                 return false

-        # Callback for multiple queries
-        callback_multiple_query: (data) =>
-            # If we get a run time error
-            if data instanceof rethinkdb.errors.RuntimeError
-                @.$('.loading_query_img').css 'display', 'none'
-                @.results_view.render_error(@query, data)
-                return false
-
-            @current_query_index++
-            if @current_query_index is @queries.length-1
-                if @cursor?
-                    @cursor.close()
-                @cursor = eval(@queries[@current_query_index])
-                @cursor.next(@callback_query)
-            else
-                if @cursor?
-                    @cursor.close()
-                @cursor = eval(@queries[@current_query_index])
-                @cursor.next(@callback_multiple_query)
-
-            return false
-
-
         # Function triggered when the user click on 'more results'
         show_more_results: (event) =>
             try
@@ -569,6 +546,72 @@
                 @.$('.loading_query_img').css 'display', 'none'
                 @results_view.render_error(@query, err)

+        # Create tagged callback so we just execute the last query executed by the user
+        # Results:
+        # User execute q1
+        # User execute q2
+        # q1 returns but no results is displayed
+        # q2 returns, we display q2 results
+        create_tagged_callback: =>
+            id = Math.random() # That should be good enough for our application
+            @last_id = id
+            @current_results = []
+            @skip_value = 0
+            @current_query_index = 0
+
+            # The callback with the id that we are going to return
+            callback_multiple_queries = (data) =>
+                if id is @last_id # We display things or fire the next query only if the user hasn't execute another query
+                    # Check if the data sent by the server is an error
+                    if data instanceof rethinkdb.errors.RuntimeError or data instanceof rethinkdb.errors.BadQuery or data instanceof rethinkdb.errors.ClientError or data instanceof rethinkdb.errors.ClientError
+                        @.$('.loading_query_img').css 'display', 'none'
+                        @.results_view.render_error(@query, data)
+                        return false
+
+                    # Look for the next query
+                    @current_query_index++
+
+                    # If we are dealing with the last query
+                    if @current_query_index >= @queries.length
+                        # If we get a run time error
+                        if data instanceof rethinkdb.errors.RuntimeError
+                            @.$('.loading_query_img').css 'display', 'none'
+                            @.results_view.render_error(@query, data)
+                            return false
+
+                        # if it's a valid result and we have not reach the maximum of results displayed
+                        if data? and  @current_results.length < @limit
+                            @current_results.push data
+                            return true
+                        else
+                            # Else we are going to display what we have
+                            @.$('.loading_query_img').css 'display', 'none'
+                            @results_view.render_result @query, @current_results
+
+                            execution_time = new Date() - @start_time
+                            @results_view.render_metadata
+                                limit_value: @current_results.length
+                                skip_value: @skip_value
+                                execution_time: execution_time
+                                query: @query
+                                has_more_data: true if data? # if data is undefined, it means that there is no more data
+
+                            if data? #there is nore data
+                                @skip_value += @current_results.length
+                                @current_results = []
+                                @current_results.push data
+                            return false
+                    else #  Else if it's not the last query, we just execute the next query
+                        if @cursor?
+                            @cursor.close()
+                        @cursor = eval(@queries[@current_query_index])
+                        @cursor.next(callback_multiple_queries)
+
+                    return false
+                else
+                    @cursor.close()
+                    return false
+            return callback_multiple_queries
         # Function that execute the query
         execute_query: =>
             # Postpone the reconnection
@@ -602,18 +645,16 @@
                 @start_time = new Date()
                 @current_results = []
                 @skip_value = 0
-                if @queries.length > 1
-                    @current_query_index = 0
-                    @cursor = eval(@queries[@current_query_index])
-                    @cursor.next(@callback_multiple_query)
-                else
-                    @cursor = eval(@query)
-                    @cursor.next(@callback_query)
+
+                tagged_callback = @create_tagged_callback()
+                @cursor = eval(@queries[@current_query_index])
+                @cursor.next(tagged_callback)
+
             catch err
                 @.$('.loading_query_img').css 'display', 'none'
                 @results_view.render_error(@query, err)

-        # Separate the queries so we can have an asychronous order
+        # Separate the queries so we can execute them in an synchronous order
         separate_queries: (query) =>
             start = 0
             count_dot = 0
@@ -776,6 +817,7 @@
                 window.conn.close()
             catch err
                 #console.log 'Could not destroy connection'
+            @cursor.close()
             clearTimeout @timeout

     class @InputQuery extends Backbone.View


rename to ResultViewWrapper and add comment to Container events

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -613,7 +613,7 @@
                 [/\[/g, '\\[']
             ]

-            @results_view = new ResultParentView
+            @results_view = new ResultViewWrapper
                 container: @
                 view: @state.view

@@ -628,7 +628,9 @@
             @driver_handler = new DriverHandler
                 container: @

-            # One callback to rule them all
+            # These events were caught here to avoid being bound and unbound every time
+            # The results changed. It should ideally be caught in the individual result views
+            # that actually need it.
             $(window).mousemove @handle_mousemove
             $(window).mouseup @handle_mouseup
             $(window).mousedown @handle_mousedown
@@ -3434,7 +3436,7 @@
                 @delegateEvents()
             @

-    class ResultParentView extends Backbone.View
+    class ResultViewWrapper extends Backbone.View
         className: 'result_view'
         template: Handlebars.templates['dataexplorer_result_container-template']
         metadata_template: Handlebars.templates['dataexplorer-metadata-template']


Enable getLastModified tests, as CORS PR has now been deployed

--- tests/integration.spec.coffee
+++ tests/integration.spec.coffee
@@ -1145,8 +1145,6 @@
 		describe 'OS Model', ->

 			describe 'resin.models.os.getLastModified()', ->
-				# TODO: can be reenabled after the CORS PR is deployed to prod
-				return if IS_BROWSER

 				describe 'given a valid device slug', ->


syntax tweaks, more docs

syntax tweaks, more docs

--- source/javascripts/grid.coffee
+++ source/javascripts/grid.coffee
@@ -1,13 +1,11 @@
 render = ->
-  width  = $(document.body).width()
-  height = 280
-  gridWidth = Math.round(width / 4) - 41
-  colors = ['#1a1a1a', '#353535', '#555', '#757575', '#959595', '#b5b5b5', '#d5d5d5', '#f5f5f5']
-
-  color = d3.scale.quantile().range colors
-
+  width       = $(document.body).width()
+  height      = 280
+  gridWidth   = Math.round(width / 4) - 41
+  colors      = ['#1a1a1a', '#353535', '#555', '#757575', '#959595', '#b5b5b5', '#d5d5d5', '#f5f5f5']
+  color       = d3.scale.quantile().range colors
   projection  = d3.geo.albers().scale(1).translate [ 0, 0 ]
-  path = d3.geo.path().projection(projection)
+  path        = d3.geo.path().projection(projection)

   d3.json 'data/pdx.json', (pdx) ->
     $('.js-loading').hide()
@@ -25,12 +23,14 @@
     water       = topojson.feature pdx, pdx.objects.water
     parks       = topojson.feature pdx, pdx.objects.parks

+    # Auto scale map to fit within bounds
     projection.scale(1).translate([0, 0])
     b = path.bounds(blockgroups)
     s = .99 / Math.max((b[1][0] - b[0][0]) / gridWidth, (b[1][1] - b[0][1]) / height)
     t = [(gridWidth - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2]
     projection.scale(s).translate(t)

+    # Build the maps from the mappings in mappings.coffee
     items.each (mapping) ->
       canvas = d3.select(this).append('canvas')
         .attr('width', gridWidth)
@@ -70,6 +70,7 @@
       context.fillStyle = '#151515'
       context.fill()

+      # Draw legends
       quantiles = color.quantiles()
       legend = d3.select(this).append('ol')
         .attr('class', 'legend')


add new handlebar helpers

add new handlebar helpers

--- SingularityUI/app/handlebarsHelpers.coffee
+++ SingularityUI/app/handlebarsHelpers.coffee
@@ -7,12 +7,20 @@
 Handlebars.registerHelper 'ifEqual', (v1, v2, options) ->
     if v1 is v2 then options.fn @ else options.inverse @

+Handlebars.registerHelper 'ifNotEqual', (v1, v2, options) ->
+    if v1 isnt v2 then options.fn @ else options.inverse @
+
 Handlebars.registerHelper 'ifLT', (v1, v2, options) ->
     if v1 < v2 then options.fn @ else options.inverse @

 Handlebars.registerHelper 'ifGT', (v1, v2, options) ->
     if v1 > v2 then options.fn @ else options.inverse @

+# Override decimal rounding: {{fixedDecimal data.cpuUsage place="4"}}
+Handlebars.registerHelper 'fixedDecimal', (value, options) ->
+    if options.hash.place then place = options.hash.place else place = 2
+    +(value).toFixed(place)
+
 Handlebars.registerHelper 'ifInSubFilter', (needle, haystack, options) ->
     return options.fn @ if haystack is 'all'
     if haystack.indexOf(needle) isnt -1
@@ -26,6 +34,17 @@
         options.fn @
     else
         options.inverse @
+
+
+Handlebars.registerHelper 'rename', (text,  options) ->
+    text = text.trim()
+    cases =
+        'cooling down': 'cooling'
+        'load balancer cleanup': 'LB cleanup'
+
+    return cases[text] if cases[text]
+    text
+

 # {{#withLast [1, 2, 3]}}
 #     {{! this = 3 }}
@@ -52,6 +71,7 @@
 Handlebars.registerHelper 'timestampDuration', (timestamp) ->
     return '' if not timestamp
     moment.duration(timestamp).humanize()
+

 # 1234567890 => 1 Aug 1991 15:00
 Handlebars.registerHelper 'timestampFormatted', (timestamp) ->


sidebar/managed: don't try to use template early

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -155,13 +155,7 @@
     { template, stack } = @props
     { storage } = kd.singletons.computeController

-    template = template.toJS()
     stack = stack.toJS()
-    account = whoami()
-
-    isMyTemplate = template?.originId is account._id
-
-    debug 'accessLevel', template.accessLevel

     if @getStackUnreadCount()
       menuItems['Update'] = { callback }
@@ -178,6 +172,13 @@
       ['VMs', 'Destroy VMs'].forEach (name) -> menuItems[name] = { callback }

     else
+
+      template = template.toJS()
+      account = whoami()
+
+      isMyTemplate = template?.originId is account._id
+
+      debug 'accessLevel', template.accessLevel

       if isAdmin() or isMyTemplate
         menuItems['Edit'] = { callback }


VM.re-create issue fixed, I hate this kind of micro-optimizations at all.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -157,12 +157,13 @@
   fetchVMs: do (waiting = []) ->
     (force, callback)->
       [callback, force] = [force, callback]  unless callback?
-
       return  unless callback?

-      if @vms.length then return @utils.defer => callback null, @vms
-
-      return  if not force and (waiting.push callback) > 1
+      if @vms.length
+        return @utils.defer => callback null, @vms
+
+      if not force and (waiting.push callback) > 1
+        return  callback null, []

       KD.remote.api.JVM.fetchVms (err, vms)=>
         @vms = vms  unless err
@@ -174,7 +175,6 @@

   fetchGroupVMs:(callback = noop)->
-    # if KD.isGuest() then @groupVms = ['guest']
     if @groupVms.length > 0
       return @utils.defer =>
         callback null, @groupVms
@@ -184,7 +184,6 @@
       callback? err, vms

   fetchVMDomains:(vmName, callback = noop)->
-    # if KD.isGuest() then @vmDomains = guest: ['guest.kd.io']
     if domains = @vmDomains[vmName]
       return @utils.defer -> callback null, domains


:white_check_mark: #91

:white_check_mark: #91

--- spec/motion-search-spec.coffee
+++ spec/motion-search-spec.coffee
@@ -80,6 +80,15 @@
         ensure ['V/', {search: 'ef'}],
           selectedText: "abc\ndef\n",

+      it 'not extend linwise selection if search matches on same line', ->
+        # settings.set 'incrementalSearch', true
+        set text: """
+          abc def
+          def\n
+          """
+        ensure ['V/', {search: 'ef'}],
+          selectedText: "abc def\n",
+
       describe "case sensitivity", ->
         beforeEach ->
           set


Update routes

Update routes

--- app/src/routes.coffee
+++ app/src/routes.coffee
@@ -2,7 +2,7 @@

   ## Default
   route '/', ->
-    app.router.go '/onboarding/management/welcome'
+    app.router.go '/onboarding/management/frozen'

   ## Onboarding
   # Device


Added new queries to Sankey

Added new queries to Sankey

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -24,46 +24,51 @@

     ]
     links: [
-      #Direct links between carrier and sector
-	    {left: 'coal',      right: 'households', gquery: 'coal_households_in_mekko_of_final_demand', color: 'black'},
-      {left: 'gas',       right: 'households', gquery: 'gas_households_in_mekko_of_final_demand'},
-      {left: 'oil',       right: 'households', gquery: 'oil_households_in_mekko_of_final_demand', color: 'brown'},
-      {left: 'biomass',   right: 'households',  gquery: 'biomass_households_in_mekko_of_final_demand', color: 'green'},
-      {left: 'waste',     right: 'households', gquery: 'waste_households_in_mekko_of_final_demand', color: 'darkgreen'}
-      # Electricity to sectors
-      {left: 'el_prod',   right: 'households',  gquery: 'electricity_households_in_mekko_of_final_demand'},
-      {left: 'el_prod',   right: 'buildings',   gquery: 'electricity_buildings_in_mekko_of_final_demand'},
-      {left: 'el_prod',   right: 'transport',   gquery: 'electricity_transport_in_mekko_of_final_demand'},
-      {left: 'el_prod',   right: 'industry',    gquery: 'electricity_industry_in_mekko_of_final_demand'},
-      {left: 'el_prod',   right: 'agriculture', gquery: 'electricity_agriculture_in_mekko_of_final_demand'},
-      {left: 'el_prod',   right: 'other',       gquery: 'electricity_other_in_mekko_of_final_demand'},
-
-      {left: 'heat_prod', right: 'households',  gquery: 'hot_water_households_in_mekko_of_final_demand', color: 'red'},
-      {left: 'heat_prod', right: 'buildings',   gquery: 'hot_water_buildings_in_mekko_of_final_demand', color: 'red'},
-      {left: 'heat_prod', right: 'transport',   gquery: 'hot_water_transport_in_mekko_of_final_demand', color: 'red'},
-      {left: 'heat_prod', right: 'industry',    gquery: 'hot_water_industry_in_mekko_of_final_demand', color: 'red'},
-      {left: 'heat_prod', right: 'agriculture', gquery: 'hot_water_agriculture_in_mekko_of_final_demand', color: 'red'},
-      {left: 'heat_prod', right: 'other',       gquery: 'hot_water_other_in_mekko_of_final_demand', color: 'red'},
-
-      {left: 'coal',      right: 'el_prod',     gquery: 'coal_in_source_of_electricity_production', color: 'black'},
-      {left: 'nuclear',   right: 'el_prod',     gquery: 'nuclear_in_source_of_electricity_production', color: 'red'},
-      {left: 'gas',       right: 'el_prod',     gquery: 'gas_in_source_of_electricity_production'},
-      {left: 'oil',       right: 'el_prod',     gquery: 'oil_in_source_of_electricity_production', color: 'brown'},
-      {left: 'biomass',   right: 'el_prod',     gquery: 'biomass_in_source_of_electricity_production', color: 'green'},
-      {left: 'wind',      right: 'el_prod',     gquery: 'wind_in_source_of_electricity_production'},
-      {left: 'hydro',     right: 'el_prod',     gquery: 'hydro_in_source_of_electricity_production'},
-      {left: 'solar',     right: 'el_prod',     gquery: 'solar_in_source_of_electricity_production'},
-      {left: 'waste',     right: 'el_prod',     gquery: 'waste_in_source_of_electricity_production'},
-      {left: 'coal',      right: 'heat_prod',   gquery: 'coal_in_source_of_electricity_production', color: 'black'},
-      {left: 'nuclear',   right: 'heat_prod',   gquery: 'nuclear_in_source_of_electricity_production', color: 'red'},
-      {left: 'gas',       right: 'heat_prod',   gquery: 'gas_in_source_of_electricity_production'},
-      {left: 'oil',       right: 'heat_prod',   gquery: 'oil_in_source_of_electricity_production', color: 'brown'},
-      {left: 'biomass',   right: 'heat_prod',   gquery: 'biomass_in_source_of_electricity_production', color: 'green'},
-      {left: 'wind',      right: 'heat_prod',   gquery: 'wind_in_source_of_electricity_production'},
-      {left: 'hydro',     right: 'heat_prod',   gquery: 'hydro_in_source_of_electricity_production'},
-      {left: 'solar',     right: 'heat_prod',   gquery: 'solar_in_source_of_electricity_production'},
-      {left: 'waste',     right: 'heat_prod',   gquery: 'waste_in_source_of_electricity_production'}
-
+    {left: 'electricity',      right: 'households', gquery: 'electricity_to_households_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'households', gquery: 'heat_to_households_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'households', gquery: 'distribution_to_households_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'buildings', gquery: 'electricity_to_buildings_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'buildings', gquery: 'heat_to_buildings_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'buildings', gquery: 'distribution_to_buildings_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'agriculture', gquery: 'electricity_to_agriculture_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'agriculture', gquery: 'heat_to_agriculture_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'agriculture', gquery: 'distribution_to_agriculture_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'transport', gquery: 'electricity_to_transport_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'transport', gquery: 'heat_to_transport_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'transport', gquery: 'distribution_to_transport_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'industry', gquery: 'electricity_to_industry_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'industry', gquery: 'heat_to_industry_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'industry', gquery: 'distribution_to_industry_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'other', gquery: 'electricity_to_other_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'other', gquery: 'heat_to_other_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'other', gquery: 'distribution_to_other_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'monetized_heat', gquery: 'monetized_heat_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'electricity', gquery: 'electricity_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'useful_demand', gquery: 'useful_demand_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'non_renewable_waste', gquery: 'non_renewable_waste_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'geo_solar_wind_water_ambient', gquery: 'geo_solar_wind_water_ambient_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'natural_gas', gquery: 'natural_gas_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'nuclear', gquery: 'nuclear_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'biomass_products', gquery: 'biomass_products_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'electricity',      right: 'mixed_flows', gquery: 'mixed_flows_to_electricity_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'monetized_heat', gquery: 'monetized_heat_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'electricity', gquery: 'electricity_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'useful_demand', gquery: 'useful_demand_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'non_renewable_waste', gquery: 'non_renewable_waste_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'geo_solar_wind_water_ambient', gquery: 'geo_solar_wind_water_ambient_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'natural_gas', gquery: 'natural_gas_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'nuclear', gquery: 'nuclear_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'biomass_products', gquery: 'biomass_products_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'heat',      right: 'mixed_flows', gquery: 'mixed_flows_to_heat_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'monetized_heat', gquery: 'monetized_heat_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'electricity', gquery: 'electricity_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'useful_demand', gquery: 'useful_demand_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'non_renewable_waste', gquery: 'non_renewable_waste_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'geo_solar_wind_water_ambient', gquery: 'geo_solar_wind_water_ambient_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'natural_gas', gquery: 'natural_gas_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'nuclear', gquery: 'nuclear_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'biomass_products', gquery: 'biomass_products_to_distribution_in_sankey.gql', color: 'black'},
+    {left: 'distribution',      right: 'mixed_flows', gquery: 'mixed_flows_to_distribution_in_sankey.gql', color: 'black'}

     ]


sankey: better margin handling

sankey: better margin handling

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -109,7 +109,7 @@
   # cool but not flexible enough
   Node: class extends Backbone.Model
     @width: 100
-    @horizontal_spacing: 280
+    @horizontal_spacing: 250
     vertical_margin: 10

     initialize: =>
@@ -235,9 +235,13 @@
     # this method is called when we first render the chart. It is called if we
     # want a full chart refresh when the user resizes the browser window, too
     draw: =>
-      @margin = 50
-      @width = (@container_node().width()   || 490) - 2 * @margin
-      @height = (@container_node().height() || 402) - 2 * @margin
+      @margin =
+        top: 10
+        left: 45
+        bottom: 10
+        right: 10
+      @width = (@container_node().width()   || 490) - (@margin.left + @margin.right)
+      @height = (@container_node().height() || 452) - (@margin.top + @margin.bottom)

       # set up the scaling methods
       @x = d3.scale.linear().domain([0, 600]).range([0, @width])
@@ -254,10 +258,10 @@

       @svg = d3.select("#d3_container_sankey").
         append("svg:svg").
-        attr("height", @height + 2 * @margin).
-        attr("width", @width + 2 * @margin).
+        attr("height", @height + @margin.top + @margin.bottom).
+        attr("width", @width + @margin.left + @margin.right).
         append("svg:g").
-        attr("transform", "translate(#{@margin}, #{@margin})")
+        attr("transform", "translate(#{@margin.left}, #{@margin.top})")
       @links = @draw_links()
       @nodes = @draw_nodes()
       @axis = @draw_axis()
@@ -282,7 +286,7 @@
         style("stroke-width", (link) -> link.value()).
         style("stroke", (link, i) -> link.color()).
         style("fill", "none").
-        style("opacity", 0.7).
+        style("opacity", 0.6).
         attr("d", (link) => @link_line link.path_points()).
         on("mouseover", @link_mouseover).
         on("mouseout", @node_mouseout)
@@ -347,7 +351,7 @@
       d3.selectAll(".link").
         transition().
         duration(200).
-        style("opacity", 0.7).
+        style("opacity", 0.6).
         selectAll(".link_label").
         transition().
         style("opacity", 0)


Extended robot.brain to act as a general-purpose key-value store.

Additions:
 - robot.brain.set
   For storing arbitrary key, value pairs under a private namespace to avoid collisions.
 - robot.brain.ger
   For getting from store by key.

Signed-off-by: Diwank Singh Tomer <a78369447041e6bcf2913d083e4ed301785fc609@gmail.com>

--- src/brain.coffee
+++ src/brain.coffee
@@ -106,4 +106,52 @@

     matchedUsers

-module.exports = Brain
+# Extending Brain to expose a simple key-value store for scripts to use.
+class Store extends Brain
+  # Call super to kick off initialization and then
+  # initialize private namespace for storage.
+  constructor: ->
+    super()
+    extend @data, _private: { }
+
+  # Public: Store key-value pair under the private namespace and extend
+  # existing @data before emiiting the 'loaded' event.
+  #
+  # Returns the instance for chaining.
+  set: (key, value) ->
+    # Parse key if key is object.
+    if isObject key
+      pair = key
+    else
+      pair = {}
+      pair[key] = value
+
+    # Store key-value pair.
+    extend @data._private, pair
+    @emit 'loaded', @data
+
+    this
+
+  # Public: Get value by key from the private namespace in @data
+  # or return null if not found.
+  #
+  # Returns the value.
+  get: (key) -> @data._private[key] ? null
+
+# - Utils -
+# Private: Extend obj with objects passed as additional args.
+#
+# Returns the original object with updated changes.
+extend = (obj, sources...) ->
+  for source in sources
+    obj[key] = value for own key, value of source
+
+  # Result
+  obj
+
+# Private: Check if argument is an object.
+#
+# Returns true/false.
+isObject = (obj) -> obj is Object obj
+
+module.exports = Store


simplified function

simplified function

--- core/app/assets/javascripts/lib/format_as_authority.coffee
+++ core/app/assets/javascripts/lib/format_as_authority.coffee
@@ -1,9 +1,7 @@
 window.format_as_authority = (number) ->
   if number < 10
-    number = parseFloat(number).toFixed(1)
-  else if (number >= 10) && (number < 1000)
-    number = Math.floor(number)
-  else if number >= 1000
-    number = "" + Math.floor(number/1000) + "k"
-
-  "#{number}"
+    parseFloat(number).toFixed(1)
+  else if number < 1000
+    Math.floor(number).toString()
+  else
+    "" + Math.floor(number/1000) + "k"


add logging of migration phase at startup

--- app/coffee/RedisMigrationManager.coffee
+++ app/coffee/RedisMigrationManager.coffee
@@ -133,7 +133,7 @@
 		@rclient_new = redis.createClient(@new_settings)
 		@new_key_schema = new_settings.key_schema
 		# check that migration phase is valid on startup
-		@getMigrationPhase()
+		logger.warn {migration_phase: @getMigrationPhase()}, "running with RedisMigrationManager"

 	getMigrationPhase: () ->
 		@migration_phase = @new_settings.migration_phase  # FIXME: allow setting migration phase while running for testing


Fix nominator finder loop

Fix nominator finder loop

Javascript can't break out twice (switch and for)

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -51,9 +51,10 @@

     nominators = []
     for event in @props.events by -1
-      switch event.type
-        when 'disqualify' then break
-        when 'nominate' then nominators.push(@props.users[event.user_id])
+      if event.type == 'disqualify'
+        break
+      else if event.type == 'nominate'
+        nominators.push(@props.users[event.user_id])

     div className: bn,
       div className: "#{bn}__header",


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -74,7 +74,7 @@
       g = hueToRgb(p, q, h)
       b = hueToRgb(p, q, h - 120)

-      rgbMap = ((channel * 0xFF).round() for channel in [r, g, b])
+      rgbMap = ((channel * 255).round() for channel in [r, g, b])

     return rgbMap.concat(a)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -37,10 +37,33 @@

     self =
       channels: channels
-      r: channels[0]
-      g: channels[1]
-      b: channels[2]
-      a: channels[3]
+      r: (val) ->
+        if val?
+          channels[0] = val
+          return self
+        else
+          channels[0]
+
+      g: (val) ->
+        if val?
+          channels[1] = val
+          return self
+        else
+          channels[1]
+
+      b: (val) ->
+        if val?
+          channels[2] = val
+          return self
+        else
+          channels[2]
+
+      a: (val) ->
+        if val?
+          channels[3] = val
+          return self
+        else
+          channels[3]

       equals: (other) ->
         return other.r == self.r &&


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -14,9 +14,7 @@

   parseHex = (hexString) ->
     hexString = hexString.replace(/#/, '')
-
-    bits = rgbParser.exec(hexString)
-
+
     switch hexString.length
       when 3, 4 then function() {}
       else


adding docs for Color

adding docs for Color

--- src/color.coffee
+++ src/color.coffee
@@ -805,8 +805,7 @@

     magenta.toHsl()
     # => [300, 1, 0.5, 1]
-

-
+

     @name toHsl
     @methodOf Color#
@@ -843,6 +842,13 @@

     ###*
     returns string rgba representation of the color.
+
+

+    red = Color('#ff0000')
+
+    red.toString()
+    # => 'rgba(255, 0, 0, 1)'
+

     @name toString
     @methodOf Color#


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -211,7 +211,9 @@

     red = Color(255, 0, 0)

-    red.complement().toString()
+    cyan = red.complement()
+
+    cyan.toString()
     # => 'rgba(0, 255, 255, 1)'

@@ -225,6 +227,16 @@

     ###*
     Modifies the calling color to make it the complement of its previous value.
+
+

+    red = Color(255, 0, 0)
+
+    # modifies red in place to make it into cyan
+    red.complement()
+
+    red.toString()
+    # => 'rgba(0, 255, 255, 1)'
+

     @name complement$
     @methodOf Color#


Document resin/data-prefix

Document resin/data-prefix

--- lib/resin/data/data-prefix.coffee
+++ lib/resin/data/data-prefix.coffee
@@ -1,11 +1,30 @@
 mkdirp = require('mkdirp')
 fsUtils = require('./fs-utils/fs-utils')

+# @nodoc
 prefix = null

+# Get current prefix
+#
+# @return {String} prefix
+#
+# @example Get prefix
+#		prefix = resin.data.prefix.get()
+#
 exports.get = ->
 	return prefix

+# Set prefix
+#
+# @param {String} newPrefix new prefix
+# @param {Function} callback callback (error)
+#
+# @throw {Error} Will throw if prefix is not a valid path
+#
+# @example Set prefix
+#		resin.data.prefix.set '/opt/resin', (error) ->
+#			throw error if error?
+#
 exports.set = (newPrefix, callback) ->
 	if not fsUtils.isValidPath(newPrefix)
 		return callback?(new Error('Invalid path'))
@@ -15,5 +34,9 @@
 		prefix = newPrefix
 		return callback?()

+# Clear prefix
+#
+# @example Clear prefix
+#		resin.data.prefix.clear()
 exports.clear = ->
 	prefix = null


don't use an interval, only check when needed

--- js-library/app/js/util/scrollspeed.coffee
+++ js-library/app/js/util/scrollspeed.coffee
@@ -9,7 +9,10 @@
     Factlink.el.trigger('stop_fast_scrolling') if Factlink.el.hasClass 'fl-fast-scrolling'

+recheckWhetherWereStillScrolling = null;
+
 check_scrolling_speed = =>
+    clearTimeout recheckWhetherWereStillScrolling
     scrolltop = if window.pageYOffset?
                   window.pageYOffset
                 else if  document.documentElement.scrollTop?
@@ -20,11 +23,14 @@
     scroll_speed.measure scrolltop
     if scroll_speed.is_fast()
       speeding_attention.gainAttention()
+      # when scrolling sometimes we this suddenly stops without events
+      # or we considered it fast still, while it actually wasn't anymore
+      # so we need to recheck
+      recheckWhetherWereStillScrolling = setTimeout check_scrolling_speed, 50
     else
       speeding_attention.loseAttention()

 $(window).scroll check_scrolling_speed
-setInterval check_scrolling_speed, 50

 Factlink.el.on
   start_fast_scrolling: =>


Fixed to send as html

Fixed to send as html

--- app/models/forgot-password-model.coffee
+++ app/models/forgot-password-model.coffee
@@ -43,6 +43,7 @@
           email
           'Reset Password'
           body
+          {'Content-Type': 'text/html'}
           callback
         )


Fix bar hiding logic

Fix bar hiding logic

--- app/scripts/directives/headerbar.coffee
+++ app/scripts/directives/headerbar.coffee
@@ -29,9 +29,10 @@

       _hideBarOnClickOutside = (ev) ->
         _hideBar = (ev, XThresElem, YThresElem, obj) ->
-          Ythreshold = Math.abs($(YThresElem).position().top) + $(YThresElem).height() - $($window).scrollTop()
+          YUpperThreshold = Math.abs($(YThresElem).position().top) - $($window).scrollTop()
+          YLowerThreshold = Math.abs($(YThresElem).position().top) + $(YThresElem).height() - $($window).scrollTop()
           Xthreshold = Math.abs($(XThresElem).position().left)
-          if ev.y > Ythreshold or ev.x < Xthreshold
+          if ev.y > YLowerThreshold or ev.y < YUpperThreshold and ev.x < Xthreshold
             scope.model[obj] = false
             scope.hideAlertMessage()
             scope.$apply()


Wrap long lines in /i18n views

Wrap long lines in /i18n views


use simple default path regex

use simple default path regex

--- lib/controllers/project-controller.coffee
+++ lib/controllers/project-controller.coffee
@@ -172,7 +172,7 @@
       if !config.path?
         config.path = @getPathExpression(lineIndex > -1);

-      config.expression = config.expression.replace("(path)", "("+config.path+")");
+      config.expression = config.expression.replace("(path)", "(" + config.path + ")");
       config.expression = config.expression.replace("(line)", "(\\d+)");

     try
@@ -184,16 +184,9 @@
     return null;

   getPathExpression: (hasLine) ->
-    if hasLine
-      if os.platform == "win32"
-        return "(?:[a-z]:\\\\|\\\\)?[\\w\\.\\-]+[\\\\[\\w\\.\\-]+]*";
-      else
-        return "(?:~\\/|\\/?)[\\w\\.\\-]+[\\/[\\w\\.\\-]+]*";
-
     if os.platform == "win32"
-      return "(?:[a-z]:\\\\|\\\\)?(?:[\\w\\.\\-]+\\\\)+[\\w\\.\\-]+";
-
-    return "(?:~\\/|\\/?)(?:[\\w\\.\\-]+\\/)+[\\w\\.\\-]+";
+      return "(?:[a-z]:\\\\|\\\\)?[\\w\\.\\-\\\\]+[.\\\\][\\w\\.\\-\\\\]+";
+    return "(?:~\\/|\\/)?[\\w\\.\\-\\/]+[.\\/][\\w\\.\\-\\/]+";

   editConfiguration: ->
     if (@configurationFile.isFile() and @configurationFile.existsSync())


Wrap boundingRect function and whitelist

--- apps/artwork/client/view-in-room.coffee
+++ apps/artwork/client/view-in-room.coffee
@@ -41,13 +41,16 @@
   injectImage: ->
     @$placeholder.add(@$artwork).attr('src', @$img.attr 'src')
     # Position exactly where original image was
-    @$artwork.css @$img[0].getBoundingClientRect()
+    @$artwork.css @getRect(@$img)
+
+  getRect: ($el) ->
+    _.pick($el[0].getBoundingClientRect(), 'height', 'width', 'top', 'right', 'bottom', 'left')

   transitionIn: ->
     @$el.attr 'data-state', 'in'
     @$img.css visibility: 'hidden'
     @$artwork.
-      css(@$placeholder[0].getBoundingClientRect()).
+      css(@getRect(@$placeholder)).
       one($.support.transition.end, =>
         @$artwork.addClass('is-notransition')
       ).emulateTransitionEnd(750)
@@ -56,7 +59,7 @@
     @$el.attr 'data-state', 'out'
     @$artwork.
       removeClass('is-notransition').
-      css @$img[0].getBoundingClientRect()
+      css @getRect(@$img)

   scalePlaceholder: ->
     @$placeholder.css
@@ -69,7 +72,7 @@
     @$room.css transform: "scale(#{@roomScalingFactor()})"

   scaleArtwork: ->
-    @$artwork.css @$placeholder[0].getBoundingClientRect()
+    @$artwork.css @getRect(@$placeholder)

   # Called on the throttled window resize event
   scale: =>
@@ -79,9 +82,9 @@

   roomScalingFactor: ->
     roomRatio       = @$room.width() / @$room.height()
-    viewportRatio   = @$window.width() / @$window.height()
+    viewportRatio   = @$el.width() / @$el.height()
     direction       = if viewportRatio > roomRatio then 'width' else 'height'
-    factor          = @$window[direction]() / @$room[direction]()
+    factor          = @$el[direction]() / @$room[direction]()
     Math.ceil(factor * 100) / 100

   artworkScalingFactor: ->