Add extendable admin navi

Add extendable admin navi

--- modules/gui/lib/widgets/pages/Content.coffee
+++ modules/gui/lib/widgets/pages/Content.coffee
@@ -1,93 +1,91 @@

 module.exports =
-
+
     class ContentContext extends floyd.gui.ViewContext
-
+
         configure: (config)->
-
+
             super new floyd.Config
-
+
                 data:
                     adminnavi:
                         parent: '#header'
-
+
             , config
-
-
-
+
+
+
         ##
         ##
         wire: (done)->
             super (err)=>
                 done(err)
-
+
                 #console.log @identity.login(), floyd.system.platform, location.pathname
-
-                if @identity.login()
-
-                    path = location.pathname.substr(1).split('/')
+
+                if @identity.login()
+
+                    path = location.pathname.substr(1).split('/')
                     path.pop()
-
+
                     origin = @data.find 'origin'
-
+
                     @lookup origin, @identity, (err, ctx)=>
                         return done(err) if err
-
+
                         $(@data.adminnavi.parent).append ul = $('
')
-
-                        if @identity.hasRole ['admin', 'editor']
-
-                            ul.append $('
bearbeiten
').click ()=>
-
-                                @_openEditor ctx
-
+                        _mklink = (roles, id, handler)=>
+                            return if roles.length > 0 && !@identity.hasRole roles
+
+                            ul.append $('
'+(@data.adminnavi[id]||id)+'
').click (e)=>
+                                handler(e)
                                 return false
-
-                        ul.append $('
logout
').click ()=>
-
-                            @_getAuthManager().logout (err)=> location.reload()
-
-                            return false
-
-
-
+
+                        @_makeAdminNavi ctx, _mklink, done

         ##
         ##
         ##
-        _openEditor: (ctx)->
-
+        _makeAdminNavi: (ctx, _mklink, done)->
+            _mklink ['editor'], 'edit', ()=> @_openEditor 'File', ctx
+            _mklink [], 'logout', ()=> @_getAuthManager().logout (err)=> location.reload()
+
+            done()
+
+        ##
+        ##
+        ##
+        _openEditor: (type, ctx)->
+
             ##
-            ctx.readFile (err, data)=>
-
+            ctx['read'+type] (err, data)=>
+
                 return @logger.error(err) if err
-
+
                 ##
-                floyd.tools.gui.popup @,
-
+                floyd.tools.gui.popup @,
+
                     id: 'editor'
-
+
                     data:
                        close: false
-
+
                     buttons:
                         content: ->
                             button class: 'cancel', 'Abbrechen'
                             button class: 'save', 'Speichern'
-
-                , (err, popup)=>
-
+
+                , (err, popup)=>
+
                     popup.append textarea = $('<textarea/>').val(data)
-
+
                     popup.on 'cancel', -> location.reload()
-
-                    popup.on 'save', (e)=>
-
-                        ctx.writeFile textarea.val(), (err)=>
-
-                            if err
-                                alert(err.message)
-
+
+                    popup.on 'save', (e)=>
+
+                        ctx['write'+type] textarea.val(), (err)=>
+
+                            if err
+                                alert(err.message)
+
                             else location.reload()
-
-


Added `chya` support

Added `chya` support

--- src/coffee/wanakana.coffee
+++ src/coffee/wanakana.coffee
@@ -472,6 +472,10 @@
   cyu: 'ちゅ'
   cye: 'ちぇ'
   cyo: 'ちょ'
+  chya: 'ちゃ'
+  chyu: 'ちゅ'
+  chye: 'ちぇ'
+  chyo: 'ちょ'
   tsa: 'つぁ'
   tsi: 'つぃ'
   tse: 'つぇ'


refresh is more complicated than I thought

--- js/app.coffee
+++ js/app.coffee
@@ -239,17 +239,21 @@
       display "dashboard",
         builds_table: 'project'

-  loadEditOrg: (username, refresh) =>
+  loadEditOrg: (username, refresh, callback) =>
     if refresh
       @org_has_been_loaded(false)
       @org().clean() if @org()
       @org(null)

-    $.getJSON "/api/v1/organization/#{username}/settings", (data) =>
-      @org().clean() if @org()
-      @org(new CI.inner.Org data)
-      @org_has_been_loaded(true)
-      mixpanel.track("View Org", {"username": username})
+    if refresh or !@org() or (@org().name() isnt username)
+      $.getJSON "/api/v1/organization/#{username}/settings", (data) =>
+        @org().clean() if @org()
+        @org(new CI.inner.Org data)
+        @org_has_been_loaded(true)
+        mixpanel.track("View Org", {"username": username})
+        if callback? then callback()
+    else
+      if callback? then callback()

   loadEditOrgPage: (username, subpage) =>
     @loadEditOrg(username, false)


Only shrink one xp vm

Only shrink one xp vm

--- src/commands/shrink.coffee
+++ src/commands/shrink.coffee
@@ -4,4 +4,10 @@
   .command('shrink [names]')
   .description('shrink disk usage for virtual machines if archive is present')
   .action (names, command) ->
-    cli.fail cli.find(names, 'ovaed', 'archived').found().all (vm) -> vm.unova()+    cli.fail cli.find(names, 'ovaed', 'archived').found()
+      .then (vms) ->
+        # Pull out duplicate XP vms because they share an OVA.
+        xps = (vm for vm in vms when vm.os is 'WinXP')
+        rest = (vm for vm in vms when vm.os isnt 'WinXP')
+        rest.push xps[0] if xps.length > 0
+        cli.dsl(rest).all (vm) -> vm.unova()


Server: better description

Server: better description

--- components/Server.coffee
+++ components/Server.coffee
@@ -2,7 +2,7 @@
 express = require 'express'

 class Server extends noflo.Component
-  description: ""
+  description: "A basic express.js server creation component"

   constructor: ->
     @inPorts =


updating generator index

updating generator index

--- lib/generators/luca/application/templates/javascripts/index.js.coffee
+++ lib/generators/luca/application/templates/javascripts/index.js.coffee
@@ -11,4 +11,4 @@
 #= require_self

 <%= javascript_namespace %>.onReady ->
-  (window.<%= javascript_namespace %>App = new <%= javascript_namespace %>Application()).boot();
+  (window.<%= javascript_namespace %>App = new <%= javascript_namespace %>.Application()).boot();


Throw an error when attempting to interact with elements from a different page.

Also improves documentation of Page.

--- application/chrome/content/wesabe/dom/Page.coffee
+++ application/chrome/content/wesabe/dom/Page.coffee
@@ -1,7 +1,3 @@
-#
-# Provides a wrapper around an +HTMLDocument+ to simplify interaction with it.
-#
-
 {Pathway} = require 'xpath'
 Colorizer = require 'util/Colorizer'
 inspect   = require 'util/inspect'
@@ -14,9 +10,7 @@
 privacy   = require 'util/privacy'
 {tryCatch, tryThrow} = require 'util/try'

-# ==== Types (shortcuts for use in this file)
-# Xpath:: <String, Array[String], Pathway, Pathset>
-#
+# Internal: Event name to internal type mapping.
 EVENT_TYPE_MAP =
   click:     'MouseEvents'
   mousedown: 'MouseEvents'
@@ -32,83 +26,153 @@
   blur:      'UIEvents'
   focusout:  'UIEvents'

+# Internal: Base error class for errors raised in Page.
+class PageError
+  constructor: (@page, @message) ->
+
+  raise: ->
+    error = new Error @message
+    error.name = @constructor.name
+    error.page = @page
+    throw error
+
+  @raise: (page, message) ->
+    (new @ page, message).raise()
+
+# Internal: Specialized error for when an attempt to
+# interact with elements from another page happens.
+class DocumentMismatchError extends PageError
+  # Internal: Asserts that element belongs to document.
+  #
+  # node - Any Node whose ownership we'd like to verify.
+  # document - Any Document we'd like to verify as the owner.
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError if element's owner isn't document.
+  @assert: (page, node) ->
+    if node.ownerDocument isnt page.document
+      @raise page, node
+
+  constructor: (page, @node) ->
+    super page, "you're attempting to use a node (#{inspect @node}) that doesn't belong to this page"
+
+# Internal: Specialized error for when we were unable to find a node.
+class NodeNotFoundError extends PageError
+  constructor: (page, @xpath) ->
+    super page, "expected to find a node matching #{inspect @xpath}, but none were found"
+
+# Internal: Specialized error for when we're looking for an element's containing form.
+class MissingFormError extends PageError
+  constructor: (page, @xpath, @document) ->
+    super page, "expected node matching #{inspect @xpath} to be contained by a <form>"
+
+
+# Public: Provides a wrapper around an +HTMLDocument+ to simplify interaction with it.
+#
+# Examples
+#
+#   # click the first link on a page
+#   page.click '//a'
+#
+#   # fill in a search field
+#   page.fill '//input[@name="q"]', "doctor who"
 class Page
   constructor: (@document) ->

-  #
-  # Returns the title of this page.
-  #
+  # Public: Gets the String page title, tainted.
   @::__defineGetter__ 'title', ->
     privacy.taint @document.title

+  # Public: Sets the String page title.
   @::__defineSetter__ 'title', (title) ->
     @document.title = privacy.untaint title

-  #
-  # Returns the window name for this page.
-  #
+  # Public: Gets the tainted String page name, tainted.
   @::__defineGetter__ 'name', ->
     privacy.taint @defaultView.name

-  #
-  # Returns the +Window+ associated with this page.
-  #
+  # Public: Gets this page's Window, tainted.
   @::__defineGetter__ 'defaultView', ->
     privacy.taint @document.defaultView

-  #
-  # Returns the URL of this page.
-  #
+  # Public: Gets this page's String URL, tainted.
   @::__defineGetter__ 'url', ->
     privacy.taint @document.URL

-  #
-  # Returns true if this page is in a frame, false otherwise.
-  #
+  # Public: Gets this page's HTMLBodyElement, tainted.
+  @::__defineGetter__ 'body', ->
+    privacy.taint @document.body
+
+  # Public: Determines whether this page is framed or not.
+  #
+  # Returns a Boolean that's true if this page is framed.
   @::__defineGetter__ 'framed', ->
     if frameElement = @defaultView.frameElement
       return frameElement.tagName?.toLowerCase() isnt 'browser'
     else
       return false

-  #
-  # Returns the top-most +Page+, the one at the root of the frame hierarchy.
-  #
+  # Public: Gets the Page at the root of the frame hierarchy.
+  #
+  # Returns a Page that is not contained by any other pages.
   @::__defineGetter__ 'topPage', ->
     @constructor.wrap @defaultView.top.document

-  #
-  # Returns an array of +Page+ objects wrapping all the frames contained in this +Page+.
-  #
+  # Public: Gets all Pages contained in this Page's frames.
+  #
+  # Return an Array containing Pages.
   @::__defineGetter__ 'framePages', ->
     @constructor.wrap frame.document for frame in @defaultView.frames

-  #
-  # Finds the first node matching +xpathOrNode+ with optional
-  # +scope+ when it is an xpath, returns it when it's a node.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to look for.
-  # scope<HTMLElement>:: The element to scope the search to.
-  #
-  # ==== Returns
-  # HTMLElement, null:: The found element, if one was found.
-  #
-  # @public
-  #
+  # Public: Finds the first matching node. This is like select(), but
+  # returns the first matching node rather than all matching nodes.
+  #
+  # xpathOrNode - If it's a Node, just returns it. Otherwise it should
+  #               be a String xpath.
+  # scope - An Element to restrict the xpath search to, ignored if xpathOrNode
+  #         is a Node.
+  #
+  # Examples
+  #
+  #   # finds the first link on the page
+  #   page.find '//a'
+  #
+  #   # nodes passed in will simply be returned.
+  #   link = page.find '//a'
+  #   (page.find link) is link
+  #   # => true
+  #
+  # Returns a tainted Node.
+  # Raises DocumentMismatchError given a node not belonging to this page.
   find: (xpathOrNode, scope) ->
-    return xpathOrNode if xpathOrNode?.nodeType
+    if xpathOrNode?.nodeType
+      DocumentMismatchError.assert @, xpathOrNode
+      return xpathOrNode
+
     xpath = Pathway.from(xpathOrNode)
     return xpath.first(@document, scope)

-  #
-  # Shorthand for finding an element by id, returns null if not found.
-  #
+  # Public: Finds an Element by id.
+  #
+  # id - A String matching a page element's id attribute.
+  #
+  # Examples
+  #
+  #   page.click page.byId('next-button')
+  #
+  # Returns a tainted Element or null if no matching Element was found.
   byId: (id) ->
-    privacy.taint @document.getElementById(id)
-
-  #
-  # Find a link either by id, href, content, or using a matcher function.
+    privacy.taint @document.getElementById(privacy.untaint id)
+
+  # Public: Finds a matching anchor on this page.
+  #
+  # matcher - If a String, matches links whose id or href matches exactly
+  #           or whose text content contains matcher.
+  #           If a Function, it will be called with each link on the page
+  #           until it returns true, which will cause that link to match.
+  # scope - An Element to restrict the link search to.
+  #
+  # Examples
   #
   #   # matches <a id="formSubmitButton">
   #   page.link 'formSubmitButton'
@@ -120,7 +184,11 @@
   #   # matches the first link whose content text is longer than 1000 characters
   #   page.link (a) -> page.text(a).length > 1000
   #
+  # Returns an HTMLAnchorElement matching matcher and contained by scope, or null
+  # if no link on the page matches.
   link: (matcher, scope) ->
+    matcher = privacy.untaint matcher
+
     if type.isString matcher
       escaped = matcher.replace /"/, '\\"'
       @find """//a[@id="#{escaped}" or @href="#{escaped}" or contains(string(.), "#{escaped}")]""", scope
@@ -131,64 +199,78 @@

       return null

-  #
-  # Finds all nodes matching +xpathOrNode+ with optional
-  # +scope+ when it is an xpath, returns it when it's a node.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to look for.
-  # scope<HTMLElement>:: The element to scope the search to.
-  #
-  # ==== Returns
-  # tainted(Array[HTMLElement]):: The found elements.
-  #
-  # @public
-  #
+  # Public: Finds matching nodes on the page. This is much like find(), but returns
+  # all matching nodes instead of a single node.
+  #
+  # xpathOrNode - If it's a Node, returns it wrapped in an Array.
+  #               Otherwise it should be a String xpath.
+  # scope - An Element to restrict the search to.
+  #
+  # Examples
+  #
+  #   # finds all links on the page
+  #   page.select '//a'
+  #
+  #   # gets a string containing all the text strings in the <body>
+  #   (page.select '//text()', page.body).join('')
+  #
+  # Returns an Array of tainted Nodes found by xpath.
+  # Raises DocumentMismatchError given a node not belonging to this page.
   select: (xpathOrNode, scope) ->
-    return xpathOrNode if xpathOrNode?.nodeType
+    if xpathOrNode?.nodeType
+      DocumentMismatchError.assert @, xpathOrNode
+      return [xpathOrNode]
+
     xpath = Pathway.from(xpathOrNode)
-    return xpath.select(@document, scope and @find(scope))
-
-  #
-  # Finds the first node matching +xpathOrNode+ with optional
-  # +scope+ when it is an xpath, returns it when it's a node. If nothing
-  # is found an exception is thrown.
-  #
-  # ==== Parameters
-  # xpathOrNode<Xpath, HTMLElement>:: The thing to look for.
-  # scope<HTMLElement>:: The element to scope the search to.
-  #
-  # ==== Returns
-  # tainted(HTMLElement):: The found element.
-  #
-  # ==== Raises
-  # Error:: When no element is found.
-  #
-  # @public
-  #
+    return xpath.select @document, scope and @find(scope)
+
+  # Public: Finds the first matching node, throwing an error
+  # if there is no such matching node. Use this when you want to blow up
+  # when the node cannot be found.
+  #
+  # xpathOrNode - If it's a Node, just returns it. Otherwise it should
+  #               be a String xpath.
+  # scope - An Element to restrict the xpath search to, ignored if xpathOrNode
+  #         is a Node.
+  #
+  # Examples
+  #
+  #   # raises NodeNotFoundError because the xpath condition can never be satisfied
+  #   page.findStrict '//*[@id!=@id]'
+  #
+  #   # just like page.find() when there's at least one link on the page
+  #   link = page.findStrict '//a'
+  #
+  # Returns a tainted Node.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   findStrict: (xpathOrNode, scope) ->
-    result = @find xpathOrNode, scope
-    throw new Error "No element found matching #{inspect xpathOrNode}" unless result
-    return result
-
-  #
-  # Fills the given node/xpath endpoint with the given value.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to fill.
-  # valueOrXpathOrNode<Xpath, HTMLElement>::
-  #   The value to set the node to if it's a string, or the element whose
-  #   value to use otherwise.
-  #
-  # ==== Raises
-  # Error:: When the element can't be found.
-  #
-  # ==== Notes
-  # The value assigned is truncated according to the maxlength property,
-  # if present. This also triggers the +change+ event on the element it finds.
-  #
-  # @public
-  #
+    if result = @find xpathOrNode, scope
+      return result
+
+    NodeNotFoundError.raise @, xpathOrNode
+
+  # Public: Fills an element with a given value. Note that the value will
+  # be truncated according to any maxlength property found on the Element.
+  #
+  # xpathOrNode - If it's an Element, uses it as the Element to fill.
+  #               Otherwise it should be a String xpath.
+  # valueOrXpathOrNode - If it's a String, the value to use when filling
+  #                      the Element. Otherwise it should be an Element
+  #                      whose value we use to fill the Element.
+  #
+  # Examples
+  #
+  #   # fill in the search field
+  #   page.fill '//input[@name="q"]', "angry birds"
+  #
+  #   # select an option in a dropdown
+  #   colorSelect = page.findStrict '//select[@name="color"]'
+  #   page.fill colorSelect, colorSelect.options[1]
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   fill: (xpathOrNode, valueOrXpathOrNode) ->
     tryThrow 'Page.fill', (log) =>
       element = privacy.untaint @findStrict(xpathOrNode)
@@ -204,9 +286,9 @@

       log.radioactive 'value=', value

-      maxlength = element.getAttribute("maxlength")
+      maxlength = element.getAttribute "maxlength"
       if value and maxlength
-        maxlength = number.parse(maxlength)
+        maxlength = number.parse maxlength
         if maxlength
           log.warn "Truncating value to ", maxlength, " characters"
           value = value[0...maxlength]
@@ -240,20 +322,20 @@
       @fireEvent element, 'focusout'
       @fireEvent element, 'blur'

-  #
-  # Clicks the given node/xpath endpoint.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to click.
-  #
-  # ==== Raises
-  # Error:: When the element can't be found.
-  #
-  # ==== Notes
-  # Triggers the events +mousedown+, +click+, then +mouseup+.
-  #
-  # @public
-  #
+  # Public: Clicks the given element by firing mousedown, click,
+  # and mouseup events.
+  #
+  # xpathOrNode - If it's an Element, uses it as the Element to click.
+  #               Otherwise it should be a String xpath.
+  #
+  # Examples
+  #
+  #   # clicks a logoff link
+  #   page.click page.link("Log off")
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   click: (xpathOrNode) ->
     tryThrow 'Page.click', (log) =>
       element = @findStrict xpathOrNode
@@ -262,75 +344,90 @@
       @fireEvent element, 'click'
       @fireEvent element, 'mouseup'

-  #
-  # Checks the element given by +xpathOrNode+.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to check.
-  #
-  # ==== Raises
-  # Error:: When the element can't be found.
-  #
-  # @public
-  #
+  # Public: Checks an element.
+  #
+  # xpathOrNode - If it's an Element, uses it as the Element to check.
+  #               Otherwise it should be a String xpath.
+  #
+  # Examples
+  #
+  #   # checks all checkboxes on a page
+  #   page.check '//input[@type="checkbox"]'
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   check: (xpathOrNode) ->
     tryThrow 'Page.check', (log) =>
       element = @findStrict xpathOrNode
       log.info 'element=', element
       privacy.untaint(element).checked = true
-
-  #
-  # Unchecks the element given by +xpathOrNode+.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to uncheck.
-  #
-  # ==== Raises
-  # Error:: When the element can't be found.
-  #
-  # @public
-  #
+      @fireEvent element, 'change'
+
+  # Public: Unchecks an element.
+  #
+  # xpathOrNode - If it's an Element, uses it as the Element to uncheck.
+  #               Otherwise it should be a String xpath.
+  #
+  # Examples
+  #
+  #   # unchecks all checkboxes on a page
+  #   page.uncheck '//input[@type="checkbox"]'
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   uncheck: (xpathOrNode) ->
     tryThrow 'Page.uncheck', (log) =>
       element = @findStrict xpathOrNode
       log.info('element=', element)
       privacy.untaint(element).checked = false
-
-  #
-  # Simulates submitting a form.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to uncheck.
-  #
-  # ==== Raises
-  # Error::
-  #   When the element can't be found or when it is not a form
-  #   or contained by a form.
-  #
-  # ==== Notes
-  # The found element can be either a form or a descendent of a form.
-  #
-  # @public
-  #
+      @fireEvent element, 'change'
+
+  # Public: Simulates submitting a form.
+  #
+  # xpathOrNode - If it's an HTMLFormElement, uses it as the HTMLFormElement to submit.
+  #               If it's any other Element, searches through its ancestors to find an HTMLFormElement.
+  #               Otherwise it should be a String xpath.
+  #
+  # Examples
+  #
+  #   # submits a form by name
+  #   page.submit '//form[@name="quickreplyform"]'
+  #
+  #   # submits the form containing the search field
+  #   page.submit '//input[@name="q"]'
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   submit: (xpathOrNode) ->
     tryThrow 'Page.submit', (log) =>
       element = @findStrict xpathOrNode
       log.info 'element=', element
       # find the containing form
+      element = privacy.untaint element
       element = element.parentNode while element and element.tagName.toLowerCase() isnt 'form'
-      throw new Error 'No form found wrapping element! Cannot submit' unless element
+
+      unless element
+        MissingFormError.raise @, xpathOrNode

       @fireEvent element, 'submit'

-  #
-  # Fires an event on the given node/xpath endpoint.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to fire the event on.
-  # type<String>:: The name of the event to fire (e.g. 'click').
-  #
-  # @public
-  #
+  # Internal: Fires an event on the given node/xpath endpoint.
+  #
+  # xpathOrNode - If it's an Element, uses it as the event's target Element.
+  #               Otherwise it should be a String xpath.
+  # type - The name of the event to fire (e.g. 'click').
+  #
+  # Examples
+  #
+  #   # fire the keypress event with the letter 'a'
+  #   @fireEvent input, 'keypress', charCode: 'a'
+  #
+  # Returns nothing.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   fireEvent: (xpathOrNode, eventType, args...) ->
     options = if args.length is 1 and typeof args[args.length-1] is 'object' then args.pop() else {}
     element = privacy.untaint(@findStrict xpathOrNode)
@@ -354,17 +451,27 @@

     element.dispatchEvent(event)

-  #
-  # Determines whether the given node/xpath endpoint is visible.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to check for visibility.
-  #
-  # ==== Notes
-  # Returns +true+ when given a text node.
-  #
-  # @public
-  #
+  # Public: Determines whether a node is visible.
+  #
+  # xpathOrNode - If it's a Node, uses it as the Node to check for visibility.
+  #               Otherwise it should be a String xpath.
+  #
+  # Examples
+  #
+  #   page.visible '//imnotonthispage'
+  #   # => false
+  #
+  #   page.visible '//div[contains(@style, "display:none")]'
+  #   # => false
+  #
+  #   page.visible '//body'
+  #   # => true
+  #
+  #   page.visible '//div[@class="this-class-hides-things"]//a'
+  #   # => false
+  #
+  # Returns a Boolean indicating whether the Node is visible.
+  # Raises DocumentMismatchError given a node not belonging to this page.
   visible: (xpathOrNode) ->
     element = privacy.untaint @find(xpathOrNode)

@@ -383,52 +490,40 @@
     # must be visible
     return true

-  #
-  # Determines whether the given node/xpath endpoint is visible.
-  #
-  # ==== Parameters
-  # xpathOrNode<HTMLElement, Xpath>:: The thing to check for existence.
-  # scope<HTMLElement>:: The element to scope the search to.
-  #
-  # @public
-  #
+  # Pubilc: Determines whether a node is present.
+  #
+  # xpathOrNode - If it's a Node, it's automatically present.
+  #               Otherwise it should be a String xpath.
+  #
+  # Returns a Boolean indicating whether the Node is present.
+  # Raises DocumentMismatchError given a node not belonging to this page.
   present: (xpathOrNode, scope) ->
     !!(@find xpathOrNode, scope)

-  #
-  # Generates a percentage match between the page and the xpaths based on
+  # Public: Generates a percentage match between the page and the xpaths based on
   # how many of the elements are present on the page.
   #
-  # ==== Parameters
-  # xpaths<Array[String, Pathway]>:: The xpaths to look for.
-  #
-  # ==== Returns
-  # Number:: A number between 0 and 1 representing a percentage match.
-  #
-  # @public
-  #
+  # xpaths - An Array of xpaths to search for.
+  #
+  # Returns a Number between 0 and 1 representing a percentage match.
   match: (xpaths) ->
     match = 0
     for xpath in xpaths
-      xpath = wesabe.xpath.from(xpath)
+      xpath = Pathway.from xpath
       match++ if @present xpath

     return match / xpaths.length

-  #
-  # Gets all the cells associated with the given node. For a <th> or <td> node,
+  # Public: Gets all the cells associated with the given node. For a <th> or <td> node,
   # that's all the <td> elements in the same column. For a <tr> node, that's
   # all the <td> elements in the same row.
   #
-  # @param xpathOrNode [HTMLElement, Xpath]
-  #   The thing to get related cells for.
-  #
-  # @return [tainted(Array[HTMLElement]), null]
-  #   The cells related to +xpathOrNode+, or +null+ if the found node
-  #   is not of a type that has related cells.
-  #
-  # @public
-  #
+  # xpathOrNode - The <td>, <tr> or xpath String to use.
+  #
+  # Returns an Array of HTMLTableCellElements in the row given by a <tr> or in the column
+  # given by a <td> or <th>.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   cells: (xpathOrNode) ->
     node = privacy.untaint @findStrict(xpathOrNode)
     name = node.tagName.toLowerCase()
@@ -443,27 +538,26 @@
       else
         null

-  #
-  # Finds the next sibling matching +siblingMatcher+, if given.
-  #
-  # @param xpathOrNode [HTMLElement, Xpath]
-  #   The thing whose next sibling is wanted.
-  # @param siblingMatcher [String, null]
-  #   An Xpath expression to use to match the following sibling (defaults to "*").
-  #
-  # @return [tainted([HTMLElement]), null]
-  #
+  # Public: Finds the next matching sibling.
+  #
+  # xpathOrNode - The Element whose next sibling to get or a String
+  #               xpath to locate said element.
+  # siblingMatcher - An xpath expression to use to match the next sibling (default: '*').
+  #
+  # Returns an Element, tainted.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   next: (xpathOrNode, siblingMatcher='*') ->
     @find "following-sibling::#{siblingMatcher}", @findStrict(xpathOrNode)

-  #
-  # Returns the text content of +xpathOrNode+.
-  #
-  # @param xpathOrNode [HTMLElement, Xpath]
-  #   The thing whose text is wanted.
-  #
-  # @return [tainted([String])]
-  #
+  # Public: Gets the text content of the Node.
+  #
+  # xpathOrNode - If it's a Node, retrieve all its text.
+  #               Otherwise it should be a String xpath.
+  #
+  # Returns a String containing all Node's text, tainted.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   text: (xpathOrNode) ->
     node = @findStrict xpathOrNode
     if node.nodeType is 3
@@ -472,28 +566,37 @@
       textNodes = @select './/text()', node
     privacy.taint (privacy.untaint(node.nodeValue) for node in textNodes).join('')

-  #
-  # Returns +true+ if +xpathOrNode+ has class +className+, +false+ otherwise.
-  #
+  # Public: Determines whether an Element has a given class.
+  #
+  # xpathOrNode - If it's an Element, use it to check the class.
+  #               Otherwise it should be a String xpath.
+  # className - The class to check the Element for.
+  #
+  # Examples
+  #
+  #   page.hasClass page.find('//div[@class="section"]'), 'section'
+  #   # => true
+  #
+  #   page.hasClass page.find('//div[@class="section"]'), 'section'
+  #   # => true
+  #
+  # Returns true if the Element has the given class.
+  # Raises DocumentMismatchError given a node not belonging to this page.
+  # Raises NodeNotFoundError given an xpath for which there are no matches.
   hasClass: (xpathOrNode, className) ->
     " #{@findStrict(xpathOrNode).className} ".indexOf(" #{className} ") > -1

-  #
-  # Goes back one step in the document's window's history.
-  #
-  # @public
-  #
+  # Public: Goes back one step in the page's history.
+  #
+  # Returns nothing.
   back: ->
     @document.defaultView.history.back()

-  #
-  # Inject some javascript into a document by appending a script tag.
-  #
-  # @param script [String]
-  #   JavaScript to be put into the page and executed.
-  #
-  # @public
-  #
+  # Public: Inject some javascript into a document by appending a script tag.
+  #
+  # script - JavaScript to be run in the page.
+  #
+  # Returns nothing.
   inject: (script) ->
     element = @document.createElementNS "http://www.w3.org/1999/xhtml", "script"
     element.setAttribute "type", "text/javascript"
@@ -501,16 +604,11 @@
     element.innerHTML = script
     @document.documentElement.appendChild element

-  #
-  # Dumps the HTML and PNG representations of the page to the profile
+  # Public: Dumps the HTML and PNG representations of the page to the profile
   # under +%PROFILE_DIR%/wesabe-page-dumps+.
   #
-  # @return Options
-  #   :html<String>:: The path of the dumped HTML.
-  #   :png<String>:: The path of the dumped PNG.
-  #
-  # @public
-  #
+  # Returns an Object whose html property is the path of the dumped HTML,
+  # and whose png property is the path of the dumped PNG.
   dump: ->
     tryThrow 'Page.dump', =>
       folder = dir.profile
@@ -543,9 +641,11 @@

     return null

-  # This method replaces all words in text nodes with asterisks unless
-  # the word is a dictionary word (defined in wesabe.util.words.list),
+  # Public: This method replaces all words in text nodes with asterisks unless
+  # the word is a dictionary word (defined in util/words),
   # then dumps the page as usual.
+  #
+  # Returns nothing.
   dumpPrivately: ->
     for text in @select '//body//text()'
       text = privacy.untaint text
@@ -570,12 +670,18 @@

     @dump

+  # Internal: Returns alternate content for util/inspect to use when
+  # generating a string representing this Page.
+  #
+  # Returns an Object.
   contentForInspect: ->
     url: @url, title: @title, name: @name

-  #
-  # Wraps documents for scripts to have easy helper access.
-  #
+  # Public: Wraps documents in Pages.
+  #
+  # document - Either a Page or a Document to be wrapped.
+  #
+  # Returns document if document is a Page, otherwise returns a new Page wrapping document.
   @wrap: (document) ->
     if type.is document, @
       document


many changes to group-page component to update state on change of dropdown element

--- app/assets/javascripts/components/group-page.cjsx
+++ app/assets/javascripts/components/group-page.cjsx
@@ -2,6 +2,7 @@
 GroupBrowser  = require('./group-browser')
 GenericButton   = require('components/buttons/generic-button')
 FirstPageThumbnail = require('./first-page-thumbnail')
+SelectDropdown = require('./select-dropdown')
 API           = require('../lib/api')

 GroupPage = React.createClass
@@ -11,13 +12,33 @@
     group: null

   componentDidMount: ->
-    # make a request to http://localhost:3000/subject_set_first_pages?group_id=578b885e3dfe9ecf50896966
-    # using the incoming query param, and use the results of that query to populate and update the view
+    # make a request to http://localhost:3000/subject_set_first_pages?group_id={{requested group id}}
+    # using the incoming query param, and use the results of that query to define the first page array
+    # as well as the available institutions and playwrights that users can select
+    API.type("subject_set_first_pages").get(group_id: @props.params.group_id).then (first_pages_json) =>
+      all_institutions = []
+      all_playwrights = []

-    API.type("subject_set_first_pages").get(group_id: @props.params.group_id).then (firstPagesJson) =>
+      # iterate through the first pages and update the available institutions and playwright options
+      for first_page_json in first_pages_json
+        institution = first_page_json.meta_data.location
+        playwright = first_page_json.meta_data.written_by
+
+        if institution
+          if institution not in all_institutions
+            all_institutions.push(institution)
+
+        if playwright
+          if playwright not in all_playwrights
+            all_playwrights.push(playwright)
+
       @setState
-        all_first_pages: firstPagesJson
-        current_first_pages: firstPagesJson
+        all_first_pages: first_pages_json
+        all_institutions: all_institutions
+        all_playwrights: all_playwrights
+        first_pages_initialized: 0
+
+      @initializeFirstPageArray()

     API.type("groups").get(@props.params.group_id).then (group) =>
       @setState
@@ -27,29 +48,79 @@
       @setState
         subject_sets: sets

+
+  initializeFirstPageArray: ->
+    # iterate over all first pages to display and build the view
+    first_pages_view = []
+    for page_json, index in @state.all_first_pages
+      first_pages_view.push <FirstPageThumbnail page_json={page_json} key={index} />
+    @setState
+      first_page_array: first_pages_view
+      first_pages_initialized: 1
+
+
+  updateFirstPageArray: ->
+    console.log("updating first page array")
+    selected_institution = $(".collection-institution select").val()
+    selected_playwright  = $(".collection-playwright select").val()
+
+    # define the selection criteria to be used to filter first page objects
+    selection_criteria_options = [
+      {
+        "label": "selected_institution",
+        "value": selected_institution,
+        "key_in_first_page_json": "location"
+      },
+      {
+        "label": "selected_playwright",
+        "value": selected_playwright,
+        "key_in_first_page_json": "written_by"
+      }
+    ]
+
+    # if either selected_institution or selected_playwright are == "",
+    # don't use them as selection criteria
+    selection_criteria = []
+    for selection_criterion in selection_criteria_options
+      if selection_criterion.value?
+        selection_criteria.push(selection_criterion)
+
+    console.log(selection_criteria)
+
+    # iterate over the first page array and retain only those elements that match
+    # the user-specified selection criteria
+    first_pages_to_display = []
+    for first_page in @state.all_first_pages
+      keep_page = 1
+      for selection_criterion in selection_criteria
+        console.log(first_page.meta_data[selection_criterion.key_in_first_page_json], selection_criterion.value)
+        if first_page.meta_data[selection_criterion.key_in_first_page_json] != selection_criterion.value
+          keep_page = 0
+          console.log("not keeping page")
+      if keep_page == 1
+        first_pages_to_display.push(first_page)
+
+    # iterate over the first pages to display and build the view
+    first_pages_view = []
+    for page_json, index in first_pages_to_display
+      first_pages_view.push <FirstPageThumbnail page_json={page_json} key={index} />
+    @setState
+      first_page_array: first_pages_view
+
+
   render: ->
     if ! @state.group?

Loading...

-
     else
-      firstPages = []
-      for page_json, index in @state.current_first_pages
-        firstPages.push <FirstPageThumbnail page_json={page_json} key={index} />
-

James Bundy Era

-

-
-            {firstPages}
-
-

-
+
{ @state.first_page_array }

Praesent vitae lobortis, tempor vitae magna sed interdum nascetur. Eu eget facilisi urna laoreet, risus numquam bibendum, pellentesque odio dictum. Risus quam qui amet pellentesque nonummy, feugiat sodales lacus luctus.

@@ -62,17 +133,15 @@

Range slider goes here

-                  <select>
-                    <option disabled selected value="">Select Institution</option>
-                    <option value="0">Culinary Institute of America</option>
-                  </select>
+                  <SelectDropdown options_array={@state.all_institutions}
+                                  placeholder_text={"Select Institution"}
+                                  onSelect={@updateFirstPageArray} />

-                  <select>
-                    <option disabled selected value="">Select Playwright</option>
-                    <option value="0">Bob Barker</option>
-                  </select>
+                  <SelectDropdown options_array={@state.all_playwrights}
+                                  placeholder_text={"Select Playwright"}
+                                  onSelect={@updateFirstPageArray} />


Removed duplicate callback of $.fn.application.

--- app/assets/javascripts/koi/application/koi.coffee
+++ app/assets/javascripts/koi/application/koi.coffee
@@ -44,7 +44,6 @@
     [ f ] = args

     fStar = -> $( @ ).each ( i, e ) -> $.application.call( e, $(e), f )
-    $ => @livequery fStar
     $ => if live then @livequery fStar else fStar.call @
     @


Remove testing code

Remove testing code

--- app/assets/javascripts/lib/core/ad_manager.coffee
+++ app/assets/javascripts/lib/core/ad_manager.coffee
@@ -4,8 +4,7 @@
     sizes:
       adSense: [155,256]
       leaderboard: [[970,66], [728,90]]
-      # mpu: [[300,250], [300, 600]]
-      mpu: [300, 600]
+      mpu: [[300,250], [300, 600]]
       oneByOne: [1,1]
       sponsorTile: [276,32]
       trafficDriver: [192,380]


OX-721 testing

OX-721 testing

--- app/assets/javascripts/oxalis/model/binary.coffee
+++ app/assets/javascripts/oxalis/model/binary.coffee
@@ -32,7 +32,12 @@
       if layer.typ == @dataLayerName
         dataLayer = layer

-    upperBoundary = [dataLayer.maxCoordinates.width, dataLayer.maxCoordinates.height, dataLayer.maxCoordinates.depth]
+    lowerBoundary = [dataLayer.maxCoordinates.topLeft]
+    upperBoundary = [
+      dataLayer.maxCoordinates.width + dataLayer.maxCoordinates.topLeft[0]
+      dataLayer.maxCoordinates.height + dataLayer.maxCoordinates.topLeft[1]
+      dataLayer.maxCoordinates.depth + dataLayer.maxCoordinates.topLeft[2]
+    ]

     @cube = new Cube(upperBoundary, dataLayer.resolutions.length, @BIT_DEPTH)
     @queue = new PullQueue(@dataSetName, @cube, @dataLayerName, @testData)


Simplify setting lastOpened serialized value

Iterate over the project's edit sessions instead of
the stored paths in the serialized state.

--- lib/fuzzy-finder.coffee
+++ lib/fuzzy-finder.coffee
@@ -18,10 +18,8 @@
       @loadPathsTask = new LoadPathsTask((paths) => @projectPaths = paths)
       @loadPathsTask.start()

-    for path, lastOpened of state
-      session = _.detect rootView.project.getEditSessions(), (editSession) ->
-        editSession.getPath() is path
-      session?.lastOpened = lastOpened
+    for editSession in rootView.project.getEditSessions()
+      editSession.lastOpened = state[editSession.getPath()]

   deactivate: ->
     @loadPathsTask?.terminate()


Remove console.log

Remove console.log

--- static/src/scripts/app/views/TeamsMapView.coffee
+++ static/src/scripts/app/views/TeamsMapView.coffee
@@ -94,7 +94,6 @@
           @infowindow.setContent marker.html
           @infowindow.open @map, marker

-        console.log marker.position
         @markerBounds.extend marker.position

       @map.fitBounds(@markerBounds)


Event List: 每次进来都是第一页

Event List: 每次进来都是第一页

--- src/app/wap/script/mod/information/main.coffee
+++ src/app/wap/script/mod/information/main.coffee
@@ -14,36 +14,36 @@
 	_bodyTpl: require './body.tpl.html'

 	website: null
+	pageSelector: null

 	_afterFadeIn: =>
-		@loadEvents()
+		@loadEvents(1)

 	render: =>
 		super

 		@pageSelector = $('#information-page-index')
-		@loadEvents()
+		@loadEvents(1)

 	prePage: =>
 		page = parseInt(@pageSelector.text())
 		if page - 1 > 0
 			@loadEvents(page - 1)
-			@pageSelector.text(page - 1)

 	nextPage: =>
 		page = parseInt(@pageSelector.text())
 		@loadEvents(page + 1)
-		@pageSelector.text(page + 1)
+

 	loadEvents: (page=1, pageSize=10)=>
 		@website = G.state.get('website')
-		console.log @website
+
+		@pageSelector.text(page)
 		app.ajax.get
 			url: '/api/v1/website/{website_id}/events?page={page}&pageSize={pageSize}'.replace('{website_id}', @website._id).replace('{page}', page).replace('{pageSize}', pageSize)
 			success: (res)=>
-				console.log(res)
 				React.render(
 					React.createElement(EventList, {events: res}),
 					document.getElementById('container-event-list')


#123 Converter cleanup

#123 Converter cleanup

--- converter/convert.coffee
+++ converter/convert.coffee
@@ -6,6 +6,8 @@
 basePath = ''
 inputDir = ''

+
+# File functions
 copyShaderDir = ->
 	from = path.resolve 'converter', 'shaders'
 	to = path.resolve basePath, 'shaders'
@@ -15,8 +17,6 @@
   indata = fs.readFileSync source
   mkdirp.sync path.dirname(target)
   fs.writeFileSync target, indata
-
-sceneFiles = []

 outputFile = (file, data, notinscene) ->
 	unless notinscene
@@ -30,9 +30,36 @@
 		data = JSON.stringify data, null, "\t"
 	fs.writeFileSync file, data

+sceneFiles = []
+
+
+# Converter functions
 convertColor = (hexColor) ->
 	color =	for i in [1..7] by 2
 		Math.round((parseInt hexColor.substr(i,2), 16)/2.55)/100
+
+convertMaterials = (materials) ->
+	for material in materials
+		newmat =
+			uniforms: {}
+		newmat.name = material.MaterialName
+
+		for key, value of material when /Color$/.test key
+			match = key.match(/^(.*)Color$/)
+			newkey = match[1]
+			newmat.uniforms['material'+newkey] = convertColor(value)
+
+		if material.Shininess?
+			newmat.uniforms.materialSpecularPower = material.Shininess
+
+		# TODO: Should be done in a better way
+		if material.TextureEntries?
+			convertTextures(newmat, material.TextureEntries)
+			newmat.shaderRef = 'shaders/texturedLit.shader'
+		else
+			newmat.shaderRef = 'shaders/simpleLit.shader'
+
+		outputFile "materials/#{newmat.name}.mat.json", newmat

 convertTextures = (material, textures) ->
 	material.textures = []
@@ -47,17 +74,16 @@
 		outfile = path.resolve(basePath, newtex.url)
 		copyFile infile, outfile

-
-convertMeshData = (data, entity, compression) ->
+convertMeshData = (data, entity, type, compression) ->
 	newmesh =
 		data: data.MeshData
 		compression: compression
+		type: type
 	outputFile "meshes/#{data.Name}.mesh.json", newmesh

 	_.extend entity.components,
 		meshData:
 			mesh: "meshes/#{data.Name}.mesh"
-

 convertRotation = (matrix) ->
 	#Taken and rewritten from Matrix3x3.toAngles
@@ -75,9 +101,7 @@
 		result[1] = Math.atan2(-matrix[2], matrix[0])
 		result[0] = Math.atan2(-matrix[7], matrix[4])
 		result[2] = Math.asin(matrix[1])
-
 	return result
-

 convertChildren = (children, parent, entities, compression) ->
 	for child in children
@@ -95,14 +119,16 @@
 					materials: ["materials/#{child.Material}.mat"]

 		if child.MeshData?
-			convertMeshData(child, entity, compression)
+			convertMeshData(child, entity, child.Type, compression)

 		if parent
 			entity.components.transform.parentRef = "entities/#{parent.name}.ent"
-
+
 		entities.push entity
 		if child.Children? then convertChildren(child.Children, entity, entities, compression)

+
+# Main function
 convert = (inputFile, outputPath, objectName) ->
 	file = path.resolve(inputFile)
 	basePath = path.resolve(outputPath)
@@ -110,49 +136,19 @@

 	data = fs.readFileSync file
 	oldObject = JSON.parse(data)
+
+	convertMaterials oldObject.Materials

-	for material in oldObject.Materials
-		newmat =
-			uniforms: {}
-		newmat.name = material.MaterialName
-
-		for key, value of material when /Color$/.test key
-			match = key.match(/^(.*)Color$/)
-			newkey = match[1]
-			newmat.uniforms['material'+newkey] = convertColor(value)
-
-		if material.Shininess?
-			newmat.uniforms.materialSpecularPower = material.Shininess
-
-		if material.TextureEntries?
-			convertTextures(newmat, material.TextureEntries)
-			newmat.shaderRef = 'shaders/texturedLit.shader'
-		# TODO: Should be done in a better way
-		else
-			newmat.shaderRef = 'shaders/simpleLit.shader'
-
-		outputFile "materials/#{newmat.name}.mat.json", newmat
-
-	transform = oldObject.Scene.Transform
 	entities = []
 	compression =
 		compressed: oldObject.UseCompression
 		compressedVertsRange: oldObject.CompressedVertsRange
 		compressedColorsRange: oldObject.CompressedColorsRange
 		compressedUnitVectorRange: oldObject.CompressedUnitVectorRange
-
-	rootObject =
-		name: objectName
-		components:
-			transform:
-				translation: transform.Translation
-				scale: transform.Scale
-				rotation: convertRotation transform.Rotation
-
-	entities.push rootObject

-	convertChildren oldObject.Scene.Children, rootObject, entities, compression
+	oldObject.Scene.Name = objectName

+	convertChildren [oldObject.Scene], null, entities, compression

 	for entity in entities
 		outputFile "entities/#{entity.name}.ent.json", entity


Removed unused Collections.resolve() method.

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -25,7 +25,8 @@
     if name then Strings.toTitleCase(name) else name

   # @param {String|Meteor.Collection|Cursor} arg
-  # @returns {Meteor.Collection|null} The underlying collection or null if none is found.
+  # @returns {Meteor.Collection|Cursor} Either a Meteor collection, a cursor, or null if none is
+  #     found.
   get: (arg) ->
     if Types.isString(arg)
       # Collection name.
@@ -56,20 +57,6 @@
       arg
     else
       null
-
-  # @param {String|Meteor.Collection|Cursor} arg
-  # @returns {Meteor.Collection|Cursor} Either a Meteor collection, a cursor, or null if none is
-  #     found.
-  resolve: (arg) ->
-    if Types.isString(arg)
-      # Collection name.
-      return global[arg]
-    else if @isCursor(arg)
-      return arg
-    else if @isCollection(arg)
-      return arg
-    else
-      return null

   # @param obj
   # @returns {Boolean} Whether the given object is a collection.


Troubleshoot: HealthChecker status enum values are changed

--- client/Main/troubleshoot/healthchecker.coffee
+++ client/Main/troubleshoot/healthchecker.coffee
@@ -1,26 +1,25 @@
 class HealthChecker extends KDObject
-  [NOTSTARTED, WAITING, SUCCESS, FAILED] = [1..4]

   constructor: (options={}, @cb) ->
     super options

     @identifier = options.identifier or Date.now()
-    @status = NOTSTARTED
+    @status = "not started"

   run: ->
-    @status = WAITING
+    @status = "waiting"
     @startTime = Date.now()
     @setPingTimeout()
     @cb @finish.bind(this)

   setPingTimeout: ->
     @pingTimeout = setTimeout =>
-      @status = FAILED
       @emit "failed"
+      @status = "down"
     , 5000

   finish: (data)->
-    @status = SUCCESS
+    @status = "success"
     @finishTime = Date.now()
     clearTimeout @pingTimeout
     @pingTimeout = null


:bug: Set student name on student page title instead of logged in user (#177)

--- app/views/teachers/TeacherStudentView.ozar.coffee
+++ app/views/teachers/TeacherStudentView.ozar.coffee
@@ -26,7 +26,7 @@
     'click .level-progress-dot': 'onClickStudentProgressDot'
     'click .nav-link': 'onClickSolutionTab'

-  getMeta: -> { title: "#{$.i18n.t('teacher.student_profile')} | #{me?.broadName()} | #{$.i18n.t('common.ozaria')}" }
+  getMeta: -> { title: "#{$.i18n.t('teacher.student_profile')} | #{$.i18n.t('common.ozaria')}" }

   onClickSolutionTab: (e) ->
     link = $(e.target).closest('a')
@@ -74,6 +74,7 @@
     @selectedCourseId = @courses.first().id if @courses.loaded and @courses.length > 0 and not @selectedCourseId
     if @students.loaded and not @destroyed
       @user = _.find(@students.models, (s)=> s.id is @studentID)
+      @setMeta({ title: "#{$.i18n.t('teacher.student_profile')} | #{@user.broadName()} | #{$.i18n.t('common.ozaria')}" })
       @updateLastPlayedInfo()
       @updateLevelProgressMap()
       @updateLevelDataMap()


Allow premium to drop more files

Allow premium to drop more files

--- apps/channel/client/channel_file_drop_view.coffee
+++ apps/channel/client/channel_file_drop_view.coffee
@@ -17,11 +17,10 @@
     'dragend' : 'clearDrag'
     'dragleave .channel--drop-zone' : 'clearDrag'

-  initialize: (options)->
-    @channel = options.channel
-    @blocks = options.blocks
-    @policy = options.policy
-
+  initialize: ({ @channel, @blocks, @policy })->
+    if mediator.shared.current_user.isPremium()
+      @dropLimit = 100
+
     @renderFileDrop()
     @setupFileDrop()


Finder: loader color changed on finderitems

--- client/ide/filetree/itemviews/fileitem.coffee
+++ client/ide/filetree/itemviews/fileitem.coffee
@@ -14,7 +14,7 @@
         width       : 16
       loaderOptions :
         # color       : @utils.getRandomHex()
-        color       : "#222222"
+        color       : "#71BAA2"
         shape       : "spiral"
         diameter    : 16
         density     : 30


Collaboration: Clear all participants workspaces when session is ended

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -973,6 +973,8 @@
       @setMachineSharingStatus off, (err) ->
         throwError err  if err

+      @clearParticipantsWorkspaces()
+
       socialHelpers.destroyChannel @socialChannel, (err) ->
         throwError err  if err

@@ -980,6 +982,23 @@
         throwError err  if err

       callback()
+
+
+  clearParticipantsWorkspaces: ->
+
+    { users } = @mountedMachine.data
+
+    @listChatParticipants (accounts) =>
+      accounts.forEach (account) =>
+        { nickname } = account.profile
+
+        machineUser  = _.find users, {
+          username  : nickname
+          owner     : no # Don't remove host's workspace
+          approved  : yes
+        }
+
+        @removeWorkspaceSnapshot nickname  if machineUser

   handleCollaborationEndedForHost: ->


Add missing imports to server/usercontext/usercontext.coffee

--- bokehjs/src/coffee/server/usercontext/usercontext.coffee
+++ bokehjs/src/coffee/server/usercontext/usercontext.coffee
@@ -1,4 +1,7 @@
 define [
+    "underscore",
+    "jquery",
+    "bootstrap/collapse",
     "common/base",
     "../serverutils",
     "common/continuum_view",
@@ -8,10 +11,10 @@
     "common/has_parent",
     "common/build_views",
     "common/load_models",
-],  (base, serverutils, continuum_view,
+], (_, $, $1, base, serverutils, continuum_view,
     userdocstemplate, documentationtemplate,
-    wrappertemplate, HasParent, build_views, load_models
-    ) ->
+    wrappertemplate, HasParent, build_views, load_models) ->
+
   exports = {}
   ContinuumView = continuum_view.View
   utility = serverutils.utility


Show pending shifts on dashboard (fixes #84)

--- client/views/pages/dashboard/dashboard.coffee
+++ client/views/pages/dashboard/dashboard.coffee
@@ -184,6 +184,9 @@
 					if team.report && team.report.submitted
 						missingReport = false

+			for user in team.pending when user._id == Meteor.userId()
+				myShift = true
+
 		myShift && (@date >= today && @end >= now || Session.get('showOlder') || missingReport)

 	showOlder: -> Session.get 'showOlder'


Fix the hook for the source compile button

--- src/events.register.coffee
+++ src/events.register.coffee
@@ -19,4 +19,4 @@

 # Register UI controls events
 registerControlEvents = () ->
-  ($ '#compile').click controlsSourceCompile
+  ($ '#source-compile').click controlsSourceCompile


Fix for missing import.

Fix for missing import.

--- packages/retronator-accounts/user/methods.coffee
+++ packages/retronator-accounts/user/methods.coffee
@@ -1,3 +1,4 @@
+AE = Artificial.Everywhere
 RA = Retronator.Accounts

 Meteor.methods


Insert missing '.'

Insert missing '.'

--- src/flux/ecosystems.coffee
+++ src/flux/ecosystems.coffee
@@ -37,7 +37,7 @@
         _.extend( _.first(ecosystem.books), {ecosystemId: "#{ecosystem.id}"} )

     first: ->
-      _first @_ecosystems
+      _.first @_ecosystems

     getBook: (ecosystemId) ->
       _.findWhere(@_ecosystems, id: parseInt(ecosystemId, 10))


Using updated serizalization functions in Elyssa.Node

--- src/foundation/node.coffee
+++ src/foundation/node.coffee
@@ -13,4 +13,9 @@
     draw: ->

-    toString: -> JSON.stringify {@position, @color, @scale, @texture}+    toString: -> JSON.stringify
+      position: @position.toString()
+      color: @color.toString()
+      scale: @scale.toString()
+      texture: @texture.toString()
+


Fixed context reduction in top level expressions

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -265,6 +265,9 @@
   isAtDefinition: ->
     (definition = @_currentDefinition()) and definition.pattern and definition.inside is 0

+  isAtBareDefinition: ->
+    (definition = @_currentDefinition()) and definition.inside is 0
+
   isAtSimpleDefinition: ->
     @isAtDefinition() and @definitionName()

@@ -1009,6 +1012,13 @@
       return malformed to, 'Not an assignable pattern'
     map compileVariableAssignment, (join translationCache, assigns)
   else
+    if ctx.isAtBareDefinition()
+      # Force context reduction
+      inferredType = (substitute ctx.substitution, expression.tea)
+      deferConstraints ctx,
+        ctx.allBoundTypeVariables(),
+        (findFree inferredType),
+        inferredType.constraints
     translatedExpression

 # Pushes the deferring to the parent scope
@@ -5246,12 +5256,13 @@
     empty (: bag)

     fold (fn [with initial over]
-      (: (Fn (Fn item a a) a bag a))
-      (# Fold over with using initial ...))
+      (: (Fn (Fn item a a) a bag a)))

     append (fn [what to]
-      (: (Fn bag bag bag))
-      (# Fold over with using initial ...)))
+      (: (Fn bag bag bag)))
+
+    first (fn [of]
+      (: (Fn bag item))))

   array-bag (instance (Bag (Array a) a)
     empty {}
@@ -5263,11 +5274,11 @@

     append (macro [what to]
       (: (Fn (Array a) (Array a) (Array a)))
-      (Js.method to "concat" {what})))
-
-  first (macro [list]
-    (: (Fn (Array a) a))
-    (Js.method list "first" {}))
+      (Js.method to "concat" {what}))
+
+    first (macro [list]
+      (: (Fn (Array a) a))
+      (Js.method list "first" {})))

   concat (fn [bag-of-bags]
     (fold append empty bag-of-bags))


Remove the condition, the check for usage takes care of it

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -426,13 +426,6 @@
     [op, args...] = inside node
     if op.type is 'operator' and op.token is 'match'
       return trueMacros[op.token] hoistableWheres, args...
-
-  # If this isn't match there should be no hoistableWheres
-  if hoistableWheres.length > 0
-    allNames = []
-    for [_, _, names] in hoistableWheres
-      allNames.push names...
-    throw new Error "#{allNames.join ','} used but not defined yet"

   switch node.type
     when 'comment' then 'null'
@@ -904,7 +897,6 @@
         varNames.push vars...
       {conds, preassigns} = constructCond precs
       [hoistedWheres, furtherHoistable] = hoistWheres hoistableWheres, assigns
-      #log "assigns", printAst assigns
       """#{control} (#{conds}) {
            #{preassigns.concat(assigns).map(compileAssign).join '\n'}
            #{hoistedWheres.map(compileDef).join '\n'}


Only require Raygun if key is set

Only require Raygun if key is set

--- components/inquiry_questionnaire/error.coffee
+++ components/inquiry_questionnaire/error.coffee
@@ -1,9 +1,11 @@
 FlashMessage = require '../flash/index.coffee'
 Errors = require '../form/errors.coffee'
-rg4js = require 'raygun4js'
+sd = require('sharify').data

 module.exports = (error) ->
-  rg4js 'send', error
+  if sd.RAYGUN_KEY
+    rg4js ?= require 'raygun4js'
+    rg4js 'send', error

   parser = new Errors $('<form></form>')


warn in console

warn in console

--- src/utils/logger.coffee
+++ src/utils/logger.coffee
@@ -26,6 +26,6 @@
   verbose: (args...)->
     log(args...) if enabled and verbose
   warn: (args...)->
-    log(args...)
+    console.warn(args...)
   error: error
 }


fix other usages of computation.stop

fix other usages of computation.stop

--- client/ts_client.coffee
+++ client/ts_client.coffee
@@ -35,14 +35,14 @@

 TurkServer.currentRound = UI.emboxValue(currentRound, EJSON.equals)

+# Called to start the monitor with given settings when in experiment
+# Similar to usage in user-status demo
 safeStartMonitor = (threshold, idleOnBlur) ->
-  Deps.autorun ->
-    # Don't try to start the monitor in case the state changed
-    @stop() unless TurkServer.inExperiment()
+  Deps.autorun (c) ->
     try
       settings = {threshold, idleOnBlur}
       UserStatus.startMonitor(settings)
-      @stop()
+      c.stop()
       console.log "Idle monitor started with ", settings

 idleComp = null


changed spawn timer to count instead of score

--- src/lib/States/Play.coffee
+++ src/lib/States/Play.coffee
@@ -53,7 +53,7 @@
             @scoreboard.text = @score.toString()

         @count = @count + 1
-        if (@GameState.numAliens < 2 and Math.random() < 0.03) or (@score % 1000 == 0 and @GameState.numAliens < 4)
+        if (@GameState.numAliens < 2 and Math.random() < 0.03) or (@count % 1000 == 0 and @GameState.numAliens < 4)
             @newAlien()

         for k, v of @GameObjects


fixed update issue when using multiple tile layers

--- bokehjs/src/coffee/renderer/geo/tile_layer.coffee
+++ bokehjs/src/coffee/renderer/geo/tile_layer.coffee
@@ -161,18 +161,24 @@
   get_level_by_extent: (extent, height, width) ->
     x_rs = (extent[2] - extent[0]) / width
     y_rs = (extent[3] - extent[1]) / height
-    res = Math.max(x_rs, y_rs)
+    resolution = Math.max(x_rs, y_rs)
     i = 0
     for r in @resolutions
-      if res > r
-        return i
+      if resolution > r
+        return 0 if i == 0
+        return i - 1 if i > 0
       i += 1

-    # closest = @resolutions.reduce (previous, current) ->
-    #   return current if (Math.abs(current - res) < Math.abs(previous - res))
-    #   return previous
-    #
-    # return @resolutions.indexOf(closest)
+  get_closest_level_by_extent:(extent, height, width) ->
+    x_rs = (extent[2] - extent[0]) / width
+    y_rs = (extent[3] - extent[1]) / height
+    resolution = Math.max(x_rs, y_rs)
+
+    closest = @resolutions.reduce (previous, current) ->
+      return current if (Math.abs(current - resolution) < Math.abs(previous - resolution))
+      return previous
+
+    return @resolutions.indexOf(closest)

   snap_to_zoom: (extent, height, width, level) ->
     desired_res = @resolutions[level]
@@ -240,7 +246,6 @@
     [txmin, tymin] = @meters_to_tile(xmin, ymin, level)
     [txmax, tymax] = @meters_to_tile(xmax, ymax, level)

-
     # add tiles which border
     txmin -= tile_border
     tymin -= tile_border
@@ -251,9 +256,7 @@
     for ty in [tymax..tymin] by -1
       for tx in [txmin..txmax] by 1
         tiles.push([tx, ty, level, @get_tile_meter_bounds(tx, ty, level)])
-
         tiles = @sort_tiles_from_center(tiles, [txmin, tymin, txmax, tymax])
-
     return tiles

   quadkey_to_tile_xyz: (quadKey) ->
@@ -349,6 +352,36 @@
   trace: (msg) ->
     console.warn "TileLayerView :: " + msg.toString()

+  get_extent: () ->
+    return [@x_range.get('start'), @y_range.get('start'), @x_range.get('end'), @y_range.get('end')]
+
+  _doubletap: (e) =>
+    extent = @get_extent()
+    [xmin, ymin, xmax, ymax] = extent
+
+    x_percent = (e.bokeh.plot_x - xmin) / (xmax - xmin)
+    y_percent = (e.bokeh.plot_y - ymin) / (ymax - ymin)
+
+    zoom_level = @tile_provider.get_closest_level_by_extent(extent, @map_frame.get('height'), @map_frame.get('width'))
+
+    if e.srcEvent.shiftKey
+      zoom_level = zoom_level - 1
+    else
+      zoom_level = zoom_level + 1
+
+    new_resolution = @tile_provider.resolutions[zoom_level]
+    new_xrange = new_resolution * @map_frame.get('width')
+    new_yrange = new_resolution * @map_frame.get('height')
+    nxmin = e.bokeh.plot_x - (x_percent * new_xrange)
+    nymin = e.bokeh.plot_y - (y_percent * new_yrange)
+    nxmax = xmin + new_xrange
+    nymax = ymin + new_yrange
+    new_extent = @tile_provider.snap_to_zoom([nxmin, nymin, nxmax, nymax], @map_frame.get('height'), @map_frame.get('width'), zoom_level)
+    @x_range.set('start', new_extent[0])
+    @y_range.set('start', new_extent[1])
+    @x_range.set('end', new_extent[2])
+    @y_range.set('end', new_extent[3])
+
   _create_tile_provider: (provider_type, service_url, tile_size) ->
     return new QUADKEYTileProvider(service_url, tile_size) if(provider_type.toLowerCase() == 'quadkeytileprovider')
     return new TMSTileProvider(service_url, tile_size) if(provider_type.toLowerCase() == 'tmstileprovider')
@@ -357,38 +390,6 @@
   _index_data: () ->
     @_xy_index()

-  get_extent: () ->
-    return [@x_range.get('start'), @y_range.get('start'), @x_range.get('end'), @y_range.get('end')]
-
-  _on_dblclick: (e) =>
-    extent = @get_extent()
-    [xmin, xmax, ymin, ymax] = extent
-    zoom_level = @tile_provider.get_level_by_extent(extent, @map_frame.get('height'), @map_frame.get('width'))
-
-    rect = @map_canvas.canvas.getBoundingClientRect()
-    x = e.clientX + rect.left
-    #TODO: y-dim is not perfect.  zooming doesn't seem completely correct
-    y = e.clientY - rect.top - (@map_frame.get('top') - @map_frame.get('height'))
-    y = @map_frame.get("height") - y
-
-    x_meters = @x_mapper.map_from_target(e.clientX)
-    y_meters = @y_mapper.map_from_target(e.clientY)
-
-    x_offset = Math.abs((@x_range.get('end') - @x_range.get('start')) / 2)
-    y_offset = Math.abs((@y_range.get('end') - @y_range.get('start')) / 2)
-
-    updated_extent =  [x_meters - x_offset, y_meters - y_offset, x_meters + x_offset, y_meters + y_offset]
-
-    if e.shiftKey
-      zoom_level = zoom_level - 1
-    else
-      zoom_level = zoom_level + 1
-
-    new_extent = @tile_provider.snap_to_zoom(updated_extent, @map_frame.get('height'), @map_frame.get('width'), zoom_level)
-    @x_range.set('start', new_extent[0])
-    @y_range.set('start', new_extent[1])
-    @x_range.set('end', new_extent[2])
-    @y_range.set('end', new_extent[3])

   _set_data: () ->
     @tile_provider = @_create_tile_provider(@mget('tile_provider'), @mget('url'), @mget('tile_size'))
@@ -400,13 +401,9 @@
     @x_mapper = this.map_frame.get('x_mappers')['default']
     @y_range = @map_plot.get('y_range')
     @y_mapper = this.map_frame.get('y_mappers')['default']
-    @renderer.plot_view.$el.dblclick(@_on_dblclick)
-
-
+    @renderer.listenTo(@renderer, 'doubletap', @_doubletap)

   _map_data: () ->
-    # @sw = @sdist(@renderer.xmapper, [0], [@tile_provider.tile_size], 'edge', [true])[0]
-    # @sh = @sdist(@renderer.ymapper, [0], [@tile_provider.tile_size], 'edge', [true])[0]
     if not @map_initialized?
       @initial_extent = [-20037508.34, -20037508.34, 20037508.34, 20037508.34 ]
       zoom_level = @tile_provider.get_level_by_extent(@initial_extent, @map_frame.get('height'), @map_frame.get('width'))
@@ -453,6 +450,8 @@
     return tile

   _render: (ctx, indices, {url, image, need_load, sx, sy, sw, sh, angle}) ->
+
+    console.warn("RENDER: " + this.tile_provider.url)

     @_update(abridged=true)

@@ -516,11 +515,12 @@
           @_create_tile(cx, cy, cz, cbounds, true)

   _update: (abridged=false) =>
-
-    if window.stop?
-      window.stop()
-    else if document.execCommand?
-      document.execCommand("Stop", false)
+    console.warn("RENDER: " + this.tile_provider.url)
+
+    # if window.stop?
+    #   window.stop()
+    # else if document.execCommand?
+    #   document.execCommand("Stop", false)

     @tile_provider.update()
     extent = @get_extent()
@@ -573,7 +573,6 @@

   display_defaults: ->
     return _.extend {}, super(), {
-      level: 'glyph'
       tile_provider: "WMTSTileProvider"
       url: "http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png"
       tile_size: 256


[MOI-HMO-005] mostrar/ocultar nodos en el árbol

--- app/assets/javascripts/admin/moi_tree.js.coffee
+++ app/assets/javascripts/admin/moi_tree.js.coffee
@@ -61,6 +61,7 @@
     @setChildren(@rootNeuron)

     @neurons = @tree.nodes(@rootNeuron)
+    @shownNeurons = @neurons

     @drawLinks()
     @drawNeurons()
@@ -69,7 +70,7 @@
   drawNeurons: ->
     self = this
     neuron = @svg.selectAll("g.node")
-                  .data(@neurons)
+                  .data(@shownNeurons)
                   .enter()
                   .append("g")
                   .attr("class", "node")
@@ -78,6 +79,10 @@
                   )
     neuron.append("circle")
           .attr("r", 4.5)
+          .style("fill", (d) ->
+            if d._children then "lightsteelblue" else "#fff"
+          ).on "click", (node) =>
+            @toggleNode(node)

     neuron.append('text')
           .attr('dx', -8)
@@ -89,7 +94,7 @@
             self.showDetails(node, this)

   drawLinks: ->
-    links = @tree.links(@neurons)
+    links = @tree.links(@shownNeurons)
     diagonal = d3.svg.diagonal().projection((d) ->
       [ d.x, d.y ]
     )
@@ -129,7 +134,44 @@
       for child in neuron.children
         @setChildren(child)

-  showDetails: (node) ->
+  hideChildren: (node) ->
+    node.hidden = true
+    return unless node.children
+    node._children = node.children
+    node.children = null
+    for child in node._children
+      @hideChildren(child)
+    null
+
+  showChildren: (node) ->
+    node.hidden = false
+    return unless node._children
+    node.children = node._children
+    node._children = null
+    for child in node.children
+      @showChildren(child)
+    null
+
+  filterShownNeurons: ->
+    @shownNeurons = @neurons.filter (neuron) ->
+      !neuron.hidden
+
+  toggleNode: (node) ->
+    if node.children
+      @hideChildren(node)
+      node.hidden = false
+    else
+      @showChildren(node)
+    @filterShownNeurons()
+
+    d3.select(@selector).html("")
+    @createD3Elements()
+    @drawSVG()
+    @drawLinks()
+    @drawNeurons()
+    @drawWithoutParent()
+
+  showDetails: (node, text) ->
     $popover = $(".popover")
     # format:
     $popover.find(".popover-title").html(node.title)


always show last chars for short hash #478

--- app/assets/javascripts/libs/format_utils.coffee
+++ app/assets/javascripts/libs/format_utils.coffee
@@ -20,7 +20,7 @@

   @formatHash : (id) ->

-    return id.slice(0, 6)
+    return id.slice(-6)

   @formatShortText : (text, maxLength = 100) ->


Added type to backbone message model

Added type to backbone message model

--- app/assets/javascripts/models/message.js.coffee
+++ app/assets/javascripts/models/message.js.coffee
@@ -1,6 +1,7 @@
 class Cloudsdale.Models.Message extends Backbone.Model

   sync: railsRestSync
+  type: 'message'

   initialize: () ->


Redirect to rails 404, when angular cannot find route

--- app/assets/javascripts/sprangular/routes.coffee
+++ app/assets/javascripts/sprangular/routes.coffee
@@ -101,7 +101,9 @@
           Orders.find($route.current.params.number)

     .when '/404',
-      templateUrl: '404.html'
+      template: ""
+      controller: ($window) ->
+        $window.location.href = "/not-found"

     .otherwise
       redirectTo: "/404"


fix mouse2D.y

fix mouse2D.y

--- app/assets/javascripts/static_editors.js.coffee
+++ app/assets/javascripts/static_editors.js.coffee
@@ -40,15 +40,13 @@
   light = new THREE.DirectionalLight( 0xffffff, 3 );
   light

-MYTHREE.global = {}
-MYTHREE.global.voxels = {};
-
-
-
-
-
-
-
+MYTHREE.getCubeGeometry = ->
+  geometry = new THREE.CubeGeometry(1,1,1)
+  i = 0
+  while i < geometry.faces.length
+    geometry.faces[i].color.setHex 0x00ff80
+    i++
+  geometry

 #---組み立て---
@@ -65,7 +63,7 @@
   theta = 45
   isShiftDown = false
   isCtrlDown = false
-  target = new THREE.Vector3( 0, 4, 0 )
+  origin = new THREE.Vector3( 0, 0, 0 )
   normalMatrix = new THREE.Matrix3()
   ROLLOVERED = null

@@ -82,9 +80,8 @@
     mouse2D = new THREE.Vector3( 0, 10000, 0.5 )
     scene.add( new THREE.AmbientLight( 0x606060 ))
     scene.add( MYTHREE.getDirectionalLight() )
-    #axis
-    axisHelper = new THREE.AxisHelper(10);
-    scene.add(axisHelper);
+    axis = new THREE.AxisHelper(10);
+    scene.add(axis);
     renderer = new MYTHREE.getRenderer()
     container.appendChild(renderer.domElement); #<canvas>
     $('canvas').attr('id', 'canvas_id')
@@ -95,20 +92,17 @@

   onDocumentMouseMove = ( event ) ->
     event.preventDefault()
-    #mouse2D.x = ( event.clientX / window.innerWidth ) * 2 - 1
-    #mouse2D.y = - ( event.clientY / window.innerHeight ) * 2 + 1
     offsetY = $("#header").height()
+    #ブラウザの左上からのピクセル位置 = event.clientX,Y
     canvasX = event.clientX
-    canvasY = event.clientY + offsetY
-    mouse2D.x = ( canvasX / MYTHREE.const.W ) * 2 - 1
-    mouse2D.y = - ( canvasY / MYTHREE.const.W ) * 2 + 1
-    #console.log("mouse X, Y", mouse2D.x, mouse2D.y); #-1.0 ~ 1.0
+    canvasY = event.clientY - offsetY
+    mouse2D.x = ( canvasX / MYTHREE.const.W ) * 2 - 1 #-1~+1
+    mouse2D.y = - ( canvasY / MYTHREE.const.H ) * 2 + 1 #-1~+1
     intersects = raycaster.intersectObjects( scene.children )
-    if intersects.length > 0
-      if ( ROLLOVERED )
-        ROLLOVERED.color.setHex( 0x00ff80 );
-      ROLLOVERED = intersects[ 0 ].face;
-      ROLLOVERED.color.setHex( 0xff8000 )
+    #ray hit color
+    # if intersects.length > 0
+    #   ROLLOVERED = getColoredIntersect(intersects)
+    #   ROLLOVERED.color.setHex( 0xff8000 )

   #複数の交差点からfaceがセットされているものを返す
   getFacedIntersect = (intersects) ->
@@ -126,26 +120,17 @@
         #remove voxel from scene
         scene.remove intersect.object  unless intersect.object is plane
       else
-        #add voxel to scene
+        #draw cube
         normalMatrix.getNormalMatrix intersect.object.matrixWorld
         normal = intersect.face.normal.clone()
         normal.applyMatrix3(normalMatrix).normalize()
         position = new THREE.Vector3().addVectors(intersect.point, normal)
-        geometry = new THREE.CubeGeometry(1,1,1)
-        i = 0
-        while i < geometry.faces.length
-          geometry.faces[i].color.setHex 0x00ff80
-          i++
+        geometry = MYTHREE.getCubeGeometry()
         material = new THREE.MeshLambertMaterial(vertexColors: THREE.FaceColors)
         voxel = new THREE.Mesh(geometry, material)
-        voxel.position.x = Math.floor(position.x / 1) * 1 + 0.5
-        voxel.position.y = Math.floor(position.y / 1) * 1 + 0.5
-        voxel.position.z = Math.floor(position.z / 1) * 1 + 0.5
-
-        voxel.position.x = 0
-        voxel.position.y = 0
-        voxel.position.z = 0
-        console.log 'voxel.position', voxel.position
+        voxel.position.x = Math.floor(position.x) + 0.5
+        voxel.position.y = Math.floor(position.y) + 0.5
+        voxel.position.z = Math.floor(position.z) + 0.5
         voxel.matrixAutoUpdate = false
         voxel.updateMatrix()
         scene.add voxel
@@ -176,8 +161,8 @@
     theta += mouse2D.x * 3  if isShiftDown
     camera.position.x = 28 * Math.sin(theta * Math.PI / 360)
     camera.position.z = 28 * Math.cos(theta * Math.PI / 360)
-    camera.lookAt target
-    raycaster = projector.pickingRay(mouse2D.clone(), camera)
+    camera.lookAt origin
+    raycaster = projector.pickingRay(mouse2D.clone(), camera) #ray from 2D to 3D
     renderer.render scene, camera

   #main


jQ-plugin building fixes; options fixes; underscore.js usage; debug; comments; syntax fixes.

--- app/assets/javascripts/ultimate/flash.js.coffee
+++ app/assets/javascripts/ultimate/flash.js.coffee
@@ -15,7 +15,7 @@

 class UltimateFlashWidget

-  @defaults:
+  @defaults =
     constants:
       widgetDataKey: 'ultimate_flash'
     locales:
@@ -42,31 +42,38 @@
       regExpLastWordWithoutDot: /[\wа-яёА-ЯЁ]{3,}$/
       regExpLastWordWithDot: /([\wа-яёА-ЯЁ]{3,})\.$/
       locale: 'en'
+      translations: {}

   jContainer: null
   settings: {}

   constructor: (@jContainer, options = {}) ->
+    # if global compatible I18n
     if I18n? and I18n.locale and I18n.t
+      # can set locale
       options['locale'] ||= I18n.locale
+      # try read localized strings
       if _localesFromI18n = I18n.t 'ultimate_flash'
+        # pointing to default UF locales of language specified in I18n
         _defaultLocales = @constructor.defaults.locales[I18n.locale]
+        # fill it from I18n
         for key, value of _localesFromI18n
           _defaultLocales[_.camelize key] = value
     _locale = options['locale'] or @constructor.defaults.options['locale']
     $.error "Locale [#{_locale}] not exists in UltimateFlash default locales."  unless @constructor.defaults.locales[_locale]
-    @settings = $.extend {}, @constructor.defaults.options, @constructor.defaults.locales[_locale], options
+    @settings = $.extend true, {}, @constructor.defaults.options, translations: @constructor.defaults.locales[_locale], options
     @jContainer.data @constructor.defaults.constants.widgetDataKey, @
     _self = @
     # delegate event for hide on click
-    @jContainer.delegate '.flash:not(:animated)', 'click.ultimate_flash_close', ->
-      _self._hide $ @  if _self.settings.hideOnClick
-      false
+    @jContainer.on 'click.ultimate_flash_close', '.flash:not(:animated)', (event) =>
+      if @settings.hideOnClick
+        @_hide $ event.currentTarget
+        false
     # init flashes from server
-    @jFlashes().each ->
-      jFlash = $ @
-      jFlash.html _self._prepareText jFlash.html()
-      _self._setTimeout jFlash
+    @jFlashes().each (index, flash) =>
+      jFlash = $ flash
+      jFlash.html @_prepareText jFlash.html()
+      @_setTimeout jFlash
     # binding hook ajaxError handler
     @jContainer.ajaxError =>
       @ajaxError arguments  if @settings.showAjaxErrors
@@ -86,18 +93,16 @@
     text

   _hide: (jFlash) ->
-    clearTimeout jFlash.data 'timeout_id'
+    clearTimeout jFlash.data 'timeoutId'
     _self = @
     jFlash.slideUp @settings.slideTime, -> $(@).remove()  if _self.settings.removeOnHide

-  _setTimeout: (jFlash) ->
-    timeoutId = false
-    if @settings.showTime or @settings.showTimePerChar
-      timeoutId = setTimeout =>
+  _setTimeout: (jFlash, timeout = @settings.showTime + jFlash.text().length * @settings.showTimePerChar) ->
+    if timeout
+      jFlash.data 'timeoutId', setTimeout =>
+          jFlash.removeData 'timeoutId'
           @_hide jFlash
-        , @settings.showTime + jFlash.text().length * @settings.showTimePerChar
-      jFlash.data 'timeout_id', timeoutId
-    timeoutId
+        , timeout

   getSettings: ->
     @settings
@@ -139,17 +144,17 @@
       # remove event
       successArgs.shift()
     # arrange arguments
-    if $.isString successArgs[0]
+    if _.isString successArgs[0]
       # from jQuery.ajax().success()
       [data, _textStatus, jqXHR] = successArgs
     else
-      #from. jQuery.ajaxSuccess()
+      # from jQuery.ajaxSuccess()
       [jqXHR, _ajaxSettings, data] = successArgs
     # prevent recall
     return false  if jqXHR.breakFlash
     jqXHR.breakFlash = true
     # detect notice
-    if $.isString data
+    if _.isString data
       # catch plain text message
       data = $.trim data
       return @notice data  if data.length <= @settings.detectPlainTextMaxLength and not $.isHTML data
@@ -164,14 +169,14 @@
    * @return {Boolean}                     статус выполнения показа сообщения
   ###
   ajaxError: ->
-    text = @settings.defaultErrorText
+    text = @settings.translations.defaultErrorText
     errorArgs = []
     if arguments.length
       _next = arguments[0]
-      if $.isString _next
+      if _.isString _next
         text = _next
         _next = arguments[1]  if arguments.length > 1
-      errorArgs = _next  if not $.isString(_next) and _next.length
+      errorArgs = _next  if not _.isString(_next) and _next.length
     if errorArgs.length
       errorArgs = args errorArgs
       errorArgs.shift()  if errorArgs[0].target # remove event
@@ -190,7 +195,7 @@
             # may be parsedJSON is form errors
             if @settings.detectFormErrors is true
               # show message about form with errors
-              return @alert @settings.formFieldsError
+              return @alert @settings.translations.formFieldsError
             else if _.isFunction @settings.detectFormErrors
               # using showFormError as callback
               return @settings.detectFormErrors.apply @, [parsedJSON]
@@ -202,12 +207,17 @@
       if jqXHR.status >= 400 and jqXHR.responseText
         # try detect Rails raise message
         if raiseMatches = jqXHR.responseText.match /<\/h1>\n<pre>(.+?)<\/pre>/
+          cout "replace thrownError = '#{thrownError}' with raiseMatches[1] = '#{raiseMatches[1]}'"
           thrownError = raiseMatches[1]
         else
-          # get short text message as error
-          thrownError = jqXHR.responseText  if jqXHR.responseText.length <= @settings.detectPlainTextMaxLength
-      else
-        thrownError = @settings.defaultThrownError  if $.isString thrownError and not $.isEmptyString thrownError
+          # try detect short text message as error
+          if jqXHR.responseText.length <= @settings.detectPlainTextMaxLength
+            cout "replace thrownError = '#{thrownError}' with jqXHR.responseText = '#{jqXHR.responseText}'"
+            thrownError = jqXHR.responseText
+      else
+        if _.isString thrownError and not $.isEmptyString thrownError
+          cout "replace thrownError = '#{thrownError}' with @settings.translations.defaultThrownError = '#{@settings.translations.defaultThrownError}'"
+          thrownError = @settings.translations.defaultThrownError
       text += ': '  if text
       text += "#{thrownError} [#{jqXHR.status}]"
     return @alert text
@@ -231,30 +241,42 @@
    * updateSettings  .ultimateFlash({Object} options)                       ~ {Object} settings
    * auto            .ultimateFlash('auto', {ArrayOrObject} obj)            ~ {Array} ajFlashes | {Boolean} false
    * ajaxSuccess     .ultimateFlash('ajaxSuccess'[, Arguments successArgs = []])
-   * ajaxError       .ultimateFlash('ajaxError'[, String text = settings.defaultErrorText][, Arguments errorArgs = []])
+   * ajaxError       .ultimateFlash('ajaxError'[, String text = settings.translations.defaultErrorText][, Arguments errorArgs = []])
   ###
   $.fn.ultimateFlash = ->
-    return @  unless @length
+    a = args arguments
+    argsLength = a.length
+    # Shall return the Widget, if have arguments and last argument of the call is a Boolean true.
+    _returnWidget =
+      if argsLength and _.isBoolean a[argsLength - 1]
+        argsLength--
+        a.pop()
+      else
+        false
+    unless @length
+      return if _returnWidget then undefined else @
+    # Get the first
     jContainer = @eq 0
+    # Try to get the Widget-object, controlling everything that happens in our magical container.
     widget = jContainer.data UltimateFlashWidget.defaults.constants.widgetDataKey
-    argsLength = arguments.length
-    _returnWidget = argsLength && arguments[argsLength - 1] == true
-    if widget and widget.jContainer[0] == jContainer[0]
-      a = args arguments
-      if argsLength and typeof a[0] == 'string'
+    if widget and widget.jContainer[0] is jContainer[0]
+      if argsLength and _.isString a[0]
         command = a.shift()
       else
         return widget  if _returnWidget
         return jContainer  unless argsLength
         command = 'updateSettings'
-      if $.isFunction widget[command]
+      if _.isFunction widget[command]
         return widget[command].apply widget, a
       else
         $.error "Command [#{command}] does not exist on jQuery.ultimateFlash()"
     else
-      options = if argsLength then arguments[0] else {}
-      widget = new UltimateFlashWidget jContainer, options
-      jContainer.data UltimateFlashWidget.defaults.constants.widgetDataKey, widget
+      options = if argsLength then a[0] else {}
+      if $.isPlainObject options
+        widget = new UltimateFlashWidget jContainer, options
+        jContainer.data UltimateFlashWidget.defaults.constants.widgetDataKey, widget
+      else
+        $.error "First argument of jQuery.ultimateFlash() must be plain object"
     if _returnWidget then widget else jContainer

 )( jQuery )


ProfileView: remove not used trollSwitch

--- client/Social/Members/ContentDisplays/profileview.coffee
+++ client/Social/Members/ContentDisplays/profileview.coffee
@@ -162,18 +162,6 @@
       @sendMessageLink?.on 'MessageShouldBeSent', ({formOutput, callback}) =>
         @prepareMessage formOutput, callback

-    if KD.checkFlag 'super-admin' and not KD.isMine @memberData
-      @trollSwitch   = new KDCustomHTMLView
-        tagName      : "a"
-        partial      : if @memberData.isExempt then 'Unmark Troll' else 'Mark as Troll'
-        cssClass     : "troll-switch"
-        click        : =>
-          if @memberData.isExempt
-          then mainController.unmarkUserAsTroll @memberData
-          else mainController.markUserAsTroll   @memberData
-    else
-      @trollSwitch = new KDCustomHTMLView
-
   viewAppended:->
     super
     @createExternalProfiles()


Client: Activity: onEditInputKeyDown method to delete message when onEditInput value is empty and pressed backspace on edited

--- client/activity/lib/components/chatlistitem/index.coffee
+++ client/activity/lib/components/chatlistitem/index.coffee
@@ -26,6 +26,7 @@
 ImmutableRenderMixin  = require 'react-immutable-render-mixin'
 MessageLink           = require 'activity/components/publicchannelmessagelink'
 EmbedBox              = require 'activity/components/embedbox'
+KeyboardKeys          = require 'app/util/keyboardKeys'

 module.exports = class ChatListItem extends React.Component

@@ -247,6 +248,8 @@

   updateMessage: ->

+    return @deletePostButtonHandler()  if @state.isDeleting
+
     name  = @props.channelName
     value = @state.editInputValue
     messageId = @props.message.get '_id'
@@ -265,6 +268,8 @@

   cancelEdit: ->

+    return @closeDeletePostModal()  if @state.isDeleting
+
     messageId = @props.message.get '_id'
     ActivityFlux.actions.message.unsetMessageEditMode messageId

@@ -281,14 +286,19 @@

   onEditInputKeyDown: (event) ->

-    code = event.which or event.keyCode
-    key  = keycode code
-
-    switch key
-      when 'esc'
-        @cancelEdit()
-      when 'enter'
-        @updateMessage()
+    { ESC, ENTER, BACKSPACE } = KeyboardKeys
+
+    switch event.which
+      when ESC         then @cancelEdit()
+      when ENTER       then @updateMessage()
+      when BACKSPACE   then @onEditInputBackspace event
+
+
+  onEditInputBackspace: (event) ->
+
+    return yes  if @state.editInputValue
+    kd.utils.stopDOMEvent event
+    @setState isDeleting: yes

   getEditModeClassNames: -> classnames


app-settings-menu: Removed ace header bar.

--- client/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/app/Applications/Ace.kdapplication/AppView.coffee
@@ -14,22 +14,6 @@
     super

     @listenWindowResize()
-
-    @editorHeader = new KDView
-      cssClass : "editor-header header-buttons"
-
-    @editorHeader.addSubView @headerButtonContainer = new KDView
-      cssClass : "header-buttons"
-
-    @saveButton = new KDButtonViewWithMenu
-      title         : "Save"
-      style         : "editor-button save-menu"
-      type          : "contextmenu"
-      delegate      : @
-      menu          : @getSaveMenu.bind @
-      callback      : =>
-        @ace.requestSave()
-    @saveButton.disable()

     @caretPosition = new KDCustomHTMLView
       tagName       : "div"
@@ -50,47 +34,8 @@
       menu          : @getAdvancedSettingsMenuItems.bind @
     @advancedSettings.disable()

-    publicUrlCheck = /.*\/(.*\.koding.com)\/website\/(.*)/
-    @previewButton = new KDButtonView
-      style     : "editor-button"
-      icon      : yes
-      iconOnly  : yes
-      iconClass : "preview"
-      callback  : =>
-        publicPath = @getData().path.replace publicUrlCheck, 'http://$1/$2'
-        return if publicPath is @getData().path
-        KD.getSingleton("appManager").open "Viewer", (appInstance)->
-          appInstance.open publicPath
-
-    @previewButton.hide() unless publicUrlCheck.test(@getData().path)
-    @previewButton.disable()
-
-    @compileAndRunButton = new KDButtonView
-      style     : "editor-button"
-      title     : "Compile & Run"
-      loader    :
-        color   : "#444444"
-        diameter: 12
-      callback  : =>
-        manifest = KodingAppsController.getManifestFromPath @getData().path
-        @ace.notify "Compiling...", null, yes
-        @getSingleton('kodingAppsController').compileApp manifest.name, (err)=>
-          if not err
-            @ace.notify "App compiled!", "success"
-          else
-            @ace.notify "Trying to run old version..."
-          @getSingleton('kodingAppsController').runApp manifest
-          @compileAndRunButton.hideLoader()
-
-    @compileAndRunButton.hide() unless /\.kdapp\//.test @getData().path
-    @compileAndRunButton.disable()
-
     @findAndReplaceView = new AceFindAndReplaceView delegate: @
     @findAndReplaceView.hide()
-
-    @headerButtonContainer.addSubView @compileAndRunButton
-    @headerButtonContainer.addSubView @previewButton
-    @headerButtonContainer.addSubView @saveButton

     @setViewListeners()

@@ -122,6 +67,24 @@
       else
         @getData().emit "file.requests.save", contents

+  preview: ->
+    publicUrlCheck = /.*\/(.*\.koding.com)\/website\/(.*)/
+    publicPath = @getData().path.replace publicUrlCheck, 'http://$1/$2'
+    return if publicPath is @getData().path
+    KD.getSingleton("appManager").open "Viewer", (appInstance)->
+      appInstance.open publicPath
+
+  compileAndRun: ->
+    manifest = KodingAppsController.getManifestFromPath @getData().path
+    @ace.notify "Compiling...", null, yes
+    @getSingleton('kodingAppsController').compileApp manifest.name, (err)=>
+      if not err
+        @ace.notify "App compiled!", "success"
+      else
+        @ace.notify "Trying to run old version..."
+      @getSingleton('kodingAppsController').runApp manifest
+      @compileAndRunButton.hideLoader()
+
   viewAppended:->

     super
@@ -130,7 +93,6 @@
   pistachio:->

     """
-    {{> @editorHeader}}

       {{> @ace}}


No need a seperate method to updateCanvasSize, cleanup could do the same job

--- client/Framework/components/dia/kddiascene.coffee
+++ client/Framework/components/dia/kddiascene.coffee
@@ -228,16 +228,11 @@
     @fakeContext = @fakeCanvas.getElement().getContext "2d"

   cleanup:(canvas)->
-    canvas.setDomAttributes width: canvas.getWidth()
-
-  updateCanvasSize:->
-    @realCanvas.setDomAttributes @getSceneSize()
-    @fakeCanvas.setDomAttributes @getSceneSize()
-    @updateScene()
+    canvas.setDomAttributes @getSceneSize()

   parentDidResize:->
     super
-    do _.throttle => @updateCanvasSize()
+    do _.throttle => @updateScene()

   getSceneSize:-> width: @getWidth(), height: @getHeight()


Added throttle to resizeTabHandles method also added min heigth and max height for tab handlers.

--- client/Framework/components/tabs/KDTabView.coffee
+++ client/Framework/components/tabs/KDTabView.coffee
@@ -3,6 +3,8 @@
   constructor:(options = {}, data)->

     options.resizeTabHandles  ?= no
+    options.maxHandleWidth    ?= 128
+    options.minHandleWidth    ?= 30
     @handles                   = []
     @panes                     = []
     @selectedIndex             = []
@@ -225,7 +227,7 @@
     handle = @getHandleByIndex index
     handle.getDomElement().addClass "hide-close-icon"

-  resizeTabHandles:->
+  resizeTabHandles: KD.utils.throttle ->
     return if not @getOptions().resizeTabHandles or @_tabHandleContainerHidden

     @tabHandleContainer.$().addClass "resize-in-action"
@@ -233,21 +235,25 @@

     visibleHandles   = []
     visibleTotalSize = 0
-    containerSize    = @tabHandleContainer.getWidth()
+    options          = @getOptions()
+    containerSize    = @tabHandleContainer.$().outerWidth no
+

     for handle in @handles
       unless handle.isHidden()
         visibleHandles.push handle
-        visibleTotalSize += handle.getOptimalWidth()
-
-    if containerSize < visibleTotalSize
+        visibleTotalSize += handle.$().outerWidth no
+
+    if containerSize < visibleTotalSize + 40
       for handle in visibleHandles
-        handle.$().css width: containerSize / visibleHandles.length - 10
+        possibleWidth = containerSize / visibleHandles.length - 10
+        handle.$().css width: if possibleWidth > options.maxHandleWidth then options.maxHandleWidth else possibleWidth
     else
       for handle in visibleHandles
-        handle.$().css width: handle.getOptimalWidth()
+        handle.$().css width: options.maxHandleWidth

     @resizeTimer = @utils.wait 300, =>
       @tabHandleContainer.$().removeClass "resize-in-action"
+  , 100

   _windowDidResize:(event)=> @resizeTabHandles event


ComputeController: showBuildLogs: update copy

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -939,6 +939,6 @@
     kd.singletons.appManager.tell 'IDE', 'tailFile', {
       file
       description:
-        "Your stack build completed successfully, in the following
-         logs you can find details about your custom stack script."
+        "Your Koding Stack has successfully been initialized. The log here
+         describes each executed step of the Stack creation process."
     }


envdataprovider: add removeCollaborationMachine

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -232,6 +232,11 @@
     sinkrow.dash queue, callback

+  removeCollaborationMachine: (machine) ->
+
+    @removeMachine 'collaboration', machine
+
+
   removeMachine: (type, machine) ->

     envData = globals.userEnvironmentData[type]


oauth: remove commented out code

oauth: remove commented out code

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -117,9 +117,6 @@
                 title : "Thanks for linking your #{provider.capitalize()} account!"
             else
               @afterLoginCallback err, {account, replacementToken}
-
-    #mainController.on "ForeignAuthFailed", ->
-      #new KDNotificationView title : "Authorization failed."

   viewAppended:->


Improved final api url building.

Improved final api url building.

--- app/coffee/services/resource.coffee
+++ app/coffee/services/resource.coffee
@@ -22,10 +22,11 @@
         return data

     queryMany = (name, params, options) ->
-        defauts = {method: "GET", headers:  headers()}
-        current = {url: $gmUrls.api(name), params: params or {}}
-
-        httpParams = _.extend({}, defauts, options, current)
+        defaultHttpParams = {method: "GET", headers:  headers(), url: $gmUrls.api(name)}
+        if not _.isEmpty(params)
+            defaultHttpParams.params = params
+
+        httpParams = _.extend({}, defaultHttpParams, options)
         defered = $q.defer()

         promise = $http(httpParams)
@@ -39,10 +40,11 @@
         return defered.promise

     queryOne = (name, id, params, options, cls) ->
-        defauts = {method: "GET", headers:  headers()}
-        current = {url: "#{$gmUrls.api(name)}/#{id}", params: params or {}}
-
-        httpParams =  _.extend({}, defauts, options, current)
+        defaultHttpParams = {method: "GET", headers:  headers(), url: "#{$gmUrls.api(name)}/#{id}"}
+        if not _.isEmpty(params)
+            defaultHttpParams.params = params
+
+        httpParams =  _.extend({}, defaultHttpParams, options)

         defered = $q.defer()


ace: remove findAll command (not used and default binding ctrl+alt+k collides with toggle filetree)

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -110,8 +110,9 @@
         'gotoline'         # overriding this
         'sortlines'        # using same mapping for 'save all' action. XXX: re-map sortlines
         'showSettingsMenu' # f ace default settings menu
-        'findprevious',    # we have our own find and replace dialogs
+        'findprevious'     # we have our own find and replace dialogs
         'findnext'         # ace default for findnext is cmd+g (mapped to gotoline)
+        'findAll'          # not used and collides with toggle filetree
       ]

       @focus()


Cleaned highlight detection code and made it work for rotated text as well.

--- client/publication.coffee
+++ client/publication.coffee
@@ -4,22 +4,34 @@
     Meteor.subscribe 'annotations-by-publication', Session.get 'currentPublicationId'
     Meteor.subscribe 'comments-by-publication', Session.get 'currentPublicationId'

-Deps.autorun ->
-  publication = Publications.findOne Session.get 'currentPublicationId'
-
-  return unless publication
-
-  unless Session.equals 'currentPublicationSlug', publication.slug
-    Meteor.Router.to Meteor.Router.publicationPath publication._id, publication.slug
-    return
-
-  # Maybe we don't yet have whole publication object available
-  try
-    unless publication.url()
-      return
-  catch e
-    return
-
+# This function should work on rotated elements as well for all values
+getElementPosition = (element) ->
+  dims =
+    left: 0
+    top: 0
+    right: 0
+    bottom: 0
+    width: 0
+    height: 0
+
+  if element
+    rect = element.getBoundingClientRect()
+    parentRect = $(element).parent().get(0).getBoundingClientRect()
+    dims.left = rect.left - parentRect.left
+    dims.top = rect.top - parentRect.top
+    dims.right = rect.right - parentRect.right
+    dims.bottom = rect.bottom - parentRect.bottom
+
+    if rect.width
+      dims.width = rect.width
+      dims.height = rect.height
+    else
+      dims.width = dims.right - dims.left
+      dims.height = dims.bottom - dims.top
+
+  dims
+
+displayPublication = (publication) ->
   PDFJS.getDocument(publication.url()).then (pdf) ->
     for pageNumber in [1..pdf.numPages]
       $canvas = $('<canvas/>').addClass('display-canvas')
@@ -35,18 +47,11 @@
             height: viewport.height
             width: viewport.width

-          $pageDisplay.attr
-            height: viewport.height
-            width: viewport.width
-
           $textLayerDiv = $('<div/>').addClass('display-text').css(
             height: viewport.height + 'px'
             width: viewport.width + 'px'
           ).appendTo $pageDisplay

-          $pageDisplay.css
-            position: 'relative'
-
           $closestTextDiv = null
           closestDistance = Number.MAX_VALUE;

@@ -57,25 +62,23 @@

             closestDistance = Number.MAX_VALUE;
             $textLayerDiv.children().each ->
-              textDivOffset = $(this).data("cachedOffset")
-              textDivWidth = $(this).data("cachedWidth")
-              textDivHeight = $(this).data("cachedHeight")
-
-              distXLeft = relX - textDivOffset.left
-              distXRight = relX - (textDivOffset.left + textDivWidth)
-              distXCenter = relX - (textDivOffset.left + textDivWidth/2.0)
-
-              distYTop = relY - textDivOffset.top
-              distYBottom = relY - (textDivOffset.top + textDivHeight)
-              distYCenter = relY - (textDivOffset.top + textDivHeight/2.0)
+              position = $(this).data 'position'
+
+              distXLeft = relX - position.left
+              distXRight = relX - (position.left + position.width)
+              distXCenter = relX - (position.left + position.width/2.0)
+
+              distYTop = relY - position.top
+              distYBottom = relY - (position.top + position.height)
+              distYCenter = relY - (position.top + position.height/2.0)

               distX = if Math.abs(distXLeft) < Math.abs(distXRight) then distXLeft else distXRight
-              if relX > textDivOffset.left and relX < textDivOffset.left + textDivWidth
+              if relX > position.left and relX < position.left + position.width
                 distX = 0

               distY = if Math.abs(distYTop) < Math.abs(distYBottom) then distYTop else distYBottom
-              if relY > textDivOffset.top and relY < textDivOffset.top + textDivHeight
+              if relY > position.top and relY < position.top + position.height
                 distY = 0
               # distY = if Math.abs(distY) < Math.abs(distYCenter) then distY else distYCenter

@@ -136,9 +139,26 @@

             page.render(renderContext).then ->
               $textLayerDiv.children().each ->
-                $(this).data("cachedOffset", $(this).position())
-                $(this).data("cachedWidth", $(this).width())
-                $(this).data("cachedHeight", $(this).height())
+                $(this).data
+                  position: getElementPosition this
+
+Deps.autorun ->
+  publication = Publications.findOne Session.get 'currentPublicationId'
+
+  return unless publication
+
+  unless Session.equals 'currentPublicationSlug', publication.slug
+    Meteor.Router.to Meteor.Router.publicationPath publication._id, publication.slug
+    return
+
+  # Maybe we don't yet have whole publication object available
+  try
+    unless publication.url()
+      return
+  catch e
+    return
+
+  displayPublication publication

 # TODO: Destroy/clear pdf.js structures/memory on autorun cycle/stop