Revert the timeout change in the docker runner

Revert the timeout change in the docker runner as the conversion from seconds to milliseconds as well as the default timeout are already handled in the Request parser.

--- app/coffee/DockerRunner.coffee
+++ app/coffee/DockerRunner.coffee
@@ -24,7 +24,7 @@
       proc = spawn 'docker', ['stop', "texlive-#{project_id}"]
       proc.on "close", ->
         callback timedout: true
-    , (timeout ?= 30) * 1000
+    , timeout
     proc.on "close", () ->
       clearTimeout timer
       callback()


Add success and error messages to the auth directives

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -162,10 +162,12 @@
             promise.then (response) ->
                 # TODO: finish this. Go tu user home page
                 $location.path("/project/project-example-0/backlog")
-                #
+
             promise.then null, (response) ->
                 if response.data._error_message
-                    $confirm.notify("light-error", response.data._error_message)
+                    $confirm.notify("light-error", "According to our Oompa Loompas,
+                                                    your username/email or password
+                                                    are incorrect.") #TODO: i18n

         $el.on "submit", (event) ->
             event.preventDefault()
@@ -195,13 +197,13 @@
             promise = $auth.publicRegister($scope.data)
             promise.then (response) ->
                 # TODO: finish this. Authenticate user and go to projects page
-                #$confirm.notify("success", response.data.detail)
                 console.log response
-                #

             promise.then null, (response) ->
                 if response.data._error_message
-                    $confirm.notify("light-error", response.data._error_message)
+                    $confirm.notify("light-error", "According to our Oompa Loompas,
+                                                    your are not registered yet or
+                                                    type an invalid password.") #TODO: i18n

         $el.on "submit", (event) ->
             event.preventDefault()
@@ -231,11 +233,15 @@
             promise.then (response) ->
                 if response.data.detail
                     $location.path("/login") # TODO: Use the future 'urls' service
-                    $confirm.success(response.data.detail)
+                    $confirm.success("Check your inbox!

+                                     We have sent a mail to

+                                     #{data.email}

+                                     with the instructions to set a new password") #TODO: i18n

             promise.then null, (response) ->
                 if response.data._error_message
-                    $confirm.notify("light-error", response.data._error_message)
+                    $confirm.notify("light-error", "According to our Oompa Loompas,
+                                                    your are not registered yet.") #TODO: i18n

         $el.on "submit", (event) ->
             event.preventDefault()


changed to outerHeight(), outerWidth()

changed to outerHeight(), outerWidth()

--- app/coffee/drm-tooltips/drm-tooltips.coffee
+++ app/coffee/drm-tooltips/drm-tooltips.coffee
@@ -37,10 +37,10 @@
             that = $ @
             positionTop = that.offset().top + parseInt(that.css('padding-top'), 10)
             positionLeft = that.offset().left + parseInt(that.css('padding-left'), 10)
-            height = parseInt(newTooltip.css('height'), 10) + parseInt(newTooltip.css('padding-top'), 10) + parseInt(newTooltip.css('padding-bottom'), 10)
-            width = parseInt(newTooltip.css('width'), 10) + parseInt(newTooltip.css('padding-left'), 10) + parseInt(newTooltip.css('padding-right'), 10)
-            elWidth = parseInt(that.css('width'), 10) + parseInt(that.css('padding-left'), 10) + parseInt(that.css('padding-right'), 10)
-            elHeight = parseInt(that.css('height'), 10) + parseInt(that.css('padding-top'), 10) + parseInt(that.css('padding-bottom'), 10)
+            height = parseInt(newTooltipouterHeight()
+            width = parseInt(newTooltip.outerWidth()
+            elWidth = parseInt(that.outerWidth()
+            elHeight = parseInt(that.outerHeight()

             if position == 'left'
                 tooltipTop = positionTop - (elHeight / 2)


translate: confirm delete message

translate: confirm delete message

--- jsapp/xlform/src/view.surveyApp.coffee
+++ jsapp/xlform/src/view.surveyApp.coffee
@@ -8,6 +8,7 @@
 $rowView = require './view.row'
 $baseView = require './view.pluggedIn.backboneView'
 $viewUtils = require './view.utils'
+_t = require('utils').t

 module.exports = do ->
   surveyApp = {}
@@ -609,7 +610,8 @@

     clickRemoveRow: (evt)->
       evt.preventDefault()
-      if confirm("Are you sure you want to delete this question? This action cannot be undone.")
+      if confirm(_t("Are you sure you want to delete this question?") + " " +
+          _t("This action cannot be undone."))
         $et = $(evt.target)
         rowEl = $et.parents(".survey__row").eq(0)
         rowId = rowEl.data("rowId")


Test for number

Test for number

--- app/components/price-selector.coffee
+++ app/components/price-selector.coffee
@@ -2,13 +2,20 @@
 `import EmberCLIICAjax from 'ic-ajax';`
 `import ENUMS from '../enums';`

-_makePayment = (store, pricingSlug, count) ->
+isNumber = (n) ->
+  /^\d+$/.test n

 PriceSelectorComponent = Ember.Component.extend

   count: 1
   pricing: null
   store: null
+
+  countObserver:(->
+    count = @get "count"
+    if !isNumber(count) or 1 > parseInt count
+      @set "count", 1
+  ).observes 'count'

   totalPrice: (->
     count = parseInt @get "count"


fix #40

fix #40

--- app/controllers/browser.coffee
+++ app/controllers/browser.coffee
@@ -34,8 +34,11 @@
         note: note

   changeNotebook: =>
+    dateSort = (a, b) ->
+      return b.date - a.date
+
     noteList = ""
-    for note in Note.filter(Notebook.current.id, Notebook.current.category)
+    for note in Note.filter(Notebook.current.id, Notebook.current.category).sort(dateSort)
       note.date = note.prettyDate()
       noteList += @template note


rename for bootstrap

rename for bootstrap

--- lib/package-view.coffee
+++ lib/package-view.coffee
@@ -4,7 +4,7 @@
 packageManager = require './package-manager'
 _ = require 'underscore'
 {$$, View} = require 'space-pen'
-requireWithGlobals 'bootstrap/js/bootstrap-dropdown', jQuery: require 'jquery'
+requireWithGlobals 'bootstrap/js/dropdown', jQuery: require 'jquery'

 ### Internal ###
 module.exports =


Update error message when no http server file is found

--- lib/plugins/http.coffee
+++ lib/plugins/http.coffee
@@ -38,7 +38,7 @@
         else if path.existsSync settings.workspace + '/app.coffee'
             args.unshift 'coffee ' + settings.workspace + '/app.coffee'
         else
-            next new Error('Failed to find appropriate "server.js" or "app.js" file')
+            next new Error 'Failed to discover a "server.js" or "app.js" file'
         args = args.join ' '
         http = exec args
         #done = false


Fixed wrong point for speech bubble for vPos top

--- app/lib/surface/Label.coffee
+++ app/lib/surface/Label.coffee
@@ -178,6 +178,7 @@
       # Just draw a rounded rectangle
       o.hpos ?= "middle"
       o.vPos ?= "middle"
+      pointerHeight = 0
       g.drawRoundRect(o.label.x - o.marginX, o.label.y - o.marginY, w, h, o.backgroundBorderRadius)

     background.regX = w / 2
@@ -191,7 +192,7 @@
     if o.vPos is "bottom"
       background.regY = h + pointerHeight
     else if o.vPos is "top"
-      background.regX = -pointerHeight
+      background.regY = -pointerHeight

     o.label.regX = background.regX - o.marginX
     o.label.regY = background.regY - o.marginY


Use getActiveTextEditor instead of getActiveEditor

--- lib/mixins/view-management.coffee
+++ lib/mixins/view-management.coffee
@@ -35,7 +35,7 @@
   # Public: Returns the {MinimapView} of the active editor view.
   #
   # Returns a {MinimapView}.
-  getActiveMinimap: -> @minimapForEditor(atom.workspace.getActiveEditor())
+  getActiveMinimap: -> @minimapForEditor(atom.workspace.getActiveTextEditor())

   # Public: Calls `iterator` for each present and future minimap views.
   # It returns a subscription {Object} with a `off` method so that


webpack: edit config

webpack: edit config

--- config/webpack.coffee
+++ config/webpack.coffee
@@ -20,7 +20,6 @@
 module.exports =

   entry: [
-    # path.join(__dirname, "../bower_components/social-snippet-registry-assets/src/coffee/main.coffee")
     "main"
   ]


More punny

More punny

--- lib/react_components/hello_world.cjsx
+++ lib/react_components/hello_world.cjsx
@@ -6,7 +6,7 @@

Hello world!

You're looking at the Coffeescript React Quickstart project by Kyle Mathews.

-
It has a number of nice goodies included like:

+
It has a number of nice goodies baked in including:

Live reloading for both CSS and Javascript! This really speeds up your development. Live reloading is powered by the Webpack module bundler and the react-hot-loader projects.

Full JSX with Coffeescript support provided by coffee-react-transform.


scandaemons change for ubuntu...

scandaemons change for ubuntu...

--- lib/scan-deamons.coffee
+++ lib/scan-deamons.coffee
@@ -1,5 +1,6 @@
 {Directory} = require 'atom'
 DaemonItem = require "./daemon-item"
+{BufferedProcess} = require 'atom'

 module.exports =
 class ScanDeamons
@@ -8,18 +9,22 @@
     @defineScanFunction()

   defineScanFunction: ->
-    if process.platform == "darwina" #mac
+    if process.platform == "darwin" #mac
       @scanFunction = ScanDaemonsBrew
-    else #if /^win/.test(process.platform) #win
+    else if /^linux/.test(process.platform) #linux
+      @isLinuxDistribution "Ubuntu", =>
+        @scanFunction = ScanDaemonsUbuntu
+    #else if /^win/.test(process.platform) #win
       #atom.notifications.addInfo "There is no scan algorithm for your #{process.platform} platform, plaese add this!"
       #atom.notifications.addInfo "Add manual daemons with CMD+SHIFT+P -> Smart Daemon Control: New Daemon"
       #@daemonManagement.newDaemon()
-    #else if /^linux/.test(process.platform) #linux

   run: ->
     if @scanFunction?
       new @scanFunction(this)
-    else atom.notifications.addInfo "There is no scan-algorithm for your OS #{process.platform} right now."
+    else
+      #console.log process
+      atom.notifications.addInfo "There is no scan-algorithm for your OS #{process.platform} right now."

   thereIsScanDaemonForOs: ->
     if @scanFunction == null
@@ -33,26 +38,50 @@
                                                 fileNameWithoutAfterDot, false, false, false
     atom.notifications.addInfo "#{daemonName} added"

+  isLinuxDistribution: (name,callback) -> #other better solution?
+    command = "uname"
+    args = ["-v"]
+    alreadyGetCheckString = false
+    stdout = (output) ->
+      if output.indexOf(name) >- 1
+        alreadyGetCheckString = true
+    exit = (code) ->
+      callback() if alreadyGetCheckString
+    process = new BufferedProcess({command, args, stdout, exit})
+
+class ScanDaemons #extends this
+  name : ""   #replace
+  init : null # replace
+
+  constructor: (@scanDaemons) ->
+    @init()
+    @info()
+
+  startScan: (dirUrl,re) -> #run this
+    dir = new Directory(dirUrl)
+    @scanForFile dir, re if dir.isDirectory()
+  scanForFile = (dir,re) ->
+    dir.getEntries (err,entries) =>
+      for entrie in entries
+        if not entrie.isDirectory()
+          result = re.exec entrie.getBaseName()
+          if result != null
+            @regNewDaemon entrie, result[1]
+        else @scanForFile entrie #scan subfolder
+
+  regNewDaemon = (file,fileNameWithoutAfterDot) => #TODO: change format
+    @scanDeamons.addDaemon file.getParent().getBaseName(), file.path, fileNameWithoutAfterDot
+
+  info : ->
+    atom.notifications.addInfo "Scan #{@name} Daemons"
+#Scan Class for (Debian)/Ubuntu
+class ScanDaemonsUbuntu extends ScanDaemons
+  name: "Brew"
+  init: ->
+    @startScan "/usr/local/opt/", /^([\w\.]+)\.plist$/
+
 #Scan Class for Brew
-class ScanDaemonsBrew
-  constructor: (@scanDeamons) ->
-    dir = new Directory('/usr/local/opt/')
-    if dir.isDirectory()
-      atom.notifications.addInfo "Scan Brew like Daemons"
-      @searchPlist dir
-
-  searchPlist: (dir) ->
-    re = /^([\w\.]+)\.plist$/
-    searchPlist = (dir) =>
-      dir.getEntries (err,entries) =>
-        for entrie in entries
-          if entrie.isDirectory()
-            searchPlist entrie
-          else
-            result = re.exec(entrie.getBaseName())
-            if result != null
-              regNewPlist entrie, result[1]
-
-    regNewPlist = (file,fileNameWithoutAfterDot) =>
-      @scanDeamons.addDaemon file.getParent().getBaseName(), file.path, fileNameWithoutAfterDot
-    searchPlist dir
+class ScanDaemonsBrew extends ScanDaemons
+  name: "Brew"
+  init: ->
+    @startScan "/usr/local/opt/", /^([\w\.]+)\.plist$/


Update spec descriptions

Update spec descriptions

--- test/mock_node.spec.coffee
+++ test/mock_node.spec.coffee
@@ -42,27 +42,27 @@
       @boundingClientRect = @node.getBoundingClientRect()

     describe '#height', ->
-      it 'returns node height', ->
+      it 'is zero by default', ->
         @boundingClientRect.height.should.equal(0)

     describe '#width', ->
-      it 'returns node width', ->
+      it 'is zero by default', ->
         @boundingClientRect.width.should.equal(0)

     describe '#left', ->
-      it 'returns node left', ->
+      it 'is zero by default', ->
         @boundingClientRect.left.should.equal(0)

     describe '#bottom', ->
-      it 'returns node bottom', ->
+      it 'is zero by default', ->
         @boundingClientRect.bottom.should.equal(0)

     describe '#right', ->
-      it 'returns node right', ->
+      it 'is zero by default', ->
         @boundingClientRect.right.should.equal(0)

     describe '#top', ->
-      it 'returns node top', ->
+      it 'is zero by default', ->
         @boundingClientRect.top.should.equal(0)

   describe '#scrollTop', ->


Don't check for callback parameter.

Don't check for callback parameter.

--- lib/target_local.coffee
+++ lib/target_local.coffee
@@ -21,7 +21,7 @@
 		child.on 'error', (error) -> shell_stream.emit_error error
 		child.on 'exit', (code) -> shell_stream.emit_exit code

-		callback null, shell_stream if callback
+		callback null, shell_stream

 	do_spawn: (command, args, callback) ->
 		shell_stream = new helpers.Stream
@@ -31,7 +31,7 @@
 		child.on 'error', (error) -> shell_stream.emit_error error
 		child.on 'exit', (code) -> shell_stream.emit_exit code

-		callback null, shell_stream if callback
+		callback null, shell_stream

 	copy: (source, dest) ->
 		real_source = path.resolve path.dirname(@manifest.meta.location), source


Fix comment.

Fix comment.

--- app/javascripts/volumeDisplay.coffee
+++ app/javascripts/volumeDisplay.coffee
@@ -7,7 +7,7 @@
 #
 #   volumedisplay.factory 'VolumeDisplay', ['$rootScope', '$q', 'File', ($rootScope, $q, File) ->
 #     ### This class is broken after the AngularJS 1.4.9 update. ###
-#     # TODO - revisit if XNAT is resurrected.
+#     # TODO - revisit if XTK is resurrected.
 #     #
 #     # TODO - dynamically load XTK. See, e.g.,
 #     # http://www.bennadel.com/blog/2554-loading-angularjs-components-with-requirejs-after-application-bootstrap.htm


Remove debug output

Remove debug output

--- app/js/lib/views/report/frame.coffee
+++ app/js/lib/views/report/frame.coffee
@@ -8,7 +8,6 @@
     'click .capture': 'captureTransaction'

   showMenu: =>
-    console.log 'Show menu'
     LedgerWeb.app.set(showMenu: true)

   captureTransaction: =>


Fix broken test cases

Fix broken test cases

Test suite was broken due to previous changes. Include correct HTML
to pass the tests.

--- _includes/coffee/test.coffee
+++ _includes/coffee/test.coffee
@@ -225,7 +225,7 @@
       }
     assert.deepEqual(
       benchmark_id({}, {}, data())
-      '\n1a.0\n'
+      '\n1a.0\n'
     )
   )
 )
@@ -247,7 +247,7 @@
       {revisions:{commit:{sha:'abcd', url:'a/b'}}}
     data_out = ->
       '\nabcd\n'
+      blob/abcd/a/b" target="_blank">\nabcd\n'
     assert.deepEqual(commit({}, {}, data_in()), data_out())
   )
 )


Include selected workflow on main Classify link

--- app/pages/project/index.cjsx
+++ app/pages/project/index.cjsx
@@ -99,6 +99,8 @@
   render: ->
     projectPath = "/projects/#{@props.project.slug}"

+    currentWorkflow = @props.preferences?.preferences.selected_workflow ? @props.project.configuration.default_workflow
+
     pages = [{}, @state.pages...].reduce (map, page) =>
       map[page.url_key] = page
       map
@@ -131,7 +133,7 @@
             <Translate content="project.nav.classify" />
           </a>
         else
-          <Link to="#{projectPath}/classify" activeClassName="active" className="classify tabbed-content-tab">
+          <Link to="#{projectPath}/classify" query={workflow: currentWorkflow} activeClassName="active" className="classify tabbed-content-tab">
             <Translate content="project.nav.classify" />
           </Link>}


Configurable pending open

Configurable pending open

--- lib/util/gotodef.coffee
+++ lib/util/gotodef.coffee
@@ -22,8 +22,9 @@

 module.exports =
-goto: (symbolTableOrPromise) ->
+goto: (symbolTableOrPromise, opts) ->
   @view ?= new GotoView()
+  @view.pending = opts?.pending

   # this allows either a promise or a symbolTable as the input
   promise = Promise.resolve symbolTableOrPromise
@@ -33,7 +34,7 @@
       @view.setError symbolTable.items
       @view.show()
     else if symbolTable.items.length == 1
-      GotoView.openItem symbolTable.items[0]
+      @view.openItem symbolTable.items[0]
     else if symbolTable.items.length > 1
       @view.setItems symbolTable.items
       @view.show()
@@ -84,14 +85,14 @@

   # Jump to `item.file` at line `item.line`, when an item was selected.
   confirmed: (item) ->
-    GotoView.openItem item
+    @openItem item
     @hide()

   # Return to previously focused element when the modal panel is cancelled.
   cancelled: ->
     @hide()

-  @openItem: (item) ->
+  openItem: (item) ->
     open(item.file, item.line, {
-      pending: true
+      pending: @pending
     })


segregate out the CSS/JS linkage toolkit

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -38,107 +38,18 @@
 http = require "http"
 util = require "util"

-{BowerSupport} = require "./bower"
+{Barebones} = require "./skeleton"
+{Screenplay} = require "./visual"
 {Extending} = require "../nucleus/extends"
 {Composition} = require "../nucleus/compose"
 {Archetype} = require "../nucleus/arche"
-
-# A complementary part of the preflight procedures that provides the
-# ability to create and emit arbitrary linkage in the contexts that
-# are going to be assembled within the current preflight hierarchy.
-# The facilities of this toolkit should typically be used when you
-# need to link to a static asset file, within any domain or path.
-module.exports.LToolkit = class LToolkit extends BowerSupport
-
-    # This is a marker that indicates to some internal subsystems
-    # that this class has to be considered abstract and therefore
-    # can not be treated as a complete class implementation. This
-    # mainly is used to exclude or account for abstract classes.
-    # Once inherited from, the inheritee is not abstract anymore.
-    @abstract yes
-
-    # Symbol declaration table, that states what keys, if those are
-    # vectors (arrays) should be exported and then merged with their
-    # counterparts in the destination, once the composition process
-    # takes place. See the `Archetype::composition` hook definition
-    # for more information. Keys are names, values can be anything.
-    @COMPOSITION_EXPORTS = jscripts: 1, stsheets: 1, metatags: 1
-
-    # This server side method is called on the context prior to the
-    # context being compiled and flushed down to the client site. The
-    # method is wired in an asynchronous way for greater functionality.
-    # This is the place where you would be importing the dependencies.
-    # Pay attention that most implementations side effect the context.
-    prelude: (symbol, context, request, next) ->
-        assert jscripts = @constructor.jscripts or []
-        assert stsheets = @constructor.stsheets or []
-        assert metatags = @constructor.metatags or []
-        assert jscripts = _.unique jscripts or Array()
-        assert stsheets = _.unique stsheets or Array()
-        assert metatags = _.unique metatags or Array()
-        assert _.isFunction context.sheets.push or null
-        assert _.isFunction context.scripts.push or null
-        assert _.isFunction context.metatag.push or null
-        context.metatag.push mettag for mettag in metatags
-        context.scripts.push script for script in jscripts
-        context.sheets.push sheet for sheet in stsheets
-        return do => next.call this, undefined
-
-    # This is a preflight directive that can be used to generate and
-    # emit meta tags for the client browser. The directive expects a
-    # aggregate definition (an object) whose key/value pairs will be
-    # diretly corellated as parameter name and value for a meta tag
-    # definition. Any number of pairs (parameters) may be supplied.
-    @metatag: (aggregate) ->
-        failed = "param has to be the plain object"
-        noPrevious = "got invalid previous metatags"
-        assert previous = @metatags or new Array()
-        assert _.isEmpty a = accumulate = new Array
-        assert _.isArray(previous or 0), noPrevious
-        assert _.isPlainObject(aggregate), failed
-        f = (val, key) -> "#{key}=\x22#{val}\x22"
-        _.map aggregate, (v, k) -> a.push f(v, k)
-        assert _.isString j = accumulate.join " "
-        @metatags = previous.concat j.toString()
-
-    # This is a preflight directive that can be used to link any
-    # arbitrary JavaScript file source. Is important do understand
-    # that this directive only compiles the appropriate statement
-    # to be transferred to the server and it is up to you to ensure
-    # the existence of that file and its ability to be downloaded.
-    @javascript: (xoptions, xdirection) ->
-        assert previous = @jscripts or Array()
-        options = _.find arguments, _.isObject
-        direction = _.find arguments, _.isString
-        indirect = "an inalid direction supplied"
-        noPrevious = "invalid previous jscripts"
-        assert _.isArray(previous), noPrevious
-        assert _.isString(direction), indirect
-        @jscripts = previous.concat direction
-        assert @jscripts = _.unique @jscripts
-
-    # This is a preflight directive that can be used to link any
-    # arbitrary CSS style file source. Is important do understand
-    # that this directive only compiles the appropriate statement
-    # to be transferred to the server and it is up to you to ensure
-    # the existence of that file and its ability to be downloaded.
-    @stylesheet: (xoptions, xdirection) ->
-        assert previous = @stsheets or Array()
-        options = _.find arguments, _.isObject
-        direction = _.find arguments, _.isString
-        indirect = "an inalid direction supplied"
-        noPrevious = "invalid previous stsheets"
-        assert _.isArray(previous), noPrevious
-        assert _.isString(direction), indirect
-        @stsheets = previous.concat direction
-        assert @stsheets = _.unique @stsheets

 # This abstract base class service is an extension of the Screenplay
 # family that does some further environment initialization and set
 # up. These preparations will be nececessary no matter what sort of
 # Screenplay functionality you are going to implement. Currently the
 # purpose of preflight is drawing in the remotes and Bower packages.
-module.exports.Preflight = class Preflight extends LToolkit
+module.exports.Preflight = class Preflight extends Screenplay

     # This is a marker that indicates to some internal subsystems
     # that this class has to be considered abstract and therefore


Re-arrange regional dashboard charts

Re-arrange regional dashboard charts

--- app/scripts/dcplotsctrl.coffee
+++ app/scripts/dcplotsctrl.coffee
@@ -14,6 +14,7 @@

     $scope.gridLayout =
       statusPerLga: { sizeX: 6, sizeY: 4, row: 0, col: 0 }
+      topProblems: { sizeX: 6, sizeY: 4, row: 18, col: 0 }
       constrYear: { sizeX: 6, sizeY: 2, row: 0, col: 6 }
       breakYear: { sizeX: 6, sizeY: 2, row: 2, col: 6 }

@@ -29,11 +30,8 @@
       funderPie: { sizeX: 3, sizeY: 3, row: 11, col: 6 }
       installerPie: { sizeX: 3, sizeY: 3, row: 11, col: 9 }

-      statusPerSourceType: { sizeX: 6, sizeY: 4, row: 14, col: 0 }
+      statusPerManagement: { sizeX: 6, sizeY: 4, row: 18, col: 6 }
       statusPerExtraction: { sizeX: 6, sizeY: 4, row: 14, col: 6 }
-
-      topProblems: { sizeX: 6, sizeY: 4, row: 18, col: 0 }
-      statusPerManagement: { sizeX: 6, sizeY: 4, row: 18, col: 6 }

       costImpactBubble: { sizeX: 12, sizeY: 5, row: 22, col: 0 }

@@ -65,6 +63,9 @@

           setupCharts $scope.region

+    # what value to use for year 0
+    YEAR_ZERO=1900
+
     getData = (region, callback) ->
       filter =  region: region

@@ -87,8 +88,8 @@

       d3.json url, (data) ->
         data._items.forEach (d) ->
-          d.breakdown_year = new Date(d.breakdown_year || 1900, 0, 1)
-          d.construction_year = new Date(d.construction_year || 1900, 0, 1)
+          d.breakdown_year = new Date(d.breakdown_year || YEAR_ZERO, 0, 1)
+          d.construction_year = new Date(d.construction_year || YEAR_ZERO, 0, 1)

         callback data._items

@@ -165,7 +166,6 @@
       getData region, (data) ->
         statusPerLgaChart = dc.barChart("#statusPerLga")
         statusPerWardChart = dc.barChart("#statusPerWard")
-        statusPerSourceTypeChart = dc.barChart("#statusPerSourceType")
         constrYearChart = dc.barChart("#constrYear")
         breakYearChart = dc.barChart("#breakYear")
         quantityChart = dc.pieChart("#quantityPie")
@@ -191,9 +191,6 @@
         wards = createDim (d) -> d.ward
         statusPerWard = reduceStatus wards.group()

-        sourceTypes = createDim (d) -> d.source_type
-        statusPerSourceType = reduceStatus sourceTypes.group()
-
         constrYears = createDim (d) -> d.construction_year
         constrYearsGroup = reduceStatus constrYears.group()

@@ -237,7 +234,6 @@
         costStatusGroup = reduceCostStatus wards.group()

         statusBarChart statusPerLgaChart, lgas, statusPerLga, 15
-        statusBarChart statusPerSourceTypeChart, sourceTypes, statusPerSourceType
         statusBarChart statusPerExtraction, extractionTypes, extractionStatusGroup
         statusBarChart statusPerManagement, managements, managementsStatusGroup
         statusBarChart statusPerWardChart, wards, statusPerWard, 1
@@ -385,7 +381,7 @@
           .elasticY(true)
           .elasticX(true)
           .gap(1)
-          .x(d3.time.scale().domain([new Date(1900, 0, 1), new Date(2014, 12, 31)]))
+          .x(d3.time.scale().domain([new Date(YEAR_ZERO, 0, 1), new Date(2014, 12, 31)]))
           .xUnits(d3.time.years)
           .yAxis().ticks(4)


DE19697: fixing failing test that likes to be flaky

--- test/spec/roadmapplanningboard/RoadmapPlanningBoardAppSpec.coffee
+++ test/spec/roadmapplanningboard/RoadmapPlanningBoardAppSpec.coffee
@@ -59,15 +59,6 @@
     @createApp().then =>
       expect(@planningBoard.timeline.getId()).toBe @timelineStore.first().getId()

-  it 'should define height for planning board based on content window', ->
-    Ext.DomHelper.append Ext.getBody(), '

'
-    @createApp().then =>
-      expect(@app._computePanelContentAreaHeight()).toBe 580
-
-  it 'should define height for grid board based on container height', ->
-    @createApp(false, {height: 600}).then =>
-      expect(@app._computePanelContentAreaHeight()).toBe 600
-
   it 'should notify of error if the timeline store fails to load', ->
     @stub @timelineStore, 'load', ->
       deferred = new Deft.promise.Deferred()


Use id instead of channel slug

Use id instead of channel slug

--- apps/marklet/components/marklet/view.coffee
+++ apps/marklet/components/marklet/view.coffee
@@ -110,7 +110,7 @@
   save: ->
     val = @dom.input.val()?.trim() or @model.get('content')

-    data = channel_ids: @connections.pluck 'slug'
+    data = channel_ids: @connections.pluck 'id'
     data[if (isURL(val) or isDataURI(val)) then 'source' else 'content'] = val
     data = extend {}, data, QUERY


Fix wrong output script

Fix wrong output script

--- app/src/dongle/dongle.coffee
+++ app/src/dongle/dongle.coffee
@@ -660,7 +660,7 @@
         .concat(PkScript(recipientAddress))

       if changeAmount.gt(0)
-        outputScript
+        outputScript = outputScript
           .concat(changeAmount.toScriptByteString())
           .concat(PkScript(changeAddress))


tweak pagination style

tweak pagination style

--- lib/carte/client/views/pagination.cjsx
+++ lib/carte/client/views/pagination.cjsx
@@ -28,7 +28,7 @@

         }

-

+

         {
           if @props.cards.page


Fix redirect from auth page

Fix redirect from auth page

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -455,12 +455,12 @@

   actions:
     afterSignIn: ->
-      @transitionTo('index')
+      @transitionTo('main')
       return true

   redirect: ->
     if @signedIn()
-      @transitionTo('index')
+      @transitionTo('main')

 Travis.SettingsRoute = Travis.Route.extend
   needsAuth: true


chore(web): update some build tasks;

chore(web): update some build tasks;

--- apps/actor-web/gulpfile.coffee
+++ apps/actor-web/gulpfile.coffee
@@ -32,9 +32,9 @@
     .pipe connect.reload()

 gulp.task 'html', ->
-  gulp.src ['*.html']
-    .pipe gulp.dest './dist/'
-    .pipe connect.reload()
+#  gulp.src ['*.html']
+#    .pipe gulp.dest './dist/'
+#    .pipe connect.reload()
   gulp.src ['./app/**/*.html']
     .pipe gulp.dest './dist/app/'
     .pipe connect.reload()
@@ -57,8 +57,9 @@
       js: [uglify()]
       css: [autoprefixer(), minifycss()]
     .pipe gulp.dest './dist/'
+    .pipe connect.reload()

-gulp.task 'server', ->
+gulp.task 'server', ['usemin'], ->
   connect.server
     port: 3000
     root: 'dist'


chore(web): put files from push to root

chore(web): put files from push to root

--- apps/actor-web/gulpfile.coffee
+++ apps/actor-web/gulpfile.coffee
@@ -76,6 +76,11 @@
     .pipe gulp.dest './dist/'
     .pipe connect.reload()

+gulp.task 'push', ->
+  gulp.src ['./push/*']
+  .pipe gulp.dest './dist/'
+  .pipe connect.reload()
+
 gulp.task 'watch', ['server'], ->
   gulp.watch ['./app/**/*.coffee'], ['coffee']
   gulp.watch ['./styles/**/*.scss'], ['sass']
@@ -106,9 +111,9 @@
     root: ['./dist/', './']
     livereload: true

-gulp.task 'build', ['assets', 'browserify', 'sass', 'html', 'usemin']
+gulp.task 'build', ['assets', 'browserify', 'sass', 'html', 'usemin', 'push']

-gulp.task 'build:dev', ['assets', 'browserify:watchify', 'sass', 'html']
+gulp.task 'build:dev', ['assets', 'browserify:watchify', 'sass', 'html', 'push']

 gulp.task 'dev', ['build:dev', 'server', 'watch']


checkoutSide takes more arguments now.

checkoutSide takes more arguments now.

--- lib/view/merge-conflicts-view.coffee
+++ lib/view/merge-conflicts-view.coffee
@@ -130,10 +130,10 @@
   sideResolver: (side) ->
     (event) =>
       p = $(event.target).closest('li').data('path')
-      GitBridge.checkoutSide side, p, (err) =>
+      GitBridge.checkoutSide @state.repo, side, p, (err) =>
         return if handleErr(err)

-        full = path.join atom.project.relativizePath(atom.workspace.getActivePaneItem()?.getPath?())[0], p
+        full = path.join @state.repo.getWorkingDirectory(), p
         @pkg.didResolveConflict file: full, total: 1, resolved: 1
         atom.workspace.open p


Fixed image loading

Fixed image loading

--- platform/src/app.coffee
+++ platform/src/app.coffee
@@ -59,8 +59,9 @@
 currentServices = null
 currentEvaluator = null

-# Contains references to object URLS or DOM elements
 assetNamesToData = {}
+assetNamesToObjectUrls = {}
+
 compiledActions = {}
 compiledTools = {}

@@ -326,9 +327,10 @@
     else if splitUrl.mimeType == "text/css"
       assetNamesToData[name].remove()
     else if splitUrl.mimeType.indexOf("image/") == 0
-      URL.revokeObjectUrl(assetNamesToData[name])
+      URL.revokeObjectURL(assetNamesToObjectUrls[name])

   assetNamesToData = {}
+  assetNamesToObjectUrls = {}

   # Create new assets
   for name, dataUrl of newAssetMap
@@ -341,7 +343,15 @@
       css = atob(splitUrl.data)
       assetNamesToData[name] = $('<style type="text/css"></style>').html(css).appendTo("head")
     else if splitUrl.mimeType.indexOf("image/") == 0
-      assetNamesToData[name] = Util.strToObjectURL(splitUrl.data, splitUrl.mimeType)
+      blob = Util.dataURLToBlob(dataUrl)
+      objectUrl = URL.createObjectURL(blob)
+
+      image = new Image()
+      image.src = objectUrl
+      # TODO: verify that images loaded correctly?
+
+      assetNamesToObjectUrls[name] = objectUrl
+      assetNamesToData[name] = image
     else
       assetNamesToData[name] = atob(splitUrl.data)


altered some of the label text

altered some of the label text

--- client/account/lib/views/accounteditusername.coffee
+++ client/account/lib/views/accounteditusername.coffee
@@ -107,7 +107,7 @@
           cssClass         : 'AppModal-sectionHeader'
         shareLocationLabel :
           itemClass        : KDCustomHTMLView
-          partial          : 'Share my location when I post something on Koding Channels'
+          partial          : 'Share my location when I make a post on a #Channel'
           tagName          : 'ul'
           cssClass         : 'AppModal--account-switchList left-aligned'
           name             : 'shareLocationLabel'
@@ -177,7 +177,7 @@
       =>
         # update firstname and lastname
         me = whoami()
-
+
         me.modify {
           "profile.firstName": firstName,
           "profile.lastName" : lastName


Account Settings: Gravatar: Add loader icon to "Use Gravatar" button.

--- client/account/lib/views/accounteditusername.coffee
+++ client/account/lib/views/accounteditusername.coffee
@@ -57,8 +57,11 @@
       style          : 'solid small gray'
       cssClass       : 'use-gravatar'
       title          : 'Use Gravatar'
+      loader         : yes
       callback       : =>
-        @account.modify "profile.avatar": ""
+        @account.modify 'profile.avatar' : '', (err) =>
+          console.warn err  if err
+          @useGravatarBtn.hideLoader()

     @userProfileFormFields =
       firstName          :


Fixes for CoffeeScript style issues

Fixes for CoffeeScript style issues

--- lib/backofftimer.coffee
+++ lib/backofftimer.coffee
@@ -6,6 +6,11 @@
 max = (a, b) ->
   if a.gte b then a else b

+# This is a timer that is smart about backing off exponentially when there
+# are problems
+#
+# Ported from pynsq:
+#   https://github.com/bitly/pynsq/blob/master/nsq/BackoffTimer.py
 class BackoffTimer

   constructor: (minInterval, maxInterval, ratio=.25, shortLength=10,
@@ -15,11 +20,11 @@
       @maxInterval = decimal maxInterval

       ratio = decimal ratio
-      intervalDelta = decimal(@maxInterval - @minInterval)
+      intervalDelta = decimal @maxInterval - @minInterval
       # (maxInterval - minInterval) * ratio
       @maxShortTimer = intervalDelta.times ratio
       # (maxInterval - minInterval) * (1 - ratio)
-      @maxLongTimer = intervalDelta.times  decimal(1).minus(ratio)
+      @maxLongTimer = intervalDelta.times decimal(1).minus ratio

       @shortUnit = @maxShortTimer.dividedBy shortLength
       @longUnit = @maxLongTimer.dividedBy longLength
@@ -30,8 +35,8 @@
   success: ->
     @shortInterval = @shortInterval.minus @shortUnit
     @longInterval = @longInterval.minus @longUnit
-    @shortInterval = max(@shortInterval, decimal(0))
-    @longInterval = max @longInterval, decimal(0)
+    @shortInterval = max @shortInterval, decimal 0
+    @longInterval = max @longInterval, decimal 0

   failure: ->
     @shortInterval = @shortInterval.plus @shortUnit


Added collision detection to game.coffee

--- game.coffee
+++ game.coffee
@@ -28,6 +28,7 @@

     @VELOCITY = 0.1

+    @use_collisions = true

     constructor: ->
       @players = []
@@ -54,6 +55,14 @@
     getCurrentLines: ->
       p.currentLine() for p in @players

+    handle_collisions: (player) ->
+      pos = player.currentLinePos()
+      for p in @players
+        continue if p.name == player.name
+        for pos2 in p.currentLine()
+          if @arrayEqual(pos2.pos, pos.pos)
+            listener.notify(player, p, new Point(pos.pos...)) for listener in @listeners
+
     timer_tick: ->
       new_time = SynchronizedTime.getTime()
       elapsed_time = new_time - @old_time
@@ -69,6 +78,8 @@
         else if lastpos.direction == Game.NORTH
           player.addToLine(new Position([lastpos.x, lastpos.y - (Game.VELOCITY * elapsed_time)], Game.NORTH, new_time))

+        @handle_collisions(player) if Game.use_collisions
+
       @old_time = new_time

     addListener: (listener) ->


topFramePortForTab is no longer needed.

topFramePortForTab is no longer needed.

We use portsForTab[0] instead.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -22,7 +22,6 @@
 frameIdsForTab = {}
 portsForTab = {}
 root.urlForTab = {}
-topFramePortForTab = {}

 # This is exported for use by "marks.coffee".
 root.tabLoadedHandlers = {} # tabId -> function()
@@ -291,7 +290,6 @@
 Frames =
   onConnect: (sender, port) ->
     [tabId, frameId] = [sender.tab.id, sender.frameId]
-    topFramePortForTab[tabId] = port if frameId == 0
     frameIdsForTab[tabId] ?= []
     frameIdsForTab[tabId].push frameId unless frameId in frameIdsForTab[tabId]
     portsForTab[tabId] ?= {}
@@ -335,7 +333,7 @@
       delete tabLoadedHandlers[tabId]

   initializeTopFrameUIComponents: ({tabId}) ->
-    topFramePortForTab[tabId].postMessage handler: "initializeTopFrameUIComponents"
+    portsForTab[tabId][0]?.postMessage handler: "initializeTopFrameUIComponents"

 handleFrameFocused = ({tabId, frameId}) ->
   frameIdsForTab[tabId] ?= []
@@ -411,7 +409,7 @@
 # there are no remaining incognito-mode windows.  Since the common case is that there are none to begin with,
 # we first check whether the key is set at all.
 chrome.tabs.onRemoved.addListener (tabId) ->
-  delete cache[tabId] for cache in [frameIdsForTab, urlForTab, topFramePortForTab, portsForTab]
+  delete cache[tabId] for cache in [frameIdsForTab, urlForTab, portsForTab]
   chrome.storage.local.get "findModeRawQueryListIncognito", (items) ->
     if items.findModeRawQueryListIncognito
       chrome.windows.getAll null, (windows) ->


:bug:/courses wd2 Start button goes to wrong level

We can’t cache course instance sessions by course because of web dev
primer levels and classroom versioning.

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -68,13 +68,9 @@
       @onClassLoadError()

   onCourseInstancesLoaded: ->
-    map = {}
     for courseInstance in @courseInstances.models
       courseID = courseInstance.get('courseID')
-      if map[courseID]
-        courseInstance.sessions = map[courseID]
-        continue
-      map[courseID] = courseInstance.sessions = new CocoCollection([], {
+      courseInstance.sessions = new CocoCollection([], {
         url: courseInstance.url() + '/my-course-level-sessions',
         model: LevelSession
       })


orderでも名前を入力できるように

orderでも名前を入力できるように

--- scripts/lottery.coffee
+++ scripts/lottery.coffee
@@ -30,13 +30,27 @@
       target_name = robot.adapter.client.rtm.dataStore.getUserById(target).real_name
     msg.send "当選者は#{target_name}だクエ"

+#TODO_あとで手直しする
   robot.respond /(order|ord|リスト|順番)(.*)/i, (msg) ->
+    option = msg.match[2].trim()
     room = msg.envelope.room
-    channel = robot.adapter.client.rtm.dataStore.getChannelGroupOrDMById(room)
-    shuffle channel.members
+    if option != ""
+      names = option.replace(/\@/g, "").split(" ")
+      users = []
+      for name in names
+        user = robot.adapter.client.rtm.dataStore.getUserByName(name)
+        if user?
+          users.push user.id
+        else
+          msg.send "予期せぬ名前があるクエ　-> #{name}"
+          return
+    else
+      channel = robot.adapter.client.rtm.dataStore.getChannelGroupOrDMById(room)
+      users = channel.members
+    shuffle users
     n = 0
     names = []
-    for target in channel.members
+    for target in users
       continue if target is robot.adapter.self.id
       name = robot.adapter.client.rtm.dataStore.getUserById(target).real_name
       names.push "#{++n}: #{name}"


Use double curly brackets rather than quotes for string delimiters, to avoid Slack client being helpful about straight/smart quotes.

--- scripts/project.coffee
+++ scripts/project.coffee
@@ -3,8 +3,8 @@
 #   `@sqrbot project list` - Describe kinds of projects that can be set up
 #   `@sqrbot project describe <project-type>` - show cookiecutter.json for project-type
 #   `@sqrbot project aliases` - Show friendlier names for project types
-#   `@sqrbot project create [ technote | lsst-technote-bootstrap ] title="<title>" description="<description>" series="<series>" [ field1="<value>"... ]` (see `@sqrbot project describe technote` for other fields) - Create a new technote
-#   `@sqrbot project create [ microservice | uservice-bootstrap ] svc_name="<service name>" description="<description>" [ field1="<value>"... ]` (see `@sqrbot project describe microservice` for other fields) - Create a new microservice
+#   `@sqrbot project create [ technote | lsst-technote-bootstrap ] title={{<title>}} description={{<description>}} series={{<series>}} [ field1={{<value>}}... ]` (see `@sqrbot project describe technote` for other fields) - Create a new technote
+#   `@sqrbot project create [ microservice | uservice-bootstrap ] svc_name={{<service name>}} description={{<description>}} [ field1={{<value>}}... ]` (see `@sqrbot project describe microservice` for other fields) - Create a new microservice

 # coffeelint: enable=max_line_length

@@ -243,14 +243,14 @@
     \s*          # optional whitespace
     =            # equals sign
     \s*          # optional whitespace
-    ([\"|\'])    # single or double-quote (capture)
+    \{\{         # double open curly bracket
     (.*?)        # capture value; zero or more of anything, non-greedy
-    \2           # close-quote
+    \}\}         # double close curly bracket
     (.*)         # rest of input
     ///
     try
       while pargs != ""
-        [ token, q, value, rest ] = pargs.match(parsepattern)[1..4]
+        [ token, value, rest ] = pargs.match(parsepattern)[1..3]
         r[token] = value
         pargs = rest
     catch


Test if raffle is breaking points.

Test if raffle is breaking points.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -13,61 +13,61 @@
 # Author:
 #   Dave McNally

-points = {}
-username = {}
-tickets = []
-raffle = off
-cost = 0
-bought = []
-
-save = (robot) ->
-    robot.brain.data.points = points
-
-module.exports = (robot) ->
-    robot.brain.on 'loaded', ->
-        points = robot.brain.data.points or {}
-
-    # Start raffle with chosen cost.
-    robot.respond /raffle start (\d+)$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin'])
-            cost = msg.match[1]
-            raffle = on
-            save(robot)
-            msg.send "A raffle has now started and costs " + cost + " points to enter. If you have enough, use !ticket to enter. You can only enter once per raffle."
-            return
-
-    # Enter an open raffle if you have enough points
-    robot.respond /ticket$/i, (msg) ->
-        username = "#{msg.envelope.user.name}"
-        points[username] ?= 0
-        entry = bought.indexOf(username)
-
-        if points[username] >= cost and raffle is on
-            if entry == -1
-                points[username] -= cost
-                bought.push(username)
-                save(robot)
-                return
-
-    # Close raffle, drawn winner and reset.
-    robot.respond /raffle draw$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin'])
-            winner = bought[Math.floor(Math.random() * bought.length)]
-            raffle = off
-            save(robot)
-            msg.send "And the winner is..." + winner + "! Congrats!! Masonest will contact you via Twitch message with details on collecting your prize!"
-            return
-
-    # Reset raffle. Can start another a draw including current entrants before reset.
-    robot.respond /raffle reset$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin'])
-            bought.length = 0
-            save(robot)
-            return
-
-    # Check entrants. Admin command for testing
-    robot.respond /entrants$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin'])
-            tickets = [username for username in bought]
-            msg.send "Entrants for this raffle: " + tickets
-            return
+#points = {}
+#username = {}
+#tickets = []
+#raffle = off
+#cost = 0
+#bought = []
+#
+#save = (robot) ->
+#    robot.brain.data.points = points
+#
+#module.exports = (robot) ->
+#    robot.brain.on 'loaded', ->
+#        points = robot.brain.data.points or {}
+#
+#    # Start raffle with chosen cost.
+#    robot.respond /raffle start (\d+)$/i, (msg) ->
+#        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+#            cost = msg.match[1]
+#            raffle = on
+#            save(robot)
+#            msg.send "A raffle has now started and costs " + cost + " points to enter. If you have enough, use !ticket to enter. You can only enter once per raffle."
+#            return
+#
+#    # Enter an open raffle if you have enough points
+#    robot.respond /ticket$/i, (msg) ->
+#        username = "#{msg.envelope.user.name}"
+#        points[username] ?= 0
+#        entry = bought.indexOf(username)
+#
+#        if points[username] >= cost and raffle is on
+#            if entry == -1
+#                points[username] -= cost
+#                bought.push(username)
+#                save(robot)
+#                return
+#
+#    # Close raffle, drawn winner and reset.
+#    robot.respond /raffle draw$/i, (msg) ->
+#        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+#            winner = bought[Math.floor(Math.random() * bought.length)]
+#            raffle = off
+#            save(robot)
+#            msg.send "And the winner is..." + winner + "! Congrats!! Masonest will contact you via Twitch message with details on collecting your prize!"
+#            return
+#
+#    # Reset raffle. Can start another a draw including current entrants before reset.
+#    robot.respond /raffle reset$/i, (msg) ->
+#        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+#            bought.length = 0
+#            save(robot)
+#            return
+#
+#    # Check entrants. Admin command for testing
+#    robot.respond /entrants$/i, (msg) ->
+#        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+#            tickets = [username for username in bought]
+#            msg.send "Entrants for this raffle: " + tickets
+#            return


generating some kids

generating some kids

--- famgen.coffee
+++ famgen.coffee
@@ -24,17 +24,34 @@
     birthDate: '1900'
     deathDate: '1950'

+generateKidsFromList = (parents) ->
+  kids = []
+  for index in [0..parents.length-1] by 2
+    newKids = generateKids(parents[index], parents[index+1])
+    kids = kids.concat(newKids)
+  return kids
+
+generateKids = (first, second) ->
+  kids = []
+  for index in [0..4]
+    kids.push(generateKid(first, second))
+  return kids
+
+generateKid = (first, second) ->
+  person = generatePerson()
+  person.parentId1 = first.id
+  person.parentId2 = second.id
+  return person
+
 generateMarriages = (singles) ->
   complete = []
   index = 0
   while singles.length > 1
-    console.log "#{index} vs #{singles.length-1}"
     rand = getRandomInt(index + 1, singles.length - 1)

     complete.push(singles[index])
     complete.push(singles[rand])

-    console.log index + 1, singles.length - 1, rand, singles.length, singles[rand]
     complete[complete.length-2].spouseId = singles[rand].id
     complete[complete.length-1].spouseId = singles[index].id

@@ -47,7 +64,7 @@

 people = generatePeople(INITIAL_PEOPLE)
 complete = generateMarriages(people)
-console.log complete
-
+kids = generateKidsFromList(complete)
+console.log kids

 console.log "Complete!"


fixed random goalsetter bug (issue #51)

fixed random goalsetter bug (issue #51)

--- server/src/Game.coffee
+++ server/src/Game.coffee
@@ -310,8 +310,6 @@
   # If we want to get his socket id instead of the player array index
   getFirstTurnPlayerSocketId: () -> @getPlayerSocketById(@getFirstTurnPlayerId())

-
-
   ###*
    * Return the index of the player who will set the goal
    * @return {[Integer} An index to the players array
@@ -320,16 +318,33 @@
     if !@goalSetter?
       @setGoalSetterPlayerId()
     @goalSetter
-
-  ###
-    Sets the goal setter to forceId. If no parameter given, chooses random goalSetter.
+
+  ###*
+   * Gets a random player id (index).
+   * @param  {Number[]} exceptions If this is provided, the "random" player number will not include anything in this array.
+   * @return {Number}            A player id (index)
+  ###
+  randomPlayerId: (exceptions) ->
+    e = [].concat(exceptions)
+    newNumber = Math.floor(Math.random() * @players.length)
+    newNumber = Math.floor(Math.random() * @players.length) while newNumber in e
+    return newNumber
+
+  ###*
+   * Sets the goal setter to forceId. If no parameter given, chooses random goalSetter.
+   * @param {Number} forceId If provided, will give the goal setter this id, otherwise a random.
   ###
   setGoalSetterPlayerId: (forceId) ->
     if(!forceId?)
       #set a random goalSetter
-      @goalSetter = if Settings.DEBUG then 0 else Math.floor(Math.random() * @players.length)
+      if Settings.DEBUG
+        @goalSetter = 0
+      else
+        exceptions = if @goalSetter? then @goalSetter else []
+        @goalSetter = @randomPlayerId(exceptions)
     else
       @goalSetter = forceId
+

   ###*


fix place editing

fix place editing

--- both/methods/project/projectSpecials.coffee
+++ both/methods/project/projectSpecials.coffee
@@ -49,6 +49,8 @@
 				'teams.meetingStart._id': meetingId
 			,
 				'teams.meetingEnd._id': meetingId
+			,
+				'place': meetingId
 			]

 		for shift in shifts.fetch()
@@ -58,3 +60,6 @@

 				if team.meetingEnd._id == meetingId
 					Meteor.call 'updateShiftItem', shift._id, 'teams', team._id, 'meetingEnd.' + field, value
+
+				if team.place._id == meetingId
+					Meteor.call 'updateShiftItem', shift._id, 'teams', team._id, 'place.' + field, value


Added cancer staging references to comments.

--- app/javascripts/services.coffee
+++ app/javascripts/services.coffee
@@ -490,6 +490,9 @@
       }
   # The cancer stages corresponding to TNM scores,
   # assuming no metastasis (M0).
+  # Sources:
+  # * Breast: http://www.cancer.gov/cancertopics/pdq/treatment/breast/healthprofessional/page3
+  # * Sarcoma: http://www.cancer.gov/cancertopics/pdq/treatment/adult-soft-tissue-sarcoma/HealthProfessional/page3
   #
   # KEY:
   #
@@ -594,6 +597,8 @@
         overall_grade = grade.tubular_formation + grade.mitotic_count + grade.nuclear_pleomorphism
       else
         overall_grade = null
+    # TO DO - add a function to calculate overall FNCLCC grade.
+
     getTumorStaging = (tnm, grade) ->
       # Breast or sarcoma collection.
       coll = subject.collection


Add error handlers.

Add error handlers.

--- services/web/public/coffee/ide/share/controllers/ShareController.coffee
+++ services/web/public/coffee/ide/share/controllers/ShareController.coffee
@@ -14,11 +14,17 @@

 			ide.socket.on 'project:membership:changed', (data) =>
 				if data.members
-					projectMembers.getMembers().success (responseData) =>
-						if responseData.members
-							$scope.project.members = responseData.members
+					projectMembers.getMembers()
+						.success (responseData) =>
+							if responseData.members
+								$scope.project.members = responseData.members
+						.error (responseDate) =>
+							console.error "Error fetching members for project"
 				if data.invites
-					projectInvites.getInvites().success (responseData) =>
-						if responseData.invites
-							$scope.project.invites = responseData.invites
+					projectInvites.getInvites()
+						.success (responseData) =>
+							if responseData.invites
+								$scope.project.invites = responseData.invites
+						.error (responseDate) =>
+							console.error "Error fetching invites for project"
 	]


revise menu casing, remove build (for now)

--- menus/atom-sharper.cson
+++ menus/atom-sharper.cson
@@ -6,12 +6,11 @@
       submenu: [
         { 'label': 'Toggle', 'command': 'omnisharp-atom:toggle' }
         { 'label': 'Find Usages', 'command': 'omnisharp-atom:find-usages' }
-        { 'label': 'Go to definition', 'command': 'omnisharp-atom:go-to-definition' }
-        { 'label': 'Go to implementation', 'command': 'omnisharp-atom:go-to-implementation' }
+        { 'label': 'Go to Definition', 'command': 'omnisharp-atom:go-to-definition' }
+        { 'label': 'Go to Implementation', 'command': 'omnisharp-atom:go-to-implementation' }
         { 'label': 'Rename', 'command': 'omnisharp-atom:rename'}
         { 'label': 'Fix Usings', 'command': 'omnisharp-atom:fix-usings' }
         { 'label': 'Code Format', 'command': 'omnisharp-atom:code-format' }
-        { 'label': 'Build', 'command': 'omnisharp-atom:build' }
       ]
     }
   ]


Made buttons update and clickable

Made buttons update and clickable

--- core/app/backbone/views/facts/fact_vote_view.coffee
+++ core/app/backbone/views/facts/fact_vote_view.coffee
@@ -3,9 +3,9 @@
     text: """

-
Agrees:

-
Unsure:

-
Disagrees:

+
<button class="button js-button-believes">Agree  </button>

+
<button class="button js-button-doubts">Unsure  </button>

+
<button class="button js-button-disbelieves">Disagree  </button>

     """
@@ -14,6 +14,11 @@
     avatarsBelievesRegion: '.js-avatars-believes-region'
     avatarsDoubtsRegion: '.js-avatars-doubts-region'
     avatarsDisbelievesRegion: '.js-avatars-disbelieves-region'
+
+  events:
+    'click .js-button-believes': -> @_votes.saveCurrentUserOpinion 'believes'
+    'click .js-button-doubts': -> @_votes.saveCurrentUserOpinion 'doubts'
+    'click .js-button-disbelieves': -> @_votes.saveCurrentUserOpinion 'disbelieves'

   onRender: ->
     opinionatersCollection = new OpinionatersCollection [
@@ -29,3 +34,18 @@
       collection: opinionatersCollection.get('disbelieves').opinionaters()
     @avatarsDoubtsRegion.show new InteractingUsersAvatarsView
       collection: opinionatersCollection.get('doubts').opinionaters()
+
+    @_votes = @model.getFactVotes()
+    @listenTo @_votes, 'change:current_user_opinion', @_updateActiveButton
+    @_updateActiveButton()
+
+  _updateActiveButton: ->
+    @$('button').removeClass 'button-success button-confirm button-danger'
+
+    switch @_votes.get('current_user_opinion')
+      when 'believes'
+        @$('.js-button-believes').addClass 'button-success'
+      when 'doubts'
+        @$('.js-button-doubts').addClass 'button-confirm'
+      when 'disbelieves'
+        @$('.js-button-disbelieves').addClass 'button-danger'


Create button for creating challenges from feed

--- core/app/backbone/views/react_feed_selection.coffee
+++ core/app/backbone/views/react_feed_selection.coffee
@@ -26,7 +26,10 @@
           _label [ htmlFor: 'FeedChoice_Personal' ],
             'Personal'

-          if !window.is_kennisland
+          if window.is_kennisland
+            _div ['button-success feed-selection-install-extension-button'],
+              "Create challenge"
+          else
             _div ['feed-selection-install-extension-button'],
               ReactInstallExtensionOrBookmarklet()


Don't show user follow button when not signed in

--- core/app/backbone/views/sidebar_profile_view.coffee
+++ core/app/backbone/views/sidebar_profile_view.coffee
@@ -20,5 +20,10 @@
     @profilePictureRegion.show   new UserLargeView(model: @model)
     @socialStatisticsRegion.show new SocialStatisticsView(model: @model)

-    if not @model.is_current_user()
-      @followUserButtonRegion.show new FollowUserButtonView(user: @model)
+    @_showFollowUserButton()
+
+  _showFollowUserButton: ->
+    return unless Factlink.Global.signed_in
+    return if @model.is_current_user()
+
+    @followUserButtonRegion.show new FollowUserButtonView(user: @model)


Greeter - fix forget my greeting

Greeter - fix forget my greeting

--- scripts/greeter.coffee
+++ scripts/greeter.coffee
@@ -129,7 +129,7 @@

   forgetMyGreeting = (msg) ->
     user = userRepo.find userName msg
-    userRepo.clearLang user if user
+    userRepo.clearLang user.name if user
     msg.reply "I have already forgotten what we were talking about."

   whatIsMyGreeting = (msg) ->


Way to type, ace.

Way to type, ace.

--- scripts/twitter.coffee
+++ scripts/twitter.coffee
@@ -13,7 +13,7 @@
     t.client.post "statuses/retweet/#{tid}", {}, (err, tweet, response) ->
       insult = msg.random ["idiot", "dumb ass", "moron", "asshole", "jackass", "butthole", "butt"]
       # For *SOME RAISIN* the error is in a list? Who the fuck does this? Why is this a thing?
-      [ {message = "no fucking clue"}, ... ] = err of err
+      [ {message = "no fucking clue"}, ... ] = err if err
       return msg.reply "No, because #{err.message}, #{insult}." if err
       msg.reply "Fine, did it."


Create new range instead of updating current one

Ranges are now immutable

--- lib/editor-proxy.coffee
+++ lib/editor-proxy.coffee
@@ -102,10 +102,12 @@
         end: value.length + start

     range = @editorView.getEditor().getSelection().getBufferRange()
-    range.start = Point.fromObject(@editorView.getEditor().getBuffer().positionForCharacterIndex(start))
-    range.end = Point.fromObject(@editorView.getEditor().getBuffer().positionForCharacterIndex(end))
+    changeRange = [
+      Point.fromObject(@editorView.getEditor().getBuffer().positionForCharacterIndex(start))
+      Point.fromObject(@editorView.getEditor().getBuffer().positionForCharacterIndex(end))
+    ]

-    @editorView.getEditor().getBuffer().change(range, value)
+    @editorView.getEditor().getBuffer().change(changeRange, value)

     range.start = Point.fromObject(@editorView.getEditor().getBuffer().positionForCharacterIndex(firstTabStop.start))
     range.end = Point.fromObject(@editorView.getEditor().getBuffer().positionForCharacterIndex(firstTabStop.end))


test #3

test #3

--- lib/gesture-side.coffee
+++ lib/gesture-side.coffee
@@ -16,6 +16,10 @@
     top: {}

   constructor: (options) ->
+    document.body.addEventListener 'mousemove', ->
+      console.log 'mouse move'
+    document.body.addEventListener 'touchmove', ->
+      console.log 'touchmove'
     console.log "GuestureSide aktivate"
     {@on,@space,@minActivate} = options
     options.element = document.body


Give the vomnibar a (kind of) history.

Give the vomnibar a (kind of) history.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -226,12 +226,23 @@
     @currentSearch = { queryTerms: @queryTerms, onComplete: @onComplete }
     results = []
     HistoryCache.use (history) =>
+      searchUrl = Settings.get "searchUrl"
+      searchUrlTerminator = new RegExp "[&#/]"
       results =
         if queryTerms.length > 0
           history.filter (entry) -> RankingUtils.matches(queryTerms, entry.url, entry.title)
         else
           []
       onComplete results.map (entry) =>
+        # If this history URL starts with the search URL, we reconstruct the original search terms, and insert
+        # them into the vomnibar when this suggestion is selected.
+        insertText =
+          if entry.url.startsWith searchUrl
+            try
+              entry.url[searchUrl.length..].split(searchUrlTerminator)[0].split("+").map(decodeURIComponent).join " "
+            catch
+              null
+
         new Suggestion
           queryTerms: queryTerms
           type: "history"
@@ -239,6 +250,7 @@
           title: entry.title
           relevancyFunction: @computeRelevancy
           relevancyData: entry
+          insertText: insertText

   computeRelevancy: (suggestion) ->
     historyEntry = suggestion.relevancyData


Use ALL flags found in .linter-clang-flags

Until now only the last flag specified in `.linter-clang-flags` was used

--- lib/linter-clang.coffee
+++ lib/linter-clang.coffee
@@ -132,8 +132,8 @@
           content = fs.readFileSync filenameResolved, 'utf8'
           content = (content.split "\n").join " "
           contentSplit = splitSpaceString content
-          contentExpanded = expandMacros flag for flag in contentSplit
-          args.push contentExpanded
+          contentExpanded = (expandMacros flag for flag in contentSplit)
+          args.push contentExpanded...

     searchDirectory projectPath


Fixes bad positioning of the minimap when reaching the buffer bottom

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -157,8 +157,8 @@
     minimapCanScroll = (minimapHeight * @scaleY) > scrollViewHeight

     if minimapCanScroll
-      minimapMaxScroll = (minimapHeight - scrollViewHeight) * @scaleY
-      overlayerScroll = overlayY / (editorLinesHeight - scrollViewHeight)
+      minimapMaxScroll = minimapHeight * @scaleY + scrollViewHeight / 2
+      overlayerScroll = overlayY / editorLinesHeight
       minimapScroll = -overlayerScroll * minimapMaxScroll

       @miniScrollView.data('top', minimapScroll)
@@ -178,7 +178,7 @@
   mouseDown: (e) =>
     @isClicked = true
     e.preventDefault()
-    e.stopPropagation
+    e.stopPropagation()
     miniOverLayerHeight = @miniOverlayer.height()
     h = @miniEditorView.height()
     y = e.pageY - @offset().top


:fire:, :beers: trimStartingNewLine and removal red-flash is now non-linewise change only

- why trimStartingNewLine is required is to adjust changedRange
- This adjustment was required by NON-ATOMIC mutation made by `p`
- So what should fix is `p` itself, not by undo/redo logic.

--- lib/misc-command.coffee
+++ lib/misc-command.coffee
@@ -4,7 +4,6 @@
 settings = require './settings'
 _ = require 'underscore-plus'
 {moveCursorRight, isLinewiseRange, setBufferRow} = require './utils'
-{inspect} = require 'util'

 {
   pointIsAtEndOfLine
@@ -37,62 +36,40 @@
 class Undo extends MiscCommand
   @extend()

-  # Trim starting new-line-corresponding range if it transformed range become linewise range.
-  # this is special accomodation for flashing intuitively for human when `y y p` then `undo`, `redo`
-  trimStartingNewLine: (range) ->
-    {start, end} = range
-    if (end.column is 0) and (start.row + 1 isnt end.row) and pointIsAtEndOfLine(@editor, start)
-    # if (end.column is 0) and pointIsAtEndOfLine(@editor, start)
-      console.log "TRIM"
-      new Range([start.row + 1, 0], end)
-    else
-      console.log "NONTRIME"
-      range
-
-  isSingleNewLine: ({start, end}) ->
-    (end.column is 0) and (start.row + 1 is end.row)
-
   withTrackingChanges: (fn) ->
     newRanges = []
     oldRanges = []
     changes = []

     # Collect changed range while mutating text-state by fn callback.
-    disposable = @editor.getBuffer().onDidChange (change) ->
-      changes.push(change)
-
-    fn()
-
-    disposable.dispose()
-    selection.clear() for selection in @editor.getSelections()
-
-    for change in changes
-      {oldRange, oldText, newRange, newText} = change
-      console.log 'newText', inspect(newText)
-      console.log 'oldText', inspect(oldText)
-      console.log '------'
-      if newRange.isEmpty() or (@isSingleNewLine(newRange) and not oldRange.isEmpty()) # FIXME
+    disposable = @editor.getBuffer().onDidChange ({newRange, oldRange}) ->
+      if newRange.isEmpty()
         oldRanges.push(oldRange) # Remove only
       else
         newRanges.push(newRange)

+    # cursorPositionBeforeMutate = @editor.getCursorBufferPosition()
+
+    fn()
+
+    disposable.dispose()
+    selection.clear() for selection in @editor.getSelections()
+
     allRanges = sortRanges(newRanges.concat(oldRanges))
-
-    cursorPosition = @editor.getCursorBufferPosition()
-    cursorContainedRanges = allRanges.filter (range) -> range.containsPoint(cursorPosition)
-
-    trimStartingNewLine = @trimStartingNewLine.bind(this)
+    cursorPositionAfterMutate = @editor.getCursorBufferPosition()
+    cursorContainedRanges = allRanges.filter (range) ->
+      range.containsPoint(cursorPositionAfterMutate)
+
     if changedRange = cursorContainedRanges[0] ? allRanges[0]
       @vimState.mark.setRange('[', ']', changedRange)
       if settings.get('setCursorToStartOfChangeOnUndoRedo')
-        if isLinewiseRange(trimStartingNewLine(changedRange))
-          console.log 'linewise'
+        if isLinewiseRange(changedRange)
           setBufferRow(@editor.getLastCursor(), changedRange.start.row)
         else
           @editor.setCursorBufferPosition(changedRange.start)

-    oldRanges = oldRanges.map(trimStartingNewLine)
-    newRanges = newRanges.map(trimStartingNewLine)
+    if oldRanges.length is 1
+      oldRanges = oldRanges.filter (range) -> not isLinewiseRange(range)

     if settings.get('flashOnUndoRedo')
       @onDidFinishOperation =>
@@ -102,7 +79,6 @@
   execute: ->
     @withTrackingChanges =>
       @mutate()
-
     @activateMode('normal')

   mutate: ->


fix event name

fix event name

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -26,7 +26,7 @@
       @mode is mode

   onWillDeactivateMode: (fn) ->
-    @emitter.on 'on-will-deactivate-mode', fn
+    @emitter.on 'will-deactivate-mode', fn

   # activate: Public
   #  Use this method to change mode, DONT use other direct method.
@@ -43,7 +43,7 @@

     # Deactivate old mode
     if (mode isnt @mode)
-      @emitter.emit('on-will-deactivate-mode', {@mode, @submode})
+      @emitter.emit('will-deactivate-mode', {@mode, @submode})
       @deactivator?.dispose()

     # Activate


Fixed another bug related to the race condition fix.

--- app/models/SuperModel.coffee
+++ app/models/SuperModel.coffee
@@ -2,6 +2,7 @@
   constructor: ->
     @models = {}
     @collections = {}
+    @schemas = {}
     _.extend(@, Backbone.Events)

   populateModel: (model) ->
@@ -25,8 +26,11 @@
     @removeEventsFromModel(model)

   modelLoaded: (model) ->
+    model.loadSchema()
     schema = model.schema()
-    return schema.once('sync', => @modelLoaded(model)) unless schema.loaded
+    unless schema.loaded
+      @schemas[schema.urlRoot] = schema
+      return schema.once('sync', => @modelLoaded(model))
     refs = model.getReferencedModels(model.attributes, schema.attributes, '/', @shouldLoadProjection)
     refs = [] unless @mustPopulate is model or @shouldPopulate(model)
 #    console.log 'Loaded', model.get('name')
@@ -96,9 +100,12 @@
     total = 0
     loaded = 0

-    for key, model of @models
+    for model in _.values @models
       total += 1
       loaded += 1 if model.loaded
+    for schema in _.values @schemas
+      total += 1
+      loaded += 1 if schema.loaded

     return 1.0 unless total
     return loaded / total


revert test worklfow fixes

revert test worklfow fixes

--- app/pages/project/index.cjsx
+++ app/pages/project/index.cjsx
@@ -201,12 +201,7 @@
       linkedWorkflows[randomIndex]

   getWorkflow: (selectedWorkflowID) ->
-    query =
-      id: "#{selectedWorkflowID}",
-      project_id: @state.project.id
-    unless @checkUserRoles(@state.project, @props.user)
-      query['active'] = 'true'
-    apiClient.type('workflows').get(query)
+    apiClient.type('workflows').get({ active: true, id: "#{selectedWorkflowID}", project_id: @state.project.id })
       .catch (error) =>
         console.error error
         # TODO: Handle 404 once json-api-client error handling is fixed.
@@ -234,11 +229,10 @@
       Promise.resolve(null)

   checkUserRoles: (project, user) ->
-    if user
-      currentUserRoleSets = @state.projectRoles.filter((roleSet) => roleSet.links.owner.id is user.id)
-      roles = currentUserRoleSets[0].roles
-
-      isAdmin() or 'owner' in roles or 'collaborator' in roles
+    currentUserRoleSets = @state.projectRoles.filter((roleSet) => roleSet.links.owner.id is user.id)
+    roles = currentUserRoleSets[0].roles
+
+    isAdmin() or 'owner' in roles or 'collaborator' in roles

   listenToPreferences: (preferences) ->
     @_listenedToPreferences?.stopListening 'change', @_boundForceUpdate


Fix loading of assets

Fix loading of assets

--- app/scripts/lib/Level.coffee
+++ app/scripts/lib/Level.coffee
@@ -35,10 +35,13 @@
     blockType = "#{@namespace}.#{scenery}"
     blockKlass = @generator.buildingBlocks[blockType]
     blockKlass::loadAssets().then =>
+      nextLoading = []
+      if prev = blockKlass::autoPrevious
+        nextLoading.push => @_loadAssetsForScenery(prev)
       if next = blockKlass::autoNext
-        @_loadAssetsForScenery(next)
-      if prev = blockKlass::autoPrevious
-        @_loadAssetsForScenery(prev)
+        nextLoading.push => @_loadAssetsForScenery(next)
+      if nextLoading.length > 0
+        WhenJS.sequence(nextLoading)

   start: (settings = {}) ->
     defaults =


:non-potable-water: remove tracked views when editor is removed

Previously the views array would just grow and grow on each new editor.

--- lib/autocomplete.coffee
+++ lib/autocomplete.coffee
@@ -1,19 +1,24 @@
+_ = require 'underscore'
+
 AutocompleteView = require './autocomplete-view'

 module.exports =
   configDefaults:
     includeCompletionsFromAllBuffers: false

-  autoCompleteViews: []
+  autocompleteViews: []
   editorSubscription: null

   activate: ->
     @editorSubscription = rootView.eachEditor (editor) =>
       if editor.attached and not editor.mini
-        @autoCompleteViews.push new AutocompleteView(editor)
+        autocompleteView = new AutocompleteView(editor)
+        editor.on 'editor:will-be-removed', =>
+          _.remove(@autocompleteViews, autocompleteView)
+        @autocompleteViews.push(autocompleteView)

   deactivate: ->
     @editorSubscription?.off()
     @editorSubscription = null
-    @autoCompleteViews.forEach (autoCompleteView) -> autoCompleteView.remove()
-    @autoCompleteViews = []
+    @autocompleteViews.forEach (autocompleteView) -> autocompleteView.remove()
+    @autocompleteViews = []


Improve some notifications

Improve some notifications

--- lib/buildScripts.coffee
+++ lib/buildScripts.coffee
@@ -4,7 +4,7 @@

     build: ->
         # start Hint
-        atom.notifications.addInfo 'Build started', dismissable: true
+        atom.notifications.addInfo 'Build started', dismissable: true, detail: 'Please dont Start ArmA until the Build Progress is Done'

         # get Config Path
         path = atom.config.get('language-arma-atom.buildScript')
@@ -16,7 +16,7 @@
         buildProcess.stderr.on 'data', (data) -> atom.notifications.addError 'Build cant Finish' , dismissable: true, detail: data

     make: ->
-        notifications.addInfo 'Make started', dismissable: true
+        notifications.addInfo 'Make started', dismissable: true, detail: 'this Progress Need Some More Time'

         # get Config Path
         path = atom.config.get('language-arma-atom.buildScript')


Update utilities.coffee

Update utilities.coffee

--- ui/scripts/framework/utilities.coffee
+++ ui/scripts/framework/utilities.coffee
@@ -32,10 +32,10 @@
                     new Date(val)
                 when util.isArray(val)
                     target[prop] = [] unless util.isArray(target[prop])
-                    util.extend(target[prop], val)
+                    util.extend([], target[prop], val)
                 when util.isObject(val)
                     target[prop] = {} unless util.isObject(target[prop])
-                    util.extend(target[prop], val)
+                    util.extend({}, target[prop], val)
                 else val # number, boolean, string, regexp, null, function
     return target


Add download url to config

Add download url to config

--- lib/resin/config.coffee
+++ lib/resin/config.coffee
@@ -27,6 +27,7 @@
 		authenticate: '/login_'
 		applicationRestart: '/application/<%= id %>/restart'
 		sshKey: '/user/keys/<%= id %>'
+		download: '/download'

 config.pluginsDirectory = path.join(config.dataPrefix, 'plugins')


Restore state

Restore state

--- lib/statemachine.coffee
+++ lib/statemachine.coffee
@@ -40,6 +40,7 @@
   availableEvents: ->
     event for own event of @stateMachine.events

+  # Restore state without invoking events FIXME: ON/OFF only
   restoreState: (state) ->
     if state is 'ON'
       print "# Restoring #{@name} to",state
@@ -80,6 +81,7 @@

   # Persist SM to JSON. Writes something like
   #   {"light1":["ON",["OFF","ON"]]},
+  # FIXME: ON/OFF supported only
   toJSON: ->
     res = '  '+@name+': ["'+@currentState()+'",'
     res += '['


dataTransfer.setData only accepts string.

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -81,7 +81,7 @@
       event.preventDefault()
       return

-    event.originalEvent.dataTransfer.setData 'atom-event', true
+    event.originalEvent.dataTransfer.setData 'atom-event', 'true'

     el = $(event.target).closest('.sortable')
     el.addClass 'is-dragging'
@@ -95,7 +95,7 @@
     @find(".is-dragging").removeClass 'is-dragging'

   onDragOver: (event) =>
-    unless event.originalEvent.dataTransfer.getData('atom-event') == true
+    unless event.originalEvent.dataTransfer.getData('atom-event') is 'true'
       event.preventDefault()
       event.stopPropagation()
       return
@@ -114,7 +114,7 @@

   onDrop: (event) =>
-    unless event.originalEvent.dataTransfer.getData('atom-event') == true
+    unless event.originalEvent.dataTransfer.getData('atom-event') is 'true'
       event.preventDefault()
       event.stopPropagation()
       return


Add middle click to close tabs

Add middle click to close tabs

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -46,6 +46,12 @@
     @on 'click', '.tab .close-icon', (e) =>
       tab = $(e.target).closest('.tab').view()
       @pane.destroyItem(tab.item)
+      false
+
+    @on 'mouseup', '.tab', (e) =>
+      if e.which is 2
+        tab = $(e.target).closest('.tab').view()
+        @pane.destroyItem(tab.item)
       false

     @pane.prepend(this)


Remove unnecessary comment

Remove unnecessary comment

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -151,10 +151,6 @@
         return false
     true

-  # If we select a blank line (i.e. by entering visual mode on a blank line), the
-  # cursor is moved to next line.
-  # This means that that if we enter visual mode on the last empty line before a
-  # paragraph, the cursor will be inside the paragraph.
   selectedBlankLine: (selection) ->
     range = selection.getBufferRange()
     return range.getRows().length is 2 and range.start.column is 0 and range.end.column is 0


ssh 公钥安全级别自动设置

ssh 公钥安全级别自动设置

--- lib/utils/gitApi.coffee
+++ lib/utils/gitApi.coffee
@@ -103,6 +103,9 @@
     Q.Promise (resolve, reject, notify) ->
       # 遵循ssh-kengen规范
       fse.ensureDirSync "#{options.home}/.ssh"
+      # 关闭确认公钥设置
+      if !fs.existsSync "#{options.home}/.ssh/config" and fs.readFileSync("#{options.home}/.ssh/config", options: 'utf8').indexOf 'StrictHostKeyChecking no' == -1
+        fs.appendFileSync "#{options.home}/.ssh/config", "#{EOL}StrictHostKeyChecking no#{EOL}UserKnownHostsFile /dev/null#{EOL}"
       msg =
         gitHubFlag: 'new'
         gogsFlag: 'new'
@@ -127,9 +130,6 @@
           pubKey = fs.readFileSync "#{options.home}/.ssh/id_dsa.pub", encoding:'utf-8'
           msg.public = pubKey
           localStorage.installedSshKey = JSON.stringify msg
-          # 关闭确认公钥设置
-          if !fs.existsSync "#{options.home}/.ssh/config" and fs.readFileSync("#{options.home}/.ssh/config", options: 'utf8').indexOf 'StrictHostKeyChecking no' == -1
-            fs.appendFileSync "#{options.home}/.ssh/config", "#{EOL}StrictHostKeyChecking no#{EOL}UserKnownHostsFile /dev/null#{EOL}"
           resolve(pubKey)

   github: ->
@@ -171,7 +171,6 @@
                 result: true
                 message: data
                 type: 'github'
-              # # ssh -T git@github.com
               # exec 'ssh -T git@github.com', (code, output) ->
               #   console.log('Exit code:', code);
               #   console.log('Program output:', output);


double social worker memory limit (experimental)

--- workers/social/lib/social/main.coffee
+++ workers/social/lib/social/main.coffee
@@ -41,7 +41,7 @@
   stats_id: "worker.social." + process.pid
   interval : 30000
   limit_hard  :
-    memory   : 300
+    memory   : 600
     callback : (name,msg,details)->
       console.log "[#{JSON.stringify(new Date())}][SOCIAL WORKER #{name}] Using excessive memory, exiting."
       process.exit()


Update menu.coffee

Update menu.coffee

--- ui/tests/views/menu.coffee
+++ ui/tests/views/menu.coffee
@@ -2,10 +2,7 @@
 MenuView = require('../../scripts/views/menu')

-describe('MenuView', ->
-    it.skip('should render its dom', ->
-
-    )
-
+describe.skip('MenuView', ->
+    it('should render its dom', ->)
 )


Added some error messags and debug messages

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -100,7 +100,9 @@
       if (m = reUseSpecialPath.exec value)?
         useFlashLoc = m[1]
       else
-        error "Unknown flash player location: #{value}"
+        error "Invalid flash player location: '#{value}'."
+        error "Use one of [@auto, @builtin, @chrome, @system], \
+          or the path to your '#{filename}'"
     else
       useFlashLoc = 'cli'
       CLIFlashPath = value
@@ -117,24 +119,30 @@
       return flashPath
     when 'builtin'
       flashPath = getBuiltInFlashPath()
+      error 'Could not load built-in Pepper Flash Player'
     when 'chrome'
       flashPath = findChromeFlashPath()
+      error 'Could not load Chrome Pepper Flash Player'
     when 'system'
       flashPath = findSystemFlashPath()
+      error 'Could not load system Pepper Flash Player plug-in'
     when 'cli'
       if validatePath CLIFlashPath
         flashPath = CLIFlashPath
       else
-        error "Invalid path for flash player: \n#{CLIFlashPath}"
+        error "Invalid path to '#{filename}': \n#{CLIFlashPath}"
+  dbg.ex.flashLoader?.log 'Fall back to auto-detection'
   flashPath ?= getPath 'auto'
   flashPath

 load = ->
   flashPath = getPath()
-  dbg.ex.flashLoader?.assert validatePath(flashPath), 'No installed Pepper Flash Player found.'
-  dbg.ex.flashLoader?.log "Loading Pepper Flash Player from:"
-  dbg.ex.flashLoader?.log flashPath
-  app.commandLine.appendSwitch 'ppapi-flash-path', flashPath
+  if flashPath?
+    dbg.ex.flashLoader?.log "Loading Pepper Flash Player from:"
+    dbg.ex.flashLoader?.log flashPath
+    app.commandLine.appendSwitch 'ppapi-flash-path', flashPath
+  else
+    error 'No installed Pepper Flash Player found'

 exports.parseCLIArg = parseCLIArg


social kite: this default is now supported by the kite library

--- workers/social/lib/social/main.coffee
+++ workers/social/lib/social/main.coffee
@@ -63,7 +63,7 @@
     username    : 'koding'
     port        : argv['kite-port']
     prefix      : 'social'
-    kiteKey     : argv['kite-key'] or joinPath(process.env.HOME,".kite/kite.key")
+    kiteKey     : argv['kite-key']

     fetchClient: (name, context, callback) ->
       { JAccount } = koding.models


Utils: Updated coffeescript require url.

--- core/utils.coffee
+++ core/utils.coffee
@@ -774,7 +774,7 @@
     return "#{bytes.toFixed 2} #{units[unitIndex]}"

   compileCoffeeOnClient: (coffeeCode, callback = noop) ->
-    require ["https://raw.github.com/jashkenas/coffee-script/master/extras/coffee-script.js"], (coffeeCompiler) ->
+    require ["//cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js"], (coffeeCompiler) ->
       callback coffeeCompiler.eval coffeeCode

   openGithubPopUp:->


Improving Factory class documentation.

Improving Factory class documentation.

--- www/src/theoricus/core/factory.coffee
+++ www/src/theoricus/core/factory.coffee
@@ -8,7 +8,7 @@
 Controller = require 'theoricus/mvc/controller'

 ###*
-  Factory is responsible for loading/creating the MVC classes.
+  Factory is responsible for loading/creating the MVC classes, templates and stylesheets using AMD loader.

   @class Factory
 ###
@@ -16,7 +16,7 @@
 module.exports = class Factory

   ###*
-    Store the loaded controllers.
+    Stores the loaded controllers for subsequent calls.
     @property controllers {Array}
   ###
   controllers: {}
@@ -31,7 +31,9 @@
     Model.Factory = @

   ###*
-  Returns an instantiated Model.
+  Loads and returns an instantiated Model given the name.
+
+  If a model by given name was not found, returns an instance of `AppModel`.

   @method model
   @param name {String} Model name.
@@ -69,7 +71,9 @@
       fn null

   ###*
-  Returns an instantiated View.
+  Loads and returns an instantiated View given the name.
+
+  If a view by given name was not found, returns an instance of `AppView`.

   @method view
   @param path {String} Path to the view file.
@@ -105,7 +109,11 @@

   ###*
-  Returns an instantiated Controller.
+  Returns an instantiated Controller given the name.
+
+  If the controller was not loaded yeat, load it using AMD loader, otherwise, get it from `@controllers` object.
+
+  Throws an error if no controller is found.

   @method controller
   @param name {String} Controller name.
@@ -145,6 +153,9 @@
   @method template
   @param path {String} Path to the template.
   @param fn {Function} Callback function returning the template string.
+
+  @example
+
   ###
   @template=@::template=( path, fn )->
     # console.log "Factory.template( #{path} )"


Fixed a bug that kept sections from being changed to subsections on the design editor

--- app/assets/javascripts/designs.js.coffee
+++ app/assets/javascripts/designs.js.coffee
@@ -99,13 +99,18 @@
   $("#interactive-design-container [data-object~='push-partial-change']").removeAttr('disabled')

 @pushPartialChangeWithFile = (element) ->
+  if $("#section_section_type").is(":checked")
+    section_type = 1
+  else
+    section_type = 0
+
   formData = new FormData()

   formData.append("section[section_name]", $("#section_section_name").val())
   formData.append("section[section_description]", $("#section_section_description").val())
   formData.append("section[section_image]", $("#section_section_image").prop("files")[0])
   formData.append("section[section_branching_logic]", $("#section_section_branching_logic").val())
-  formData.append("section[section_type]", $("#section_section_type").is(":checked") ? 1 : 0)
+  formData.append("section[section_type]", section_type)

   formData.append("position", $(element).data('position')) unless $(element).data('position') == undefined
   formData.append("variable_id", $(element).data('variable-id')) unless $(element).data('variable-id') == undefined


removed boot method

removed boot method

--- modules/stores/lib/Context.coffee
+++ modules/stores/lib/Context.coffee
@@ -35,17 +35,7 @@
                     type: type
                     pk: @data.pk||'id'

-                done()
-
-
-        ##
-        ##
-        ##
-        boot: (fn)->
-            super (err)=>
-                return fn(err) if err
-
-                @_engine.init @data, fn
+                @_engine.init @data, done


cleanup

cleanup

--- app/assets/javascripts/file_table.coffee
+++ app/assets/javascripts/file_table.coffee
@@ -80,7 +80,6 @@
       else
         $('.remove-selected-files').attr('disabled', false)

-
   delete_selected_files_ajax_request: ( url, filenames ) ->
     $.ajax
       type: 'delete'


Use `new Date().getTime()` instead of `Date.now()` (it is redefined by some libraries -_-)

--- app/assets/javascripts/hub_client.coffee
+++ app/assets/javascripts/hub_client.coffee
@@ -34,7 +34,7 @@

 class PickerResult
   constructor: (@api) ->
-    @id = Date.now()
+    @id = new Date().getTime()

   show: (type) ->
     @container = $('<div>')


fixed mousemove/mouseup attachment, also fixed trackballcontrols, fixes #278

--- app/assets/javascripts/libs/input.coffee
+++ app/assets/javascripts/libs/input.coffee
@@ -185,7 +185,7 @@
     @isMouseOver = false
     @lastPosition = null

-    $(window).on
+    $(document).on
       "mousemove" : @mouseMove
       "mouseup"   : @mouseUp

@@ -201,7 +201,7 @@

   unbind : ->

-    $(window).off
+    $(document).off
       "mousemove" : @mouseMove
       "mouseup" : @mouseUp


Bump license overrides for new jschardet version

--- build/tasks/license-overrides.coffee
+++ build/tasks/license-overrides.coffee
@@ -65,7 +65,7 @@
         IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       """

-  'jschardet@1.1.0':
+  'jschardet@1.1.1':
     license: 'LGPL'
     source: 'README.md in the repository'
     sourceText: """


Fix the checkIfFileExists API.

Fix the checkIfFileExists API.

--- app/coffee/AWSSDKPersistorManager.coffee
+++ app/coffee/AWSSDKPersistorManager.coffee
@@ -80,6 +80,7 @@
 		logger.log bucketName:bucketName, key:key, "check file existence in s3"
 		s3.headObject {Bucket: bucketName, Key: key}, (err, data) ->
 			if err?
+				return (callback null, false) if err.code == 'NotFound'
 				logger.err err:err, bucketName:bucketName, key:key, "something went wrong checking head in s3"
 				return callback err
 			callback null, data.ETag?


example correction

example correction

--- examples/config.cson
+++ examples/config.cson
@@ -32,7 +32,7 @@
             hideInputText: true
             utils: [
               "clear" # Enter 'clear' in console, your console would be cleared
-              "start" # Enter 'start <cmd>'' in console, a new console would be opened
+              "start" # Enter 'start <cmd>' in console, a new console would be opened
               "stdin" # Enter 'stdin <data>', '<data>' would be written to child_process.stdin,
                   # e.g. 'stdin start <cmd>' would run '<cmd>' in a new windows instead of inside Atom
               "beep" # Just like atom.beep()


:art: Tweak renderPanelMessages a bit

:art: Tweak renderPanelMessages a bit

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -8,7 +8,7 @@
     @showBubble = true
     @underlineIssues = true

-    @messages = new Set # Snapshot of linter's messages when it was last updated
+    @messages = new Set # Classified snapshot of linter's messages when it was last updated
     @lineMessages = new Set
     @markers = []
     @statusTiles = []
@@ -190,19 +190,18 @@
         )

   renderPanelMessages: ->
-    return @setPanelVisibility(false) if (@tabs['Line'].active and not @lineMessages.size) or not @messages.size
+    messages =
+      if @tabs['Line'].active
+        @lineMessages
+      else
+        @messages
+    return @setPanelVisibility(false) unless messages.size
     @setPanelVisibility(true)
     @panel.innerHTML = ''
-    if @tabs['Line'].active
-      @lineMessages.forEach (message) =>
-        return if @state.scope isnt 'Project' and not message.currentFile
-        Element = Message.fromMessage(message, addPath: @state.scope is 'Project', cloneNode: true)
-        @panel.appendChild Element
-    else
-      @messages.forEach (message) =>
-        return if @state.scope isnt 'Project' and not message.currentFile
-        Element = Message.fromMessage(message, addPath: @state.scope is 'Project', cloneNode: true)
-        @panel.appendChild Element
+    messages.forEach (message) =>
+      return if @state.scope isnt 'Project' and not message.currentFile
+      Element = Message.fromMessage(message, addPath: @state.scope is 'Project', cloneNode: true)
+      @panel.appendChild Element

   removeMarkers: ->
     return unless @markers.length


:bug: Fix broken dragging on the minimap when minimap could scroll

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -202,7 +202,7 @@
       overlayerOffset = @scrollView.find('.overlayer').offset().top
       overlayY = -overlayerOffset + scrollViewOffset

-    @indicator.setY(overlayY)
+    @indicator.setY(overlayY * @scaleY)
     @updatePositions()

   updateScrollX: =>
@@ -215,9 +215,9 @@
     @trigger 'minimap:scroll'

   updatePositions: ->
-    @transform @miniVisibleArea[0], @translate(@indicator.x * @scaleX, @indicator.y * @scaleY)
-    @transform @miniWrapper[0], @translate(@indicator.scroller.x * @scaleX, @indicator.scroller.y * @scaleY)
-    @miniEditorView.scrollTop @indicator.scroller.y * -1 * @scaleY
+    @transform @miniVisibleArea[0], @translate(@indicator.x, @indicator.y)
+    @transform @miniWrapper[0], @translate(@indicator.scroller.x, @indicator.scroller.y)
+    @miniEditorView.scrollTop @indicator.scroller.y * -1

     @updateScrollerPosition()

@@ -227,7 +227,7 @@

     scrollRange = totalHeight - height

-    @transform @miniScroller[0], @translate(0, @indicator.ratioY * scrollRange *  @scaleY)
+    @transform @miniScroller[0], @translate(0, @indicator.ratioY * scrollRange)

   # EVENT CALLBACKS


Fixed bugs in oauth flow of middleware

Fixed bugs in oauth flow of middleware

--- lib/personal-app.coffee
+++ lib/personal-app.coffee
@@ -451,17 +451,20 @@
                 redirect_uri: sess.redirect_uri
             promise.then (access_obj) ->
                 sess[key] = val for key,val of access_obj
-                sess.client = new PersonalClient access_obj
-                sess.state = sess.redirect_uri = null
+                req.personal.client = new PersonalClient access_obj
                 next()
             ,(err) ->
                 next(err)
+            promise.fin () ->
+                sess.state = sess.redirect_uri = null
+                #TODO: remove code, state, and personal from query (redirect)
             return

     #we need to create the url for login and auth
     if (not sess.state?) or (not sess.redirect_uri?)
         new_redir_uri = url.parse "#{req.protocol}://#{req.headers.host}#{req.url}", true
         new_redir_uri.search = ""
+        delete new_redir_uri.query.code
         new_redir_uri.query.personal = true
         auth_req_obj = app.get_auth_request_url
             scope: connect_opts.get "scope"


Remove useless

Remove useless

--- lib/shuss-server.coffee
+++ lib/shuss-server.coffee
@@ -1,7 +1,6 @@
 express = require 'express'
 livereload = require 'livereload'
 path = require 'path'
-# livereload = require 'livereload'

 class ShussServer


Set a -1 tabindex on on the tab bar

Set a -1 tabindex on on the tab bar

This prevents it from gaining focus when a close icon is clicked.

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -6,7 +6,7 @@
 module.exports =
 class TabBarView extends View
   @content: ->
-    @ul class: "tabs sortable-list"
+    @ul tabindex: -1, class: "tabs sortable-list"

   initialize: (@pane) ->
     @on 'dragstart', '.sortable', @onDragStart


:lipstick: Use dataTransfer variable

:lipstick: Use dataTransfer variable

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -202,13 +202,14 @@
   onDrop: (event) =>
     event.preventDefault()
     event.stopPropagation()
-
-    return unless event.originalEvent.dataTransfer.getData('atom-event') is 'true'
-
-    fromProcessId = parseInt(event.originalEvent.dataTransfer.getData('from-process-id'))
-    fromRoutingId = parseInt(event.originalEvent.dataTransfer.getData('from-routing-id'))
-    fromIndex = parseInt(event.originalEvent.dataTransfer.getData('sortable-index'))
-    fromPaneIndex = parseInt(event.originalEvent.dataTransfer.getData('from-pane-index'))
+    {dataTransfer} = event.originalEvent
+
+    return unless dataTransfer.getData('atom-event') is 'true'
+
+    fromProcessId = parseInt(dataTransfer.getData('from-process-id'))
+    fromRoutingId = parseInt(dataTransfer.getData('from-routing-id'))
+    fromIndex = parseInt(dataTransfer.getData('sortable-index'))
+    fromPaneIndex = parseInt(dataTransfer.getData('from-pane-index'))

     @clearDropTarget()
     if fromProcessId is @getProcessId()
@@ -226,7 +227,7 @@
         toPane.showItem(item)
         toPane.focus()
     else
-      if droppedPath = event.originalEvent.dataTransfer.getData('text/plain')
+      if droppedPath = dataTransfer.getData('text/plain')
         atom.workspace.open(droppedPath)
         atom.focus()
         BrowserIpc.sendChannel(fromProcessId, fromRoutingId, 'tab:dropped', fromIndex, fromPaneIndex)


Client/Activity: add announcement route

Client/Activity: add announcement route

--- client/Social/Activity/routes.coffee
+++ client/Social/Activity/routes.coffee
@@ -11,12 +11,24 @@

     '/:name?/Activity/Public' : ({params: {name}}) -> handleChannel 'topic', 'public'

+    # TODO ~ i tried to unify this route and following 4 routes, couldnt manage
+    # to make it work, did not spend so much time on it and gave up, it would be
+    # better to have only one route for those 5
+    '/:name?/Activity/Announcement/:slug' : ({params: {name, slug}}) ->
+      handleChannel 'announcement', slug
+
     '/:name?/Activity/Topic/:slug?' : ({params:{name, slug}, query}) ->
       if slug is 'public'
       then KD.singletons.router.handleRoute '/Activity/Public'
       else handleChannel 'topic', slug

     '/:name?/Activity/Post/:slug?' : ({params:{name, slug}, query}) ->
+      handleChannel 'post', slug
+
+    '/:name?/Activity/Message/:slug?' : ({params:{name, slug}, query}) ->
+      handleChannel 'message', slug
+
+    '/:name?/Activity/:slug' : ({params:{name, slug}, query}) ->
       handleChannel 'post', slug

     '/:name?/Activity/Message/New' : ->
@@ -28,12 +40,6 @@
     '/:name?/Activity/Post/All' : ({params:{name, slug}, query}) ->
       handleChannel null, null, (app) -> app.getView().showAllConversationsModal()

-    '/:name?/Activity/Message/:slug?' : ({params:{name, slug}, query}) ->
-      handleChannel 'message', slug
-
-    '/:name?/Activity/:slug' : ({params:{name, slug}, query}) ->
-      handleChannel 'post', slug
-
     '/:name?/Activity' : ({params:{name, slug}, query}) ->
       # handle legacy topic routes
       if query.tagged?


Add spinner when sending and saving Refs #57

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -167,13 +167,19 @@
                                 type: 'button',
                                 disable: if @state.sending then true else null
                                 onClick: @onSend,
-                                    span
-                                        className: 'fa fa-send'
+                                    if @state.sending
+                                        span className: 'fa fa-refresh fa-spin'
+                                    else
+                                        span className: 'fa fa-send'
                                     span null, labelSend
                             button
                                 className: 'btn btn-cozy',
+                                disable: if @state.saving then true else null
                                 type: 'button', onClick: @onDraft,
-                                    span className: 'fa fa-save'
+                                    if @state.saving
+                                        span className: 'fa fa-refresh fa-spin'
+                                    else
+                                        span className: 'fa fa-save'
                                     span null, t 'compose action draft'
                             if @props.message?
                                 button
@@ -237,6 +243,7 @@
             state.accountID ?= @props.selectedAccount.get 'id'

         state.sending = false
+        state.saving  = false
         return state

     componentWillReceiveProps: (nextProps) ->
@@ -297,11 +304,15 @@
             if not isDraft and @_saveInterval
                 window.clearInterval @_saveInterval

-            if not isDraft
+            if isDraft
+                @setState saving: true
+            else
                 @setState sending: true

             MessageActionCreator.send message, (error, message) =>
-                if not isDraft
+                if isDraft
+                    @setState saving: false
+                else
                     @setState sending: false
                 if isDraft
                     msgKo = t "message action draft ko"


client: export redux store as `singletons.store`

--- client/app/lib/maincontroller.coffee
+++ client/app/lib/maincontroller.coffee
@@ -46,7 +46,7 @@
 DesktopNotificationsController = require './desktopnotificationscontroller'
 bowser                         = require 'bowser'
 fetchChatlioKey                = require 'app/util/fetchChatlioKey'
-
+createStore = require './redux/createStore'

 module.exports = class MainController extends KDController

@@ -112,6 +112,7 @@
     kd.registerSingleton 'onboarding',                new OnboardingController
     kd.registerSingleton 'machineShareManager',       new MachineShareManager
     kd.registerSingleton 'reactor',                   new KodingFluxReactor
+    kd.registerSingleton 'store',                     createStore()

     @registerFluxModules()


teams: removed unused varibales

teams: removed unused varibales

--- client/teams/lib/teamsappview.coffee
+++ client/teams/lib/teamsappview.coffee
@@ -1,6 +1,5 @@
 kd              = require 'kd'
 JView           = require 'app/jview'
-FooterView      = require 'app/commonviews/footerview'
 remote          = require('app/remote').getInstance()

 module.exports = class TeamsAppView extends kd.CustomHTMLView


ssh 公钥安全级别自动设置

ssh 公钥安全级别自动设置

--- lib/utils/gitApi.coffee
+++ lib/utils/gitApi.coffee
@@ -2,6 +2,7 @@
 request = require 'request'
 GitHubApi = require 'github'
 {exec} = require 'child_process'
+{EOL} = require 'os'
 fse = require 'fs-extra'
 fs = require 'fs'
 github = null
@@ -129,7 +130,7 @@
           localStorage.installedSshKey = JSON.stringify msg
           # 关闭确认公钥设置
           if !fs.existsSync "#{options.home}/.ssh/config" and fs.readFileSync("#{options.home}/.ssh/config", options: 'utf8').indexOf 'StrictHostKeyChecking no' == -1
-            fs.appendFileSync "#{options.home}/.ssh/config", '\nStrictHostKeyChecking no\nUserKnownHostsFile /dev/null\n'
+            fs.appendFileSync "#{options.home}/.ssh/config", "#{EOL}StrictHostKeyChecking no#{EOL}UserKnownHostsFile /dev/null#{EOL}"
           resolve(pubKey)

   github: ->