Different queues for different tasks

Different queues for different tasks

--- lib/ghc-modi-process.coffee
+++ lib/ghc-modi-process.coffee
@@ -7,8 +7,16 @@
 module.exports =
 class GhcModiProcess
   processMap: null
-  commandQueue: []
-  commandRunning: false
+  commandQueues:
+    checklint:
+      running: false
+      queue: []
+    completion:
+      running: false
+      queue: []
+    typeinfo:
+      running: false
+      queue: []

   constructor: ->
     @processMap = new WeakMap
@@ -58,24 +66,28 @@
   onBackendIdle: (callback) =>
     @emitter.on 'backend-idle', callback

-  queueCmd: (runFunc, rootDir, command, callback) =>
-    @commandQueue.push({f:runFunc,rd:rootDir,cmd:command,cb:callback})
-    @runQueuedCommands()
-
-  runQueuedCommands: =>
-    if @commandQueue.length == 0
-      @emitter.emit 'backend-idle'
+  queueCmd: (qn, runFunc, rootDir, command, callback) =>
+    @commandQueues[qn].queue.push
+      f:runFunc
+      rd:rootDir
+      cmd:command
+      cb:callback
+    @runQueuedCommands qn
+
+  runQueuedCommands: (qn) =>
+    if @commandQueues[qn].queue.length == 0
+      @emitter.emit 'backend-idle', qn
       return
-    else if @commandRunning
+    else if @commandQueues[qn].running
       return

-    @commandRunning = true
-    {f,rd,cmd,cb}=@commandQueue.shift()
-    @emitter.emit 'backend-active', cmd.join(' ')
+    @commandQueues[qn].running = true
+    {f,rd,cmd,cb}=@commandQueues[qn].queue.shift()
+    @emitter.emit 'backend-active', {queue: qn, command: cmd.join(' ')}
     f rd, cmd, (lines) =>
       cb lines
-      @commandRunning=false
-      @runQueuedCommands()
+      @commandQueues[qn].running=false
+      @runQueuedCommands qn

   runCmd: (rootDir, command, callback) =>
     unless atom.config.get('haskell-ghc-mod.enableGhcModi')
@@ -114,16 +126,17 @@
             replaceAll(line,'\0','\n')

   runList: (rootDir, callback) =>
-    @queueCmd @runModCmd, rootDir, ['list'], callback
+    @queueCmd 'completion', @runModCmd, rootDir, ['list'], callback

   runLang: (callback) =>
-    @queueCmd @runModCmd, null, ['lang'], callback
+    @queueCmd 'completion', @runModCmd, null, ['lang'], callback

   runFlag: (callback) =>
-    @queueCmd @runModCmd, null, ['flag'], callback
+    @queueCmd 'completion', @runModCmd, null, ['flag'], callback

   runBrowse: (rootDir, modules,callback) =>
-    @queueCmd @runModCmd, rootDir, ['browse','-d'].concat(modules), callback
+    @queueCmd 'completion', @runModCmd,
+      rootDir, ['browse','-d'].concat(modules), callback

   withTempFile: (contents,callback) ->
     Temp.open
@@ -143,36 +156,39 @@
       cpos = crange.start
       command = ["type",path,"",cpos.row+1,cpos.column+1]

-      @queueCmd @runCmd, atom.project.getDirectories()[0], command, (lines) ->
-        close()
-        [range,type]=lines.reduce ((acc,line) ->
-          return acc if acc!=''
-          tokens=line.split '"'
-          pos=tokens[0].trim().split(' ').map (i)->i-1
-          type=tokens[1]
-          myrange = new Range [pos[0],pos[1]],[pos[2],pos[3]]
-          return acc unless myrange.containsRange(crange)
-          return [myrange,type]),
-          ''
-        type='???' unless type
-        range=crange unless range
-        callback range,type
+      @queueCmd 'typeinfo', @runCmd,
+        atom.project.getDirectories()[0], command, (lines) ->
+          close()
+          [range,type]=lines.reduce ((acc,line) ->
+            return acc if acc!=''
+            tokens=line.split '"'
+            pos=tokens[0].trim().split(' ').map (i)->i-1
+            type=tokens[1]
+            myrange = new Range [pos[0],pos[1]],[pos[2],pos[3]]
+            return acc unless myrange.containsRange(crange)
+            return [myrange,type]),
+            ''
+          type='???' unless type
+          range=crange unless range
+          callback range,type

   getInfo: (text,symbol,callback) =>
     @withTempFile text, (path,close) =>
       command = ["info",path,"",symbol]
-      @queueCmd @runCmd, atom.project.getDirectories()[0], command, (lines) ->
-        close()
-        callback lines.join('\n'), path
+      @queueCmd 'typeinfo', @runCmd,
+        atom.project.getDirectories()[0], command, (lines) ->
+          close()
+          callback lines.join('\n'), path

   doCheck: (text, callback) =>
     @withTempFile text, (path,close) =>
       command = ["check",path]
-      @queueCmd @runModCmd,atom.project.getDirectories()[0], command, (lines) ->
-        close()
-        lines.forEach (line) ->
-          [m,file,row,col] = line.match(/^(.*?):([0-9]+):([0-9]+):/)
-          callback new Point(row-1, col-1), line.replace(m,''), file, path
+      @queueCmd 'checklint', @runModCmd,
+        atom.project.getDirectories()[0], command, (lines) ->
+          close()
+          lines.forEach (line) ->
+            [m,file,row,col] = line.match(/^(.*?):([0-9]+):([0-9]+):/)
+            callback new Point(row-1, col-1), line.replace(m,''), file, path

   #buffer commands
   getTypeInBuffer: (buffer, crange, callback) =>
@@ -180,7 +196,7 @@
       cpos = crange.start
       command = ["type",path,"",cpos.row+1,cpos.column+1]

-      @queueCmd @runCmd, @getRootDir(buffer), command, (lines) ->
+      @queueCmd 'typeinfo', @runCmd, @getRootDir(buffer), command, (lines) ->
         close()
         [range,type]=lines.reduce ((acc,line) ->
           return acc if acc!=''
@@ -212,7 +228,7 @@
         crange2=crange
       symbol = buffer.getTextInRange(crange2)
       command = ["info",path,"",symbol]
-      @queueCmd @runCmd, @getRootDir(buffer), command, (lines) ->
+      @queueCmd 'typeinfo', @runCmd, @getRootDir(buffer), command, (lines) ->
         close()
         text = lines
           .map (line) ->
@@ -223,7 +239,7 @@
   doCheckOrLintBuffer: (cmd, buffer, callback) =>
     @withTempFile buffer.getText(), (path,close) =>
       command = [cmd,path]
-      @queueCmd @runModCmd, @getRootDir(buffer), command, (lines) ->
+      @queueCmd 'checklint',@runModCmd,@getRootDir(buffer),command,(lines) ->
         close()
         results = []
         lines.forEach (line) ->


Update XML attributes properly.

Update XML attributes properly.

--- create-form.coffee
+++ create-form.coffee
@@ -22,7 +22,7 @@
   elm.change (x) ->
                v = ($ x.target).val()
                if field.attr
-                 n.attr v
+                 n.attr (field_name field), v
                else
                  n.text v


Form semantics for login

Form semantics for login

--- Web/js/classes/login-manager.coffee
+++ Web/js/classes/login-manager.coffee
@@ -9,7 +9,8 @@

 			@password.val '' if info.Success

-		$('#loginButton').on 'click', =>
+		$('#login-form').on 'submit', (e) =>
+			e.preventDefault()
 			@login @username.val(), @password.val()

 		$('#registerButton').on 'click', =>


Run after-brunch commands sequentially

Run after-brunch commands sequentially

--- config.coffee
+++ config.coffee
@@ -29,9 +29,9 @@

   plugins:
     afterBrunch: [
-      'echo -n "Cleaning coffee files..." && find public/ -type f -name "*.coffee" -delete'
-      'echo -n "Building examples..." && coffee --compile --output public examples/'
-      'echo -n "Cleaning ui assets..." && rm -rf public/ui'
+      'echo "Cleaning coffee files..." && find public/ -type f -name "*.coffee" -delete' + '; ' +
+      'echo "Building examples..." && coffee --compile --output public examples/' + '; ' +
+      'echo "Cleaning ui assets..." && rm -rf public/ui' + '; ' +
       'echo "Done."'
     ]
     stylus:
@@ -45,8 +45,8 @@
     production:
       plugins:
         afterBrunch: [
-          'echo -n "Cleaning coffee files..." && find public/ -type f -name "*.coffee" -delete'
-          'echo -n "Building examples and digesting..." && coffee --compile --output public examples/ && ./bin/digest'
-          'echo -n "Cleaning ui assets..." && rm -rf public/ui'
+          'echo "Cleaning coffee files..." && find public/ -type f -name "*.coffee" -delete' + '; ' +
+          'echo "Building examples and digesting..." && coffee --compile --output public examples/ && ./bin/digest' + '; ' +
+          'echo "Cleaning ui assets..." && rm -rf public/ui' + '; ' +
           'echo "Done."'
         ]


fix test by submitting via a button click

--- test/javascripts/initializers_test.coffee
+++ test/javascripts/initializers_test.coffee
@@ -1,33 +1,36 @@
 describe 'Initializers', ->

   # can't figure out why this doesn't work:
-  # describe 'tg-remote on forms', ->
-  #   beforeEach ->
-  #     @Remote = stub(TurboGraft, "Remote").returns({submit: ->})
-
-  #   afterEach ->
-  #     @Remote.restore()
-
-  #   it.only 'creates a remote based on the options passed in', ->
-  #     $form = $("<form>")
-  #       .attr("tg-remote", "true")
-  #       .attr("method", "put")
-  #       .attr("refresh-on-success", "foo")
-  #       .attr("refresh-on-error", "bar")
-  #       .attr("full-refresh-on-error-except", "zar")
-  #       .attr("action", "somewhere")
-
-  #     $("body").append($form)
-  #     $form.trigger("submit")
-
-  #     assert @Remote.called
-  #     assert @Remote.calledWith
-  #       httpRequestType: "put"
-  #       httpUrl: "somewhere"
-  #       fullRefresh: false
-  #       refreshOnSuccess: "foo"
-  #       refreshOnError: "bar"
-  #       refreshOnErrorExcept: "zar"
+  describe 'tg-remote on forms', ->
+    beforeEach ->
+      @Remote = stub(TurboGraft, "Remote").returns({submit: ->})
+
+    afterEach ->
+      @Remote.restore()
+
+    it 'creates a remote based on the options passed in', ->
+      $form = $("<form>")
+        .attr("tg-remote", "true")
+        .attr("method", "put")
+        .attr("refresh-on-success", "foo")
+        .attr("refresh-on-error", "bar")
+        .attr("full-refresh-on-error-except", "zar")
+        .attr("action", "somewhere")
+      $form.append("<input type='submit'>")
+
+      $("body").append($form)
+      $form.find("input").trigger("click")
+
+      assert @Remote.called
+      assert @Remote.calledWith
+        httpRequestType: "put"
+        httpUrl: "somewhere"
+        fullRefresh: false
+        refreshOnSuccess: "foo"
+        refreshOnError: "bar"
+        refreshOnErrorExcept: "zar"
+
+      $form.remove()

   describe 'tg-remote on links', ->
     beforeEach ->


Introduce means to unmute/wake Oscar

Introduce means to unmute/wake Oscar

Fixes #11

--- kills.coffee
+++ kills.coffee
@@ -3,6 +3,7 @@
 # OscarRobo losses - Ask about recent losses by your corp
 # OscarRobo kills - Ask about recent kills by your corp
 # OscarRobo sleep - Mute OscarRobo's snark for 15 minutes
+# OscarRobo wake - Unmute OscarRobo's snark
 # I hate <target> - Oscar will insult <target>
 # ...(and in random conversation) - Oscar will otherwise snark in channel, spam with image macros, and othwerwise be a twit until muted

@@ -99,6 +100,10 @@
     robot.brain.set 'sleepUntil', wake
     msg.send "Do you want me to sit in a corner and rust or just fall apart where I'm standing?"

+  robot.respond /wake|rise and shine|unmute/i, (msg) ->
+    robot.brain.set 'sleepUntil', moment()
+    msg.send "This will all end in tears."
+
   robot.error (err, msg) ->
     robot.logger.error "BARF! #{err}"


:shirt: unnecessary fat arrow

:shirt: unnecessary fat arrow

--- lib/spell-check-task.coffee
+++ lib/spell-check-task.coffee
@@ -61,7 +61,7 @@

   @startNextJob: ->
     activeEditorId = atom.workspace.getActiveTextEditor()?.id
-    job = @jobs.find((j) => j.editorId is activeEditorId) or @jobs[0]
+    job = @jobs.find((j) -> j.editorId is activeEditorId) or @jobs[0]

     job.manager.check(job.args, job.args.text).then (results) =>
       @removeFromArray(@jobs, (j) -> j.args.id is job.args.id)


Added function to load system pepper flash player

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -52,6 +52,19 @@
     when 'linux' then ''  # TODO
     when 'win32' then ''  # TODO

+findSystemFlashPath = ->
+  # Download/install from: https://get.adobe.com/flashplayer/otherversions/
+  switch platform
+    when 'darwin'
+      try
+        systemFlashPath = '/Library/Internet Plug-Ins/PepperFlashPlayer/PepperFlashPlayer.plugin'
+        fs.accessSync systemFlashPath
+        systemFlashPath
+      catch
+        ''
+    when 'linux' then ''  # TODO
+    when 'win32' then ''  # TODO
+
 getFlashVersion = (path) ->
   return '' if not validatePath path
   switch platform
@@ -69,13 +82,14 @@
   # --flash-path=/Applications/Google\ Chrome.app/Contents/Versions/48.0.2564.109/Google\ Chrome\ Framework.framework/Internet\ Plug-Ins/PepperFlash/PepperFlashPlayer.plugin

 getPath = ->
-  chromeFlashPath = findChromeFlashPath()
-  return chromeFlashPath if validatePath chromeFlashPath
-  builtInPath
+  flashPath = findChromeFlashPath()
+  flashPath = findSystemFlashPath() if not flashPath
+  flashPath = builtInPath if not flashPath
+  flashPath

 load = ->
   flashPath = getPath()
-  dbg.extra('flashLoader').assert validatePath(flashPath), ''
+  dbg.extra('flashLoader').assert validatePath(flashPath), 'Path to Pepper Flash Player is invalid.'
   dbg.ex.flashLoader.log "Loading flash player from:"
   dbg.ex.flashLoader.log flashPath
   app.commandLine.appendSwitch 'ppapi-flash-path', flashPath


Missing ifs

Missing ifs

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -119,13 +119,13 @@
       return flashPath
     when 'builtin'
       flashPath = getBuiltInFlashPath()
-      error 'Could not load built-in Pepper Flash Player'
+      error 'Could not load built-in Pepper Flash Player' if not flashPath?
     when 'chrome'
       flashPath = findChromeFlashPath()
-      error 'Could not load Chrome Pepper Flash Player'
+      error 'Could not load Chrome Pepper Flash Player' if not flashPath?
     when 'system'
       flashPath = findSystemFlashPath()
-      error 'Could not load system Pepper Flash Player plug-in'
+      error 'Could not load system Pepper Flash Player plug-in' if not flashPath?
     when 'cli'
       if validatePath CLIFlashPath
         flashPath = CLIFlashPath


Label

Label

--- both/collections/users.coffee
+++ both/collections/users.coffee
@@ -5,6 +5,7 @@
   picture:
     type: String
     optional:true
+    label: 'Profile picture'

   firstName:
     type: String


Fix refactor bug

Fix refactor bug

--- src/context.coffee
+++ src/context.coffee
@@ -312,7 +312,7 @@
       $domainService: =>
         (@getDomainService arguments[0]).apply @, [arguments[1], arguments[2]]
       $projectionStore: (projectionName, callback) =>
-        @projectionStore projectionName, callback
+        @getProjectionStore projectionName, callback

     @_initializeProjections()
     .then =>
@@ -344,7 +344,7 @@
       async.eachSeries @_projectionClasses, (projection, next) =>
         @clearProjectionStore projection.name
         .then =>
-          @projectionStore projection.name
+          @getProjectionStore projection.name
         .then (projectionStore) =>
           @_initializeProjection projection, projectionStore, =>
             next()
@@ -425,10 +425,10 @@
         @_eventBus.subscribeToDomainEvent domainEventName, domainEventHandler

-  projectionStore: (projectionName, callback) =>
+  getProjectionStore: (projectionName, callback) =>
     new Promise (resolve, reject) =>

-      @_store.projectionStore (@_projectionStoreName projectionName), (err, projectionStore) =>
+      @_store.getProjectionStore (@_projectionStoreName projectionName), (err, projectionStore) =>
         callback? err, projectionStore
         return reject err if err
         resolve projectionStore


killed some strangely defensive code.

killed some strangely defensive code.

--- lib/gather-tasks.coffee
+++ lib/gather-tasks.coffee
@@ -1,10 +1,10 @@
 _ = require('grunt').util._

 module.exports = (phase) ->
-  config = require(process.cwd() + "/config/application")
+  config = require("#{process.cwd()}/config/application")
   _([].concat(
-    config?.prependTasks?[phase]
-    (if config?.enableSass && phase == "common" then ["sass"] else [])
-    config?.appTasks?[phase]
-    config?.appendTasks?[phase]
+    config.prependTasks?[phase]
+    (if config.enableSass && phase == "common" then ["sass"] else [])
+    config.appTasks?[phase]
+    config.appendTasks?[phase]
   )).compact()


Renders intervention form in dialogs instead of redirect.

--- app/assets/javascripts/backend/animals.js.coffee
+++ app/assets/javascripts/backend/animals.js.coffee
@@ -1,6 +1,6 @@
 #= require bootstrap/modal

-((G, $) ->
+((E, G, $) ->

   $(document).ajaxSend (e, xhr, options) ->
     token = $('meta[name=\'csrf-token\']').attr('content')
@@ -88,7 +88,16 @@
         for id, item of @selectedItemsIndex
           @moveAnimalsModal.animals.push item

-        Turbolinks.visit @animal_moving_url
+        E.dialog.open @animal_moving_url,
+          returns:
+            success: (frame, data, status, request) ->
+              frame.dialog "close"
+              return
+
+            invalid: (frame, data, status, request) ->
+              frame.html request.responseText
+              return
+

 #        @moveAnimalsModal.show(true)

@@ -233,8 +242,15 @@
         animals_ids: Object.keys(app.selectedItemsIndex)
       })}"

-    #TODO call modal to directly add a new intervention instead of redirect
-    Turbolinks.visit link
+    E.dialog.open link,
+      returns:
+        success: (frame, data, status, request) ->
+          frame.dialog "close"
+          return
+
+        invalid: (frame, data, status, request) ->
+          frame.html request.responseText
+          return
     false

@@ -250,4 +266,4 @@
       window.app = new golumn(golumn_id)
       window.loadData(golumn_id, $(this))

-) golumn, jQuery
+) ekylibre, golumn, jQuery


Remove delay.

Remove delay.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -49,14 +49,12 @@
     data = context.getImageData(1, 1, 1, 1)
     "rgba(#{data.data[0]}, #{data.data[1]}, #{data.data[2]}, #{data.data[3]})"

-  setNativeTitleColor: (rgb, timeout = 1) ->
+  setNativeTitleColor: (rgb) ->
     ctx = document.createElement('canvas').getContext('2d')
     ctx.strokeStyle = rgb
     hex = ctx.strokeStyle
     feedbin.themeColorHex = hex
-    setTimeout ( ->
-      feedbin.nativeMessage("performAction", { action: "titleColor", color: hex })
-    ), timeout
+    feedbin.nativeMessage("performAction", { action: "titleColor", color: hex })

   nativeMessage: (name, data) ->
     if typeof(webkit) != "undefined" && webkit.messageHandlers && webkit.messageHandlers.turbolinksDemo


Don't hide subscribe form when clicking on subscribe form.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -644,7 +644,7 @@
         return

       $(document).on 'click', (event) ->
-        unless $(event.target).is('[data-behavior~=show_subscribe]')
+        unless $(event.target).is('[data-behavior~=show_subscribe]') || $(event.target).is('.subscribe-wrap') || $(event.target).parents('.subscribe-wrap').length > 0
           $('.feeds').removeClass('show-subscribe')

       subscription = feedbin.queryString('subscribe')


fix(grunt): update location of rpm icon name

--- build/tasks/mkrpm-task.coffee
+++ build/tasks/mkrpm-task.coffee
@@ -32,7 +32,7 @@

     installDir = grunt.config.get('nylasGruntConfig.installDir')
     shareDir = path.join(installDir, 'share', 'nylas')
-    iconName = path.join(shareDir, 'resources', 'app', 'resources', 'nylas.png')
+    iconName = 'nylas'

     data = {name, version, description, installDir, iconName}
     specFilePath = fillTemplate(path.join('build', 'resources', 'linux', 'redhat', 'nylas.spec'), data)


Use two lines

Use two lines

--- src/item-serializer.coffee
+++ src/item-serializer.coffee
@@ -149,7 +149,8 @@
   # Returns {Array} of {Items}.
   @deserializeItems: (serializedItems, outline, options={}) ->
     options['type'] ?= ItemSerializer.BMLType
-    (each for each in @getSerializationsForType(options['type']) when each.deserializeItems)[0].deserializeItems(serializedItems, outline, options)
+    serialization = (each for each in @getSerializationsForType(options['type']) when each.deserializeItems)[0]
+    serialization.deserializeItems(serializedItems, outline, options)

 ItemSerializer.registerSerialization
   priority: 0


OX-347 corrected tracingType and syntax

OX-347 corrected tracingType and syntax

--- app/assets/javascripts/oxalis/model/route.coffee
+++ app/assets/javascripts/oxalis/model/route.coffee
@@ -99,8 +99,8 @@
       else
         @createNewTree()

-    tracingType = $('#container').data("tracing-type")
-    if (tracingType == "Task" or tracingType == "Training") and !nodeList.length
+    tracingType = data.tracingType
+    if (tracingType == "Task" or tracingType == "Training") and nodeList.length == 0
       @addNode(data.editPosition)

     #@createNewTree()


Override "handleCloseIcon" and emit an event.

--- client/ide/lib/views/tabview/ideapplicationtabview.coffee
+++ client/ide/lib/views/tabview/ideapplicationtabview.coffee
@@ -124,3 +124,10 @@
     @splitRegions?.destroy()
     @splitRegions = null

+
+  handleCloseIcon: (pane) ->
+
+    @emit 'PaneRemovedByUserAction', pane
+
+    super pane
+


Fix reloading bug (the 3rd time..)

Fix reloading bug (the 3rd time..)

--- app/assets/javascripts/deals.js.coffee
+++ app/assets/javascripts/deals.js.coffee
@@ -61,8 +61,16 @@
         $('#deal_forms').empty()
         $('#deal_forms').append(result.error_view)
       else
-        result_url = $('#deal_form').data("result-url").replace(/_YEAR_/, result.year).replace(/_MONTH_/, result.month) + "#" + result.id
-        location.assign(result_url)
+        resultUrl = $('#deal_form').data("result-url").replace(/_YEAR_/, result.year).replace(/_MONTH_/, result.month)
+        resultUrlWithHash = resultUrl + "#" + result.id
+        prevUrl = location.pathname
+        prevSearch = location.search
+        if prevSearch && prevSearch != ""
+          prevUrl += "?" + prevSearch
+        location.assign(resultUrlWithHash)
+        # NOTE: assign しても location.pathname などが古いケースがあるため、resultUrlベースで用意して比較している
+        if resultUrl == prevUrl
+          location.reload()
     , 'JSON')
     return false # 通常の Form 動作は行わない
   )


cleaned up

cleaned up

--- app/assets/javascripts/pt/tags/confirm.js.coffee
+++ app/assets/javascripts/pt/tags/confirm.js.coffee
@@ -1,52 +1,66 @@
 window.pt ||= tags: []
+
+confirm = ($el, type = 'confirm') ->
+    attr = $el.ptAttr()
+    url  = attr.confirmDelete
+
+    switch type
+      when 'delete'
+        buttonName = attr.confirmButton or 'Delete'
+        ajaxType = 'DELETE'
+      else
+        buttonName = attr.confirmButton or 'Yes'
+        ajaxType = 'PUT'
+
+    if attr.confirmContainer
+      $confirmContainer = $el.closest(attr.confirmContainer)
+
+    unless $el.data 'ptConfirmLoaded'
+      $el.data 'ptConfirmLoaded', true
+
+      $html = $ '<div>',
+        style: 'text-align: center;'
+
+      $deleteButton = $ '<button>',
+        class: 'btn btn-danger confirm'
+        style: 'margin-right: 10px;'
+        text: buttonName
+      $html.append $deleteButton
+
+      $cancelButton = $ '<button>',
+        class: 'btn cancel'
+        text: 'Cancel'
+      $html.append $cancelButton
+
+      $confirmContainer.on 'click', '.popover-content .confirm', ->
+        $.ajax
+          url: url
+          type: ajaxType
+          success: ->
+            $confirmContainer.popover 'hide'
+
+      $confirmContainer.on 'click', '.popover-content .cancel', ->
+        $confirmContainer.popover 'hide'
+
+      $confirmContainer.popover
+        content: $html
+        html: true
+        trigger: 'manual'
+        title: attr.confirmTitle or 'Are you sure?'
+        placement: attr.confirmPlacement or 'right'
+        container: $confirmContainer
+
+    $confirmContainer.popover 'show'
+

 pt.tags.push
   confirmDelete:
     event: 'click'
     callback: ->
-      $el  = $confirmContainer  = $ this
-      attr = $el.ptAttr()
-      url  = attr.confirmDelete
-
-      if attr.confirmContainer
-        $confirmContainer = $el.closest(attr.confirmContainer)
-
-      unless $el.data 'ptConfirmLoaded'
-        $el.data 'ptConfirmLoaded', true
-
-        $html = $ '<div>',
-          style: 'text-align: center;'
-
-        $deleteButton = $ '<button>',
-          class: 'btn btn-danger delete'
-          style: 'margin-right: 10px;'
-          text: 'Delete'
-        $html.append $deleteButton
-
-        $cancelButton = $ '<button>',
-          class: 'btn cancel'
-          text: 'Cancel'
-        $html.append $cancelButton
-
-        $confirmContainer.on 'click', '.popover-content .delete', ->
-          $.ajax
-            url: url
-            type: 'DELETE'
-            success: ->
-              $confirmContainer.popover 'hide'
-
-        $confirmContainer.on 'click', '.popover-content .cancel', ->
-          $confirmContainer.popover 'hide'
-
-        $confirmContainer.popover
-          content: $html
-          html: true
-          trigger: 'manual'
-          title: attr.confirmTitle or 'Are you sure you want to delete?'
-          placement: attr.confirmPlacement or 'right'
-          container: $confirmContainer
-
-      $confirmContainer.popover 'show'
+      confirm $(this), 'delete'
+  confirm:
+    event: 'click'
+    callback: -> confirm $(this)
 #
 #       buttons = [
 #         {


Better HTML quoting

Better HTML quoting

--- app/assets/javascripts/sugar/rich_text.js.coffee
+++ app/assets/javascripts/sugar/rich_text.js.coffee
@@ -42,13 +42,11 @@
       ["
#{codeTag}", escapeEntities(str), "
"]

     quote: (text, html, username, permalink) ->
-      stripParagraphs = (str) ->
-        ($.trim(line).replace(/^
/, '').replace(/<\/p>$/, '') for line in str.split(/\n[\n]+/)).join("\n\n")
       cite = if permalink
         "Posted by #{username}:"
       else
         "Posted by #{username}:"
-      content = stripParagraphs(html.replace(/
/g, "\n"))
+      content = html.replace(/\n/g, "").replace(/
/g, "\n")
       quotedPost = "
#{cite}#{content}
"
       ["", quotedPost + "\n\n", ""]


fold late in the game with a bad hand

fold late in the game with a bad hand

--- src/player.coffee
+++ src/player.coffee
@@ -49,6 +49,9 @@
       unless value < 0.1
         return sendBet minBet

+    if communityCards.length > 3 and value < 0.1
+      return sendBet 0
+
     if value > 0
       bet = (gameState.small_blind * 2) * 2
       bet = minBet unless bet > minBet


Killing old code

Killing old code

--- app/assets/javascripts/ideas.js.coffee
+++ app/assets/javascripts/ideas.js.coffee
@@ -3,20 +3,6 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/

 $ ->
-  $(".auto-replace").focus ->
-    val = $(this).val()
-    if val is $(this).attr('data-default')
-      $(this).val('')
-    else
-      console.log "Not replacing because value is " + val
-
-  $(".auto-replace").blur ->
-    val = $(this).val()
-    if val is ""
-      $(this).val($(this).attr('data-default'))
-    else
-      console.log "Not replacing because value is " + val
-
   counter = ->
     value = $("#idea-body").val()
     if value.length is 0


Correcting for edge case in line wrapping

If you look at the flow of the `eachWord` function, you'll notice that we add the word to the buffer if the width of the word `w` is less than the space available.

There's an edge case where `linebreaker` says that a line break is required on a word (because of a manual break: `theword\n`) and the width of the word being considered is greater than `spaceLeft`. In the current pdfkit, if this is the case:

 * we do not append the word to the buffer
 * we emit the line
 * we break and move on to the next line, starting with a clean line

This omits the word entirely.

This commit makes it so that we only break the line and start with a clean line if we already had the space to write the line to the buffer. Otherwise, we append the word to the buffer and go to a new line.

--- lib/line_wrapper.coffee
+++ lib/line_wrapper.coffee
@@ -165,7 +165,7 @@
                         return no

                 # reset the space left and buffer
-                if bk.required
+                if bk.required and w <= @spaceLeft
                     @spaceLeft = @lineWidth
                     buffer = ''
                     textWidth = 0


Fix typo

Fix typo

--- lib/line_wrapper.coffee
+++ lib/line_wrapper.coffee
@@ -145,7 +145,7 @@
                     textWidth = @wordWidth buffer + @ellipsis

                     # remove characters from the buffer until the ellipsis fits
-                    while w > @lineWidth
+                    while textWidth > @lineWidth
                         buffer = buffer.slice(0, -1).trimRight()
                         textWidth = @wordWidth buffer + @ellipsis


:new: Implement renderPanel in LinterViews

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -2,6 +2,7 @@
 BottomTabProject = require './views/bottom-tab-project'
 BottomStatus = require './views/bottom-status'
 Panel = require './views/panel'
+Message = require './views/message'
 Bubble = require './views/bubble'

 class LinterViews
@@ -16,7 +17,6 @@

     @_bottomTabFile.initialize(@linter)
     @_bottomTabProject.initialize(@linter)
-    @_panel.initialize(@linter)
     @_bottomStatus.initialize()
     @_panelWorkspace = atom.workspace.addBottomPanel item: @_panel, visible: false

@@ -85,7 +85,23 @@
     @_removeDecorations()
     @bubble?.remove()
     if not @messages.length
-      return @linter.views.setPanelVisibility(false)
+      return @setPanelVisibility(false)
+    @setPanelVisibility(true)
+    @messages.forEach (message)=>
+      if @scope is 'file' then return unless message.currentFile
+      if message.currentFile and message.position #Add the decorations to the current TextEditor
+        p = message.position
+        range = [[p[0][0] - 1, p[0][1] - 1], [p[1][0] - 1, p[1][1]]]
+        marker = @linter.activeEditor.markBufferRange range, {invalidate: 'never'}
+        @_decorations.push @linter.activeEditor.decorateMarker(
+          marker, type: 'line-number', class: "line-number-#{message.type.toLowerCase()}"
+        )
+        @_decorations.push @linter.activeEditor.decorateMarker(
+          marker, type: 'highlight', class: "highlight-#{message.type.toLowerCase()}"
+        )
+      Element = Message.fromMessage(message, @scope is 'project')
+      @_panel.appendChild Element
+    @bubble?.update(@linter.activeEditor.getCursorBufferPosition())

   _removeDecorations: ->


:art: Add first and last classes for first and last tabs

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -91,10 +91,17 @@
     @bottomStatus.count = counts.project

   updateTabs: ->
+    first = null
+    last = null
     for key, tab of @tabs # for...of (key, value)
       tab.classList.remove('first')
       tab.classList.remove('last')
       tab.visibility = atom.config.get("linter.showErrorTab#{key}")
+      if tab.visibility
+        if first then last = tab
+        else first = tab
+    first.classList.add('first') if first
+    last.classList.add('last') if last

   # consumed in editor-linter, _renderPanel
   updateBubble: (point) ->


Increasing max depth and max number of children.

--- lib/proto-repl-sayid.coffee
+++ lib/proto-repl-sayid.coffee
@@ -14,11 +14,11 @@
     maxDepth:
       description: "The maximum depth of the callstack to try to display. Setting this too high will take a long time when tracing very deep callstacks."
       type: "number"
-      default: 50
+      default: 100
     maxChildren:
       description: "The maximum number of children of a single node to try to display. Setting this too high will take a long time when tracing functions that make many calls."
       type: "number"
-      default: 30
+      default: 100

   subscriptions: null


Replace all dashes in help topic name.

Replace all dashes in help topic name.

--- community/browser/app/scripts/directives/helpTopic.coffee
+++ community/browser/app/scripts/directives/helpTopic.coffee
@@ -33,7 +33,7 @@
         element.on 'click', (e) ->
           e.preventDefault()

-          topic = topic.toLowerCase().trim().replace('-', ' ')
+          topic = topic.toLowerCase().trim().replace(/-/g, ' ')
           Frame.create(input: "#{Settings.cmdchar}#{command} #{topic}")

           $rootScope.$apply() unless $rootScope.$$phase


Agent training now takes time and waits for the agent to finish their task

--- assets/agent.coffee
+++ assets/agent.coffee
@@ -26,18 +26,22 @@
   tick: (state) ->
     console.log("Working " + @working + ", request: " + @request)
     if @working > 0
-      @working = @working - 1
+      @working -= 1
       if @working == 0
         @completed++
         @request.resolved()
         @request = null
+    else if @training > 0
+      @training -= 1
+      if @training == 0
+        @skills[@trainingSkill] += 1
+        @trainingSkill = null
     else if @queue and @queue.length()
        @handleRequest(@queue.pop())

   assign: (queue) ->
     @queue = queue
-

   remove: ->
     @queue = null
@@ -48,7 +52,8 @@
     @working = Math.floor(request.complexity / @skills[request.type])

   train: (skill)->
-    @skills[skill] = 1
+    @trainingSkill = skill
+    @training = 30

   toString: ->
-    "Name: " + @name + ", request: " + @request + ", completed: " + @completed
+    "Name: " + @name + ", request: " + @request + ", completed: " + @completed + ", training: " + @trainingSkill


fix undo/redo-flashing for linewise mutation

--- lib/misc-command.coffee
+++ lib/misc-command.coffee
@@ -13,6 +13,7 @@
   findRangeContainsPoint
   mergeIntersectingRanges
   isSingleLineRange
+  isLinewiseRange
   isLeadingWhiteSpaceRange
 } = require './utils'

@@ -117,7 +118,7 @@
   humanizeNewLineForRange: (range) ->
     {start, end} = range

-    if isSingleLineRange(range)
+    if isSingleLineRange(range) or isLinewiseRange(range)
       return range

     if pointIsAtEndOfLine(@editor, start)


Remove space before exclamation mark in award alert

[ci skip]

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -113,7 +113,7 @@
   renderNote: (note) ->
     unless note.valid
       if note.award
-        flash = new Flash('You have already used this award emoji !', 'alert')
+        flash = new Flash('You have already used this award emoji!', 'alert')
         flash.pin()
       return


Show progress while fetching link

Show progress while fetching link

--- app/assets/javascripts/posts.js.coffee
+++ app/assets/javascripts/posts.js.coffee
@@ -66,8 +66,15 @@
   e.preventDefault()
   url = prompt('URL')
   if url
+    NProgress.start()
     params =
       url: url
-    $.post '/links', params, (json) ->
-      insert("[#{json.title}](#{json.url})")
-
+    $.ajax
+      url: '/links'
+      method: 'POST'
+      data: params
+      success: (json) ->
+        insert("[#{json.title}](#{json.url})")
+      complete: ->
+        NProgress.done()
+


#239 :fire: leftover

#239 :fire: leftover

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -115,7 +115,6 @@
   activateVisualMode: (submode) ->
     if @submode?
       @selectCharacterwise()
-      # swrap.resetProperties(@editor)
     else if @editor.getLastSelection().isEmpty()
       @editor.selectRight()

@@ -154,8 +153,6 @@
           bs.restoreCharacterwise()
         @vimState.clearBlockwiseSelections()

-    # swrap.resetProperties(@editor)
-
   normalizeSelections: ({preservePreviousSelection}={}) ->
     @selectCharacterwise()
     swrap.resetProperties(@editor)


don't call reRender explicitely

don't call reRender explicitely

--- core/app/backbone/views/wheel/base_fact_wheel_view.coffee
+++ core/app/backbone/views/wheel/base_fact_wheel_view.coffee
@@ -27,6 +27,7 @@
   initialize: (options) ->
     @options = $.extend(true, {}, BaseFactWheelView.prototype.defaults, @defaults, options)
     @opinionTypeRaphaels = {}
+    @bindTo @model, 'change', @reRender, @

   defaultStrokeWidth: -> 3/5 * @options.radius
   hoverStrokeWidth: -> @defaultStrokeWidth() + 2
@@ -187,4 +188,4 @@
       opinionType.percentage = newOpinionType.percentage
       opinionType.is_user_opinion = newOpinionType.is_user_opinion

-    @reRender()
+    @model.trigger 'change'


Oops - little bugfix

Oops - little bugfix

--- assets/front.coffee
+++ assets/front.coffee
@@ -281,7 +281,7 @@
 					# Temporarily tagging the artist so we know it came from send_next_track
 					document.getElementById('artist').innerHTML = "[snt] " + data.segment.next_track.artist
 					document.getElementById('artist_next').innerHTML = "Up next: (unknown)"
-					length = segment.tracks[0].metadata.length
+					length = data.segment.tracks[0].metadata.length
 					minutes = Math.floor(length/60)
 					seconds = Math.floor(length%60)
 					if seconds < 10


Support T

Support T

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -942,6 +942,12 @@
         else
           m0

+  T: (args)->
+    @checkArgumentSize(args, 1)
+    x = @getValue(args[0])
+    unless Utils.isString(x)
+      x = ""
+    String(x)

   SUM: (args)->


tableverse/avatar: Fade in labels when mouse enters an avatar.

--- Oghma/tableverse/avatar.coffee
+++ Oghma/tableverse/avatar.coffee
@@ -20,15 +20,48 @@
 Oghma.Thingy.Tableverse.register( ( thingyverse, O ) ->
   thingyverse.avatar = new Heron.Index.MapIndex( 'owner', 'name' )

+  show_labels = ->
+    thingyverse.avatar.each( ( avatar ) ->
+      avatar.show_label()
+    )
+  hide_labels = ->
+    thingyverse.avatar.each( ( avatar ) ->
+      avatar.hide_label()
+    )
+
   class AvatarDelegate extends Oghma.KineticThingyDelegate
+    constructor: ( args... ) ->
+      super args...
+
+      @thingy().show_label = =>
+        if @is_visible() && @__k?
+          tween = new Kinetic.Tween(
+            node:     @__k.label,
+            duration: 0.25,
+            opacity:  1
+          )
+          tween.play()
+        this
+      @thingy().hide_label = =>
+        if @is_visible() && @__k?
+          tween = new Kinetic.Tween(
+            node:     @__k.label,
+            duration: 0.25,
+            opacity:  0
+          )
+          tween.play()
+        this
+
     draw: ->
       if ! @__k?
         @__k =
+          disk: new Kinetic.Group({})
           a: new Kinetic.Wedge({})
           b: new Kinetic.Wedge({})
           label: new Kinetic.Text({})
-        @group().add( @__k.a )
-        @group().add( @__k.b )
+        @__k.disk.add( @__k.a )
+        @__k.disk.add( @__k.b )
+        @group().add( @__k.disk )
         @group().add( @__k.label )

       attrs = @thingy().get()
@@ -53,12 +86,18 @@
         fill:     O.ui_colors.value().primary
         stroke:   O.ui_colors.value().primary
         fontSize: 16
+        opacity:  0
       )

       # Center text
       @__k.label.setOffset(
         x: @__k.label.getWidth()  / 2
       )
+
+      # Label Bindings
+      @__k.disk.on( 'mouseenter', show_labels )
+      @__k.disk.on( 'mouseleave', hide_labels )
+

     remove: ( thingy ) ->
       thingyverse.avatar.remove( thingy )


Using multiline CS string

Using multiline CS string

--- app/scripts/components/GrowlNotifications.coffee
+++ app/scripts/components/GrowlNotifications.coffee
@@ -21,18 +21,18 @@
     ###
     itemViewClass: Ember.View.extend(
         classNames: ['notification']
-        template: Ember.Handlebars.compile [
-            ''
-            ''
-                ''
-            ''
-            ''
-                '{{view.content.title}}'
-            ''
-            '
'
-                '{{view.content.sub}}'
-            '
'
-        ].join("\n")
+        template: Ember.Handlebars.compile '''
+
+
+
+
+
+                {{view.content.title}}
+
+

+                {{view.content.sub}}
+

+        '''
         classNameBindings: [
             ":notification"
             "content.closed"


Only use joinTo value for sorting if it's an array

Otherwise it interferes with default vendor ordering when joinTo is
something like `'app.js': /^(vendor|bower_components|app)/`

--- src/fs_utils/generate.coffee
+++ src/fs_utils/generate.coffee
@@ -57,7 +57,7 @@
   indexes = Object.create(null)
   files.forEach (file, index) -> indexes[file.path] = file
   order = extractOrder files, config
-  order.joinToValue = joinToValue
+  order.joinToValue = joinToValue if Array.isArray joinToValue
   sortByConfig(paths, order).map (path) ->
     indexes[path]


base collection

base collection

--- collections/base.coffee
+++ collections/base.coffee
@@ -4,8 +4,29 @@

 Collection = require("chaplin").Collection
 sd = require("sharify").data
+_ = require 'underscore'
 Model = require("../models/base.coffee")
+ModelLib = require '../lib/model_lib.coffee'

 module.exports = class Blocks extends Collection

-  model:Model+  model:Model
+
+  _.extend @prototype, ModelLib
+
+  initialize: (models, options={})->
+    @setOptions(options)
+    super
+
+  next: (model) ->
+    @at((@indexOf(model) + 1) % _.size(@models))
+
+  prev: (model) ->
+    index = @indexOf(model) - 1
+    @at(if index > -1 then index else _.size(@models) - 1)
+
+  last: ->
+    @at @length - 1
+
+  isEmpty: ->
+    @models.length is 0


Handle missing user

Handle missing user

--- collections/post.coffee
+++ collections/post.coffee
@@ -39,17 +39,18 @@
   author: ->
     user = User.first(@userId)

-    if user.profile and user.profile.firstName and user.profile.lastName
-      return "#{user.profile.firstName} #{user.profile.lastName}"
+    if user
+      if user.profile and user.profile.firstName and user.profile.lastName
+        return "#{user.profile.firstName} #{user.profile.lastName}"

-    else if user.profile and user.profile.twitter
-      return "#{user.profile.twitter}"
+      else if user.profile and user.profile.twitter
+        return "#{user.profile.twitter}"

-    else if user.username
-      return user.username
+      else if user.username
+        return user.username

-    else if user.emails and user.emails[0]
-      return user.emails[0].address
+      else if user.emails and user.emails[0]
+        return user.emails[0].address

     'Mystery blogger'


Get reserves from the API into MinerlCtrl scope.

--- app/static/script/controller/mineral-ctrl.coffee
+++ app/static/script/controller/mineral-ctrl.coffee
@@ -26,6 +26,10 @@
     $http.get('/api/v1/estimate')
       .success (response) ->
         $scope.functions = response
+
+    $http.get('/api/v1/reserves')
+      .success (response) ->
+        $scope.reserves = response

     productionSeries = (series) ->
       production = R.match /(World.+production|(P|p)roduction|Total)/


Use node's require instead of internal require

--- lib/spell-check-view.coffee
+++ lib/spell-check-view.coffee
@@ -1,5 +1,5 @@
 {View} = require 'space-pen'
-_ = nodeRequire 'underscore'
+_ = require 'underscore'
 SpellCheckTask = require './spell-check-task'
 MisspellingView = require './misspelling-view'


[Backport] Fix move stories from backlog to current sprint button

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -652,7 +652,12 @@
             # Update the total of points
             sprint.total_points += totalExtraPoints

-            $rs.userstories.bulkUpdateMilestone($scope.project.id, $scope.sprints[0].id, selectedUss).then =>
+            data = _.map selectedUss, (us) ->
+                return {
+                    us_id: us.id
+                    order: us.sprint_order
+                }
+            $rs.userstories.bulkUpdateMilestone($scope.project.id, $scope.sprints[0].id, data).then =>
                 $ctrl.loadSprints()
                 $ctrl.loadProjectStats()
                 $ctrl.toggleVelocityForecasting()


Fix broken unit test

Fix broken unit test

--- Src/coffeescript/cql-execution/test/elm/clinical/test.coffee
+++ Src/coffeescript/cql-execution/test/elm/clinical/test.coffee
@@ -161,7 +161,7 @@

     # this is getting the possible number of months in years with the addtion of an offset
     # to get the correct number of months
-    @full_months = ((@today.getFullYear() - 1980) * 12) + (@today.getMonth() - 5)
+    @full_months = ((@today.getFullYear() - 1980) * 12) + (@today.getMonth() - 6)
     @timediff = @today - @bday # diff in milliseconds

   it 'should execute age in years', ->


Introduce "smart block selection". Make default

--- lib/python-tools.coffee
+++ lib/python-tools.coffee
@@ -8,6 +8,12 @@
   return false

 PythonTools =
+  config:
+    smart_block_selection:
+      type: 'boolean'
+      description: 'Do not select whitespace outside logical string blocks'
+      default: true
+
   subscriptions: null

   activate: (state) ->
@@ -133,9 +139,11 @@

       # Detect if we are at the boundaries of the block string
       delim_index = line.indexOf(delimiter)
+
       if delim_index != -1
         scopes = editor.scopeDescriptorForBufferPosition(new Point(start, delim_index))
         scopes = scopes.getScopesArray()
+
         # We are at the beginning of the block
         if regexPatternIn(/punctuation.definition.string.begin.*/, scopes)
           start_index = line.indexOf(delimiter)
@@ -143,6 +151,7 @@
             end = end + 1
             line = editor.lineTextForBufferRow(end)
             end_index = line.indexOf(delimiter)
+
         # We are the end of the block
         else if regexPatternIn(/punctuation.definition.string.end.*/, scopes)
           end_index = line.indexOf(delimiter)
@@ -150,6 +159,7 @@
             start = start - 1
             line = editor.lineTextForBufferRow(start)
             start_index = line.indexOf(delimiter)
+
       else
         # We are neither at the beginning or the end of the block
         while end_index == -1
@@ -161,10 +171,40 @@
           line = editor.lineTextForBufferRow(start)
           start_index = line.indexOf(delimiter)

-      editor.setSelectedBufferRange(new Range(
-        new Point(start, start_index + delimiter.length),
-        new Point(end, end_index),
-      ))
+      if atom.config.get('python-tools.smart_block_selection')
+        # Smart block selections
+        selections = []
+        start_range = new Range(
+          new Point(start, start_index + delimiter.length),
+          new Point(start, editor.lineTextForBufferRow(start).length),
+        )
+        if not start_range.isEmpty()
+          selections.push start_range
+
+        for i in [start + 1 ... end] by 1
+          line = editor.lineTextForBufferRow(i)
+          trimmed = line.replace(/^\s+/,"")  # left trim
+          selections.push new Range(
+            new Point(i, line.length - trimmed.length),
+            new Point(i, line.length),
+          )
+
+        line = editor.lineTextForBufferRow(end)
+        trimmed = line.replace(/^\s+/,"")  # left trim
+
+        end_range = Range(
+          new Point(end, line.length - trimmed.length),
+          new Point(end, end_index),
+        )
+        if not end_range.isEmpty()
+          selections.push new end_range
+
+        editor.setSelectedBufferRanges(selections)
+      else
+        editor.setSelectedBufferRange(new Range(
+          new Point(start, start_index + delimiter.length),
+          new Point(end, end_index),
+        ))

   handleJsonResponse: (response) ->
     console.log "tools.py => #{response}"


loop over mask to plot points, will add data masking later

--- bokehjs/src/coffee/renderer/glyph/rect.coffee
+++ bokehjs/src/coffee/renderer/glyph/rect.coffee
@@ -28,7 +28,9 @@

     _render: (ctx, glyph_props, use_selection) ->
       if glyph_props.fill_properties.do_fill
-        for i in [0..@sx.length-1]
+
+        for i in @mask
+
           if isNaN(@sx[i] + @sy[i] + @sw[i] + @sh[i] + @angle[i])
             continue
           if use_selection == true and not @selected_mask[i]
@@ -53,7 +55,7 @@

         ctx.beginPath()

-        for i in [0..@sx.length-1]
+        for i in @mask

           if isNaN(@sx[i] + @sy[i] + @sw[i] + @sh[i] + @angle[i])
             continue


Fix list input indexes

Fix list input indexes

--- client/app/views/components/form_field_list.tmpl.coffee
+++ client/app/views/components/form_field_list.tmpl.coffee
@@ -69,7 +69,7 @@
                         row
                         lock: data.lock
                     })
-                ) for col, index in columns
+                ) for col in columns
                 # TODO-2 move row td(
                 #     a(
                 #         {title: 'Reorder', href: '#', className: 'move-row'}
@@ -82,6 +82,6 @@
                         i({className: 'fa fa-times-circle'})
                     )
                 )
-            ) for row in values
+            ) for row, index in values
         )
     )


helper: environment add setMachineUser

helper: environment add setMachineUser

--- client/ide/lib/collaboration/helpers/environment.coffee
+++ client/ide/lib/collaboration/helpers/environment.coffee
@@ -30,7 +30,39 @@
   remote.api.JWorkspace.update workspaceData._id, { $set : options }

+###*
+ * Shares or unshares given machine with given users.
+ *
+ * @param {object} machine
+ * @param {array.<string>} usernames
+ * @param {boolean} share
+ * @param {function(err: object)}
+###
+setMachineUser = (machine, usernames, share, callback) ->
+
+  method   = if share then 'share' else 'unshare'
+  jMachine = machine.getData()
+  jMachine[method] usernames, (err) ->
+    return callback err  if err
+
+    kite   = machine.getBaseKite()
+    method = if share then 'klientShare' else 'klientUnshare'
+
+    queue = usernames.map (username) ->
+      ->
+        kite[method]({username}).then ->
+          callback null
+          queue.fin()
+
+        .error (err) ->
+          queue.fin()
+          callback err
+
+    sinkrow.dash queue, callback
+
+
 module.exports = {
   detachSocialChannel
   updateWorkspace
+  setMachineUser
 }


Skip all nodes in an iteration context unless building the views for the iterated items.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -114,38 +114,52 @@
   # Builds the Rivets.Binding instances for the view.
   build: =>
     @bindings = []
+    skipNodes = []
+    iterator = null
     bindingRegExp = @bindingRegExp()
     eventRegExp = /^on-/
     iterationRegExp = /^each-/

     parseNode = (node) =>
-      for attribute in node.attributes
-        if bindingRegExp.test attribute.name
-          options = {}
-
-          type = attribute.name.replace bindingRegExp, ''
-          pipes = (pipe.trim() for pipe in attribute.value.split '|')
-          context = (ctx.trim() for ctx in pipes.shift().split '>')
-          path = context.shift()
-          splitPath = path.split /\.|:/
-          options.formatters = pipes
-          model = @models[splitPath.shift()]
-          options.bypass = path.indexOf(":") != -1
-          keypath = splitPath.join()
-
-          if model
-            if dependencies = context.shift()
-              options.dependencies = dependencies.split /\s+/
-
-            if eventRegExp.test type
-              type = type.replace eventRegExp, ''
-              options.special = "event"
+      unless node in skipNodes
+        for attribute in node.attributes
+          if bindingRegExp.test attribute.name
+            type = attribute.name.replace bindingRegExp, ''

             if iterationRegExp.test type
-              type = type.replace iterationRegExp, ''
-              options.special = "iteration"
-
-            @bindings.push new Rivets.Binding node, type, model, keypath, options
+              unless @models[type.replace iterationRegExp, '']
+                skipNodes.push n for n in node.getElementsByTagName '*'
+                iterator = [attribute]
+
+        for attribute in iterator or node.attributes
+          iterator = null
+
+          if bindingRegExp.test attribute.name
+            options = {}
+
+            type = attribute.name.replace bindingRegExp, ''
+            pipes = (pipe.trim() for pipe in attribute.value.split '|')
+            context = (ctx.trim() for ctx in pipes.shift().split '>')
+            path = context.shift()
+            splitPath = path.split /\.|:/
+            options.formatters = pipes
+            model = @models[splitPath.shift()]
+            options.bypass = path.indexOf(":") != -1
+            keypath = splitPath.join()
+
+            if model
+              if dependencies = context.shift()
+                options.dependencies = dependencies.split /\s+/
+
+              if eventRegExp.test type
+                type = type.replace eventRegExp, ''
+                options.special = "event"
+
+              if iterationRegExp.test type
+                type = type.replace iterationRegExp, ''
+                options.special = "iteration"
+
+              @bindings.push new Rivets.Binding node, type, model, keypath, options

     for el in @els
       parseNode el


Quasi-fix for #47

Quasi-fix for #47

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -324,6 +324,12 @@
       include: '#pragma'
     ,
       name: 'string.quoted.double.haskell'
+      match: /(")[^"]*(")/
+      captures:
+        1: name: 'punctuation.definition.string.begin.haskell'
+        2: name: 'punctuation.definition.string.end.haskell'
+    ,
+      name: 'string.quoted.double.haskell'
       begin: /"/
       end: /"/
       beginCaptures:


fix(helpers): exchanged encrypted_container with containerized export

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -73,8 +73,7 @@
     unless @.id?
       throw new Error 'Empty ID. Save parent model first!'

-    if model.encrypted_container?
-      params = model.encrypt_container(params)
+    params = model(params).export(false, true)

     unless api?
       throw new Error 'No Connector found for resource "' + api_name + '" found: ', api


Use _.find in Kona.Scene#findTile

Use _.find in Kona.Scene#findTile

--- src/scenes.coffee
+++ src/scenes.coffee
@@ -252,7 +252,5 @@
   # Returns Kona.Tile or null if none found
   #
   findTile: (opts={}) ->
-    for tile in Kona.Scenes.currentScene.tiles()
-      return tile if tile.position.x == opts.x && tile.position.y == opts.y
-
-    return null # No match
+    tiles = Kona.Scenes.currentScene.tiles()
+    _.find(tiles, (tile) -> tile.position.x == opts.x && tile.position.y == opts.y) || null


Ensure we clearTimeout when disconnecting

--- src/server.coffee
+++ src/server.coffee
@@ -6,6 +6,7 @@
 class Server
   buffer: []
   ws: undefined
+  timer: undefined

   constructor: (@host, @port, @options = {}) ->

@@ -33,10 +34,18 @@
       # make an initial call to our recursive _write_pipe() method
       @_write_pipe()

-    socket.on 'connect_error', (obj) => console.log 'connect error', obj
-    socket.on 'disconnect',          => console.log "socket at #{@_url()} disconnected"
+    socket.on 'connect_error', (obj) => logger.info 'connect error', obj
+    socket.on 'disconnect',          => @_disconnected
     socket.on 'colorChanged', (data) => @_to_buffer(data)
     socket.on 'colorSet',     (data) => @_to_buffer(data)
+
+  #
+  # when a client disconnects, clear any timer in memory to avoid memory leak or multiple
+  # messages being sent in the future!
+  #
+  _disconnected: ->
+    logger.info "socket at #{@_url()} disconnected"
+    clearTimeout(@timer)

   #
   # create writestream to kernel at /dev/ttyO1
@@ -50,9 +59,9 @@
   # if buffer has content, at 30ms resolution. recursively call this function on success
   #
   _write_pipe: ->
-    timer = setTimeout (=>
-      # clear timer (memory leak!)
-      clearTimeout(timer)
+    @timer = setTimeout (=>
+      # clear @timer (memory leak!)
+      clearTimeout(@timer)

       # recurse
       if @buffer.length == 0


server: magic number

server: magic number

--- src/server.coffee
+++ src/server.coffee
@@ -1,3 +1,4 @@
+#!/usr/local/bin/coffee
 # diff server

 http = require 'http'


convert old routes into new

convert old routes into new

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -408,12 +408,26 @@

     if !push_state_support && !@use_hash_routing_for_old_browsers
       @logger("Warning! You browser not support pushState, and you disable hash routing for old browser")
-
+
+    R = Sirius.RouteSystem
+
+    if @use_hash_routing_for_old_browsers and !push_state_support
+      # convert to new routing
+      urls = [] #save urls into array, for check collision
+      route = {}
+      for url, action of @route
+        urls.push(url) if R._hash_route(url)
+        if R._plain_route(url)
+          url = "\##{url}"
+          if urls.indexOf(url) != -1
+            @logger("Warning! Routes already have '#{url}' url")
+        route[url] = action
+      @route = route

     # start
-    Sirius.RouteSystem.create(@route, () =>
-      @adapter.fire(document, "application:run", new Date());
-    );
+    #R.create(@route, () =>
+    #  @adapter.fire(document, "application:run", new Date());
+    #);

     @logger()
     if @start


adds lots of new windows..and breaks things...

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -219,7 +219,10 @@
       false

   onDragLeave: (event) =>
+    event.stopPropagation()
+    event.preventDefault()
     @removePlaceholder()
+    RendererIpc.send('command', 'application:new-window')

   onDragEnd: (event) =>
     @clearDropTarget()


Insert/append using DOM APIs

Insert/append using DOM APIs

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -98,9 +98,9 @@
   insertTabAtIndex: (tab, index) ->
     followingTab = @tabAtIndex(index) if index?
     if followingTab
-      $(tab).insertBefore(followingTab)
-    else
-      @append(tab)
+      @element.insertBefore(tab, followingTab)
+    else
+      @element.appendChild(tab)
     tab.updateTitle()

   removeTabForItem: (item) ->


Show some text instead of the profile when the user has been deleted

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -12,10 +12,14 @@
     title: 'Profile'
     active_tab: 'show'
     render: (main_region, user) =>
-      main_region.show new ProfileView
-        model: user
-        collection: window.Channels
-        created_facts_view: @getFactsView user.created_facts()
+      if user.get('deleted')
+        main_region.show new TextView model:
+          new Backbone.Model text: 'This profile has been deleted.'
+      else
+        main_region.show new ProfileView
+          model: user
+          collection: window.Channels
+          created_facts_view: @getFactsView user.created_facts()

   notification_options: (username)->
     title: 'Notification Settings'


Made code a bit nicer

Made code a bit nicer

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -4,6 +4,20 @@

   routes: ['showProfile', 'showNotificationSettings', 'showFact']

+  # ACTIONS
+  showProfile: (username) -> @showPage username, @profile_options(username)
+  showNotificationSettings: (username) -> @showPage username, @notification_options(username)
+
+  showFact: (slug, fact_id)->
+    app.closeAllContentRegions()
+    @main = new TabbedMainRegionLayout();
+    app.mainRegion.show(@main)
+
+    fact = new Fact(id: fact_id)
+    fact.fetch
+      success: (model, response) => @withFact(model)
+
+  # HELPERS
   profile_options: (username) ->
     title: 'About ' + username
     active_tab: 'show'
@@ -21,21 +35,6 @@
     mainRegion: (user) ->
       new NotificationSettingsView model: user

-  # ACTIONS
-  showProfile: (username) -> @showPage username, @profile_options(username)
-  showNotificationSettings: (username) -> @showPage username, @notification_options(username)
-
-  showFact: (slug, fact_id)->
-    app.closeAllContentRegions()
-    @main = new TabbedMainRegionLayout();
-    app.mainRegion.show(@main)
-
-    fact = new Fact(id: fact_id)
-    fact.fetch
-      success: (model, response) => @withFact(model)
-
-
-  # HELPERS
   showPage: (username, options) ->
     app.leftBottomRegion.close()
     @main = new TabbedMainRegionLayout();
@@ -70,12 +69,10 @@
       forProfile: true

   getFactsView: (channel) ->
-    facts_view = new FactsView(
-        collection: new ChannelFacts([],
-          channel: channel
-        ),
+    collection = new ChannelFacts [], channel: channel
+    facts_view = new FactsView
+        collection: collection,
         model: channel
-      )

   withFact: (fact)->
     window.efv = new ExtendedFactView(model: fact)
@@ -109,7 +106,6 @@
     app.leftMiddleRegion.show(channelCollectionView)
     channelCollectionView.setActive('profile')

-
   makePermalinkEvent: (baseUrl) ->
     @permalink_event = @bindTo FactlinkApp.vent, 'factlink_permalink_clicked', (e, fact) =>
       navigate_to = fact.get('url')


`require` tag now is void

`require` tag now is void

--- Element/element/parser.coffee
+++ Element/element/parser.coffee
@@ -2,6 +2,9 @@

 attrsKeyGen = (elem, i) -> elem
 attrsValueGen = (elem, i) -> i
+
+VOID_ELEMENTS =
+	require: true

 module.exports = (Element) ->

@@ -47,7 +50,8 @@

 			@_addDomElement element

-			@_tagStack.push element
+			unless VOID_ELEMENTS[name]
+				@_tagStack.push element

 		ontext: (data) ->


Fix post moderation checks and display style

--- resources/assets/coffee/react/modding-profile/posts.coffee
+++ resources/assets/coffee/react/modding-profile/posts.coffee
@@ -30,7 +30,7 @@
       div className: 'osu-layout',
         [
           for post in @props.posts
-            canModeratePosts = BeatmapDiscussionHelper.canModeratePosts(@props.currentUser)
+            canModeratePosts = BeatmapDiscussionHelper.canModeratePosts(currentUser)
             canBeDeleted = canModeratePosts || currentUser.id? == post.user_id

             topClasses = 'beatmap-discussion beatmap-discussion--preview'
@@ -54,7 +54,7 @@
                     beatmapset: post.beatmap_discussion.beatmapset
                     discussion: post.beatmap_discussion
                     post: post
-                    type: 'discussion'
+                    type: 'reply'
                     users: @props.users
                     user: @props.users[post.user_id]
                     read: true


fix error parsing

fix error parsing

--- lib/view/body/parser.coffee
+++ lib/view/body/parser.coffee
@@ -1,3 +1,5 @@
+"using babel"
+
 regexHeader = /^(Goal|Have)\: ((?:\n|.)+)/
 parseHeader = (str) ->
     result = str.match regexHeader
@@ -77,7 +79,7 @@
         term: result[1]
         errorType: 'not in scope'

-regexTypeMismatch = /\n\s*((?:\n|.)*)\s* \!\=\< \s*((?:\n|.)*)\s* of type \s*((?:\n|.)*)\s*\nwhen checking that the expression \s*((?:\n|.)*)\s* has type\n\s*((?:\n|.)*)\s*/
+regexTypeMismatch = /\n\s*((?:\n|.)*)\s*\!\=\<?\s*((?:\n|.)*)\s*of type \s*((?:\n|.)*)\s*when checking that the expression\s*((?:\n|.)*)\s*has type\s*((?:\n|.)*)\s*/
 parseTypeMismatch = (str) ->
     result = str.match regexTypeMismatch
     if result
@@ -89,7 +91,7 @@
         termType: result[5]
         errorType: 'type mismatch'

-regexWrongConstructor = /The constructor \s*((?:\n|.)*)\s* does not construct an element of \s*((?:\n|.)*)\s*\nwhen checking that the expression \s*((?:\n|.)*)\s* has type \s*((?:\n|.)*)\s*/
+regexWrongConstructor = /The constructor\s*((?:\n|.)*)\s*does not construct an element of\s*((?:\n|.)*)\s*when checking that the expression\s*((?:\n|.)*)\s*has type\s*((?:\n|.)*)\s*/
 parseWrongConstructor = (str) ->
     result = str.match regexWrongConstructor
     if result


only allow users to delete comments

only allow users to delete comments

--- client/views/blog/blog.coffee
+++ client/views/blog/blog.coffee
@@ -63,8 +63,9 @@
               comment: 'Please login to post comments'

       sideComments.on 'commentDeleted', (comment) ->
-        Comment.destroyAll comment.id
-        sideComments.removeComment comment.sectionId, comment.id
+        if Meteor.user()
+          Comment.destroyAll comment.id
+          sideComments.removeComment comment.sectionId, comment.id

 Template.blogShowBody.rendered = ->


allowing keywords as identifiers, when used as identifiers, because we've got more keywords than JavaScript does.

--- lexer.coffee
+++ lexer.coffee
@@ -61,6 +61,10 @@
 # Tokens which could legitimately be invoked or indexed.
 CALLABLE: ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING']

+# Tokens that indicate an access -- keywords immediately following will be
+# treated as identifiers.
+ACCESSORS: ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@']
+
 # Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
 BEFORE_WHEN: ['INDENT', 'OUTDENT', 'TERMINATOR']

@@ -72,7 +76,6 @@
   @indent  : 0          # The current indent level.
   @indents : []         # The stack of all indent levels we are currently within.
   @tokens  : []         # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
-  @spaced  : null       # The last token that has a space following it.
   while @i < @code.length
     @chunk: @code.slice(@i)
     @extract_next_token()
@@ -98,17 +101,16 @@
 # Matches identifying literals: variables, keywords, method names, etc.
 lex::identifier_token: ->
   return false unless id: @match IDENTIFIER, 1
-  # Keywords are special identifiers tagged with their own name,
-  # '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 '::'
-  if tag is 'IDENTIFIER' and @value() is '.' and !(@value(2) is '.')
+  @tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
+  if @value() is '.' and not (@value(2) is '.')
     if @tag(2) is '?'
       @tag(1, 'SOAK_ACCESS')
       @tokens.splice(-2, 1)
     else
       @tag(1, 'PROPERTY_ACCESS')
+  tag: 'IDENTIFIER'
+  tag:  id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and not (ACCESSORS.indexOf(@tag()) >= 0)
+  tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
   @token(tag, id)
   @i += id.length
   true
@@ -199,7 +201,7 @@
 # Matches and consumes non-meaningful whitespace.
 lex::whitespace_token: ->
   return false unless space: @match WHITESPACE, 1
-  @spaced: @value()
+  @tokens[@tokens.length - 1].spaced: true
   @i += space.length
   true

@@ -224,7 +226,8 @@
   value ||= @chunk.substr(0, 1)
   tag: if value.match(ASSIGNMENT) then 'ASSIGN' else value
   tag: 'TERMINATOR' if value == ';'
-  if @value() isnt @spaced and CALLABLE.indexOf(@tag()) >= 0
+
+  if not @tokens[@tokens.length - 1].spaced and CALLABLE.indexOf(@tag()) >= 0
     tag: 'CALL_START'  if value is '('
     tag: 'INDEX_START' if value is '['
   @token tag, value


fixing Lexer#ASSIGNED

fixing Lexer#ASSIGNED


Don't blow up when queueing events on a destroyed browser.

--- lib/zombie/eventloop.coffee
+++ lib/zombie/eventloop.coffee
@@ -265,7 +265,7 @@

   # Add a function to the event queue, to be executed in order.
   enqueue: (fn)->
-    if fn
+    if fn && @queue
       @queue.push(fn)
       @eventLoop.run()
     return


clearInterval/clearTimeout don't raise exceptions when invalid things happen, so make that true of the zombie implementations

--- lib/zombie/eventloop.coffee
+++ lib/zombie/eventloop.coffee
@@ -96,9 +96,9 @@
       return timer

     window.clearTimeout = (timer)->
-      timer.stop()
+      try timer.stop() catch exc
     window.clearInterval = (timer)->
-      timer.stop()
+      try timer.stop() catch exc

   # ### perform(fn)


category rename

category rename

--- app/assets/javascripts/controllers/categories.js.coffee
+++ app/assets/javascripts/controllers/categories.js.coffee
@@ -4,4 +4,10 @@
     detete_category: (category) ->
       console.log(category)
     edit_category: (category) ->
-      console.log(category)+      bootbox.prompt
+        title: 'Изменить название категории'
+        value: category.get 'name'
+        callback: (result) ->
+          if result
+            category.set 'name', result
+            category.save()


Added support for backordering

Added support for backordering

--- app/assets/javascripts/sprangular/models/variant.coffee
+++ app/assets/javascripts/sprangular/models/variant.coffee
@@ -11,4 +11,4 @@
     @image = @images[0]

   isAvailable: ->
-    !@track_inventory || @in_stock
+    !@track_inventory || @in_stock || @is_backorderable


prevent precision overflow

prevent precision overflow

--- app/js/controllers/account_vote.coffee
+++ app/js/controllers/account_vote.coffee
@@ -57,7 +57,7 @@

     $scope.updateVotes = ->
         myBal = $scope.balances[Info.symbol]
-        balMinusFee = myBal.amount / myBal.precision - Wallet.info.transaction_fee.amount / myBal.precision
+        balMinusFee = parseInt(myBal.amount / myBal.precision - Wallet.info.transaction_fee.amount / myBal.precision)
         $modal.open
             templateUrl: "dialog-confirmation.html"
             controller: "DialogConfirmationController"


Intensify bossfight intro

Intensify bossfight intro

--- app/scripts/lazerscripts/stage1.coffee
+++ app/scripts/lazerscripts/stage1.coffee
@@ -184,10 +184,30 @@
     @sequence(
       @checkpoint @checkpointStart('Bay', 300000)
       @setScenery('UnderBridge')
+      @parallel(
+        @if((-> @player(1).active), @drop(item: 'lasers', inFrontOf: @player(1)))
+        @if((-> @player(2).active), @drop(item: 'lasers', inFrontOf: @player(2)))
+      )
       @mineSwarm()
+      @parallel(
+        @if((-> @player(1).active), @drop(item: 'lasers', inFrontOf: @player(1)))
+        @if((-> @player(2).active), @drop(item: 'lasers', inFrontOf: @player(2)))
+      )
       @mineSwarm direction: 'left'
+      @parallel(
+        @if((-> @player(1).active), @drop(item: 'lasers', inFrontOf: @player(1)))
+        @if((-> @player(2).active), @drop(item: 'lasers', inFrontOf: @player(2)))
+      )
       @mineSwarm()
-      @waitForScenery('UnderBridge', event: 'inScreen')
+      @while(
+        @waitForScenery('UnderBridge', event: 'inScreen')
+        @sequence(
+          @pickTarget('PlayerControlledShip')
+          @runScript(Game.Scripts.Stage1BossRocket, @targetLocation(x: 1.1))
+          @wait 200
+        )
+      )
+
       @setSpeed 0
       @placeSquad Game.Scripts.Stage1BossStage1


Slow down start of stage 2

Slow down start of stage 2

--- app/scripts/lazerscripts/stage2.coffee
+++ app/scripts/lazerscripts/stage2.coffee
@@ -33,9 +33,10 @@
       )
       @setScenery 'Skyline2'
       # Add enemies during decent
+      @chapterTitle(2, 'Underground')
+      @showText 'Get Ready', color: '#00FF00', mode: 'blink', blink_amount: 3, blink_speed: 300
       @parallel(
         @gainHeight(-1300, duration: 30000)
-        @chapterTitle(2, 'Underground')
         @placeSquad Game.Scripts.HeliAttack,
           amount: 3
           delay: 6000


allows tools to be create with a key a secret

test plan:
 * add a tool with a key/secret from app center
   - ensure that the key/secret is saved on the tool
  * add a tool without a key/secret from app center
   - it should have a key of N/A

Change-Id: I159034a6c00dd120619d73b9bb0f3c4926922778
Reviewed-on: https://gerrit.instructure.com/21618
Reviewed-by: Eric Berry <b4e131942074b4e4dafdcc14a63e85b7d8e18577@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Adam Phillipps <0e18f44c1fec03ec4083422cb58ba6a09ac4fb2a@instructure.com>
Product-Review: Brad Humphrey <9233b6f5388d6867a2a7be14d8b4ba53c86cfde2@instructure.com>

--- app/coffeescripts/views/ExternalTools/AddAppView.coffee
+++ app/coffeescripts/views/ExternalTools/AddAppView.coffee
@@ -63,8 +63,10 @@

     submit: ->
       formData = @$el.getFormData()
-      @model.set 'name', formData['canvas_app_name'] if formData['canvas_app_name']
       if @validate(formData)
+        @model.set 'name', formData['canvas_app_name'] if formData['canvas_app_name']
+        @model.set 'consumer_key', formData['consumer_key'] if formData['consumer_key']
+        @model.set 'shared_secret', formData['shared_secret'] if formData['shared_secret']
         disablingDfd = new $.Deferred()
         @updateConfigUrl(formData)
         @model.save
@@ -78,6 +80,8 @@
       configUrl = @model.get('config_url')
       queryParams = {}
       queryParams[option['name']] = formData[option['name']] for option in @configOptions when formData[option['name']]
+      delete queryParams['consumer_key']
+      delete queryParams['shared_secret']
       newConfigUrl = @model.get('config_url') + (if configUrl.indexOf('?') != -1 then '&' else '?') + $.param(queryParams)
       @model.set('config_url', newConfigUrl)


remove attachment fragment generator

remove attachment fragment generator

--- app/modules/components/wysiwyg/wysiwyg.directive.coffee
+++ app/modules/components/wysiwyg/wysiwyg.directive.coffee
@@ -314,9 +314,6 @@
             analytics.trackEvent('develop', 'save wysiwyg', $scope.mode, 1)

         uploadEnd = (name, url, type, id=null) ->
-            if id
-                url = "#{url}#_taiga-refresh=#{type}:#{id}"
-
             if taiga.isImage(name)
                 mediumInstance.pasteHTML("
")
             else
@@ -324,10 +321,6 @@
                 mediumInstance.pasteHTML("" + name + "
")

         uploadEndMarkdown = (name, url, type, id=null) ->
-            if id
-                url = "#{url}#_taiga-refresh=#{type}:#{id}"
-
-
             if taiga.isImage(name)
                 $scope.markdown += "\n![#{name}](#{url} \"#{name}\")\n"
             else


delete unnecesary code

delete unnecesary code

--- app/modules/components/wysiwyg/wysiwyg.directive.coffee
+++ app/modules/components/wysiwyg/wysiwyg.directive.coffee
@@ -100,11 +100,6 @@

                 pre.parentNode.insertBefore(p, pre.nextSibling)

-    refreshAttachments = (mediumInstance) ->
-        promise = wysiwygService.refreshAttachmentURL(mediumInstance.getContent())
-        promise.then (html) =>
-            mediumInstance.setContent(html)
-
     AlignRightButton = MediumEditor.extensions.button.extend({
         name: 'rtl',
         init: () ->


Add pageviews to analytics

Add pageviews to analytics

--- client/view/setup.coffee
+++ client/view/setup.coffee
@@ -15,3 +15,7 @@
         m.parentNode.insertBefore(a, m)
     )(window, document, 'script', '//www.google-analytics.com/analytics.js','ga')
     ga('create', 'UA-36904731-2', 'reversim.com')
+
+  ga('send', 'pageview',
+    'page': document.location.pathname,
+    'location': document.location.href)


Removing fixed-width as doesn't always work nicely (if replacement is shorter than original)

--- annotran/static/scripts/annotator/plugin/substitution.coffee
+++ annotran/static/scripts/annotator/plugin/substitution.coffee
@@ -68,16 +68,10 @@
     newEle.text(substituteText)

     newEle.css({
-      "background-color" : "#F5F5F5",
-      "display": "inline-block"
+      "background-color" : "#F5F5F5"
     })

     ele.before(newEle)
-
-    width = newEle.width() + "px"
-    newEle.css({
-        "width": width
-    })

     newEle.mouseover (event) ->
       newEle.text(originalText)


Only link the username in InlineUsers

Only link the username in InlineUsers

The link text should not include the realname and email in parentheses.

--- app/assets/javascripts/components/overview/inline_users.cjsx
+++ app/assets/javascripts/components/overview/inline_users.cjsx
@@ -5,22 +5,20 @@
   displayName: 'InlineUsers'
   render: ->
     key = @props.title + '_' + @props.role
-    user_list = @props.users.map (user) ->
+    last_user_index = @props.users.length - 1
+    user_list = @props.users.map (user, index) ->
       link = "https://en.wikipedia.org/wiki/User:#{user.wiki_id}"
       if user.real_name?
-        displayName = "#{user.real_name} (#{user.wiki_id}#{if user.email? then " / " + user.email else ""})"
+        extra_info = " (#{user.wiki_id}#{if user.email? then " / " + user.email else ""})"
       else
-        displayName = user.wiki_id
-      {displayName}
+        extra_info = ''
+      extra_info = extra_info + ', ' unless index == last_user_index

-    # This is feels really hacky, but it works.
-    last_user_index = user_list.length - 1
-    inline_user_list = ([user, ', '] for user, index in user_list when index != last_user_index)
-    inline_user_list.push user_list[last_user_index]
+      {user.wiki_id}{extra_info}

-    inline_user_list = if user_list.length > 0 then inline_user_list else 'None'
+    user_list = if user_list.length > 0 then user_list else 'None'
     if @props.users.length > 0 || @props.editable
-      inline_list = {@props.title}: {inline_user_list}
+      inline_list = {@props.title}: {user_list}
     allowed = @props.role != 4 || (@props.current_user.role == 4 || @props.current_user.admin)
     button = <EnrollButton {...@props} users={@props.users} role={@props.role} key={key} inline=true allowed={allowed} show={@props.editable && allowed} />


replacing actor, group, app, user

replacing actor, group, app, user

--- dataBuilder.coffee
+++ dataBuilder.coffee
@@ -6,6 +6,10 @@

 for i in [0..100]
 	event = {}
-	event = sampleEvents[Math.floor(Math.random() * sampleEvents.length)]
+	event = clone(eventSpec[Math.floor(Math.random() * eventSpec.length)])
+	event["description"] = event["description"].replace("${actor}", users[Math.floor(Math.random() * users.length)])
+	event["description"] = event["description"].replace("${app}", apps[Math.floor(Math.random() * apps.length)])
+	event["description"] = event["description"].replace("${user}", users[Math.floor(Math.random() * users.length)])
+	event["description"] = event["description"].replace("${group}", groups[Math.floor(Math.random() * groups.length)])
 	event["datetime"] = randomDate(new Date(2012, 0, 1), new Date())
 	events.push(clone(event))


gzip all .js files

gzip all .js files

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -75,9 +75,9 @@
           mode: 'gzip'

         files: [
-          { expand: true, cwd: 'dist/', src: ['**/*.js'], dest: 'dist/', ext: '.js' }
-          { expand: true, cwd: 'dist/', src: ['**/*.css'], dest: 'dist/', ext: '.css' }
-          { expand: true, cwd: 'dist/', src: ['**/*.html'], dest: 'dist/', ext: '.html' }
+          { expand: true, cwd: 'dist/', src: ['**/*.js'], dest: 'dist/' }
+          { expand: true, cwd: 'dist/', src: ['**/*.css'], dest: 'dist/' }
+          { expand: true, cwd: 'dist/', src: ['**/*.html'], dest: 'dist/' }
         ]

   grunt.registerTask 'generate', ['datauri']


feat(gruntfile.coffee): update coffee to look for e2e.coffee & protractor.coffee files during build

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -307,6 +307,7 @@
           findModuleFilesIn('src/app/')
           findModuleFilesIn('src/common/')
           '**/*.spec.coffee'
+          '**/*.e2e.coffee'
         ]
         cwd: '<%= build_dir %>'
         dest: '<%= build_dir %>'


Replace stubbing with biasing RNG

Replace stubbing with biasing RNG

--- test/battle_engine_spec.coffee
+++ test/battle_engine_spec.coffee
@@ -26,41 +26,38 @@
         team1: [Factory('Celebi')]
         team2: [Factory('Magikarp')]
       move = moves['leaf-storm']
-      sinon.stub(move, 'willMiss', -> true)
+      shared.biasRNG.call(this, 'randInt', 'miss', 100)
       defender = @team2.at(0)
       originalHP = defender.currentHP
       @battle.makeMove(@player1, 'leaf-storm')
       @battle.continueTurn()
       defender.currentHP.should.equal originalHP
-      move.willMiss.restore()

     it 'triggers effects dependent on the move missing', ->
       shared.create.call this,
         team1: [Factory('Hitmonlee')]
         team2: [Factory('Magikarp')]
       move = moves['hi-jump-kick']
-      sinon.stub(move, 'willMiss', -> true)
+      shared.biasRNG.call(this, 'randInt', 'miss', 100)
       mock = sinon.mock(move)
       mock.expects('afterMiss').once()
       @battle.makeMove(@player1, 'hi-jump-kick')
       @battle.continueTurn()
       mock.verify()
       mock.restore()
-      move.willMiss.restore()

     it 'does not trigger effects dependent on the move hitting', ->
       shared.create.call this,
         team1: [Factory('Celebi')]
         team2: [Factory('Gyarados')]
       move = moves['hi-jump-kick']
-      sinon.stub(move, 'willMiss', -> true)
+      shared.biasRNG.call(this, 'randInt', 'miss', 100)
       mock = sinon.mock(move)
       mock.expects('afterSuccessfulHit').never()
       @battle.makeMove(@player1, 'leaf-storm')
       @battle.continueTurn()
       mock.verify()
       mock.restore()
-      move.willMiss.restore()

   describe 'an attack with 0 accuracy', ->
     it 'can never miss', ->


updated setScalingDisabled() condition, maybe one day minScale can be greater than maxScale?

--- coffee/angular-pintura.coffee
+++ coffee/angular-pintura.coffee
@@ -96,7 +96,7 @@
     if viewport.height < @node.height()*@minScale
       # compute min scale based on heights of stage and image
       @minScale = viewport.height/@node.height()
-    # never
+    # never exceed/scale up original image size
     @minScale = Math.min(@minScale, 1)

   _fitScale: (scale) ->
@@ -308,7 +308,7 @@
         syncScope()

       setScalingDisabled = ->
-        scope.scalingDisabled = ngPintura.image.minScale is ngPintura.image.maxScale
+        scope.scalingDisabled = ngPintura.image.minScale >= ngPintura.image.maxScale

       imageLoad = ->
         scope.fitInView() if scope.fitOnload
@@ -324,6 +324,7 @@
       scope.scaleStep ?= 0.4
       scope.mwScaleStep ?= 0.1
       scope.moveStep ?= 100
+      scope.fitOnload ?= true
       # append stage container to current element
       element.append(ngPintura.stage.content)
       # resize stage container


fix code style.

fix code style.

--- coffee/services/ticket.coffee
+++ coffee/services/ticket.coffee
@@ -95,10 +95,9 @@
           console.debug 'runtime error'
           callback? null; return

-        if not (tickets[TICKET]?)
+        if not tickets[TICKET]?
           console.debug 'project or ticket does not exists'
-          callback? null
-          return
+          callback? null; return

         tmp = []
         for t in tickets[TICKET]


Update Leebo translation. #565

Update Leebo translation. #565

--- coffeescripts/cards-ru.coffee
+++ coffeescripts/cards-ru.coffee
@@ -1552,7 +1552,7 @@
             text: """Вы можете проводить атаки, перекрывая подставкой препятствие.

Ваши атаки не могут быть перекрыты."""
         '"Leebo"':
             name: "Лиибо"
-            text: """Действие: Выполните свободное действие Бочку. Затем получите 1 ионный жетон."""
+            text: """Действие: Выполните свободное действие Ускорение. Затем получите 1 ионный жетон."""
         "Ruthlessness":
             name: "Беспощадность"
             text: """После того, как вы выполните атаку, которая попала, вы должны выбрать 1 корабль на расстоянии 1 от защищающегося (кроме себя). Этот корабль получает 1 повреждение."""


Make all quotes multiline. Fixes a bunch of annoying errors.

--- app/assets/javascripts/discourse/components/bbcode.js.coffee
+++ app/assets/javascripts/discourse/components/bbcode.js.coffee
@@ -88,7 +88,7 @@
     if stripped_hashed == contents_hashed
       result += ", full:true"

-    result += "\"]#{sansQuotes}[/quote]\n\n"
+    result += "\"]\n#{sansQuotes}\n[/quote]\n\n"

   formatQuote: (text, opts) ->


this is the simplest way to reset the classes on the pinned components.  without wasnt working because the function expects the excluded values as individual and not the whole array of values to exclude. for that, difference is the function to use.  also, pinned-on and pinned-shy, classes added or removed based on scroll were not being considered despite needing to be removed on unmount

--- shared/src/components/pinned-header-footer-card/index.cjsx
+++ shared/src/components/pinned-header-footer-card/index.cjsx
@@ -3,7 +3,6 @@

 union = require 'lodash/union'
 omit  = require 'lodash/omit'
-without = require 'lodash/without'

 ScrollListenerMixin = require '../../mixins/ScrollListener'

@@ -34,6 +33,7 @@
     shouldBeShy: false
     headerHeight: 0
     containerMarginTop: '0px'
+    originalClassName: document.body.className

   mixins: [ScrollListenerMixin, ResizeListenerMixin, GetPositionMixin]

@@ -46,7 +46,7 @@
     @setBodyClassList()

   componentWillUnmount: ->
-    document.body.className = without(document.body.classList, @desiredBodyClassList()).join(' ')
+    document.body.className = @state.originalClassName

   setBodyClassList: ->
     document.body.className = union(document.body.classList, @desiredBodyClassList()).join(' ')


add optional keepalive

add optional keepalive

--- lib/builder.coffee
+++ lib/builder.coffee
@@ -32,6 +32,7 @@
       buildpack: req.body.buildpack
       cache:     req.body.cache
       env:       req.body.env
+      keepalive: req.body.keepalive
     require("builder").init().build req.body.source, options, (build, builder) ->
       res.writeHead 200
         "Content-Type":      "text/plain"
@@ -39,6 +40,16 @@
         "X-Cache-Url":       builder.cache_url
         "X-Manifest-Id":     builder.id
         "X-Slug-Url":        builder.slug_url()
+
+      if options.keepalive
+        ping = setInterval (->
+          try
+            res.write "\000\000\000"
+          catch error
+            console.log "error writing to socket"
+            clearInterval ping
+        ), 1000
+
       build.on "data", (data)   ->
         exit_header = "ANVIL!EXITCODE:"
         if (pos = data.toString().indexOf(exit_header)) > -1
@@ -49,6 +60,7 @@
         else
           res.write(data)
       build.on "end", (success) ->
+        clearInterval ping if ping
         logger.finish()
         res.end()


Adjusting API URL to work locally and remotely.

--- collector/scripts/init.coffee
+++ collector/scripts/init.coffee
@@ -3,7 +3,7 @@
         touchrec = new TouchRecorder()
         stream = touchrec.stream
         motiontrck = touchrec.mtntrckr
-        dataupldr = new DataUploader(window.location.protocol+'//'+window.location.host+':8080', stream)
+        dataupldr = new DataUploader(window.location.protocol+'//'+window.location.hostname+':8080', stream)
         window.motionTracking = {touchrec: touchrec, stream: stream, motiontrck: motiontrck, dataupldr: dataupldr}
         true
     else


Port to Process API

Port to Process API

--- components/Store.coffee
+++ components/Store.coffee
@@ -10,36 +10,50 @@
   c.outPorts.add 'pass',
     datatype: 'object'

-  c.inPorts.state.on 'data', (state) ->
-    c.state = state
-
   c.state = {}
-  c.shutdown = ->
+  c.tearDown = (callback) ->
     c.state = {}
-  noflo.helpers.WirePattern c,
-    in: 'action'
-    out: 'pass'
-    forwardGroups: false
-    async: true
-  , (data, groups, out, callback) ->
-    if data and typeof data is 'object' and data.payload and data.action
-      # New-style action object
-      if data.state
-        # Keep track of last state
-        c.state = data.state
-      else
-        debug "#{data.action} was sent without state, using previous state"
-      out.send
-        action: data.action
-        state: data.state or c.state
-        payload: data.payload
-      do callback
+    do callback
+  c.forwardBrackets = {}
+  c.process (input, output) ->
+    if input.hasData 'state'
+      c.state = input.getData 'state'
+      output.done()
       return
-    # Old-style action with only payload, and action defined by brackets
-    action = groups.join ':'
-    debug "#{action} was sent in legacy payload-only format"
-    out.send
-      action: action
-      state: c.state
-      payload: data
-    do callback
+    return unless input.hasStream 'action'
+    packets = []
+    brackets = []
+    input.getStream('action').forEach (ip) ->
+      if ip.type is 'openBracket'
+        brackets.push ip.data
+      if ip.type is 'closeBracket'
+        brackets.pop()
+      if ip.type is 'data'
+        packets.push
+          data: ip.data
+          brackets: brackets.slice 0
+    for packet in packets
+      data = packet.data
+      if data and typeof data is 'object' and data.payload and data.action
+        # New-style action object
+        if data.state
+          # Keep track of last state
+          c.state = data.state
+        else
+          debug "#{data.action} was sent without state, using previous state"
+        output.send
+          pass:
+            action: data.action
+            state: c.state
+            payload: data.payload
+        continue
+      # Old-style action with only payload, and action defined by brackets
+      action = packet.brackets.join ':'
+      debug "#{action} was sent in legacy payload-only format"
+      output.send
+        pass:
+          action: action
+          state: c.state
+          payload: data
+    output.done()
+    return


fixed some commenting errors and file path errors

--- src/js/main.coffee
+++ src/js/main.coffee
@@ -27,7 +27,7 @@
     # Battle screens
     Battle = require './battle.coffee'

-    LEVEL 1 - Novice
+    # LEVEL 1 - Novice
     game.addScreen(new Battle(game, require('debaters/novice.coffee'),
                               'This is my first debate! I hope I can
                               convince you of the merits of COMMUNISM...
@@ -123,7 +123,7 @@
                               [require('moves/bake-cookies.coffee'), require('moves/save-world.coffee')]))

     # LEVEL 28 - Ron Paul (thinks he is at Libertarian National Convention)
-    game.addScreen(new Battle(game, require('debaters/ron-paul.coffee'),
+    game.addScreen(new Battle(game, require('./debaters/ron-paul.coffee'),
                               'Is this the Libertarian National Convention??
                               ...     ...     ...whaaaaaat? Invisible hands?
                                     Radical freedom?',


fixed read attr

fixed read attr

--- src/xml.coffee
+++ src/xml.coffee
@@ -110,9 +110,10 @@

     attr: (key, value) =>
         if typeof key is 'string'
-            attr = @builder.query 'attr', this, key
-            if attr and not value?
-                return @attrs[key] = attr # sync it and return value
+            if not value? and ((attr = @builder.query 'attr', this, key))?
+                # sync it and return value
+                @attrs[key] = attr
+                return attr
             @attrs[key] = value
             @emit 'attr', this, key, value
         else
@@ -223,7 +224,7 @@
     # intern getter to let intern tag structure stay in sync with eg dom
     query: (type, tag, key) ->
         if type is 'attr'
-            tag.attr[key]
+            tag.attrs[key]
         else if type is 'text'
             tag.content


Fix custom loaders callback handling

Fix custom loaders callback handling

--- src/lib/loader/NodeJs.coffee
+++ src/lib/loader/NodeJs.coffee
@@ -8,6 +8,13 @@
 CoffeeScript = require 'coffeescript'
 if typeof CoffeeScript.register != 'undefined'
   CoffeeScript.register()
+
+registerCustomLoaders = (loader, componentLoaders, callback) ->
+  return callback null unless componentLoaders.length
+  customLoader = require componentLoaders.shift()
+  loader.registerLoader customLoader, (err) ->
+    return callback err if err
+    registerCustomLoaders loader, componentLoaders, callback

 registerModules = (loader, modules, callback) ->
   compatible = modules.filter (m) -> m.runtime in ['noflo', 'noflo-nodejs']
@@ -22,16 +29,7 @@
     for c in m.components
       loader.registerComponent m.name, c.name, path.resolve loader.baseDir, c.path

-  return callback null unless componentLoaders.length
-  done = ->
-    if --componentLoaders.length < 1
-      return callback.apply this, arguments
-    return
-  componentLoaders.forEach (loaderPath) =>
-    cLoader = require loaderPath
-    loader.registerLoader cLoader, (err) ->
-      return callback err if err
-      done null
+  registerCustomLoaders loader, componentLoaders, callback

 manifestLoader =
   writeCache: (loader, options, manifest, callback) ->


added toggleMod to block

added toggleMod to block

--- app/assets/javascripts/block.coffee
+++ app/assets/javascripts/block.coffee
@@ -46,6 +46,12 @@
 	delMod: (mod, value) ->
 		@$node.delMod(mod, value)

+	toogleMod: (mod, value) ->
+		if @hasMod(mod, value)
+			@$node.delMod(mod, value)
+		else
+			@$node.addMod(mod, value)
+
 	hasMod: (mod, value) ->
 		mod = $$.makeMod(mod, value)
 		@$node.hasClass("b-#{@name}--#{mod}")


dispfile => secondary in gotodef technically breaking, but I don't think anyone uses this yet...

--- lib/gotodef.coffee
+++ lib/gotodef.coffee
@@ -15,7 +15,7 @@
 #     - `.text` -       Displayed text, searchable.
 #     - `.file` -       File in which this method is defined, not displayed.
 #     - `.line` -       Line of definition.
-#     - `.dispfile` -   Humanized file path, displayed.
+#     - `.secondary` -  Secondary information, displayed below `text` (e.g. file).
 #
 #   or a plain text string if `symbolTable.error` is true.

@@ -49,7 +49,7 @@
     @panel.destroy()

   # Create the view for one item.
-  viewForItem: ({text, dispfile, line}) ->
+  viewForItem: ({text, secondary, line}) ->
     # the highlighting is taken verbatim from https://github.com/atom/command-palette
     filterQuery = @getFilterQuery()
     matches = fuzzaldrinPlus.match(text, filterQuery)
@@ -77,7 +77,7 @@

       @li class: 'two-lines', =>
         @div class: 'primary-line', -> highlighter(text, matches, 0)
-        @div dispfile + ":" + line, class: 'secondary-line'
+        @div secondary, class: 'secondary-line'

   # Only `item.text` is searchable.
   getFilterKey: -> 'text'


listen to canplay event in thyme instead of loadedmetadata

--- app/assets/javascripts/thyme.coffee
+++ app/assets/javascripts/thyme.coffee
@@ -147,7 +147,7 @@

   # set up the chapter elements
   displayChapters = ->
-    if chaptersElement.readyState >= 1 and
+    if chaptersElement.readyState == 2 and
     (chaptersTrack = chaptersElement.track)
       chaptersTrack.mode = 'hidden'
       i = 0
@@ -212,7 +212,7 @@

   # set up the metadata elements
   displayMetadata = ->
-    if metadataElement.readyState >= 1 and (metaTrack = metadataElement.track)
+    if metadataElement.readyState == 2 and (metaTrack = metadataElement.track)
       metaTrack.mode = 'hidden'
       i = 0
       times = []
@@ -341,15 +341,18 @@

   # after video metadata have been loaded, display chapters and metadata in the
   # interactive area
-  $(video).on 'loadedmetadata', ->
+  initialChapters = true
+  initialMetadata = true
+  video.addEventListener 'canplay', ->
     console.log 'chapters.readyState:' + chaptersElement.readyState
     console.log 'metadata.readyState:' + metadataElement.readyState
     console.log 'video.readyState:' + video.readyState
-    if video.readyState >= 1
+    if initialChapters and chaptersElement.readyState == 2
       displayChapters()
+      initialChapters = false
+    if initialMetadata and metadataElement.readyState == 2
       displayMetadata()
-    else
-      alert($('body').data('nometadata'))
+      initialMetadata = false
   return

 $(document).on 'turbolinks:load', ->


Return app properly.

Return app properly.

--- lib/hemlock.coffee
+++ lib/hemlock.coffee
@@ -99,6 +99,9 @@
     app.run = ->
       app.listen app.settings.port, ->
         console.log "up & running @ http://localhost:#{app.settings.port}"
+      app
+
+    app

   server: ->
     app = @createServer()


fix temp mention caret positioning for ie

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -66,7 +66,7 @@

     textNode = document.createTextNode(' ')
     $(textNode).insertAfter(@_current.$tempMention)
-    @_setCaretPosition(@_current.$tempMention[0], 1)
+    @_focusTempMention()

   updateAutocomplete: ->
     search = @_current.$tempMention.text().slice(@_current.trigger.length)
@@ -144,15 +144,25 @@
     @$editable.bind 'keypress', @handleInput
     @$editable.bind 'keyup', @handleKeyup

+  _focusTempMention: ->
+    if @isAutocompleting()
+      element = @_current.$tempMention[0]
+      node = element.firstChild
+      offset = @_current.trigger.length
+      # ie8
+      if document.selection
+        element.focus()
+        node.nodeValue = node.nodeValue + ' '
+        range = document.body.createTextRange()
+        range.moveToElementText(element)
+        range.moveStart('character', offset)
+        range.select()
+      else
+        @_setCaretPosition(element, offset, node)
+
   _setCaretPosition: (element, position, node)->
     node ||= element.firstChild
-    element.focus()
-    if document.selection
-      sel = document.selection.createRange()
-      sel.moveStart('character', position)
-      sel.select()
-    else
-      window.getSelection().collapse(node, position)
+    window.getSelection?().collapse(node, position)

   _getCaretPosition: ->
     window.getSelection().baseOffset


Remove unnecessary '-' (standardizing)

Remove unnecessary '-' (standardizing)

--- lib/latexmk.coffee
+++ lib/latexmk.coffee
@@ -31,7 +31,7 @@

     enableShellEscape = atom.config.get("latex.enableShellEscape")
     pdfOpts.push("-shell-escape %O %S") if enableShellEscape?
-    args.push("--pdflatex=\"pdflatex #{pdfOpts.join(" ")}\"")
+    args.push("-pdflatex=\"pdflatex #{pdfOpts.join(" ")}\"")

     dir = path.dirname(filePath)
     outdir = atom.config.get("latex.outputDirectory")


[Backport] Fix confirm on disabled edit mode lighbox in wiki pages

--- app/coffee/modules/wiki/main.coffee
+++ app/coffee/modules/wiki/main.coffee
@@ -293,12 +293,13 @@
             $scope.$apply(cancelEdition)

         $el.on "keydown", "textarea", (event) ->
-            if event.keyCode == 27
-                $scope.$applyAsync () ->
-                    confirmTitle = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE")
-                    $confirm.ask(confirmTitle).then (askResponse) ->
-                        cancelEdition()
-                        askResponse.finish()
+            return if event.keyCode != 27
+            $scope.$applyAsync () ->
+                title = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_TITLE")
+                message = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_MESSAGE")
+                $confirm.ask(title, null, message).then (askResponse) ->
+                    cancelEdition()
+                    askResponse.finish()

         $scope.$watch $attrs.ngModel, (wikiPage) ->
             return if not wikiPage


Better multi-project handling

Better multi-project handling

--- lib/manager.coffee
+++ lib/manager.coffee
@@ -6,10 +6,19 @@
 module.exports =
 class Manager extends Disposable
   rootDir: ->
-    if atom.workspace.getActiveTextEditor()?
+    # Collect all open TextEditors with  LaTeX grammar
+    texEditors = (editor for editor in atom.workspace.getTextEditors()\
+                    when editor.getGrammar().scopeName.match(/text.tex.latex/))
+    if atom.workspace.getActiveTextEditor()? # An active editor is open
       return atom.project.relativizePath(atom.workspace.getActiveTextEditor().getPath())[0]
-    else
-      return atom.project.getPaths()[0] # backup, return first active project
+    else if texEditors.length > 0   # First open editor with LaTeX grammar
+      return atom.project.relativizePath(texEditors[0].getPath())[0]
+    else # backup, return first active project
+        @latex.logger.log.push {
+          type: status
+          text: "No active TeX editors were open - Setting Project: #{atom.project.getPaths()[0]}"
+        }
+      return atom.project.getPaths()[0]
   constructor: (latex) ->
     @latex = latex