single-shot transactions for rescan

single-shot transactions for rescan

--- content/keymanager.coffee
+++ content/keymanager.coffee
@@ -77,12 +77,13 @@

     if unset.length
       flash('Assigning citation keys', "Found #{unset.length} references without a citation key")
-      yield Zotero.DB.executeTransaction(co(->
-        for item in yield Zotero.Items.getAsync(unset)
+      for id in unset
+        yield Zotero.DB.executeTransaction(co(->
+          item = yield Zotero.Items.getAsync(id)
           debug('KeyManager.rescan: saving item', item.id)
           yield item.save()
-        return
-      ))
+          return
+        ))
     debug('KeyManager.rescan: done updating citation keys')

     return


+ </script> tag in username no longer breaks the page

--- server_setup.coffee
+++ server_setup.coffee
@@ -91,8 +91,8 @@
 sendMain = (req, res) ->
   fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) ->
     log.error "Error modifying main.html: #{err}" if err
-    # insert the user object directly into the html so the application can have it immediately
-    data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)))
+    # insert the user object directly into the html so the application can have it immediately. Sanitize </script>
+    data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace('/', '\\/'))
     res.send data

 setupFacebookCrossDomainCommunicationRoute = (app) ->


create new statusBarManager on re-activate

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,4 +1,4 @@
-{Notification, CompositeDisposable, Disposable} = require 'atom'
+{Notification, CompositeDisposable} = require 'atom'
 fs = require 'fs-plus'
 StackTraceParser = null
 NotificationElement = require './notification-element'
@@ -73,10 +73,12 @@
     @notificationsElement?.remove()
     @notificationsPanel?.destroy()
     @notificationsLog?.destroy()
+    @statusBarManager?.destroy()

     @subscriptions = null
     @notificationsElement = null
     @notificationsPanel = null
+    @statusBarManager = null

     @isInitialized = false

@@ -144,10 +146,7 @@
     @lastNotification = notification

   statusBarService: (statusBar) ->
-    return if @statusBarManager?
-
     @statusBarManager = new StatusBarManager(statusBar, @duplicateTimeDelay)
-    @subscriptions.add new Disposable => @statusBarManager.destroy()

 isCoreOrPackageStackTrace = (stack) ->
   StackTraceParser ?= require 'stacktrace-parser'


Remove debug code.

Remove debug code.

--- lib/main.coffee
+++ lib/main.coffee
@@ -18,46 +18,42 @@
   firstWindowAtLaunch: false

   activate: (state) ->
-    atom.openDevTools()
-    timeout = =>
-      window.codewars = @
-      @state = state
-      @path = path.join atom.getConfigDirPath(), DATA_DIR
-      @pathStatesDir = path.join @path, '.states'
-      @pathWindowState = path.join @pathStatesDir, 'window'
-      @pathFocusState = path.join @pathStatesDir, 'focus'
+    window.codewars = @
+    @state = state
+    @path = path.join atom.getConfigDirPath(), DATA_DIR
+    @pathStatesDir = path.join @path, '.states'
+    @pathWindowState = path.join @pathStatesDir, 'window'
+    @pathFocusState = path.join @pathStatesDir, 'focus'

-      if state.isCodewarsWindow
-        @_clearWindowState ->
-          atom.close()
-        return
+    if state.isCodewarsWindow
+      @_clearWindowState ->
+        atom.close()
+      return

-      if atom.getCurrentWindow().id is 1
-        @firstWindowAtLaunch = true
-        fs.access @pathWindowState, (err) =>
-          if err then @firstWindowAtLaunch = false
+    if atom.getCurrentWindow().id is 1
+      @firstWindowAtLaunch = true
+      fs.access @pathWindowState, (err) =>
+        if err then @firstWindowAtLaunch = false

-      checkIfCodewarsWindow = (textEditorFile) =>
-        if (path.basename textEditorFile) is OPEN_FILE_KEY
-          @_writeWindowState()
-          # Actually create the view now
-          @createView state
+    checkIfCodewarsWindow = (textEditorFile) =>
+      if (path.basename textEditorFile) is OPEN_FILE_KEY
+        @_writeWindowState()
+        # Actually create the view now
+        @createView state

-      mkdirp @pathStatesDir, (err) =>
-        throw err if err
-        # Check if the window has the codewars initial file open
-        disposable = new CompositeDisposable
-        disposable.add atom.workspace.observeTextEditors (editor) =>
-            checkIfCodewarsWindow editor?.getPath()
-            disposable.dispose()
+    mkdirp @pathStatesDir, (err) ->
+      throw err if err
+      # Check if the window has the codewars initial file open
+      disposable = new CompositeDisposable
+      disposable.add atom.workspace.observeTextEditors (editor) ->
+        checkIfCodewarsWindow editor?.getPath()
+        disposable.dispose()

-      # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
-      @subscriptions = new CompositeDisposable
+    @subscriptions = new CompositeDisposable

-      # Register command that toggles this view
-      @subscriptions.add atom.commands.add 'atom-workspace', 'codewars:toggle': => @toggle()
-
-    setTimeout timeout, 0
+    # Register command that toggles this view
+    @subscriptions.add atom.commands.add 'atom-workspace',
+      'codewars:toggle': => @toggle()

   createView: (state) ->
     MainView ?= require './main-view'


#update #3

#update #3

--- lib/main.coffee
+++ lib/main.coffee
@@ -40,7 +40,7 @@
     # fullscreen: true
     icon: "#{__dirname}/../gfx/Dashy.ico"
     'web-preferences':
-      '''plugins': true
+      'plugins': true
     'always-on-top' : false
   webContents = mainWindow.webContents
   webContents.enableDeviceEmulation fitToView: true


Add .webp to image extensions

Add .webp to image extensions

--- lib/main.coffee
+++ lib/main.coffee
@@ -18,7 +18,7 @@
     view.attach()

 # Files with these extensions will be opened as images
-imageExtensions = ['.gif', '.ico', '.jpeg', '.jpg', '.png']
+imageExtensions = ['.gif', '.ico', '.jpeg', '.jpg', '.png', '.webp']
 openUri = (uriToOpen) ->
   uriExtension = path.extname(uriToOpen).toLowerCase()
   if _.include(imageExtensions, uriExtension)


Handle preview:file command with atom.commands

It’s matched against specific selectors in the tree view.

--- lib/main.coffee
+++ lib/main.coffee
@@ -40,8 +40,13 @@
       'markdown-preview:toggle-break-on-single-newline': ->
         atom.config.toggle('markdown-preview.breakOnSingleNewline')

-    atom.workspaceView.on 'markdown-preview:preview-file', (event) =>
-      @previewFile(event)
+    previewFile = @previewFile.bind(this)
+    atom.commands.add '.tree-view .file .name[data-name$=\\.md]', 'markdown-preview:preview-file', previewFile
+    atom.commands.add '.tree-view .file .name[data-name$=\\.mdown]', 'markdown-preview:preview-file', previewFile
+    atom.commands.add '.tree-view .file .name[data-name$=\\.mkd]', 'markdown-preview:preview-file', previewFile
+    atom.commands.add '.tree-view .file .name[data-name$=\\.mkdown]', 'markdown-preview:preview-file', previewFile
+    atom.commands.add '.tree-view .file .name[data-name$=\\.ron]', 'markdown-preview:preview-file', previewFile
+    atom.commands.add '.tree-view .file .name[data-name$=\\.text]', 'markdown-preview:preview-file', previewFile

     atom.workspace.registerOpener (uriToOpen) ->
       try


remove obsolete provider dispose

remove obsolete provider dispose

--- lib/main.coffee
+++ lib/main.coffee
@@ -78,7 +78,6 @@
         'bibliography:reload': => @forceReload()

   deactivate: ->
-    @provider.registration.dispose()
     @commands.dispose()

   serialize: ->


dispose commands on deactivate

dispose commands on deactivate

--- lib/main.coffee
+++ lib/main.coffee
@@ -14,6 +14,9 @@

     # Register commands
     @subscriptions.add atom.commands.add 'atom-workspace', 'nsl-assembler:save-&-compile': => @buildScript()
+
+  deactivate: ->
+    @subscriptions.dispose()

   buildScript: ->
     editor = atom.workspace.getActiveTextEditor()


Access status bar via element

Access status bar via element

--- lib/main.coffee
+++ lib/main.coffee
@@ -31,14 +31,16 @@
         workspaceView.trigger 'window:reload'

     atom.packages.onDidActivateAll ->
-      if atom.workspaceView?.statusBar?
-        incompatibleCount = 0
-        for pack in atom.packages.getLoadedPackages()
-          incompatibleCount++ unless pack.isCompatible()
+      statusBar = document.querySelector('status-bar')
+      return unless statusBar?

-        if incompatibleCount > 0
-          IncompatiblePackagesStatusView = require './incompatible-packages-status-view'
-          incompatiblePackagesStatusView ?= new IncompatiblePackagesStatusView(atom.workspaceView.statusBar, incompatibleCount)
+      incompatibleCount = 0
+      for pack in atom.packages.getLoadedPackages()
+        incompatibleCount++ unless pack.isCompatible()
+
+      if incompatibleCount > 0
+        IncompatiblePackagesStatusView = require './incompatible-packages-status-view'
+        incompatiblePackagesStatusView ?= new IncompatiblePackagesStatusView(atom.workspaceView.statusBar, incompatibleCount)

   deactivate: ->
     incompatiblePackagesStatusView?.remove()


Ace: AppView: assign selected folders machine instance to current file

--- client/Ace/AppView.coffee
+++ client/Ace/AppView.coffee
@@ -171,6 +171,8 @@
       contents          = @ace.getContents()
       oldCursorPosition = @ace.editor.getCursorPosition()

+      file.machine = parent.machine
+
       file.emit 'file.requests.saveAs', contents, name, parent.path
       file.once 'fs.saveAs.finished',   @ace.bound 'saveAsFinished'
       @ace.emit 'AceDidSaveAs', name, parent.path


Add TODO for focus on local annotation

Add TODO for focus on local annotation

--- client/publication.coffee
+++ client/publication.coffee
@@ -628,6 +628,7 @@
       Meteor.Router.toNew Meteor.Router.annotationPath Session.get('currentPublicationId'), Session.get('currentPublicationSlug'), template.data._id

     # Focus by clicking on the annotation
+    # TODO: Make it work when converting local annotation
     $(template.findAll '.body[contenteditable=true]').get(0).focus()

     return # Make sure CoffeeScript does not return anything


getModulesExportingSymbolAt docs

getModulesExportingSymbolAt docs

--- lib/ide-backend.coffee
+++ lib/ide-backend.coffee
@@ -140,5 +140,13 @@
   shutdownBackend: =>
     @process.killProcess() if @isActive()

+  ###
+  getModulesExportingSymbolAt()
+  Get all modules exporting symbol at position
+  buffer: TextBuffer with source
+  range: Range or Point, signifying extent of expression
+  callback: ([module]) ->
+    module: Module name
+  ###
   getModulesExportingSymbolAt: (buffer, range, callback) =>
     @process.findSymbolProvidersInBuffer buffer,range,callback


animation feature detection

animation feature detection

--- coffee/application.coffee
+++ coffee/application.coffee
@@ -475,29 +475,40 @@

 class Animator
   constructor: ->
+    @oldSize = null
+    @uploadWorker = null
     @reset()
-    @oldSize = null
-
+
+  assertNotBusy: ->
+    if @uploadWorker isnt null
+      throw new Error "Animator is busy"
+
   reset: ->
+    if @uploadWorker isnt null
+      @cancelWork()
     @startChain = null
     @startOffset = null
     @endChain = null
     @endOffset = null
     E('animate-view-start').disabled = true
     E('animate-view-end').disabled = true
-
+
   setStart: (observer) ->
+    @assertNotBusy()
     @startChain = observer.getViewCenter()
     @startOffset = observer.getViewOffsetMatrix()
     E('animate-view-start').disabled = false

   setEnd: (observer) ->
+    @assertNotBusy()
     @endChain = observer.getViewCenter()
     @endOffset = observer.getViewOffsetMatrix()
     E('animate-view-end').disabled = false
   viewStart: (observer) ->
+    @assertNotBusy()
     observer.navigateTo @startChain, @startOffset
   viewEnd: (observer) ->
+    @assertNotBusy()
     observer.navigateTo @endChain, @endOffset

   _setCanvasSize: ->
@@ -514,9 +525,24 @@
     [canvas.width, canvas.height] = @oldSize
     @oldSize = null
     canvasSizeUpdateBlocked = false
+
+  beginWork: ->
+    @_setCanvasSize()
+    console.log "Started animation"
+
+  endWork: ->
+    @_restoreCanvasSize()
+    console.log "End animation"
+
+  cancelWork: ->
+    return unless @uploadWorker
+    clearInterval @uploadWorker
+    @uploadWorker = null
+    @endWork()

   animate: (observer, stepsPerGen, generations, callback)->
     return unless @startChain? and @endChain?
+    @assertNotBusy()
     #global (surreally big) view matrix is:
     #
     # Moffset * M(chain)
@@ -553,9 +579,8 @@
     framesBeforeGeneration = stepsPerGen

     imageNameTemplate = E('upload-name').value
-
-    @_setCanvasSize()
-    timerId = setInterval (=>
+    @beginWork()
+    @uploadWorker = setInterval (=>
       observer.navigateTo @startChain, @startOffset
       p = index / totalSteps
       observer.modifyView M.hyperbolicInv Tinterp(p)
@@ -570,19 +595,21 @@
       index +=1
       framesBeforeGeneration -= 1
       if framesBeforeGeneration is 0
-        console.log "Next generaion!"
         doStep()
         framesBeforeGeneration = stepsPerGen

       if index > totalSteps
-        clearInterval timerId
-        @_restoreCanvasSize()
-        console.log "End animation"
+        clearInterval @uploadWorker
+        @endWork()
       ), 100
-
+
+serverSupportsUpload = -> ((""+window.location).match /:8000\//) and true
 # ============================================  app code ===============
 #
 animator = new Animator()
+if serverSupportsUpload()
+  console.log "Enable upload"
+  E('animate-controls').style.display=''

 canvas = E "canvas"
 context = canvas.getContext "2d"


Replace custom Directory identifier with built-in

--- lib/linter-flow.coffee
+++ lib/linter-flow.coffee
@@ -26,10 +26,7 @@

     str = ''
     file = (atom.workspace.getActiveEditor()).getPath()
-    if file.indexOf("/") is -1 # windows
-      file_path = file.substring(0, file.lastIndexOf("\\"))
-    else # unix
-      file_path = file.substring(0, file.lastIndexOf("/"))
+    file_path = path.dirname(file)
     child = spawn @flowPath, ['status', '--json', file_path]
     child.stdout.on 'data', (x) -> str += x
     child.stderr.on 'data', (x) -> str += x


Fixed specs locking up

Fixed specs locking up

Credit: Ivan Žužak

--- lib/svg-preview.coffee
+++ lib/svg-preview.coffee
@@ -5,7 +5,7 @@

 module.exports =
   configDefaults:
-    grammars: [ 'text.xml' ]
+    grammars: [ 'text.plain.null-grammar', 'text.xml' ]

   activate: ->
     atom.workspaceView.command 'svg-preview:toggle', =>


reduce unnecessary text buffer refreshing

--- lib/text-buffer.coffee
+++ lib/text-buffer.coffee
@@ -165,17 +165,20 @@
         textBracket = convertToHoles textRaw            #   ?  => {!!}
         text        = resizeHoles textBracket, indices  # {!!} => {!  !}

-        if @indicesChanged(indices) or @textBufferChanged(text)
-            log 'Text Buffer', "setting goals #{indices}"
-            @removeGoals()
-
+        log 'Text Buffer', "setting goals #{indices}"
+
+        # update text buffer only when there's hole need digging
+        if @textBufferChanged(text)
+            log 'Text Buffer', "digging holes"
             @core.editor.setText text

-            positions   = findHoles text
-            positions.forEach (pos, i) =>
-                index = indices[i]
-                goal  = new Goal @core.editor, index, pos.start, pos.end - 2
-                @goals.push goal
+        # refresh goals
+        @removeGoals()
+        findHoles(text).forEach (pos, i) =>
+            index = indices[i]
+            goal  = new Goal @core.editor, index, pos.start, pos.end - 2
+            @goals.push goal
+

     giveAction: (index, content) -> @protectCursor =>
         log 'Text Buffer', "handling give-action #{content}"


Add update command

Add update command

--- lib/tree-ignore.coffee
+++ lib/tree-ignore.coffee
@@ -17,6 +17,7 @@

     activate: ->
         atom.workspaceView.command "tree-ignore:toggle", => @toggle()
+        atom.workspaceView.command "tree-ignore:update", => @update()

         _bHideState = atom.config.get "tree-ignore.enabled"


Fix sticky error page on angular

Fix sticky error page on angular

--- lineman/app/app.coffee
+++ lineman/app/app.coffee
@@ -29,6 +29,7 @@
   $scope.currentComponent = 'nothing yet'

   $scope.$on 'currentComponent', ->
+    $scope.pageError = null
     angular.element(document.querySelector('.main-container'))

   $scope.$on 'setTitle', (event, title) ->


Update example-01

Update example-01

--- examples/01-bounce/js/src/App.coffee
+++ examples/01-bounce/js/src/App.coffee
@@ -20,6 +20,7 @@
       @rightPane = new Pane()
       @rightPane.setCSS({ right:0, top:0, bottom:0, width:40 })

+      # Create randomly bouncing particles
       for i in [1 .. 40]
         red = new BouncingParticle()
         @leftPane.addChild(red)
@@ -39,7 +40,4 @@
         blue.setRandomPosition()
         blue.setRandomMomentum()

-      # TODO
-      # Create bouncing particles inside those panes
-
     return


using minified jquery

using minified jquery

--- js/UMLEditor.coffee
+++ js/UMLEditor.coffee
@@ -137,7 +137,9 @@
                 source = connection.source
                 target = connection.target
                 type = connection.type
-                @graph.setEdge(source, target, {arrowhead: type}, "#{type}_from_#{source}_to_#{target}")
+                # draw only those edges that go to classes that are drawn!
+                if @getClass(target)?
+                    @graph.setEdge(source, target, {arrowhead: type}, "#{type}_from_#{source}_to_#{target}")

         svg = @svg
         inner = svg.select(".zoomer")


#1534 class then "use strict": multiple directives support

--- nodes.coffee
+++ nodes.coffee
@@ -386,6 +386,7 @@
   isComplex      : -> @hasProperties() or @base.isComplex()
   isAssignable   : -> @hasProperties() or @base.isAssignable()
   isSimpleNumber : -> @base instanceof Literal and SIMPLENUM.test @base.value
+  isString       : -> @base instanceof Literal and IS_STRING.test @base.value
   isAtomic       : ->
     for node in @properties.concat @base
       return no if node.soak or node instanceof Call
@@ -915,13 +916,18 @@
           if node instanceof Value and node.isObject(true)
             exps[i] = @addProperties node, name, o
         child.expressions = exps = flatten exps
-
-  checkStrict: ->
-    for node,i in @body.expressions when node not instanceof Comment
-      return unless node instanceof Value
-      return unless /^['"]use strict['"]$/.test node.unwrapAll().value
-      @body.expressions.splice i,1
-      return @strict = yes
+
+  # `use strict` (and other directives) must be the first expression statement(s)
+  # of a function body. This method ensures the prologue is correctly positioned
+  # above the `constructor`.
+  hoistDirectivePrologue: ->
+    index = 0
+    {expressions} = @body
+    ++index while (node = expressions[index]) and
+      node instanceof Comment or
+      node instanceof Value and
+      node.isString()
+    @directives = expressions.splice 0, index

   # Make sure that a constructor is defined for the class, and properly
   # configured.
@@ -945,7 +951,7 @@
     name = "_#{name}" if name.reserved
     lname = new Literal name

-    @checkStrict()
+    @hoistDirectivePrologue()
     @setContext name
     @walkBody name, o
     @ensureConstructor name
@@ -954,7 +960,7 @@
     if decl
       @body.expressions.unshift new Assign (new Value (new Literal name), [new Access new Literal 'name']), (new Literal "'#{name}'")
     @body.expressions.push lname
-    @body.expressions.unshift(new Value(new Literal "'use strict'")) if @strict
+    @body.expressions.unshift @directives...
     @addBoundFunctions o

     call  = Closure.wrap @body


closes #1669; loop results are now collected in the same way as auto-`return`s are generated

--- nodes.coffee
+++ nodes.coffee
@@ -76,8 +76,12 @@
   # Construct a node that returns the current node's result.
   # Note that this is overridden for smarter behavior for
   # many statement nodes (e.g. If, For)...
-  makeReturn: ->
-    new Return this
+  makeReturn: (res) ->
+    me = @unwrapAll()
+    if res
+      new Call new Literal("#{res}.push"), [me]
+    else
+      new Return me

   # Does this node, or any of its children, contain a node of a certain kind?
   # Recursively traverses down the *children* of the nodes, yielding to a block
@@ -191,12 +195,12 @@

   # A Block node does not return its entire body, rather it
   # ensures that the final expression is returned.
-  makeReturn: ->
+  makeReturn: (res) ->
     len = @expressions.length
     while len--
       expr = @expressions[len]
       if expr not instanceof Comment
-        @expressions[len] = expr.makeReturn()
+        @expressions[len] = expr.makeReturn res
         @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
         break
     this
@@ -281,7 +285,7 @@
   constructor: (@value) ->

   makeReturn: ->
-    if @isStatement() then this else new Return this
+    if @isStatement() then this else super

   isAssignable: ->
     IDENTIFIER.test @value
@@ -373,9 +377,6 @@

   isSplice: ->
     last(@properties) instanceof Slice
-
-  makeReturn: ->
-    if @properties.length then super() else @base.makeReturn()

   # The value can be unwrapped as its inner node, if there are no attached
   # properties.
@@ -1210,9 +1211,12 @@

   isStatement: YES

-  makeReturn: ->
-    @returns = yes
-    this
+  makeReturn: (res) ->
+    if res
+      super
+    else
+      @returns = yes
+      this

   addBody: (@body) ->
     this
@@ -1234,10 +1238,9 @@
     if body.isEmpty()
       body = ''
     else
-      if o.level > LEVEL_TOP or @returns
-        rvar = o.scope.freeVariable 'results'
+      if @returns
+        body.makeReturn rvar = o.scope.freeVariable 'results'
         set  = "#{@tab}#{rvar} = [];\n"
-        body = Push.wrap rvar, body if body
       if @guard
         if body.expressions.length > 1
           body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
@@ -1419,9 +1422,9 @@

   jumps: (o) -> @attempt.jumps(o) or @recovery?.jumps(o)

-  makeReturn: ->
-    @attempt  = @attempt .makeReturn() if @attempt
-    @recovery = @recovery.makeReturn() if @recovery
+  makeReturn: (res) ->
+    @attempt  = @attempt .makeReturn res if @attempt
+    @recovery = @recovery.makeReturn res if @recovery
     this

   # Compilation is more or less as you would expect -- the *finally* clause
@@ -1497,7 +1500,6 @@

   unwrap    : -> @body
   isComplex : -> @body.isComplex()
-  makeReturn: -> @body.makeReturn()

   compileNode: (o) ->
     expr = @body.unwrap()
@@ -1518,7 +1520,7 @@
 # Unlike Python array comprehensions, they can be multi-line, and you can pass
 # the current index of the loop as a second parameter. Unlike Ruby blocks,
 # you can map and filter in a single pass.
-exports.For = class For extends Base
+exports.For = class For extends While
   constructor: (body, source) ->
     {@source, @guard, @step, @name, @index} = source
     @body    = Block.wrap [body]
@@ -1533,14 +1535,6 @@
     @returns = false

   children: ['body', 'source', 'guard', 'step']
-
-  isStatement: YES
-
-  jumps: While::jumps
-
-  makeReturn: ->
-    @returns = yes
-    this

   # Welcome to the hairiest method in all of CoffeeScript. Handles the inner
   # loop, filtering, stepping, and result saving for array, object, and range
@@ -1582,7 +1576,7 @@
     if @returns
       resultPart   = "#{@tab}#{rvar} = [];\n"
       returnResult = "\n#{@tab}return #{rvar};"
-      body         = Push.wrap rvar, body
+      body.makeReturn rvar
     if @guard
       if body.expressions.length > 1
         body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
@@ -1636,9 +1630,10 @@
       return block if block.jumps o
     @otherwise?.jumps o

-  makeReturn: ->
-    pair[1].makeReturn() for pair in @cases
-    @otherwise?.makeReturn()
+  makeReturn: (res) ->
+    pair[1].makeReturn res for pair in @cases
+    @otherwise or= new Block [new Literal 'void 0'] if res
+    @otherwise?.makeReturn res
     this

   compileNode: (o) ->
@@ -1696,9 +1691,10 @@
   compileNode: (o) ->
     if @isStatement o then @compileStatement o else @compileExpression o

-  makeReturn: ->
-    @body     and= new Block [@body.makeReturn()]
-    @elseBody and= new Block [@elseBody.makeReturn()]
+  makeReturn: (res) ->
+    @elseBody  or= new Block [new Literal 'void 0'] if res
+    @body     and= new Block [@body.makeReturn res]
+    @elseBody and= new Block [@elseBody.makeReturn res]
     this

   ensureBlock: (node) ->
@@ -1752,15 +1748,6 @@
 # Faux-nodes are never created by the grammar, but are used during code
 # generation to generate other combinations of nodes.

-#### Push
-
-# The **Push** creates the tree for `array.push(value)`,
-# which is helpful for recording the result arrays from comprehensions.
-Push =
-  wrap: (name, exps) ->
-    return exps if exps.isEmpty() or last(exps.expressions).jumps()
-    exps.push new Call new Value(new Literal(name), [new Access new Literal 'push']), [exps.pop()]
-
 #### Closure

 # A faux-node used to wrap an expressions body in a closure.


Fixes #3063 -- wait a moment so that an error can be raised.

--- nodes.coffee
+++ nodes.coffee
@@ -1092,7 +1092,7 @@

     call  = Closure.wrap @body

-    if @parent
+    if @parent and call.args
       @superClass = new Literal o.scope.freeVariable 'super', no
       @body.expressions.unshift new Extends lname, @superClass
       call.args.push @parent


Update Atom config

Update Atom config

--- atom.cson
+++ atom.cson
@@ -30,6 +30,7 @@
       "language-property-list"
       "open-on-github"
       "package-generator"
+      "whitespace"
     ]
     projectHome: "/home/ai/Dev"
     telemetryConsent: "limited"


First attempt at adjusting code for the new ITAD API endpoint and schema.

Also, improve the quality of the imported data:
- Mark imported DRM-free downloads as redeemed and owned
- Properly indicate the source for Groupees and Flying Bundle games

Contains initial solutions for issue #28

--- itad_importer.user.coffee
+++ itad_importer.user.coffee
@@ -1,7 +1,7 @@
 ### IsThereAnyDeal.com Collection Importer
 // ==UserScript==
 // @name IsThereAnyDeal.com Collection Importer
-// @version 0.1b16
+// @version 0.1b17
 // @namespace http://isthereanydeal.com/
 // @description Adds buttons to various sites to export your game lists to ITAD
 // @icon http://s3-eu-west-1.amazonaws.com/itad/images/banners/50x50.gif
@@ -10,8 +10,8 @@
 // @require https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js
 //
 // @match *://fireflowergames.com/my-lists/*
-// @match *://flyingbundle.com/users/account
-// @match *://www.flyingbundle.com/users/account
+// @match *://flyingbundle.com/users/account*
+// @match *://www.flyingbundle.com/users/account*
 // @match *://www.gog.com/account*
 // @match *://www.gog.com/order/status/*
 // @match *://groupees.com/purchases*
@@ -77,14 +77,18 @@
   .append(label)
   .append(a)

-humble_parse = -> { title: x.textContent.trim(), sources: ['humblestore']
+humble_parse = -> {
+  version: "02",
+  data: {
+    title: x.textContent.trim(),
+    copies: [{ type: 'humblestore' }]
   } for x in $('div.row').has(
-  # Humble Library has no easy way to list only games
-  ' .downloads.windows .download,
-    .downloads.linux .download,
-    .downloads.mac .download,
-    .downloads.android .download'
-  ).find('div.title')
+    # Humble Library has no easy way to list only games
+    ' .downloads.windows .download,
+      .downloads.linux .download,
+      .downloads.mac .download,
+      .downloads.android .download'
+  ).find('div.title')}

 # Scrapers are looked up first by domain (lightweight) and then by
 # a regex check on the URL (accurate).
@@ -103,10 +107,17 @@
           results = $('table.wl-table td.product-name a')

         {
-          title: $(x).text().trim()
-          url: x.href
-          sources: ['fireflower']
-        } for x in results
+          version: "02",
+          data: {
+            title: $(x).text().trim()
+            url: x.href
+            copies: [{
+              type: 'fireflower',
+              status: 'redeemed',
+              owned: 1,
+            }]
+          } for x in results
+        }
       'insert_button': ->
         # XXX: If you can debug the broken behaviour with <button>, please do.
         # I don't have time.
@@ -117,13 +128,24 @@
       'is_wishlist': true

   'flyingbundle.com':
-    'https?://(www\\.)?flyingbundle\\.com/users/account':
+    'https?://(www\\.)?flyingbundle\\.com/users/account#?':
       'source_id': 'flying_bundle'
       'game_list': -> {
-        title: $(x).text()
-        sources: 'flying_bundle'
-      } for x in $(".div_btn_download[href^='/users/sources']"
-      ).parents('li').find(':first')
+        version: "02",
+        data: {
+          title: $(x).text(),
+          copies: [{
+            type: 'Flying Bundle',
+            status: 'redeemed',
+            owned: 1,
+            source: {
+              type: "s",
+              id: "other"
+            }
+          }]
+        } for x in $(".div_btn_download[href^='/users/sources']"
+        ).parents('li').find(':first')
+      }
       'insert_button': ->
         li = $("

"
         ).appendTo('.legenda_points ul')
@@ -139,9 +161,16 @@
       'game_list': ->
         console.debug("game_list called for GOG order status page")
         {
-          title: gog_prepare_title(x)
-          sources: ['gog']
-        } for x in $('.order + .container .product-row')
+          "version": "02",
+          "data": {
+            title: gog_prepare_title(x)
+            copies: [{
+              type: 'gog',
+              status: 'redeemed',
+              owned: 1,
+            }]
+          } for x in $('.order + .container .product-row')
+        }

       'insert_button': ->
         console.debug("insert_button called for GOG order status page")
@@ -161,10 +190,17 @@
       'game_list': ->
         console.debug("game_list called for GOG collection page")
         {
-          id: attr(x, 'gog-product')
-          title: gog_prepare_title(x)
-          sources: ['gog']
-        } for x in $('.product-row')
+          "version": "02",
+          "data": {
+            # id: attr(x, 'gog-account-product')
+            title: gog_prepare_title(x)
+            copies: [{
+              type: 'gog',
+              status: 'redeemed',
+              owned: 1,
+            }]
+          } for x in $('.product-row')
+        }

       'insert_button': ->
         console.debug("insert_button called for GOG collection page")
@@ -184,10 +220,21 @@
       'source_id': 'other'
       'game_list': ->
         {
-          title: x.textContent.trim(),
-          sources: ['other']
-        } for x in $('.product ul.dropdown-menu')
-                    .parents('.details').find('h3')
+          "version": "02",
+          "data": {
+            title: x.textContent.trim(),
+            copies: [{
+              type: 'Groupees.com',
+              status: 'redeemed',
+              owned: 1,
+              source: {
+                type: "s",
+                id: "other"
+              }
+            }]
+          } for x in $('.product ul.dropdown-menu')
+                      .parents('.details').find('h3')
+        }
       'insert_button': ->
         $("<button></button>")
           .css({ float: 'right' }).addClass('button btn btn-sm btn-primary')
@@ -198,8 +245,16 @@
     'https://www\\.humblebundle\\.com/home/library/?':
       'source_id': 'humblestore'
       'game_list': -> {
-        title: x.textContent.trim(), sources: ['humblestore']
-      } for x in $('.subproduct-selector h2')
+        "version": "02",
+        "data": {
+          title: x.textContent.trim(),
+          copies: [{
+            type: 'humblestore',
+            status: 'redeemed',
+            owned: 1,
+          }]
+        } for x in $('.subproduct-selector h2')
+      }
       # TODO: Figure out how to filter out non-games again
       # TODO: Figure out how to tap their Backbone.js store

@@ -263,14 +318,15 @@
 # Callback for the button
 scrapeGames = (scraper_obj) ->
   params = {
-    json: JSON.stringify(scraper_obj.game_list()),
-    source: scraper_obj.source_id
+    file: btoa(unescape(encodeURIComponent(JSON.stringify(
+      scraper_obj.game_list())))),
+    upload: 'x'
   }

   url = if scraper_obj.is_wishlist?
-    'https://isthereanydeal.com/outside/user/wait/3rdparty'
+    'https://isthereanydeal.com/waitlist/import/'
   else
-    'https://isthereanydeal.com/outside/user/collection/3rdparty'
+    'https://isthereanydeal.com/collection/import/'

   # **TODO:** Figure out why attempting to use an iframe for non-HTTPS sites
   # navigates the top-level window.


Add Time Picker To Example

Add Time Picker To Example

--- examples/coffeescript/example.coffee
+++ examples/coffeescript/example.coffee
@@ -80,6 +80,14 @@

         div className: "row",
           div className: "sm-col-12",
+            f.input "startTime",
+              type: "time",
+              xs: "6"
+              placeholder: "12:00pm"
+              label: "Start Time"
+
+        div className: "row",
+          div className: "sm-col-12",
             h3 {}, "Additional Sketchy Info"

         div className: "row",


Added Title setup through response headers

--- lib/assets/javascripts/wiselinks.js.coffee
+++ lib/assets/javascripts/wiselinks.js.coffee
@@ -52,18 +52,18 @@
     self = this
     $(document).trigger('wiselinks:loading')

-    console.log @$target
-
     $.ajax(
       url: url
       headers:
         'X-Slide': slide
-      success: (data) ->
+      success: (data, status, xhr) ->
+        document.title = xhr.getResponseHeader('X-Title')
+
         $target = if target? then $(target) else self.$target
         $target.html(data)

         $(document).trigger('wiselinks:success', data)
-      error: (xhr)->
+      error: (xhr, status, error)->
         $(document).trigger('wiselinks:error', xhr)
       dataType: "html"
     )


return back to commit (b2bc1ed) and add check method of source

--- lib/client/javascripts/nali/view.js.coffee
+++ lib/client/javascripts/nali/view.js.coffee
@@ -93,9 +93,11 @@
       [ chain, segments... ] = match[2].split '/'
       if result = @analizeChain chain, ( if match[1].length is 1 then @ else @model )
         [ source, method ] = result
-        args = @parseUrlSegments segments
-        args.unshift params if params
-        source[ method ] args...
+        if method of source
+          args = @parseUrlSegments segments
+          args.unshift params if params
+          source[ method ] args...
+        else console.warn "Method %s not exists of %O", method, source
     else @redirect url, params
     @

@@ -302,8 +304,5 @@
   analizeChain: ( chain, source = @model ) ->
     segments = chain.split '.'
     property = segments.pop()
-    source = @getSource segments, source
-    if source and property of source then [ source, property ]
-    else
-      console.warn "Property \"%s\" not exists in %O", property, source
-      null
+    return null unless source = @getSource segments, source
+    [ source, property ]


fix prototype_access lexing

fix prototype_access lexing

--- lexer.coffee
+++ lexer.coffee
@@ -98,7 +98,7 @@
   # 'if' will result in an ['IF', "if"] token.
   tag: if KEYWORDS.indexOf(id) >= 0 then id.toUpperCase() else 'IDENTIFIER'
   tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
-  @tag(-1, 'PROTOTYPE_ACCESS') if tag is 'IDENTIFIER' and @value() is '::'
+  @tag(1, 'PROTOTYPE_ACCESS') if tag is 'IDENTIFIER' and @value() is '::'
   if tag is 'IDENTIFIER' and @value() is '.' and !(@value(2) is '.')
     if @tag(2) is '?'
       @tag(1, 'SOAK_ACCESS')


making regexes stricter about their flags.

--- lexer.coffee
+++ lexer.coffee
@@ -165,7 +165,8 @@
     return false unless @chunk.match REGEX_START
     return false if     include NOT_REGEX, @tag()
     return false unless regex: @balanced_token ['/', '/']
-    regex: + (flags: @chunk.substr(regex.length).match REGEX_FLAGS)
+    return false unless end: @chunk.substr(regex.length).match REGEX_END
+    regex: + flags: end[2] if end[2]
     if regex.match REGEX_INTERPOLATION
       str: regex.substring(1).split('/')[0]
       str: str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
@@ -493,7 +494,7 @@
 # Regex-matching-regexes.
 REGEX_START        : /^\/[^\/ ]/
 REGEX_INTERPOLATION: /([^\\]\$[a-zA-Z_@]|[^\\]\$\{.*[^\\]\})/
-REGEX_FLAGS        : /^[imgy]{0,4}/
+REGEX_END          : /^(([imgy]{1,4})\b|\W)/
 REGEX_ESCAPE       : /\\[^\$]/g

 # Token cleaning regexes.


:racehorse: Use Deprecation::getStackCount

--- lib/deprecation-cop-status-bar-view.coffee
+++ lib/deprecation-cop-status-bar-view.coffee
@@ -34,9 +34,9 @@

   getDeprecatedSelectorCount: ->
     @deprecatedSelectorCount ?= getSelectorDeprecationsCount()
-
+
   getDeprecatedCallCount: ->
-    Grim.getDeprecations().map((d) -> d.getStacks().length).reduce(((a, b) -> a + b), 0)
+    Grim.getDeprecations().map((d) -> d.getStackCount()).reduce(((a, b) -> a + b), 0)

   updateDeprecatedSelectorCount: =>
     @deprecatedSelectorCount = null


Added isReady to map to prevent the scrollback to fire when the overlay is not fully loaded

--- js/functions.coffee
+++ js/functions.coffee
@@ -32,7 +32,7 @@
     }

   _scrollCallback : (props) ->
-    if TRIP.map.isReady
+    if TRIP.map.isReady()
       currentPOIIndex = TRIP.pois.calculateCurrentPOIIndex(props.curTop,TRIP.scrolling.settings)
       TRIP.map.updatePosition(currentPOIIndex,TRIP.pois.list)
   }


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -111,6 +111,6 @@
   5.times ->
     obj.update()

-  equals obj.I.health
+  equals obj.I.health, 5

 module()


make the mission summaries on live map clickable

--- src/scripts/controllers/mapController.coffee
+++ src/scripts/controllers/mapController.coffee
@@ -146,7 +146,7 @@
       lon = data.mission.longitude
       if lat? && lon?
         v = @updateVehicle(key, lat, lon)
-        angular.extend(v.payload, data.mission)
+        @updateMarkerPopup(v, data.mission)
     )

   onMissionUpdate: (data) =>
@@ -157,7 +157,7 @@
       lon = payload.mission.longitude
       if lat? && lon?
         v = @updateVehicle(key, lat, lon)
-        angular.extend(v.payload, payload.mission)
+        @updateMarkerPopup(v, data.mission)
     )

   onMissionEnd: (data) =>
@@ -191,15 +191,25 @@
       # (kevinh - if we receive a message before we know the vehicle loc just drop it because having a marker without a loc freaks out leaflet)
       vehicleKey = @vehicleKey(data.missionId)
       marker = @scope.vehicleMarkers[vehicleKey]
-      if marker?
-        # We merge the misc payload fields into one dictionary - showing the latest combination of all data
-        angular.extend(marker.payload, data.payload)
-        p = marker.payload
-        marker.message = "User: #{p.userName}, summary: #{p.summaryText} - #{p.flightDuration / 60} minutes"
-    )
+      @updateMarkerPopup(marker, data.payload)
+    )
+
+  # Change our popup text as needed
+  updateMarkerPopup: (marker, payload) =>
+    if marker?
+      # We merge the misc payload fields into one dictionary - showing the latest combination of all data
+      angular.extend(marker.payload, payload)
+      p = marker.payload
+      marker.message = "#{p.userName}

+        #{p.summaryText}

+        #{Math.round(p.flightDuration / 60)} minutes

+        "

   motionTracking: (vehicleKey, latlng) =>
     v = @scope.vehiclePaths[vehicleKey] ? { color: '#f76944', weight: 7, latlngs: []}
+
+    # Only keep the last 100 pts around
+    v.latlngs = v.latlngs[..99]
     v.latlngs.push(latlng)
     @scope.vehiclePaths[vehicleKey] = v


refactor file parser

refactor file parser

--- parsers/file_parser.coffee
+++ parsers/file_parser.coffee
@@ -19,13 +19,13 @@

 FileParser =
   relativeImages: []
-  absoluteImages: []

   sameDirMarkdowns:  []
   otherDirMarkdowns: []

   currentPath: ''
-  projectDir: path.join(__dirname, '..', '..')
+
+  files: []

   parse: (file, failed) ->
     App.failures = []
@@ -46,32 +46,16 @@
       @sameDirMarkdowns.push sameDirMarkdownFileMatch[1] if sameDirMarkdownFileMatch
       @otherDirMarkdowns.push otherDirMarkdownFileMatch[1] if otherDirMarkdownFileMatch

-    @checkRelativeImages(@relativeImages, file.fullPath)
-    @checkSameDirMarkdowns(@sameDirMarkdowns, @currentPath, file.fullPath)
-    @checkOtherDirMarkdowns(@otherDirMarkdowns, @currentPath, file.fullPath)
+      @files = @relativeImages.concat(@sameDirMarkdowns, @otherDirMarkdowns)
+
+    @checkFiles(@files, @currentPath, file.fullPath)

     failed = true if App.failures.length
     @relativeImages = @sameDirMarkdowns = @otherDirMarkdowns = []

     failed

-  checkRelativeImages: (images, refFile) ->
-    _.each images, (file) =>
-      try
-        fd = fs.openSync path.join(@projectDir, 'images', file), 'r'
-        fs.closeSync(fd)
-      catch err
-        @printErrorAndReturnFailure(file, refFile)
-
-  checkSameDirMarkdowns: (files, curPath, refFile) ->
-    _.each files, (file) =>
-      try
-        fd = fs.openSync path.join(curPath, file), 'r'
-        fs.closeSync(fd)
-      catch err
-        @printErrorAndReturnFailure(file, refFile)
-
-  checkOtherDirMarkdowns: (files, curPath, refFile) ->
+  checkFiles: (files, curPath, refFile) ->
     _.each files, (file) =>
       resolvedPath = path.resolve(curPath, file)
       try


fix event handling order

fix event handling order

--- graphics/002-rotated-wahlque/csmain.coffee
+++ graphics/002-rotated-wahlque/csmain.coffee
@@ -51,14 +51,14 @@
             y = y - height / 2

             if x > 0
+                frame = rotater.left(frame, y / height * 2)
+            else
+                frame = rotater.right(frame, - y / height * 2)
+
+            if y > 0
                 frame = rotater.up(frame, x / width * 2)
             else
                 frame = rotater.down(frame, - x / width * 2)
-
-            if y > 0
-                frame = rotater.left(frame, y / height * 2)
-            else
-                frame = rotater.right(frame, - y / height * 2)

         bean.add(
             $('#btnStart').get(0), 'click', (-> invoke())


this is crazy if this works

this is crazy if this works

--- mapping.coffee
+++ mapping.coffee
@@ -13,7 +13,7 @@
   ]

   mustache: affine (face) -> [
-    [249, 0, face.nose.x, face.nose.y]
+    [249, 0, face.mouth_center.x, face.nose.y]
     [249, 105, face.mouth_center.x, face.mouth_center.y]
   ]


Updating the automation output room

Updating the automation output room

--- scripts/crashlytics-notifications.coffee
+++ scripts/crashlytics-notifications.coffee
@@ -38,9 +38,9 @@
         issue = data.payload

         if issue.impact_level == 1
-          robot.messageRoom '#publishmobileautomati', 'New Crashlytics issue in ' + app + '! See it at ' + issue.url
+          robot.messageRoom '#mobile-engineering', 'New Crashlytics issue in ' + app + '! See it at ' + issue.url
         else
-          robot.messageRoom '#publishmobileautomati', 'Crashlytics issue ' + issue.display_id + ' in ' + app + ' upgraded to impact level' + issue.impact_level + '. Affected ' + issue.impacted_devices_count + ' users ' + issue.crashes_count + ' times.'
+          robot.messageRoom '#mobile-engineering', 'Crashlytics issue ' + issue.display_id + ' in ' + app + ' upgraded to impact level' + issue.impact_level + '. Affected ' + issue.impacted_devices_count + ' users ' + issue.crashes_count + ' times.'

         res.writeHead 204, { 'Content-Length': 0}


Agent pie: edit transitions

Agent pie: edit transitions

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -291,10 +291,10 @@
             maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
             return
         )
-        .attr("x",0)
-        .attr("y",0)
+        #.attr("x",0)
+        #.attr("y",0)
         .transition()
-            .duration(1000)
+            .duration(500)
             .attr('x', (d) ->
                 d.x
             ).attr('y', (d) ->


fullpieagent testing

fullpieagent testing


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -286,7 +286,7 @@
             # check whether the default position
             #   overlaps any other labels
             conflicts = []
-            labelLayout.visit( (node) -> #, x1, y1, x2, y2) ->
+            labelLayout.visit( (node, x1, y1, x2, y2) ->
                 #recurse down the tree, adding any overlapping
                 #node is the node in the quadtree,
                 #node.point is the value that we added to the tree


SocialWorker: return error if delegate is not set

--- workers/social/lib/social/models/s3.coffee
+++ workers/social/lib/social/models/s3.coffee
@@ -35,6 +35,8 @@

     { connection: { delegate } } = client

+    return callback new KodingError 'Delegate is not set'  unless delegate
+
     unless delegate.type is 'registered'
       return callback new KodingError 'Not allowed'


vm: add disableSecurePage flag to schema

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -111,6 +111,7 @@
       webHome           : String
       planCode          : String
       subscriptionCode  : String
+      disableSecurePage : Boolean
       vmType            :
         type            : String
         default         : 'user'


remove alert message

remove alert message

--- Framework/Views/Controls/SearchResultsTable_Framework.coffee
+++ Framework/Views/Controls/SearchResultsTable_Framework.coffee
@@ -21,7 +21,7 @@
     @table.addEventListener("click", (e) =>
       switch e.row.type
         when "noResultsRow", "loadMoreRow", "retryRow"
-          alert 'do nothing'
+          Ti.API.info("do nothing")
         else @options.onTableClick(e)
       # if e.row.type != "noResultsRow" && e.row.type != "loadMoreRow" && e.row.type != "retryRow"
         # @options.onTableClick(e)


Remove test error

Remove test error

--- lib/global_client_setup.coffee
+++ lib/global_client_setup.coffee
@@ -61,7 +61,6 @@
   if sd.RAYGUN_KEY
     rg4js 'enableCrashReporting', true
     rg4js 'apiKey', sd.RAYGUN_KEY
-    rg4js 'send', new Error()

 setupJquery = ->
   require 'typeahead.js/dist/typeahead.bundle.min.js'


Reintroduce computeIntactRanges and adapt it to canvas code.

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -151,7 +151,8 @@
       @tokenColorCache[flatScopes] = color
     @tokenColorCache[flatScopes]

-  drawLines: (firstRow, lastRow, offsetRow, context) ->
+  drawLines: (context, firstRow, lastRow, offsetRow) ->
+    return if firstRow > lastRow
     lines = @editor.linesForScreenRows(firstRow, lastRow)
     lineHeight = @getLineHeight()
     charHeight = @getCharHeight()
@@ -188,6 +189,23 @@
           x += w * charWidth
     context.fill()

+  copyBitmapPart: (context, bitmapCanvas, srcRow, destRow, rowCount) ->
+    lineHeight = @getLineHeight()
+    context.drawImage(bitmapCanvas,
+        0, srcRow * lineHeight,
+        bitmapCanvas.width, rowCount * lineHeight,
+        0, destRow * lineHeight,
+        bitmapCanvas.width, rowCount * lineHeight)
+
+  fillGapsBetweenIntactRanges: (context, intactRanges, firstRow, lastRow) ->
+    currentRow = firstRow
+    # intactRanges is sorted, we can safely fill between ranges
+    for intact in intactRanges
+      @drawLines(context, currentRow, intact.start-1, currentRow-firstRow)
+      currentRow = intact.end
+    if currentRow <= lastRow
+      @drawLines(context, currentRow, lastRow, currentRow-firstRow)
+
   update: =>
     return unless @editorView?
     return unless @displayBuffer.tokenizedBuffer.fullyTokenized
@@ -199,19 +217,13 @@
     firstRow = @getFirstVisibleScreenRow()
     lastRow = @getLastVisibleScreenRow()

-    # TODO: for now we don't handle screen changes, simply ask for a full redraw
-    if @pendingChanges.length > 0
-      @offscreenFirstRow = null
-      @pendingChanges = []
-
-    if @offscreenFirstRow?
-      @context.drawImage(@offscreenCanvas, 0, (@offscreenFirstRow-firstRow) * @getLineHeight())
-      if firstRow < @offscreenFirstRow
-        @drawLines(firstRow, @offscreenFirstRow, 0, @context)
-      if lastRow > @offscreenLastRow
-        @drawLines(@offscreenLastRow, lastRow, @offscreenLastRow-firstRow, @context)
+    intactRanges = @computeIntactRanges(firstRow, lastRow)
+    if intactRanges.length is 0
+      @drawLines(@context, firstRow, lastRow, 0)
     else
-      @drawLines(firstRow, lastRow, 0, @context)
+      for intact in intactRanges
+        @copyBitmapPart(@context, @offscreenCanvas, intact.domStart, intact.start-firstRow, intact.end-intact.start)
+      @fillGapsBetweenIntactRanges(@context, intactRanges, firstRow, lastRow)

     # copy displayed canvas to offscreen canvas
     @offscreenCanvas.width = @lineCanvas[0].width
@@ -231,3 +243,54 @@
       width: canvas.scrollWidth,
       height: @getMinimapHeight()
     }
+
+  computeIntactRanges: (firstRow, lastRow) ->
+    return [] if !@offscreenFirstRow? and !@offscreenLastRow?
+
+    intactRanges = [{start: @offscreenFirstRow, end: @offscreenLastRow, domStart: 0}]
+
+    for change in @pendingChanges
+      newIntactRanges = []
+      for range in intactRanges
+        if change.end < range.start and change.screenDelta != 0
+          newIntactRanges.push(
+            start: range.start + change.screenDelta
+            end: range.end + change.screenDelta
+            domStart: range.domStart
+          )
+        else if change.end < range.start or change.start > range.end
+          newIntactRanges.push(range)
+        else
+          if change.start > range.start
+            newIntactRanges.push(
+              start: range.start
+              end: change.start - 1
+              domStart: range.domStart)
+          if change.end < range.end
+            newIntactRanges.push(
+              start: change.end + change.screenDelta + 1
+              end: range.end + change.screenDelta
+              domStart: range.domStart + change.end + 1 - range.start
+            )
+
+      intactRanges = newIntactRanges
+
+    @truncateIntactRanges(intactRanges, firstRow, lastRow)
+
+    @pendingChanges = []
+
+    intactRanges
+
+  truncateIntactRanges: (intactRanges, firstRow, lastRow) ->
+    i = 0
+    while i < intactRanges.length
+      range = intactRanges[i]
+      if range.start < firstRow
+        range.domStart += firstRow - range.start
+        range.start = firstRow
+      if range.end > lastRow
+        range.end = lastRow
+      if range.start >= range.end
+        intactRanges.splice(i--, 1)
+      i++
+    intactRanges.sort (a, b) -> a.domStart - b.domStart


Fix crash when reloading freshly installed package detail

- We have to give this view the Package object, not just a plain JS object of
the metadata.

--- lib/package-detail-view.coffee
+++ lib/package-detail-view.coffee
@@ -98,7 +98,8 @@

   subscribeToPackageManager: ->
-    @subscribe @packageManager, 'theme-installed package-installed theme-install-failed package-install-failed', (pack) =>
+    @subscribe @packageManager, 'theme-installed package-installed', (pack) =>
+      @pack = atom.packages.getLoadedPackage(pack.name)
       @updateInstalledState()

     @subscribe @packageManager, 'theme-updated package-updated', (pack, newVersion) =>


Fix material with unknown type

Fix material with unknown type

--- client/components/Material.coffee
+++ client/components/Material.coffee
@@ -9,15 +9,15 @@

 MaterialProper = (props) ->
-    if props.material.type == 'page'
+    if props.material?.type == 'page'
         `<Page {...props} />`
-    else if props.material.type == 'level'
+    else if props.material?.type == 'level'
         `<Level {...props} />`
-    else if props.material.type == 'contest'
+    else if props.material?.type == 'contest'
         `<Contest {...props} />`
-    else if props.material.type == 'epigraph'
+    else if props.material?.type == 'epigraph'
         `<Page {...props} />`
-    else if props.material.type == 'problem'
+    else if props.material?.type == 'problem'
         `<Problem {...props} />`
     else

Unknown material type


Updated english warning

Updated english warning

--- client/components/Sceleton.coffee
+++ client/components/Sceleton.coffee
@@ -177,8 +177,8 @@
             <TopPanel me={@props.me} myUser={@props.myUser} toggleTree={@toggleTree}/>

                 {@props.lang == "en" &&
-

-                        English version is under construction. Not all parts of the site has been translated yet.
+

+                        English version is under construction. All contents of the site should be already translated (mostly using machine translation), but some functions may not work properly. Please contact me if you find any problems.

                 }
                 <Grid fluid>


add login/logout class to the button

add login/logout class to the button

--- modules/gui/lib/widgets/LoginForm.coffee
+++ modules/gui/lib/widgets/LoginForm.coffee
@@ -23,19 +23,31 @@
                 content: ->

                     form method:'post', action:'#', class:'gui widgets LoginForm', ->
-
-                        p class:'hint'
-
-                        user = @identity.login()
-
-                        if !user
+
+                        if !(user = @identity.login())
+                            p class:'hint'
+
                             div (if @data.minimized then style:'display:none' else {}), ->
                                 input name:'user', value:'', placeholder: @data.strings.user

                                 input name:'pass', value:'', type:'password', placeholder:@data.strings.pass

-                        button type: 'submit', name:'button', ->
-                            if user then @data.strings.logout else @data.strings.login
+                        attr =
+                            type: 'submit'
+                            name:'button'
+                            class: if user then 'logout' else 'login'
+
+                        if cls = @data.button?.class || @data.button
+                            attr.class = attr.class+' '+cls
+
+                        title = if user then @data.strings.logout else @data.strings.login
+
+                        if @data.button?.title
+                            attr.title = title
+
+                        button attr, ->
+                            if !@data.button?.title
+                                text title

             , config


Collaboration: Fix type argument value

Collaboration: Fix type argument value

--- client/ide/RealTimeManager.coffee
+++ client/ide/RealTimeManager.coffee
@@ -148,7 +148,7 @@
     data   = model[methods[type]] initialValue
     model.getRoot().set key, data

-    @bindRealtimeListeners data, 'type'
+    @bindRealtimeListeners data, type

     return data


surgery ops to the old coding

surgery ops to the old coding

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -253,15 +253,19 @@
     # returns them to the caller. Override it to do some real job.
     # The captured groups may be used by the overrider or ditched.
     process: (request, response, next) ->
-        gdomain = null; gresource = null
-        resources = @constructor.resources or []
-        domains = @constructor.domains or [/^.+$/]
-        pathname = url.parse(request.url).pathname
-        hostname = _.first request.headers.host.split ":"
-        pdomain = (p) -> gdomain = hostname.match p
-        presource = (p) -> gresource = pathname.match p
-        pdomain = try _.find(domains, pdomain) or null
-        presource = _.find(resources, presource) or null
+        gdomain = null; gresource = null # var holders
+        assert resources = @constructor.resources or []
+        assert domains = @constructor.domains or [/^.+$/]
+        assert pathname = url.parse(request.url).pathname
+        assert identify = @constructor.identify().underline
+        hostname = try _.head request.headers.host.split ":"
+        assert not _.isEmpty hostname, "missing hostname"
+        pdomain = (repat) -> gdomain = hostname.match repat
+        presource = (repat) -> gresource = pathname.match repat
+        xdomain = try _.find(domains, pdomain) or null # side
+        xresource = _.find(resources, presource) or null # side
+        message = "Begin service processing sequence in %s"
+        logger.debug message.toString().yellow, identify
         assert gdomain isnt null, "missing the domain"
         assert gresource isnt null, "missing resource"
         @emit "process", gdomain, gresource, arguments...


Fix the way @location works

Fix the way @location works

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -72,9 +72,9 @@
     # the deduced default is returned. Default location is the first
     # resource regular expression pattern being unescaped to string.
     @location: (location) ->
-        auto = _.head(@resources)?.unescape()
-        @$location = auto if @$location is undefined
-        return @$location if arguments.length is 0
+        current = => @$location? or automatic
+        automatic = _.head(@resources)?.unescape()
+        return current() if arguments.length is 0
         isLocation = _.isString location
         noLocation = "The location is not a string"
         throw new Error noLocation unless isLocation


Prevent multiple fetches from on-google-sign-in

--- elements/livesnooker-api.coffee
+++ elements/livesnooker-api.coffee
@@ -31,9 +31,10 @@
     @data = data

   onGoogleSignIn: (event, auth) ->
-    data.authentication = auth
-    @maybeFetchUser()
-    @fire('iron-signal', {name: "api-ready"}) if data.authentication
+    if data.authentication != auth
+      data.authentication = auth
+      @maybeFetchUser()
+      @fire('iron-signal', {name: "api-ready"}) if data.authentication

   maybeFetchUser: ->
     @fetchUser() if data.host && data.authentication


Display single error as array of one.

Display single error as array of one.

--- src/Witness/_witness/Scripts/witness-ui/ScenarioViewModel.coffee
+++ src/Witness/_witness/Scripts/witness-ui/ScenarioViewModel.coffee
@@ -15,7 +15,10 @@
 		@errors = ko.observableArray []

 		@scenario.on.failed.addHandler (errors) =>
-			@errors errors
+			if jQuery.isArray errors
+				@errors errors
+			else
+				@errors [ errors ]

 	templateId: "scenario"


FB load script from ajax

FB load script from ajax

--- app/assets/javascripts/sharing_tags/share/facebook.js.coffee
+++ app/assets/javascripts/sharing_tags/share/facebook.js.coffee
@@ -23,14 +23,18 @@
     # js.src = "//connect.facebook.net/en_US/sdk/debug.js";
     if not FB?
       sdk_url = locale + (if @debug then "/sdk/debug.js" else "/all.js")
-
-      `(function(d, s, id){
-      var js, fjs = d.getElementsByTagName(s)[0];
-      if (d.getElementById(id)) {return;}
-      js = d.createElement(s); js.id = id;
-      js.src = "http://connect.facebook.net/" + sdk_url;
-      fjs.parentNode.insertBefore(js, fjs);
-      }(document, 'script', 'facebook-jssdk'));`
+      jQuery.ajax(
+        url: "//connect.facebook.net/#{sdk_url}"
+        dataType: "script"
+        cache: true
+      )
+#      `(function(d, s, id){
+#      var js, fjs = d.getElementsByTagName(s)[0];
+#      if (d.getElementById(id)) {return;}
+#      js = d.createElement(s); js.id = id;
+#      js.src = "http://connect.facebook.net/" + sdk_url;
+#      fjs.parentNode.insertBefore(js, fjs);
+#      }(document, 'script', 'facebook-jssdk'));`

   share: (provider = @provider)->
     @callback.before_sharing(provider)


Remove standard text

Remove standard text

--- app/assets/javascripts/widgit/admin/modules/editor.js.coffee
+++ app/assets/javascripts/widgit/admin/modules/editor.js.coffee
@@ -25,7 +25,7 @@
     $.ajax
       url: "/admin/widget_groups/new",
       method: 'GET'
-      data: { widget_group: { widgets_attributes: [{ type: type, text: '', columns: 12 }] } }
+      data: { widget_group: { widgets_attributes: [{ type: type, columns: 12 }] } }

 $(document).ready ->
   window.editor = new Editor()


fix(composer): mod+(a|x|c|v) always fall through to window

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -12,7 +12,9 @@
 mousetrap.prototype.stopCallback = (e, element, combo, sequence) ->
   withinTextInput = element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable
   if withinTextInput
-    return /(mod|command|ctrl)/.test(combo) is false
+    isPlainKey = /(mod|command|ctrl)/.test(combo)
+    isReservedTextEditingShortcut = /(mod|command|ctrl)\+(a|x|c|v)/.test(combo)
+    return isPlainKey || isReservedTextEditingShortcut
   return false

 class KeymapManager


Inline the handleKeyEvent functions

Inline the handleKeyEvent functions

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -411,53 +411,30 @@
   #
   # * `event` A `KeyboardEvent` of type 'keydown'
   handleKeyboardEvent: (event, {replay, disabledBindings}={}) ->
-    eventResult = @_handleKeyboardEvent(event, @queuedKeystrokes, disabledBindings)
-    return unless eventResult?
-    {keystroke, dispatchedBinding, queuedKeystrokes, exactMatch, partialMatches} = eventResult
-
-    hasPartialMatches = partialMatches? and partialMatches.length > 0
+    keystroke = @keystrokeForKeyboardEvent(event)
+
+    # We dont care about bare modifier keys in the bindings. e.g. `ctrl y` isnt going to work.
+    if event.type is 'keydown' and @queuedKeystrokes.length > 0 and isAtomModifier(keystroke)
+      event.preventDefault()
+      return

     @queuedKeystrokes.push(keystroke)
     @queuedKeyboardEvents.push(event)
-    @bindingsToDisable.push(dispatchedBinding) if dispatchedBinding
-
-    if queuedKeystrokes? and hasPartialMatches
-      enableTimeout = (
-        @pendingStateTimeoutHandle? or
-        exactMatch? or
-        characterForKeyboardEvent(@queuedKeyboardEvents[0])?
-      )
-      enableTimeout = false if replay
-      @enterPendingState(partialMatches, enableTimeout)
-    else if not exactMatch? and not hasPartialMatches and @pendingPartialMatches?
-      @terminatePendingState()
-    else
-      @clearQueuedKeystrokes()
-
-  # This function does not set any state. State is handled in the
-  # `handleKeyboardEvent` function.
-  _handleKeyboardEvent: (event, queuedKeystrokes=[], disabledBindings) ->
-    keystroke = @keystrokeForKeyboardEvent(event)
-
-    if event.type isnt 'keyup' and queuedKeystrokes.length > 0 and isAtomModifier(keystroke)
-      event.preventDefault()
-      return
+    keystrokes = @queuedKeystrokes.join(' ')
+    shouldUsePartialMatches = true

     # If the event's target is document.body, assign it to defaultTarget instead
     # to provide a catch-all element when nothing is focused.
     target = event.target
     target = @defaultTarget if event.target is document.body and @defaultTarget?

-    queuedKeystrokes = queuedKeystrokes.concat([keystroke])
-    keystrokes = queuedKeystrokes.join(' ')
-
     # First screen for any bindings that match the current keystrokes,
     # regardless of their current selector. Matching strings is cheaper than
     # matching selectors.
-    {partialMatchCandidates, keydownExactMatchCandidates, exactMatchCandidates} = @findMatchCandidates(queuedKeystrokes, disabledBindings)
-    exactMatch = null
-    dispatchedBinding = null
+    {partialMatchCandidates, keydownExactMatchCandidates, exactMatchCandidates} = @findMatchCandidates(@queuedKeystrokes, disabledBindings)
+    dispatchedExactMatch = null
     partialMatches = @findPartialMatches(partialMatchCandidates, target)
+    hasPartialMatches = partialMatches.length > 0

     # Determine if the current keystrokes match any bindings *exactly*. If we
     # do find and exact match, the next step depends on whether we have any
@@ -471,17 +448,19 @@
         exactMatches = @findExactMatches(exactMatchCandidates, currentTarget)
         for exactMatchCandidate in exactMatches
           if exactMatchCandidate.command is 'native!'
-            return {keystroke, queuedKeystrokes: null}
+            shouldUsePartialMatches = false
+            eventHandled = true # Kicks it out of the parent loop
+            break

           if exactMatchCandidate.command is 'abort!'
             event.preventDefault()
-            return {keystroke, queuedKeystrokes: null}
+            eventHandled = true
+            break

           if exactMatchCandidate.command is 'unset!'
             break

-          exactMatch = exactMatchCandidate
-          if partialMatches.length > 0
+          if hasPartialMatches
             # When there is a set of bindings like `'ctrl-y', 'ctrl-y ^ctrl'`,
             # and a `ctrl-y` comes in, this will allow the `ctrl-y` command to be
             # dispatched without waiting for any other keystrokes
@@ -491,7 +470,7 @@
                 allPartialMatchesContainKeyupRemainder = false
             break if allPartialMatchesContainKeyupRemainder is false
           else
-            queuedKeystrokes = null
+            shouldUsePartialMatches = false

           if @dispatchCommandEvent(exactMatchCandidate.command, target, event)
             @emitter.emit 'did-match-binding', {
@@ -500,25 +479,14 @@
               binding: exactMatchCandidate,
               keyboardEventTarget: target
             }
+            dispatchedExactMatch = exactMatchCandidate
             eventHandled = true # Kicks it out of the parent loop
-            dispatchedBinding = exactMatchCandidate
             break
         currentTarget = currentTarget.parentElement

-    # If we're at this point in the method, we either found no matches for the
-    # currently queued keystrokes or we found a match, but we need to enter a
-    # pending state due to partial matches. We only enable the timeout of the
-    # pending state if we found an exact match on this or a previously queued
-    # keystroke.
-    if partialMatches.length > 0
-      event.preventDefault()
-      @emitter.emit 'did-partially-match-binding', {
-        keystrokes,
-        eventType: event.type,
-        partiallyMatchedBindings: partialMatches,
-        keyboardEventTarget: target
-      }
-    else if exactMatch is null
+    @bindingsToDisable.push(dispatchedExactMatch) if dispatchedExactMatch
+
+    if not dispatchedExactMatch? and not hasPartialMatches
       @emitter.emit 'did-fail-to-match-binding', {
         keystrokes,
         eventType: event.type,
@@ -530,10 +498,32 @@
       # simulate the text input event that was previously prevented to insert
       # the missing characters.
       @simulateTextInput(event) if event.defaultPrevented and event.type is 'keydown'
-      queuedKeystrokes = null
-
-    {keystroke, dispatchedBinding, exactMatch, partialMatches, queuedKeystrokes}
-
+
+    if hasPartialMatches and shouldUsePartialMatches
+      event.preventDefault()
+
+      enableTimeout = (
+        @pendingStateTimeoutHandle? or
+        dispatchedExactMatch? or
+        characterForKeyboardEvent(@queuedKeyboardEvents[0])?
+      )
+      enableTimeout = false if replay
+      @enterPendingState(partialMatches, enableTimeout)
+
+      @emitter.emit 'did-partially-match-binding', {
+        keystrokes,
+        eventType: event.type,
+        partiallyMatchedBindings: partialMatches,
+        keyboardEventTarget: target
+      }
+    else if not dispatchedExactMatch? and not hasPartialMatches and @pendingPartialMatches?
+      # There are partial matches from a previous event, but none from this
+      # event. This means the current event has removed any hope that the queued
+      # key events will ever match anything. So we will clear the state and
+      # start over after replaying the events in `terminatePendingState`.
+      @terminatePendingState()
+    else
+      @clearQueuedKeystrokes()

   # Public: Translate a keydown event to a keystroke string.
   #


Doc private methods

Doc private methods

Just for reference and in case we want to make them public later.

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -148,9 +148,23 @@
   onDidFailToMatchBinding: (callback) ->
     @emitter.on 'did-fail-to-match-binding', callback

+  # Invoke the given callback when a keymap file is reloaded.
+  #
+  # * `callback` {Function} to be called when a keymap file is reloaded.
+  #   * `event` {Object} with the following keys:
+  #     * `path` {String} representing the path of the reloaded keymap file.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidReloadKeymap: (callback) ->
     @emitter.on 'did-reload-keymap', callback

+  # Invoke the given callback when a keymap file is unloaded.
+  #
+  # * `callback` {Function} to be called when a keymap file is unloaded.
+  #   * `event` {Object} with the following keys:
+  #     * `path` {String} representing the path of the unloaded keymap file.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidUnloadKeymap: (callback) ->
     @emitter.on 'did-unload-keymap', callback


Always clear inspection indices

Always clear inspection indices

Previously it could happen that mouse moved out of frame quickly
enough to not give hover tool a chance to clear indices in the
main inspection routine.

--- bokehjs/src/coffee/models/tools/inspectors/hover_tool.coffee
+++ bokehjs/src/coffee/models/tools/inspectors/hover_tool.coffee
@@ -29,6 +29,12 @@

     @plot_view.canvas_view.$el.css('cursor', 'crosshair')

+  _clear: () ->
+    @_inspect(Infinity, Infinity)
+
+    for rid, tt of @mget('ttmodels')
+      tt.clear()
+
   _move: (e) ->
     if not @mget('active')
       return
@@ -36,14 +42,11 @@
     vx = canvas.sx_to_vx(e.bokeh.sx)
     vy = canvas.sy_to_vy(e.bokeh.sy)
     if not @plot_view.frame.contains(vx, vy)
-      for rid, tt of @mget('ttmodels')
-        tt.clear()
-      return
-    @_inspect(vx, vy)
-
-  _move_exit: ()->
-    for rid, tt of @mget('ttmodels')
-      tt.clear()
+      @_clear()
+    else
+      @_inspect(vx, vy)
+
+  _move_exit: () -> @_clear()

   _inspect: (vx, vy, e) ->
     geometry = {


need more permissive prop for tooltips

need more permissive prop for tooltips

--- bokehjs/src/coffee/models/tools/inspectors/hover_tool.coffee
+++ bokehjs/src/coffee/models/tools/inspectors/hover_tool.coffee
@@ -231,12 +231,12 @@

   props: () ->
     return _.extend({}, super(), {
-      tooltips: [ p.Array,
+      tooltips: [ p.Any,
         [
           ["index",         "$index"]
           ["data (x, y)",   "($x, $y)"]
           ["canvas (x, y)", "($sx, $sy)"]
-        ] ]
+        ] ] # TODO (bev)
       renderers:    [ p.Array,  []             ]
       names:        [ p.Array,  []             ]
       mode:         [ p.String, 'mouse'        ] # TODO (bev)


refactored default resolutions to calculate off of min_zoom and max_zoom


ActivityTicker: disable add comment

ActivityTicker: disable add comment

--- client/Social/Activity/ContentDisplays/ActivityTicker.coffee
+++ client/Social/Activity/ContentDisplays/ActivityTicker.coffee
@@ -38,7 +38,9 @@
     group.on "LikeIsAdded", @bound "addLike"
     group.on "FollowHappened", @bound "addFollow"
     group.on "PostIsCreated", @bound "addActivity"
-    group.on "ReplyIsAdded", @bound "addComment"
+    # disable for now, since we dont have comment view
+    # and comments doesnt have slug
+    # group.on "ReplyIsAdded", @bound "addComment"
     group.on "PostIsDeleted", @bound "deleteActivity"

     @listController.listView.on 'ItemWasAdded', (view, index) =>


Removing unnecessary webterm options - https://app.asana.com/0/3716548652471/7508318293081

--- client/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -15,7 +15,7 @@

     @terminal = new WebTerm.Terminal @container.$()
     KD.track "userOpenedTerminal", KD.getSingleton("groupsController").getCurrentGroup()
-    @options.advancedSettings ?= yes
+    @options.advancedSettings ?= no
     if @options.advancedSettings
       @advancedSettings = new KDButtonViewWithMenu
         style         : 'editor-advanced-settings-menu'


Remove log

Remove log

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -22,7 +22,6 @@
   viewAppended:->

     @appStorage.fetchStorage (storage)=>
-      log storage
       require ['ace/ace'], (ace)=>
         @editor = ace.edit "editor#{@getId()}"
         @prepareEditor()


move turkcell up as they are the largest prize

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -424,6 +424,12 @@

Additional Prizes

+

+

+
+
Turkcell is contributing $5,000 to the total prize money!

+

+


show the annotation callout at the span center rather than at the mouse

--- common/lib/xmodule/xmodule/js/src/annotatable/display.coffee
+++ common/lib/xmodule/xmodule/js/src/annotatable/display.coffee
@@ -1,7 +1,7 @@
 class @Annotatable
     _debug: false

-    wrapperSelector:            '.annotatable-wrapper'
+    # selectors for the annotatable xmodule
     toggleAnnotationsSelector:  '.annotatable-toggle-annotations'
     toggleInstructionsSelector: '.annotatable-toggle-instructions'
     instructionsSelector:       '.annotatable-instructions'
@@ -9,6 +9,7 @@
     spanSelector:               '.annotatable-span'
     replySelector:              '.annotatable-reply'

+    # these selectors are for responding to events from the annotation capa problem type
     problemXModuleSelector:     '.xmodule_CapaModule'
     problemSelector:            'section.problem'
     problemInputSelector:       'section.problem .annotation-input'
@@ -17,6 +18,7 @@
     constructor: (el) ->
         console.log 'loaded Annotatable' if @_debug
         @el = el
+        @$el = $(el)
         @init()

     $: (selector) ->
@@ -27,31 +29,30 @@
         @initTips()

     initEvents: () ->
-        # For handling hide/show of annotations and instructions
-        @annotationsHidden = false
+        # Initialize toggle handlers for the instructions and annotations sections
+        [@annotationsHidden, @instructionsHidden] = [false, false]
         @$(@toggleAnnotationsSelector).bind 'click', @onClickToggleAnnotations
-
-        @instructionsHidden = false
         @$(@toggleInstructionsSelector).bind 'click', @onClickToggleInstructions

-        # For handling 'reply to annotation' events that scroll to the associated capa problem.
-        # These are contained in the tooltips, which should be rendered somewhere in the wrapper
-        # (see the qtip2 options, this must be set explicitly, otherwise they render in the body).
-        @$(@wrapperSelector).delegate @replySelector, 'click', @onClickReply
+        # Initialize handler for 'reply to annotation' events that scroll to
+        # the associated problem. The reply buttons are part of the tooltip
+        # content. It's important that the tooltips be configured to render
+        # as descendants of the annotation module and *not* the document.body.
+        @$el.delegate @replySelector, 'click', @onClickReply

-        # For handling 'return to annotation' events from capa problems. Assumes that:
+        # Initialize handler for 'return to annotation' events triggered from problems.
         #   1) There are annotationinput capa problems rendered on the page
-        #   2) Each one has an embedded "return to annotation" link (from the capa problem template).
-        # The capa problem's html is injected via AJAX so this just sets a listener on the body and
-        # handles the click event there.
+        #   2) Each one has an embedded return link (see annotation capa problem template).
+        # Since the capa problem injects HTML content via AJAX, the best we can do is
+        # is let the click events bubble up to the body and handle them there.
         $('body').delegate @problemReturnSelector, 'click', @onClickReturn

     initTips: () ->
-        @savedTips = []
+        # tooltips are used to display annotations for highlighted text spans
         @$(@spanSelector).each (index, el) =>
-            $(el).qtip(@getTipOptions el)
+            $(el).qtip(@getSpanTipOptions el)

-    getTipOptions: (el) ->
+    getSpanTipOptions: (el) ->
         content:
             title:
                 text: @makeTipTitle(el)
@@ -59,29 +60,21 @@
         position:
             my: 'bottom center' # of tooltip
             at: 'top center' # of target
-            target: 'mouse'
-            container: @$(@wrapperSelector)
+            target: $(el) # where the tooltip was triggered (i.e. the annotation span)
+            container: @$el
             adjust:
-                mouse: false # dont follow the mouse
-                y: -10
+                y: -5
         show:
             event: 'click mouseenter'
             solo: true
         hide:
             event: 'click mouseleave'
-            delay: 250,
-            fixed: true
+            delay: 500,
+            fixed: true # don't hide the tooltip if it is moused over
         style:
             classes: 'ui-tooltip-annotatable'
         events:
             show: @onShowTip
-            visible: @onVisibleTip
-
-    onShowTip: (event, api) =>
-        event.preventDefault() if @annotationsHidden
-
-    onVisibleTip: (event, api) =>
-        @constrainTipHorizontally(api.elements.tooltip, event.originalEvent.pageX)

     onClickToggleAnnotations: (e) => @toggleAnnotations()

@@ -90,6 +83,9 @@
     onClickReply: (e) => @replyTo(e.currentTarget)

     onClickReturn: (e) => @returnFrom(e.currentTarget)
+
+    onShowTip: (event, api) =>
+        event.preventDefault() if @annotationsHidden

     getSpanForProblemReturn: (el) ->
         problem_id = $(@problemReturnSelector).index(el)
@@ -109,7 +105,8 @@
         @toggleTips hide

     toggleTips: (hide) ->
-        if hide then @closeAndSaveTips() else @openSavedTips()
+        visible = @findVisibleTips()
+        @hideTips visible

     toggleAnnotationButtonText: (hide) ->
         buttonText = (if hide then 'Show' else 'Hide')+' Annotations'
@@ -126,7 +123,8 @@
         @$(@toggleInstructionsSelector).text(txt).removeClass(cls[0]).addClass(cls[1])

     toggleInstructionsText: (hide) ->
-        @$(@instructionsSelector)[if hide then 'slideUp' else 'slideDown']()
+        slideMethod = (if hide then 'slideUp' else 'slideDown')
+        @$(@instructionsSelector)[slideMethod]()

     toggleSpans: (hide) ->
         @$(@spanSelector).toggleClass 'hide', hide, 250
@@ -180,44 +178,17 @@
     createReplyLink: (problem_id) ->
         $("Reply to Annotation")

-    openSavedTips: () ->
-        @showTips @savedTips
-
-    closeAndSaveTips: () ->
-        @savedTips = @findVisibleTips()
-        @hideTips @savedTips
-
     findVisibleTips: () ->
         visible = []
         @$(@spanSelector).each (index, el) ->
             api = $(el).qtip('api')
             tip = $(api?.elements.tooltip)
             if tip.is(':visible')
-                visible.push [el, tip.offset()]
+                visible.push el
         visible

-    hideTips: (pairs) ->
-        elements = (pair[0] for pair in pairs)
+    hideTips: (elements) ->
         $(elements).qtip('hide')
-
-    showTips: (pairs) ->
-        $.each pairs, (index, pair) ->
-            [el, offset] = pair
-            $(el).qtip('show')
-            api = $(el).qtip('api')
-            $(api?.elements.tooltip).offset(offset)
-
-    constrainTipHorizontally: (tip, mouseX) ->
-        win_width = $(window).width()
-        tip_center = $(tip).width() / 2 # see position setting of tip
-        tip_offset = $(tip).offset()
-
-        if (tip_center + mouseX) > win_width
-          adjust_left = '-=' + (tip_center + mouseX - win_width)
-        else if (mouseX - tip_center) < 0
-          adjust_left = '+=' + (tip_center - mouseX)
-
-        $(tip).animate({ left: adjust_left }) if adjust_left?

     _once: (fn) ->
         done = false


Update tshirts text

Update tshirts text

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -853,6 +853,7 @@
         на бесплатных футболках. Ориентировочная стоимость — 2000 рублей. Если вы хотите такую футболку, напишите мне, можете также приложить
         набросок дизайна. (Если я выпущу футболку с вашим дизайном, я оставляю за собой право в дальнейшем выпускать футболки с таким же дизайном
         для любых других желающих.)

+
Через некоторое время тут будут примеры «стандартных» дизайнов для платных футболок, можно будет выбрать любой.

     """, {id: "tshirts"})

 module20927_17 = () ->


Server: /Login: pass tfcode to JUser.login

--- servers/lib/server/handlers/login.coffee
+++ servers/lib/server/handlers/login.coffee
@@ -5,15 +5,16 @@
 module.exports = (req, res) ->

   { JUser } = koding.models
-  { username, password, redirect, groupName, token } = req.body
+  { username, password, tfcode, redirect, groupName, token } = req.body

   clientId =  getClientId req, res

   return handleClientIdNotFound res, req unless clientId

-  options = { username, password, groupName, invitationToken: token }
+  options = { username, password, tfcode, groupName, invitationToken: token }

   JUser.login clientId, options, (err, info) ->
+
     if err
       return res.status(403).send err.message
     else if not info


save mixer state.

save mixer state.

--- src/models/preset.coffee
+++ src/models/preset.coffee
@@ -13,7 +13,11 @@
       noise: 'n'
       attack: 'a'
       decay: 'd'
-
+      mixer0: 'm0'
+      mixer1: 'm1'
+      mixer2: 'm2'
+      oneshotstate: 'o'
+
     loadFromLocalStorage: ->
       c = {}
       for k, mk of @minAttrs


Updates unit test

Updates unit test

--- test/util.spec.coffee
+++ test/util.spec.coffee
@@ -805,7 +805,6 @@
         resp.write('FOOBAR')
         resp.end()
       sendRequest (err, data) ->
-        console.log('cb was called')
         expect(data).to.be.undefined
         expect(err).to.not.be.null
         expect(err.code).to.equal('SomeError')


Updated webserver test

Updated webserver test

--- test/webserver.coffee
+++ test/webserver.coffee
@@ -173,16 +173,14 @@
         qs:
           info: true
         , (err, res, data) =>
-          clients = data.clients
-
-          client_list = Object.keys(clients)
+          client_list = Object.keys(data.clients)
           client_list.length.should.equal 2
           client_list.should.include 'bob'
           client_list.should.include 'joe'

           for k in ['bob', 'joe']
-            clients[k].mappings.length.should.equal 1
-            ainfo = clients[k].mappings[0]
+            data.clients[k].mappings.length.should.equal 1
+            ainfo = data.clients[k].mappings[0]
             ainfo.mbean.should.equal \
             'java.lang:name=ConcurrentMarkSweep,type=GarbageCollector'
             ainfo.attributes.length.should.equal 1


rename ck var in test_optimized

rename ck var in test_optimized

--- test_optimized.coffee
+++ test_optimized.coffee
@@ -66,7 +66,7 @@
     template: 'h1 "harcoded: " + obj.foo'
     run: ->
       obj = {foo: 'bar'}
-      @compiled = ck.compile(@template, hardcode: {obj})
+      @compiled = cc.compile(@template, hardcode: {obj})
       @expected = '
harcoded: bar
'
       @result = @compiled()
       @success = @result is @expected
@@ -193,7 +193,7 @@
       if test.run
         test.run()
       else
-        test.result = ck.render(test.template, test.params)
+        test.result = cc.render(test.template, test.params)
         test.success = test.result is test.expected

       if test.success


Improve scaling of waterfall charts

Improve scaling of waterfall charts

Waterfall charts would continue to use default scaling if data from
ETEngine wasn't ready.

--- app/assets/javascripts/d3/waterfall.coffee
+++ app/assets/javascripts/d3/waterfall.coffee
@@ -138,6 +138,9 @@

       @y.domain([min_value, max_value])

+      # Refresh the unit scaling.
+      @y_axis.tickFormat(@main_formatter())
+
       # animate the y-axis
       @svg.selectAll(".y_axis").transition().call(@y_axis.scale(@y))


More descriptive variable name in DateChooser

--- app/assets/javascripts/date_chooser.coffee
+++ app/assets/javascripts/date_chooser.coffee
@@ -3,7 +3,7 @@
 class DateChooser

   constructor: (chooser) ->
-    @values = chooser.find(".date-chooser-values")
+    @dateInput = chooser.find(".date-chooser-values").find(".date")

     selectors = chooser.find(".date-chooser-select") # Today/Tomorrow selection
     today = selectors.find(".today")
@@ -37,10 +37,11 @@
   toggleDate: (selector, otherSelector) =>
     selector.html(selector.text())
     @setDate selector
+    @dateInput.focus()
     @enableLink otherSelector

   setDate: (selector) =>
     date = selector.data("date-display")
-    @values.find(".date").val(date)
+    @dateInput.val(date)

 root.DateChooser = DateChooser


bump inDuration for steps to 10 so that step_shown isn't triggered too early

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -86,6 +86,7 @@
       beforeSerialize: (form, options) =>
         if options.data._ht_step_link == this.currentStepId()
           log "Warning: Tried to link to the current step; this is probably not what you want."
+        return true;
       # beforeSubmit: (data) =>
       #   log "Sending these data to the server: #{JSON.stringify data}"
       success: (serverData) =>
@@ -164,7 +165,8 @@
       validationEnabled: false,
       focusFirstInput: true,
       disableUIStyles: true,
-      inDuration: 0,
+      outDuration: 200,
+      inDuration: 10, # don't set this to 0 or step_shown will be triggered too early
       next: "button:submit",
       back: "button:reset",
       linkClass: ".#{@linkClass}",


Scripts resolving the first set of test cases

--- app/coffee/module/object_comparator.coffee
+++ app/coffee/module/object_comparator.coffee
@@ -1,4 +1,9 @@
 jUtil = jUtil ? this

 jUtil.compare = (param1, param2) ->
-	JSON.stringify(param1) == JSON.stringify(param2)+	temp = {}
+	for key,value of param1
+		temp[key] = param2[key]
+	JSON.stringify(param1) == JSON.stringify(temp)
+
+


Fix promise return value

Fix promise return value

--- lib/debugger-model.coffee
+++ lib/debugger-model.coffee
@@ -55,14 +55,15 @@
     @breakpoints = []

     @api.connect(wsUrl)
-
+
+    deferred = q.defer()
     @api.once 'connect', =>
       @isActive = true
-
-      debuggerEnabledPromise = q.all [
+      q.all [
         q.ninvoke @api.debugger, 'enable', null
         q.ninvoke @api.page, 'getResourceTree', null
       ]
+      .then deferred.resolve

     @api.once 'close', => @close()

@@ -94,7 +95,7 @@

     @api.on 'scriptParsed', (scriptObject)=>@addScript(scriptObject)

-    debuggerEnabledPromise
+    deferred.promise

   close: ->


Bind broadcast socket to same address/interface as web server

--- lib/devices/Device.coffee
+++ lib/devices/Device.coffee
@@ -51,7 +51,7 @@
         @uuid = "uuid:#{res.uuid}"
         @address = res.address
         @httpPort = res.port
-        @broadcastSocket.bind @ssdp.port,'0.0.0.0', =>
+        @broadcastSocket.bind @ssdp.port,@address, =>
           @broadcastSocket.addMembership @ssdp.address
           @broadcastSocket.setMulticastTTL @ssdp.ttl
           @addServices()


Remove console.log

Remove console.log

--- lib/languages/html.coffee
+++ lib/languages/html.coffee
@@ -29,7 +29,6 @@
       if />/.test matchText
         isBreak = true
         return
-      console.log "before: #{matchText}"
       rangeStart = start
       stop()
     return if isBreak
@@ -37,7 +36,6 @@
       if /</.test matchText
         isBreak = true
         return
-      console.log "after: #{matchText}"
       rangeEnd = end
       stop()
     return if isBreak
@@ -49,12 +47,10 @@
     ].join ''
     matched = node.match /<img\s[\s\S]*?src\s*=\s*["'](.*?)["']/
     return unless (src = matched?[1])? and src isnt ''
-    console.log "src: #{src}"

     new Node range, node, src

   replace: ({ range, text }, { width, height }) ->
-    console.log [width, height]
     if width?
       if /width/.test text
         text = text.replace /width(?:=".*?")?/, "width=\"#{width}\""


updating hidden theme updater

updating hidden theme updater

--- app/coffee/metamenu.coffee
+++ app/coffee/metamenu.coffee
@@ -75,6 +75,18 @@

 # Appearance Model Subclass
 class AppearancePanel extends MetaMenu
+  constructor: (@name) ->
+    super(@name)
+    @setupThemeSelector()
+
+  setupThemeSelector: ->
+    @$currentMenuPanel.find('.buttons .sun').click( (event) =>
+      $('#view_preference_theme').val('light')
+    )
+    @$currentMenuPanel.find('.buttons .moon').click( (event) =>
+      $('#view_preference_theme').val('dark')
+    )
+
   displayBedsheets: ->
     # code to display bedsheets here. Need JSON call
     # $.publish('follow/changing', [node])
@@ -82,6 +94,7 @@

       @$currentMenuPanel.find('.bedsheets ul').html('');

+      # Add elements for each bedsheet returned
       for node in data
         newElement = '

'
         @$currentMenuPanel.find('.bedsheets ul').append(newElement)
@@ -91,7 +104,7 @@
         bedsheetUrl = 'url("/images/uploads/originals/' + $(this).data('id') + '.jpg")'
         $('#body').css('background-image', bedsheetUrl)

-        $('#bedsheetInput').val($(this).data('id'))
+        $('#view_preference_image_id').val($(this).data('id'))
     )


tweak formatter config a bit

tweak formatter config a bit

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -122,19 +122,20 @@
         title: 'Formatting Options'
         description:
           '''
-          This config is deprecated. In order to specify [Formatting Options](https://domluna.github.io/JuliaFormatter.jl/dev/#Formatting-Options-1),
+          ⚠ This config is deprecated. In order to specify
+          [Formatting Options](https://domluna.github.io/JuliaFormatter.jl/dev/#Formatting-Options-1),
           use `.JuliaFormatter.toml` configuration file instead.
-          See the ["Configuration File" section in JuliaFormatter.jl's documentation](https://domluna.github.io/JuliaFormatter.jl/dev/config/)
-          for more details.
+          See the ["Configuration File" section](https://domluna.github.io/JuliaFormatter.jl/stable/config/)
+          in JuliaFormatter.jl's documentation for more details.
           '''
         type: 'object'
         order: 14
         collapsed: true
         properties:
-          mock:
-            title: 'mock (not appear in the settings view)'
-            type: 'object'
-            default: {}
+          mock: # NOTE: otherwise the deprecated description doesn't show up
+            title: "mock (doesn't have any effect)"
+            type: 'boolean'
+            default: false

   uiOptions:
     title: 'UI Options'


Add Ctrl-S to whitelisted REPL keybindings

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -505,7 +505,7 @@
       whitelistedKeybindingsREPL:
         title: 'Whitelisted Keybindings for the Julia REPL'
         type: 'array'
-        default: ['Ctrl-C', 'F5', 'F8', 'F9', 'F10', 'F11', 'Shift-F5', 'Shift-F8', 'Shift-F9', 'Shift-F10', 'Shift-F11']
+        default: ['Ctrl-C', 'Ctrl-S', 'F5', 'F8', 'F9', 'F10', 'F11', 'Shift-F5', 'Shift-F8', 'Shift-F9', 'Shift-F10', 'Shift-F11']
         description: 'The listed keybindings are not handled by the REPL and instead directly passed to Atom.'
         order: 5
       whitelistedKeybindingsTerminal:


update

update

--- lib/path-to-module.coffee
+++ lib/path-to-module.coffee
@@ -21,12 +21,12 @@

   getRelativePath: ->
     uri = atom.workspace.activePaneItem.getUri()
-    rootDirectory = atom.project.rootDirectory
+    rootDirectory = atom.project.rootDirectories[0]
     directoryPath = rootDirectory.getPath()
     return uri.substr(directoryPath.length + 1)

   getAbsolutePath: (file, prefix, extention) ->
-    path = atom.project.rootDirectory.getPath();
+    path = atom.project.rootDirectories[0].getPath();
     return path + "/" + prefix + file + extention

   module2file: ->


fix pandoc options.continue issue.

fix pandoc options.continue issue.

--- lib/process-graphs.coffee
+++ lib/process-graphs.coffee
@@ -209,21 +209,27 @@
           cmd = options.cmd if options.cmd
           id = options.id

-          codeChunksArr.push {id, code: content}
+          codeChunksArr.push {id, code: content, options}

           # check continue
-          if options.continue
+          offset = codeChunksArr.length - 1
+          currentCodeChunk = codeChunksArr[offset]
+          while currentCodeChunk?.options.continue
             last = null
-            if options.continue == true
-              last = codeChunksArr[codeChunksArr.length - 2]
+            if currentCodeChunk.options.continue == true
+              last = codeChunksArr[offset - 1]
             else
               for c in codeChunksArr
-                if c.id == options.continue
+                if c.id == currentCodeChunk.options.continue
                   last = c
                   break

             if last
               content = last.code + '\n' + content
+              options.matplotlib = last.options.matplotlib or last.options.mpl
+
+            offset--
+            currentCodeChunk = codeChunksArr[offset]

           codeChunkAPI.run content, rootDirectoryPath, cmd, options, (error, data, options)->
             outputType = options.output || 'text'


Include animation.

Include animation.

--- app/javascripts/app.coffee
+++ app/javascripts/app.coffee
@@ -3,7 +3,7 @@
   # the vendor modules used directly in the application
   # HTML, and the top-level application Javascript. Service
   # dependencies are declareed by the application modules.
-  ['angular', 'ngnvd3', 'uibootstrap', 'controllers', 'directives',
+  ['angular', 'nganimate', 'ngnvd3', 'uibootstrap', 'controllers', 'directives',
    'filters', 'routes'],
   (ng) ->
     ng.module 'qiprofile', ['nvd3ChartDirectives', 'ui.bootstrap',


Added login-related statistics accumulation.

--- app/templates/index.coffee
+++ app/templates/index.coffee
@@ -34,7 +34,9 @@

 app.get "/app", (req, res) ->
 	if req.isAuthenticated()
-		res.render "app"
+		User.touch req.user._id, req.ip, (error) ->
+			Log.error "Can't touch user #{req.user._id}." if error
+			res.render "app"
 	else
 		res.redirect "/"


Fixed #96.

Fixed #96.

--- app/views/home_view.coffee
+++ app/views/home_view.coffee
@@ -54,10 +54,10 @@
     @stage.update()

   onMouseOverButton: ->
-    @wizardSprite.queueAction 'cast'
+    @wizardSprite?.queueAction 'cast'

   onMouseOutButton: ->
-    @wizardSprite.queueAction 'idle'
+    @wizardSprite?.queueAction 'idle'

   updateStage: =>
     @stage.update()


Add specs of outline view UI

Add specs of outline view UI

--- spec/extensions/outline-view-spec.coffee
+++ spec/extensions/outline-view-spec.coffee
@@ -1,6 +1,61 @@
+RootView = require 'root-view'
+OutlineView = require 'outline-view'
 TagGenerator = require 'outline-view/tag-generator'

 describe "OutlineView", ->
+  [rootView, outlineView] = []
+
+  beforeEach ->
+    rootView = new RootView(require.resolve('fixtures'))
+    rootView.activateExtension(OutlineView)
+    outlineView = OutlineView.instance
+    rootView.attachToDom()
+
+  afterEach ->
+    rootView.deactivate()
+
+  it "displays all JavaScript functions with line numbers", ->
+    tags = []
+    waitsForPromise ->
+      tags = []
+      path = require.resolve('fixtures/sample.js')
+      callback = (tag) ->
+        tags.push tag
+      generator = new TagGenerator(path, callback)
+      generator.generate()
+
+    runs ->
+      rootView.open('sample.js')
+      expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [0,0]
+      expect(rootView.find('.outline-view')).not.toExist()
+      outlineView.setArray(tags)
+      outlineView.attach()
+      expect(rootView.find('.outline-view')).toExist()
+      expect(outlineView.list.children('li').length).toBe 2
+      expect(outlineView.list.children('li:first').find('.function-name')).toHaveText 'quicksort'
+      expect(outlineView.list.children('li:first').find('.function-line')).toHaveText 'Line 1'
+      expect(outlineView.list.children('li:last').find('.function-name')).toHaveText 'quicksort.sort'
+      expect(outlineView.list.children('li:last').find('.function-line')).toHaveText 'Line 2'
+
+  it "moves the cursor to the selected function", ->
+    tags = []
+    waitsForPromise ->
+      tags = []
+      path = require.resolve('fixtures/sample.js')
+      callback = (tag) ->
+        tags.push tag
+      generator = new TagGenerator(path, callback)
+      generator.generate()
+
+    runs ->
+      rootView.open('sample.js')
+      expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [0,0]
+      expect(rootView.find('.outline-view')).not.toExist()
+      outlineView.setArray(tags)
+      outlineView.attach()
+      expect(rootView.find('.outline-view')).toExist()
+      outlineView.confirmed(tags[1])
+      expect(rootView.getActiveEditor().getCursorBufferPosition()).toEqual [1,0]

   describe "TagGenerator", ->
     it "generates tags for all JavaScript functions", ->


use spy obj for checking triggered events

--- spec/javascripts/fragmentsSpec.js.coffee
+++ spec/javascripts/fragmentsSpec.js.coffee
@@ -16,7 +16,8 @@

   describe "on ajax:complete on [data-behavior=fragments]", ->
     beforeEach ->
-      @topLevelFragmentTriggerSpy = spyOn($.fn, "trigger").and.callThrough()
+      @fragmentUpdateTriggerSpy = jasmine.createSpy("trigger")
+      $(document).off("fragment:update").on("fragment:update", @fragmentUpdateTriggerSpy)
       $("#fragments-behavior-source").trigger("ajax:complete", @xhr)

     it "updates page fragments with top-level fragments found in response", ->
@@ -26,8 +27,5 @@
       expect($.trim($("#nested-fragment").text())).toEqual("For tonight, we dine in hell!")

     it "triggers fragment:update event on updated elements", ->
-      expect(@topLevelFragmentTriggerSpy).toHaveBeenCalledWith("fragment:update", [
-        jasmine.any(Object),
-        jasmine.any(Object),
-        @xhr
-      ])
+      expect(@fragmentUpdateTriggerSpy).toHaveBeenCalled()
+      expect(@fragmentUpdateTriggerSpy.calls.count()).toEqual(2)