blur optimization

blur optimization

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -219,63 +219,116 @@
   applyConfToEditors: ->
     @applyConfToEditor style for style in @editorStyles

-
-
-  drawTextBackground:(event,editor)->
+  drawIsWaiting:false
+
+  drawTextBackground:(event,editor,wait)->
     if editor?
-      conf = atom.config.get 'editor-background'
-      rgb=colorToArray conf.textBackground.toRGBAString()
-      opacity = (conf.textBackgroundOpacity / 100).toFixed(2)
-      if opacity > 0
-        @textBackground.style.display='block'
-        rgba = rgb[0]+','+rgb[1]+','+rgb[2]+',0.25'
-        rgb = rgb[0]+','+rgb[1]+','+rgb[2]
-
-        view = editor
-        #console.log 'view',view
-        #console.log 'event',event
-        editor = editor.editor
-        range = editor.getVisibleRowRange()
-        first = range[0]
-        last = range[1]
-        buff = editor.displayBuffer
-        height = editor.getLineHeightInPixels()
-        w = editor.getWidth()
-        h = (last-first)*height
-        if w? and h?
-          @setCanvasSize w,h
-          eView= atom.workspaceView.getActiveView()
-          if eView?
-            if eView[0]?
-              leftPos = eView[0].getBoundingClientRect().left
-              gutterWidth=view.gutter[0].offsetWidth
-              leftPos+=gutterWidth-editor.getScrollLeft()
-              #console.log 'leftPos',leftPos
-              @textBackground.style.left = leftPos+'px'
-              firstLine = editor.getFirstVisibleScreenRow()
-              firstLineTop = editor.pixelPositionForBufferPosition([firstLine,0]).top
-              if view.element.offsetParent?
-                editorTop=view.element.offsetParent.offsetTop
-              else
-                editorTop=0
-              topPos=firstLineTop - editor.getScrollTop()+editorTop
-              #console.log 'topPos',topPos
-              @textBackground.style.top = topPos+'px'
-          top = 0
-          charWidth = editor.getDefaultCharWidth()
-          @frontContext.fillStyle="rgba(#{rgb},#{opacity})"
-          for lineNumber in [first..last]
-            line=editor.lineTextForBufferRow(lineNumber)
-            if line?
-              if line.length>0
-                indent = editor.indentationForBufferRow(lineNumber)
-                offsetLeft=(indent*editor.getTabLength())*charWidth
-                left = buff.pixelPositionForBufferPosition([lineNumber,0]).left+offsetLeft-charWidth
-                right = buff.pixelPositionForBufferPosition([lineNumber,line.length-1]).left+(charWidth*2)
-                @frontContext.fillRect left,top,right-left,height
-            top+=height
-      else
-          @textBackground.style.display='none'
+      #console.log 'drawIsWaiting:',@drawIsWaiting
+      if @drawIsWaiting==false
+        if wait
+          @drawIsWaiting=true
+          setTimeout (=>@drawTextBackgroundRun.apply @,[event,editor]),500
+        else
+          @drawTextBackgroundRun event,editor
+
+  calculateRegion: (event,buffer)->
+    if @frontBuffer?
+      start=event.start
+      end=event.end
+      range=buffer.getVisibleRowRange()
+      if start>=range[0] and end<=range[1]
+        height=buffer.getLineHeightInPixels()
+        width=@frontBuffer.width
+        start_left_top=buffer.pixelPositionForBufferPosition([range[0],0])
+        left_top = buffer.pixelPositionForBufferPosition([start,0])
+        right_bottom = buffer.pixelPositionForBufferPosition([end,0])
+        #console.log left_top.top-right_bottom.top
+        x_=0
+        y_=left_top.top-start_left_top.top
+        w_=width
+        h_=right_bottom.top-left_top.top+height
+        region = {x_,y_,w_,h_}
+        #console.log 'region',region
+        region
+
+  drawTextBackgroundRun:(event,editor)->
+    @drawIsWaiting=false
+    if editor?
+      activeEditor=atom.workspaceView.getActiveView()[0]
+      if editor.element == activeEditor
+        conf = atom.config.get 'editor-background'
+        rgb=colorToArray conf.textBackground.toRGBAString()
+        opacity = (conf.textBackgroundOpacity / 100).toFixed(2)
+        if opacity > 0 and @textBackground?
+          textBlur=conf.textBackgroundBlurRadius
+          @textBackground.style.webkitFilter="blur(#{textBlur}px)"
+          @textBackground.style.display='block'
+          rgba = rgb[0]+','+rgb[1]+','+rgb[2]+',0.25'
+          rgb = rgb[0]+','+rgb[1]+','+rgb[2]
+
+          view = editor
+          editor = editor.editor
+          #console.log view
+          range = editor.getVisibleRowRange()
+          first = range[0]
+          last = range[1]
+          buff = editor.displayBuffer
+          height = editor.getLineHeightInPixels()
+          w = editor.getWidth()
+          h = (last-first)*height
+          if w? and h?
+            @setCanvasSize w,h
+            eView= atom.workspaceView.getActiveView()
+            if eView?
+              if eView[0]?
+                leftPos = eView[0].getBoundingClientRect().left
+                gutterWidth=view.gutter[0].offsetWidth
+                leftPos+=gutterWidth-editor.getScrollLeft()
+                #console.log 'leftPos',leftPos
+                @textBackground.style.left = leftPos+'px'
+                firstLine = editor.getFirstVisibleScreenRow()
+                firstLineTop = editor.pixelPositionForBufferPosition([firstLine,0]).top
+                if view.element.offsetParent?
+                  editorTop=view.element.offsetParent.offsetTop
+                else
+                  editorTop=0
+                topPos=firstLineTop - editor.getScrollTop()+editorTop
+                #console.log 'topPos',topPos
+                @textBackground.style.top = topPos+'px'
+            top = 0
+            charWidth = editor.getDefaultCharWidth()
+            @frontContext.fillStyle="rgba(#{rgb},#{opacity})"
+            #console.log 'drawing...'
+            x_=0
+            y_=0
+            w_=w
+            h_=h
+
+            if event.change?
+              region=@calculateRegion event.change,buff
+              if event.change.start>=range[0] and event.change.end<=range[1]
+                first=event.change.start
+                last=event.change.end
+                top=(first-range[0])*height
+              if region?
+                {x_,y_,w_,h_}=region
+                @frontContext.clearRect( 0, y_, w_, h_)
+            else
+              @frontContext.clearRect( 0, 0, w, h)
+            for lineNumber in [first..last]
+              line=editor.lineTextForBufferRow(lineNumber)
+              if line?
+                if line.length>0
+                  indent = editor.indentationForBufferRow(lineNumber)
+                  offsetLeft=(indent*editor.getTabLength())*charWidth
+                  left = buff.pixelPositionForBufferPosition([lineNumber,0]).left+offsetLeft-charWidth
+                  right = buff.pixelPositionForBufferPosition([lineNumber,line.length-1]).left+(charWidth*2)
+                  @frontContext.fillRect left,top,right-left,height
+              top+=height
+            #if @frontBuffer?
+              #blur.stackBlurCanvasRGBA @frontBuffer,x_,y_,w_,h_,textBlur
+        else
+            @textBackground.style.display='none'

   watchEditor: (editor) ->
     conf = atom.config.get('editor-background')
@@ -288,26 +341,28 @@
     @applyConfToEditor linesBg
     ed = editor.editor
     @editor.lineHeight = ed.getLineHeightInPixels()
-    ed.onDidChange (e)=> @drawTextBackground.apply @,[e,editor]
+    ed.onDidChange (e)=> @drawTextBackground.apply @,[{change:e},editor,true]
+    #ed.onDidStopChanging (e)=> @drawTextBackground.apply @,[e,editor,true]
     ed.onDidChangeScrollTop (e)=> @drawTextBackground.apply @,[{scrollTop:e},editor]
     ed.onDidChangeScrollLeft (e)=> @drawTextBackground.apply @,[{scrollLeft:e},editor]
-
+    if editor?
+      if editor.editor?
+        atom.workspace.onDidChangeActivePaneItem (e)=> @drawTextBackground.apply @,[{paneItem:e},editor,true]

   watchEditors: ->
     atom.workspaceView.eachEditorView (editor) => @watchEditor.apply @,[editor]

   setCanvasSize: (w,h)->
-    @frontBuffer.style.width  = w + "px"
-    @frontBuffer.style.height = h + "px"
-    @frontBuffer.width = w
-    @frontBuffer.height = h
+    if w!=@frontBuffer.width or h!=@frontBuffer.height
+      @frontBuffer.style.width  = w + "px"
+      @frontBuffer.style.height = h + "px"
+      @frontBuffer.width = w
+      @frontBuffer.height = h
     @backBuffer.style.width  = w + "px"
     @backBuffer.style.height = h + "px"
     @backBuffer.width = w
     @backBuffer.height = h
-    @frontContext.clearRect( 0, 0, w, h )
-    @backContext.clearRect( 0, 0, w, h )

   initCanvas: (w,h) ->
     @frontBuffer = document.createElement 'canvas'
@@ -318,11 +373,11 @@
     @backContext = @backBuffer.getContext("2d")
     @textBackground = document.createElement 'div'
     @textBackground.id = 'editor-background-text'
+    blurRadius=atom.config.get('editor-background.textBackgroundBlurRadius')
     @textBackground.style.cssText="
       position:absolute;
       left:70px;
       top:34px;
-      -webkit-filter:blur(15px);
       -webkit-transform: translate3d(0, 0, 0);
       transform: translate3d(0,0,0);
     "


bugfix: windows file path

bugfix: windows file path

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -130,7 +130,6 @@
     @elements.css = cssstyle

   createBox: (depth) ->
-    #body=(qr 'atom-text-editor').shadowRoot.querySelector('.editor--private')
     body = qr 'body'
     jest = qr 'body .eb-box-wrapper'
     if not jest? or jest.length==0
@@ -314,6 +313,7 @@
       imageData = blur.stackBlurImage @elements.image, conf.blurRadius, false
       base64Data = imageData.replace(/^data:image\/png;base64,/, "");
       filename = atom.packages.resolvePackagePath('editor-background')+"/blur.png"
+      filename = filename.replace /\\/gi,'/'
       fs.writeFileSync filename, base64Data,{mode:0o777,encoding:'base64'}
       imageData=filename+'?timestamp'+Date.now();
     else


Rename `initialize` to `initializePackage` on main module

Atom 1.14 introduces predefined semantics for an `initialize` method on
the main module of a package, which will cause the existing `initialize`
method to be called at the wrong time.

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -230,7 +230,7 @@
         else
             @startYouTube.apply @,[]

-    @initialize()
+    @initializePackage()

   deactivate: ()->
     if @subs?
@@ -832,7 +832,7 @@
     #@subs.add atom.workspace.onDidInsertText (text)=>

-  initialize: ->
+  initializePackage: ->
     @elements.body = qr 'body'
     @elements.workspace = qr 'atom-workspace'
     @elements.editor = null
@@ -906,7 +906,7 @@

       @applyBackground.apply @
     else
-      setTimeout (=>@initialize.apply @),1000
+      setTimeout (=>@initializePackage.apply @),1000

   updateBgPos: ->
     conf = @configWnd.get('editor-background')


dont't need this

dont't need this

--- lib/atom-ternjs-provider.coffee
+++ lib/atom-ternjs-provider.coffee
@@ -73,7 +73,6 @@
               replacementPrefix: prefix
               className: null
               type: obj._typeSelf
-              #rightLabel: obj.rightLabel
               rightLabel: " "
               leftLabel: obj.leftLabel
               snippet: obj._snippet


make editor cancel when not active pane

make editor cancel when not active pane

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -28,6 +28,8 @@
     @handleEvents()
     @setCurrentBuffer @editor.getBuffer()

+    @compositeDisposable.add atom.workspace.observeActivePaneItem(@updateCurrentEditor)
+
     @compositeDisposable.add atom.commands.add 'atom-text-editor',
       "autocomplete-plus:activate": @runAutocompletion

@@ -39,6 +41,9 @@
       "autocomplete-plus:select-next": @selectNext,
       "autocomplete-plus:select-previous": @selectPrevious,
       "autocomplete-plus:cancel": @cancel
+
+  updateCurrentEditor: (currentPaneItem) =>
+    @cancel() unless currentPaneItem == @editor

   confirmSelection: =>
     @emitter.emit 'do-confirm-selection'
@@ -238,24 +243,6 @@
     @editor.insertText(match.word)
     @editor.setSelectedBufferRanges(newSelectedBufferRanges)

-  # Private: As soon as the list is in the DOM tree, it calculates the maximum width of
-  # all list items and resizes the list so that all items fit
-  #
-  # onDom - {Boolean} is the element in the DOM?
-  afterAttach: (onDom) ->
-    return unless onDom
-
-    widestCompletion = parseInt(@css("min-width")) or 0
-    @list.querySelector("li").each ->
-      wordWidth = $(this).querySelector("span.word").outerWidth()
-      labelWidth = $(this).querySelector("span.label").outerWidth()
-
-      totalWidth = wordWidth + labelWidth + 40
-      widestCompletion = Math.max widestCompletion, totalWidth
-
-    @list.width widestCompletion
-    @width @list.outerWidth()
-
   # Private: Sets the current buffer, starts listening to change events and delegates
   # them to #onChanged()
   #


remove radio input from user goals, only use slider

--- user-goals/user-goals.coffee
+++ user-goals/user-goals.coffee
@@ -19,24 +19,17 @@
   IMAGE_FILES = [26, 27, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 61]

   @sampleArray: (arr) -> arr[Math.floor(Math.random() * arr.length)]
+
   @randomKelpImage: -> @sampleArray(IMAGE_FILES)

   Input =
-    slider: """
-
0 Classifications

+    min: 1
+    max: 50
+    slider:
+     "

       <form id='user-goal-form'>
-        <input type='range' id='user-goal-slider' value='25' min='1' max='50' step='1'>
-      </form>
-    """
-    radios: """
-      <form id='user-goal-form'>
-        <input type="radio" name="goal-radio" value="10">10
-        <input type="radio" name="goal-radio" value="20">20
-        <input type="radio" name="goal-radio" value="30">30
-        <input type="radio" name="goal-radio" value="40">40
-        <input type="radio" name="goal-radio" value="50">50
-      </form>
-    """
+        <input type='range' id='user-goal-slider' min='1' max='50' step='1'>
+      </form>"

   Feedback =
     message: "
Classification Goal Achieved!
Thank you for your efforts!
"
@@ -49,35 +42,35 @@
   SPLIT =
     A:
       message: Messages.personallyMotivated
-      input: Input.radios
+      input: Input.min
       feedback: Feedback.image
     B:
       message: Messages.personallyMotivated
-      input: Input.radios
+      input: Input.min
       feedback: Feedback.message
     C:
       message: Messages.personallyMotivated
-      input: Input.slider
+      input: Input.max
       feedback: Feedback.image
     D:
       message: Messages.personallyMotivated
-      input: Input.slider
+      input: Input.max
       feedback: Feedback.message
     E:
       message: Messages.sociallyMotivated
-      input: Input.radios
+      input: Input.min
       feedback: Feedback.image
     F:
       message: Messages.sociallyMotivated
-      input: Input.radios
+      input: Input.min
       feedback: Feedback.message
     G:
       message: Messages.sociallyMotivated
-      input: Input.slider
+      input: Input.max
       feedback: Feedback.image
     H:
       message: Messages.sociallyMotivated
-      input: Input.slider
+      input: Input.max
       feedback: Feedback.message

   constructor: (@splitGroup) ->
@@ -99,10 +92,13 @@
     @populatePromptContent()
     @el.show()

+  userShouldSeeImage: ->
+    !!~SPLIT[@splitGroup].feedback.indexOf("img")
+
   feedback: ->
     @content.html(SPLIT[@splitGroup].feedback)
     @el.show()
-    @el.addClass("feedback-image") if !!~SPLIT[@splitGroup].feedback.indexOf("img")
+    @el.addClass("feedback-image") if @userShouldSeeImage()
     User?.current?.setPreference "goal_set", "false"

   currentGoal: ->
@@ -118,7 +114,7 @@
     return if @closed
     (@sessionHasExpired() and @userClassifyCount() > 2) or (@userClassifyCount() is 2)

-  showIfNeeded: ->
+  showIfNeeded: ->
     @prompt() if @promptShouldBeDisplayed()

   promptOrUpdateCurrentGoal: ->
@@ -141,30 +137,37 @@
     if @completedSuccessfully() then @feedback() else @decrimentGoal()
     @increaseSessionExpiration()

+  inputFormHTML: ->
+    HEADER + SPLIT[@splitGroup].message + Input.slider + SUBMIT_BUTTON
+
+  createInputForm: ->
+    @content.html @inputFormHTML()
+
   populatePromptContent: ->
-    @content.html HEADER + SPLIT[@splitGroup].message + SPLIT[@splitGroup].input + SUBMIT_BUTTON
-    @listenForSliderChange() if !!~SPLIT[@splitGroup].input.indexOf("user-goal-slider")
+    @createInputForm()
+    @listenForSliderChange()
     @listenForSubmit()

   listenForSubmit: ->
     @el.find("button[name='user-goal-submit']").on 'click', => @setUserGoal()

+  sCheck: (int) ->
+    if +int is 1 then '' else 's'
+
   listenForSliderChange: ->
     goalSlider = @el.find("#user-goal-slider")

-    defaultValue = 25
+    defaultValue = SPLIT[@splitGroup].input
     goalSlider.val(defaultValue)
-    @el.find("#slider-val").text(defaultValue + " Classifications")
+    @el.find("#slider-val").text(defaultValue + " Classification#{@sCheck(defaultValue)}")
     @goal = defaultValue

     goalSlider.on 'input', (e) =>
       @goal = goalSlider.val()
-      @el.find("#slider-val").text(@goal + " Classifications")
+      @el.find("#slider-val").text(@goal + " Classification#{@sCheck(@goal)}")

   setUserGoal: ->
-    @goal or= @el.find("#user-goal-form input[type='radio']:checked").val()
-
-    @content.html("
Goal set for #{@goal} Classification#{if +@goal is 1 then '' else 's'}
")
+    @content.html("
Goal set for #{@goal} Classification#{@sCheck(@goal)}
")

     User?.current?.setPreference "goal_set", "true"
     User?.current?.setPreference "goal", @goal
@@ -179,4 +182,4 @@
     time.setMinutes(time.getMinutes() + SESSION_TIME)
     User?.current?.setPreference "goal_end_date", time

-module?.exports = UserGoals+module?.exports = UserGoals


Get hostname from options.host (#167) (#213)

--- src/index.coffee
+++ src/index.coffee
@@ -65,7 +65,7 @@
       @server = http.createServer @app
     if @serverInit
       @serverInit @server
-    @server.listen @port, (err) =>
+    @server.listen @port, @host, (err) =>
       if err
         @log "Error on starting server: #{err}"
       else


Do not convert colorspace for sRGB images

--- src/index.coffee
+++ src/index.coffee
@@ -118,7 +118,7 @@
     .quality(version.quality or @config.resizeQuality)

   img.autoOrient() if @meta.orientation
-  img.colorspace('RGB') if @meta.colorSpace isnt 'RGB'
+  img.colorspace('RGB') if @meta.colorSpace not in ['RGB', 'sRGB']

   img.write version.src, (err) ->
       return cb err if err


+ implemented the file proc

+ implemented the file proc

--- src/index.coffee
+++ src/index.coffee
@@ -5,8 +5,11 @@
 pug = require 'pug'
 slug = require 'slug'
 _assign = require 'lodash/assign'
+_has = require 'lodash/has'
+_find = require 'lodash/find'
 mkdirp = require 'mkdirp'
 fs = require 'fs'
+path = require 'path'
 moment = require 'moment'

@@ -18,15 +21,15 @@
   paths:
     layouts: 'layouts'
     content: 'content'
+    public: 'public'
+    root: 'blog'

   fields:
     title: 'title'
     date: 'published'
-    category: 'category'

   wrap_html: yes
   slugify: yes
-  categorize: yes

   marked:
     gfm: yes
@@ -75,18 +78,41 @@
     )
     unless proceed then return

-    destination = null
+    payload = @grabFrontAndContent file.data

+    unless payload.path? or not _has(payload, [@fields.title, @fields.date])
+      throw error: 'Required fields not found'

-    data = @grabFrontAndContent file.data
-    title_slug = slug(data.title, @slug)
-    dates = moment(data.published).format('Y/MM/DD')
-    path = "#{@pub_path}/#{dates}/#{title_slug}"
+    if payload.layout?
+      format_layout = (dir) => "#{dir}/#{@paths.layouts}/#{payload.layout}.jade"
+      dir = _find @paths.watched, (dir) ->
+        fs.statSync(format_layout(dir)).isFile()

-    if data.layout?
-      template_path = "#{@layout_root}/layouts/#{data.layout}.jade"
-      data.content = @applyTemplate template_path, data
+      unless dir then throw error: 'Cannot locate layout'

-    mkdirp path, (er) ->
-      fs.writeFile "#{path}/index.html", data.content, (er) ->
-        console.log 'Done.'
+      payload.content = @applyTemplate format_layout(dir), payload
+
+    destination = do =>
+      base_name = path.basename(file.path, @extension)
+      dir_name = "#{@paths.public}"
+      if payload.path?
+        dir_name += "/#{payload.path}"
+      else
+        slug_name = slug(payload[@fields.title], @slug)
+        date_dirs = moment(payload[@fields.date]).format('Y/MM/DD')
+        dir_name += "/#{@paths.root}/#{date_dirs}"
+
+        if @wrap_html
+          dir_name += "/#{slug_name}"
+          base_name = 'index'
+        else
+          base_name = slug_name
+
+      return dir: dir_name, name: base_name
+
+    mkdirp.sync destination.dir
+
+    outfile = "#{destination.dir}/#{destination.name}.html"
+
+    fs.writeFileSync outfile, payload.content
+


class, typedef ...

class, typedef ...

--- snippets/dart.cson
+++ snippets/dart.cson
@@ -66,3 +66,20 @@
         //doSomeStuff
       }
       """
+  'import':
+    'prefix': 'import'
+    'body': """
+      import '$1';
+      """
+    'class':
+      'prefix': 'class'
+      'body': """
+        class ${1:name} {
+
+        }
+        """
+    'typedef':
+      'prefix': 'typedef'
+      'body': """
+        typedef ${1:type} ${2:name};
+        """


some keyboard convenient

some keyboard convenient

--- src/maps.coffee
+++ src/maps.coffee
@@ -125,12 +125,26 @@
     $('#search-dialog').show()

+$(document).keydown (event) ->
+    if event.which == 27 # esc
+        if $('#search-box:focus').length
+            $('#search-box').blur()
+        else
+            info_window.close()
+
+$(document).keypress (event) ->
+    unless $('#search-box:focus').length
+        if event.which == 47 # /
+            $('#search-box').focus()
+        event.preventDefault()
+
+
 $(document).ready ->
     $('.prehide').hide()
     $('#search-box').focus()

     $('#search-box').keydown (event) ->
-        $('#search').click() if event.keyCode == 13
+        $('#search').click() if event.keyCode == 13 # enter

     $('#search').click ->
         get_data =


trim the paths from user

trim the paths from user

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -131,7 +131,7 @@
   search: ->
     regex = @getRegex()
     @results = []
-    paths = (path for path in @pathsEditor.getText().trim().split(',') when path)
+    paths = (path.trim() for path in @pathsEditor.getText().trim().split(',') when path)

     deferred = $.Deferred()
     promise = project.scan regex, {paths}, ({path, matchText, lineText, range: bufferRange}) =>


Minor test formatting

Minor test formatting

--- test/sticky.coffee
+++ test/sticky.coffee
@@ -43,7 +43,7 @@
     runs ->
       expect(handlerSpy).toHaveBeenCalled()

-  describe 'Waypoint unsticky element', ->
+  describe '#waypoint("unsticky")', ->
     beforeEach ->
       $return = $sticky.waypoint 'unsticky'


simple touch/ipad tweaks -- don't force keyboard opening

--- src/smc-webapp/console.coffee
+++ src/smc-webapp/console.coffee
@@ -179,10 +179,9 @@
         # Init pausing rendering when user clicks
         @_init_rendering_pause()

-        if not IS_TOUCH
-            @textarea.on 'blur', =>
-                if @_focusing?          # see comment in @focus.
-                    @_focus_hidden_textarea()
+        @textarea.on 'blur', =>
+            if @_focusing?          # see comment in @focus.
+                 @_focus_hidden_textarea()

         # delete scroll buttons except on mobile
         if not IS_TOUCH
@@ -875,7 +874,7 @@
         # Determine number of rows from the height of the row, as computed above.
         height = elt.height()
         if IS_TOUCH
-            height -= 120
+            height -= 60
         new_rows = Math.max(1, Math.floor(height / row_height))

         # Resize the renderer
@@ -915,9 +914,6 @@
             focused_console = undefined

         @is_focused = false
-
-        if IS_TOUCH
-            $(document).off('keydown', @mobile_keydown)

         try
             @terminal.blur()
@@ -947,12 +943,9 @@
         @textarea.blur()
         $(@terminal.element).focus()

-        if IS_TOUCH
-            @element.find(".webapp-console-input-line").focus()
-            @terminal.focus()
-        else
+        if not IS_TOUCH
             @_focus_hidden_textarea()
-            @terminal.focus()
+        @terminal.focus()

         $(@terminal.element).addClass('webapp-console-focus').removeClass('webapp-console-blur')
         setTimeout((()=>delete @_focusing), 0)   # critical!


Use right cwd

Use right cwd

Fixes #13

--- lib/linter-less-provider.coffee
+++ lib/linter-less-provider.coffee
@@ -50,10 +50,13 @@

   lessParse: (text, filePath, callback) ->
+
+    cwd = path.dirname filePath
+
     parser = new less.Parser(
       verbose: false
       silent: true
-      paths: [@cwd, @config('includePath')...]
+      paths: [cwd, @config('includePath')...]
       filename: filePath
     )


Map accessibility profile into profile ids.

--- src/p13n.coffee
+++ src/p13n.coffee
@@ -139,6 +139,24 @@
             if not mode_name of acc_vars
                 throw new Error "Attempting to get invalid accessibility mode: #{mode_name}"
             return acc_vars[mode_name]
+        get_accessibility_profile_ids: () ->
+            ids = []
+            acc_vars = @get 'accessibility'
+            mobility = acc_vars['mobility']
+            switch mobility
+                when 'wheelchair'
+                    ids.push 1
+                when 'reduced_mobility'
+                    ids.push 2
+                when 'rollator'
+                    ids.push 3
+                when 'stroller'
+                    ids.push 4
+            if @get_accessibility_mode('visually_impaired')
+                ids.push 5
+            if @get_accessibility_mode('hearing_aid')
+                ids.push 6
+            ids

         set_transport: (new_val) ->
             @_set_value ['transport'], new_val


Assert request-level errors are passed through.

If an error is made when making a request (say, internet connection is disabled), that error should be returned through node-trello.

--- test/trello.coffee
+++ test/trello.coffee
@@ -107,3 +107,13 @@
         @trello.request "VERB", "/test?name=values", () ->
         @request.options.json.should.have.property "name"
         @request.options.json.name.should.equal "values"
+
+      it "should pass through any errors without response bodies", () ->
+        method = request.get
+        request.get = (options, callback) ->
+          callback(new Error("Something bad happened."))
+
+        @trello.request "GET", "/test", (err, response) ->
+          err.message.should.equal "Something bad happened."
+
+        request.get = method


minor update

minor update

--- test_docopt.coffee
+++ test_docopt.coffee
@@ -1,14 +1,34 @@
-# it's a troublesome policy that node's got...
-
-print = console.log
+print = () -> console.log.apply @, [].slice.call(arguments)
 eq = (a, b) ->
     as = a.toString()
     bs = b.toString()
     if as == bs then return else throw new Error "#{as} != #{bs}"

-((module) ->
-    `with (module) {//`
-    tests =
+(() ->
+    [setup, teardown, clear] = (() ->
+        buffer = ''
+        cl = console.log
+        psw = process.stdout.write
+        [() ->
+            console.log = () ->
+                buffer += ([].slice.call arguments).join() + '\n'
+            process.stdout.write = () ->
+                buffer += ([].slice.call arguments).join()
+            null
+
+        () ->
+             console.log = cl
+             process.stdout.write = psw
+             null
+
+        () ->
+            stdout = buffer
+            buffer = ''
+            stdout]
+    )()
+
+    `with (this) //`
+    tests =
         test_opt_parse: ->
             eq Option.parse('-h'), new Option('-h', null)
             eq Option.parse('-h'), new Option('-h', null)
@@ -80,7 +100,7 @@
             eq printable_usage(usage, 'my_script'), 'Usage: my_script <a> <b> <c>'
             eq printable_usage(usage, null), 'Usage: prog <a> <b> <c>'

-            usage = 'Usage:\tprog <a> <b> <c>\n' +
+            usage = 'Usage:\tprog <a> <b> <c>\n' +
                             'prog --version\n'   +
                             'prog (--help | -h)\n\n'
             eq printable_usage(usage, 'rn'), 'Usage:\trn <a> <b> <c>\n' +
@@ -91,7 +111,7 @@
                                              '      \tprog (--help | -h)'

             usage = 'Usage:\n' +
-                    '  prog <a> <b> <c>\n' +
+                    '  prog <a> <b> <c>\n' +
                     '  prog --version\n'   +
                     '  prog (--help | -h)\n\n'
             eq printable_usage(usage, 'rn'), 'Usage:\n' +
@@ -107,25 +127,44 @@
             eq printable_usage(usage, null), 'usage: nospace'

-    `}`
+    wr = console.log
+    wr '================================================================'

-    print '================================================================'
+    setup()

+    number = 0
     passes = 0
-    errors = []
+    failures = 0
+    results = []
     for test of tests
+        continue if test[0..3] isnt 'test'
         try
             tests[test]()
-            passes += 1
-            process.stdout.write '.'
+            results.push ['.', test, 'OK', clear()]
         catch e
-            errors.push([test, e])
-            process.stdout.write 'F'
-    print ''
+            results.push ['F', test, e.message, clear()]

-    for [test, e] in errors
-        print "In test #{test}, #{e.message}"
+    teardown()

-    print "#{passes} successes, #{errors.length} failures"
-    print '================================================================'
-)(require './docopt')
+    for result in results
+        if result[0] == 'F'
+            ++failures
+            half = (56 - result[1].length)/2
+            if half > 0
+                [left, right] = [new Array(half + half%1).join('=') + ' ',
+                           ' ' + new Array(half - half%1).join('=')]
+            else
+                [left, right] = ['', '']
+            wr "#{left}In test #{result[1]}#{right}"
+            wr "#{result[2]}"
+            if result[3] isnt ''
+                wr '------------------------ captured stdout -----------------------'
+                wr result[3]
+         else
+             ++passes
+
+    wr '================================================================'
+    wr((result[0] for result in results).join(''))
+    wr "#{passes} successes, #{failures} failures"
+    wr '================================================================'
+).call(require './docopt')


reset candidate symbol index

reset candidate symbol index

--- lib/view/input-method.coffee
+++ lib/view/input-method.coffee
@@ -7,9 +7,9 @@
     props: ['input']
     template: '''

-
{{input.rawInput}}

+
{{rawInput}}

-                <button class="btn" v-repeat="input.suggestionKeys" v-on="click: selectKey($value)">{{$value}}</button>
+                <button class="btn" v-repeat="suggestionKeys" v-on="click: selectKey($value)">{{$value}}</button>

                 <button class="btn" v-repeat="selectedLeft" v-on="click: selectSymbol($value)">{{$value}}</button>
@@ -22,22 +22,22 @@

         commands =
             'core:move-up' : (event) =>
-                if @input? and @input?.candidateSymbols.length isnt 0
+                if @candidateSymbols?.length isnt 0
                     @moveUp()
                     @replaceSymbol @selected[0]
                     event.stopImmediatePropagation()
             'core:move-right' : (event) =>
-                if @input? and @input?.candidateSymbols.length isnt 0
+                if @candidateSymbols?.length isnt 0
                     @moveRight()
                     @replaceSymbol @selected[0]
                     event.stopImmediatePropagation()
             'core:move-down' : (event) =>
-                if @input? and @input?.candidateSymbols.length isnt 0
+                if @candidateSymbols?.length isnt 0
                     @moveDown()
                     @replaceSymbol @selected[0]
                     event.stopImmediatePropagation()
             'core:move-left' : (event) =>
-                if @input? and @input?.candidateSymbols.length isnt 0
+                if @candidateSymbols?.length isnt 0
                     @moveLeft()
                     @replaceSymbol @selected[0]
                     event.stopImmediatePropagation()
@@ -46,6 +46,10 @@
     destroy: ->
         @subscriptions.destroy()
     data: ->
+        partialCandidateSymbols: []
+        suggestionKeys: []
+        candidateSymbols: []
+        rawInput: ''
         subscriptions: new CompositeDisposable
         index: 0
     methods:
@@ -59,21 +63,22 @@
             if (@index - 10) >= 0
                 @index -= 10
         moveDown: ->
-            if (@index + 10) < @input.candidateSymbols.length
+            if (@index + 10) < @candidateSymbols.length
                 @index += 10
         moveLeft: ->
             if (@index - 1) >= 0
                 @index -= 1
         moveRight: ->
-            if (@index + 1) < @input.candidateSymbols.length
+            if (@index + 1) < @candidateSymbols.length
                 @index += 1
     computed:
-        partialCandidateSymbols: ->
-            if @input?.candidateSymbols
-                _.take(_.drop(@input.candidateSymbols, @row * 10), 10)
-            else
-                []
-
+        input:
+            set: (input) ->
+                @candidateSymbols = input.candidateSymbols
+                @suggestionKeys = input.suggestionKeys
+                @rawInput = input.rawInput
+                @index = 0
+        partialCandidateSymbols: -> _.take(_.drop(@candidateSymbols, @row * 10), 10)
         selectedLeft:   -> _.take(@partialCandidateSymbols, @col)
         selected:       -> _.compact(@partialCandidateSymbols[@col])
         selectedRight:  -> _.drop(@partialCandidateSymbols, @col + 1)


Boilerplate for password reset.

Boilerplate for password reset.

--- web/coffee/front-page.coffee
+++ web/coffee/front-page.coffee
@@ -12,13 +12,18 @@
     userGlyph = <Glyphicon glyph="user"/>
     lockGlyph = <Glyphicon glyph="lock"/>

-    formButton = if @props.status == "Login" then <Button>Login</Button> else \
-    Go back to Login.
+    formButton = if @props.status == "Login" then \
+

+      <Button>Login</Button> Need to reset your password?
+
 else \
+    Go back to Login.

     <Panel className="form-test">
       <Input type="text" valueLink={@props.username} addonBefore={userGlyph} label="Username"/>
       <Input type="text" valueLink={@props.password} addonBefore={lockGlyph} label="Password"/>
-      {formButton}
+

+        {formButton}
+

     </Panel>

 RegistrationForm = React.createClass


Taking text child nodes into account

Taking text child nodes into account

--- framer/SVG.coffee
+++ framer/SVG.coffee
@@ -38,6 +38,9 @@

 		if elements?
 			for element in elements
+				unless element instanceof SVGElement
+					# Children can contain text nodes
+					continue
 				name = element.getAttribute("name")
 				if not name?
 					if element instanceof SVGGElement


fixed remaining unit tests and disabling adding and removing tasking plans

--- src/components/task-plan/builder.cjsx
+++ src/components/task-plan/builder.cjsx
@@ -204,7 +204,7 @@
           <BS.Col sm=4 md=3>
             <input
               id={"period-toggle-#{period.id}"}
-              disabled={not isEditable}
+              disabled={isVisibleToStudents}
               type='checkbox'
               onChange={_.partial(togglePeriodEnabled, period)}
               checked={true}/>
@@ -221,7 +221,7 @@
             <input
               id={"period-toggle-#{period.id}"}
               type='checkbox'
-              disabled={not isEditable}
+              disabled={not isVisibleToStudents}
               onChange={_.partial(togglePeriodEnabled, period)}
               checked={false}/>
             <label className="period" htmlFor={"period-toggle-#{period.id}"}>{period.name}</label>
@@ -476,8 +476,8 @@
         </BS.Col>
       </BS.Row>

-      {@renderCommonChoice() unless not @state.isEditable and @state.showingPeriods}
-      {@renderPeriodsChoice() unless not @state.isEditable and not @state.showingPeriods}
+      {@renderCommonChoice() unless @state.isVisibleToStudents and @state.showingPeriods}
+      {@renderPeriodsChoice() unless @state.isVisibleToStudents and not @state.showingPeriods}
       { invalidPeriodsAlert }
     </div>

@@ -488,8 +488,9 @@
       name='toggle-periods-radio'
       ref='allPeriodsRadio'
       type='radio'
+      disabled={@state.isVisibleToStudents}
       onChange={@setAllPeriods}
-      checked={not @state.showingPeriods}/> if @state.isEditable
+      checked={not @state.showingPeriods}/> if not @state.isVisibleToStudents

     <BS.Row className="common tutor-date-input">
       <BS.Col sm=4 md=3>
@@ -504,8 +505,9 @@
       id='show-periods-radio'
       name='toggle-periods-radio'
       type='radio'
+      disabled={@state.isVisibleToStudents}
       onChange={@setIndividualPeriods}
-      checked={@state.showingPeriods}/> if @state.isEditable
+      checked={@state.showingPeriods}/> if not @state.isVisibleToStudents

     choiceLabel = <BS.Row key='tasking-individual-choice'>
       <BS.Col md=12>


Update teacher survey email validation

Update teacher survey email validation

Removing restrictive regex matching.

--- app/views/TeachersFreeTrialView.coffee
+++ app/views/TeachersFreeTrialView.coffee
@@ -47,8 +47,7 @@
     $('.container-num-students').removeClass('has-error')
     $('.container-heard-about').removeClass('has-error')
     $('.error-message').hide()
-    emailPattern = /^([\w.-]+)@([\w.-]+)\.([a-zA-Z.]{2,6})$/i
-    unless email?.match(emailPattern)
+    unless email
       $('.container-email-address').addClass('has-error')
       $('.error-message').show()
       return


car_crashes: remove tool tips; add highlighting on mouseover

--- car_crashes/car_crashes.coffee
+++ car_crashes/car_crashes.coffee
@@ -52,6 +52,7 @@
                                     .yticks(d3.range(n_states).map (d) -> d+1)
                                     .xlab(xlab[i])
                                     .ylab("")
+                                    .pointsize(3)
                                     .dataByInd(false)
                                     .xvar(top_panel_var[i])
                                     .yvar("rank")
@@ -65,6 +66,16 @@

         this_g.datum({data:data, indID:data.abbrev})
               .call(this_dotplot)
+
+        # remove the tool tips
+        d3.selectAll(".d3-tip").remove()
+        points = this_dotplot.pointsSelect().on("mouseover.paneltip", (d) -> d)
+                                            .on("mouseout.paneltip", (d) -> d)
+
+
+
+        points.on("mouseover", highlight_state)
+              .on("mouseout",  lowlight_state)

     # make the horizontal grid lines gray
     d3.selectAll("g.dotplot g.y.axis line").attr("stroke", "#bbb")
@@ -82,7 +93,26 @@
                      .style("font-size", "8pt")
                      .style("dominant-baseline", "middle")
                      .style("text-anchor", "end")
-                     .attr("id", (d,i) -> "text#{i}")
+                     .attr("id", (d,i) -> "state#{i}")
+                     .on("mouseover", highlight_state)
+                     .on("mouseout",  lowlight_state)
+
+
+highlight_state = (d,i) ->
+    d3.selectAll("circle.pt#{i}")
+      .attr("fill", "Orchid")
+      .attr("r", 5)
+    d3.select("text#state#{i}")
+      .attr("fill", "violetred")
+
+lowlight_state = (d,i) ->
+    d3.selectAll("circle.pt#{i}")
+      .attr("fill", "slateblue")
+      .attr("r", 3)
+    d3.select("text#state#{i}")
+      .attr("fill", "black")
+
+

 # load the data and make the plot
 d3.json("data.json", plot)


Minor changes in the remote detection

Minor changes in the remote detection

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -72,8 +72,9 @@
         sources = -> context.sources.push compile()
         compile = -> subject.remote?.compile? symbol
         invalid = "not a remote object and not a link"
-        return sources() if _.isString subject.remote.source
+        compilable = _.isFunction subject.remote.compile
         return scripts() if _.isString subject
+        return sources() if compilable
         throw new Error invalid

     # This is an internal routine that performs the task of compiling


A more appropriate name for ssl stub

A more appropriate name for ssl stub

--- library/nucleus/protect.coffee
+++ library/nucleus/protect.coffee
@@ -43,7 +43,7 @@
 # providing the boilerplate for ensuring that the connection is
 # going through the HTTPS channel. If a request is not going via
 # SSL transport then redirect the current request to such one.
-module.exports.StubWithSsl = class StubWithSsl extends api.Stub
+module.exports.SslStub = class SslStub extends api.Stub

     # A hook that will be called prior to invoking the API method
     # implementation. Please refer to this prototype signature for


Ignore initial popstate event

Ignore initial popstate event

"Browsers tend to handle the popstate event differently on page load. Chrome (prior to v34) and Safari always emit a popstate event on page load, but Firefox doesn't."
- https://developer.mozilla.org/en-US/docs/Web/Events/popstate

--- src/turbolinks/history.coffee
+++ src/turbolinks/history.coffee
@@ -1,3 +1,9 @@
+pageLoaded = false
+
+addEventListener "load", ->
+  Turbolinks.defer ->
+    pageLoaded = true
+
 class Turbolinks.History
   constructor: (@delegate) ->

@@ -22,12 +28,17 @@
   # Event handlers

   onPopState: (event) =>
-    if turbolinks = event.state?.turbolinks
-      location = Turbolinks.Location.box(window.location)
-      restorationIdentifier = turbolinks.restorationIdentifier
-      @delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier)
+    if @shouldHandlePopState()
+      if turbolinks = event.state?.turbolinks
+        location = Turbolinks.Location.box(window.location)
+        restorationIdentifier = turbolinks.restorationIdentifier
+        @delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier)

   # Private
+
+  shouldHandlePopState: ->
+    # Safari dispatches a popstate event after window's load event, ignore it
+    pageLoaded is true

   update: (method, location, restorationIdentifier) ->
     state = turbolinks: {restorationIdentifier}


CI.inner.Project#invite_team_member.

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -501,11 +501,18 @@
         true
     false

-
   invite_user: (user) =>
     $.ajax
       type: "POST"
       url: "/api/v1/project/#{@project_name()}/invite/#{user.login}"
+
+  invite_team_member: (login, email) =>
+    $.ajax
+      type: "POST"
+      url: "/api/v1/project/#{@project_name()}/invite-team-member"
+      data: JSON.stringify
+        login: login
+        email: email

   refresh: () =>
     $.getJSON "/api/v1/project/#{@project_name()}/settings", (data) =>


tree: improve command support

tree: improve command support

--- src/tree.coffee
+++ src/tree.coffee
@@ -68,10 +68,12 @@
       ev.emit 'error', err
       callback err
       return
+    # tree = @load_tree modules, options.command
     # Filter with the modules options
     if options.modules.length
       modules = tree.map (leaf) -> leaf.module
       modules = multimatch modules, options.modules
+      # tree = @load_tree modules, options.command
       tree = @load_tree modules, options.command
       # Filter with the fast options
       tree = tree.filter( (leaf) =>
@@ -117,29 +119,41 @@
 Tree::load_tree = (modules, command) ->
   called = {}
   tree = []
-  build_tree = (module, parent) =>
-    return if called[module]
-    called[module] = true
-    leaf = module: module, actions: []
-    for action in @load_module module, parent
+  normalize_action = (action) =>
+    # Normalize
+    action = modules: action if typeof action is 'string'
+    action.commands ?= []
+    action.commands = [action.commands] unless Array.isArray action.commands
+    action.commands.push command if command and not action.commands.length
+    action.modules ?= []
+    action.modules = [action.modules] unless Array.isArray action.modules
+    if typeof action.callback is 'string'
+      action.modules.push action.callback
+      action.callback = null
+    action
+  build_module = (name, modules, filtering, parent) =>
+    return if called[name]
+    called[name] = true
+    modules = @load_module name, parent unless modules
+    leaf = module: name, actions: []
+    for action in modules
       # Normalize
-      action = modules: action if typeof action is 'string'
-      action.modules ?= []
-      action.modules = [action.modules] unless Array.isArray action.modules
-      if typeof action.callback is 'string'
-        action.modules.push action.callback
-        action.callback = null
+      action = normalize_action action
       # Filter
-      continue if command and action.command and action.command isnt command
+      continue if (filtering and action.commands.length is 0 and command isnt null and command isnt 'install') or (command and action.commands.length and command not in action.commands)
       # Discover
       if action.modules.length
         tree.push leaf if leaf.actions.length
-        leaf = module: module, actions: []
+        leaf = module: name, actions: []
         for childmod in action.modules
-          build_tree childmod, module
+          f = if filtering is false then false else !action.commands.length
+          build_module childmod, null, f, module
       leaf.actions.push action if action.callback
-    tree.push leaf
-  for module in modules then build_tree module
+    # Push module into tree if actions or module is named and not already present
+    if leaf.actions.length or (leaf.module and not (tree.some (l) -> l.module is leaf.module))
+      tree.push leaf
+  build_module null, modules, null
+  # for module in modules then build_module module
   tree

 ###


Fixed extendAttributes converting "" to 0

--- src/util.coffee
+++ src/util.coffee
@@ -87,7 +87,7 @@
       if output[key] in ["true", "false"]
         output[key] = output[key] is "true"
       # Convert to integer or numbers from strings
-      else if not isNaN +output[key]
+      else if output[key]?.length > 0 and not isNaN +output[key]
         output[key] = +output[key]

     return output


opt: performance

opt: performance

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -591,9 +591,6 @@
 			settlePromise p, $rejected, genTypeError($circularChain)
 			return

-		if isLongStackTrace and x instanceof Yaku
-			p._next = x
-
 		# 2.3.2
 		# 2.3.3
 		if x != null and (isFunction(x) or isObject(x))
@@ -606,6 +603,9 @@
 				return

 			if isFunction xthen
+				if isLongStackTrace and x instanceof Yaku
+					p._next = x
+
 				settleXthen p, x, xthen
 			else
 				# 2.3.3.4