:lipstick: on find all specs

:lipstick: on find all specs

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -461,17 +461,15 @@
       expect(findView.resultCounter.text()).toEqual('2 of 5')
       expect(editor.getSelectedBufferRange()).toEqual [[1, 6], [1, 10]]

-
-    it "selects the all matches when the 'find-and-replace:find-all' event is triggered and correctly focuses the editor", ->
+    it "selects all matches when 'find-and-replace:find-all' is triggered and correctly focuses the editor", ->
       expect(findView).toHaveFocus()
       findView.findEditor.trigger('find-and-replace:find-all')
-      expect(editor.getSelectedBufferRanges()).toEqual [[[1, 27], [1, 22]],[[2, 8], [2, 13]], [[2, 34], [2, 39]], [[3, 16], [3, 21]],[[4,10], [4, 15]],[[5, 16], [5, 21]]]
+      expect(editor.getSelectedBufferRanges()).toEqual [[[1, 27], [1, 22]], [[2, 8], [2, 13]], [[2, 34], [2, 39]], [[3, 16], [3, 21]], [[4,10], [4, 15]], [[5, 16], [5, 21]]]
       expect(editorView).toHaveFocus()

     it "will re-run search if 'find-and-replace:find-all' is triggered after changing the findEditor's text", ->
       findView.findEditor.setText 'sort'
       findView.findEditor.trigger('find-and-replace:find-all')
-
       expect(editor.getSelectedBufferRanges()).toEqual [[[0, 9], [0, 13]], [[1, 6], [1, 10]],[[8, 11], [8, 15]], [[8, 43], [8, 47]], [[11, 9], [11, 13]]]

     it "replaces results counter with number of results found when user moves the cursor", ->


Remove generated tests

Remove generated tests

--- spec/list-edit-spec.coffee
+++ spec/list-edit-spec.coffee
@@ -11,52 +11,3 @@
   beforeEach ->
     workspaceElement = atom.views.getView(atom.workspace)
     activationPromise = atom.packages.activatePackage('list-edit')
-
-  describe "when the list-edit:toggle event is triggered", ->
-    it "hides and shows the modal panel", ->
-      # Before the activation event the view is not on the DOM, and no panel
-      # has been created
-      expect(workspaceElement.querySelector('.list-edit')).not.toExist()
-
-      # This is an activation event, triggering it will cause the package to be
-      # activated.
-      atom.commands.dispatch workspaceElement, 'list-edit:toggle'
-
-      waitsForPromise ->
-        activationPromise
-
-      runs ->
-        expect(workspaceElement.querySelector('.list-edit')).toExist()
-
-        listEditElement = workspaceElement.querySelector('.list-edit')
-        expect(listEditElement).toExist()
-
-        listEditPanel = atom.workspace.panelForItem(listEditElement)
-        expect(listEditPanel.isVisible()).toBe true
-        atom.commands.dispatch workspaceElement, 'list-edit:toggle'
-        expect(listEditPanel.isVisible()).toBe false
-
-    it "hides and shows the view", ->
-      # This test shows you an integration test testing at the view level.
-
-      # Attaching the workspaceElement to the DOM is required to allow the
-      # `toBeVisible()` matchers to work. Anything testing visibility or focus
-      # requires that the workspaceElement is on the DOM. Tests that attach the
-      # workspaceElement to the DOM are generally slower than those off DOM.
-      jasmine.attachToDOM(workspaceElement)
-
-      expect(workspaceElement.querySelector('.list-edit')).not.toExist()
-
-      # This is an activation event, triggering it causes the package to be
-      # activated.
-      atom.commands.dispatch workspaceElement, 'list-edit:toggle'
-
-      waitsForPromise ->
-        activationPromise
-
-      runs ->
-        # Now we can test for view visibility
-        listEditElement = workspaceElement.querySelector('.list-edit')
-        expect(listEditElement).toBeVisible()
-        atom.commands.dispatch workspaceElement, 'list-edit:toggle'
-        expect(listEditElement).not.toBeVisible()


add option for defining the port through an environment variable

--- server.coffee
+++ server.coffee
@@ -11,6 +11,8 @@
   'fi-FI':  require 'nls/root/strings'
 supportedLocales = _.keys(strings)

+port = parseInt(vertx.env['REITTIOPAS_PORT'] or 8080, 10)
+
 eb = vertx.eventBus
 server = vertx.createHttpServer()
 routeMatcher = new vertx.RouteMatcher
@@ -83,6 +85,6 @@
       blankSlateActive: blankSlateActive
       blankSlateHidden: blankSlateHidden

-server.requestHandler(routeMatcher).listen 8080
+server.requestHandler(routeMatcher).listen port

 stdout.println "Server started"


added timestamp to connection log

added timestamp to connection log

--- server.coffee
+++ server.coffee
@@ -5,7 +5,7 @@

 clientCount = 0
 server = net.createServer (client) =>
-  console.log "client ##{++clientCount} connected."
+  console.log "#{new Date()}\t client ##{++clientCount} connected."
   tentacle = new Tentacle(client)
   tentacle.start()


Unnecessary code.

Unnecessary code.

--- server.coffee
+++ server.coffee
@@ -93,9 +93,6 @@

   updatedWithValue: (id, value) =>
     unless _.isObject(value) and _.isString(value._id)
-      # Special case: when elements are being deleted from the array they are temporary set to null value, so we are ignoring this
-      return if _.isNull(value) and @isArray
-
       # Optional field
       return if _.isNull(value) and not @required


Rename the collider/blocker events

Rename the collider/blocker events

Signed-off-by: Balthazar Rouberol <a9d237650e4d4cb3de4969f778ca372566bedbf9@mapado.com>

--- server.coffee
+++ server.coffee
@@ -86,9 +86,9 @@
         io.sockets.emit('score updated', player)

     # Add a wall and broadcast it to all players
-    socket.on 'add wall', (ax, ay, cx, cy) ->
-        io.sockets.emit('wall added', player, ax, ay, cx, cy)
+    socket.on 'add bubble', (x, y) ->
+        io.sockets.emit('bubble added', player, x, y)

     # Remove a wall and broadcast it to all players
-    socket.on 'remove wall', (ax, ay, cx, cy) ->
-        io.sockets.emit('wall removed', player, ax, ay, cx, cy)
+    socket.on 'remove bubble', (x, y) ->
+        io.sockets.emit('bubble removed', player, x, y)


fixign

fixign

--- server.coffee
+++ server.coffee
@@ -1,5 +1,6 @@
 util = require('util')
 Game = require('./game')
+http = require('http')
 Helper = require('./helpers')
 express = require('express')
 app = express()


Add tests for goto (G) line motions with delete, yank, and change

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -328,6 +328,45 @@
             keydown('k')
             expect(editor.getText()).toBe("12345")

+    describe "when followed by a G", ->
+      beforeEach ->
+        originalText = "12345\nabcde\nABCDE"
+        editor.setText(originalText)
+
+      describe "on the beginning of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,0])
+          keydown('d')
+          keydown('G', shift: true)
+          expect(editor.getText()).toBe("12345\n")
+
+      describe "on the middle of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,2])
+          keydown('d')
+          keydown('G', shift: true)
+          expect(editor.getText()).toBe("12345\n")
+
+    describe "when followed by a goto line G", ->
+      beforeEach ->
+        originalText = "12345\nabcde\nABCDE"
+        editor.setText(originalText)
+
+      describe "on the beginning of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,0])
+          keydown('d')
+          keydown('2')
+          keydown('G', shift: true)
+          expect(editor.getText()).toBe("12345\nABCDE")
+
+      describe "on the middle of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,2])
+          keydown('d')
+          keydown('2')
+          keydown('G', shift: true)
+          expect(editor.getText()).toBe("12345\nABCDE")

   describe "the D keybinding", ->
     beforeEach ->
@@ -401,6 +440,50 @@
         keydown('r', ctrl: true)
         expect(editor.getText()).toBe "12345\nfg\nABCDE"

+    describe "when followed by a G", ->
+      beforeEach ->
+        originalText = "12345\nabcde\nABCDE"
+        editor.setText(originalText)
+
+      describe "on the beginning of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,0])
+          keydown('c')
+          keydown('G', shift: true)
+          keydown('escape');
+          expect(editor.getText()).toBe("12345\n");
+
+      describe "on the middle of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,2])
+          keydown('c')
+          keydown('G', shift: true)
+          keydown('escape');
+          expect(editor.getText()).toBe("12345\n");
+
+    describe "when followed by a goto line G", ->
+      beforeEach ->
+        originalText = "12345\nabcde\nABCDE"
+        editor.setText(originalText)
+
+      describe "on the beginning of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,0])
+          keydown('c')
+          keydown('2')
+          keydown('G', shift: true)
+          keydown('escape');
+          expect(editor.getText()).toBe("12345\n\nABCDE")
+
+      describe "on the middle of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,2])
+          keydown('c')
+          keydown('2')
+          keydown('G', shift: true)
+          keydown('escape');
+          expect(editor.getText()).toBe("12345\n\nABCDE")
+
   describe "the C keybinding", ->
     beforeEach ->
       editor.getBuffer().setText("012\n")
@@ -495,6 +578,50 @@

       it "leaves the cursor at the starting position", ->
         expect(editor.getCursorScreenPosition()).toEqual [0, 4]
+
+    describe "when followed by a G", ->
+      beforeEach ->
+        originalText = "12345\nabcde\nABCDE"
+        editor.setText(originalText)
+
+      describe "on the beginning of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,0])
+          keydown('y')
+          keydown('G', shift: true)
+          keydown('P', shift: true)
+          expect(editor.getText()).toBe("12345\nabcde\nABCDE\nabcde\nABCDE")
+
+      describe "on the middle of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,2])
+          keydown('y')
+          keydown('G', shift: true)
+          keydown('P', shift: true)
+          expect(editor.getText()).toBe("12345\nabcde\nABCDE\nabcde\nABCDE")
+
+    describe "when followed by a goto line G", ->
+      beforeEach ->
+        originalText = "12345\nabcde\nABCDE"
+        editor.setText(originalText)
+
+      describe "on the beginning of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,0])
+          keydown('y')
+          keydown('2')
+          keydown('G', shift: true)
+          keydown('P', shift: true)
+          expect(editor.getText()).toBe("12345\nabcde\nabcde\nABCDE")
+
+      describe "on the middle of the second line", ->
+        it "deletes the bottom two lines", ->
+          editor.setCursorScreenPosition([1,2])
+          keydown('y')
+          keydown('2')
+          keydown('G', shift: true)
+          keydown('P', shift: true)
+          expect(editor.getText()).toBe("12345\nabcde\nabcde\nABCDE")

   describe "the yy keybinding", ->
     describe "on a single line file", ->


Ext/KineticPanel: Move stage initialization to initStage to allow subclasses to tie in.

--- oghma/ext/KineticPanel.coffee
+++ oghma/ext/KineticPanel.coffee
@@ -27,19 +27,22 @@
   # If true, stage will automatically resize to match panel.
   autoResizeStage: true

+  # Initialize stage.  Called at boxready.
+  initStage: ( width, height ) ->
+    @stage = new Kinetic.Stage(
+      container: @body
+      width:     width
+      height:    height
+    )
+
   # See ExtJS.
   initComponent: ->
     @on( 'boxready', ( it, width, height ) =>
-      @stage = new Kinetic.Stage(
-        container: @body
-        width:     width
-        height:    height
-      )
+      @initStage( width, height )

       @on( 'resize', =>
         if @autoResizeStage
-          @stage.setHeight( @getHeight() )
-          @stage.setWidth(  @getWidth() )
+          @stage.setSize( @getWidth(), @getHeight() )
       )
     )
     @callParent( arguments )


Change `clang++` to `g++` because OS X already links them. However, if the developer prefers to use `g++` on Darwin (i.e., explicitly linking `g++` to gcc), he shouldn't be forced to use clang..

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -128,7 +128,7 @@
     if GrammarUtils.OperatingSystem.isDarwin()
       "File Based":
         command: "bash"
-        args: (context) -> ['-c', "xcrun clang++ -fcolor-diagnostics -Wc++11-extensions -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+        args: (context) -> ['-c', "xcrun g++ -fcolor-diagnostics -Wc++11-extensions -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
     else if GrammarUtils.OperatingSystem.isLinux()
       "Selection Based":
         command: "bash"


Add default sort and sets cookie when sort changes

--- models/state.coffee
+++ models/state.coffee
@@ -6,6 +6,7 @@
   defaults:
     view_mode: 'grid'
     lightbox: false
+    sort: 'updated_at'

   initialize: ->
     # set values from cookies
@@ -13,6 +14,7 @@
       @set(key, val) if val = cookies.get key

     @on 'change:view_mode', @setCookie
+    @on 'change:sort', @setCookie

   setCookie: (model, value)->
     cookies.set keys(model.changed)[0], value


Added field to error details so it can be thrown from server method.

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -1,9 +1,8 @@
-@FormError = Meteor.makeErrorType 'FormError',
-  (error, reason, field) ->
+class @FormError extends Meteor.Error
+  constructor: (error, reason, field) ->
     throw new Error "Error not set" unless error
-    @error = error
-    @reason = reason or ''
-    @field = field or ''
+    details = "Field: #{ field or '' }"
+    super error, reason, details

 # Session variable wrapper for info/error messages
 class @FormMessages
@@ -44,7 +43,12 @@
       @_set @messageType.ERROR, field, ''

   setError: (error) =>
-    @setErrorMessage error.reason, error.field
+    # We get field value from error details
+    if _.startsWith error.details, 'Field: '
+      field = error.details.substring 'Field: '.length
+    else
+      field = ''
+    @setErrorMessage error.reason, field

   # Sets error message for given field or global error message if field is not set
   setErrorMessage: (message, field) =>


rename for being descriptive

rename for being descriptive

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -598,8 +598,8 @@
   # we have to manage two separate checkpoint for different purpose(timing is different)
   # - one for undo(handled by modeManager)
   # - one for preserve last inserted text
-  setCheckpoint: (kind) ->
-    @checkpoint[kind] = @editor.createCheckpoint()
+  setCheckpoint: (purpose) ->
+    @checkpoint[purpose] = @editor.createCheckpoint()

   getCheckpoint: ->
     @checkpoint


fix lint

fix lint

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -182,7 +182,7 @@

     fn()
     options = {includeIntersects: true, exclusiveIntersects: wasVisual}
-    scanRanges ?= @editor.getSelectedBufferRanges(),
+    scanRanges ?= @editor.getSelectedBufferRanges()
     ranges = scanInRanges(@editor, @patternForOccurence, scanRanges)

     if ranges.length


Remove no longer valid comment

Remove no longer valid comment

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -82,7 +82,6 @@
     $(html).find('code').css('font-family', fontFamily)

   for preElement in $.merge(html.filter("pre"), html.find("pre"))
-    # go to next block unless this one has a class
     codeBlock = $(preElement.firstChild)
     fenceName = codeBlock.attr('class')?.replace(/^lang-/, '') ? 'text'


Check for incomplete data

Check for incomplete data

--- lib/surveyor.coffee
+++ lib/surveyor.coffee
@@ -248,7 +248,7 @@
               routes[service.repo][k] = v

             routes[service.repo].routes ?= []
-            return checkDone() if !model.slaves[name].processes[pid]?
+            return checkDone() if !model.slaves[name]?.processes[pid]?
             if model.slaves[name].processes[pid].status is 'running'
               routes[service.repo].routes.push
                 host: model.slaves[name].ip


Delete app.coffee

Delete app.coffee

更新為js檔案

--- src/devtools/panel/app/app.coffee
+++ src/devtools/panel/app/app.coffee
@@ -1,206 +0,0 @@
-((exports)->
-  Vue.config.debug = true;
-  exports.tohken = {} if !exports.tohken
-  exports.tohken.control = new Vue {
-    el: '#app'
-
-    data: {
-      version: '0.1.0'
-      gaming: false
-      tab: 2
-      data: exports.tohken.data
-      view: exports.tohken.view
-      config: exports.tohken.config
-      vdata: true
-      dataExist: false
-      status: {
-        # 是否在战斗状态
-        "in_battle": false
-        # 当前所在地图
-        "router": null
-        # 上一次所在地图
-        "last_router": null
-        # 战斗中的队伍ID
-        "battle_id": null
-        # 战斗中地图
-        "battle_episode": null
-        "battle_field": null
-        # 战斗中位置
-        "battle_pos": null
-        # 显示锻刀结果
-        "show_forge": false
-        # 是否重伤刷新
-        "need_reload": false
-      }
-    }
-
-    ready: ->
-      # Listen the network request
-      chrome.devtools.network.onRequestFinished.addListener (request)=>
-        # filter
-        tohken = request.request.url.match /http:\/\/(.*?)\.touken-ranbu\.jp\/(.*)/
-        if tohken != null
-          # throw static
-          return if tohken[1] == "static"
-          # pass
-          @route(request, tohken[2])
-      # loadData
-      @loadData()
-    filters: {
-      conquest_time: (finished_at)->
-        d = new Date()
-        d.setTime(Date.parse("#{finished_at} GMT+0900"))
-        d.toTimeString().slice(0, 8)
-      e2n: (equip_id)->
-        exports.tohken.define.equip[equip_id]['name']
-      ls2t: (time)->
-        date = new Date()
-        date.setTime time
-        year    = date.getFullYear()
-        month   = if (date.getMonth() + 1) < 10 then "0#{(date.getMonth() + 1)}"   else (date.getMonth() + 1)
-        day     = if date.getDate()        < 10 then "0#{date.getDate()}"          else date.getDate()
-        hour    = if date.getHours()       < 10 then "0#{date.getHours()}"         else date.getHours()
-        minute  = if date.getMinutes()     < 10 then "0#{date.getMinutes()}"       else date.getMinutes()
-        second  = if date.getSeconds()     < 10 then "0#{date.getSeconds()}"       else date.getSeconds()
-        "#{year}/#{month}/#{day} #{hour}:#{minute}:#{second}"
-      tt2lt: (time)->
-        date = new Date()
-        date.setTime(Date.parse("#{time} GMT+0900"))
-        year    = date.getFullYear()
-        month   = if (date.getMonth() + 1) < 10 then "0#{(date.getMonth() + 1)}"   else (date.getMonth() + 1)
-        day     = if date.getDate()        < 10 then "0#{date.getDate()}"          else date.getDate()
-        hour    = if date.getHours()       < 10 then "0#{date.getHours()}"         else date.getHours()
-        minute  = if date.getMinutes()     < 10 then "0#{date.getMinutes()}"       else date.getMinutes()
-        second  = if date.getSeconds()     < 10 then "0#{date.getSeconds()}"       else date.getSeconds()
-        "#{year}/#{month}/#{day} #{hour}:#{minute}:#{second}"
-      ls2n: (sword_id)->
-        if sword_id == null || parseInt(sword_id, 10) == 0
-          return '未获得'
-        exports.tohken.define.tohkens[sword_id]['name']
-      fs2n: (sword_id)->
-        return '???' unless @status['show_forge']
-        if sword_id == null || parseInt(sword_id, 10) == 0
-          return '未获得'
-        exports.tohken.define.tohkens[sword_id]['name']
-      r2r: (rank)->
-        r = ['S', 'A', 'B', 'C']
-        return r[rank-2]
-
-      sid2n: (sid)->
-        exports.tohken.define.tohkens[@data['sword']['data'][sid]['sword_id']]['name']
-    }
-
-    methods: {
-      # Route the request
-      route: (request, action)->
-        exports.tohken.router.call this, request, action
-        return if @config['dataCache'] == 0
-        exports.tohken.store.set "TRH_DataCache", JSON.stringify(@data)
-
-      # Parse data to json
-      parser: (request, callback)->
-        spost = request['request']['postData']['text'].replace(/%5F/g, '_').split('&')
-        post = {}
-        _.forEach spost, (n)->
-          t = n.split('=')
-          post[t[0]] = t[1]
-        request.getContent (content, encoding)->
-          con = JSON.parse(content)
-          con['post_data'] = post
-          callback.call this, con
-
-      # Apply Config
-      applyConfig: (key, value)->
-        if key == 'dataCache' && value == -1
-          exports.tohken.store.remove "TRH_DataCache"
-          @dataExist = false
-          return
-        @config[key] = value
-        exports.tohken.store.set "TRH_Config", JSON.stringify(@config)
-
-      # Load Data
-      loadData: ->
-        # Config
-        @config = exports.tohken.store.get "TRH_Config", exports.tohken.config, true
-        # CheckData
-        ex = exports.tohken.store.get "TRH_DataCache", "nil", true
-        @dataExist = true if ex != "nil"
-        # Data
-        return if @config['dataCache'] == 0
-        if @dataExist
-          @data = exports.tohken.store.get "TRH_DataCache", exports.tohken.data, true
-          exports.tohken.parse.view.call this, 'player'
-          exports.tohken.parse.view.call this, 'resource'
-          exports.tohken.parse.view.call this, 'equip'
-          exports.tohken.parse.view.call this, 'party'
-          exports.tohken.parse.conquest.call this, @data['party']['data']
-        'done'
-      # Send Message to background page
-      sendMessage: (msg)->
-        chrome.runtime.sendMessage(msg)
-
-      grouter: (s)->
-        @status['router'] == s
-
-      in_battle: (p)->
-        @status['in_battle'] && (parseInt(@status['battle_id']) == parseInt(p, 10))
-
-      t2t: (time)->
-        date = new Date()
-        date.setTime time
-        year    = date.getFullYear()
-        month   = if (date.getMonth() + 1) < 10 then "0#{(date.getMonth() + 1)}"   else (date.getMonth() + 1)
-        day     = if date.getDate()        < 10 then "0#{date.getDate()}"          else date.getDate()
-        hour    = if date.getHours()       < 10 then "0#{date.getHours()}"         else date.getHours()
-        minute  = if date.getMinutes()     < 10 then "0#{date.getMinutes()}"       else date.getMinutes()
-        second  = if date.getSeconds()     < 10 then "0#{date.getSeconds()}"       else date.getSeconds()
-        "#{year}/#{month}/#{day} #{hour}:#{minute}:#{second}"
-
-      s2n: (sword_id)->
-        if sword_id == null || parseInt(sword_id, 10) == 0
-          return '未获得'
-        exports.tohken.define.tohkens[sword_id]['name']
-
-      r2r: (rank)->
-        r = ['S', 'A', 'B', 'C']
-        return r[rank-2]
-
-      checkout: (type)->
-        console.log @data['repair']['data']
-        switch type
-          when 'repair'
-            r = 0
-            _.forEach @data['repair']['data'], (v)->
-              r = r + 1
-            return r
-          when 'forge'
-            r = 0
-            _.forEach @data['forge']['data'], (v)->
-              r = r + 1
-            return r
-        'done'
-
-      toggleForge: ->
-        @status['show_forge'] = !@status['show_forge']
-
-      saveLogs: ->
-        blob = new Blob([JSON.stringify(@data['logs'])], {type: "text/plain;charset=utf-8"})
-        saveAs(blob, "TRHData#{Date.now()}.json")
-        # 生成 CSV
-        forge = "\"时间\",\"结果\",\"耗时\",\"木炭\",\"玉鋼\",\"冷却材\",\"砥石\""
-        _.forEach @data['logs']['forge'], (v, k)=>
-          if v['resource']
-            forge += "\n\"'#{@t2t(v['finish_at'])}\",\"'#{@s2n(v['sword_id'])}\",\"'#{v['times']}\",\"'#{v['charcoal']}\",\"'#{v['steel']}\",\"'#{v['coolant']}\",\"'#{v['file']}\""
-          else
-            forge += "\n\"'#{@t2t(v['finish_at'])}\",\"'#{@s2n(v['sword_id'])}\",\"-\",\"-\",\"-\",\"-\",\"-\""
-        blob = new Blob([forge], {type: "text/plain;charset=utf-8"})
-        saveAs(blob, "TRHForge#{Date.now()}.csv")
-        # 战斗
-        battle = "\"时间\",\"队伍\",\"评分\",\"获得\",\"地图\",\"位置\""
-        _.forEach @data['logs']['battle'], (v, k)=>
-          battle += "\n\"'#{@t2t(v['time'])}\",\"'#{v['party_no']}\",\"'#{@r2r(v['rank'])}\",\"'#{@s2n(v['get_sword_id'])}\",\"'#{v['battle_episode']}-#{v['battle_field']}\",\"'#{v['battle_pos']}\""
-        blob = new Blob([battle], {type: "text/plain;charset=utf-8"})
-        saveAs(blob, "TRHBattle#{Date.now()}.csv")
-    }
-  }
-)(window)


change o.error to o.debug

change o.error to o.debug

--- src/coffee/breadcrumbsEatr.coffee
+++ src/coffee/breadcrumbsEatr.coffee
@@ -87,12 +87,12 @@
         o.init = ->
             # check if underscore.js is required.
             if typeof window._ is "undefined"
-                o.error 'underscore.js is required.'
+                o.debug 'underscore.js is required.'

                 return false

             if typeof window.Modernizr is "undefined"
-                o.error 'Modernizr is required.'
+                o.debug 'Modernizr is required.'

                 return false


add a variable to check the status of resize

--- src/coffee/breadcrumbsEatr.coffee
+++ src/coffee/breadcrumbsEatr.coffee
@@ -117,6 +117,7 @@
             o.windowWidth   = o.browserWindow.width()
             o.unwrapWidth   = o.getChildrenWidth()   # unwrapped width for the breadcrumbs
             o.documentBody  = $ document.body
+            o.resizeStarted = false

             o.opts.onLoad(_this) if $.isFunction(o.opts.onLoad)


i18n-dir defaults to 'i18n'

i18n-dir defaults to 'i18n'

--- views/services/plugin-manager.cjsx
+++ views/services/plugin-manager.cjsx
@@ -495,20 +495,19 @@
         plugin.description = "unknown"

     # i18n
-    if plugin['i18n-dir']?
-      i18nFile = path.join pluginPath, plugin['i18n-dir']
-      namespace = plugin.packageName
-      window.i18n[namespace] = new (require 'i18n-2')
-        locales: ['en-US', 'ja-JP', 'zh-CN', 'zh-TW'],
-        defaultLocale: 'zh-CN',
-        directory: i18nFile,
-        updateFiles: false,
-        indent: "\t",
-        extension: '.json'
-        devMode: false
-      window.i18n[namespace].setLocale(window.language)
-      plugin.name = window.i18n[namespace].__ plugin.name
-      plugin.description = window.i18n[namespace].__ plugin.description
+    i18nFile = path.join pluginPath, (plugin['i18n-dir'] || 'i18n')
+    namespace = plugin.packageName
+    window.i18n[namespace] = new (require 'i18n-2')
+      locales: ['en-US', 'ja-JP', 'zh-CN', 'zh-TW'],
+      defaultLocale: 'zh-CN',
+      directory: i18nFile,
+      updateFiles: false,
+      indent: "\t",
+      extension: '.json'
+      devMode: false
+    window.i18n[namespace].setLocale(window.language)
+    plugin.name = window.i18n[namespace].__ plugin.name
+    plugin.description = window.i18n[namespace].__ plugin.description

     plugin.pluginPath = pluginPath
     plugin.enabled = config.get "plugin.#{plugin.packageName}.enable", true


premature optimization

premature optimization

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -233,7 +233,8 @@

 Zotero.BetterBibTeX.clearCiteKeys = (onlyCache) ->
   win = @windowMediator.getMostRecentWindow('navigator:browser')
-  for item in Zotero.Items.get((item.id for item in win.ZoteroPane.getSelectedItems() when !item.isAttachment() && !item.isNote()))
+  items = Zotero.Items.get((item.id for item in win.ZoteroPane.getSelectedItems() when !item.isAttachment() && !item.isNote()))
+  for item in items
     @clearKey(item, onlyCache)
   return items


this ought to deal with #192

this ought to deal with #192

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -424,22 +424,21 @@
         delete @_collection
         @_items = Zotero.Items.getAll(false, @_group.libraryID)

-      # regular behavior for non-BBT translators
+      # regular behavior for non-BBT translators, or if translating to string
       header = Zotero.BetterBibTeX.translators[translatorID]
-      return original.apply(this, arguments) unless header
-
-      if @location && typeof @location == 'object'
-        # export path for relative exports
-        @_displayOptions.exportPath = @location.path.slice(0, -@location.leafName.length)
-        path = @location.path
-        ext = ".#{header.target}"
-        path += ext unless path.slice(-ext.length).toLowerCase() == ext
-        Zotero.BetterBibTeX.debug('export to', path, ext)
+      return original.apply(this, arguments) unless header && @_displayOptions && @location?.path
+
+      if @_displayOptions.exportFileData # export directory selected
+        location = @location.clone()
+        location.append(location.leafName + '.' + header.target)
+      else
+        location = @location
+
+      Zotero.BetterBibTeX.debug('export to', location.path)
+      @_displayOptions.exportPath = location.parent.path

       # If no capture, we're done
-      return original.apply(this, arguments) unless @_displayOptions?['Keep updated'] && path
-
-      delete @_displayOptions.exportFileData
+      return original.apply(this, arguments) unless @_displayOptions?['Keep updated']

       progressWin = new Zotero.ProgressWindow()
       progressWin.changeHeadline('Auto-export')
@@ -466,8 +465,8 @@

       if collection
         @_displayOptions.translatorID = translatorID
-        Zotero.BetterBibTeX.auto.add(collection, path, @_displayOptions)
-        Zotero.BetterBibTeX.debug('Captured auto-export:', path, Zotero.BetterBibTeX.log.object(@_displayOptions))
+        Zotero.BetterBibTeX.auto.add(collection, location.path, @_displayOptions)
+        Zotero.BetterBibTeX.debug('Captured auto-export:', location.path, Zotero.BetterBibTeX.log.object(@_displayOptions))

       return original.apply(this, arguments)
     )(Zotero.Translate.Export::translate)


Client: remove decorate follow button from avatartooltipview

--- client/app/lib/commonviews/avatarviews/avatartooltipview.coffee
+++ client/app/lib/commonviews/avatarviews/avatartooltipview.coffee
@@ -54,34 +54,11 @@
   click:(event)->
     # @getDelegate()?.getTooltip().hide()

-  decorateFollowButton:(data)->
-
-    # no dummy data!
-    return unless data.getId?
-
-    unless data.followee?
-      whoami().isFollowing? data.getId(), "JAccount", (err, following)=>
-        data.followee = following
-        kd.warn err  if isLoggedIn()
-        if data.followee
-          @followButton.setClass 'following-btn'
-          @followButton.setState "Following"
-        else
-          @followButton.setState "Follow"
-          @followButton.unsetClass 'following-btn'
-    else
-      if data.followee
-        @followButton.setClass 'following-btn'
-        @followButton.setState "Following"
-    @followButton.setData data
-    @followButton.render()

   updateData:(data={})->

     # lazy loading data is spoonfed to the individual views
     @setData data
-
-    @decorateFollowButton data

     @profileName.setData data
     @profileName.render()


Workspace: Changed SplitView to SplitViewOlderSiblings.

--- client/landing/app/CommonViews/workspace/workspacelayout.coffee
+++ client/landing/app/CommonViews/workspace/workspacelayout.coffee
@@ -18,4 +18,4 @@

         views.push wrapper

-    new KDSplitView { type, sizes, views }+    return new SplitViewWithOlderSiblings { type, sizes, views }


Very rudimentary first version, illustrating the idea

--- core/app/backbone/views/activities/activities_group_view.coffee
+++ core/app/backbone/views/activities/activities_group_view.coffee
@@ -91,3 +91,7 @@
   @actions: ["followed_user"]
   actions: -> UsersFollowedGroupView.actions

+  appendHtml: (collectionView, itemView, index) ->
+    return if @collection.models.length > 1 &&
+      itemView.model.get('activity').followed_user.username == @collection.models[@collection.length - 2].get('activity').followed_user.username
+    super


Update build task

Update build task

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -131,9 +131,19 @@
     cp 'src', path.join(appDir, 'src'), filter: /.+\.(cson|coffee|cjsx)$/
     cp 'static', path.join(appDir, 'static')

-    cp path.join('zpm', 'node_modules', 'zenit-package-manager'), path.resolve(appDir, '..', 'new-app', 'zpm'), filter: filterNodeModule
-    if process.platform isnt 'win32'
-      fs.symlinkSync(path.join('..', '..', 'bin', 'zpm'), path.resolve(appDir, '..', 'new-app', 'zpm', 'node_modules', '.bin', 'zpm'))
+    # Move all of the node modules inside /apm/node_modules to new-app/apm/node_modules
+    apmInstallDir = path.resolve(appDir, '..', 'new-app', 'zpm')
+    mkdir apmInstallDir
+    cp path.join('zpm', 'node_modules'), path.resolve(apmInstallDir, 'node_modules'), filter: filterNodeModule
+
+    # Move /zpm/node_modules/zenit-package-manager to new-app/apm. We're essentially
+    # pulling the zenit-package-manager module up outside of the node_modules folder,
+    # which is necessary because npmV3 installs nested dependencies in the same dir.
+    apmPackageDir = path.join(apmInstallDir, 'node_modules', 'zenit-package-manager')
+    for name in fs.readdirSync(apmPackageDir)
+      fs.renameSync path.join(apmPackageDir, name), path.join(apmInstallDir, name)
+    fs.unlinkSync(path.join(apmInstallDir, 'node_modules', '.bin', 'zpm'))
+    fs.rmdirSync(apmPackageDir)

     dependencies = ['compile', 'generate-module-cache', 'compile-packages-slug']
     dependencies.push('set-exe-icon') if process.platform is 'win32'


Fix the Desktop file we pass to mkdeb

Fix the Desktop file we pass to mkdeb

--- build/tasks/mkdeb-task.coffee
+++ build/tasks/mkdeb-task.coffee
@@ -57,14 +57,15 @@

       controlFilePath = fillTemplate(path.join('resources', 'linux', 'debian', 'control'), data)
       desktopFilePath = fillTemplate(path.join('resources', 'linux', 'app.desktop'), data)
+      realDesktopFilePath = path.join(path.dirname(desktopFilePath), "#{pkgName}.desktop")

-      cp(desktopFilePath, path.join(path.dirname(desktopFilePath), "#{pkgName}.desktop"))
+      cp(desktopFilePath, realDesktopFilePath)
       rm(desktopFilePath)

       icon = path.join('resources', 'app.png')

       cmd = path.join('script', 'mkdeb')
-      args = [version, arch, controlFilePath, desktopFilePath, icon, buildDir, data.name]
+      args = [version, arch, controlFilePath, realDesktopFilePath, icon, buildDir, data.name]

       grunt.verbose.ok "About to invoke #{cmd} #{args.join(' ')}"
       spawn {cmd, args}, (error) ->


AddWorkspaceView: Return no when clicked to prevent node changing on sidebar.

--- client/Main/addworkspaceview.coffee
+++ client/Main/addworkspaceview.coffee
@@ -17,6 +17,10 @@
       click    : @bound 'destroy'

+  click: ->
+    return no
+
+
   handleKeyDown: (event) ->

     if event.which is 13


Remove .prepare and add .load to loader

Remove .prepare and add .load to loader

--- src/moka/applicationLoader.coffee
+++ src/moka/applicationLoader.coffee
@@ -5,15 +5,9 @@
 AppNotLoaded = 0
 LoadingApp = 1
 AppLoaded = 2
-loadApp = null
 readyObservers_ = []
-preparesObservers_ = []
 appLoadingStatus_ = AppNotLoaded
-internalReadyCallback_ = null
 domLoaded_ = false
-prepareCalls_ = []
-forceLoadApp_ = false
-

 isFunction = (arg) ->
   typeof arg is "function"
@@ -24,51 +18,6 @@
     obs[i]()
     i++
   obs.length = 0
-
-loadApplication = ->
-  if domLoaded_
-    loadApp() if appLoadingStatus_ is AppNotLoaded
-  else
-    forceLoadApp_ = true
-
-
-buildAppLoader = () ->
-  (cb) ->
-    loadApp_ (cb) ->
-      notifyObservers readyObservers_
-      i = 0
-      api = window[namespace]
-      method = api["prepare"]
-      while i < prepareCalls_.length
-        method.apply api, prepareCalls_[i]
-        i++
-
-      # TODO: before or after invoking cb()? I would say after...
-      applicationDidLoad()
-
-      cb() if cb
-
-
-loadApp_ = (callback) ->
-  console.log('load app')
-  callback()  if appLoadingStatus_ is AppLoaded
-  return  unless appLoadingStatus_ is AppNotLoaded
-  appLoadingStatus_ = LoadingApp
-  loadScript applicationURL(applicationOptions), ->
-    console.log('loaded')
-    window[privateNamespace]["init"] applicationOptions, ->
-      appLoadingStatus_ = AppLoaded
-      callback()
-
-domReady = ->
-  ->
-    if forceLoadApp_ or shouldLoadApplication()
-      loadApp ->
-        notifyObservers preparesObservers_
-    else
-      notifyObservers readyObservers_
-
-

 loadScript = (src, callback) ->
   if src
@@ -82,16 +31,9 @@
       if not callback.done and (not state or (/loaded|complete/).test(state))
         callback.done = true
         callback()
-
     _container.appendChild _element
   else
     callback()
-onReady = (callback) ->
-  if domLoaded_
-    callback()
-  else
-    internalReadyCallback_ = callback
-    bindReady()

 domDidLoad = ->
   ready = 0
@@ -101,10 +43,9 @@
   else if doc.addEventListener
     doc.removeEventListener DOMContentLoadedString, domDidLoad, false
     ready = 1
-
   if ready and not domLoaded_
     domLoaded_ = true
-    internalReadyCallback_()  if internalReadyCallback_
+    loadApplication() if shouldLoadApplication() or forceAppLoading_

 bindReady = ->
   if doc.addEventListener
@@ -117,6 +58,7 @@
     try
       toplevel = not _window.frameElement?
     doScrollCheck()  if doc.docElement and doc.docElement.doScroll and toplevel
+
 doScrollCheck = ->
   unless domLoaded_
     try
@@ -134,31 +76,35 @@
     error: f
     debug: f

+forceAppLoading_ = no
+
+loadApplication = ->
+  return  unless appLoadingStatus_ is AppNotLoaded
+  appLoadingStatus_ = LoadingApp
+  loadScript applicationURL(applicationOptions), ->
+    window[privateNamespace]["init"] applicationOptions, ->
+      appLoadingStatus_ = AppLoaded
+      notifyObservers readyObservers_
+      applicationDidLoad()
+  `undefined`
+
 api = window[namespace]
-if api and api['ready'] and api['prepare']
-  console.error 'SublimeVideo loader has been injected more than once.'
+if api and api['ready'] and api['load']
+  console.error 'SublimeVideo loader has been installed more than once.'
 else
   api = window[namespace] = {}
   api["ready"] = (observer) ->
-    readyObservers_.push observer  if isFunction(observer)
+    if appLoadingStatus_ is AppLoaded
+      observer()
+    else
+      readyObservers_.push observer if isFunction(observer)
+    `undefined`

-  api["prepare"] = (arg1, arg2) ->
-    observer = (if isFunction(arg1) then arg1 else (if isFunction(arg2) then arg2 else null))
-    if appLoadingStatus_ is LoadingApp
-      prepareCalls_.push [ arg1, arg2 ]
-    else if appLoadingStatus_ is AppNotLoaded
-      if shouldLoadApplication()
-        preparesObservers_.push observer if observer
-        if domLoaded_
-          prepareCalls_.push [ arg1, arg2 ]
-          loadApp ->
-            notifyObservers preparesObservers_
-      else (if (domLoaded_) then observer() else readyObservers_.push(observer))  if observer
-    undefined
+  api["load"] = ->
+    if domLoaded_
+      loadApplication()
+    else
+      forceAppLoading_ = yes

-  loadApp = buildAppLoader()
   customAPI()
-  onReady domReady()
-
-
-
+  bindReady()


delete log message oops

delete log message oops

--- src/smc-webapp/client_browser.coffee
+++ src/smc-webapp/client_browser.coffee
@@ -175,7 +175,7 @@
     # considered idle, and also emit event indicator using is currently active.
     _idle_reset: =>
         @_idle_time = (new Date()).getTime() + @_idle_timeout + 1000
-        console.log '_idle_reset', new Date(@_idle_time), ' _idle_timeout=', @_idle_timeout
+        # console.log '_idle_reset', new Date(@_idle_time), ' _idle_timeout=', @_idle_timeout
         @emit('idle', 'active')

     # Change the standby timeout to a particular time in minutes.


disable jupyter + firefox

disable jupyter + firefox

--- src/smc-webapp/editor_jupyter.coffee
+++ src/smc-webapp/editor_jupyter.coffee
@@ -789,13 +789,11 @@
             mode              : undefined   # ignored
             default_font_size : 14          # set in editor.coffee
             cb                : undefined   # optional
-        ###
         if $.browser.firefox
-            @element = $("
Unfortunately, Jupyter notebooks are not currently supported in SageMathCloud using Firefox.
Please use Google Chrome or Safari.
")
+            @element = $("
Unfortunately, Jupyter notebooks are not currently supported in SageMathCloud using Firefox.
Please use Google Chrome or Safari.
")
             @element.data("jupyter_notebook", @)
             opts.cb?()
             return
-        ###
         @project_id = @parent.project_id
         @editor = @parent.editor
         @read_only = opts.read_only


Changed capitalization, watching changes in config.

--- lib/bookmarklet.coffee
+++ lib/bookmarklet.coffee
@@ -4,10 +4,10 @@

 module.exports =
   bookmarkletView: null
-  includeJQuery: false
+  includeJquery: false

   configDefaults:
-    includeJQuery: false
+    includeJquery: false

   activate: (state) ->
     @bookmarkletView = new BookmarkletView(state.bookmarkletViewState)
@@ -17,6 +17,9 @@
     @footer = '})();'
     atom.workspaceView.command "bookmarklet:create-javaScript", => @createJS()
     atom.workspaceView.command "bookmarklet:create-link", => @createLink()
+
+    atom.config.observe 'bookmarklet.includeJquery', callNow:true, (value) =>
+      @includeJquery = value

   deactivate: ->
     @bookmarkletView.destroy()


added error message to districts that dont have a contact

--- src/javascripts/contact_us.coffee
+++ src/javascripts/contact_us.coffee
@@ -51,9 +51,14 @@
     showDistrictContact = (district) ->
       # Hide all contact cards
       $("aside.contact-us.multi-district .vcard").hide()
+      $("aside.contact-us.multi-district .error").remove()

       # Show selected contact card
-      $("#district-#{district}").show()
+      $chosen = $("#district-#{district}")
+      if($chosen.length)
+        $chosen.show()
+      else
+        $chooseDistrict.after('
Kontaktinformation för valt stadsområde saknas.
')

       # Set district in select menu
       $selectDistrict.val district


Send explicit parameters in channel subscription

--- examples/todo-vuejs/app/assets/javascripts/channels/todo.coffee
+++ examples/todo-vuejs/app/assets/javascripts/channels/todo.coffee
@@ -1,4 +1,6 @@
-App.todo = App.cable.subscriptions.create "TodoChannel",
+App.todo = App.cable.subscriptions.create {
+  channel: "TodoChannel"
+  },
   connected: ->
     # Called when the subscription is ready for use on the server


fix block rendering issue

fix block rendering issue

--- models/block.coffee
+++ models/block.coffee
@@ -25,7 +25,7 @@
       @get('title')

   parse: (data) ->
-    if data.kind is 'profile'
+    if data?.kind is 'profile'
       data.user
     else
       data


Fix params to call channelsList (#3687)

Fix params to call channelsList (#3687)

--- packages/rocketchat-ui-sidenav/side-nav/listChannelsFlex.coffee
+++ packages/rocketchat-ui-sidenav/side-nav/listChannelsFlex.coffee
@@ -87,7 +87,7 @@
 			if @channelsList.get().length < @limit.get()
 				@hasMore.set false
 		else
-			Meteor.call 'channelsList', @nameFilter.get(), @limit.get(), @sortChannels.get(), (err, result) =>
+			Meteor.call 'channelsList', @nameFilter.get(), 'public', @limit.get(), @sortChannels.get(), (err, result) =>
 				if result
 					@hasMore.set true
 					@channelsList.set result.channels


output to stdout in export-portals

output to stdout in export-portals

--- src/plugins/export-portals.coffee
+++ src/plugins/export-portals.coffee
@@ -12,29 +12,21 @@

 bootstrap = (callback) ->

-    fs.open './export.csv', 'w', (err, fd) ->
+    cursor = Database.db.collection('Portals').find().toArray (err, portals) ->

         if err
             logger.error '[Export] %s', err.message
             return callback()

-        count = 0
+        for po in portals

-        cursor = Database.db.collection('Portals').find().toArray (err, portals) ->
+            line = []
+            line.push po.title.replace(/,/g, '-').trim() if argv.title or argv.t
+            line.push po.latE6 / 1e6 if argv.latlng or argv.l
+            line.push po.lngE6 / 1e6 if argv.latlng or argv.l
+            line.push po.image if argv.image or argv.I
+            line.push po._id if argv.id or argv.i

-            for po in portals
+            console.log line.join(',')

-                count++
-                line = []
-                line.push po.title.replace(/,/g, '-').trim() if argv.title or argv.t
-                line.push po.latE6 / 1e6 if argv.latlng or argv.l
-                line.push po.lngE6 / 1e6 if argv.latlng or argv.l
-                line.push po.image if argv.image or argv.I
-                line.push po._id if argv.id or argv.i
-
-                fs.writeSync fd, line.join(',') + '\n'
-
-            fs.closeSync fd
-
-            logger.info '[Export] Exported %d portals.', count
-            callback()+        callback()


do not re-write timestamps and userIds when uploading data

--- lib/export_data.coffee
+++ lib/export_data.coffee
@@ -51,12 +51,22 @@
     for _id of raw_data
       raw_doc = raw_data[_id]
       current_doc = coll.findOne({_id:_id})
-      if !current_doc? # if we already have this key, do not update it
-        coll.insert( raw_doc, (error,result) ->
-          if error?
-            console.error 'for collection "'+collection_name+'", _id "'+_id+'": ',error
-            console.error '  raw doc:',raw_doc
-          )
+      if current_doc? # if we already have this key, do not update it
+        console.error "not inserting doc with key "+_id+": we already have that key"
+        continue
+
+      # validate the doc
+      isValid = coll.simpleSchema().namedContext().validate(raw_doc, {modifier: false})
+      if !isValid
+        console.log "invalid doc: ",raw_doc
+        throw new Error("invalid document: "+raw_doc)
+
+      # Insert but not change timestamps, usernames.
+      coll.insert( raw_doc, {validate: false, getAutoValues: false}, (error,result) ->
+        if error?
+          console.error 'for collection "'+collection_name+'", _id "'+_id+'": ',error
+          console.error '  raw doc:',raw_doc
+        )

 # ----


Update test AMI

Update test AMI

--- scripts/test-instance/instance-ami.coffee
+++ scripts/test-instance/instance-ami.coffee
@@ -1,5 +1,5 @@
 module.exports  =
-  current: 'ami-e7a05a8c'
+  current: 'ami-5dd50436'

   # Ordered by creation date
   list: [
@@ -7,4 +7,5 @@
     'ami-b920d7d2'
     'ami-9b02f0f0'
     'ami-e7a05a8c'
+    'ami-5dd50436'
   ]


more work on landing page

more work on landing page

Former-commit-id: 01825862cc9bb662bded330dcfe884c1171667f3 [formerly 3b91924b7d1462143b83407966df6466aa3cbd6c] [formerly 91e97f7e8acb046b819a015ffc3b475b7c136be9 [formerly b52cd4166d5c75e4fb4b21db096a72363d6ebf71]] [formerly 2b04f4a4c6fb84830b37f9469951c8ab642ab490 [formerly b1830c9a0c6f6505cdf8d9424e118356e9a3d2e8] [formerly b91805258c054fbd8a11aeae1f1db2ce857f1f4e [formerly b91805258c054fbd8a11aeae1f1db2ce857f1f4e [formerly 4021769f492fe9f8ee723eba015dc2e7aecc1ecc]]]]
Former-commit-id: 2ef3c1a900a05f3b3a40e1fa305137c34bfaa951 [formerly 4ae83a08d9c4ce7b13fc6e405b38733131ef7a2b] [formerly bfd151f834a336b48a698da2dcd78980bab56a65 [formerly b4f3656e1d4a98539dd0c375c8322f437c905606]]
Former-commit-id: ee72db3a4dd944ddb556f292c14480357de72855 [formerly 835c84c1341db5d24dd796da8824f962d2c763a6]
Former-commit-id: f4e79576b0051f1c74be7476126c07106a683a21
Former-commit-id: 06aa55a4967c22ea20b1f53e7712e71489347bbe

--- @client/saas_landing_page.coffee
+++ @client/saas_landing_page.coffee
@@ -19,6 +19,7 @@

 small_text = _.extend {}, base_text,
   fontSize: 20
+  fontWeight: 300

 h1 = _.extend {}, base_text,
   fontSize: 48
@@ -47,7 +48,6 @@

-
 SaasHomepage = ReactiveComponent
   displayName: "SaasHomepage"
   render: ->
@@ -56,19 +56,19 @@
     if !ui.initialized
       _.extend ui,
         initialized: true
-        video_file_name: 'with_screen'
+        video_file_name: 'deathstar-cam'
         video_file_name_instr: "Only available video is 'deathstar-cam'"
-        video_elements_order: "caption,controls,video"
+        video_elements_order: "caption,video"
         video_elements_order_inst: """
           A list of video elements to show and in which order (top to bottom).
           e.g. \"caption,controls,video\"
           Must contain 'video' somewhere.
           """
-        frame: 'none'
+        frame: 'laptop'
         frame_instr: """"
           Whether to draw a frame around the video, and if so, what it should be.
           Supported values:
-            'gray border', 'mbp', 'windoze', 'laptop', 'app window', 'browser window'
+            'laptop', 'browser', 'none'
           """
       save ui

@@ -80,63 +80,67 @@
       story()

-HEADER_HEIGHT = 75
+HEADER_HEIGHT = 80
 Header = ReactiveComponent
   displayName: "Header"
   render: ->
     current_user = fetch("/current_user")

-    DIV null,
+    DIV
+      style:
+        position: "relative"
+        margin: "0 auto"
+        backgroundColor: logo_red
+        height: HEADER_HEIGHT
+
+
       DIV
         style:
-          position: "relative"
-          margin: "0 auto"
-          backgroundColor: logo_red
-          height: HEADER_HEIGHT
+          width: SAAS_PAGE_WIDTH
+          margin: 'auto'

         IMG
           src: asset("saas_landing_page/considerit_logo.svg")
           style:
             position: "relative"
-            top: 23
-            left: 12
-            height: 68
-
-      # nav menu
-      DIV
-        style:
-          width: SAAS_PAGE_WIDTH
-          margin: 'auto'
-          position: 'relative'
-          top: -34
-
+            top: HEADER_HEIGHT * .05 + 18
+            left: 0
+            height: HEADER_HEIGHT * .95
+
+        # nav menu
         DIV
           style:
+            width: SAAS_PAGE_WIDTH
+            margin: 'auto'
+            position: 'relative'
+            top: -34
+
+          DIV
+            style:
+              position: 'absolute'
+              right: 0
+
+            A
+              style: nav_link_style
+              href: "#pricing"
+              'pricing'
+            A
+              style: nav_link_style
+              href: "#contact"
+              'contact'
+            A
+              style: nav_link_style
+              href: "#story"
+              'story'
+
+        DIV
+          style:
+            fontSize: 24
+            fontWeight: 300
+            color: logo_red
             position: 'absolute'
-            right: 0
-
-          A
-            style: nav_link_style
-            href: "#pricing"
-            'pricing'
-          A
-            style: nav_link_style
-            href: "#contact"
-            'contact'
-          A
-            style: nav_link_style
-            href: "#story"
-            'story'
-
-      DIV
-        style:
-          fontSize: 24
-          fontWeight: 300
-          color: logo_red
-          position: 'absolute'
-          left: 16
-          top: 4 + HEADER_HEIGHT
-        "for thinking together"
+            top: 4 + HEADER_HEIGHT
+          "for thinking together"

 VIDEO_SLIDER_WIDTH = 250
@@ -152,26 +156,92 @@
       controls.playing = true
       save controls

-
-    video_drawn = false
-    captions_drawn = false
-    controls_drawn = false
-
-    DIV null,
-      for video_el in ui.video_elements_order.split(',')
-        video_el = video_el.trim()
-        if video_el == 'video'
-          video_drawn = true
-
-        switch video_el
-          when 'controls'
-            @drawVideoControls(video_drawn, captions_drawn)
-          when 'video'
-            @drawVideo(controls_drawn, captions_drawn)
-          when 'caption'
-            @drawCaptions(video_drawn, captions_drawn)
-          else
-            console.error "#{video_el} is not a valid video element"
+    video = DIV null,
+      @drawCaptions()
+      if ui.frame == 'none'
+        @drawVideoControls()
+      @drawVideo()
+
+    if ui.frame == 'laptop'
+      @drawInLaptop video
+    else if ui.frame == 'browser'
+      @drawInBrowserWindow video
+    else
+      @drawWithoutFrame video
+
+
+  drawInLaptop : (children) ->
+
+    DIV
+      style:
+        marginTop: 70
+        height: 756
+        position: 'relative'
+
+      DIV
+        style:
+          position: 'relative'
+          top: 39
+        children
+
+      IMG
+        src: asset('saas_landing_page/laptop-frame.png')
+        style:
+          top: 0
+          position: 'absolute'
+          left: -130
+          pointerEvents: 'none'
+
+  drawWithoutFrame : (children) ->
+
+    DIV
+      style:
+        marginTop: 70
+
+      children
+
+  drawInBrowserWindow : (children) ->
+    DIV
+      style:
+        border: "1px solid #ccc"
+        borderTop: 'none'
+        borderRadius: 8
+        backgroundColor: 'white'
+        boxShadow: "0 3px 8px rgba(0,0,0,.1)"
+        marginTop: 70
+
+      # window bar / url
+      DIV
+        style:
+          height: 31
+          borderRadius: '8px 8px 0 0'
+          backgroundColor: '#ccc'
+          backgroundImage: "linear-gradient(0deg, #D4D3D4, #EEEDEE)"
+          boxShadow: "0 1px 1px rgba(0,0,0,.35)"
+
+        # url area
+        DIV
+          style:
+            position: 'relative'
+            width: 300
+            margin: 'auto'
+            height: 20
+            top: (31 - 20) / 2
+            borderRadius: 4
+            backgroundColor: 'white'
+            boxShadow: "0 1px 1px rgba(0,0,0,.1)"
+            textAlign: 'center'
+            fontSize: 12
+
+          SPAN
+            style:
+              position: 'relative'
+              top: 3
+            'http://video-demo.consider.it'
+
+      DIV null,
+
+        children

   drawCaptions : ->
@@ -180,17 +250,21 @@

     DIV
       style:
-        marginTop: 10
         position: 'relative'
+        zIndex: 1
+        top: if ui.frame == 'laptop' then -12

       H2
         style: _.extend {}, h2,
           textAlign: "center"
+          fontSize: if ui.frame == 'laptop' then 30 else 36
+          color: if ui.frame == 'laptop' then 'white' else 'black'
+

         if chapter.text
           chapter.text
         else
-          "Sometimes a proposal bears careful thought"
+          ""

   drawVideoControls : ->
     controls = fetch('video_controls')
@@ -269,14 +343,17 @@
     DIV
       id: "homepage_video"
       style:
-        width: SAAS_PAGE_WIDTH
+        width: SAAS_PAGE_WIDTH - 1
+        height: 551

       VIDEO
         preload: "auto"
         loop: true
         autoPlay: true
-        width: SAAS_PAGE_WIDTH
+        width: SAAS_PAGE_WIDTH - 2
+        height: 551
         ref: "video"
+        controls: if ui.frame != 'none' then true

         SOURCE
           src: asset("saas_landing_page/#{ui.video_file_name}.mp4")
@@ -304,36 +381,34 @@
       controls = fetch('video_controls')

       text =
-        if v.currentTime < 5
+        if v.currentTime < 5.5
           "Proposals often require careful thought"
-        else if v.currentTime < 10
+        else if v.currentTime < 10.5
           "The Pro/Con list encourages thinking about tradeoffs"
-        else if v.currentTime < 14.5
+        else if v.currentTime < 18
           "Singular points describe a consideration"
-        else if v.currentTime < 15
-          "Singular points describe a consideration"
-        else if v.currentTime < 23
+        else if v.currentTime < 25
           "Learn and build from other\’s thoughts"
-        else if v.currentTime < 32
+        else if v.currentTime < 33.5
           "Use a slider to express your conclusion"
-        else if v.currentTime < 37
+        else if v.currentTime < 39
           "Integrate your considered opinion with the group"
-        else if v.currentTime < 41
+        else if v.currentTime < 43
           "Histogram shows the spectrum of opinion"
         else if v.currentTime < 46
           "Points ranked by salience across all individuals"
-        else if v.currentTime < 53
+        else if v.currentTime < 55
           "Now explore patterns of thought!"
-        else if v.currentTime < 58
+        else if v.currentTime < 61
           "Understand the reservations of proposal opposers"
-        else if v.currentTime < 65
+        else if v.currentTime < 67.5
           "Inspect what a particular individual believes"
-        else if v.currentTime < 67.5
+        else if v.currentTime < 72
           "Figure out who has been persuaded by the top Pro"
-        else if v.currentTime < 84
+        else if v.currentTime < 87
           "Drill into points for focused discussion"
         else
-          "For small and large groups alike"
+          ""

       controls.value = v.currentTime / v.duration

@@ -378,27 +453,27 @@

 usedFor = ->
-
+  ui = fetch('homepage_ui')
   DIV
     style:
-      marginTop: 60
+      marginTop: if ui.frame == 'laptop' then 30 else 60

     H1 style: h1,
       'The first forum to function better when more people participate'

     bullet
       point_style: 'bullet'
-      strong: "Collecting opinions on specific ideas"
+      strong: "Collects opinions on specific ideas"
       body: ", proposals, plans of action, or hypotheticals. Even job candidates, products, or designs."

     bullet
       point_style: 'bullet'
-      strong: "Fostering considerate interactions"
+      strong: "Fosters considerate interactions"
       body: ". The interface makes it easy to consider all sides without putting people in direct conflict. Personal attacks and trolling don’t fit the format."

     bullet
       point_style: 'bullet'
-      strong: "Producing an interactive summary of group thought"
+      strong: "Produces an interactive summary of group thought"
       body: ". Patterns of thought across the whole group can be identified. Perhaps 80% of opposers have a single con point that can be addressed!"

@@ -521,6 +596,10 @@
         email: "toomim@consider.it"

 story = ->
+  story_link = _.extend {}, a, small_text,
+    fontWeight: 500
+    textDecoration: 'none'
+
   DIV
     id: 'story'
     style:
@@ -539,9 +618,10 @@
       DIV
         style: _.extend {}, small_text,
           display: 'inline-block'
-          width: 500
+          width: SAAS_PAGE_WIDTH * .6 - 40
           marginRight: 40
           verticalAlign: 'top'
+

         P
@@ -553,24 +633,25 @@
           Computer Science """

           A
-            style: _.extend {}, a, small_text
+            style: story_link
             href: 'https://www.dropbox.com/s/ycqfoxdl5sghaud/dissertation.pdf?dl=0'
             "PhD dissertation"

-          """
-          at the University of Washington, in collaboration with fellow graduate
-          student Michael Toomim, and Professor """
+
+          ' at the University of Washington, in collaboration with fellow graduate '
+          'student Michael Toomim, Professor '

           A
-            style: _.extend {}, a, small_text
+            style: story_link
             href: 'http://www.cs.washington.edu/people/faculty/borning'
             'Alan Borning'

-          """. The project was funded by a generous National
-          Science Foundation grant. Other early contributors to the research project
-          include Jonathan Morgan (phd...), Deen Freelon, Lance Bennett, Sheetal
-          Agarwal, and Andrew Hamada.
-          """
+          ', Jonathan Morgan, and a team of political communication researchers. The project was funded by a generous '
+          A
+            href: "http://www.nsf.gov/awardsearch/showAward?AWD_ID=0966929"
+            style: story_link
+            "National Science Foundation grant"
+          '.'

         P null,
           """
@@ -582,18 +663,22 @@
       DIV
         style: _.extend {}, small_text,
           display: 'inline-block'
-          style:
-            width: 400
-            verticalAlign: 'top'
+          width: SAAS_PAGE_WIDTH * .4
+          marginTop: 10

         IFRAME
-          width: 347
-          height: 211
-          src: "https://www.youtube.com/embed/RIUD4Ty2ZAE"
+          width: 400
+          height: 243
+          src: "https://www.youtube.com/embed/jl1AsVM_8hk?modestbranding=1&showinfo=0&theme=dark&fs=1"
           frameborder: "0"
           allowfullscreen: true
-
+
+        SPAN
+          style:
+            fontSize: 14
+            fontWeight: 400
+          'Travis\' Phd defense, from December 2011'

     DIV
@@ -603,35 +688,65 @@
       DIV
         style: _.extend {}, small_text,
           display: 'inline-block'
-          verticalAlign: 'top'
+          verticalAlign: 'middle'
+          width: SAAS_PAGE_WIDTH * .4
+          marginTop: 10
+
+        IMG
+          src: asset('saas_landing_page/sifp.jpg')
           width: 400
-          marginRight: 40
-
-        IFRAME
-          width: 400
-          height: 243
-          src: "https://www.youtube.com/embed/RIUD4Ty2ZAE"
-          frameborder: "0"
-          allowfullscreen: true
-
+        SPAN
+          style:
+            fontSize: 14
+            fontWeight: 400
+          'Travis delivers the '
+          A
+            style: _.extend {}, a,
+              fontSize: 14
+            href: 'https://www.youtube.com/watch?v=RIUD4Ty2ZAE'
+            'winning talk'
+          ' at Social Innovation Fast Pitch'

       DIV
         style: _.extend {}, small_text,
           display: 'inline-block'
-          width: 500
+          width: SAAS_PAGE_WIDTH * .6 - 40
           verticalAlign: 'top'
+          marginLeft: 40

         P
           style:
             paddingBottom: 15

-          """
-          The original and longest-running Consider.it application is the
-          award-winning Living Voters Guide, a crowd-sourced guide to public
-          thought on Washington State’s ballot measures, with on-demand
-          fact-checking by Seattle Public Librarians. Our partners Seattle
-          CityClub have hosted the dialogue for five years and counting.
-          """
+          'The original and longest-running Consider.it application is the '
+
+          A
+            style: story_link
+            href: "http://www.evergreenapps.org/congratulations-to-the-winners-of-the-evergreen-apps-challenge/"
+            'award'
+          '-'
+          A
+            style: story_link
+            href: "http://www.socialventurepartners.org/seattle/2012/10/25/2012-sifp-winners/"
+            'winning'
+          ' '
+          A
+            style: story_link
+            href:'https://livingvotersguide.org'
+            "Living Voters Guide"
+
+          """, a crowd-sourced guide to public
+          thought on Washington State’s ballot measures, with """
+          A
+            style: story_link
+            href: "http://blogs.seattletimes.com/monica-guzman/2012/10/27/seattle-library-fact-check-experiment-risky-but-valuable/"
+            "on-demand fact-checking"
+          ' provided by Seattle Public Librarians. Our partners '
+          A
+            style: story_link
+            href: 'http://seattlecityclub.org'
+            "Seattle CityClub"
+          ' have hosted the dialogue for five years and counting.'

         P
           style:
@@ -651,7 +766,7 @@
       DIV
         style: _.extend {}, small_text,
           display: 'inline-block'
-          width: 500
+          width: SAAS_PAGE_WIDTH * .6 - 40
           marginRight: 40
           verticalAlign: 'top'

@@ -660,28 +775,80 @@
             paddingBottom: 15

           """
-          We decided to strike out on our own after growing frustrated by the
-          academic system, leaving our comfortable yet confining ivory tower for the
-          excitement of the unexplored (for us) jungle of capitalism.
-          In those wilds we are creating a decentralized R&D laboratory to
+          We decided to leave our comfortable ivory tower for the
+          excitement of the jungle of capitalism after growing
+          frustrated by the academic system. Out of the
+          frying pan and into the fire, right?
+          """
+
+        P
+          style:
+            paddingBottom: 15
+          """
+          We are creating a decentralized R&D laboratory to
           bring new social technologies into the world. We call this organization
-          The Invisible College.
-          """
-
-        P
-          style:
-            paddingBottom: 15
-
-          """
-          The Invisible College’s non-profit mission is funded by internal
-          for-profit companies based on the social technologies we create.
-          Consider.it is the most mature of these companies.
-          """
+          The Invisible College. The Invisible College’s non-profit mission is
+          funded by internal for-profit companies based on the social technologies
+          we create. Consider.it is the most mature of these companies.
+          """
+
+
+        DIV null,
+          """
+          Published research about Consider.it:
+          """
+          UL
+            style:
+              listStyle: 'none'
+
+            LI
+              style:
+                paddingTop: 5
+
+              A
+                style:  _.extend {}, story_link,
+                  textDecoration: 'underline'
+                href: "http://dub.washington.edu/djangosite/media/papers/kriplean-cscw2012.pdf"
+                'Supporting Reflective Public Thought with Consider.it'
+              DIV
+                style: _.extend {}, small_text,
+                  fontSize: 16
+                '2012 ACM Conference on Computer Supported Cooperative Work'
+
+
+            LI
+              style:
+                paddingTop: 15
+              A
+                style:  _.extend {}, story_link,
+                  textDecoration: 'underline'
+                href: "http://dx.doi.org/10.1080/19331681.2012.665755"
+                'Facilitating Diverse Political Engagement'
+              DIV
+                style: _.extend {}, small_text,
+                  fontSize: 16
+                'Journal of Information Technology & Politics, Volume 9, Issue 3'
+
+            LI
+              style:
+                paddingTop: 15
+              A
+                style:  _.extend {}, story_link,
+                  textDecoration: 'underline'
+                href: "http://homes.cs.washington.edu/~borning/papers/kriplean-cscw2014.pdf"
+                'On-demand Fact-checking in Public Dialogue'
+
+              DIV
+                style: _.extend {}, small_text,
+                  fontSize: 16
+                '2014 ACM Conference on Computer Supported Cooperative Work'
+
+

       DIV
         style: _.extend {}, small_text,
           display: 'inline-block'
-          width: 400
+          width: SAAS_PAGE_WIDTH * .4
           verticalAlign: 'top'
           position: 'relative'

@@ -689,23 +856,40 @@
         IMG
           src: asset('saas_landing_page/b&w_kev_mike.png')
           style:
-            width: 300
+            width: 250
             position: 'relative'
             zIndex: 1

         IMG
           src: asset('saas_landing_page/consult.png')
           style:
-            width: 300
+            width: 250
             position: 'absolute'
-            top: 200
+            top: 150
             left: 80

-
-
-
-
-
+Footer = ->
+
+  DIV
+    style:
+      backgroundColor: logo_red
+      padding: '20px 0'
+    DIV
+      style: _.extend {}, h2,
+        width: SAAS_PAGE_WIDTH
+        margin: 'auto'
+        color: 'white'
+
+      'Friend, that is our story.'
+      BR null,
+      'Tell us '
+      A
+        style: _.extend {}, a,
+          fontSize: 36
+          color: 'white'
+        href: 'mailto:admin@consider.it'
+        'your story'
+      '. What led you here?'

 Page = ReactiveComponent
@@ -745,7 +929,7 @@
       Header()
       DIV
         style:
-          width: 1000
+          width: SAAS_PAGE_WIDTH
           backgroundColor: "white"
           margin: "auto"
           paddingBottom: 20
@@ -753,6 +937,8 @@
         BrowserHacks()
         Page(key: "/page" + loc.url)

+      Footer()
+
       Tooltip()

       if app.dev


fix vertical flip

fix vertical flip

--- static/coffee/glyph_renderers.coffee
+++ static/coffee/glyph_renderers.coffee
@@ -87,14 +87,14 @@
         sx[i] = glyph.select(xname, data[i])
     else
       for i in [0..data.length-1]
-        sx[i] = @xmapper.map_screen(glyph.select(xname, data[i]))
+        sx[i] = @plot_view.viewstate.xpos(@xmapper.map_screen(glyph.select(xname, data[i])))

     if y_units == "screen"
       for i in [0..data.length-1]
         sy[i] = glyph.select(yname, data[i])
     else
       for i in [0..data.length-1]
-        sy[i] = @ymapper.map_screen(glyph.select(yname, data[i]))
+        sy[i] = @plot_view.viewstate.ypos(@ymapper.map_screen(glyph.select(yname, data[i])))

     return [sx, sy]


Minor tweak

Minor tweak

--- _assets/javascripts/app.js.coffee
+++ _assets/javascripts/app.js.coffee
@@ -5,11 +5,11 @@
 ), false

 document.getElementById("show-disqus-comments").addEventListener "click", ((event) ->
+  @innerText = "Loading..."
   script = document.createElement("script")
   script.type = "text/javascript"
   script.src = "//dhilipsiva.disqus.com/embed.js"
   document.body.appendChild script
-  @innerText = "Loading..."
 ), false

 # GoSquared tracker


Dirty fix for attach kpis failing to load properly

The real problem is the kpis.svc load method locking issue. When the load
method locks and recursively calls itself, it causing weirdness with the promise chain.
Explicitly resolving a new deferred once the value is finally returned seems to
do the job.

--- src/services/kpis/kpis.svc.coffee
+++ src/services/kpis/kpis.svc.coffee
@@ -25,12 +25,15 @@
       )

     @getAttachableKpis = (widgetEngine) ->
+      deferred = $q.defer()
       _self.load().then(->
-        _.select(_self.getKpisTemplates(), (kpiTemplate) ->
+        templates = _.select(_self.getKpisTemplates(), (kpiTemplate) ->
           return false unless _.isArray(kpiTemplate.attachables)
           _.includes(kpiTemplate.attachables, widgetEngine)
         )
-      )
+        deferred.resolve(templates)
+      )
+      deferred.promise

     #====================================
     # Register Listeners


errors are now computed correctly

errors are now computed correctly

--- src/shared/component/login.coffee
+++ src/shared/component/login.coffee
@@ -5,13 +5,22 @@
   ComponentNavigation
   login
   validateLogin
-  getCursorStates
+  # getCursorStates
 ) ->
   React.createClass
     mixins: [Cursors]
     handleChange: (event) ->
-      command = {page: {data: {}}}
-      command.page.data[event.target.name] = $set: event.target.value
+      # TODO this does not create paths that dont yet exist
+      # this sucks
+      delta = {}
+      delta[event.target.name] = event.target.value
+      # TODO $merge is not a deep merge
+      # but a shallow one
+      command = {page: {data: {$merge: delta}}}
+      # here we simulate the state changes
+      updated = React.addons.update this.state, command
+      errors = validateLogin updated.page.data
+      console.log 'errors', errors
       this.update command
     handleClick: (event) ->
       event.preventDefault()
@@ -25,7 +34,7 @@
       console.log 'ComponentLogin', 'componentWillMount'
       this.update {page: {data: {$set: {}}}}
     componentWillReceiveProps: (nextProps) ->
-      # after calling `this.update()` in `handlChange` there is no guarantee
+      # after calling `this.update()` in `handleChange` there is no guarantee
       # that `this.state` already reflects our changes.
       # `this.update()` calls `this.setState().
       # `this.setState()` does not immediately mutate `this.state` but
@@ -38,11 +47,13 @@
       # (https://facebook.github.io/react/docs/component-specs.html).
       # we need to update the state with validation results in response
       # to prop changes.
-      console.log 'ComponentLogin', 'componentWillReceiveProps'
-      nextState = getCursorStates nextProps.cursors
-      console.log 'nextState', nextState
-      errors = validateLogin nextState.page.data
-      this.update {page: {errors: {$set: errors}}}
+      # console.log 'ComponentLogin', 'componentWillReceiveProps'
+      # nextState = getCursorStates nextProps.cursors
+      # console.log 'nextState', nextState
+      # we might check if they've actually differed
+      # errors = validateLogin nextState.page.data
+      # if errors
+      # this.update {page: {errors: {$set: errors}}}
     render: ->
       that = this
       console.log 'ComponentLogin', 'render', 'this.state', this.state


grunt warn about cs errors instead of console log

--- src/steroids/GruntDefaults.coffee
+++ src/steroids/GruntDefaults.coffee
@@ -42,7 +42,7 @@
           text += "#red[#{path.basename(filePath)}:#{error.lineNumber}] > #yellow[#{error.message if error.message?}] #green[#{'('+error.context+')' if error.context?}]\n\n"
           if error.line?
             text += "\n#{error.line}\n\n\n"
-        console.error colorize.ansify("#{text}\n\nWhat would Richard Dean Anderson do?\n")
+        grunt.warn colorize.ansify("#{text}\n\nWhat would Richard Dean Anderson do?\n")

       try
         compiledSource = coffee.compile(grunt.file.read(filePath, "utf8").toString())


Fix manifest for U-wing.

Fix manifest for U-wing.

fixes #573

--- coffeescripts/manifest.coffee
+++ coffeescripts/manifest.coffee
@@ -3612,6 +3612,11 @@
             name: 'Stealth Device'
             type: 'modification'
             count: 2
+        }
+        {
+            name: 'Flechette Torpedoes'
+            type: 'upgrade'
+            count: 1
         }
         {
             name: 'Sensor Jammer'


Use the new active editor API

Use the new active editor API

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -81,7 +81,7 @@

   buildCodeContext: (argType='Selection Based') ->
     # Get current editor
-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()
     # No editor available, do nothing
     return unless editor?


reset `@method` after sending request to OUT and on `connect` to METHOD

--- components/MakeRequest.coffee
+++ components/MakeRequest.coffee
@@ -10,15 +10,19 @@
     @method = 'GET'

     @inPorts =
-      in:    new Port 'string'
+      in:     new Port 'string'
       method: new Port 'string'
     @outPorts =
       out: new Port 'object'
+
+    @inPorts.method.on 'connect', () =>
+      @method = 'GET'

     @inPorts.method.on 'data', (@method) =>

     @inPorts.in.on 'data', (url) =>
       @outPorts.out.send request(@method, url)
+      @method = 'GET'

     @inPorts.in.on 'disconnect', () =>
       @outPorts.out.disconnect()


Remove regex variable

Remove regex variable

--- lib/select-next.coffee
+++ lib/select-next.coffee
@@ -35,8 +35,7 @@

   isNonWordCharacter: (character) ->
     nonWordCharacters = atom.config.get('editor.nonWordCharacters')
-    regex = "[\\s#{_.escapeRegExp(nonWordCharacters)}]"
-    new RegExp(regex).test(character)
+    new RegExp("[\\s#{_.escapeRegExp(nonWordCharacters)}]").test(character)

   isNonWordCharacterToTheLeft: (selection) ->
     selectionStart = selection.getBufferRange().start


Pass to access on all tasks

Pass to access on all tasks

--- static/scripts/controllers.coffee
+++ static/scripts/controllers.coffee
@@ -217,10 +217,9 @@
             $http.get(projectUrl).success (project) =>
                 $scope.project = project
                 ngProgress.complete()
-        .error (data, status) =>
-            ngProgress.complete()
-            if status == 404
-                $location.path _.sprintf '/access/%s/', $routeParams['pk']
+        .error =>
+            ngProgress.complete()
+            $location.path _.sprintf '/access/%s/', $routeParams['pk']

         $scope.taskScrolled = (violation) => (to) =>
             getActive = =>


Fail tests when zero passed

Fail tests when zero passed

--- lib/testing/cli.coffee
+++ lib/testing/cli.coffee
@@ -43,6 +43,9 @@
             **#{stat.passed + stat.failed}** total
         """

+        unless stat.passed
+            err ?= "No tests passed for `#{name}`"
+
     # result
     log.log ''
     if err or not utils.isEmpty(processLogs.errors)


Fix tests in EditSession

Fix tests in EditSession

--- spec/app/edit-session-spec.coffee
+++ spec/app/edit-session-spec.coffee
@@ -1717,10 +1717,10 @@
         editSession.setSelectedBufferRange([[4, 5], [7, 5]])
         editSession.toggleLineCommentsInSelection()

-        expect(buffer.lineForRow(4)).toBe "//     while(items.length > 0) {"
-        expect(buffer.lineForRow(5)).toBe "//       current = items.shift();"
-        expect(buffer.lineForRow(6)).toBe "//       current < pivot ? left.push(current) : right.push(current);"
-        expect(buffer.lineForRow(7)).toBe "//     }"
+        expect(buffer.lineForRow(4)).toBe "    // while(items.length > 0) {"
+        expect(buffer.lineForRow(5)).toBe "    //   current = items.shift();"
+        expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
+        expect(buffer.lineForRow(7)).toBe "    // }"
         expect(editSession.getSelectedBufferRange()).toEqual [[4, 8], [7, 8]]

         editSession.toggleLineCommentsInSelection()
@@ -1732,9 +1732,9 @@
       it "does not comment the last line of a non-empty selection if it ends at column 0", ->
         editSession.setSelectedBufferRange([[4, 5], [7, 0]])
         editSession.toggleLineCommentsInSelection()
-        expect(buffer.lineForRow(4)).toBe "//     while(items.length > 0) {"
-        expect(buffer.lineForRow(5)).toBe "//       current = items.shift();"
-        expect(buffer.lineForRow(6)).toBe "//       current < pivot ? left.push(current) : right.push(current);"
+        expect(buffer.lineForRow(4)).toBe "    // while(items.length > 0) {"
+        expect(buffer.lineForRow(5)).toBe "    //   current = items.shift();"
+        expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
         expect(buffer.lineForRow(7)).toBe "    }"

       it "uncomments lines if all lines match the comment regex", ->


Test the whole output too.

Test the whole output too.

--- spec/buffered-process-spec.coffee
+++ spec/buffered-process-spec.coffee
@@ -119,13 +119,16 @@
     exitCallback = jasmine.createSpy('exit callback')
     baseContent = "There are dozens of us! Dozens! It's as Ann as the nose on Plain's face. Can you believe that the only reason the club is going under is because it's in a terrifying neighborhood? She calls it a Mayonegg. Waiting for the Emmys. BTW did you know won 6 Emmys and was still canceled early by Fox? COME ON. I'll buy you a hundred George Michaels that you can teach to drive! Never once touched my per diem. I'd go to Craft Service, get some raw veggies, bacon, Cup-A-Soup…baby, I got a stew goin'"
     content = (baseContent for _ in [1..200]).join('\n')
+    stdout = ''
+    endLength = 10
     outputAlwaysEndsWithStew = true
     process = new BufferedProcess
       command: '/bin/echo'
       args: [content]
       options: {}
       stdout: (lines) ->
-        endLength = 10
+        stdout += lines
+
         end = baseContent.substr(baseContent.length - endLength, endLength)
         lineEndsWithStew = lines.substr(lines.length - endLength, endLength) is end
         expect(lineEndsWithStew).toBeTrue
@@ -137,3 +140,4 @@

     runs ->
       expect(outputAlwaysEndsWithStew).toBeTrue
+      expect(stdout).toBe content += '\n'


adjusted how handler function objects are loaded, so the can be more easily added to configuration.

--- vendor/assets/javascripts/mercury/mercury.js.coffee
+++ vendor/assets/javascripts/mercury/mercury.js.coffee
@@ -40,11 +40,11 @@
   supported: document.getElementById && document.designMode && !jQuery.browser.konqueror && !jQuery.browser.msie

   # Mercury object namespaces
-  Regions: {}
-  modalHandlers: {}
-  lightviewHandlers: {}
-  dialogHandlers: {}
-  preloadedViews: {}
+  Regions: Mercury.Regions || {}
+  modalHandlers: Mercury.modalHandlers || {}
+  lightviewHandlers: Mercury.lightviewHandlers || {}
+  dialogHandlers: Mercury.dialogHandlers || {}
+  preloadedViews: Mercury.preloadedViews || {}

   # Custom event and logging methods
   bind: (eventName, callback) ->


change expense / revenue labels

change expense / revenue labels


Fix singular form of web UI message

Fix singular form of web UI message

Closes #5753.

--- admin/static/coffee/modals.coffee
+++ admin/static/coffee/modals.coffee
@@ -293,7 +293,10 @@
         super

         # Build feedback message
-        message = "The tables "
+        if @tables_to_delete.length is 1
+            message = "The table "
+        else
+            message = "The tables "
         for table, index in @tables_to_delete
             message += "#{table.database}.#{table.table}"
             if index < @tables_to_delete.length-1


Fixed multiple pop Ups

Fixed multiple pop Ups

--- lib/atox-questions.coffee
+++ lib/atox-questions.coffee
@@ -31,8 +31,9 @@
     @buttons.append @yesB
     @buttons.append @noB

-    @canAsk = true
-    @event  = new Emitter
+    @canAsk     = true
+    @wasAnswerd = false
+    @event      = new Emitter

   ask: ->
     console.error "Already asked!" if !@canAsk
@@ -40,10 +41,16 @@
     @fadeIn atom.config.get 'atox.fadeDuration'

   yes: ->
+    return if @wasAnswerd
+    @wasAnswerd = true
+
     @event.emit 'yes'
     @delQ()

   no: ->
+    return if @wasAnswerd
+    @wasAnswerd = true
+
     @event.emit 'no'
     @delQ()


Don't display tabs when logged out

Don't display tabs when logged out

--- resources/assets/coffee/react/beatmapset-page/scoreboard.coffee
+++ resources/assets/coffee/react/beatmapset-page/scoreboard.coffee
@@ -51,14 +51,16 @@
           Lang.get 'beatmaps.beatmapset.show.extra.scoreboard.no-scores'
       else
         div {},
-          div className: 'beatmapset-scoreboard__tabs',
-            scoreboards.map (m) =>
-              el BeatmapSetPage.ScoreboardTab,
-                key: m
-                scoreboard: m
-                currentScoreboard: @props.currentScoreboard
+          if !_.isEmpty currentUser
+            div {},
+              div className: 'beatmapset-scoreboard__tabs',
+                scoreboards.map (m) =>
+                  el BeatmapSetPage.ScoreboardTab,
+                    key: m
+                    scoreboard: m
+                    currentScoreboard: @props.currentScoreboard

-          div className: 'beatmapset-scoreboard__line'
+              div className: 'beatmapset-scoreboard__line'

           div className: className,
             el BeatmapSetPage.ScoreboardFirst,


:bug: Fix a broken spec

:bug: Fix a broken spec

--- spec/message-registry-spec.coffee
+++ spec/message-registry-spec.coffee
@@ -91,7 +91,7 @@
       messageRegistry.onDidUpdateMessages ({messages}) ->
         wasUpdated = 1
         expect(objectSize(messages)).toBe(1)
-        messageRegistry.deleteEditorMessages(editor)
+        messageRegistry.deleteEditorMessages({editor})
       waitsForPromise ->
         linterRegistry.lint({onChange: false, editorLinter}).then ->
           expect(wasUpdated).toBe(1)


Delete specs testing previous behavior

Delete specs testing previous behavior

When emitting the `did-change` event, `updateFoldableStatus` used to extend the
change region up and down to include all the lines that changed their
foldability status because of a buffer change. I assume this was supposed to
invalidate folds that *contained* the change whenever a line was edited in a way
that affected also the previous or subsequent ones.

That information, however, is not being used by `DisplayBuffer`, which does not
alter existing folded regions when they become invalid.

I believe the correct behavior should be to unfold the invalid region and
recompute those screen lines. Nonetheless, it seems reasonable to me to keep the
original (wrong) behavior and not address it in this branch, because it strays
from the original intent of this refactoring.

We should probably fix it once for all in another PR or when
integrating/implementing `DisplayLayer`.

--- spec/tokenized-buffer-spec.coffee
+++ spec/tokenized-buffer-spec.coffee
@@ -202,8 +202,7 @@
             expect(tokenizedBuffer.firstInvalidRow()).toBe 3

             advanceClock()
-             # we discover that row 2 starts a foldable region when line 3 gets tokenized
-            expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 7, delta: 0)
+            expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 7, delta: 0)
             expect(tokenizedBuffer.firstInvalidRow()).toBe 8

       describe "when there is a buffer change surrounding an invalid row", ->
@@ -253,7 +252,7 @@
               expect(changeHandler).toHaveBeenCalled()
               [event] = changeHandler.argsForCall[0]
               delete event.bufferChange
-              expect(event).toEqual(start: 1, end: 2, delta: 0)
+              expect(event).toEqual(start: 2, end: 2, delta: 0)
               changeHandler.reset()

               advanceClock()
@@ -263,8 +262,7 @@
               expect(changeHandler).toHaveBeenCalled()
               [event] = changeHandler.argsForCall[0]
               delete event.bufferChange
-               # we discover that row 2 starts a foldable region when line 3 gets tokenized
-              expect(event).toEqual(start: 2, end: 5, delta: 0)
+              expect(event).toEqual(start: 3, end: 5, delta: 0)

           it "resumes highlighting with the state of the previous line", ->
             buffer.insert([0, 0], '/*')
@@ -292,7 +290,7 @@
             expect(changeHandler).toHaveBeenCalled()
             [event] = changeHandler.argsForCall[0]
             delete event.bufferChange
-            expect(event).toEqual(start: 0, end: 3, delta: -2) # starts at 0 because foldable on row 0 becomes false
+            expect(event).toEqual(start: 1, end: 3, delta: -2)

         describe "when the change invalidates the tokenization of subsequent lines", ->
           it "schedules the invalidated lines to be tokenized in the background", ->
@@ -305,7 +303,7 @@
             expect(changeHandler).toHaveBeenCalled()
             [event] = changeHandler.argsForCall[0]
             delete event.bufferChange
-            expect(event).toEqual(start: 1, end: 3, delta: -1)
+            expect(event).toEqual(start: 2, end: 3, delta: -1)
             changeHandler.reset()

             advanceClock()
@@ -314,8 +312,7 @@
             expect(changeHandler).toHaveBeenCalled()
             [event] = changeHandler.argsForCall[0]
             delete event.bufferChange
-            # we discover that row 2 starts a foldable region when line 3 gets tokenized
-            expect(event).toEqual(start: 2, end: 4, delta: 0)
+            expect(event).toEqual(start: 3, end: 4, delta: 0)

         describe "when lines are both updated and inserted", ->
           it "updates tokens to reflect the change", ->
@@ -339,7 +336,7 @@
             expect(changeHandler).toHaveBeenCalled()
             [event] = changeHandler.argsForCall[0]
             delete event.bufferChange
-            expect(event).toEqual(start: 0, end: 2, delta: 2) # starts at 0 because .foldable becomes false on row 0
+            expect(event).toEqual(start: 1, end: 2, delta: 2)

         describe "when the change invalidates the tokenization of subsequent lines", ->
           it "schedules the invalidated lines to be tokenized in the background", ->
@@ -350,7 +347,7 @@
             expect(changeHandler).toHaveBeenCalled()
             [event] = changeHandler.argsForCall[0]
             delete event.bufferChange
-            expect(event).toEqual(start: 1, end: 2, delta: 2)
+            expect(event).toEqual(start: 2, end: 2, delta: 2)
             expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
             expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
             expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
@@ -894,7 +891,7 @@
         buffer.setTextInRange([[7, 0], [8, 65]], '    ok')

         delete changeHandler.argsForCall[0][0].bufferChange
-        expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 10, delta: -1) # starts at row 4 because it became foldable
+        expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1)

         expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2
         expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2


`util/routes`: rewrote `productSymbol`

`util/routes`: rewrote `productSymbol`

--- util/routes.coffee
+++ util/routes.coffee
@@ -24,36 +24,40 @@

+firstToUpperCase = (string) -> string.substr(0, 1).toUpperCase() + string.substr 1

-productSymbol = (type) ->
-	# todo:
-	# Since xterm does only support 0..5 for red, green and blue, we need to convert the rgb value here.
-	# Find a cleaner solution, probably a dedicated module, for that.
-	fg = hexRgb 'ffffff'   # todo: use a foreground that is readable on the background color
-	fg[0] = Math.round fg[0] / 256 * 5
-	fg[1] = Math.round fg[1] / 256 * 5
-	fg[2] = Math.round fg[2] / 256 * 5
-	bg = hexRgb util.products[type].color
-	bg[0] = Math.round bg[0] / 256 * 5
-	bg[1] = Math.round bg[1] / 256 * 5
-	bg[2] = Math.round fg[2] / 256 * 5
-	return [
-		c256.fg.getRgb fg[0], fg[1], fg[2]
-		c256.bg.getRgb bg[0], bg[1], bg[2]
-		util.products[type].short
-		c256.reset
-	].join ''
+productColor = (product, text) ->
+	product = util.products[product] or util.products.unknown
+	styles = chalk
+	for style in product.ansi
+		if styles[style]
+			styles = styles[style]
+	return styles text
+
+productSymbol = (part) ->
+	if part.transport is 'public'
+		product = util.products[part.type] or util.products.unknown
+		productColor part.type, product.short
+	else return util.routes.legs.types[part.transport].unicode + ' '
+
+partSymbol = (part) ->
+	if part.transport is 'public'
+		product = util.products[part.type] or util.products.unknown
+		return productColor part.type, product.name + ' ' + part.line
+	else return util.routes.legs.types[part.transport].unicode + ' '
+
+
+
+

 routeName = (route) ->
-	start = route.parts[0].from.when
-	stop = route.parts[route.parts.length - 1].to.when
+	first = route.parts[0].from
+	last = route.parts[route.parts.length - 1].to

-	types = ''
+	types = []
 	for part in route.parts
-		if part.transport is 'public' then types += productSymbol part.type
-		else types += util.routes.legs.types[part.transport].symbol
-		types += ' '   # fix weird unicode spacing error
-	console.log types
+		types.push productSymbol part
+	types = types.join ' '

 	return [
 		[


only update client filetree on success

only update client filetree on success

--- services/web/public/coffee/ide/file-tree/FileTreeManager.coffee
+++ services/web/public/coffee/ide/file-tree/FileTreeManager.coffee
@@ -341,12 +341,12 @@

 		renameEntity: (entity, name, callback = (error) ->) ->
 			return if entity.name == name
-			if name.length < 150
+			return if name.length >= 150
+			@ide.$http.post("/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/rename", {
+				name: name,
+				_csrf: window.csrfToken
+			}).then () ->
 				entity.name = name
-			return @ide.$http.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/rename", {
-				name: entity.name,
-				_csrf: window.csrfToken
-			}

 		deleteEntity: (entity, callback = (error) ->) ->
 			# We'll wait for the socket.io notification to
@@ -362,11 +362,11 @@
 			# Abort move if the folder being moved (entity) has the parent_folder as child
 			# since that would break the tree structure.
 			return if @_isChildFolder(entity, parent_folder)
-			@_moveEntityInScope(entity, parent_folder)
-			return @ide.queuedHttp.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/move", {
+			@ide.queuedHttp.post("/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/move", {
 				folder_id: parent_folder.id
 				_csrf: window.csrfToken
-			}
+			}).then () =>
+				@_moveEntityInScope(entity, parent_folder)

 		_isChildFolder: (parent_folder, child_folder) ->
 			parent_path = @getEntityPath(parent_folder) or "" # null if root folder


bugfix

bugfix

--- shared/idle/controllers/global/IdleGlobalStatsController.coffee
+++ shared/idle/controllers/global/IdleGlobalStatsController.coffee
@@ -75,8 +75,8 @@
         switch key
           when "__personalities" then player.personalityStrings?.length
           when "__achievements" then player.achievements?.length
-          when "__guilddonations" then _.reduce (_.values player.statistics["calculated guild donations"] or []), ((prev, val) -> prev+val), 0
-          when "__guildtax" then _.reduce (_.values player.statistics["calculated guild taxes paid"] or []), ((prev, val) -> prev+val), 0
+          when "__guilddonations" then _.reduce (_.values player.statistics?["calculated guild donations"] or []), ((prev, val) -> prev+val), 0
+          when "__guildtax" then _.reduce (_.values player.statistics?["calculated guild taxes paid"] or []), ((prev, val) -> prev+val), 0
           else $scope.decompose player, key

       $scope.totalFromStatistics = (key) ->


update to fix tests using openstax/react-components#32

--- test/components/practice.spec.coffee
+++ test/components/practice.spec.coffee
@@ -112,7 +112,7 @@
   it 'should load expected practice at the practice url', ->
     tests = ({div}) ->
       expect(div.querySelector('.task-practice')).to.not.be.null
-      expect(div.querySelector('.task-practice .stimulus').innerText).to.equal(VALID_MODEL.steps[0].content.stimulus_html)
+      expect(div.querySelector('.task-practice .exercise-stimulus').innerText).to.equal(VALID_MODEL.steps[0].content.stimulus_html)

     tests(@result)


graph: fix bug where installed activity was shown everywhere

--- workers/social/lib/social/models/graph/fetch.coffee
+++ workers/social/lib/social/models/graph/fetch.coffee
@@ -18,24 +18,20 @@
     @overviewObjects      = []
     @newMemberBucketIndex = null

+    kodingMethods = [@fetchInstalls]
     if group.facets[0] is 'Everything'
       @globalMethods = [@fetchSingles, @fetchTagFollows, @fetchNewMembers, @fetchMemberFollows]
+
+      # HACK: we don't want to show app install in groups other than koding,
+      #       but they're currently global, so we manually filter them out.
+      if @group.groupName == "koding"
+        @globalMethods = @globalMethods.concat kodingMethods
     else
       @globalMethods = [@fetchSingles]

   get:(callback)->
-    kodingMethods = [@fetchInstalls]
-    methods = []
-
-    # HACK: we don't want to show app install in groups other than koding,
-    #       but they're currently global, so we manually filter them out.
-    if @group.groupName == "koding"
-      methods = @globalMethods.concat kodingMethods
-    else
-      methods = @globalMethods
-
     holder = []
-    boundMethods = holder.push method.bind this for method in methods
+    boundMethods = holder.push method.bind this for method in @globalMethods
     async.parallel holder, (err, results)=>
       callback @decorateAll(err, results)