Fix js error in selectFirstInput

Fix js error in selectFirstInput

--- app/assets/javascripts/boot.js.coffee
+++ app/assets/javascripts/boot.js.coffee
@@ -2,7 +2,7 @@
 window.OUT = {} unless window.OUT?

 OUT.selectFirstInput = (ele) ->
-  ele or= window
+  ele or= document
   $(ele).find("input[type=text], textarea").first().select()


Add button `blurOnClick` property.

Add button `blurOnClick` property.

--- src/coffee/button/Button.coffee
+++ src/coffee/button/Button.coffee
@@ -98,6 +98,19 @@
   @property 'svgIcon'

+  # If set to `true`, this button will blur immediately after a click event.
+  # In other words, the button does not keep focus after it has been clicked.
+  # This should be set to false if the button is intended to be clicked
+  # multiple times, like in a game interfacer.
+  #
+  # @property {boolean} blurOnClick
+  # @default true
+  # @public
+
+  @property 'blurOnClick',
+    default: true
+
+
   ##
   # Where the icon should appear relative to the icon text.
   #
@@ -133,6 +146,8 @@
     # Update the label in real-time if it changes.
     @on 'change:label', (e) =>
       @_getButton().textContent = e.data.value
+
+    if @blurOnClick then @on 'click', (e) => @_getButton().blur()

   ##


Add a method for checking if Serrano is outdated

--- src/coffee/cilantro/core.coffee
+++ src/coffee/cilantro/core.coffee
@@ -78,6 +78,14 @@
                     parseInt(versionFields[1], 10),
                     parseInt(versionFields[2], 10)]

+        isSerranoOutdated: ->
+            serranoVersion = @getSerranoVersion()
+            console.log("#{serranoVersion} v. #{c.minimumSerranoVersion}")
+            for i in [0..2] by 1
+                if serranoVersion[i] < c.minimumSerranoVersion[i]
+                    return true
+
+            return false

     channels = _.extend {}, channels, session.channels


added method to get a number in bytes, kb, mb etc.

--- app/assets/javascripts/mercury/utility.js.coffee
+++ app/assets/javascripts/mercury/utility.js.coffee
@@ -37,3 +37,11 @@
   result = @toString(16).toUpperCase()
   return if result[1] then result else "0#{result}"

+
+Number::toBytes = ->
+  bytes = parseInt(@)
+  i = 0
+  while 1023 < bytes
+    bytes /= 1024
+    i += 1
+  return if i then "#{bytes.toFixed(2)}#{['', ' kb', ' Mb', ' Gb', ' Tb', ' Pb', ' Eb'][i]}" else "#{bytes} bytes"


Add missing Backbone import in core

Add missing Backbone import in core

--- src/coffee/cilantro/core.coffee
+++ src/coffee/cilantro/core.coffee
@@ -1,5 +1,6 @@
 define [
     'underscore'
+    'backbone'
     'promiser'
     './logger'
     './config'
@@ -12,7 +13,7 @@
     # Note, these are applied in place.
     'plugins/js'
     'plugins/jquery-ajax-queue'
-], (_, promiser, loglevel, config, channels, utils, changelog) ->
+], (_, Backbone, promiser, logger, config, channels, utils, changelog) ->

     c =
         # Version of cilantro


use correct unit label on hover

use correct unit label on hover

--- app/assets/javascripts/monitoring/util.js.coffee
+++ app/assets/javascripts/monitoring/util.js.coffee
@@ -96,7 +96,7 @@
   xvf = xOptView('valueFormatter')
   opts = {
     highlightCallback: (e, x, pts, row) ->
-      date = xvf(x, xOptView, "", g) + ", " + sprintf("val = %0.2f%s", pts[0].yval, unit)
+      date = xvf(x, xOptView, "", g) + ", " + sprintf("val = %0.2f%s", pts[0].yval, unit_label)
       footer.text(date)

     unhighlightCallback: (e) ->


implement hoverTimeout and smartCompressionTime options

--- src/coffee/ezBreadcrumbs.coffee
+++ src/coffee/ezBreadcrumbs.coffee
@@ -312,7 +312,7 @@
                 o.el.on "mouseleave.#{ pluginName }", ".#{ o.opts.holder.class }", ->
                     o.dropdownTimer = window.setTimeout( ->
                         o.dropdownWrapper.trigger "hide.#{ pluginName }"
-                    , 500)
+                    , o.opts.hoverTimeout)

                 # bind the mouseenter event for hoverIn to the dropdownWrapper
                 o.dropdownWrapper.on "mouseenter.#{ pluginName }", ->
@@ -355,7 +355,7 @@

                         # immediately set to null after clearing timer to prevent memory leaks
                         o.smartCompressionTimer = null
-                , 200)
+                , o.opts.smartCompressionTime)

         o.resize = (e) ->
             current = $ this


add option to control animation speed

add option to control animation speed

--- src/coffeescript/notifyr.coffee
+++ src/coffeescript/notifyr.coffee
@@ -16,6 +16,7 @@
   defaults =
     sticky: true
     location: 'top-right'
+    animationSpeed: 250
     classes: []
     closeButtonHtml: '<button class="notification-close">×</button>'

@@ -65,10 +66,7 @@
       @el.append @notice
       @notice
         .stop()
-        .animate
-          opacity: 1
-          right: '15px'
-        , 250, 'easeOutBack', =>
+        .animate @animateOptions('show'), @options.animationSpeed, 'easeOutBack', =>
           @el.trigger 'notification-display-complete'

     Notifyr::empty = ->
@@ -77,12 +75,19 @@
     Notifyr::remove = ->
       @notice
         .stop()
-        .animate
-          opacity: 0
-          right: '-300px'
-        , 250, 'easeInBack', =>
+        .animate @animateOptions('hide'), @options.animationSpeed, 'easeInBack', =>
           @el.empty()
           @el.trigger 'notification-remove-complete'
+
+    Notifyr::animateOptions = (state) ->
+      opts = {}
+      if (state == 'show')
+        opts.opacity = 1
+        opts.right = '15px'
+      else
+        opts.opacity = 0
+        opts.right = '-300px'
+      opts

     Notifyr


Providers: EnvironmentsMachineStateModal: Move "open tailer pane logic" into IDEAppController

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -764,7 +764,7 @@
   prepareIDE: (initial) ->

-    { appManager, computeController } = kd.singletons
+    { appManager } = kd.singletons

     environmentDataProvider.fetchMachine @machine.uid, (machine) =>

@@ -775,9 +775,7 @@
       @machine = machine
       @setData machine

-      @emit 'IDEBecameReady', machine
-
-      computeController.showBuildLogs machine  if initial
+      @emit 'IDEBecameReady', machine, initial

   verifyAccount: ->


[Group Notifications] open group link in another window

--- client/landing/app/CommonViews/linkviews/grouplinkview.coffee
+++ client/landing/app/CommonViews/linkviews/grouplinkview.coffee
@@ -7,7 +7,8 @@
   render:->
     {slug} = @getData()
     @setAttribute "href", "/#{slug}"
+    @setAttribute "target", "_blank"
     super

   pistachio:->
-    super "{{#(title)}}"+    super "{{#(title)}}"


site.landing/mainheaderview: use img instead of object

--- client/landing/site.landing/coffee/core/mainheaderview.coffee
+++ client/landing/site.landing/coffee/core/mainheaderview.coffee
@@ -35,7 +35,7 @@
     @addSubView @logo = headerLogo or new kd.CustomHTMLView
       tagName   : 'a'
       cssClass  : 'koding-header-logo'
-      partial   : '<object type="image/svg+xml" data="/a/images/logos/header_logo.svg" class="main-header-logo">Koding Logo</object>'
+      partial   : '<img src="/a/images/logos/header_logo.svg" class="main-header-logo">'
       click     : (event) ->
         kd.utils.stopDOMEvent event
         kd.singletons.router.handleRoute '/'


Eliminate unnecessary update of settings

--- app/assets/javascripts/lib/app.coffee
+++ app/assets/javascripts/lib/app.coffee
@@ -29,8 +29,9 @@

     # Store the scenario id
     @api.ensure_id().done (id) =>
-      @settings.save { api_session_id: id },
-        success: @enableIdDependantSettings
+      if id != globals.api_session_id
+        @settings.save({ api_session_id: id })
+      @enableIdDependantSettings()

   # (Re)builds the list of sliders and renders them. This is usually called by
   # play.js.erb
@@ -203,8 +204,10 @@
     $(@disabledSettings())
       .removeClass('wait').off('click', disabledSetting)

+    id = @settings.get('api_session_id')
+
     $('#settings_menu a#show_graph').attr('href', ->
-      $(this).data('url').replace(/:id/, App.settings.get('api_session_id'))
+      $(this).data('url').replace(/:id/, id)
     )

   # TODO: Move this interface methods to a separate Interface class


Working on checkout/stash logic

Working on checkout/stash logic

--- compilation/script-git-setup.coffee
+++ compilation/script-git-setup.coffee
@@ -7,9 +7,11 @@
   git.exec args: 'branch', (err, stdout) ->
     return defer.reject("Unable to retrieve current branch") unless stdout?
     [__, configuration.currentBranch] = stdout.match /\*\s*(.+)\s/
-    return defer.reject("Unable to retrieve current branch") unless currentBranch?
-    tag = args[1] or currentBranch
+    return defer.reject("Unable to retrieve current branch") unless configuration.currentBranch?
     git.exec args: 'stash', (err, stdout) ->
-      git.checkout tag, quiet: yes, ->
-        defer.resolve()
+      return defer.reject(err) if err
+      console.log err
+      console.log stdout
+      #git.checkout tag, quiet: yes, ->
+      #  defer.resolve()
   defer.promise


"standalone" no longer enables the request proxy

The ETPlugin request proxy was created for old browsers where CORS was
not supported. This is no longer a problem and the proxy does not need
to be enabled whenever "standalone" mode is enabled.

--- app/assets/javascripts/lib/app.coffee
+++ app/assets/javascripts/lib/app.coffee
@@ -17,7 +17,6 @@
     @api = new ApiGateway
       api_path:           globals.api_url
       api_proxy_path:     globals.api_proxy_url
-      offline:            globals.standalone
       scenario_id:        globals.api_session_id
       beforeLoading:      @showLoading
       afterLoading:       @hideLoading


Listen to both blur and submit events

Listen to both blur and submit events

--- app/assets/javascripts/signups.coffee
+++ app/assets/javascripts/signups.coffee
@@ -53,11 +53,17 @@
       @log 'Installing listener to input field', input
       input.addEventListener 'blur', (event) =>
         @handleBlur(event.target)
+    form.addEventListener 'submit', (event) =>
+      @handleSubmit(form)

   handleBlur: (field) ->
     cleanEmail = field.value.replace(/^\s+|\s+$/g, '')
     if @validateEmailFormat(cleanEmail)
       @send(cleanEmail).end()
+
+  handleSubmit: (form) ->
+    for input in form.querySelectorAll(DEFAULT_EMAIL_INPUT_SELECTOR)
+      input.blur()

   send: (email) ->
     if @token


Use `isBrowser` for test switching

Use `isBrowser` for test switching

--- spec/Client.coffee
+++ spec/Client.coffee
@@ -1,6 +1,6 @@
 noflo = require 'noflo'

-if typeof process is 'object' and process.title is 'node'
+unless noflo.isBrowser()
   chai = require 'chai' unless chai
   Client = require '../components/Client.coffee'
 else


Fix #284 (view only)

Fix #284 (view only)

--- server/app/assets/javascript/drop.coffee
+++ server/app/assets/javascript/drop.coffee
@@ -139,9 +139,8 @@
     map_rank_otsu: -> @getJSON()
     map_rank_hei: -> @getJSON()
     drops: (drops) ->
-      if drops.length > 0
-        $("#panel#{cell}")[0].scrollIntoView(true)
-        @draw()
+      $("#panel#{cell}")[0].scrollIntoView(true)
+      @draw()
     period: -> @getJSON()
     from: ->
       if @period


TabView: fixes emitting inactive event order

--- src/components/tabs/tabpaneview.coffee
+++ src/components/tabs/tabpaneview.coffee
@@ -41,6 +41,7 @@
     return  unless @active

     @setScrollTops()
+    @emit 'KDTabPaneInactive'

     @unsetClass 'active'
     @setClass 'kdhiddentab'
@@ -49,7 +50,6 @@
       @parent?.getElement().removeChild @getElement()

     @active = no
-    @emit 'KDTabPaneInactive'

   setScrollTops: ->


fix try another showing as 'Loading' when recovering starts

--- src/components/task-step/exercise.cjsx
+++ src/components/task-step/exercise.cjsx
@@ -37,6 +37,7 @@
       waitingText={waitingText}

       canTryAnother={TaskStepStore.canTryAnother(id, task)}
+      isRecovering={TaskStepStore.isRecovering(id)}
       disabled={TaskStepStore.isSaving(id)}
       canReview={StepPanel.canReview(id)}
       isContinueEnabled={StepPanel.canContinue(id)}


Change full_name for full_name_display in assigned-to column at issues list

--- app/coffee/modules/issues/list.coffee
+++ app/coffee/modules/issues/list.coffee
@@ -758,7 +758,7 @@
             member = $scope.usersById[issue.assigned_to]
             if member
                 ctx.imgurl = member.photo
-                ctx.name = member.full_name
+                ctx.name = member.full_name_display

             $el.find(".avatar").html(template(ctx))


Bugfix in the frontend

Bugfix in the frontend

--- core/app/backbone/views/sub_comments/sub_comments_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_view.coffee
@@ -105,9 +105,6 @@
   events:
     'click .js-sub-comments-link': 'toggleList'

-  initialize: ->
-    @count = @model.get('sub_comments_count')
-
   onRender: ->
     @bindTo @model, 'change:sub_comments_count', @updateLink, @
     @updateLink()
@@ -135,9 +132,11 @@
     @subCommentsListRegion.close()

   updateLink: ->
+    count = @model.get('sub_comments_count')
+
     count_str = ""

-    if @count
-      count_str = " (#{@count})"
+    if count
+      count_str = " (#{count})"

     @$(".js-sub-comments-link").text "Comments#{count_str}"


Change '(->)' to 'nop' for consistency.

Change '(->)' to 'nop' for consistency.

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -692,7 +692,7 @@
         buffer.schedule()
     withDescription(this, "bufferWithTimeOrCount", delay, count, @buffer(delay, flushOrSchedule, flushOrSchedule))

-  buffer: (delay, onInput = (->), onFlush = (->)) ->
+  buffer: (delay, onInput = nop, onFlush = nop) ->
     buffer = {
       scheduled: false
       end: null


get basic event-based functions working

get basic event-based functions working

--- app/lib/mini-course.coffee
+++ app/lib/mini-course.coffee
@@ -7,18 +7,19 @@
     # super
     console.log 'MiniCourse.constructor()'

-    $(classifier.el).find("#course-prompt").hide()
-    $(classifier.el).find("#course-container").hide()
     @rate = 10 # set default rate
+    @prompt_el = $(classifier.el).find("#course-prompt")
+    @course_el = $(classifier.el).find("#course-container")

-    $(classifier.el).find("#course-prompt").on "click", "#course-yes", (e) =>
-      @onClickCourseYes()
-
-    $(classifier.el).find("#course-prompt").on "click", "#course-no", (e) =>
-      @onClickCourseNo()
-
-    $(classifier.el).find("#course-prompt").on "click", "#course-never", (e) =>
-      @onClickCourseNever()
+    @prompt_el.hide()
+    @course_el.hide()
+
+    # event callbacks
+    @prompt_el.on "click", "#course-yes", (e) => @onClickCourseYes()
+    @prompt_el.on "click", "#course-no", (e) => @onClickCourseNo()
+    @prompt_el.on "click", "#course-never", (e) => @onClickCourseNever()
+    @prompt_el.on "click", "#course-prompt-close", (e) => @onClickCoursePromptClose()
+    @course_el.on "click", "#course-close", (e) => @onClickCourseClose()

   setRate: (rate) ->
@@ -27,6 +28,7 @@

   onClickCourseYes: ->
     console.log "onClickCourseYes()"
+    @displayCourse()

   onClickCourseNo: ->
     console.log "onClickCourseNo()"
@@ -36,15 +38,21 @@

   displayCourse: ->
     console.log 'displayCourse()'
+    @course_el.fadeIn()
+
+  hideCourse: ->
+    @course_el.fadeOut()

   onClickCourseClose: ->
     console.log 'courseClose()'
+    @hideCourse()
+
+  hidePrompt: ->
+    @prompt_el.hide()

   onClickCoursePromptClose: ->
     console.log 'onClickCoursePromptClose()'
-
-  hideCoursePrompt: ->
-    console.log 'hideCoursePrompt()'
+    @prompt_el.slideUp()

   showCoursePrompt: ->
     console.log 'showCoursePrompt()'


hide transporter iframe

hide transporter iframe

--- app/lib/transporter.coffee
+++ app/lib/transporter.coffee
@@ -12,7 +12,7 @@

     unless @el
       @el = $ """
-
+
       """
       @el.appendTo document.body
       @el = @el[0]


Use jquery for Post.make_request ajax

Use jquery for Post.make_request ajax

--- app/javascript/src/legacy/post.coffee
+++ app/javascript/src/legacy/post.coffee
@@ -14,24 +14,22 @@
     'faults'
   ]
   make_request: (path, params, finished) ->
-    new (Ajax.Request)(path,
-      requestHeaders: 'X-CSRF-Token': jQuery('meta[name=csrf-token]').attr('content')
-      parameters: params
-      onFailure: (req) ->
-        resp = req.responseJSON
-        notice 'Error: ' + resp.reason
-        return
-      onSuccess: (resp) ->
-        resp = resp.responseJSON
-        Post.register_resp resp
-
-        # Fire posts:update, to allow observers to update their display on change.
-        if resp.posts? && resp.posts.length > 0
-          jQuery(document).trigger 'posts:update', [new Set(resp.posts.map((post) => post.id))]
-
-        finished?(resp)
-        return
-)
+    jQuery.ajax path,
+      data: params
+      dataType: 'json'
+      method: 'POST'
+    .fail (xhr) =>
+      notice "Error: #{xhr.responseJSON?.reason ? 'unknown error'}"
+
+    .done (resp) =>
+      Post.register_resp resp
+
+      # Fire posts:update, to allow observers to update their display on change.
+      if resp.posts? && resp.posts.length > 0
+        jQuery(document).trigger 'posts:update', [new Set(resp.posts.map((post) => post.id))]
+
+      finished?(resp)
+
   approve: (post_id, delete_reason, finished) ->
     notice 'Approving post #' + post_id
     params = {}


jupyter: slideshow export support

jupyter: slideshow export support

--- src/smc-webapp/jupyter/export-to-ipynb.coffee
+++ src/smc-webapp/jupyter/export-to-ipynb.coffee
@@ -44,6 +44,10 @@
     exec_count = cell.get('exec_count') ? 0
     if obj.cell_type == 'code'
         obj.execution_count = exec_count
+
+    slide = cell.get('slide')
+    if slide
+        metadata.slideshow = {slide_type:slide}

     output = cell.get('output')
     if output?.size > 0


Add missing keys

Add missing keys

--- app/pages/lab/project.cjsx
+++ app/pages/lab/project.cjsx
@@ -77,7 +77,7 @@

}

                 {for workflow in workflows
-                  <ChangeListener target={workflow} eventName="save" handler={renderWorkflowListItem.bind this, workflow} />}
+                  <ChangeListener key={workflow.id} target={workflow} eventName="save" handler={renderWorkflowListItem.bind this, workflow} />}

                   <button type="button" onClick={@createNewWorkflow} disabled={@state.workflowCreationInProgress} title="A workflow is the sequence of tasks that you’re asking volunteers to perform.">
@@ -104,7 +104,7 @@

}

                 {for subjectSet in subjectSets
-                  <ChangeListener target={subjectSet} eventName="save" handler={renderSubjectSetListItem.bind this, subjectSet} />}
+                  <ChangeListener key={subjectSet.id} target={subjectSet} eventName="save" handler={renderSubjectSetListItem.bind this, subjectSet} />}

                   <button type="button" onClick={@createNewSubjectSet} disabled={@state.subjectSetCreationInProgress} title="A subject is an image (or group of images) to be analyzed.">


[grunt] split fetched `locales` into array, since during `grunt.template.process()` it converted into an array

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -17,7 +17,7 @@
   marked       = require('marked')
   markdown     = require('nunjucks-markdown')

-  locales    = grunt.template.process('<%= data.site.locales %>')
+  locales    = grunt.template.process('<%= data.site.locales %>').split(',')
   baseLocale = grunt.template.process('<%= data.site.baseLocales %>')

   buildDir   = grunt.template.process('<%= path.build.root %>')


Add support for single page applications.

--- app/templates/common/Gruntfile.coffee
+++ app/templates/common/Gruntfile.coffee
@@ -21,6 +21,8 @@
   dir:
     tmp: 'tmp'
     dist: 'dist'
+  app:
+    singlePage: false

 extraConfig = defaults
 if fs.existsSync extraConfigFile
@@ -276,6 +278,23 @@
           useAvailablePort: true
           open: true
           livereload: extraConfig.ports.livereload
+          middleware: (connect, options, middlewares) ->
+            middlewares.push (req, res, next) ->
+              return next() unless req.headers['accept'].indexOf('text/html') >= 0
+              return next() unless extraConfig.app.singlePage
+
+              file = extraConfig.app.singlePage
+              file = 'index.html' unless path.extname(file) == '.html'
+              filepath = path.join(extraConfig.dir.tmp, file)
+              stat = fs.statSync filepath
+              res.writeHead 200,
+                'Content-Type': 'text/html'
+                'Content-Length': stat.size
+              res.write fs.readFileSync(filepath)
+              res.end()
+              next()
+
+            middlewares

     copy:
       tmpAssets:


use ctrl+shift+r instead. fixes #95

use ctrl+shift+r instead. fixes #95

--- keymaps/atom-ternjs.cson
+++ keymaps/atom-ternjs.cson
@@ -10,6 +10,6 @@
 'atom-text-editor':
   'ctrl-alt-d': 'tern:definition'
   'ctrl-alt-c': 'tern:rename'
-  'ctrl-alt-r': 'tern:references'
+  'ctrl-shift-r': 'tern:references'
   'ctrl-alt-z': 'tern:markerCheckpointBack'
   'ctrl-alt-space': 'tern:startCompletion'


started building the model class.

started building the model class.

--- src/Model.coffee
+++ src/Model.coffee
@@ -1,5 +1,93 @@
-Stateful = require "stateful"
+Stateful = require 'stateful'
+_        = require 'underscore'
+Store    = require './Store'

 class Model extends Stateful
+
+	###
+	This is really how I want to describe the states of a model.
+	Need to update stateful to support this.

-	+	ALL_CAPS -> these are States.  These you enter / exit
+	One_cap -> these are actions.  These are available actions to perform when in the owning state.
+
+	Siblings are mutually exclusive.
+
+	'UNSAVED'
+		'Updating'
+		'Saving'
+
+
+	'SAVED'
+		'UNLOADED'
+			'Loading'
+
+		'LOADED'
+			'Updating'
+			'Editing'
+			'DIRTY'
+				'CONFLICTED'
+				'Syncing'
+	###
+
+	# OLD WAY (similiar to rubicon model)
+	@addState 'EMPTY',
+		transitions :
+			initial : true
+			exit    : 'UPDATING'
+
+	@addState 'UPDATING',
+		transitions :
+			enter   : 'EMPTY'
+			exit    : 'NEW, READY'
+
+	@addState 'NEW',
+		transitions :
+			enter   : 'UPDATING'
+			exit    : 'SYNCING'
+
+	@addState 'READY',
+		transitions :
+			enter   : 'UPDATING,DIRTY,SYNCING,CONFLICTED,EDITING'
+			exit    : 'EDITING'
+
+	@addState 'DIRTY',
+		transitions :
+			enter   : 'CONFLICTED,EDITING'
+			exit    : 'READY,SYNCING,CONFLICTED,EDITING'
+
+	@addState 'CONFLICTED',
+		transitions :
+			enter   : 'DIRTY'
+			exit    : 'DIRTY,READY,EDITING'
+
+	@addState 'EDITING',
+		transitions :
+			enter   : 'READY,CONFLICTED,DIRTY'
+			exit    : 'DIRTY,READY'
+
+	@addState 'SYNCING',
+		transitions :
+			enter   : 'DIRTY'
+			exit    : 'READY'
+
+	@buildStateChart()
+
+	#
+	# Store proxy methods
+	#
+
+	@store: (name) -> @store = new Store type:@
+
+	@get    : -> @store.get.apply @store, arguments
+	@find   : -> @store.find.apply @store, arguments
+	@create : -> @store.create.apply @store, arguments
+	@delete : -> @store.delete.apply @store, arguments
+
+
+	#
+	# Model declarative definitions
+	#
+	@_attribute: (kind, name, type, config = {}) ->
+		@::_schema = {} unless @::_schema?
+		@::_schema[name] = _


on successful token creation, send to api to save the customer id

--- apps/tools/client/premium_view.coffee
+++ apps/tools/client/premium_view.coffee
@@ -17,10 +17,17 @@
   openCheckout: (e) =>
     @handler.open
       name: 'Are.na'
-      description: 'Premium Subscription'
+      description: '1 year / Premium subscription'
       amount: 4500

     e.preventDefault()

   handleToken: (token)->
-    console.log 'handle token', token+    $.ajax
+      url: "#{sd.API_URL}/charges"
+      type: 'POST'
+      data: stripeToken: token
+      success: (response) ->
+        console.log 'successful charge', response
+      error: (response) ->
+        console.log 'charge error', response


Make HUD.init() idempotent.

Make HUD.init() idempotent.

--- content_scripts/hud.coffee
+++ content_scripts/hud.coffee
@@ -13,9 +13,10 @@
   # it doesn't sit on top of horizontal scrollbars like Chrome's HUD does.

   init: ->
-    @hudUI = new UIComponent "pages/hud.html", "vimiumHUDFrame", ({data}) =>
-      this[data.name]? data
-    @tween = new Tween "iframe.vimiumHUDFrame.vimiumUIComponentVisible", @hudUI.shadowDOM
+    unless @hudUI?
+      @hudUI = new UIComponent "pages/hud.html", "vimiumHUDFrame", ({data}) =>
+        this[data.name]? data
+      @tween = new Tween "iframe.vimiumHUDFrame.vimiumUIComponentVisible", @hudUI.shadowDOM

   showForDuration: (text, duration) ->
     @show(text)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -42,7 +42,7 @@

     if arguments.length == 2
       parsedColor = [parseInt(arguments[0][0]), parseInt(arguments[0][1]), parseInt(arguments[0][2]), parseFloat(arguments[1])]
-    if arguments.length > 2
+    else if arguments.length > 2
       alpha = if arguments[3]? then arguments[3] else 1
       parsedColor = [parseInt(arguments[0]), parseInt(arguments[1]), parseInt(arguments[2]), parseFloat(alpha)]
     else if color.length > 2 && Object.prototype.toString.call(color) == '[object Array]'


adding hsl support to color class

adding hsl support to color class

--- src/color.coffee
+++ src/color.coffee
@@ -145,39 +145,17 @@
           h = s = 0
         else
           d = max - min
-          s = (if l > 0.5 then d / (2 - max - min) else d / (max + min))
+          s = (if l > 0.5 then d / (2 - max - min) else d / (max + min))
+
+          switch max
+            when r then h = (g - b) / d + (g < b ? 6 : 0)
+            when g then h = (b - r) / d + 2
+            when b then h = (r - g) / d + 4
+
+          h /= 6
+
+        return [h, s, l]

-        Color.RGBtoHSL = function (rgb) {
-          // in JS 1.7 use: var [r, g, b] = rgb;
-          var r = rgb[0],
-              g = rgb[1],
-              b = rgb[2];
-
-          r /= 255;
-          g /= 255;
-          b /= 255;
-
-          var max = math.max(r, g, b),
-              min = math.min(r, g, b),
-          h, s, l = (max + min) / 2;
-
-          if (max === min) {
-            h = s = 0; // achromatic
-          } else {
-            var d = max - min;
-            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
-            switch (max) {
-                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
-                case g: h = (b - r) / d + 2; break;
-                case b: h = (r - g) / d + 4; break;
-            }
-            h /= 6;
-          }
-
-          return [h, s, l];
-
-        };
-
       toString: -> self.rgba()

     return self


Handle null rootscope

Handle null rootscope

--- annotran/static/scripts/widget-controller.coffee
+++ annotran/static/scripts/widget-controller.coffee
@@ -121,7 +121,7 @@

         crossframe.call "resetDOM"

-        if $scope.$root.mode == "view"
+        if $scope.$root != null and $scope.$root.mode == "view"
           crossframe.call "hideAdder"

         if selectedUser != undefined


Form method bug fixed

Form method bug fixed

--- app/assets/javascript/railx/04_ajaxable.coffee
+++ app/assets/javascript/railx/04_ajaxable.coffee
@@ -7,7 +7,7 @@
         headers:
           TARGET: target
         target: $(target)
-        type: @dataset.method || "POST"
+        type: @method || "POST"

         # TODO fix jquery.form.js 165 -182
         iframe: false


call super

call super

--- app/assets/javascripts/models/metric.js.coffee
+++ app/assets/javascripts/models/metric.js.coffee
@@ -3,9 +3,10 @@

   class exports.Metric extends Stark.Model

-    initialize: (data) ->
-      @extract_param(data, "metric", true)
-      @extract_param(data, "host")
+    initialize: (attributes, options) ->
+      super
+      @extract_param(attributes, "metric", true)
+      @extract_param(attributes, "host")

     url: ->
       host_id = @host_id || @get("host_id")


improve drawing of edge arrowhead.

improve drawing of edge arrowhead.

--- app/assets/javascripts/plugins/graph.js.coffee
+++ app/assets/javascripts/plugins/graph.js.coffee
@@ -103,7 +103,7 @@
       .enter().append("svg:marker")
         .attr("id", String)
         .attr("viewBox", "0 -5 10 10")
-        .attr("refX", 17)
+        .attr("refX", 14.5)
         .attr("refY", -0.0)
         .attr("markerWidth", 6)
         .attr("markerHeight", 6)


Decreases chart height to account for verbose legends.

--- app/assets/javascripts/visualization.js.coffee
+++ app/assets/javascripts/visualization.js.coffee
@@ -57,7 +57,7 @@
 			data = google.visualization.arrayToDataTable(dataArray);
 			options = {
         legend: {'position':'bottom'}
-        chartArea:{left:0,top:0,width:"90%",height:"90%"}
+        chartArea:{left:0,top:0,width:"99%",height:"50%"}
         colors: chartColors
       };
 			chart.draw(data, options);


dom_text_mapper.coffee - Added explicit change management system.   To enable caching, user application now must explicitly send change notifications   (by calling the documentChanged() function.) Otherwise, not caching will be performed. - Removed the "rescan" argument from some of the calls.   Obsoleted by the more declarative change management system, see above. - Added ability to look up nodes (when selecting a path), if node is not referenced in the path info. - The scan() method now calls getAllPaths() automatically. (Useful if the client app did not bother to do so.) - Added getMappingsForRanges function (in addition to the existing getMappingForRange function) - Added config method "restrictToSerializable", which restrict   all the various API calls to return only data that is serializable.   (End thus excludes all links to actual DOM nodes, etc.)   Currently, filtering is implemented in:   - getAllPaths   - getRangeForPath   - getRangesForPaths

--- app/lib/dom_text_mapper/dom_text_mapper.coffee
+++ app/lib/dom_text_mapper/dom_text_mapper.coffee
@@ -1,8 +1,15 @@
 class window.DomTextMapper
   constructor: ->
     @setRealRoot()
+    @restrictToSerializable false

   # ===== Public methods =======
+
+  # Switch the library into "serializable-only" mode.
+  # If set to true, all public API calls will be restricted to return
+  # strictly serializable data structures.
+  # (References to DOM objects will be omitted.)
+  restrictToSerializable: (value = true) -> @restricted = value

   # Consider only the sub-tree beginning with the given node.
   #
@@ -36,24 +43,53 @@
     @rootNode = document
     @pathStartNode = @getBody()

+  # Notify the library that the document has changed.
+  # This means that subsequent calls can not safely re-use previously cached
+  # data structures, so some calculations will be necessary again.
+  #
+  # The usage of this feature is not mandatorry; if not receiving change notifications,
+  # the library will just assume that the document can change anythime, and therefore
+  # will not assume any stability.
+  documentChanged: ->
+    @lastDOMChange = @timestamp()
+    console.log "Registered document change."
+
   # The available paths which can be scanned
-  #
+  #
   # An map is returned, where the keys are the paths, and the values are objects with the following fields:
   #   path: the valid path value
   #   node: reference to the DOM node
   #   content: the text content of the node, as rendered by the browser
   #   length: the length of the next content
   getAllPaths: ->
+#    console.log "in getAllPaths"
+    if @domStableSince @lastCollectedPaths
+      # We have a valid paths structure!
+#      console.log "We have a valid cache."
+      return if @restricted then @cleanPaths else @allPaths
+
+    console.log "No valid cache, will have to calculate getAllPaths."
     @saveSelection()
     @allPaths = @collectPathsForNode @pathStartNode
     @restoreSelection()
-    @allPaths
+    @lastCollectedPaths = @timestamp()
+    if @restricted
+      @cleanPaths = {}
+      for path, info of @allPaths
+        cleanInfo = $.extend({}, info);
+        delete cleanInfo.node
+        @cleanPaths[path] = cleanInfo
+      @cleanPaths
+    else
+      @allPaths

   # Return the default path
   getDefaultPath: -> @getPathTo @pathStartNode

   # Select the given path (for visual identification), and optionally scroll to it
-  selectPath: (path, scroll = false) -> @selectNode @allPaths[path].node, scroll
+  selectPath: (path, scroll = false) ->
+    info = @allPaths[path]
+    @selectNode info.node ? @lookUpNode info.path

   # Scan the given part of the document.
   #
@@ -67,49 +103,85 @@
   # If no path is given, the whole sub-tree is scanned,
   # starting with the configured root node.
   #
-  # The "rescan" parameter specifies whether the scan should be run again
-  # even if the specified region is the same one that has been scanned
-  # the last time.
-  #
   # Nothing is returned; the following properties are populated:
   #
   #  mappings will contain the created mappings
   #  corpus will contain the text content of the selected path#
   #  scannedPath will be set to the path
-  scan: (path = null, rescan = false) ->
+  scan: (path = null) ->
+#    console.log "In scan"
     path ?= @getDefaultPath()
-    if path is @scannedPath and not rescan then return
-#    console.log "Scanning path: " + path
+    if path is @scannedPath and @domStableSince @lastScanned
+#      console.log "We have a valid cache. Returning instead of scanning."
+      return
+    console.log "Scanning path: " + path
+    @getAllPaths()
     node = @allPaths[path].node
     @mappings = {}
     @saveSelection()
     @collectStrings node, path, null, 0, 0
     @restoreSelection()
     @scannedPath = path
+    @lastScanned = @timestamp()
     @corpus = @mappings[path].pathInfo.content
 #    console.log "Corpus is: " + @corpus
     null

-  getRangeForPath: (path) -> @mappings[path]
+  getRangeForPath: (path) ->
+    result = @mappings[path]
+    if @restricted
+      result = $.extend {}, result;
+      result.pathInfo = $.extend {}, result.pathInfo
+      delete result.pathInfo.node
+    result
+
+  # Get the matching DOM elements for a given set of text ranges
+  # (Calles getMappingsForRange for each element in the givenl ist)
+  getMappingsForRanges: (ranges, path = null) ->
+#    console.log "Ranges:"
+#    console.log ranges
+    mappings = (for range in ranges
+      mapping = @getMappingsForRange range.start, range.end, path
+    )
+#    console.log "Raw mappings:"
+#    console.log mappings
+
+    if @restricted
+      mappings = (for mapping in mappings
+        cleanMapping = $.extend {}, mapping
+        delete cleanMapping.range
+        cleanMapping.nodes = (for node in cleanMapping.nodes
+          cleanNode = $.extend {}, node
+          cleanNode.element = $.extend {}, cleanNode.element
+          cleanNode.element.pathInfo = $.extend {}, cleanNode.element.pathInfo
+          delete cleanNode.element.pathInfo.node
+          cleanNode
+        )
+        cleanMapping
+      )
+#      console.log "Cleaned mappings:"
+#      console.log mappings
+
+    mappings

   # Get the matching DOM elements for a given text range
   #
   # If the "path" argument is supplied, scan is called automatically.
-  # (Except if the supplied path is the same as the last scanned path,
-  # and rescan is false.)
-  getMappingsForRange: (start, end, path = null, rescan = false) ->
+  # (Except if the supplied path is the same as the last scanned path.)
+  getMappingsForRange: (start, end, path = null) ->
+#    console.log "Collecting matches for [" + start + ":" + end + "]"
     unless (start? and end?) then throw new Error "start and end is required!"
-#    console.log "Collecting matches for [" + start + ":" + end + "]"
-    if path?
-      @scan(path, rescan)
-    else
-      if not @scannedPath?
-        throw new Error "Can not run getMappingsFor() without existing mappings. Either supply a path to scan, or call scan() beforehand!"
+
+    if path? then @scan path
+
+    unless @scannedPath? then throw new Error "Can not run getMappingsFor() without existing mappings. Either supply a path to scan, or call scan() beforehand!"

     # Collect the matching mappings
     matches = []
     for p, mapping of @mappings when mapping.atomic and @regions_overlap mapping.start, mapping.end, start, end
       do (mapping) =>
+#        console.log "Checking " + mapping.pathInfo.path
+#        console.log mapping
         match =
           element: mapping
         full_match = start <= mapping.start and mapping.end <= end
@@ -170,9 +242,25 @@
         endOffset: endOffset
         endInfo: endInfo
     }
+#    console.log "Done collecting"
     result

   # ===== Private methods (never call from outside the module) =======
+
+  timestamp: -> new Date().getTime()
+
+  domChangedSince: (timestamp) ->
+#    console.log "Has the DOM changed since " + timestamp + "?"
+    if @lastDOMChange? and timestamp? then @lastDOMChange > timestamp else true
+#    if @lastDOMChange? and timestamp?
+#      console.log "We have a timestamp, checking..."
+#      result = @lastDOMChange > timestamp
+#      console.log result
+#    else
+#      console.log "We don't have a timestamp (or a reference), assuming it has changed."
+#      true
+
+  domStableSince: (timestamp) -> not @domChangedSince timestamp

   getProperNodeName: (node) ->
     nodeName = node.nodeName
@@ -203,15 +291,15 @@
     cont = @getNodeContent node, false
     if cont.length then results[path] =
       path: path
-      node: node
       content: cont
       length: cont.length
+      node : node

     if node.hasChildNodes
       children = node.childNodes
       i = 0
       while i < children.length
-        @collectPathsForNode(children[i], results)
+        @collectPathsForNode children[i], results
         i++
     results

@@ -219,10 +307,10 @@

   regions_overlap: (start1, end1, start2, end2) -> start1 < end2 and start2 < end1

-#  lookUpNode: (path) ->
-#    doc = @rootNode.ownerDocument ? @rootNode
-#    results = doc.evaluate path, @rootNode, null, 0, null
-#    node = results.iterateNext()
+  lookUpNode: (path) ->
+    doc = @rootNode.ownerDocument ? @rootNode
+    results = doc.evaluate path, @rootNode, null, 0, null
+    node = results.iterateNext()

   # save the original selection
   saveSelection: ->
@@ -275,15 +363,22 @@

   # Convert "display" text indices to "source" text indices.
   computeSourcePositions: (match) ->
+#    console.log "In computeSourcePosition"
+#    console.log match.element.pathInfo.path
+#    console.log match.element.pathInfo.node.data
+
+    # the HTML source of the text inside a text element.
     sourceText = match.element.pathInfo.node.data.replace /\n/g, " "
-    # the HTML source of the text inside a text element.
-
+#    console.log "sourceText is '" + sourceText + "'"
+
+    # what gets displayed, when the node is processed by the browser.
     displayText = match.element.pathInfo.content
-    # what gets displayed, when the node is processed by the browser.
-
+#    console.log "displayText is '" + displayText + "'"
+
+    # The selected range in displayText.
     displayStart = if match.start? then match.start else 0
     displayEnd = if match.end? then match.end else displayText.length
-    # The selected range in displayText.
+#    console.log "Display range is: " + displayStart + "-" + displayEnd

     sourceIndex = 0
     displayIndex = 0
@@ -301,6 +396,7 @@
       sourceIndex++
     match.startCorrected = sourceStart
     match.endCorrected = sourceEnd
+ #   console.log "computeSourcePosition done. Corrected range is: " + match.startCorrected + "-" + match.endCorrected
     null

   getNodeContent: (node, shouldRestoreSelection = true) -> @getNodeSelectionText node, shouldRestoreSelection


Obtain rootElement dynamically

Obtain rootElement dynamically

--- app/scripts/components/BsModalComponent.coffee
+++ app/scripts/components/BsModalComponent.coffee
@@ -133,5 +133,5 @@
                 controller: controller
             )

-        modalComponent.appendTo('body')
+        modalComponent.appendTo(controller.namespace.rootElement)
 )


Clone chunks in createRunner

Clone chunks in createRunner

--- test/gulp-rebuild-html-test.coffee
+++ test/gulp-rebuild-html-test.coffee
@@ -81,6 +81,7 @@
   describe 'in stream mode', ->

     createRunner = (rebuildOpts, sourceChunks, expected) ->
+      sourceChunks = clone sourceChunks
       (done) ->
         contentStream = new PassThrough

@@ -107,7 +108,7 @@
                       attrs.class?
         attrs.class = ("module-#{cls}" for cls in attrs.class.split /\s+/g).join ' '
         "<#{name}#{createAttrStr attrs}>"
-    , clone(htmlChunks)
+    , htmlChunks
     , """
     <!DOCTYPE html>
     <html>
@@ -127,7 +128,7 @@
                       attrs.class?
         classStr = (".#{cls}" for cls in attrs.class.split /s+/g).join ''
         "<!-- /#{classStr} --></#{name}>"
-    , clone(htmlChunks)
+    , htmlChunks
     , """
     <!DOCTYPE html>
     <html>


Fixed typo #14

Fixed typo #14

--- app/assets/javascripts/employee_search.js.coffee
+++ app/assets/javascripts/employee_search.js.coffee
@@ -32,7 +32,7 @@
           # Create item for full search we reached the last item
           $more = ""
           if items is ul.find("li").length + 1
-            $more = $("
Visa all träffar
")
+            $more = $("
Visa alla träffar
")
               .data("ui-autocomplete-item", { path: "full-search", value: requestTerm})
           ul.addClass('search_users')
           $("
")


Corrige el bug con el resaltado del menu


OX-343 Seperate buildTreeList in extra function, attach comments to nodes

--- app/assets/javascripts/oxalis/model/route.coffee
+++ app/assets/javascripts/oxalis/model/route.coffee
@@ -120,14 +120,18 @@
     for branchPoint in @branchStack
       result.branchPoints.push({id : branchPoint.id})
     result.editPosition = @flycam.getGlobalPos()
-    result.comments = @comments
-    result.trees = []
+    #result.comments = @comments
+    result.trees = @buildTreesList()
+    return result
+
+  buildTreesList : ->
+    trees = []
     for tree in @trees
       # Don't save empty trees (id is null)
       if tree.nodes.length
         nodes = tree.nodes
         treeObj = {}
-        result.trees.push(treeObj)
+        trees.push(treeObj)
         treeColor = new THREE.Color(tree.color)
         treeObj.color = [treeColor.r, treeColor.g, treeColor.b, 1]
         treeObj.edges = []
@@ -140,19 +144,21 @@
         treeObj.nodes = []
         # Get Nodes
         for node in nodes
-          treeObj.nodes.push({
+          nodeObj = {
             id : node.id
             position : node.pos
             radius : node.radius
+            comment : ""
             # TODO: Those are dummy values
             viewport : 0
             timestamp : node.time
             resolution : 0
-          })
-
-#    console.log "NML-Objekt"
-#    console.log result
-    return result
+          }
+          for comment in @comments
+            if comment.node == nodeObj.id
+              nodeObj.comment = comment.content
+          treeObj.nodes.push(nodeObj)
+    return trees

   push : ->


Removed obsolete methods and made update more linear. Also fixed an issue with backspace & images

--- lib/ecrire/app/assets/javascripts/admin/editor/content.coffee
+++ lib/ecrire/app/assets/javascripts/admin/editor/content.coffee
@@ -40,11 +40,8 @@

   outdated: (observedMutations) =>
     @observer.hold =>
-      mutations = new Mutations(observedMutations)
-      switch mutations.status()
-        when 'appended' then @appended(mutations)
-        when 'updated' then @updated(mutations)
-        when 'removed' then @removed(mutations)
+      mutations = new Mutations(observedMutations, @element())
+      @updated(mutations)

     event = new CustomEvent('Editor:updated', {bubbles: true})
     @element().dispatchEvent(event)
@@ -84,6 +81,9 @@

   updated: (mutations) ->
+    if mutations.target.contentEditable == 'false'
+      return
+
     node = mutations.target
     while node? && node.parentElement != @element()
       node = node.parentElement
@@ -109,75 +109,6 @@
       line = cursor.focus(@updateDOM(node, lines)[0])
       cursor.update(@walker(line))
       @scrollLineIntoView(cursor.focus())
-
-
-  removed: (mutations) =>
-    line = node = mutations.target
-    while line? && line.parentElement != @element()
-      line = line.parentElement
-
-    if node.nodeType != 1 && node instanceof HTMLBRElement
-      node.remove()
-
-    if line?
-      sel = window.getSelection()
-      if sel.type == 'None'
-        offset = 0
-      else
-        offset = @lineOffset(line, sel.focusNode, sel.focusOffset)
-
-      cursor = new Editor.Cursor(offset)
-
-      lines = @parse(@cloneNodesFrom(line))
-
-      @observer.hold =>
-        line = cursor.focus(@updateDOM(line, lines)[0])
-        cursor.update(@walker(line))
-
-    if @element().childNodes.length == 0
-      p = "<p>".toHTML()
-      @element().appendChild(p)
-      cursor = new Editor.Cursor(offset)
-      cursor.update(@walker(p), 0)
-
-
-
-  appended: (mutations) =>
-    el = mutations.target
-    while el? && el.parentElement != @element()
-      el = el.parentElement
-
-    return unless el?
-
-    elements = el.querySelectorAll('[contenteditable=false]')
-
-    for element in elements
-      if element.contains(node)
-        return
-
-    if node instanceof HTMLBRElement
-      node.remove()
-      return
-
-    node = el
-
-    sel = window.getSelection()
-    if sel.type == 'None'
-      offset = 0
-    else
-      offset = @lineOffset(el, sel.focusNode, sel.focusOffset)
-
-    cursor = new Editor.Cursor(offset)
-
-    lines = @parse(@cloneNodesFrom(node))
-
-    @observer.hold =>
-      lines = @updateDOM(node, lines)
-      if node != lines[0]
-        cursor.update(@walker(lines[0]))
-      @scrollLineIntoView(lines[0])
-
-

   scrollLineIntoView: (line) =>
     height = window.innerHeight
@@ -276,15 +207,8 @@
     node1 = node1.cloneNode(true)
     node2 = node2.cloneNode(true)

-    for el in node1.querySelectorAll('[contenteditable=false]')
-      el.remove()
-
-    for el in node2.querySelectorAll('[contenteditable=false]')
-      el.remove()
-
     node1.nodeName == node2.nodeName &&
     node1.innerHTML.trim() == node2.innerHTML.trim()
-

@@ -394,13 +318,13 @@

 class Mutations
-  constructor: (mutations) ->
+  constructor: (mutations, editor) ->
     @removed = []
     @appended = []
     @updated = []

     for mutation in mutations
-      @setTarget(mutation.target)
+      @setTarget(mutation.target, editor)
       if mutation.addedNodes.length > 0
         @appended.push mutation.target
       else if mutation.type == 'characterData'
@@ -408,8 +332,8 @@
       else
         @removed.push mutation.target

-  setTarget: (target) ->
-    return unless target.parentElement?
+  setTarget: (target, editor) ->
+    return unless target.parentElement? && target != editor
     if @target? && @target.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING
       @target = target
     else


don't require a disabled message on publishable models

test plan:
 - on the wiki pages index (/course/:course_id/pages)
 * clicking the publish icon for a page should publish the page
 * clicking the icon again should unpublish the page

fixes #CNVS-7101

Change-Id: I5cba153f57b771866b3f6ce17cb27ba150e05789
Reviewed-on: https://gerrit.instructure.com/22618
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Bracken Mosbacker <48b846001bca4166dc3fb7711563afc17525921e@instructure.com>
Product-Review: Bracken Mosbacker <48b846001bca4166dc3fb7711563afc17525921e@instructure.com>
QA-Review: August Thornton <9adc7a1161ddf32ff608de792a7e50179545f026@instructure.com>

--- app/coffeescripts/views/PublishButtonView.coffee
+++ app/coffeescripts/views/PublishButtonView.coffee
@@ -120,7 +120,7 @@
       @reset()
       @$el.addClass buttonClass

-      title = if @isDisabled() then @model.disabledMessage() else text
+      title = if @model.get('publishable') then text else @model.disabledMessage()
       @$el.attr 'title', title
       @$el.attr 'aria-pressed', buttonClass is @publishedClass


Add comment indicating consideration when changing the control_iframe control

--- app/js/jail_iframe/classes/control_iframe.coffee
+++ app/js/jail_iframe/classes/control_iframe.coffee
@@ -1,4 +1,8 @@
 control_visibility_transition_time = 300+1000/60 #keep in sync with scss
+
+# future work: ideally, we'd help consumers of this class to load
+# complete dom fragments including event hooks into the frame,
+# thus avoiding then need for frame introspection.

 class FactlinkJailRoot.ControlIframe
   constructor: ->


exchange coordination

exchange coordination

--- atmospheric/002-accumulated-heat/viewer.coffee
+++ atmospheric/002-accumulated-heat/viewer.coffee
@@ -80,9 +80,9 @@
     contour = (positioning, heatdata) ->
         for idx in [1...256]
             value = sc / 256 * idx
-            for row in [0...256]
+            for col in [0...256]
                 points = find(heatdata[row], value)
-                for col in points
+                for row in points
                     [x, y] = positioning(lng(col), lat(row))
                     if x != -1 && y != -1
                         context.fillStyle = colorHeat(idx)


Init location_carousel

Init location_carousel

--- apps/galleries_institutions2/client/index.coffee
+++ apps/galleries_institutions2/client/index.coffee
@@ -1,13 +1,18 @@
 { CURRENT_USER } = require('sharify').data
 initPartnerCarousel = require '../components/partner_cell_carousel/index.coffee'
 initPrimaryCarousel = require '../components/primary_carousel/index.coffee'
+initLocationCarousel = require '../components/location_carousel/index.coffee'
 { Following, FollowButton } = require '../../../components/follow_button/index.coffee'
 Profile = require '../../../models/profile.coffee'
 PartnerCell = require '../components/partner_cell/view.coffee'

 module.exports.init = ->
+  initPrimaryCarousel()

-  initPrimaryCarousel()
+  initLocationCarousel()
+    .then ($el) ->
+      $('#partners-page-featured-specialties').prepend $el
+      initPartnerCarousel $el

   $('.partner-category-carousel').map ->
     initPartnerCarousel(this)


Jax.Mesh.PLY: `size` option

Jax.Mesh.PLY: `size` option

--- lib/assets/javascripts/jax/builtin/meshes/ply.js.coffee
+++ lib/assets/javascripts/jax/builtin/meshes/ply.js.coffee
@@ -10,6 +10,7 @@
   ###
   constructor: (options) ->
     if typeof options is 'string' then options = path: options
+    @size = 1
     @path = options?.path
     @method = options?.method || "GET"
     delete options.path if options?.path
@@ -23,21 +24,33 @@
     xhr.onreadystatechange = ->
       if xhr.readyState is xhr.DONE
         if xhr.status is 200
-          self.parser = new Jax.Mesh.PLY.Parser xhr.response# || xhr.responseText
+          self.parser = new Jax.Mesh.PLY.Parser xhr.response
           self.rebuild()
         else throw new Error "Request for #{self.path} returned status #{xhr.status}"
     xhr.open @method, @path
     xhr.send()

   init: (vertices, colors, textures, normals, indices) ->
+    return unless @parser
+    dist = vec3.create()
+    max = 0
     for vertex in (@parser.vertex || @parser.vertices)
       vertices.push vertex.x, vertex.y, vertex.z
-      normals.push vertex.nx, vertex.ny, vertex.nz unless vertex.nx is undefined
+      [dist[0], dist[1], dist[2]] = [vertex.x, vertex.y, vertex.z]
+      len = vec3.length dist
+      max = len if max < len
+      if vertex.nx isnt undefined
+        normals.push vertex.nx, vertex.ny, vertex.nz
       # intensity is used for grayscale, if colors are omitted
       intensity = (if vertex.intensity is undefined then 1 else vertex.intensity)
       if vertex.red is undefined then [red, green, blue] = [1, 1, 1]
       else [red, green, blue] = [vertex.red, vertex.green, vertex.blue]
-      colors.push vertex.red * intensity, vertex.green * intensity, vertex.blue * intensity, 1
+      colors.push red * intensity, green * intensity, blue * intensity, 1
+
+    # Use max to scale the entire mesh into unit size, and then resize it
+    # to @size
+    for v in [0...vertices.length]
+      vertices[v] = vertices[v] / max * @size

     for face in @parser.face
       for index in (face.vertex_index || face.vertex_indices)


Do not produce an error when Pygments is not present, just output plaintext code blocks.

--- src/docco.coffee
+++ src/docco.coffee
@@ -114,12 +114,8 @@
   code = (section.codeText for section in sections).join language.codeSplitText
   docs = (section.docsText for section in sections).join language.docsSplitText

-  pygments.stderr.on 'data',  (error)  ->
-    console.error error.toString() if error
-
-  pygments.stdin.on 'error',  (error)  ->
-    console.error 'Could not highlight code, using plaintext'
-
+  pygments.stderr.on 'data', ->
+  pygments.stdin.on 'error', ->
   pygments.stdout.on 'data', (result) ->
     output += result if result


don't call action from the store, prevent dup load

--- tutor/src/flux/reference-book-exercise.coffee
+++ tutor/src/flux/reference-book-exercise.coffee
@@ -33,7 +33,8 @@
 ReferenceBookExerciseConfig = {
   _toSeparate: {}

-  loadMultiple: (multipleUrls, baseUrl) ->
+  loadMultiple: (multipleUrls) ->
+    baseUrl = _.first(multipleUrls).split(QUERY_START_STRING)[0]
     {queryString, urlsAndTags} = combineQueries(multipleUrls, baseUrl)
     url = "#{baseUrl}?q=#{queryString}"

@@ -66,13 +67,6 @@
         )
         .isObject()
         .value()
-
-    getMultipleUrl: (multipleUrls) ->
-      baseUrl = _.first(multipleUrls).split(QUERY_START_STRING)[0]
-      url = getMultipleUrl(multipleUrls, baseUrl)
-      @loadMultiple(multipleUrls, baseUrl) unless @_toSeparate[url]?
-
-      url
 }

 extendConfig(ReferenceBookExerciseConfig, new CrudConfig())


Added module name and version to right label of snippets

--- lib/autocompletes.coffee
+++ lib/autocompletes.coffee
@@ -44,6 +44,7 @@
       suggestion:
         snippet: format(snippet, definitions)
         displayText: name
+        rightLabelHTML: buildRightLabel(module, minVersion, maxVersion)
         type: 'snippet'
         iconHTML: '<i class="icon-ss"></i>'
         className: 'suggestion-ss'
@@ -78,10 +79,25 @@

 buildPrefix = (prefix, module, minVersion, maxVersion) ->
   prefix = [prefix.trim()]
-  if module? then prefix.push(module.replace(/^[\w-]*\//, ''))
-  if minVersion? and maxVersion?
-    if minVersion? then prefix.push(minVersion) else prefix.push('0')
-    if maxVersion? then prefix.push(maxVersion) else prefix.push('+')
+  if module? then prefix.push(moduleName(module))
+  if minVersion? or maxVersion?
+    prefix.push(moduleVersionString(minVersion, maxVersion))
   prefix.join('_')

+buildRightLabel = (module, minVersion, maxVersion) ->
+  label = []
+  if module? then label.push(moduleName(module))
+  if minVersion? or maxVersion?
+    label.push(moduleVersionString(minVersion, maxVersion))
+  label.join(' ')
+
+moduleName = (module) ->
+  module.replace(/^[\w-]*\//, '')
+
+moduleVersionString = (minVersion, maxVersion) ->
+  string = []
+  if minVersion? then string.push(minVersion) else string.push('0')
+  if maxVersion? then string.push('-' + maxVersion) else string.push('+')
+  string.join('')
+
 module.exports = new Autocompletes


Input: Fixed pressing Windows-Key while in content resizable input.

--- src/elements/Input/Input.coffee
+++ src/elements/Input/Input.coffee
@@ -192,9 +192,9 @@

 	setSpellcheck: (spellcheck) ->
 		if spellcheck
-			DOM.setAttribute(@__input0, "spellcheck", "default")
-		else
-			DOM.setAttribute(@__input0, "spellcheck", "false")
+			DOM.setAttribute(@__input, "spellcheck", "default")
+		else
+			DOM.setAttribute(@__input, "spellcheck", "false")

 	setPlaceholder: (placeholder) ->
 		DOM.setAttribute(@__input[0], "placeholder", placeholder)
@@ -260,7 +260,7 @@
 					return

 				# backspace and the cursor is slim and at the beginning
-				if ev.keyCode() == 8 and 0 == @__input0.selectionStart == @__input0.selectionEnd
+				if ev.keyCode() == 8 and 0 == @__input.selectionStart == @__input.selectionEnd
 					return

 				# if @_content_size and @_textarea and not @preventInvalidInput()
@@ -321,11 +321,11 @@
 			type: "mousedown"
 			node: @__input
 			call: (ev) =>
-				oldSizes = [@__input0.offsetWidth, @__input0.offsetHeight]
+				oldSizes = [@__input.offsetWidth, @__input.offsetHeight]

 				trigger = =>
-					if oldSizes[0] != @__input0.offsetWidth or
-						oldSizes[1] != @__input0.offsetHeight
+					if oldSizes[0] != @__input.offsetWidth or
+						oldSizes[1] != @__input.offsetHeight
 							Events.trigger
 								type: "content-resize"
 								node: @__input
@@ -356,6 +356,7 @@
 			type: "blur"
 			node: @__input
 			call: (ev) =>
+
 				if @hasShadowFocus()
 					return

@@ -375,8 +376,8 @@
 					@checkInput()
 					@moveCursor(ev)
 					@showCursor(ev)
-					if @getValueForStore(@__input0.value) != @getValue()
-						@storeValue(@__input0.value)
+					if @getValueForStore(@__input.value) != @getValue()
+						@storeValue(@__input.value)
 				return

 		Events.listen
@@ -393,8 +394,6 @@
 				@_onClick?(@, ev)
 				return

-		@__input0 = @__input[0]
-
 		# CUI.debug "listening for dom insert on ", @__input
 		#
 		if @_content_size
@@ -413,8 +412,8 @@
 			# CUI.debug "focus?", @hasClass("focus")
 			@initCursor(ev)
 			if @cursor == null and
-				(s = @__input0.selectionStart) == @__input0.selectionEnd and
-				@__input0.selectionEnd != @__input0.value.length
+				(s = @__input.selectionStart) == @__input.selectionEnd and
+				@__input.selectionEnd != @__input.value.length
 					blocks = @getInputBlocks()
 					if blocks.length > 0
 						for block in blocks
@@ -442,7 +441,7 @@
 		"cui-input-"+@getUniqueId()

 	markBlock: (ev, bl) ->
-		@__input0.setSelectionRange(bl.start, bl.end)
+		@__input.setSelectionRange(bl.start, bl.end)
 		@initCursor(ev)

 	remove: ->
@@ -455,9 +454,9 @@

 		# CUI.debug "focus shadow input"
 		@__shadow_focused = true
-		@__shadow0.value = @__input0.value
-		@__shadow0.focus()
-		@__shadow0.setSelectionRange(@__input0.selectionStart, @__input0.selectionEnd)
+		@__shadow.value = @__input.value
+		@__shadow.focus()
+		@__shadow.setSelectionRange(@__input.selectionStart, @__input.selectionEnd)

 	__unfocusShadowInput: ->
 		if not @hasShadowFocus()
@@ -465,7 +464,7 @@

 		# CUI.debug "unfocus shadow input"
 		@setContentSize()
-		@__input0.focus()
+		@__input.focus()
 		@showCursor()
 		@__shadow_focused = false

@@ -497,9 +496,8 @@
 		@__contentSize = $element("textarea", "cui-input-shadow", tabindex: "-1", autocomplete: "off")

 		@__contentSize.appendTo(document.body)
-		@__contentSize0 = @__contentSize[0]
-
-		style = window.getComputedStyle(@__input0)
+
+		style = window.getComputedStyle(@__input)

 		# CUI.debug style

@@ -530,7 +528,7 @@
 		if @_textarea
 			DOM.width(@__contentSize, DOM.width(@__contentSize))
 			@__max_height = parseInt(@__input.css("max-height"))
-			@__input0.style.overflow = "hidden"
+			@__input.style.overflow = "hidden"

 			if isNaN(@__max_height)
 				@__max_height = null
@@ -543,29 +541,32 @@

 	__setContentSize: ->

-		@__contentSize0.value = @__input0.value
+		if not @__contentSize
+			return
+
+		@__contentSize.value = @__input.value

 		if @hasShadowFocus()
 			# we can only do this when shadow is focused,
-			# otherwise the "blur" event on @__input0
+			# otherwise the "blur" event on @__input
 			# will remove @__contentSize and we will run into errors
-			@__contentSize0.focus()
+			@__contentSize.focus()

 		changed = false

 		if @_textarea
-			if @__input0.value.length == 0
-				@__contentSize0.value = "A" # help IE out, so we get a height
+			if @__input.value.length == 0
+				@__contentSize.value = "A" # help IE out, so we get a height

 			if DOM.width(@__input) != DOM.width(@__contentSize)
 				DOM.width(@__contentSize, DOM.width(@__input))

-			h = @__contentSize0.scrollHeight
+			h = @__contentSize.scrollHeight

 			if @__max_height == null or h <= @__max_height
-				@__input0.style.overflow = "hidden"
+				@__input.style.overflow = "hidden"
 			else
-				@__input0.style.overflow = ""
+				@__input.style.overflow = ""

 			previous_height = DOM.height(@__input)
 			DOM.height(@__input, h)
@@ -573,10 +574,10 @@
 			if DOM.height(@__input) != previous_height
 				changed = true

-			# CUI.error "__setContentSize", @_textarea, @__input0.value, @__contentSize0.value, h
-		else
-			w = @__contentSize0.scrollWidth
-			if @__contentSize0.value.length == 0
+			# CUI.error "__setContentSize", @_textarea, @__input.value, @__contentSize.value, h
+		else
+			w = @__contentSize.scrollWidth
+			if @__contentSize.value.length == 0
 				# help IE out here, IE measures one " "
 				w = 1
 			else
@@ -607,16 +608,16 @@

 	getInputBlocks: ->
 		if @_getInputBlocks
-			blocks = @_getInputBlocks(@__input0.value)
+			blocks = @_getInputBlocks(@__input.value)
 		else if @_getCursorBlocks
-			blocks = @_getCursorBlocks(@__input0.value)
-		else
-			blocks = @__getInputBlocks(@__input0.value)
+			blocks = @_getCursorBlocks(@__input.value)
+		else
+			blocks = @__getInputBlocks(@__input.value)
 		@checkBlocks(blocks)

 	__getInputBlocks: (v) ->
 		blocks = []
-		v =  @__input0.value
+		v =  @__input.value
 		re = /[0-9]+/g
 		blocks = []
 		while (match = re.exec(v)) != null
@@ -660,8 +661,8 @@
 		if blocks == false or blocks.length == 0
 			return null

-		s = @__input0.selectionStart
-		e = @__input0.selectionEnd
+		s = @__input.selectionStart
+		e = @__input.selectionEnd

 		for block, idx in blocks
 			# CUI.debug match_start, match_end, match_str
@@ -670,23 +671,23 @@
 		return null

 	getSelection: ->
-		s = @__input0.selectionStart
-		e = @__input0.selectionEnd
+		s = @__input.selectionStart
+		e = @__input.selectionEnd

 		start: s
 		end: e
-		value: @__input0.value
-		before: @__input0.value.substring(0, s)
-		selected: @__input0.value.substring(s, e)
-		after: @__input0.value.substring(e)
+		value: @__input.value
+		before: @__input.value.substring(0, s)
+		selected: @__input.value.substring(s, e)
+		after: @__input.value.substring(e)

 	setSelection: (selection) ->
-		@__input0.selectionStart = selection.start
-		@__input0.selectionEnd = selection.end
+		@__input.selectionStart = selection.start
+		@__input.selectionEnd = selection.end

 	selectAll: ->
-		@__input0.selectionStart = 0
-		@__input0.selectionEnd = @__input0.value.length
+		@__input.selectionStart = 0
+		@__input.selectionEnd = @__input.value.length
 		@

 	updateSelection: (txt="") ->
@@ -701,7 +702,7 @@
 	setValue: (v, flags = {}) ->

 		if not @hasData()
-			@__input0?.value = v
+			@__input?.value = v
 			@setContentSize()

 		super(v, flags)
@@ -710,9 +711,9 @@
 		if ev.keyCode() not in [38, 40, 33, 34] # not in TAB
 			return

-		s = @__input0.selectionStart
-		e = @__input0.selectionEnd
-		v =  @__input0.value
+		s = @__input.selectionStart
+		e = @__input.selectionEnd
+		v =  @__input.value

 		blocks = @getInputBlocks()
 		if blocks == false or blocks.length == 0
@@ -780,9 +781,9 @@
 				ev.preventDefault()
 				return

-			@__input0.value = new_value
+			@__input.value = new_value
 			@markBlock(ev, bl)
-			@storeValue(@__input0.value)
+			@storeValue(@__input.value)
 			ev.preventDefault()

 		return
@@ -790,7 +791,6 @@
 	__removeContentSize: ->
 		@__contentSize?.remove()
 		@__contentSize = null
-		@__contentSize0 = null
 		@

 	__removeShadowInput: ->
@@ -825,7 +825,6 @@
 		@__shadow.prop("tabindex", "-1")
 		@__shadow.prop("autocomplete", "off")
 		@__shadow.appendTo(document.body)
-		@__shadow0 = @__shadow[0]

 		if @_content_size
 			@__initContentSize()
@@ -857,7 +856,7 @@

 	__shadowInput: (ev) ->
-		shadow_v = @__shadow0.value
+		shadow_v = @__shadow.value

 		if @_rows and shadow_v.split("\n").length > @_rows
 			return
@@ -869,8 +868,8 @@
 				return

 		if not @_readonly
-			@__input0.value = @correctValueForInput(shadow_v)
-			@__input0.setSelectionRange(@__shadow0.selectionStart, @__shadow0.selectionEnd)
+			@__input.value = @correctValueForInput(shadow_v)
+			@__input.setSelectionRange(@__shadow.selectionStart, @__shadow.selectionEnd)

 		# CUI.debug "shadow before init cursor", @cursor?.start.idx, "-", @cursor?.end.idx
 		@initCursor(ev)
@@ -935,18 +934,18 @@

 	leaveInput: ->
 		if @getInputState() != "invalid"
-			@__input0.value = @getValueForDisplay()
+			@__input.value = @getValueForDisplay()
 			@checkInput()
 		@

 	enterInput: ->
 		if @getInputState() != "invalid"
-			@__input0.value = @getValueForInput()
+			@__input.value = @getValueForInput()
 			@checkInput()
 		@

 	hasUserInput: ->
-		@__input0.value.length > 0
+		@__input.value.length > 0

 	checkInput: (value) ->
 		state = @__checkInputInternal(value)
@@ -954,7 +953,7 @@
 			@updateInputState(state)
 		state

-	__checkInputInternal: (value = @__input0.value) ->
+	__checkInputInternal: (value = @__input.value) ->
 		if @__checkInput
 			@__checkInput(value)
 		else
@@ -972,9 +971,9 @@
 	displayValue: ->
 		super()
 		value = @getValueForDisplay()
-		if value != @__input0.value
+		if value != @__input.value
 			# prevent focus loss if value is the same
-			@__input0.value = value
+			@__input.value = value
 		@checkInput()
 		@

@@ -1003,7 +1002,7 @@
 		if @hasData()
 			super()
 		else
-			@__input0?.value
+			@__input?.value

 	enable: ->
 		super()
@@ -1014,11 +1013,11 @@
 		@__input?.prop("disabled", true)

 	focus: ->
-		@__input0?.focus()
+		@__input?.focus()
 		@

 	getCursorBlocks: ->
-		blocks = @__getCursorBlocks?(@__input0.value)
+		blocks = @__getCursorBlocks?(@__input.value)
 		@checkBlocks(blocks)

 	findBlock: (blocks, idx, cut) ->
@@ -1051,9 +1050,9 @@
 		# find block which fits the current selection
 		# positions
 		#
-		s = @__input0.selectionStart
-		e = @__input0.selectionEnd
-		len = @__input0.value.length
+		s = @__input.selectionStart
+		e = @__input.selectionEnd
+		len = @__input.value.length

 		# CUI.debug "requested: start: ",s, "end: ",e

@@ -1126,7 +1125,7 @@

 		if @cursor
 			r = @getRangeFromCursor()
-			@__input0.setSelectionRange(r[0], r[1])
+			@__input.setSelectionRange(r[0], r[1])
 		@

 	checkSelectionChange: ->


Prefix names of nested objects.

Prefix names of nested objects.

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -59,11 +59,11 @@
     else
       createList column.name, data

-convertTableToFrame = (table, metadata) ->
+convertTableToFrame = (table, tableName, metadata) ->
   #TODO handle format strings and description
   vectors = for column, i in table.columns
     convertColumnToVector column, table.data[i]
-  createDataframe table.name, vectors, (sequence table.rowcount), null, metadata
+  createDataframe tableName, vectors, (sequence table.rowcount), null, metadata

 combineTables = (tables) ->
   leader = head tables
@@ -297,7 +297,7 @@
     render_ logFile, H2O.LogFileOutput, cloud, nodeIndex, fileType, logFile

   inspectNetworkTestResult = (testResult) -> ->
-    convertTableToFrame testResult.table,
+    convertTableToFrame testResult.table, testResult.table.name,
       description: testResult.table.name
       origin: "testNetwork"

@@ -390,8 +390,8 @@
   extendDeletedKeys = (keys) ->
     render_ keys, H2O.DeleteObjectsOutput, keys

-  inspectTwoDimTable_ = (origin, table) -> ->
-    convertTableToFrame table,
+  inspectTwoDimTable_ = (origin, tableName, table) -> ->
+    convertTableToFrame table, tableName,
       description: table.name
       origin: origin

@@ -436,12 +436,14 @@

     record = {}

+    inspections[name] = inspectRawObject_ name, origin, name, record
+
     for k, v of obj when not blacklist[k]
       if v is null
         record[k] = null
       else
         if v.__meta?.schema_type is 'TwoDimTable'
-          inspections[v.name] = inspectTwoDimTable_ origin, v
+          inspections["#{name} - #{v.name}"] = inspectTwoDimTable_ origin, "#{name} - #{v.name}", v
         else
           if isArray v
             inspections[k] = inspectRawArray_ k, origin, k, v
@@ -454,13 +456,13 @@
               else if meta.schema_type is 'Frame'
                 record[k] = v.key.name
               else
-                inspectObject inspections, k, origin, v
+                inspectObject inspections, "#{name} - #{k}", origin, v
             else
               console.log "WARNING: dropping [#{k}] from inspection:", v
           else
             record[k] = v

-    inspections[name] = inspectRawObject_ name, origin, name, record
+    return

   extendModel = (model) ->
     inspections = {}


refactored ,/enter keypress listener to use keypress vs keyCode, which now doesn't submit form for enter's

--- public/dreamcatcher/controllers/common/tags_controller.coffee
+++ public/dreamcatcher/controllers/common/tags_controller.coffee
@@ -99,8 +99,9 @@

   '.tagHeader click': -> @expandContractInputField()

-  '.newTag keyup': (el, ev) ->
-    if ev.keyCode is 188 or ev.keyCode is 13 or ev.keyCode is 191 # ',', '/' and 'enter'
+  '.newTag keypress': (el, ev) ->
+    keyCode = ev.which
+    if keyCode is 44 or keyCode is 47 or keyCode is 13 # ',', '/' and 'enter'
       @addTag()
       return false


Another missed update to the new beatmapset discussion setter

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -178,7 +178,7 @@

       @nextTimeout = @checkNewTimeoutDefault

-      @setState beatmapsetDiscussion: data.beatmapsetDiscussion.data
+      @setBeatmapsetDiscussion beatmapsetDiscussion: data.beatmapsetDiscussion.data

     .always =>
       @nextTimeout = Math.min @nextTimeout, @checkNewTimeoutMax


Tunnel events needs a custom name

Tunnel events needs a custom name

--- source/core/event.coffee
+++ source/core/event.coffee
@@ -100,12 +100,15 @@
       # Recursive Callback
       callback = args[0].eventCustomCallback unless callback

-    # Default Callback
-    unless callback
-      constructor = if args.length is 1 then @constructor else args[1].constructor
-      base = @_base constructor
-      base = constructor.name if constructor.extends
-      callback = "on#{base.toClassName()}#{event.toClassName()}"
+      # Default Callback
+      unless callback
+        constructor = if args.length is 1 then @constructor else args[1].constructor
+        base = @_base constructor
+        base = constructor.name if constructor.extends
+        callback = "on#{base.toClassName()}#{event.toClassName()}"
+
+    else if type is "tunnel"
+      callback = event

     # Add type
     args[0].eventType = type


coffee

coffee

--- src/index.coffee
+++ src/index.coffee
@@ -8,7 +8,7 @@
   result = {}
   $ = cheerio.load(html)
   metaTags = $('meta').filter ->
-    if !@attribs.name
+    if not @attribs.name
       return false
     @attribs.name.match 'twitter:'


global fix

global fix

--- src/index.coffee
+++ src/index.coffee
@@ -1,246 +1,272 @@
-GMailUI = {}
-
-class GMailUI.Helper
-  @hover: (target, hoverClass) ->
-    target.hover ((e) ->
-      target.addClass hoverClass
-      return
-      ),
-      ((e) ->
-        target.removeClass hoverClass
-        return
-      )
-    return
-
-class GMailUI.UIElement
-  constructor: (html) ->
-    @setElement html
-
-  element: null
-  getElement: -> @element
-  setElement: (e) ->
-    if _.isString e or _.isElement e
-      @element = $ e
-    else
-      @element = e
-    @element
-
-  addClass: ->
-    @element.addClass.apply @element, arguments
-    return
-
-  removeClass: ->
-    @element.removeClass.apply @element, arguments
-    return
-
-  css: ->
-    @element.css.apply @element, arguments
-    return
-
-  show: ->
-    @element.show()
-    return
-
-  hide: ->
-    @element.hide()
-    return
-
-  toggle: ->
-    @element.toggle.apply @element, arguments
-    return
-
-  replaceWith: (e) ->
-    if e instanceof GMailUI.UIElement
-      @element.replaceWith e.getElement()
-    else if _.isElement
-      @element.replaceWith e
-    else
-      throw new Error 'Unknown element'
-    e
-
-class GMailUI.Container extends GMailUI.UIElement
-  constructor: (@container) ->
-
-  setContainer: (@container) ->
-  getContainer: -> @container
-
-  append: (e) ->
-    if e instanceof GMailUI.UIElement
-      @container.append e.getElement()
-    else if _.isElement
-      @container.append e
-    else
-      throw new Error 'Unknown element'
-    e
-
-class GMailUI.OnAble extends GMailUI.UIElement
-  constructor: (hoverClass, @selectedClass) ->
-    GMailUI.Helper.hover @getElement(), hoverClass if hoverClass
-
-  toggle: (target, selected) ->
-    target.toggleClass @selectedClass, selected
-    target.attr 'aria-checked', if selected then 'true' else 'false'
-    return
-
-  setSelected: (selected, exclusive = false) ->
-    throw (new Error 'Not available for this element') if not @selectedClass
-    target = @getElement()
-    @toggle target, selected
-    if exclusive and selected
-      @toggle target.siblings(), false
-    return
-
-  # Toggling checkboxes
-  addOnChange: (onChange, exclusive = false) ->
-    throw (new Error 'Not available for this element') if not @selectedClass
-    @on 'click', (e) =>
-      e = @getElement()
-      checked = e.hasClass @selectedClass
-
-      # We can't deselect the selected item if we are in exclusive mode
-      return if exclusive and checked
-
-      if @menu instanceof GMailUI.PopupMenuItem
-        @menu.setSelected !checked, true
-
-      @setSelected !checked, exclusive
-      onChange? e, !checked
-      return
-    return
-
-  on: (type, f) =>
-    element = @getElement()
-    element.on.apply element, arguments
-
-class GMailUI.Breadcrumbs
-  @LIST_SEL: 'ol.gbtc'
-  @markup: _.template """
-

-
-
-                <%- label %>
-                <% if(isMenu) { %>
-
+((root) ->
+  GMailUI = {}
+  root.GMailUI = GMailUI
+
+  class GMailUI.Helper
+    @hover: (target, hoverClass) ->
+      target.hover ((e) ->
+        target.addClass hoverClass
+        return
+        ),
+        ((e) ->
+          target.removeClass hoverClass
+          return
+        )
+      return
+
+  class GMailUI.UIElement
+    constructor: (html) ->
+      @setElement html
+
+    element: null
+    getElement: -> @element
+    setElement: (e) ->
+      if _.isString e or _.isElement e
+        @element = $ e
+      else
+        @element = e
+      @element
+
+    addClass: ->
+      @element.addClass.apply @element, arguments
+      return
+
+    removeClass: ->
+      @element.removeClass.apply @element, arguments
+      return
+
+    css: ->
+      @element.css.apply @element, arguments
+      return
+
+    show: ->
+      @element.show()
+      return
+
+    hide: ->
+      @element.hide()
+      return
+
+    toggle: ->
+      @element.toggle.apply @element, arguments
+      return
+
+    replaceWith: (e) ->
+      if e instanceof GMailUI.UIElement
+        @element.replaceWith e.getElement()
+      else if _.isElement
+        @element.replaceWith e
+      else
+        throw new Error 'Unknown element'
+      e
+
+  class GMailUI.Container extends GMailUI.UIElement
+    constructor: (@container) ->
+
+    setContainer: (@container) ->
+    getContainer: -> @container
+
+    append: (e) ->
+      if e instanceof GMailUI.UIElement
+        @container.append e.getElement()
+      else if _.isElement
+        @container.append e
+      else
+        throw new Error 'Unknown element'
+      e
+
+  class GMailUI.OnAble extends GMailUI.UIElement
+    constructor: (hoverClass, @selectedClass) ->
+      GMailUI.Helper.hover @getElement(), hoverClass if hoverClass
+
+    toggle: (target, selected) ->
+      target.toggleClass @selectedClass, selected
+      target.attr 'aria-checked', if selected then 'true' else 'false'
+      return
+
+    setSelected: (selected, exclusive = false) ->
+      throw (new Error 'Not available for this element') if not @selectedClass
+      target = @getElement()
+      @toggle target, selected
+      if exclusive and selected
+        @toggle target.siblings(), false
+      return
+
+    # Toggling checkboxes
+    addOnChange: (onChange, exclusive = false) ->
+      throw (new Error 'Not available for this element') if not @selectedClass
+      @on 'click', (e) =>
+        e = @getElement()
+        checked = e.hasClass @selectedClass
+
+        # We can't deselect the selected item if we are in exclusive mode
+        return if exclusive and checked
+
+        if @menu instanceof GMailUI.PopupMenuItem
+          @menu.setSelected !checked, true
+
+        @setSelected !checked, exclusive
+        onChange? e, !checked
+        return
+      return
+
+    on: (type, f) =>
+      element = @getElement()
+      element.on.apply element, arguments
+
+  class GMailUI.Breadcrumbs
+    @LIST_SEL: 'ol.gbtc'
+    @markup: _.template """
+

+
+
+                  <%- label %>
+                  <% if(isMenu) { %>
+
+                  <% } %>
+
+
+

+              """
+    @add: (label, onClick, isMenu = true) ->
+      item =   $ @markup
+        label: label
+        isMenu: !!isMenu
+      (item.find '.gbgt').on 'click', onClick if onClick
+      item.prependTo $ @LIST_SEL
+      item
+
+  class GMailUI.ModalDialog extends GMailUI.Container
+    @BG:   $   """
+

+          """
+    @template: _.template """
+              <div class="Kj-JD" tabindex="0" style="left: 50%; top: 40%; width: 460px; overflow: visible; margin-left: -230px; margin-top: -64px;" role="dialog" aria-labelledby="<%= id %>">
+                <div class="Kj-JD-K7 Kj-JD-K7-GIHV4" id="<%= id %>">
+                  <%- title %>
+
+

+

+

+

+              """
+              ###
+

+

+

+                    Invalid Date
+

+

+                    Select date
+
<input id="ri_selectdate" class="rbx nr">
+

+

+
+                      time (24h format)
+
+
<input id="ri_selecttime" class="rbx nr" style="width:120px" autocomplete="OFF">
+

+

+                    timezone (optional)
+
<input id="ri_selecttimezone" class="rbx nr" style="width:120px" autocomplete="OFF">
+

+

+

+

+                  <button id="ri_b2" class="J-at1-atl"> Add reminder </button>
+                  <button id="ri_b1" class="J-at1-auR"> Cancel </button>
+

+              ###
+    constructor: (title) ->
+      @setElement GMailUI.ModalDialog.template
+        id:   _.uniqueId 'modalDialog-'
+        title:   title
+      super @getElement().find "[act='container']"
+
+    title: (title) ->
+      e = (@getElement().find "[act='title']")
+      e.text.apply e, arguments
+
+    open: ->
+      body = $ 'body'
+      GMailUI.ModalDialog.BG.appendTo body
+
+      element = @getElement()
+      closeButton = element.find "[act='close']"
+      closeButton.on 'click', @close
+      element.appendTo body
+    close: =>
+      element = @getElement()
+      GMailUI.ModalDialog.BG.detach()
+      element.remove()
+      return
+
+  class GMailUI.ModalDialog.Container extends GMailUI.Container
+    constructor: ->
+      super @setElement  """
+

+                """
+
+  class GMailUI.ModalDialog.Footer extends GMailUI.Container
+    constructor: ->
+      super @setElement  """
+

+                """
+
+
+  class GMailUI.ModalDialog.Button extends GMailUI.OnAble
+    @template: _.template  """
+                <button
+                <% if(tooltip) { %>
+                data-tooltip="<%- tooltip %>"
                 <% } %>
-
-
-

-            """
-  @add: (label, onClick, isMenu = true) ->
-    item =   $ @markup
-      label: label
-      isMenu: !!isMenu
-    (item.find '.gbgt').on 'click', onClick if onClick
-    item.prependTo $ @LIST_SEL
-    item
-
-class GMailUI.ModalDialog extends GMailUI.Container
-  @BG:   $   """
-

-        """
-  @template: _.template """
-            <div class="Kj-JD" tabindex="0" style="left: 50%; top: 40%; width: 460px; overflow: visible; margin-left: -230px; margin-top: -64px;" role="dialog" aria-labelledby="<%= id %>">
-              <div class="Kj-JD-K7 Kj-JD-K7-GIHV4" id="<%= id %>">
-                <%- title %>
-
-

-

-

-

-            """
-            ###
-

-

-

-                  Invalid Date
-

-

-                  Select date
-
<input id="ri_selectdate" class="rbx nr">
-

-

-
-                    time (24h format)
-
-
<input id="ri_selecttime" class="rbx nr" style="width:120px" autocomplete="OFF">
-

-

-                  timezone (optional)
-
<input id="ri_selecttimezone" class="rbx nr" style="width:120px" autocomplete="OFF">
-

-

-

-

-                <button id="ri_b2" class="J-at1-atl"> Add reminder </button>
-                <button id="ri_b1" class="J-at1-auR"> Cancel </button>
-

-            ###
-  constructor: (title) ->
-    @setElement GMailUI.ModalDialog.template
-      id:   _.uniqueId 'modalDialog-'
-      title:   title
-    super @getElement().find "[act='container']"
-
-  title: (title) ->
-    e = (@getElement().find "[act='title']")
-    e.text.apply e, arguments
-
-  open: ->
-    body = $ 'body'
-    GMailUI.ModalDialog.BG.appendTo body
-
-    element = @getElement()
-    closeButton = element.find "[act='close']"
-    closeButton.on 'click', @close
-    element.appendTo body
-  close: =>
-    element = @getElement()
-    GMailUI.ModalDialog.BG.detach()
-    element.remove()
-    return
-
-class GMailUI.ModalDialog.Container extends GMailUI.Container
-  constructor: ->
-    super @setElement  """
-

-              """
-
-class GMailUI.ModalDialog.Footer extends GMailUI.Container
-  constructor: ->
-    super @setElement  """
-

-              """
-
-
-class GMailUI.ModalDialog.Button extends GMailUI.OnAble
-  @template: _.template  """
-              <button
-              <% if(tooltip) { %>
-              data-tooltip="<%- tooltip %>"
-              <% } %>
-              class="<%= clazz %>"><%- label %></button>
-              """
-  constructor: (label, tooltip = '', type = 'normal') ->
-
-    @setElement GMailUI.ModalDialog.Button.template
-      label:     label
-      tooltip:   tooltip
-      clazz:    switch type
-        when 'cancel' then 'J-at1-auR'
-        else 'J-at1-atl'
-
-class GMailUI.ButtonBar extends GMailUI.Container
-  constructor: ->
-    super @setElement  """
-

-              """
-
-
-
-class GMailUI.Button extends GMailUI.OnAble
-  @template: _.template  """
-

-                <div class="J-JK-Jz"
+                class="<%= clazz %>"><%- label %></button>
+                """
+    constructor: (label, tooltip = '', type = 'normal') ->
+
+      @setElement GMailUI.ModalDialog.Button.template
+        label:     label
+        tooltip:   tooltip
+        clazz:    switch type
+          when 'cancel' then 'J-at1-auR'
+          else 'J-at1-atl'
+
+  class GMailUI.ButtonBar extends GMailUI.Container
+    constructor: ->
+      super @setElement  """
+

+                """
+
+
+
+  class GMailUI.Button extends GMailUI.OnAble
+    @template: _.template  """
+

+                  <div class="J-JK-Jz"
+                    <% if(tooltip) { %>
+                      data-tooltip="<%- tooltip %>"
+                      aria-label="<%- tooltip %>"
+                    <% } %>
+                    <% if(title) { %>
+                      title="<%- title %>"
+                    <% } %>
+                    style="-webkit-user-select: none;">
+                    <%- label %>
+

+

+                """
+    @hoverClass: 'J-JK-JT'
+
+    constructor: (label, tooltip = '', title = '') ->
+      element = @setElement GMailUI.Button.template
+        label:    label
+        tooltip:  tooltip
+        title:    title
+      super GMailUI.Button.hoverClass
+
+  class GMailUI.ButtonBarButton extends GMailUI.OnAble
+    @template:   _.template   """
+                <div class="T-I J-J5-Ji T-I-Js-IF ns T-I-ax7 L3"
                   <% if(tooltip) { %>
                     data-tooltip="<%- tooltip %>"
                     aria-label="<%- tooltip %>"
@@ -248,301 +274,278 @@
                   <% if(title) { %>
                     title="<%- title %>"
                   <% } %>
-                  style="-webkit-user-select: none;">
-                  <%- label %>
-

-

-              """
-  @hoverClass: 'J-JK-JT'
-
-  constructor: (label, tooltip = '', title = '') ->
-    element = @setElement GMailUI.Button.template
-      label:    label
-      tooltip:  tooltip
-      title:    title
-    super GMailUI.Button.hoverClass
-
-class GMailUI.ButtonBarButton extends GMailUI.OnAble
-  @template:   _.template   """
-              <div class="T-I J-J5-Ji T-I-Js-IF ns T-I-ax7 L3"
-                <% if(tooltip) { %>
-                  data-tooltip="<%- tooltip %>"
-                  aria-label="<%- tooltip %>"
-                <% } %>
-                <% if(title) { %>
-                  title="<%- title %>"
-                <% } %>
-                role="button" tabindex="0" style="-webkit-user-select: none;">
-

-
-

-

-

-

-              """
-  @hoverClass: 'T-I-JW'
-  @pushedClass: 'T-I-Kq'
-
-  constructor: (label, tooltip = '', title = '') ->
-    element = @setElement GMailUI.ButtonBarButton.template
-      label:    label
-      tooltip:  tooltip
-      title:    title
-    super GMailUI.ButtonBarButton.hoverClass
-
-    # Don't focus the item when clicking on it
-    element.on 'mousedown', (e) ->
-      e.preventDefault()
-      return
-
-    element.on 'click', (e) ->
-      e.stopPropagation()
-      return
-
-class GMailUI.ButtonBarPopupButton extends GMailUI.ButtonBarButton
-  @expandedClass: 'T-I-Kq'
-
-  constructor: (@popup, label, tooltip = '', title = '') ->
-    super label, tooltip, title
-    element = @getElement()
-
-    # this button has a popup
-    element.attr 'aria-haspopup', 'true'
-
-    @on 'click', (e) =>
-      unless @popup.isAdded
-        # Add the popup markup to the DOM
-        target = element.parent().parent()
-        target.append @popup.getElement() if @popup.isAdded = (target.length > 0)
-
-      if @popup.getElement().is ':visible'
-        # Popup is visible, so close it
-        @close()
-      else
-        # As long as the popup is open, clicks anywhere else should close it
-        ($ 'body').on 'click', @close
-
-        # Add the pushed state to the button
-        element.addClass GMailUI.ButtonBarPopupButton.expandedClass
-        element.attr 'aria-expanded', 'true'
-
-        # Set the position of the popup menu right beneath the menu button (lower left corner)
-        @popup.css
-          left:   element.parent().position().left
-          top:  element.outerHeight()
-
-        # and show it :-)
-        @popup.show()
-
-  # Close the popup
-  close: =>
-    # Disable the body listener
-    ($ 'body').off 'click', @close
-
-    element = @getElement()
-    # Remove the pushed state from the menu button
-    element.removeClass GMailUI.ButtonBarPopupButton.expandedClass
-    element.attr 'aria-expanded', 'false'
-
-    # hide the popup
-    @popup.hide()
-
-class GMailUI.Popup extends GMailUI.Container
-  constructor: ->
-    element = @setElement  """
-

-

-

-

-                """
-    super element.children()
-
-    # Clicks on popup itself do not close it either
-    element.on 'click', (e) ->
-      e.stopPropagation()
-      return
-
-class GMailUI.PopupLabel extends GMailUI.UIElement
-  @template: _.template  """
-
<%- label %>

-              """
-  constructor: (label) ->
-    @setElement GMailUI.PopupLabel.template
-      label: label
-class GMailUI.PopupMenuItem extends GMailUI.OnAble
-  @template: _.template  """
-              <div class="J-N J-Ks" role="menuitemcheckbox"
-                <% if(tooltip) { %>
-                  data-tooltip="<%- tooltip %>"
-                  aria-label="<%- tooltip %>"
-                <% } %>
-                <% if(title) { %>
-                  title="<%- title %>"
-                <% } %>
-                style="-webkit-user-select: none; " aria-checked="false">
-

-

-                  <%- label %>
-                  <% if(hasChildren) { %>
-
-                  <% } %>
-

-

-              """
-
-  @hoverClass: 'J-N-JT'
-  @selectedClass: 'J-Ks-KO'
-
-  constructor: (@menu, label, tooltip = '', title = '', hasChildren = false) ->
-    element = @setElement GMailUI.PopupMenuItem.template
-      label:      label
-      tooltip:    tooltip
-      title:      title
-      hasChildren:  hasChildren
-    super GMailUI.PopupMenuItem.hoverClass, GMailUI.PopupMenuItem.selectedClass
-
-    # Don't focus the item when clicking on it
-    element.on 'mousedown', (e) ->
-      e.preventDefault()
-      return
-
-    if @menu instanceof GMailUI.PopupMenu
-      id = _.uniqueId 'PopupMenuItem-'
-      element.attr 'id', id
-
-      element.hover   ((e) =>
-        unless @menu.isAdded
+                  role="button" tabindex="0" style="-webkit-user-select: none;">
+

+
+

+

+

+

+                """
+    @hoverClass: 'T-I-JW'
+    @pushedClass: 'T-I-Kq'
+
+    constructor: (label, tooltip = '', title = '') ->
+      element = @setElement GMailUI.ButtonBarButton.template
+        label:    label
+        tooltip:  tooltip
+        title:    title
+      super GMailUI.ButtonBarButton.hoverClass
+
+      # Don't focus the item when clicking on it
+      element.on 'mousedown', (e) ->
+        e.preventDefault()
+        return
+
+      element.on 'click', (e) ->
+        e.stopPropagation()
+        return
+
+  class GMailUI.ButtonBarPopupButton extends GMailUI.ButtonBarButton
+    @expandedClass: 'T-I-Kq'
+
+    constructor: (@popup, label, tooltip = '', title = '') ->
+      super label, tooltip, title
+      element = @getElement()
+
+      # this button has a popup
+      element.attr 'aria-haspopup', 'true'
+
+      @on 'click', (e) =>
+        unless @popup.isAdded
           # Add the popup markup to the DOM
-          target = @getMenuTarget().parent()
-          target.append @menu.getElement() if @menu.isAdded = (target.length > 0)
-
-        @menu.getElement().attr 'aria-activedescendant', id
-        @menu.inMenu = true
-
-        _.delay (=>
-          return unless @menu.inMenu
-          @reposition()
-          @menu.show()
-          return), @menu.delay
-
+          target = element.parent().parent()
+          target.append @popup.getElement() if @popup.isAdded = (target.length > 0)
+
+        if @popup.getElement().is ':visible'
+          # Popup is visible, so close it
+          @close()
+        else
+          # As long as the popup is open, clicks anywhere else should close it
+          ($ 'body').on 'click', @close
+
+          # Add the pushed state to the button
+          element.addClass GMailUI.ButtonBarPopupButton.expandedClass
+          element.attr 'aria-expanded', 'true'
+
+          # Set the position of the popup menu right beneath the menu button (lower left corner)
+          @popup.css
+            left:   element.parent().position().left
+            top:  element.outerHeight()
+
+          # and show it :-)
+          @popup.show()
+
+    # Close the popup
+    close: =>
+      # Disable the body listener
+      ($ 'body').off 'click', @close
+
+      element = @getElement()
+      # Remove the pushed state from the menu button
+      element.removeClass GMailUI.ButtonBarPopupButton.expandedClass
+      element.attr 'aria-expanded', 'false'
+
+      # hide the popup
+      @popup.hide()
+
+  class GMailUI.Popup extends GMailUI.Container
+    constructor: ->
+      element = @setElement  """
+

+

+

+

+                  """
+      super element.children()
+
+      # Clicks on popup itself do not close it either
+      element.on 'click', (e) ->
+        e.stopPropagation()
+        return
+
+  class GMailUI.PopupLabel extends GMailUI.UIElement
+    @template: _.template  """
+
<%- label %>

+                """
+    constructor: (label) ->
+      @setElement GMailUI.PopupLabel.template
+        label: label
+  class GMailUI.PopupMenuItem extends GMailUI.OnAble
+    @template: _.template  """
+                <div class="J-N J-Ks" role="menuitemcheckbox"
+                  <% if(tooltip) { %>
+                    data-tooltip="<%- tooltip %>"
+                    aria-label="<%- tooltip %>"
+                  <% } %>
+                  <% if(title) { %>
+                    title="<%- title %>"
+                  <% } %>
+                  style="-webkit-user-select: none; " aria-checked="false">
+

+

+                    <%- label %>
+                    <% if(hasChildren) { %>
+
+                    <% } %>
+

+

+                """
+
+    @hoverClass: 'J-N-JT'
+    @selectedClass: 'J-Ks-KO'
+
+    constructor: (@menu, label, tooltip = '', title = '', hasChildren = false) ->
+      element = @setElement GMailUI.PopupMenuItem.template
+        label:      label
+        tooltip:    tooltip
+        title:      title
+        hasChildren:  hasChildren
+      super GMailUI.PopupMenuItem.hoverClass, GMailUI.PopupMenuItem.selectedClass
+
+      # Don't focus the item when clicking on it
+      element.on 'mousedown', (e) ->
+        e.preventDefault()
+        return
+
+      if @menu instanceof GMailUI.PopupMenu
+        id = _.uniqueId 'PopupMenuItem-'
+        element.attr 'id', id
+
+        element.hover   ((e) =>
+          unless @menu.isAdded
+            # Add the popup markup to the DOM
+            target = @getMenuTarget().parent()
+            target.append @menu.getElement() if @menu.isAdded = (target.length > 0)
+
+          @menu.getElement().attr 'aria-activedescendant', id
+          @menu.inMenu = true
+
+          _.delay (=>
+            return unless @menu.inMenu
+            @reposition()
+            @menu.show()
+            return), @menu.delay
+
+          return),
+        ((e) =>
+          @menu.inMenu = false
+          _.delay @menu.hideMenu, @menu.delay
+          return)
+
+    getMenuTarget: ->
+      @menu.parent.getElement()
+    reposition: =>
+      element = @getElement()
+      popup = @getMenuTarget()
+      ppos = popup.position()
+      @menu.css
+        top:   element.position().top + ppos.top
+        left:   ppos.left + popup.outerWidth()
+      return
+
+  class GMailUI.Section extends GMailUI.Container
+    constructor: ->
+      super @setElement   """
+

+                """
+
+  class GMailUI.Separator extends GMailUI.UIElement
+    constructor: ->
+      @setElement """
+

+            """
+
+  class GMailUI.ErrorSection extends GMailUI.Section
+    @template: _.template  """
+

+                  <%- message %>
+

+                """
+    constructor: (message) ->
+      super()
+      element = @getElement()
+      element.addClass 'b7o7Ic'
+      @append new GMailUI.Separator
+
+      @setContainer element.append $ GMailUI.ErrorSection.template
+        message: message
+    setMessage: (message) ->
+      @getContainer().html message
+      return
+
+  class GMailUI.PopupMenu extends GMailUI.Container
+    @template:  """
+

+

+          """
+
+    delay:    300
+    inMenu:    false
+
+    constructor: (@parent) ->
+      super @setElement $ GMailUI.PopupMenu.template
+      element = @getElement()
+
+      # Clicks on menu do not close the popup
+      element.on 'click', (e) ->
+        e.stopPropagation()
+        return
+
+      element.hover ((e) =>
+        @inMenu = true
         return),
       ((e) =>
-        @menu.inMenu = false
-        _.delay @menu.hideMenu, @menu.delay
+        @inMenu = false
+        _.delay @hideMenu, @delay
         return)
-
-  getMenuTarget: ->
-    @menu.parent.getElement()
-  reposition: =>
-    element = @getElement()
-    popup = @getMenuTarget()
-    ppos = popup.position()
-    @menu.css
-      top:   element.position().top + ppos.top
-      left:   ppos.left + popup.outerWidth()
-    return
-
-class GMailUI.Section extends GMailUI.Container
-  constructor: ->
-    super @setElement   """
-

-              """
-
-class GMailUI.Separator extends GMailUI.UIElement
-  constructor: ->
-    @setElement """
-

-          """
-
-class GMailUI.ErrorSection extends GMailUI.Section
-  @template: _.template  """
-

-                <%- message %>
-

-              """
-  constructor: (message) ->
-    super()
-    element = @getElement()
-    element.addClass 'b7o7Ic'
-    @append new GMailUI.Separator
-
-    @setContainer element.append $ GMailUI.ErrorSection.template
-      message: message
-  setMessage: (message) ->
-    @getContainer().html message
-    return
-
-class GMailUI.PopupMenu extends GMailUI.Container
-  @template:  """
-

-

-        """
-
-  delay:    300
-  inMenu:    false
-
-  constructor: (@parent) ->
-    super @setElement $ GMailUI.PopupMenu.template
-    element = @getElement()
-
-    # Clicks on menu do not close the popup
-    element.on 'click', (e) ->
-      e.stopPropagation()
-      return
-
-    element.hover ((e) =>
-      @inMenu = true
-      return),
-    ((e) =>
-      @inMenu = false
-      _.delay @hideMenu, @delay
-      return)
-  hideMenu: =>
-    return if @inMenu
-    @close()
-    return
-
-  show: ->
-    @onShow?()
-    super()
-    return
-
-  onShow: ->
-
-  close: ->
-    @getElement().attr 'aria-activedescendant', null
-    @hide()
-    return
-
-
-class GMailUI.RawHTML extends GMailUI.UIElement
-  constructor: (html) ->
-    super html
-
-class GMailUI.PopupCheckbox extends GMailUI.OnAble
-  @template: _.template   """
-              <div class="J-LC" aria-checked="false" role="menuitem"
-                <% if(tooltip) { %>
-                  data-tooltip="<%- tooltip %>"
-                  aria-label="<%- tooltip %>"
-                <% } %>
-                <% if(title) { %>
-                  title="<%- title %>"
-                <% } %>
-                style="-webkit-user-select: none;">
-

-

<%- label %>
-

-

-              """
-
-  @hoverClass:   'J-LC-JT'
-  @selectedClass: 'J-LC-JR-Jp'
-
-  constructor: (label, selected = false, tooltip = '', title = '') ->
-    element = @setElement GMailUI.PopupCheckbox.template
-      label:    label
-      title:     title
-      tooltip:   tooltip
-    super GMailUI.PopupCheckbox.hoverClass, GMailUI.PopupCheckbox.selectedClass
-    @setSelected selected
+    hideMenu: =>
+      return if @inMenu
+      @close()
+      return
+
+    show: ->
+      @onShow?()
+      super()
+      return
+
+    onShow: ->
+
+    close: ->
+      @getElement().attr 'aria-activedescendant', null
+      @hide()
+      return
+
+
+  class GMailUI.RawHTML extends GMailUI.UIElement
+    constructor: (html) ->
+      super html
+
+  class GMailUI.PopupCheckbox extends GMailUI.OnAble
+    @template: _.template   """
+                <div class="J-LC" aria-checked="false" role="menuitem"
+                  <% if(tooltip) { %>
+                    data-tooltip="<%- tooltip %>"
+                    aria-label="<%- tooltip %>"
+                  <% } %>
+                  <% if(title) { %>
+                    title="<%- title %>"
+                  <% } %>
+                  style="-webkit-user-select: none;">
+

+

<%- label %>
+

+

+                """
+
+    @hoverClass:   'J-LC-JT'
+    @selectedClass: 'J-LC-JR-Jp'
+
+    constructor: (label, selected = false, tooltip = '', title = '') ->
+      element = @setElement GMailUI.PopupCheckbox.template
+        label:    label
+        title:     title
+        tooltip:   tooltip
+      super GMailUI.PopupCheckbox.hoverClass, GMailUI.PopupCheckbox.selectedClass
+      @setSelected selected
+) @