use runOnlyPendingTimers which works better with debounce

--- shared/specs/model/ui-settings.spec.coffee
+++ shared/specs/model/ui-settings.spec.coffee
@@ -1,17 +1,21 @@
 {sinon, _} = require 'shared/specs/helpers'
-{ autorun } = require 'mobx';
+{ autorun } = require 'mobx'
+saveSettingsMock = jest.fn()
+jest.mock('lodash/debounce', -> jest.fn(-> saveSettingsMock ))
+
 UiSettings = require 'model/ui-settings'
 Networking = require 'model/networking'
+mockedDebounce = require 'lodash/debounce'

-jest.useFakeTimers();
+

 describe 'UiSettings', ->

   beforeEach ->
+    saveSettingsMock.mockReset()
     sinon.stub(Networking, 'perform').returns(
       then: (fn) -> fn({})
     )
-

   afterEach ->
     UiSettings._reset()
@@ -26,13 +30,9 @@
   it 'saves when set', ->
     initialSetting = {'one': 1, 'two':'II'}
     UiSettings.initialize(initialSetting)
+
     UiSettings.set(one: 'five')
-    jest.runAllTimers()
-    expect(Networking.perform).to.have.been.called
-    expect(Networking.perform.lastCall.args[0].data?.ui_settings).to.eql(
-      one: 'five', two: 'II'
-    )
-    undefined
+    expect(saveSettingsMock).toHaveBeenCalledTimes(1)

   it 'groups saves together', ->
     initialSetting = {one: 18, two:'III', deep: {key: 'value', bar: 'bz'}}
@@ -40,12 +40,7 @@
     UiSettings.set(one: 'five')
     UiSettings.set(one: 'six', deep: {bar: 'foo'})
     UiSettings.set(one: 'seven')
-    jest.runAllTimers()
-    expect(Networking.perform).to.have.been.calledOnce
-
-    expect(Networking.perform.lastCall.args[0].data?.ui_settings).to.eql(
-      one: 'seven', two: 'III', deep: { bar: 'foo'}
-    )
+    expect(saveSettingsMock).toHaveBeenCalledTimes(3)
     undefined

   it 'can set with key and id', ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -37,7 +37,7 @@
     else if index is 3
       equals obj, obj4

-  #equals indexSum, 6
+  equals indexSum, 6

 test "#find", ->
   engine = Engine()


Update name of collection test

Update name of collection test

--- test/primitives-test.coffee
+++ test/primitives-test.coffee
@@ -542,12 +542,12 @@
       it 'gets the values of all properties', ->
         expect(objectType.values()).to.deep.equal ['bar', 1]

-    collectionMethod = (method) ->
+    itHascollectionMethod = (method) ->
       describe "##{method}", ->
         it "responds to ##{method}", -> expect(objectType).to.respondTo(method)

-    collectionMethod 'map'
-    collectionMethod 'filter'
-    collectionMethod 'forEach'
-    collectionMethod 'push'
-    collectionMethod 'add'
+    itHascollectionMethod 'map'
+    itHascollectionMethod 'filter'
+    itHascollectionMethod 'forEach'
+    itHascollectionMethod 'push'
+    itHascollectionMethod 'add'


Update main.coffee fixing a typo

Update main.coffee fixing a typo

It was bugging me!

--- lib/main.coffee
+++ lib/main.coffee
@@ -17,7 +17,7 @@
     breakOnSingleNewline:
       type: 'boolean'
       default: false
-      description: 'In Markdown, a single newline character doesn\'t cause a line break in the generated HTML. In GitHub Flavored Markdow, that is not true. Enable this config option to insert line breaks in redenred HTML for single newlines in Markdown source.'
+      description: 'In Markdown, a single newline character doesn\'t cause a line break in the generated HTML. In GitHub Flavored Markdown, that is not true. Enable this config option to insert line breaks in redenred HTML for single newlines in Markdown source.'
     liveUpdate:
       type: 'boolean'
       default: true


:new: Use new helper method for generating range

--- lib/main.coffee
+++ lib/main.coffee
@@ -38,6 +38,6 @@
             messages.push
               type: "Error"
               filePath: filePath
-              range: [[match[2] - 1 , 1], [match[2] - 1, 2]]
+              range: helpers.rangeFromLineNumber(textEditor, match[2] - 1)
               text: match[1]
           return messages


Add "Save as HTML" to Command Palette

Add "Save as HTML" to Command Palette

--- lib/main.coffee
+++ lib/main.coffee
@@ -20,6 +20,8 @@
         @toggle()
       'markdown-preview:copy-html': =>
         @copyHtml()
+      'markdown-preview:save-as-html': =>
+        @saveAsHtml()
       'markdown-preview:toggle-break-on-single-newline': ->
         keyPath = 'markdown-preview.breakOnSingleNewline'
         atom.config.set(keyPath, not atom.config.get(keyPath))
@@ -109,3 +111,27 @@
         console.warn('Copying Markdown as HTML failed', error)
       else
         atom.clipboard.write(html)
+
+  saveAsHtml: ->
+    activePane = atom.workspace.getActivePaneItem()
+    if isMarkdownPreviewView(activePane)
+      activePane.saveAs()
+      return
+
+    editor = atom.workspace.getActiveTextEditor()
+    return unless editor?
+
+    grammars = atom.config.get('markdown-preview.grammars') ? []
+    return unless editor.getGrammar().scopeName in grammars
+
+    uri = @uriForEditor(editor)
+    previewPane = atom.workspace.paneForURI(uri)
+    return unless previewPane?
+
+    previousActivePane = atom.workspace.getActivePane()
+    previewPane.activate()
+    activePane = atom.workspace.getActivePaneItem()
+
+    if isMarkdownPreviewView(activePane)
+      activePane.saveAs(). then ->
+        previousActivePane.activate()


indent error

indent error

--- lib/main.coffee
+++ lib/main.coffee
@@ -217,7 +217,7 @@
         if row > 0
             for i in [0 .. row - 1] when @blank(row)
                 row -= 1
-            @createResultBubble code, row
+        @createResultBubble code, row

     runAllAbove: () ->


add tests for returned processes structure

--- test/tests/proc.test.coffee
+++ test/tests/proc.test.coffee
@@ -12,27 +12,46 @@
   process.exit(1)

 describe 'sub', ->
-  beforeEach ->
-    processes = null
   afterEach ->
     sub.killAll(processes) if processes?
+    processes = null

-  it 'starts a process', (done) ->
-    config =
-      app:
-        command: 'node test/apps/service.js %port%'
-        logFilePath: 'test/log/start-proc.log'
+  describe 'starts a process', ->
+    before (done) ->
+      config =
+        app:
+          command: 'node test/apps/service.js %port%'
+          logFilePath: 'test/log/start-proc.log'
+          port: 9903

-    sub config, (error, _processes) ->
-      processes = _processes
+      sub config, (error, _processes) =>
+        processes = _processes
+        @proc = processes?.app
+        done(error)

-      try
-        assert.falsey 'error', error
-        assert.falsey 'process.exitCode', processes.app.rawProcess.exitCode
-        assert.truthy 'process.pid', processes.app.rawProcess.pid
-        done()
-      catch testError
-        done(testError)
+    it 'has rawProcess.pid', ->
+      assert.truthy 'process.rawProcess.pid', @proc.rawProcess.pid
+
+    it 'has baseUrl', ->
+      assert.equal 'process.baseUrl', 'http://127.0.0.1:9903', @proc.baseUrl
+
+    it 'has port', ->
+      assert.equal 'process.port', 9903, @proc.port
+
+    it 'has logPath', ->
+      assert.match 'process.logPath', /test\/log\/start-proc\.log$/, @proc.logPath
+
+    it 'has logHandle', ->
+      assert.equal 'process.logHandle', 'number', typeof @proc.logHandle
+
+    it 'has launchCommand', ->
+      assert.equal 'process.launchCommand', 'node', @proc.launchCommand
+
+    it 'has launchArguments', ->
+      assert.deepEqual 'process.launchArguments', ['test/apps/service.js', '9903'], @proc.launchArguments
+
+    it 'has workingDirectory', ->
+      assert.equal 'process.workingDirectory', 'string', typeof @proc.workingDirectory

   it 'allows custom verification', (done) ->
     forceError = new Error 'force failure'


adding failing test

adding failing test

--- test/unit/entry.spec.coffee
+++ test/unit/entry.spec.coffee
@@ -6,3 +6,6 @@

   it 'should pass again', () ->
     [1,2,3].should.match([1,2,3])
+
+  it 'should fail', () ->
+    true.should.equal(false)


Quite PSCI properly

Quite PSCI properly

--- lib/psci.coffee
+++ lib/psci.coffee
@@ -10,6 +10,7 @@
     @proc.stdin.write text

   kill: ->
+    @proc.stdin.write ":q\n"
     @killed = true
     @proc.stdin.end()
     @proc.kill()
@@ -56,7 +57,6 @@
     @proc = null

   resetPsciCommand: =>
-    @killProc()
     if @editor
       @editor.buffer.reload()
     @startPsci()
@@ -66,6 +66,7 @@
     @startPsci()

   startPsci: =>
+    @killProc()
     @proc = new PsciProcess()
     @proc.start @addText


fix the hoffpath

fix the hoffpath

--- lib/util.coffee
+++ lib/util.coffee
@@ -1,5 +1,9 @@
 Promise = require('promise')
 {exec, spawn} = require('child_process')
+fs = require('fs')
+path = require ('path')
+os = require ('os')
+

 cmd = (c) ->
     return new Promise((fulfil, reject) ->
@@ -10,6 +14,20 @@
             fulfil(stdout)
         )
     )
+
+findTheHoff = (startPath, filter, callback) ->
+    files=fs.readdirSync(startPath)
+    for i in [0...files.length]
+        filename=path.join(startPath,files[i])
+        stat = fs.lstatSync(filename)
+        if stat.isDirectory()
+            try findTheHoff(filename,filter, callback)
+            catch e
+            finally continue
+        else if path.parse(filename).base == filter
+            console.log filename
+            return callback(filename)
+
 spawnHoffServer = (command, args) ->
     resolved = false
     return new Promise((fulfil, reject) ->
@@ -18,7 +36,18 @@
         if hoffpath == 'pghoffserver'
             s = spawn(hoffpath, [''], { env: process.env, detached: true })
         else
-            s = spawn('python', [ hoffpath ], { env: process.env, detached: true })
+            if fs.existsSync(path.join hoffpath, 'pghoffserver.py')
+                atom.config.set('pg-hoff.hoffServerPath', path.parse(hoffpath).dir)
+                hoffpath = path.parse(hoffpath).dir
+            else if hoffpath.indexOf('pghoffserver.py') != -1 and fs.existsSync(hoffpath)
+                atom.config.set('pg-hoff.hoffServerPath', path.parse(path.join hoffpath, '../').dir)
+                hoffpath = path.parse(path.join hoffpath, '../').dir
+            else if not fs.existsSync(path.join hoffpath, 'pghoffserver', 'pghoffserver.py')
+                 atom.notifications.addWarning('Pg-Hoffserver not found, searching...')
+                 findTheHoff(os.homedir(), 'pghoffserver.py')
+                 atom.notifications.addInfo('found the hoff')
+
+            s = spawn('python', [ (path.join hoffpath, 'pghoffserver', 'pghoffserver.py') ], { env: process.env, detached: true })

         s.stdout.on 'data', (data)      -> fulfil(data.toString()) if not resolved
         s.stderr.on 'data', (data)      -> fulfil('stderr ' + data.toString()) if not resolved


fully-react: get rid of support being at very top.

--- src/smc-webapp/top.cjsx
+++ src/smc-webapp/top.cjsx
@@ -28,8 +28,6 @@
                 <Tab eventKey={'about'} title={"About"}>
                     <HelpPage />
                 </Tab>
-                <Tab eventKey={'support'} title={"Support"}>
-                </Tab>
                 <Tab eventKey={'network'} title={"Network"}>
                 </Tab>
             </Tabs>


use hash as part of chunk filename

use hash as part of chunk filename

--- configs/webpack/base.coffee
+++ configs/webpack/base.coffee
@@ -57,6 +57,7 @@
     filename: '[name].js'
     # path: defined in project/environment specific
     # publicPath: defined in project/environment specific
+    chunkFilename: "[name]-chunk-[hash].js"
   module:
     noParse: [
       /\/sinon\.js/


fix what happens on remove

fix what happens on remove

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2887,6 +2887,8 @@

         remove: =>
             @removed_self = true
+            if @query_result.is_feed
+                @query_result.ended = true
             super()

         max_datum_threshold: 1000
@@ -3499,10 +3501,6 @@
             $(window).on('scroll', @scroll_handler)
             @handle_scroll()

-        remove: =>
-            $(window).off('scroll', @scroll_handler)
-            super()
-
         handle_scroll: =>
             scroll = $(window).scrollTop()
             pos = @$('.results_header').offset()?.top + 2
@@ -3712,8 +3710,11 @@
                 @$('.more_results_paragraph').html @cursor_timed_out_template()

         remove: =>
-            $(window).unbind 'scroll'
+            @view_object?.remove()
+            if @set_scrollbar_scroll_handler?
+                $(window).unbind 'scroll', @set_scrollbar_scroll_handler
             $(window).unbind 'resize'
+            $(window).off('scroll', @scroll_handler)
             super()

         handle_mouseup: (event) =>


Fix behavior of click when some text is selected

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -359,7 +359,8 @@
                     @last_action_is_paste = true
                     @num_released_keys = 0 # We want to know when the user release Ctrl AND V
                     @hide_suggestion_and_description()
-                return false
+                if event?.type isnt 'mouseup'
+                    return false

             if event?.which?
                 if event.which is 27 # ESC
@@ -500,6 +501,7 @@
                 @show_description result.description
             else
                 @hide_suggestion_and_description()
+            return true

         # Extract information from the current query
         # Regex used


Pad the frames correctly

Pad the frames correctly

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -60,18 +60,19 @@
     GD.openPng path.resolve(BG_BASE, "b29.png"), (err, bg) ->
       return cb(err, bg) if err
       # TODO: Composite `panels` onto `bg` correctly
+      totalPadding = 12
       left = 0
-      top = 6
+      top = totalPadding / 2
       for panel in panels
         do (panel) ->
           panelDims = [panel.width, panel.height]
           panel.copyResampled bg,
-            (left += 6), top, # dst
-            0, 0,             # src
+            (left += (totalPadding / 2)), top, # dst
+            0, 0,                              # src
             panelDims..., panelDims... # We keep the same size

-          # `left` has to grow by the width of the panel, also
-          left += panel.width
+          left += panel.width # Panel width
+          left += (totalPadding / 2)
       cb(false, bg)

 # Turn a set of 6 `lines` into 3 panels


Post new strips to THE :cloud:

Post new strips to THE :cloud:

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -15,6 +15,11 @@
 AYP_AWS_SECRET = process.env.AYP_AWS_SECRET
 AYP_AWS_BUCKET = process.env.AYP_AWS_BUCKET

+# AYP The website hooks
+AYP_SITE = "http://ayp.wtf.cat/"
+AYP_ENDPOINT = "#{AYP_SITE}new/"
+AYP_SECRET = process.env.AYP_SECRET
+
 # The size of single panel
 AYP_PANEL_WIDTH   = 348
 AYP_PANEL_HEIGHT  = 348
@@ -103,7 +108,8 @@
         return msg.reply "SOMETHING TERRIBLE HAPPENED: #{err}" if err

         # Save locally, upload, cleanup
-        name = "ayp-#{Date.now()}.jpg"
+        now = Date.now()
+        name = "ayp-#{now}.jpg"
         outPath = path.resolve("/tmp", name)
         image.saveJpeg outPath, 95, (err) ->
           return console.error "Failed to write result:", err if err
@@ -120,7 +126,16 @@
               body: data
             s3.put name, info, (err) ->
               return msg.reply "Woooops! Failed to upload: #{err}" if err
-              msg.reply "I made a thing: http://s3.amazonaws.com/#{AYP_AWS_BUCKET}/#{name}"
+              strip_url = "http://s3.amazonaws.com/#{AYP_AWS_BUCKET}/#{name}"
+              msg.reply "I made a thing: #{strip_url}"
+
+              # Now let's tell `ayp.wtf.cat` about our great work here
+              return msg.reply "I'd update the site, but I don't know the secret :(" unless AYP_SECRET
+              robot.http(AYP_ENDPOINT).
+                header('Content-Type', 'application/json').
+                post JSON.stringify(url: strip_url, time: now, secret: AYP_SECRET), (err, res, body) ->
+                  return msg.reply "Bad news. I was fed shit when I tried to update the site: #{err}" if err
+                  msg.reply "GOOD NEWS EVERYONE: #{AYP_SITE} got some fresh data"

 # This wraps up everything that builds the image strips of the comic
 #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -46,7 +46,7 @@

   # TODO: Move this into a more core module
   sendOrApply: (fn, args...) ->
-    debugg
+    debugger
     if typeof fn is "function"
       fn.apply(self, args)
     else


Tests para el formulario de guardar.

Tests para el formulario de guardar.

Comprobamos que se comporta correctamente en caso de éxito.

--- spec/javascripts/autosave_form_spec.coffee
+++ spec/javascripts/autosave_form_spec.coffee
@@ -1,5 +1,7 @@
 require 'jquery.form'
 require 'jquery.autosave_form'
+require 'jquery.effects.core'
+require 'jquery.effects.highlight'

 describe 'Formulario de guardado', ->
   beforeEach ->
@@ -20,5 +22,21 @@
   it 'llama a setInterval', ->
     expect(window.setInterval).toHaveBeenCalled()

+  describe 'una vez recibida la respuesta', ->
+    request = null
+    beforeEach ->
+      request = mostRecentAjaxRequest()
+      request.response
+        status: 200
+        responseText:
+          '
Actualizado' +
+          '<time>1998-07-01</time>
'
+
+    it 'actualiza con la respuesta', ->
+      expect($('#actualizado')).toHaveHtml request.responseText
+
+    it 'actualiza el valor del input borrador', ->
+      expect($('input[name="post[borrador_id]"]')).toHaveValue 35
+
   afterEach ->
     unrequire 'autosave_form'


reflect-to-spec: function text-object now ignore arrow function

--- spec/operator-transform-string-spec.coffee
+++ spec/operator-transform-string-spec.coffee
@@ -762,7 +762,7 @@
           runs ->
             set
               textC: """
-                hello = () => {
+                function hello() {
                   if true {
                   |  console.log('hello');
                   }
@@ -773,7 +773,7 @@
         it "adjustIndentation surrounded text ", ->
           ensure 'y s i f {',
             textC: """
-              hello = () => {
+              function hello() {
               |  {
                   if true {
                     console.log('hello');


Should be able to send click event with one method.

--- lib/zombie/browser.coffee
+++ lib/zombie/browser.coffee
@@ -312,7 +312,11 @@
     event = @document.createEvent(eventType)
     event.initEvent(eventName, true, true)
     target.dispatchEvent(event)
-    return @wait(callback)
+    return this
+
+  click: (selector)->
+    @fire(selector, "click")
+    return this

   # Dispatch asynchronously.  Returns true if preventDefault was set.
   dispatchEvent: (selector, event)->
@@ -563,7 +567,8 @@
   clickLink: (selector, callback)->
     unless link = @link(selector)
       throw new Error("No link matching '#{selector}'")
-    return @fire(link, "click", callback)
+    @fire(link, "click")
+    return @wait(callback)

   # Return the history object.
   @prototype.__defineGetter__ "history", ->


bug fixed: another change "input" for "disabled" when checking for disable attribute

--- lib/zombie/browser.coffee
+++ lib/zombie/browser.coffee
@@ -511,7 +511,7 @@
   _setCheckbox: (selector, value, callback)->
     field = @field(selector)
     if field && field.tagName == "INPUT" && field.type == "checkbox"
-      if field.getAttribute("input")
+      if field.getAttribute("disabled")
         throw new Error("This INPUT field is disabled")
       if field.getAttribute("readonly")
         throw new Error("This INPUT field is readonly")


fixed fully associative cache

fixed fully associative cache

--- js/src/cache.coffee
+++ js/src/cache.coffee
@@ -41,10 +41,10 @@
 		@cache_size = cache_size
 		@memory_size = memory_size
 		@index = if !@ways then 1 else @cache_size / (@ways * @line_size)
+		@index_bit_size = if !@ways then 0 else Math.log(@index) / Math.LN2
 		@ways = if !@ways then @cache_size / @line_size else @ways
 		@address_bit_size = Math.log(@memory_size) / Math.LN2
 		@offset_bit_size = Math.log(@line_size) / Math.LN2
-		@index_bit_size = Math.log(@index) / Math.LN2
 		@tag_bit_size = @address_bit_size - @index_bit_size - @offset_bit_size
 		i = 0
 		j = 0
@@ -54,9 +54,11 @@
 			for j in [0..@ways-1] by 1
 				@memory[i][j] = null
 	read: (address) ->
-		index = address >>> @offset_bit_size
-		index = index << (32 - @index_bit_size)
-		index = index >>> (32 - @index_bit_size)
+		index = 0
+		if @index_bit_size != 0
+			index = address >>> @offset_bit_size
+			index = index << (32 - @index_bit_size)
+			index = index >>> (32 - @index_bit_size)
 		tag = address >>> (@index_bit_size + @offset_bit_size)
 		i = 0
 		empty = null


try to remove height for iOS

try to remove height for iOS

--- src/views/layout.coffee
+++ src/views/layout.coffee
@@ -7,7 +7,7 @@
     meta(name: 'description', content: @description) if @description?
     link(rel: 'canonical', href: @canonical) if @canonical?

-    meta(name:'viewport', content:'width=device-width, height=device-height, initial-scale=1, user-scalable=no')
+    meta(name:'viewport', content:'width=device-width, initial-scale=1, user-scalable=no')
     meta(name:'apple-mobile-web-app-capable', content:'yes')

     if @dev


truthful test is passing.

truthful test is passing.

--- tests/unit/utilities.coffee
+++ tests/unit/utilities.coffee
@@ -195,19 +195,17 @@
   # the TRUTHFUL function takes an array with true and false values and returns
   # only the truthful ones.

-  # it 'TRUTHFUL method should work', ->
-  #
-  #   casper.then ->
-  #
-  #     theTrueOnes = @.evaluate ->
-  #
-  #       myArray[true,false,true,false,true]
-  #       return junction(myArray).truthful()
-  #
-  #     @.echo theTrueOnes
-  #
-  #     theTrueOne.length.should.equal(3)
-  #
-  #   return
+  it 'TRUTHFUL method should work', ->
+
+    casper.then ->
+
+      theTrueOnes = @.evaluate ->
+
+        myArray = [true,false,true,false,true]
+        return junction.truthful(myArray)
+
+      theTrueOnes.length.should.equal(3)
+
+    return

   return


debug: change receiver in alarm manager

debug: change receiver in alarm manager

--- lib/alarm_manager.coffee
+++ lib/alarm_manager.coffee
@@ -44,7 +44,7 @@
                             resource: resource
                     else
                         data =
-                            from: "Cozy Agenda"
+                            from: "Cozy Agenda <no-reply@cozycloud.cc>"
                             subject: "[Cozy-Agenda] Reminder"
                             content: "Reminder: #{alarm.description}"


increase express body size limit

increase express body size limit

--- storage/lib/rest.coffee
+++ storage/lib/rest.coffee
@@ -11,7 +11,7 @@
     app.set 'x-powered-by', false
     app.set 'etag', false
     app.use compression()
-    app.use bodyParser.json()
+    app.use bodyParser.json(limit: '50mb')

     app.use Promise.coroutine (req, res, next) ->
         if req.method in ['GET', 'HEAD', 'OPTIONS']


Add handle codes when program exited.

Add handle codes when program exited.

--- lib/debugger-view.coffee
+++ lib/debugger-view.coffee
@@ -62,6 +62,14 @@
   getEditor: (fullpath) ->
     return @cachedEditors[fullpath]

+  goExitedStatus: ->
+    @continueButton.addClass('disabled')
+    @interruptButton.addClass('disabled')
+    @stepButton.addClass('disabled')
+    @nextButton.addClass('disabled')
+    @removeClass('running')
+    @addClass('stopped')
+
   goStoppedStatus: ->
     @continueButton.removeClass('disabled')
     @interruptButton.addClass('disabled')
@@ -214,15 +222,17 @@
     @GDB.onExecAsyncStopped (result) =>
       @goStoppedStatus()

-      frame = result.frame
-      fullpath = path.resolve(frame.fullname)
-      line = Number(frame.line)-1
-
-      if @exists(fullpath)
-        atom.workspace.open(fullpath, {debugging: true, fullpath: fullpath, startline: line}).done (editor) =>
-          @stopped = {marker: @markStoppedLine(editor, line), fullpath, line}
+      unless frame = result.frame
+        @goExitedStatus()
       else
-        @GDB.next (result) ->
+        fullpath = path.resolve(frame.fullname)
+        line = Number(frame.line)-1
+
+        if @exists(fullpath)
+          atom.workspace.open(fullpath, {debugging: true, fullpath: fullpath, startline: line}).done (editor) =>
+            @stopped = {marker: @markStoppedLine(editor, line), fullpath, line}
+        else
+          @GDB.next (result) ->

   # Tear down any state and detach
   destroy: ->


pass anchor to transform()

pass anchor to transform()

--- oridomi.coffee
+++ oridomi.coffee
@@ -923,7 +923,7 @@
       deg *= -1 if options.stairs

       # Set the CSS transformation.
-      panel.style[css.transform] = @_transform deg, options.fracture
+      panel.style[css.transform] = @_transform deg, anchor, options.fracture
       # Apply shaders.
       if @shading and !(i is 0 and options.sticky) and Math.abs(deg) isnt 180
         @_setShader i, anchor, deg
@@ -936,14 +936,14 @@
     angle /=  @_getPanelType anchor

     for panel, i in @panels[anchor]
-      panel.style[css.transform] = @_transform angle
+      panel.style[css.transform] = @_transform angle, anchor
       @_setShader i, anchor, 0 if @shading

   # `ramp` lifts up all panels after the first one.
   ramp: prep (angle, anchor, options) ->
     # Rotate the second panel for the lift up.
-    @panels[anchor][1].style[css.transform] = @_transform angle
+    @panels[anchor][1].style[css.transform] = @_transform angle, anchor

     # For all but the first two panels, set the angle to 0.
     for panel, i in @panels[anchor]


Simplification

Simplification

--- lib/memory_cursor.coffee
+++ lib/memory_cursor.coffee
@@ -68,7 +68,7 @@
     else if @_cursor.$white_list
       json = _.map(json, (item) => _.pick(item, @_cursor.$white_list))

-    if @_cursor.$page or _.isString(@_cursor.$page)
+    if @_cursor.$page or @_cursor.$page is ''
       json =
         offset: @_cursor.$offset
         total_rows: @_count(keys)


remove timestamp

remove timestamp

--- lib/models/device.coffee
+++ lib/models/device.coffee
@@ -134,7 +134,6 @@
     @attributes ?= {}
     @attributes = _.extend {}, @attributes, @sanitize(attributes)
     @attributes.online = !!@attributes.online if @attributes.online?
-    @attributes.timestamp = new Date()

   validate: =>
     if @attributes.uuid? && @uuid != @attributes.uuid


Correct typo in stringscore function name

--- lib/package-panel.coffee
+++ lib/package-panel.coffee
@@ -143,7 +143,7 @@
       for packageView in children
         name = packageView.getAttribute('name')
         continue unless name
-        if /^\s*$/.test(filterString) or stringScore(name, filterString)
+        if /^\s*$/.test(filterString) or stringscore(name, filterString)
           $(packageView).show()
         else
           $(packageView).hide()


use `fs.moveSync` to publish draft

use `fs.moveSync` to publish draft

fix #62 when the destination path does not exists. `fs.moveSync` will
try to create the destination path first.

--- lib/publish-draft.coffee
+++ lib/publish-draft.coffee
@@ -33,10 +33,7 @@

   moveDraft: ->
     try
-      if fs.existsSync(@postPath)
-        alert("Error:\nPost #{@postPath} already exists!")
-      else
-        fs.renameSync(@draftPath, @postPath)
+      fs.moveSync(@draftPath, @postPath)
     catch error
       alert("Error:\n#{error.message}")

@@ -47,15 +44,18 @@
     localDir = config.get("siteLocalDir")
     postsDir = config.get("sitePostsDir")
     postsDir = utils.dirTemplate(postsDir)
-    return path.join(localDir, postsDir)
+
+    path.join(localDir, postsDir)

   getPostName: ->
     template = config.get("newPostFileName")
+
     date = utils.getDate()
     info =
       title: @getPostTitle()
       extension: @getPostExtension()
-    return utils.template(template, $.extend(info, date))
+
+    utils.template(template, $.extend(info, date))

   getPostTitle: ->
     if config.get("publishRenameBasedOnTitle")
@@ -64,6 +64,5 @@
       utils.getTitleSlug(@draftPath)

   getPostExtension: ->
-    if config.get("publishKeepFileExtname")
-      extname = path.extname(@draftPath)
-    return extname || config.get("fileExtension")
+    extname = path.extname(@draftPath) if config.get("publishKeepFileExtname")
+    extname || config.get("fileExtension")


fixing throwing an error string in the Rewriter, when it should have been an Error object

--- rewriter.coffee
+++ rewriter.coffee
@@ -199,7 +199,7 @@
       levels[open] ||= 0
       levels[open] += 1 if token[0] is open
       levels[open] -= 1 if token[0] is close
-      throw "too many " + token[1] if levels[open] < 0
+      throw new Error("too many " + token[1]) if levels[open] < 0
     return 1
   unclosed: key for key, value of levels when value > 0
   throw new Error("unclosed " + unclosed[0]) if unclosed.length


Fixes #2916 -- implicit calls with preceding herecomment

--- rewriter.coffee
+++ rewriter.coffee
@@ -251,7 +251,7 @@
       # which is probably always unintended.
       # Furthermore don't allow this in literal arrays, as
       # that creates grammatical ambiguities.
-      if @matchTags(i, IMPLICIT_FUNC, 'INDENT', null, ':') and
+      if tag in IMPLICIT_FUNC and @matchTags(i + 1, 'INDENT', null, ':') and
          not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH',
           'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])
         startImplicitCall i + 1


added config obj instead of passing options around

--- lib/runnable/sync.coffee
+++ lib/runnable/sync.coffee
@@ -8,19 +8,21 @@
 op = (require '../util/optionsParser')
 uploader = (require "../util/upload")

+config = {}
+
 die = ()-> process.kill('SIGTERM')

-watch = (local, remote, token, ignored)->
-    console.log "Watching", color(local, "white_bg+black") ,"for changes.."
+watch = (token, conf = config)->
+    console.log "Watching", color(conf.local, "white_bg+black") ,"for changes.."
     console.log ""

-    watcher = chokidar.watch local,
+    watcher = chokidar.watch conf.local,
         ignored: (item) ->
             ignored_extensions = /\.(less|git|coffee|tmpl|DS_Store)/i
             ignored_directories = /node_modules/i
             ignored_files = /grunt.js|cakefile/i

-            ignore = item.match(ignored_extensions) or item.match(ignored_directories) or item.match(ignored_files) or (ignored and items.match(ignored))
+            ignore = item.match(ignored_extensions) or item.match(ignored_directories) or item.match(ignored_files) or (conf.ignored and items.match(conf.ignored))
             # if ignore then console.log item, ignore
             return ignore

@@ -28,8 +30,8 @@
         persistent: true

     upload = (localPath, stats) ->
-        serverPath = localPath.replace(local, '')
-        simPath = "#{remote}/#{serverPath}"
+        serverPath = localPath.replace(conf.local, '')
+        simPath = "#{conf.remote}/#{serverPath}"

         time = process.hrtime()
         uploader.uploadFileAPI localPath, simPath, token, (err, stdout, stderr) ->
@@ -44,6 +46,7 @@
                 authenicateUser (newtoken)->
                     token = newtoken
                     upload localPath, stats
+
             else if response.message
                 console.error color(response.status_code + ":", "red"), response.message
             else
@@ -54,16 +57,14 @@
     watcher.on "add", upload
     watcher.close()

-authenicateUser = (user, pass, remote, callback)->
+authenicateUser = (callback, conf = config)->
     ##Authenticating to make sure wherever you're writing to exists
-    authenticate user, pass, remote, (response)->
+    authenticate conf.user, conf.pass, conf.remote, (response)->
         if !response.token
             console.error color(response.message, "red+bold")
             die()
         else
-            callback(response.token)
-
-# authenicateUser watch
+            callback(response.token, conf)

 exports.help = "Watch dir for changes and upload to sim"
@@ -89,7 +90,10 @@
     #Assume current author by default
     remote = "#{userName}/#{remote}"  if remote.indexOf('/') is -1

-    console.log ""
+    config =
+        local: local
+        remote: remote
+        user: userName
+        pass: pass

-    authenicateUser userName, pass, remote, (token)->
-        watch(local, remote, token)
+    authenicateUser watch


Join user in room via mention

Join user in room via mention

--- packages/rocketchat-lib/server/sendMessage.coffee
+++ packages/rocketchat-lib/server/sendMessage.coffee
@@ -99,6 +99,14 @@
 				mentionIds.push mention._id

 			if mentionIds.length > 0
+				usersOfMention = Meteor.users.find({_id: {$in: mentionIds}}, {fields: {_id: 1}}).fetch()
+
+				if mentionIds.indexOf('all') is -1
+					for usersOfMentionItem in usersOfMention
+						if room.usernames.indexOf(usersOfMentionItem.username) is -1
+							Meteor.runAsUser usersOfMentionItem._id, ->
+								Meteor.call 'joinRoom', room._id
+
 				###
 				Update all other subscriptions of mentioned users to alert their owners and incrementing
 				the unread counter for mentions and direct messages
@@ -141,7 +149,6 @@
 						query._id =
 							$in: mentionIds

-					usersOfMention = Meteor.users.find(query, {fields: {_id: 1}}).fetch()
 					usersOfMentionIds = _.pluck(usersOfMention, '_id');
 					if usersOfMentionIds.length > 0
 						Push.send


Better ui for adding urls

Better ui for adding urls

--- app/assets/javascripts/artifacts.coffee
+++ app/assets/javascripts/artifacts.coffee
@@ -43,10 +43,15 @@

   if $('#artifact_mirrors')[0]
     $('#artifact_mirrors')?.select2
-      tags: true
+      minimumResultsForSearch: Infinity
+      tags: []
       width: 'resolve'
       tokenSeparators: [',', ';']

       formatSelection: (object, container) ->
-        object.text
+        # Assume a url has been entered and stick a http:// in front if it's not there
+        if not object.text.match(/^http[s]?\:\/\//)
+          'http://' + object.text
+        else
+          object.text


Extract function to ensure selection doesn't unintentionally change

--- js-library/app/js/jail_iframe/util/highlight.coffee
+++ js-library/app/js/jail_iframe/util/highlight.coffee
@@ -7,6 +7,25 @@
   finally
     # Scroll back to previous location
     window.scroll scrollLeft, scrollTop
+
+ensureSelectionSaved = (func) ->
+  oldRanges = []
+  try
+    # If the user currently has selected some text, store the selection
+    selection = window.document.getSelection()
+    oldRanges =
+      for i in [0...selection.rangeCount]
+        selection.getRangeAt(i)
+    func()
+  finally
+    # Reset the selection
+    window.document.getSelection().removeAllRanges()
+
+    # Restore previous selection
+    for selectedRange in oldRanges
+      window.document.getSelection().addRange selectedRange
+
+
 # Chrome, Firefox, Safari
 searchWithWindowFind = (searchString) ->
   # Trim


Small change to allow all players on leaderboard.

--- tasks/shortStats.coffee
+++ tasks/shortStats.coffee
@@ -9,7 +9,7 @@
 updateEloLeaderboards = ->
   startTime = new Date()
   Data.find({}, 'playerName profile.elo').sort('-profile.elo').exec((err, docs)->
-    docslimited = JSON.parse(JSON.stringify(docs.splice(0, 50)))
+    # docslimited = JSON.parse(JSON.stringify(docs.splice(0, 50)))
     doc = {
       name: 'eloleaderboard'
       data: docslimited


Serenade: trim on edit (#818)

Serenade: trim on edit (#818)

--- labs/architecture-examples/serenadejs/js/app.coffee
+++ labs/architecture-examples/serenadejs/js/app.coffee
@@ -56,6 +56,7 @@

 	edited: ->
 		if @todo.title.trim()
+			@todo.title = @todo.title.trim()
 			@todo.edit = false if @todo.edit
 		else
 			@todo.remove()


Increase level cap for distinct words

Increase level cap for distinct words

--- app/assets/javascripts/papers.js.coffee
+++ app/assets/javascripts/papers.js.coffee
@@ -6,15 +6,17 @@
 	"oxford": 0,
 	"fancy": 0,
 	"awl": 0,
-	"awl-details": 0
+	"awl-details": 0,
+	"word-cloud": 0
 }
+
+update_interval = 5000;

 totalWordsLevels = [50, 100, 200, 300, 500, 1000, 2000, 3000, 5000];
 totalWordsIdentifier = "num_words";

-distinctLevels = [10, 50, 100, 150, 200, 300, 400, 800, 1000];
+distinctLevels = [50, 100, 200, 300, 500, 800, 1000, 1250, 1500];
 distinctIdentifier = "different_words";
-

 @updateSketches = (id) ->
 	$.ajax id.toString(),
@@ -71,4 +73,4 @@

 		setInterval ->
 			updateSketches(paper_id)
-		, 5000
+		, update_interval


Fix bug preventing saving of boot and bootstrap timestamps

--- lib/update-device.coffee
+++ lib/update-device.coffee
@@ -53,8 +53,8 @@
   processBatch = (updatesBatch, cb) ->
     if updatesBatch.informEvents?
       update['$set']['_lastInform'] = now
-      update['$set']['_lastBoot'] if '1 BOOT' in updatesBatch.informEvents
-      update['$set']['_lastBootstrap'] if '0 BOOTSTRAP' in updatesBatch.informEvents
+      update['$set']['_lastBoot'] = now if '1 BOOT' in updatesBatch.informEvents
+      update['$set']['_lastBootstrap'] = now if '0 BOOTSTRAP' in updatesBatch.informEvents

     if updatesBatch.parameterValues?
       for p in updatesBatch.parameterValues


fixing countdown

fixing countdown

--- frontend/src/scripts/coffee/scoreboard.coffee
+++ frontend/src/scripts/coffee/scoreboard.coffee
@@ -106,20 +106,33 @@
     return "0" + str
   else return str

+startCountdown = ->
+  _second = 1000;
+  _minute = _second * 60;
+  _hour = _minute * 60;
+  _day = _hour * 24;
+
+  now = new Date()
+  andThen = new Date("Nov 25 2013 20:00:00 CST")
+  diff = new Date(andThen - now)
+
+  hours = Math.floor((diff % _day) / _hour);
+  minutes = Math.floor((diff % _hour) / _minute);
+  seconds = Math.floor((diff % _minute) / _second);
+
+  hours = padZero(hours)
+  minutes = padZero(minutes)
+  seconds = padZero(seconds)
+  $("#Counter").countdown({
+    image: "../../images/digits.png",
+    startTime: "#{hours}:#{minutes}:#{seconds}"
+  });
+
 scoreboardViewModel = () ->
   model = scoreboardModel()
   model.load()

-  now = new Date()
-  andThen = new Date("Nov 25 2013 18:00:00 CST")
-  diff = new Date(andThen - now)
-  hours = padZero(diff.getHours() + "")
-  minutes = padZero(diff.getMinutes() + "")
-  seconds = padZero(diff.getSeconds() + "")
-  $("#Counter").countdown({
-    image: "../../images/digits.png",
-    startTime: "#{hours}:#{minutes}:#{seconds}"
-  });
+  startCountdown()

   # Setup a timer every 5 seconds
   setInterval model.reload, 5000


Update nested_form_fields.js.coffee

Update nested_form_fields.js.coffee

--- lib/assets/javascripts/nested_form_fields.js.coffee
+++ lib/assets/javascripts/nested_form_fields.js.coffee
@@ -24,7 +24,7 @@
       $child = $(this)
       $child.replaceWith($("<script id='#{$child.attr('id')}' type='text/html' />").html($child.html()))

-    if (target?)
+    if target?
       $('#' + target).append($parsed_template)
     else
       $template.before( $parsed_template )


turn off hash locations

turn off hash locations

--- app/assets/javascripts/workspace.coffee
+++ app/assets/javascripts/workspace.coffee
@@ -18,8 +18,7 @@
         container: 'map',
         style: @style,
         center: if center? then center else [-147.8, 64.85],
-        zoom: if zoom? then zoom else 3,
-        hash: true
+        zoom: if zoom? then zoom else 3
     });

     @map.on 'load', @onLoad


Formatting

Formatting

--- lib/app.coffee
+++ lib/app.coffee
@@ -1,6 +1,7 @@
 {Base} = require "#{__dirname}/base"
 {Wrappers} = require "#{__dirname}/helpers/wrappers"
 {BeanBuilder} = require "#{__dirname}/helpers/bean_builder"
+

 class App extends Base


Remove obsolete comment

Remove obsolete comment

--- lib/app.coffee
+++ lib/app.coffee
@@ -603,8 +603,6 @@

 		_.each(pluginCommands, capitano.command)

-# In case of an error, we log it to stderr, but we don't
-# allow plugins to crash the main process.
 catch error
 	errors.handle(error)


handle bare requests to /; fixes #1

handle bare requests to /; fixes #1

--- lib/app.coffee
+++ lib/app.coffee
@@ -10,6 +10,9 @@

   @app.resolver = new Resolver()

+  @app.get '/', (req, res, next) ->
+    res.send @app.resolver.latest_stable
+
   @app.get '/:range', (req, res, next) ->
     res.send @app.resolver.satisfy(req.params.range)


Don't update the placeholders if parsed min/max values are undefined

--- src/coffee/cilantro/ui/field/controls/range.coffee
+++ src/coffee/cilantro/ui/field/controls/range.coffee
@@ -97,9 +97,9 @@
         # upper and lower bounds are currently empty.
         updateBounds: ->
             if @isClosed? and not @isClosed
-                if @ui.lowerBound.val() == ""
+                if @minLowerBound?
                     @setLowerBoundPlaceholder(@minLowerBound)
-                if @ui.upperBound.val() == ""
+                if @maxUpperBound?
                     @setUpperBoundPlaceholder(@maxUpperBound)

         getField: ->


Add in polling of database

Add in polling of database

--- src/main/assets/javascripts/simple.coffee
+++ src/main/assets/javascripts/simple.coffee
@@ -69,6 +69,12 @@

   addFeedItem(time_diff, map, v)

+downloadResourceData = (map) ->
+  callback = (response) ->
+    response.forEach (v) ->
+      handleGeoJsonPacket(map, v)
+  $.get '/fgis/api/resource', {}, callback, 'json'
+
 initMap = (lat, long) ->
   map = L.map('map').setView([lat,long], 14)
   L.tileLayer('http://{s}.tile.cloudmade.com/aeb94991e883413e8262bd55def34111/997/256/{z}/{x}/{y}.png',{
@@ -76,10 +82,9 @@
     maxZoom: 18
   }).addTo(map)

-  callback = (response) ->
-    handleGeoJsonPacket(map, response)
-  $.get '/fgis/api/resource/6', {}, callback, 'json'
-  #$.get '/fgis/api/resource/geojson/?type=truck,fire,tree', {}, callback, 'json'
+  downloadResourceData(map)
+  callback = -> downloadResourceData(map)
+  setInterval callback, 1000

 showMapAtDefault = ->
   initMap(-37.793566209439, 144.94111608134)


imagebank alphabatization

imagebank alphabatization

--- app/coffee/loginpanel.coffee
+++ app/coffee/loginpanel.coffee
@@ -32,9 +32,9 @@
     @$joinButton.click => @showSignup()

   showLogin: ->
+    @$signupPanel.hide()
     @$signupButton.hide()
     @$joinButton.show()
-    @$signupPanel.hide()

     @$loginButton.hide()
     @$loginPanel.show()
@@ -45,9 +45,13 @@
   showSignup: ->
     @$loginButton.show()
     @$signupButton.show()
+
     @$joinButton.hide()
     @$loginPanel.hide()

-    @$signupPanel.show()
+    @$signupPanel.slideDown()
+
+  showSeedCodeInput: ->
+    # hide seed code button show seed code input


adding tests

adding tests

--- test/coffee/feed.coffee
+++ test/coffee/feed.coffee
@@ -4,18 +4,48 @@
   ], (Swabcast, chai) ->
   #feeds = require("app","entities/feed")
   expect = chai.expect;
-  describe "Feeds", ->
-    describe "Feed Model", ->
-      it "expect default \"urlRoot\" property to equal \"/feeds\"", ->
+  describe "Feed Model", ->
+    describe "Creation", ->
+      it "expect \"/feed\" to have default values", ->
+        feed = new Swabcast.Entities.Feed()
+        expect(feed).to.be.ok
+        expect(feed.urlRoot).to.equal("feeds")
+        expect(feed.get("albumArt")).to.equal "default.jpg"
+        return

-        feeds = new Swabcast.Entities.Feed()
-        expect(feeds.urlRoot).to.equal("feeds")
+    describe "Attributes", ->
+      it "expect can set model Attributes", ->
+
+        feed = new Swabcast.Entities.Feed()
+        expect(feed).to.be.ok
+        expect(feed.urlRoot).to.equal("feeds")
         return

       it "expect \"albumArt\" property to have a default image of \"default.jpg\" ", ->
         feeds = new Swabcast.Entities.Feed()
+        expect(feeds).to.be.ok
         expect(feeds.get("albumArt")).to.equal "default.jpg"
         return
+
+    describe "Validation", ->
+      it "expect \'Invalid\' if the feed does not contain episodes", ->
+        feed = new Swabcast.Entities.Feed(
+          "subscriptionTitle": "Test Feed"
+          "albumArt": "test.jpg"
+          "summary": "This is a test feed"
+          "feedUrl": "http://test.com/test.xml"
+          "authors": "Danny Swaby"
+        )
+        feed.on "invalid", (model, error) ->
+          throw Error(model.get("subscriptionTitle") + " " + error)
+          return
+        fn = ->
+          feed.save episodes: ""
+          return
+
+        expect(fn).to.throw("Test Feed must contain episodes attribute")
+
+      return
     return

   return


adds missing title for explore public group page meta (#4398)

--- angular/core/components/explore_page/explore_page_controller.coffee
+++ angular/core/components/explore_page/explore_page_controller.coffee
@@ -1,5 +1,5 @@
 angular.module('loomioApp').controller 'ExplorePageController', (Records, $rootScope, $timeout, AppConfig, LoadingService) ->
-  $rootScope.$broadcast('currentComponent', { page: 'explorePage'})
+  $rootScope.$broadcast('currentComponent', { titleKey: 'explore_page.header', page: 'explorePage'})

   @groupIds = []
   @resultsCount = 0


#162852547 max photos working on edit

#162852547 max photos working on edit

--- app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
@@ -230,14 +230,20 @@
     $(self).find('.photo-upload').attr('style','display:block;')

 $ ->
+  $('.file-upload').each( () ->
+    maxPhotos = $(this).find('#max-photos').attr("data-max-photos")
+    addedPhotos = $(this).find('.gallery-item').find("img").length
+    console.log(maxPhotos + ' ' + addedPhotos)
+    checkMaxPhotos(this, maxPhotos, addedPhotos)
+  )
+
   $('.file-upload').on 'click', (e) ->
     maxPhotos = $(this).find('#max-photos').attr("data-max-photos")
     $(this).bind 'cloudinarydone', (e) ->
       addedPhotos = $(this).find('.photo-preview').find("img").length
       checkMaxPhotos(this, maxPhotos, addedPhotos)

-
-  $('.file-upload').on 'click', '.remove-upload', (e) ->
+  $('.file-upload').on 'click', '.remove-upload, .delete-saved-file', (e) ->
     e.preventDefault()
     self = $(this).parents('.file-upload')
     $(self).find('.photo-upload').attr('style','display:block;')


Fix error.

Fix error.

--- js/data.coffee
+++ js/data.coffee
@@ -253,7 +253,7 @@
 Please may I ask Mr. Wang, are we or aren’t we meeting tomorrow morning at 10am?
 请问玩先生，我们是不是明天上午十点见？
 No (isn’t), we’re meeting tomorrow morning at 10:30am.
-不是，我们明天上午是点半见
+不是，我们明天上午十点半见
 Are we or aren’t we meeting in the library?
 我们是不是在图书馆见？
 Yes (is), we are meeting in the library.


deleted delete

deleted delete

--- src/teacup.coffee
+++ src/teacup.coffee
@@ -97,10 +97,10 @@
     # render explicitly ordered attributes first
     for name in @attrOrder when name of obj
       result += @renderAttr name, obj[name]
-      delete obj[name]

     # then unordered attrs
     for name, value of obj
+      continue if name in @attrOrder
       result += @renderAttr name, value

     return result


improve errors

improve errors

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -17,6 +17,16 @@
     cabbagePath = resolve.sync('cabbage', basedir: process.cwd())
     require(cabbagePath).cli()
   catch e
-    console.error("No `cabbage` module found. Are you sure you're running from a directory with cabbage installed?")
-    console.error(e.stack)
+    if e.message.indexOf("Cannot find module 'cabbage'") > -1
+      console.error """
+
+        No `cabbage` module found. Are you sure you're running from a project directory with cabbage installed?
+
+        To create a new project, run:
+
+          $ cabbage new <project_name>
+
+      """
+    else
+      console.error(e.stack)
     process.exit(1);


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -24,7 +24,8 @@
     y: 1

   other2 = GameObject
-    x
+    x: 10
+    y: 10

   equals o.closest([]), null


Manually place the render DOM element.

Manually place the render DOM element.

--- app/javascripts/image.coffee
+++ app/javascripts/image.coffee
@@ -1,4 +1,4 @@
-define ['angular', 'xtk', 'file'], (ng) ->
+define ['angular', 'xtk', 'file', 'dat'], (ng) ->
   image = ng.module 'qiprofile.image', ['qiprofile.file']

   image.factory 'Image', ['$rootScope', 'File', ($rootScope, File) ->
@@ -38,7 +38,7 @@
       state:
         loading: false

-      # The image binary content.
+      # The image file content.
       data: null

       # Transfers the image file content to the data property.
@@ -47,9 +47,10 @@
       #
       # @returns a promise which resolves when the image file
       #   read is completed
-      load: () ->
+      load: ->
         # Set the loading flag.
         @state.loading = true
+
         # Read the file into an ArrayBuffer. The Coffeescript fat
         # arrow (=>) binds the this variable to the image object
         # rather than the $http request.
@@ -58,43 +59,31 @@
           @state.loading = false
           # Set the data property to the file content.
           @data = data
-
-      # Builds an XTK renderer for this image.
-      createRenderer: ->
-        # The XTK renderer.
+
+      # Renders the image in the given parent element.
+      #
+      # @param element the Angular jQueryLite element
+      open: (element) ->
+        # The XTK renderer for this image.
         renderer = new X.renderer3D()
+        # The image is rendered within the given element.
+        renderer.container = element[0]
+        # Build the renderer.
         renderer.init()
-
         # The volume to render.
         volume = new X.volume()
-        # XTK seems to expect a .gz filename to have compressed
-        # data. However, Express and most servers uncompress
-        # content on the fly. Therefore, fool XTK into accepting
-        # the file content by stripping the .gz extension.
-        # TODO - is there a better approach?
-        if @filename[-3..] == '.gz'
-          volume.file = @filename[0..-3]
-        else
-          volume.file = @filename
-        # FIXME - XTK throws the following:
-        #   Uncaught RangeError: byte length of Uint16Array should be a multiple of 2
-        # This occurs whether or not the filedata property is set
-        # below.
-        # TODO - Why does it fail even when filedata is not set?
-        # TODO - Walk through the XTK loader code again to emulate their
-        # approach. What is the loader container object?
+        volume.file = @filename
         volume.filedata = @data
-        renderer.add volume
+        renderer.add(volume)

         # The rendering callback. This function is called after the
         # volume is initialized and prior to the first rendering.
         renderer.onShowtime = ->
-          # The volume display controls.
-          volumeCtls = new dat.GUI()
+          # The volume display controls. The element is manually
+          # placed later in this function.
+          gui = new dat.GUI(autoplace: false)
           # The controls interact with the volume.
           volumeCtls = gui.addFolder('Volume')
-          # The rendering control.
-          renderingCtl = volumeCtls.add(volume, 'volumeRendering')
           # The opacity control.
           opacityCtl = volumeCtls.add(volume, 'opacity', 0, 1).listen()
           # The threshold min and max range controls.
@@ -104,14 +93,17 @@
           sliceXCtl = volumeCtls.add(volume, 'indexX', 0, volume.range[0] - 1)
           sliceYCtl = volumeCtls.add(volume, 'indexY', 0, volume.range[1] - 1)
           sliceZCtl = volumeCtls.add(volume, 'indexZ', 0, volume.range[2] - 1)
+          # The control is placed after the image display.
+          ctlElt = ng.element(gui.domElement)
+          element.after(ctlElt)
           # Display the controls.
-          volumeCtls.open();
+          volumeCtls.open()

         # Adjust the camera position.
         renderer.camera.position = [120, 80, 160]

-        # Return the renderer.
-        renderer
+        # Render the image.
+        renderer.render()

     # Obtains image objects for the given ImageContainer. The image
     # object content is described in the create() function.


Follow NodeJS coding guide.

Follow NodeJS coding guide.

Update callback param, first should be error.

--- src/tumblr.coffee
+++ src/tumblr.coffee
@@ -65,7 +65,7 @@
   request = (url, fn = ->) ->
     xhr {url}, (error, request, body) ->
       body = JSON.parse body
-      throw new Error body.meta.msg if body.meta.status isnt 200
-      fn.call body, body.response
+      err  = body.meta.msg if body.meta.status isnt 200
+      fn.call body, err, body.response

 ).call(Tumblr.prototype)


moved partial to partials

moved partial to partials

--- app/scripts/controllers/district.coffee
+++ app/scripts/controllers/district.coffee
@@ -68,9 +68,9 @@
           "#{$scope.FIPS_to_state[d.id / 100 | 0]}-#{d.id % 100}"
         district.on("mouseover", () ->
           return tooltip.style("visibility", "visible").text(d3.select(this).text())
-        ).on("mousemove", () ->
+        ).on("mousemove", () ->
           return tooltip.style("top", (event.pageY-27)+"px").style("left", (event.pageX+"px"))
-        ).on("mouseout", () ->
+        ).on("mouseout", () ->
           return tooltip.style("visibility", "hidden")
         ).on("click", () ->
           if !$scope.district_reps.length and $scope.zoomed
@@ -106,7 +106,7 @@
         .style("opacity", 1e-6)
         .style("z-index", "15")

-      $scope.makeMapGradients()
+      $scope.makeMapGradients()
       queue().defer(d3.json, "data/us.json").defer(d3.json, "data/us-congress-113.json").await ready

     $scope.makeMapGradients = () ->
@@ -162,7 +162,7 @@
       $scope.usMap.transition().duration(750).attr("transform", "translate(0,0)scale(1)").style "stroke-width", 1 + "px"

     $scope.showDistrictDialog = () ->
-      $('#map_dialog').html($compile("<sub-view template='partial/district_reps'></sub-view>")($scope))
+      $('#map_dialog').html($compile("<sub-view template='partials/district_reps'></sub-view>")($scope))
       d3.select('#map_dialog')
         .transition()
         .duration(750)
@@ -174,7 +174,7 @@
         console.log $routeParams.bioguide_id
         ApiGet.congress "legislators?bioguide_id=#{$routeParams.bioguide_id}", (error, data) ->
           if not error
-            if not data[0].district
+            if not data[0].district
               for state in ["AK", "DE", "MT", "ND", "SD", "VT", "WY"]
                 if data[0].state is state
                   data[0].district = "0"
@@ -186,7 +186,7 @@
     $scope.drawMap()
     $scope.defaultFocus()

-    $scope.$watch('state_district', (newVals, oldVals) ->
+    $scope.$watch('state_district', (newVals, oldVals) ->
       if $scope.state_district.state
         $scope.setDistrictData(newVals, oldVals)
         $scope.highlightDistrict()
@@ -198,4 +198,3 @@
     , true)

   ]
-


Fix broken link to stack

Fix broken link to stack

--- server/materials/data/level_2B.coffee
+++ server/materials/data/level_2B.coffee
@@ -352,7 +352,7 @@

 topic_16041 = () ->
     return topic("Стек, дек, очередь", "2Б: Задачи на стек, очередь и дек", [
-        label("Теории по этим темам тут пока нет, но очень много теории есть в интернете. Вот, например, что я нашел: раз, два."),
+        label("Теории по этим темам тут пока нет, но очень много теории есть в интернете. Вот, например, что я нашел: раз, два."),
         problem(54),
         problem(58),
         problem(61),
@@ -368,4 +368,4 @@
         topic_16044(),
         topic_25224(),
         module25835(),
-    ])+    ])


Same getStringFirmware

Same getStringFirmware

--- app/src/dongle/dongle.coffee
+++ app/src/dongle/dongle.coffee
@@ -115,7 +115,7 @@
   disconnect: () -> @_setState(States.DISCONNECTED)

   # @return [String] Firmware version, 1.0.0 for example.
-  getStringFirmwareVersion: -> @firmwareVersion.byteAt(1) + "." + @firmwareVersion.byteAt(2) + "." + @firmwareVersion.byteAt(3)
+  getStringFirmwareVersion: -> Try(=> @firmwareVersion.byteAt(1) + "." + @firmwareVersion.byteAt(2) + "." + @firmwareVersion.byteAt(3)).getOrElse('unknown')

   # @return [Integer] Firmware version, 0x20010000010f for example.
   getIntFirmwareVersion: ->


Server: return http 400 if url is not set

--- servers/lib/server/image_cache.coffee
+++ servers/lib/server/image_cache.coffee
@@ -16,6 +16,9 @@

 module.exports = (req, res) ->
   { endpoint, grow, width, height, url } = req.query
+
+  unless url
+    return res.status(400).send 'Url is not set'

   fullUrl = "https://i.embed.ly/1/display/#{endpoint}?" +
              "width=#{width}&" +


Compact HUD

Compact HUD

--- modules/player/hud.coffee
+++ modules/player/hud.coffee
@@ -75,32 +75,26 @@
       url: item.sauce
       color: 0xAAFF00
       title: '[click for sauce]'
-      description: '[[donate]](https://tblnk.me/focabot-donate/)'
+      description: """
+      [[donate]](https://tblnk.me/focabot-donate/)
+      **Length**: #{@util.displayTime item.duration}
+      **Position in queue**: ##{pos}
+      """
       author:
         name: item.title
         icon_url: @util.getIcon item.sauce
       thumbnail:
         url: item.thumbnail
-      fields: [
-        { name: 'Length:', value: "#{@util.displayTime item.duration}\n ", inline: true }
-        { name: 'Position in queue:', value: "##{pos}", inline: true }
-      ]
       footer:
         icon_url: item.requestedBy.staticAvatarURL
         text: "Requested by #{item.requestedBy.name}"
     if item.filters and item.filters.length
-      reply.description = "**Filters**: #{@util.displayFilters(item.filters)}"
+      reply.description += "\n**Filters**: #{@util.displayFilters(item.filters)}"
     if item.time and item.time > 0
-      reply.fields.push {
-        name: 'Start at:'
-        value: @util.displayTime(item.time)
-        inline: true
-      }
+      reply.description += "\n**Start at**: #{@util.displayTime(item.time)}"
     if estimated
-      reply.fields.push {
-        name: 'Estimated time before playback:',
-        value: @util.displayTime(estimated)
-      }
+      est = @util.displayTime(estimated)
+      reply.description += "\n**Estimated time before playback**: #{est}"
     reply

   removeItem: (item, removedBy)=>
@@ -108,21 +102,21 @@
       url: item.sauce
       color: 0xF44277
       title: '[click for sauce]'
-      description: '[[donate]](https://tblnk.me/focabot-donate/)'
+      description: """
+      [[donate]](https://tblnk.me/focabot-donate/)
+      **Length**: #{@util.displayTime item.duration}
+      """
       author:
         name: item.title
         icon_url: @util.getIcon item.sauce
       thumbnail:
         url: item.thumbnail
-      fields: [
-        { name: 'Length:', value: "#{@util.displayTime item.duration}\n ", inline: true }
-      ]
     if removedBy
       reply.footer =
         icon_url: removedBy.staticAvatarURL
         text: "Removed by #{removedBy.name}"
     if item.filters and item.filters.length
-      reply.description = "**Filters**: #{@util.displayFilters(item.filters)}"
+      reply.description += "\n**Filters**: #{@util.displayFilters(item.filters)}"
     reply

   addPlaylist: (user, playlist, channel)=>
@@ -166,6 +160,7 @@
       description: """
       [[donate]](https://tblnk.me/focabot-donate/)
       #{@generateProgressOuter item}
+      **Length**: #{@util.displayTime item.duration}
       """
       author:
         name: item.title
@@ -175,14 +170,11 @@
       footer:
         text: "Requested by #{item.requestedBy.name}"
         icon_url: item.requestedBy.staticAvatarURL
-      fields: [
-        { inline: true, name: 'Length', value: @util.displayTime item.duration }
-      ]
     }
     if item.filters and item.filters.length
-      r.fields.push { inline: true, name: 'Filters', value: @util.displayFilters item.filters }
+      r.description += "\n**Filters**: #{@util.displayFilters item.filters}"
     if item.radioStream
-      r.description += "\n#{await @radioInfo(item)}"
+      r.description += "\n\n#{await @radioInfo(item)}"
     r

   queue: (q, page=1)=>


Debugging tz

Debugging tz

--- lib/arrivals.coffee
+++ lib/arrivals.coffee
@@ -13,9 +13,9 @@
   if not _.isArray res.data
     return console.error "Got invalid train data",res
   _.each res.data,(i)->
-    event_time = moment.tz(i.EVENT_TIME,"America/New_York").unix()
+    event_time = moment(i.EVENT_TIME).unix()
     next_arr = moment.tz(i.NEXT_ARR,'hh:mm:ss A',"America/New_York").unix()
-    console.log("This arrival is at:",next_arr,"compared to now:",moment().unix())
+    console.log("This arrival is at:",event_time,"compared to now:",moment().unix())
     Arrivals.update {
       train_id: i.TRAIN_ID
       station: i.STATION


:memo: Move comment above isFile check

:memo: Move comment above isFile check

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -326,8 +326,8 @@
   openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) ->
     {pathToOpen, initialLine, initialColumn} = @locationForPathToOpen(pathToOpen)

-    # Open files in the focused window
     unless pidToKillWhenClosed or newWindow
+      # Open files in the specified window or the last focused window
       if fs.statSyncNoException(pathToOpen).isFile?()
         existingWindow = window ? @topWindow


Use named pipe instead of socket file on Windows.

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -15,7 +15,11 @@
 {EventEmitter} = require 'events'
 _ = require 'underscore-plus'

-socketPath = path.join(os.tmpdir(), 'atom.sock')
+socketPath =
+  if process.platform is 'win32'
+    '\\\\.\\pipe\\atom-sock'
+  else
+    path.join(os.tmpdir(), 'atom.sock')

 # Private: The application's singleton class.
 #
@@ -35,7 +39,7 @@
     # take a few seconds to trigger 'error' event, it could be a bug of node
     # or atom-shell, before it's fixed we check the existence of socketPath to
     # speedup startup.
-    if (not fs.existsSync socketPath) or options.test
+    if (process.platform isnt 'win32' and not fs.existsSync socketPath) or options.test
       createAtomApplication()
       return

@@ -99,7 +103,8 @@
   # the other launches will just pass their information to this server and then
   # close immediately.
   listenForArgumentsFromNewProcess: ->
-    fs.unlinkSync socketPath if fs.existsSync(socketPath)
+    if process.platform isnt 'win32' and fs.existsSync(socketPath)
+      fs.unlinkSync socketPath
     server = net.createServer (connection) =>
       connection.on 'data', (data) =>
         @openWithOptions(JSON.parse(data))
@@ -152,7 +157,9 @@
       app.quit() if process.platform is 'win32'

     app.on 'will-quit', =>
-      fs.unlinkSync socketPath if fs.existsSync(socketPath) # Clean the socket file when quit normally.
+      # Clean the socket file when quit normally.
+      if process.platform isnt 'win32' and fs.existsSync(socketPath)
+        fs.unlinkSync socketPath

     app.on 'open-file', (event, pathToOpen) =>
       event.preventDefault()


Add selector generation to Product Component

--- src/coffee/product-component.coffee
+++ src/coffee/product-component.coffee
@@ -4,5 +4,11 @@
 			return unless @productId is args[productIdIndex]
 			handler(evt, args...)

+	generateSelectors: (selectors) =>
+		for k, v of selectors
+			@["find#{k}"] = do(v) => => @element.find(v)
+			@["show#{k}"] = do(k) => => @["find#{k}"]().show()
+			@["hide#{k}"] = do(k) => => @["find#{k}"]().hide()
+
 root = window || exports
 root.ProductComponent = ProductComponent


wireframemode

wireframemode

--- src/coffee/scenes/PizzaSpace.coffee
+++ src/coffee/scenes/PizzaSpace.coffee
@@ -39,6 +39,8 @@

 	onBeat:()=>
 		if(Stage3d.isAuto)
+			if(Math.random()>.2)
+				Stage3d.changeMaterialToWireframe()
 			if(Math.random()>.5)
 				Stage3d.changeMaterialBasicColor()
 			if(Math.random()<.1)


don't reposition on scroll

don't reposition on scroll

--- resources/assets/coffee/_classes/user-card.coffee
+++ resources/assets/coffee/_classes/user-card.coffee
@@ -50,6 +50,8 @@
       content:
         text: card
       position:
+        adjust:
+          scroll: false
         at: at
         my: my
         viewport: $(window)


Skip site testsuite for now.

Skip site testsuite for now.

--- test/server/site.coffee
+++ test/server/site.coffee
@@ -1,4 +1,4 @@
-describe 'Api.site', ->
+describe.skip 'Api.site', ->
   site   = null
   siteId = null


Comment imports for broken backend libs on Travis

Address #709

Libraries commented out are broken on Travis CI so can't use them

--- js/test.coffee
+++ js/test.coffee
@@ -1,8 +1,8 @@
 ---
 ---

-dl = require('datalib')
-evalexpr = require('expr-eval')
+# dl = require('datalib')
+# evalexpr = require('expr-eval')

 {% include coffee/essential.coffee %}
 {% include coffee/main.coffee %}


added some extra logging

added some extra logging

--- manager/lib/assets/javascripts/stark/view.js.coffee
+++ manager/lib/assets/javascripts/stark/view.js.coffee
@@ -74,7 +74,9 @@
   # Lookup @template in the global JST hash
   jst: (tpl) ->
     tpl ||= @template
-    JST[ @app.template_root + tpl ]
+    filename = @app.template_root + tpl
+    @log "template: ", filename
+    JST[filename]

   # Create a Template object for the configured @template
   #


Add a new test for listening to a subview two levels deep.

--- test/BossViewTest.coffee
+++ test/BossViewTest.coffee
@@ -38,6 +38,23 @@

       testView = new TestView()
       testView.testSubView.trigger('someevent')
+      expect(eventSpy).to.be.called
+
+    it 'should be able to listen to subView events two levels deep', ->
+      eventSpy = sinon.spy()
+      TestView = BossView.extend
+        subViews:
+          testSubView: ->
+            return new BossView
+              subViews:
+                testInnerSubView: Backbone.View
+        subViewEvents:
+          'testSubView testInnerSubView:someevent': 'onSomeEvent'
+        onSomeEvent: ->
+          eventSpy()
+
+      testView = new TestView()
+      testView.testSubView.testInnerSubView.trigger('someevent')
       expect(eventSpy).to.be.called

     it 'Should also listen to an inline function for subViewEvents', ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/approachable.coffee
+++ test/approachable.coffee
@@ -8,7 +8,7 @@
         target: 3
         approachBy: 1

-  obj.include(Approachable)
+  obj.include(Cooldown)

   5.times ->
     obj.update()
@@ -28,7 +28,7 @@
         target: 0
         approachBy: 1

-  obj.include(Approachable)
+  obj.include(Cooldown)

   11.times ->
     obj.update()
@@ -56,7 +56,7 @@
         approachBy: 1
         target: 1000

-  obj.include(Approachable)
+  obj.include(Cooldown)

   4.times ->
     obj.update()
@@ -68,7 +68,7 @@
 test "#addCooldown", 3, ->
   obj = GameObject()

-  obj.include(Approachable)
+  obj.include(Cooldown)

   obj.addCooldown 'health'

@@ -90,7 +90,7 @@
 test "should not blow up if cooldowns aren't specified", ->
   obj = GameObject()

-  obj.include(Approachable)
+  obj.include(Cooldown)

   obj.update()


remove console logs

remove console logs

--- models/article.coffee
+++ models/article.coffee
@@ -60,14 +60,11 @@
       # Get related articles for super articles
       if @get('is_super_article') and @get('super_article').related_articles?.length
         for id in @get('super_article').related_articles
-          console.log 'test!'
           dfds.push new Article(id: id).fetch
             success: (article) ->
               console.log 'success'
               relatedArticles.add article
-
       Q.allSettled(dfds).fin =>
-        console.log 'settled'
         @set('section', section) if section
         options.success(
           article: this


Fix and add spec

Fix and add spec

The existing spec wasn't doing the right thing and passed incorrectly.

--- src/desktop/apps/fair_organizer/test/routes.coffee
+++ src/desktop/apps/fair_organizer/test/routes.coffee
@@ -1,6 +1,7 @@
 { fabricate } = require 'antigravity'
 fixtures = require '../../../test/helpers/fixtures.coffee'
 _ = require 'underscore'
+Q = require 'bluebird-q'
 sinon = require 'sinon'
 Backbone = require 'backbone'
 rewire = require 'rewire'
@@ -48,32 +49,53 @@
       ]

     it 'fetches the fair organizer and associated fairs', ->
-      routes.fetchFairOrgData @req, @res, (next = sinon.stub())
-      Backbone.sync.args[0][2].success @fairs.models
-      Backbone.sync.args[1][2].success()
-      _.defer => _.defer =>
+      Backbone.sync.onCall(0).yieldsTo 'success', @fairs.models
+      Backbone.sync.yieldsTo 'success'
+      routes.fetchFairOrgData(@req, @res, (next = sinon.stub())).then =>
         @res.locals.sd.FAIR_IDS.should.eql @fairs.pluck('_id')
         @res.locals.sd.FAIR_ORGANIZER.should.eql @fairOrg.toJSON()

-    it 'redirects to the fair if there is a current fair', ->
+    it 'redirects to the fair if there is a current fair with public profile', ->
       fair = @fairs.first()
       fair.set
         autopublish_artworks_at: moment().utc().subtract(5, 'days').format()
         start_at: moment().utc().subtract(3, 'days').format()
         end_at: moment().utc().add(3, 'days').format()
-      routes.fetchFairOrgData @req, @res, (next = sinon.stub())
-      Backbone.sync.args[0][2].success @fairs.models
-      @res.redirect.args[0][0].should.equal '/the-armory-show'
+
+      Backbone.sync.onCall(0).yieldsTo 'success', @fairs.models
+      Backbone.sync
+        .onCall 1
+        .yieldsTo 'success'
+        .returns Q.promise (resolve, reject) -> resolve(fabricate('fair_profile'))
+      Backbone.sync.yieldsTo 'success'
+      routes.fetchFairOrgData(@req, @res, (next = sinon.stub())).then =>
+        @res.redirect.args[0][0].should.equal '/the-armory-show'
+
+    it 'redirects to the fair if there is a current fair with private profile', ->
+      fair = @fairs.first()
+      fair.set
+        autopublish_artworks_at: moment().utc().subtract(5, 'days').format()
+        start_at: moment().utc().subtract(3, 'days').format()
+        end_at: moment().utc().add(3, 'days').format()
+
+      Backbone.sync.onCall(0).yieldsTo 'success', @fairs.models
+      Backbone.sync
+        .onCall 1
+        .yieldsTo 'error'
+        .returns Q.promise (resolve, reject) -> reject()
+      Backbone.sync.yieldsTo 'success'
+      routes.fetchFairOrgData(@req, @res, (next = sinon.stub())).then =>
+        @res.redirect.notCalled.should.be.ok()
+        @res.locals.sd.FAIR_IDS.should.eql @fairs.pluck('_id')
+        @res.locals.sd.FAIR_ORGANIZER.should.eql @fairOrg.toJSON()

     it 'sets showName if the all the fairs dont have identical names', ->
       fair = @fairs.first()
       fair.set name: 'modern-fair'
-      routes.fetchFairOrgData @req, @res, (next = sinon.stub())
-      Backbone.sync.args[0][2].success @fairs.models
-      Backbone.sync.args[1][2].success()
-      _.defer => _.defer =>
+      Backbone.sync.onCall(0).yieldsTo 'success', @fairs.models
+      Backbone.sync.yieldsTo 'success'
+      routes.fetchFairOrgData(@req, @res, (next = sinon.stub())).then =>
         @res.locals.showName.should.be.true()
-

   describe '#overview', ->
     it 'next is called without a fair org', ->


correct comment

correct comment

--- src/components/exercises/cards.cjsx
+++ src/components/exercises/cards.cjsx
@@ -79,7 +79,7 @@
   getDefaultProps: ->
     topScrollOffset: 110

-  # Important! - as an optimization, this component will only update if exercises has changed.
+  # Important! - as an optimization, this component will only update if props have changed.
   # This is necessary because there can be a very large number of exercise previews displaying at once
   shouldComponentUpdate: (nextProps) ->
     not _.isEqual(nextProps, @props)


update defaultHealthcheckTimeoutSeconds value

--- SingularityUI/config.coffee
+++ SingularityUI/config.coffee
@@ -49,6 +49,7 @@
             hideNewRequestButton: process.env.SINGULARITY_HIDE_NEW_REQUEST_BUTTON ? "false"
             defaultHealthcheckIntervalSeconds: 5
             defaultHealthcheckTimeoutSeconds: 10
+            defaultDeployHealthTimeoutSeconds: 120

         compiledTemplate = handlebars.compile(indexTemplate)(templateData)
         fs.writeFileSync destination, compiledTemplate


Delete result files after tests

Delete result files after tests

--- test/codecat.test.coffee
+++ test/codecat.test.coffee
@@ -2,7 +2,7 @@
 chai.use(require 'chai-string')
 chai.use(require 'chai-fs')
 {expect} = chai
-{readFileSync} = require 'fs'
+{readFileSync, unlink} = require 'fs'
 {given, subject, useTempFiles} = require './test-helpers.coffee'
 CodeCat = require '../src/codecat.coffee'

@@ -68,6 +68,8 @@
 		describe '#concatTo', ->
 			given 'destination', -> 'test/temp/result.js'
 			given 'fileContent', -> readFileSync(@destination, encoding: 'utf8')
+
+			after 'delete result file', -> unlink(@destination)

 			it 'creates a file', (done) ->
 				@subject.concatTo @destination, =>
@@ -151,6 +153,8 @@
 			given 'destination', -> 'test/temp/result.coffee'
 			given 'fileContent', -> readFileSync(@destination, encoding: 'utf8')

+			after 'delete result file', -> unlink(@destination)
+
 			it 'creates a file', (done) ->
 				@subject.concatTo @destination, =>
 					done.check =>


Q should not be upper case.

Q should not be upper case.

--- test/unit/device.coffee
+++ test/unit/device.coffee
@@ -3,7 +3,7 @@
 User = require '../../lib/model/user'
 sinon = require 'sinon'
 mongoose = require 'mongoose'
-Q = require 'Q'
+Q = require 'q'

 describe 'Device', ->


Show warning if navigating away with unsaved data

--- src/components/questions/index.cjsx
+++ src/components/questions/index.cjsx
@@ -3,14 +3,39 @@
 {TocStore, TocActions} = require '../../flux/toc'
 {CourseStore} = require '../../flux/course'
 LoadableItem = require '../loadable-item'
+{UnsavedStateMixin} = require '../unsaved-state'
+{ExerciseStore} = require '../../flux/exercise'
+TutorDialog = require '../tutor-dialog'

 Dashboard = require './dashboard'
+showDialog = ->
+  body =
+

+
You have excluded exercises that have not been saved

+
Are you sure you want to leave the Question Library?

+

+  TutorDialog.show({
+    title: null, body
+  })
+

 QuestionsDashboardShell = React.createClass

   contextTypes:
     router: React.PropTypes.func
+  mixins: [UnsavedStateMixin]
+  hasUnsavedState: ->
+    ExerciseStore.hasUnsavedExclusions()
+
+  statics:
+    # Called before the Router unmounts and transistions to another screen
+    willTransitionFrom: (transition, element) ->
+      return if element.transitionConfirmed or not element.hasUnsavedState()
+      transition.abort()
+      showDialog().then ->
+        element.transitionConfirmed = true
+        transition.retry()

   render: ->
     {courseId} = @context.router.getCurrentParams()


correctly handling buffer parameter in Toolbox.profile() now

--- src/Toolbox.coffee
+++ src/Toolbox.coffee
@@ -45,12 +45,12 @@
   profile: (message, time, object, module = 'profiler', severity = 'VERBOSE', buffer = false)->
     object = object ? {}
     object.timeElapsed = time
-    @_emitWithSeverity Observatory.LOGLEVEL[severity], message, object, module, 'profile'
+    @_emitWithSeverity Observatory.LOGLEVEL[severity], message, object, module, 'profile', buffer

   _profile: (message, time, object, module = 'profiler', severity = 'VERBOSE', buffer = false)->
     object = object ? {}
     object.timeElapsed = time
-    @_forceEmitWithSeverity Observatory.LOGLEVEL[severity], message, object, module, 'profile'
+    @_forceEmitWithSeverity Observatory.LOGLEVEL[severity], message, object, module, 'profile', buffer

   inspect: (obj, long = false, print = false)->


Rename @el to exports internally

Rename @el to exports internally

--- lib/elv.coffee
+++ lib/elv.coffee
@@ -48,7 +48,7 @@
   doc.createTextNode(text)

-@el = element = (tagSpecOrEl, args...) ->
+@el = exports = (tagSpecOrEl, args...) ->
   [attributes, children] = normalizeArguments(args)

   if isElement(tagSpecOrEl)
@@ -56,8 +56,8 @@
   else
     [tag, id, classes] = parseTagSpec(tagSpecOrEl)
     el = doc.createElement(tag)
-    element.setAttr(el, 'id', id) if id
-    element.setAttr(el, 'className', classes.join(' ')) if classes.length
+    exports.setAttr(el, 'id', id) if id
+    exports.setAttr(el, 'className', classes.join(' ')) if classes.length

   if children.length
     exports.setAttr(el, 'html', children)
@@ -72,7 +72,7 @@
   'text': 'textContent'

-@el.appendChildren = (el, children) ->
+exports.appendChildren = (el, children) ->
   if children.length
     fragment = doc.createDocumentFragment()
     for child in children
@@ -82,13 +82,13 @@
     el.appendChild(fragment)

-@el.getAttr = (el, attr) ->
+exports.getAttr = (el, attr) ->
   directAttr = directAttributes[attr]
   if directAttr
     el[directAttr]

-@el.setAttr = (el, attr, value) ->
+exports.setAttr = (el, attr, value) ->
   directAttr = directAttributes[attr]
   if directAttr
     if attr is 'html' and typeof value isnt 'string'


Get specs back.

Get specs back.

--- src/spec/elasticio.spec.coffee
+++ src/spec/elasticio.spec.coffee
@@ -2,6 +2,7 @@
 elasticio = require '../lib/elasticio'
 Config = require '../config'
 StockImport = require '../lib/stockimport'
+{ElasticIo} = require 'sphere-node-utils'

 describe 'elasticio integration', ->
   it 'should work with no attachments nor body', (done) ->
@@ -14,7 +15,7 @@
       expect(error).toBe 'No data found in elastic.io msg.'
       done()

-  xdescribe 'XML file', ->
+  describe 'XML file', ->
     it 'should import from one file with 2 entries', (done) ->
       cfg =
         sphereClientId: Config.config.client_id
@@ -39,14 +40,24 @@
           'stock.xml':
             content: enc

+      spyOn(ElasticIo, 'returnSuccess').andCallThrough()
       elasticio.process msg, cfg, (error, message) ->
         expect(error).toBe null
-        expect(message['Inventory entry created.']).toBe 2
-        expect(message['Inventory entry updated.']).toBe 0
-        expect(message['Inventory update was not necessary.']).toBe 0
-        done()
+        if message is 'elastic.io messages sent.'
+          expect(ElasticIo.returnSuccess.callCount).toBe 3
+          expectedMessage =
+            body:
+              QUANTITY: -2
+              SKU: 'e-123'
+          expect(ElasticIo.returnSuccess).toHaveBeenCalledWith(expectedMessage, jasmine.any(Function))
+          expectedMessage =
+            body:
+              QUANTITY: 0
+              SKU: 'e-xyz'
+          expect(ElasticIo.returnSuccess).toHaveBeenCalledWith(expectedMessage, jasmine.any(Function))
+          done()

-  xdescribe 'CSV file', ->
+  describe 'CSV file', ->
     it 'should import from one file with 3 entries', (done) ->
       cfg =
         sphereClientId: Config.config.client_id
@@ -65,12 +76,27 @@
           'stock.csv':
             content: enc

+      spyOn(ElasticIo, 'returnSuccess').andCallThrough()
       elasticio.process msg, cfg, (error, message) ->
         expect(error).toBe null
-        expect(message['Inventory entry created.']).toBe 3
-        expect(message['Inventory entry updated.']).toBe 0
-        expect(message['Inventory update was not necessary.']).toBe 0
-        done()
+        if message is 'elastic.io messages sent.'
+          expect(error).toBe null
+          expectedMessage =
+            body:
+              QUANTITY: 1
+              SKU: 'c1'
+          expect(ElasticIo.returnSuccess).toHaveBeenCalledWith(expectedMessage, jasmine.any(Function))
+          expectedMessage =
+            body:
+              QUANTITY: 2
+              SKU: 'c2'
+          expect(ElasticIo.returnSuccess).toHaveBeenCalledWith(expectedMessage, jasmine.any(Function))
+          expectedMessage =
+            body:
+              QUANTITY: 3
+              SKU: 'c3'
+          expect(ElasticIo.returnSuccess).toHaveBeenCalledWith(expectedMessage, jasmine.any(Function))
+          done()

   describe 'CSV mapping', ->
     it 'should import a simple entry', (done) ->


Put commands in all the grammars

Put commands in all the grammars

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -3,48 +3,50 @@

 module.exports =
   CoffeeScript:
-    command: "coffee"
     "Selection Based":
+      command: "coffee"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "coffee"
       args: (filename) -> [filename]

   'CoffeeScript (Literate)':
-    command: "coffee"
     "Selection Based":
+      command: "coffee"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "coffee"
       args: (filename) -> [filename]

   Elixir:
-    command: "elixir"
     "Selection Based":
+      command: "elixir"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "elixir"
       args: (filename) -> ['-r', filename]

   Erlang:
-    command: "erl"
     "Selection Based":
+      command: "erl"
       args: (code)  -> ['-noshell', '-eval', code+', init:stop().']

   'F#':
-    command: "fsharpi"
     "File Based":
+      command: "fsharpi"
       args: (filename) -> ['--exec', filename]

   Go:
-    command: "go"
-    #"Selection Based":
-    #  args: (filename) -> []
     "File Based":
+      command: "go"
       args: (filename) -> ['run', filename]

   Groovy:
-    command: "groovy"
     "Selection Based":
+      command: "groovy"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "groovy"
       args: (filename) -> [filename]

   Haskell:
@@ -56,82 +58,94 @@
       args: (code)  -> ['-e', code]

   JavaScript:
-    command: "node"
     "Selection Based":
+      command: "node"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "node"
       args: (filename) -> [filename]

   Julia:
-    command: "julia"
     "Selection Based":
+      command: "julia"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "julia"
       args: (filename) -> [filename]

   Lua:
-    command: "lua"
     "Selection Based":
+      command: "lua"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "lua"
       args: (filename) -> [filename]

   PHP:
-    command: "php"
     "Selection Based":
+      command: "php"
       args: (code)  -> ['-r', code]
     "File Based":
+      command: "php"
       args: (filename) -> [filename]

   Perl:
-    command: "perl"
     "Selection Based":
+      command: "perl"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "perl"
       args: (filename) -> [filename]

   Python:
-    command: "python"
     "Selection Based":
+      command: "python"
       args: (code)  -> ['-c', code]
     "File Based":
+      command: "python"
       args: (filename) -> [filename]

   RSpec:
-    command: "rspec"
     "Selection Based":
       command: "ruby"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "rspec"
       args: (filename) -> [filename]

   Ruby:
-    command: "ruby"
     "Selection Based":
+      command: "ruby"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "ruby"
       args: (filename) -> [filename]

   'Shell Script (Bash)':
-    command: "bash"
     "Selection Based":
+      command: "bash"
       args: (code)  -> ['-c', code]
     "File Based":
+      command: "bash"
       args: (filename) -> [filename]

   Scala:
-    command: "scala"
     "Selection Based":
+      command: "scala"
       args: (code)  -> ['-e', code]
     "File Based":
+      command: "scala"
       args: (filename) -> [filename]

   newLISP:
-    command: "newlisp"
-    "Selection Based": (code) -> ['-e', code]
-    "File Based": (filename) -> [filename]
+    "Selection Based":
+      command: "newlisp"
+      args: (code) -> ['-e', code]
+    "File Based":
+      command: "newlisp"
+      args: (filename) -> [filename]

   Gherkin:
-    command: "cucumber"
-    #"Selection Based": (code) -> ['-e', code]
-    "File Based": (filename) -> [filename]
+    "File Based":
+      command: "cucumber"
+      args: (filename) -> [filename]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -20,10 +20,6 @@
   player.update()

   equals player.I.velocity.x, -3
-
-  player.update()
-
-  equals player.I.x, 8

   window.keydown.left = false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -2,7 +2,7 @@

 test "should move player left while key is pressed", ->
   player = GameObject
-    x: 0
-    y:
+    x: 10
+    y: 20

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -27,7 +27,7 @@
 test "should move player up while key is pressed", ->
   ;

-test "should move player down while key "
+test "should move player down while key is pres"

 module()


add jsonp support to respond helper

add jsonp support to respond helper

--- routes/helpers/respond.coffee
+++ routes/helpers/respond.coffee
@@ -16,6 +16,13 @@
   # Set any headers
   res.set key, val for key, val of headers if headers

+  # Creates json object from opts, without the http info
+  jsonFor = (opts) ->
+    json = _.extend {}, opts, {error}
+    delete json.headers
+    delete json.status
+    return json
+
   switch format
     when 'html'
       # Prepare any messages or errors
@@ -27,11 +34,13 @@
       viewOpts = _.extend {}, opts, {errors, messages, headers}
       res.render view, viewOpts
     when 'json'
-      # Create json object from opts, without the http info
-      json = _.extend {}, opts, {error}
-      delete json.headers
-      delete json.status
-      # Serve json
-      res.json json
+      res.set 'Content-Type', 'application/json'
+      res.json jsonFor opts
+    when 'jsonp'
+      json = jsonFor opts
+      cb = req.query.callback or 'jsonpCallback'
+      jsonp = cb + '(' + JSON.stringify( json, null, '  ' ) + ');'
+      res.set 'Content-Type', 'text/javascript'
+      res.send jsonp

 module.exports = respond


[grunt] add `depth` as prop of each page

--- tasks/data.coffee
+++ tasks/data.coffee
@@ -26,6 +26,7 @@
             slug:       path.slice(-2)[0]
             href:       if breadcrumb.length == 1 and  breadcrumb[0] == 'index' then '/' else resolve('/', breadcrumb.join('/'))
             breadcrumb: breadcrumb
+            depth:      breadcrumb.length
             dirname:    basename(dirname(src))
             basename:   basename(src, extname(src))
           }, data


minor fixes for prompts and handling non array returns

--- lib/lt3.coffee
+++ lib/lt3.coffee
@@ -89,12 +89,14 @@
           log "------Site------"
           log JSON.stringify(site.val(), null, 4)
           log "------End Site------"
+        rl.prompt();

     when 'get:allsites'
       getDB (db) ->
        db.get('sites').find {}, (err, sites) ->
         for site in sites
           log "_id: #{site.data._id}, slug:#{site.data.slug}"
+        rl.prompt();

     when 'get:sitemap'
       getDB (db) ->
@@ -103,6 +105,7 @@
           for object in objects
             log "_id: #{object.data._id}, collection:#{object.data.collection},
             page_type:#{object.data.page_type}"
+          rl.prompt();

     when 'get:site_ids'
       getDB (db) ->
@@ -115,6 +118,7 @@
         db.get('sites').find query, (err, sites) =>
          for site in sites
             log "_id: #{site.data._id}, slug:#{site.data.slug}"
+         rl.prompt();

     when 'get:objects_c'
       getDB (db) ->
@@ -123,6 +127,7 @@
             log "------Object------"
             log JSON.stringify(object.val(), null, 4)
             log "------End Object------"
+          rl.prompt();

     when 'get:objects_t'
       getDB (db) ->
@@ -131,6 +136,7 @@
           log "------Object------"
           log JSON.stringify(object.val(), null, 4)
           log "------End Object------"
+        rl.prompt();

     when 'help' or 'docs'
       log USAGE_SHELL
@@ -139,6 +145,15 @@
       checkForDb=l_token.match(/db.+(?=\()/)+""
       log "Check for DB #{checkForDb}"

+      #helper function to check for type is array (recommended from coffescript site for robust checking)
+      typeIsArray = ( value ) =>
+        value and
+        typeof value is 'object' and
+        value instanceof Array and
+        typeof value.length is 'number' and
+        typeof value.splice is 'function' and
+        not ( value.propertyIsEnumerable 'length' )
+
       #helper function to extract jsons from a token
       extractJsons = (token)  =>
         count=0;
@@ -170,11 +185,16 @@
             func=l_token.match(/f[a-zA-Z]+/)
             functionArgs=extractJsons(l_token)
             db.get('sites')[func] functionArgs[0],functionArgs[1], (err, objects) ->
-              log objects
-              for object in objects
-                log "------Object------"
-                log JSON.stringify(object.val(), null, 4)
-                log "------End Object------"
+              if typeIsArray objects
+                for object in objects
+                  log '------Site------'
+                  log JSON.stringify(object.val(), null, 4)
+                  log '------End Site------'
+              else
+                log '------Site------'
+                log JSON.stringify(objects.val(), null, 4)
+                log '------End Site------'
+              rl.prompt();

         when 'db.objects.find','db.objects.findOne','db.objects.findById'
           extractJsons l_token
@@ -182,11 +202,21 @@
             func=l_token.match(/f[a-zA-Z]+/)
             functionArgs=extractJsons(l_token)
             db.get('sites')[func] functionArgs[0],functionArgs[1], (err, objects) ->
-              for object in objects
-                log "------Object------"
-                log JSON.stringify(object.val(), null, 4)
-                log "------End Object------"
-
+              if typeIsArray objects
+                for object in objects
+                  log '------Object------'
+                  log JSON.stringify(object.val(), null, 4)
+                  log '------End Object------'
+              else
+                log '------Site------'
+                log JSON.stringify(objects.val(), null, 4)
+                log '------End Site------'
+              rl.prompt();
+        else
+          log """
+          log command not recognized
+          Shell Active type 'exit' or 'quit' to escape"""
+          rl.prompt();

@@ -395,7 +425,7 @@
       command=args[0]
       args.splice(0,1)
       executeCommand(token,command,args)
-      rl.prompt();
+

   when 'docs'


Add three more pending tests.

Add three more pending tests.

--- test/pending_test.coffee
+++ test/pending_test.coffee
@@ -3,3 +3,6 @@
     it 'Should test initWith option'
     it 'Should embed plain JS files'
     it 'Should allow setting the source path'
+    it 'Should allow setting the lib path'
+    it 'Should allow setting the tmp path'
+    it 'Should allow setting the out path'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -40,8 +40,8 @@
     height: 8
     collisionMargin: Point(0, 0)

-  closest: ->
-    if Object.isString(groupA)
+  closest: (selector) ->
+    if Object.isString(sel)
         groupA = engine.find(groupA)
       else
         groupA = [].concat(groupA)


add test for env var

add test for env var

--- tests/conoha_test.coffee
+++ tests/conoha_test.coffee
@@ -29,6 +29,9 @@
       expect(conoha.auth.auth.tenantId)
       .to.not.be.undefined

+    it 'identity service endpoint', ->
+      expect(process.env.CONOHA_IDENTITY_URL)
+      .to.not.be.undefined

   describe 'getService() method returns non undefined', ->
     [


Fix error if recentplunks is fewer than one page

--- assets/js/views/cards.coffee
+++ assets/js/views/cards.coffee
@@ -143,13 +143,15 @@
         self.collection.fetch()

       @collection.parse = _.wrap @collection.parse, (parse, json, xhr) ->
-        link = xhr.getResponseHeader("Link")
-        page = {}
+        self.page.clear()

-        link.replace /<([^>]+)>;\s*rel="(next|prev|first|last)"/gi, (match, href, rel) ->
-          page[rel] = href
-
-        self.page.clear(silent: true).set(page)
+        if link = xhr.getResponseHeader("Link")
+          page = {}
+
+          link.replace /<([^>]+)>;\s*rel="(next|prev|first|last)"/gi, (match, href, rel) ->
+            page[rel] = href
+
+          self.page.set(page)

         parse(json)


Whoops - broke the paid checkbox

Whoops - broke the paid checkbox

--- app/assets/javascripts/meals.js.coffee
+++ app/assets/javascripts/meals.js.coffee
@@ -34,7 +34,7 @@
 meal.addOrderListeners = () ->
   $('.ajax-paid').change(() ->
     order_id = this.value
-    meal.send_attribute( 'order', order_id, 'has_paid', this.checked, () ->
+    meal.send_attribute( 'orders', order_id, 'has_paid', this.checked, () ->
       meal.update_orders()
     )
   )


Replaced sourcemap path sep with unix one

--- tests/integrated.coffee
+++ tests/integrated.coffee
@@ -108,7 +108,7 @@
               (file) -> path.relative(
                 process.cwd()
                 path.resolve "./tests/data/multiple", file
-              )
+              ).replace /\\/g, "/"
             )
         )
       q.all(promises).done (-> done()), done
@@ -187,7 +187,7 @@
                     process.cwd(),
                     path.resolve path.join("./tests/data/glob", folder),
                     file
-                  )
+                  ).replace /\\/g, "/"
           )
         )
       q.all(promises).done (-> done()), done
@@ -260,7 +260,7 @@
               path.relative(
                 process.cwd(),
                 path.resolve "./tests/data/imports", file
-              )
+              ).replace /\\/, "/"
         )
       ]
       q.all(promises).done (-> done()), done


advanced options [+] <--> [-]

advanced options [+] <--> [-]

--- app/assets/javascripts/tasks.js.coffee
+++ app/assets/javascripts/tasks.js.coffee
@@ -78,6 +78,10 @@

   $('.expand_button').click ->
     $(this).next('.advanced_options').toggle()
+    if $(this).next('.advanced_options').css('display') == 'none'
+      $(this).html('[+]')
+    else
+      $(this).html('[–]')
   $('.advanced_options').each ->
     advanced_section = $(this)
     if  advanced_section.find('[data-error]').size() > 0


:bug: #189 change can enter insert mode even if target is empty.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -135,10 +135,14 @@
   execute: ->
     @selectTarget()
     return if @isMode('operator-pending') or @isMode('visual', 'blockwise')
-    if @target.isAllowSubmodeChange?()
+    unless @isMode('visual')
       submode = swrap.detectVisualModeSubmode(@editor)
-      if submode? and not @isMode('visual', submode)
-        @activateMode('visual', submode)
+      @activateMode('visual', submode)
+    else
+      if @target.isAllowSubmodeChange?()
+        submode = swrap.detectVisualModeSubmode(@editor)
+        if submode? and not @isMode('visual', submode)
+          @activateMode('visual', submode)

 class SelectLatestChange extends Select
   @extend()
@@ -1060,10 +1064,7 @@
   supportInsertionCount: false

   execute: ->
-    unless @selectTarget()
-      @activateMode('normal')
-      return
-
+    @selectTarget()
     text = ''
     if @target.instanceof('TextObject') or @target.instanceof('Motion')
       text = "\n" if (swrap.detectVisualModeSubmode(@editor) is 'linewise')


sort highscore correctly

sort highscore correctly

--- app/client/highscore/highscores.coffee
+++ app/client/highscore/highscores.coffee
@@ -10,7 +10,8 @@
       fields: { _id: 1 }
     ).map (g) -> g._id

-    Highscores.find gameId: { $in: highscoreGameIds }
+    Highscores.find(gameId: { $in: highscoreGameIds },
+      sort: [[ 'score', 'desc' ]])

 UI.registerHelper 'username', (userId) ->
   user = Meteor.users.findOne userId


(#3) Fix environment autocompletion mispositioned

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -43,11 +43,15 @@
         setTimeout(( -> atom.packages.getActivePackage('autocomplete-plus')\
           .mainModule.autocompleteManager.findSuggestions()), 100)
       if suggestion.latexType is 'environment'
-        if suggestion.additionalInsert?
-          row = triggerPosition.row - 1
-          col = editor.buffer.lines[row].length
-          editor.buffer.insert(
-            {row: row, column: col}, suggestion.additionalInsert)
+        rowContent = editor.buffer.lines[triggerPosition.row].slice(
+          0, triggerPosition.column)
+        if rowContent.indexOf('\\end') > rowContent.indexOf('\\begin')
+          editor.setCursorBufferPosition(
+            row: triggerPosition.row - 1
+            column: editor.buffer.lines[triggerPosition.row - 1].length
+          )
+          if suggestion.additionalInsert?
+            editor.insertText(suggestion.additionalInsert)

   completeCommand: (line, type) ->
     switch type


Attach share buttons

Attach share buttons

--- src/assets/javascripts/mobile/index.coffee
+++ src/assets/javascripts/mobile/index.coffee
@@ -3,6 +3,7 @@
 Preloader = require '../preloader'
 Carousel = require './carousel'
 Backdrops = require '../backdrops'
+Share = require '../share'

 module.exports = class Mobile
   constructor: ->
@@ -25,3 +26,5 @@
     $('.pane--info').on 'click', (e) ->
       e.stopPropagation()
       $(e.currentTarget).closest('.pane').toggleClass 'is-captioned'
+
+    new Share $facebook: $('.pane--share_facebook'), $twitter: $('.pane--share_twitter')


Add @updateURL for donkey4uEd2kGen.user.coffee

--- donkey4uEd2kGen.user.coffee
+++ donkey4uEd2kGen.user.coffee
@@ -7,6 +7,7 @@
 # @description  Generate ed2k for donkey4u detail page
 # @match      http://donkey4u.com/detail/*
 # @copyright  2014+, You
+# @updateURL  https://github.com/iron9light/userscripts/raw/master/donkey4uEd2kGen.user.js
 # ==/UserScript==
 ###


Bug fix

Bug fix

--- app/views/identifiers/convert.js.coffee
+++ app/views/identifiers/convert.js.coffee
@@ -6,4 +6,4 @@
 """
 <% end %>

-$("table#mapped-ids tbody").html "#{j(render 'results')}"
+$("table#mapped-ids tbody").html "<%= j(render 'results') %>"


Reply icon now shows up correctly

Reply icon now shows up correctly

--- core/app/backbone/views/conversations/conversation_item_view.coffee
+++ core/app/backbone/views/conversations/conversation_item_view.coffee
@@ -12,7 +12,7 @@

     first_recipient: @otherRecipients[0].toJSON()
     recipients_comma: @otherRecipients.map((user) -> user.get('name')).join(', ')
-    reply: last_message and @model.get('last_message').sender.id != currentUser.id
+    reply: last_message and last_message.sender.id == currentUser.id
     html_content: last_message and nlToBr(htmlEscape(last_message.content))

   onShow: ->


画线后关闭菜单

画线后关闭菜单

--- src/js/chapter.coffee
+++ src/js/chapter.coffee
@@ -73,5 +73,6 @@
     s = selectData()
     console.debug s
     underline($("#s#{s.row}_#{s.start}"), $("#s#{s.row}_#{s.end}"), 'ul_1')
+    $scope.menuOpen = false

 BookCtrl.$inject = ['$scope', '$modal']


trakt: add getPopular

trakt: add getPopular

--- src/js/trakttv.coffee
+++ src/js/trakttv.coffee
@@ -77,6 +77,8 @@

       callback err

+trakttv.getPopular = (callback) ->
+  @request 'shows/popular', callback

 trakttv.getWatched = (callback) ->
   trakttv.request '/sync/watched/shows', callback