Pass appParams to all create app calls

Pass appParams to all create app calls

--- client/landing/app/MainApp/ApplicationManager.coffee
+++ client/landing/app/MainApp/ApplicationManager.coffee
@@ -101,9 +101,12 @@
         kodingAppsController.runApp (KD.getAppOptions name), callback
         return

+      appParams = options.params or {}
+
       if appOptions?.multiple
         if options.forceNew or appOptions.openWith is "forceNew"
-          @create name, (appInstance)=> @showInstance appInstance, callback
+          @create name, appParams, (appInstance)=>
+            @showInstance appInstance, callback
           return

         switch appOptions.openWith
@@ -119,7 +122,7 @@
                   @show appInstance, callback
                 else if openNew
                   # user wants to open a fresh instance
-                  @create name, callback
+                  @create name, appParams, callback
                 else
                   warn "user cancelled app to open"
             else do defaultCallback


Comment out no click delay as doesn't work in nexus.

--- js/flight.coffee
+++ js/flight.coffee
@@ -54,15 +54,15 @@
     else if targetPage
       @targetPage = targetPage

-    if @noClickDelay is true
-      new NoClickDelay(@targetPage.querySelector('header'));
+    # if @noClickDelay is true
+    #   new NoClickDelay(@targetPage.querySelector('header'));

     if not @currentPage
       # No current panel set, app just started, make start panel visible
       @targetPage.style.display = "-webkit-box"
       @pageHistory = [window.location.hash]
       @currentPage = @targetPage
-      return
+      return

     # if already transitioning then return
     if @isTransitioning is true then return else @isTransitioning = true
@@ -79,7 +79,7 @@
     else
       transitionType = @targetPage.getAttribute("data-transition")
       @pageHistory.push(window.location.hash)
-
+
     # Delay transition to prevent flickering
     window.setTimeout =>
       if @transitionAnimation is true


Add scroller check to options.

Add scroller check to options.

--- js/flight.coffee
+++ js/flight.coffee
@@ -19,6 +19,8 @@
         @transitionAnimation = options.transitionAnimation
       if options.speed?
         @speed = options.speed
+      if options.useScroller?
+        @useScroller = options.useScroller

     @currentPanel = document.getElementsByClassName('visible')[0]
     if @currentPanel is undefined then throw new Error "Current panel not set"


Remove touch start and end tests.

Remove touch start and end tests.

--- js/flight.coffee
+++ js/flight.coffee
@@ -179,15 +179,6 @@

-#$('li').on 'touchstart', ->
-#  this.className = "touchstart"
-#  flight.goToPage('#panel-2')
-#
-#$('li').on 'touchend', ->
-#  window.setTimeout =>
-#    this.className = ""
-#  , 10
-
 #if 'ontouchstart' in window
 #  alert 'ontouchstart'
 #


fix bug: instance variables should be defined in constructor

--- lib/remote-atom.coffee
+++ lib/remote-atom.coffee
@@ -8,10 +8,22 @@
 status-message = require './status-message'

 class FileHandler
-    settings: {}
-
     constructor: (session) ->
         @session = session
+        @settings = {}
+        @readbytes = 0
+        @closed = false
+
+    set: (index, value) ->
+        @settings[index] = value
+        if index == "display-name"
+            @remote_address = value.split(":")[0]
+            @basename = path.basename(value.split(":")[1])
+        else if index == "data"
+            @datasize = parseInt(value,10)
+
+    get: (index) ->
+        return @settings[index]

     create: ->
         @tempfile = path.join(os.tmpdir(), randomstring(10), @basename)
@@ -20,11 +32,19 @@
         mkdirp.sync(dirname)
         @fd = fs.openSync(@tempfile, 'w')

-    write: (str) ->
-        fs.writeSync(@fd, str)
+    append: (line) ->
+        if @readbytes < @datasize
+            @readbytes += Buffer.byteLength(line)
+            # remove trailing newline if necessary
+            if @readbytes == @datasize + 1 and line.slice(-1) is "\n"
+                @readbytes = @datasize
+                line = line.slice(0, -1)
+            fs.writeSync(@fd, line)
+        if @readbytes >= @datasize
+            fs.closeSync @fd
+            @closed = true

     open: ->
-        fs.closeSync @fd
         console.log "[ratom] opening #{@tempfile}"
         # register events
         atom.workspace.open(@tempfile, activatePane:true).then (editor) =>
@@ -41,29 +61,26 @@

     save: ->
         if not @session.alive
-            console.log "[ratom] Error saving #{path.basename @tempfile} to #{@remoteAddress}"
-            status-message.display "Error saving #{path.basename @tempfile} to #{@remoteAddress}", 2000
+            console.log "[ratom] Error saving #{path.basename @tempfile} to #{@remote_address}"
+            status-message.display "Error saving #{path.basename @tempfile} to #{@remote_address}", 2000
             return
-        console.log "[ratom] saving #{path.basename @tempfile} to #{@remoteAddress}"
-        status-message.display "Saving #{path.basename @tempfile} to #{@remoteAddress}", 2000
+        console.log "[ratom] saving #{path.basename @tempfile} to #{@remote_address}"
+        status-message.display "Saving #{path.basename @tempfile} to #{@remote_address}", 2000
         @session.send "save"
-        @session.send "token: #{@token}"
+        @session.send "token: #{@settings['token']}"
         data = fs.readFileSync(@tempfile)
         @session.send "data: " + Buffer.byteLength(data)
         @session.send data

     close: ->
         console.log "[ratom] closing #{path.basename @tempfile}"
-        # try to close session
-        @session.close()
         @subscriptions.dispose()
+        @session.try_close()

 class Session
-    should_parse_data: false
-    readbytes: 0
-    nconn: 0
-
     constructor: (socket) ->
+        @should_parse_data = false
+        @nconn = 0
         @socket = socket
         @send "Atom "+ atom.getVersion()
         @alive = true
@@ -88,45 +105,29 @@

     parse_line: (line) ->
         if @should_parse_data
-            @parse_data(line)
+            @file.append(line)
+            if @file.closed
+                @should_parse_data = false
+                @file.open()
+
         else if line.match /open\n/
             @file = new FileHandler(@)
-            @readbytes = 0
             @nconn += 1
         else
             @parse_setting(line)

-    parse_data: (line) ->
-        if @readbytes < @file.datasize
-            @readbytes += Buffer.byteLength(line)
-            # remove trailing newline if necessary
-            if @readbytes == @file.datasize + 1 and line.slice(-1) is "\n"
-                line = line.slice(0, -1)
-            @file.write(line)
-        if @readbytes >= @file.datasize
-            @should_parse_data = false
-            @file.open()
-
     parse_setting: (line) ->
         m = line.match /([a-z\-]+?)\s*:\s*(.*?)\s*$/
         if m and m[2]?
-            @file.settings[m[1]] = m[2]
-            switch m[1]
-                when "token"
-                    @file.token = m[2]
-                when "display-name"
-                    @file.displayname = m[2]
-                    @file.remoteAddress = m[2].split(":")[0]
-                    @file.basename = path.basename(m[2].split(":")[1])
-                when "data"
-                    @file.datasize = parseInt(m[2],10)
-                    @file.create()
-                    @should_parse_data = true
+            @file.set(m[1], m[2])
+            if m[1] == "data"
+                @file.create()
+                @should_parse_data = true

     send: (cmd) ->
         @socket.write cmd+"\n"

-    close: ->
+    try_close: ->
         @nconn -= 1
         if @alive and @nconn == 0
             @alive = false
@@ -169,7 +170,7 @@
                 status-message.display "Starting remote atom server", 2000

         @server = net.createServer (socket) ->
-            console.log "[ratom] received connection from #{socket.remoteAddress}"
+            console.log "[ratom] received connection from #{socket.remote_address}"
             session = new Session(socket)

         port = atom.config.get "remote-atom.port"


move when template publish status is refreshed to avoid loop

--- services/web/public/coffee/project-members/ProjectMembersManager.coffee
+++ services/web/public/coffee/project-members/ProjectMembersManager.coffee
@@ -180,6 +180,8 @@
 			@model = @ide.project
 			_.bindAll(this, "render");
 			this.model.bind('change', this.render)
+			@refreshPublishStatus()
+

 		render: ->
 			viewModel =
@@ -191,7 +193,6 @@
 			$(@el).html $(Mustache.to_html(@template, viewModel))
 			@publishedArea = $('#publishedAsTemplateArea')
 			@unpublishedArea = $('#unpublishedAsTemplateArea')
-			@refreshPublishStatus()

 		refreshPublishStatus: ->


add setDelta

add setDelta

--- src/editor.coffee
+++ src/editor.coffee
@@ -164,6 +164,13 @@
         )
       )
     )
+
+  setDelta: (delta) ->
+    this.reset()
+    oldLength = delta.startLength
+    delta.startLength = @doc.length
+    this.applyDelta(delta)
+    delta.startLength = oldLength

   setSelection: (range) ->
     @selection.setRange(range)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -271,7 +271,7 @@

     self.include Bindable

-    defaultModules = ["Keyboard", ""<"Clear", "Delay", "GameState", "Selector", "Collision"]
+    defaultModules = ["Keyboard", "", "Clear", "Delay", "GameState", "Selector", "Collision"]
     modules = defaultModules.concat(I.includedModules)
     modules = modules.without([].concat(I.excludedModules))


Just use destroy

Just use destroy

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -43,8 +43,7 @@
   close: ->
     # Dismiss window and stop any running process
     if @hasParent()
-      @detach()
-      @stop()
+      @destroy()

   start: ->
     # Display error or run script


Make loaded an instance variable

Make loaded an instance variable

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -11,9 +11,8 @@
 {getPackageRoot} = require './helpers'

 module.exports =
-  loaded: false
-
   activate: ->
+    @loaded = false
     @userSnippetsPath = null
     @snippetIdCounter = 0
     @parsedSnippetsById = new Map


Added AuthHandler.key for convenience

Added AuthHandler.key for convenience

--- src/wrathnet/expansions/wotlk/handlers/auth-handler.coffee
+++ src/wrathnet/expansions/wotlk/handlers/auth-handler.coffee
@@ -55,10 +55,14 @@
         when AuthOpcode.LOGON_PROOF then @logonProof ap
     , @

+  # Retrieves the session key (if any)
+  @getter 'key', ->
+    return @srp?.K
+
   # Connects to given host through given port
   connect: (host, port=NaN) ->
     unless @connected
-      super(host, port || @constructor.PORT)
+      super(host, port or @constructor.PORT)
       console.info 'connecting to auth-server @', @host, ':', @port
     return @

@@ -101,7 +105,7 @@
   # Data received handler
   dataReceived: (socket) ->
     while true
-      if not @connected || @buffer.available < AuthPacket.HEADER_SIZE
+      if not @connected or @buffer.available < AuthPacket.HEADER_SIZE
         return

       ap = new AuthPacket(@buffer.readByte(), @buffer, false)


:lipstick

:lipstick

--- spec/atom/editor-spec.coffee
+++ spec/atom/editor-spec.coffee
@@ -17,6 +17,14 @@
     editor.enableKeymap()
     editor.setBuffer(buffer)
     editor.isFocused = true
+
+  describe "construction", ->
+    it "assigns an empty buffer and correctly handles text input (regression coverage)", ->
+      editor = new Editor
+      expect(editor.buffer.path).toBeUndefined()
+      expect(editor.lines.find('.line').length).toBe 1
+      editor.insertText('x')
+      expect(editor.lines.find('.line').length).toBe 1

   describe "text rendering", ->
     it "creates a line element for each line in the buffer with the html-escaped text of the line", ->
@@ -936,14 +944,6 @@
       expect(editor.isFocused).toBeFalsy()
       expect(editor).not.toHaveClass('focused')

-  describe "construction", ->
-    it "assigns an empty buffer and correctly handles text input (regression coverage)", ->
-      editor = new Editor
-      expect(editor.buffer.path).toBeUndefined()
-      expect(editor.lines.find('.line').length).toBe 1
-      editor.insertText('x')
-      expect(editor.lines.find('.line').length).toBe 1
-
   describe ".setBuffer(buffer)", ->
     it "sets the cursor to the beginning of the file", ->
       expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)


Add tweetnacl@0.14.3 to license-overrides

--- build/tasks/license-overrides.coffee
+++ build/tasks/license-overrides.coffee
@@ -341,3 +341,18 @@

     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     """
+  'tweetnacl@0.14.3':
+    license: 'Public Domain'
+    repository: 'https://github.com/dchest/tweetnacl'
+    source: 'COPYING.txt'
+    sourceText: """
+    Public Domain
+
+    The person who associated a work with this deed has dedicated the work to the
+    public domain by waiving all of his or her rights to the work worldwide under
+    copyright law, including all related and neighboring rights, to the extent
+    allowed by law.
+
+    You can copy, modify, distribute and perform the work, even for commercial
+    purposes, all without asking permission.
+    """


Refactoring to iteratively find the largest number in the table

--- tech-tests/primes-table/src/table-to-text-converter.coffee
+++ tech-tests/primes-table/src/table-to-text-converter.coffee
@@ -22,15 +22,17 @@
         row = row + " #{padding}#{number} |"
     row

-module.exports.convertToText = (listOfLists) ->
+module.exports.convertToText = (listOfLists, padding = 1) ->
     maxNumberWidth = findLargestNumberWidth(listOfLists)
-    padding = 1
+    widthOfNumbersInRow = listOfLists.length * maxNumberWidth
+    widthOfPaddingInRow = listOfLists.length * padding * 2
+    widthOfVerticalSeparatorsInRow = listOfLists.length + 1
+
+    tableWidth = widthOfNumbersInRow +
+        widthOfPaddingInRow +
+        widthOfVerticalSeparatorsInRow
+
     textTable = ""
-    tableWidth =
-        (listOfLists.length * maxNumberWidth) +
-        (listOfLists.length * padding * 2) +
-        listOfLists.length + 1
-
     for list in listOfLists
         textTable += horizontalLine("-", tableWidth) + "\n"
         textTable += createRow(list, maxNumberWidth) + "\n"


temp lazy view fix

temp lazy view fix

--- lib/ui/views.coffee
+++ lib/ui/views.coffee
@@ -30,7 +30,10 @@

   lazy: ({head, id}, opts) ->
     conn = client.conn
-    opts.registerLazy id
+    if opts.registerLazy?
+      opts.registerLazy id
+    else
+      console.warn 'Unregistered lazy view'
     view = @ink.tree.treeView @render(head, opts), [],
       onToggle: once =>
         return unless client.conn == conn


:bug: Show project scope erorrs in panel

--- lib/views/panel.coffee
+++ lib/views/panel.coffee
@@ -14,18 +14,18 @@
     @removeDecorations()
     @innerHTML = ''
     messages.forEach (message)=>
-      return unless message.currentFile # A custom property added while creating arrays of them
-      return unless message.position
-      return if @scope is 'file' and (not message.currentFile)
-      p = message.position
-      range = [[p[0][0] - 1, p[0][1] - 1], [p[1][0] - 1, p[1][1]]]
-      marker = @linter.activeEditor.markBufferRange range, {invalidate: 'never'}
-      @decorations.push @linter.activeEditor.decorateMarker(
-        marker, type: 'line-number', class: 'line-number-' + message.type.toLowerCase()
-      )
-      @decorations.push @linter.activeEditor.decorateMarker(
-        marker, type: 'highlight', class: 'highlight-' + message.type.toLowerCase()
-      )
+      if @linter.views.scope is 'file'
+        return unless message.currentFile
+      if message.currentFile and message.position
+        p = message.position
+        range = [[p[0][0] - 1, p[0][1] - 1], [p[1][0] - 1, p[1][1]]]
+        marker = @linter.activeEditor.markBufferRange range, {invalidate: 'never'}
+        @decorations.push @linter.activeEditor.decorateMarker(
+          marker, type: 'line-number', class: 'line-number-' + message.type.toLowerCase()
+        )
+        @decorations.push @linter.activeEditor.decorateMarker(
+          marker, type: 'highlight', class: 'highlight-' + message.type.toLowerCase()
+        )
       @appendChild @linter.view.messageLine message, @linter.views.scope is 'project'

 module.exports = PanelView = document.registerElement('linter-panel-view', {prototype: PanelView.prototype})


prevent loading api key from config file when starting up

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -159,11 +159,9 @@
       console.log 'Error: could not read wakatime config file'
       return
     commonConfigs = ini.decode configContent
-    if commonConfigs? and commonConfigs.settings?
-      atomConfigs = atom.config.get('wakatime')
-      atomConfigs.apikey = commonConfigs.settings.api_key
-      atomConfigs.debug = not not commonConfigs.settings.debug
-      atom.config.set 'wakatime', atomConfigs
+    if commonConfigs? and commonConfigs.settings? and isValidApiKey(commonConfigs.settings.api_key)
+      atom.config.set 'wakatime.apikey', '' # clear setting so it updates in UI
+      atom.config.set 'wakatime.apikey', 'Saved in your ~/.wakatime.cfg file'

 isValidApiKey = (key) ->
   if not key?


console.log version when app starts

console.log version when app starts

--- src/js/app.coffee
+++ src/js/app.coffee
@@ -528,6 +528,9 @@
   callback() if callback?

+console.log "Version: #{VERSION}"
+
+
 initSettings = ->
   Settings.init()
   Settings.config {


Strip !important before checking selector validity

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -24,7 +24,7 @@
   addKeyBindingsForSelector: (source, selector, keyBindings) ->
     # Verify selector is valid before registering any bindings
     try
-      document.body.webkitMatchesSelector(selector)
+      document.body.webkitMatchesSelector(selector.replace(/!important/g, ''))
     catch
       console.warn("Encountered an invalid selector adding keybindings from '#{source}': '#{selector}'")
       return


all good so far!

all good so far!

--- test/files/keymanager.iced
+++ test/files/keymanager.iced
@@ -13,16 +13,20 @@
 tsenc = null
 openpgp_pass = null
 pgp_private = null
+b2 = null

 compare_keys = (T, k1, k2, what) ->
   T.equal k1.ekid().toString('hex'), k2.ekid().toString('hex'), "#{what} keys match"
+
+sanity_check = (T, bundle) ->
+  T.no_error bundle.primary.key.sanity_check()
+  T.no_error bundle.subkeys[0].key.sanity_check()

 exports.step1_generated = (T,cb) ->
   await KeyManager.generate { asp, nbits : 1024, nsubs : 1, userid }, defer err, tmp
   bundle = tmp
   T.no_error err
-  T.no_error bundle.primary.key.sanity_check()
-  T.no_error bundle.subkeys[0].key.sanity_check()
+  sanity_check T, bundle
   cb()

 exports.step2_salt_triplesec = (T, cb) ->
@@ -46,13 +50,24 @@
   cb()

 exports.step4_import_pgp_public = (T,cb) ->
-  await KeyManager.import_from_armored_pgp { raw : pgp_private, asp, userid}, defer err, b2
+  await KeyManager.import_from_armored_pgp { raw : pgp_private, asp, userid}, defer err, tmp
+  b2 = tmp
   T.no_error err
   unless err?
     compare_keys T, bundle.primary, b2.primary, "primary keys"
     compare_keys T, bundle.subkeys[0], b2.subkeys[0], "subkeys[0]"
   cb()

+exports.step5_merge_pgp_private = (T,cb) ->
+  await b2.merge_pgp_private { raw : pgp_private, asp }, defer err
+  T.no_error err
+  bad_pass = "a" + openpgp_pass
+  await b2.open_pgp { passphrase : bad_pass }, defer err
+  T.assert err?, "we should have gotten an error when opening with a bad password"
+  await b2.open_pgp { passphrase : openpgp_pass }, defer err
+  T.no_error err
+  sanity_check T, b2
+  cb()

 progress_hook = (obj) ->


add insertAt tests

add insertAt tests

--- tests/scripts/unit/editor.coffee
+++ tests/scripts/unit/editor.coffee
@@ -86,6 +86,55 @@
   )

+  describe('insertAt', ->
+    insertTests = new Scribe.Test.EditorTest(
+      initial: ['
Test
']
+      fn: (testEditor, expectedEditor, delta) ->
+        testEditor.applyDelta(delta)
+    )
+
+    insertTests.run('insert in middle of text',
+      expected: ['
TeAst
']
+    , Tandem.Delta.makeInsertDelta(5, 2, 'A'))
+
+    insertTests.run('insert formatted text',
+      expected: ['
TeAst
']
+    , Tandem.Delta.makeInsertDelta(5, 2, 'A', {bold:true}))
+
+    insertTests.run('insert newline in front of text',
+      expected: ['

', '
Test
']
+    , Tandem.Delta.makeInsertDelta(5, 0, '\n'))
+
+    insertTests.run('append newline',
+      expected: ['
Test
', '

']
+    , Tandem.Delta.makeInsertDelta(5, 5, '\n'))
+
+    insertTests.run('insert newline in middle of text',
+      expected: ['
Te
', '
st
']
+    , Tandem.Delta.makeInsertDelta(5, 2, '\n'))
+
+    insertTests.run('simple multiline insert',
+      expected: ['
TeA
', '
Bst
']
+    , Tandem.Delta.makeInsertDelta(5, 2, 'A\nB'))
+
+    insertTests.run('multiline insert with newline prefix',
+      expected: ['
Te
', '
A
', '
Bst
']
+    , Tandem.Delta.makeInsertDelta(5, 2, '\nA\nB'))
+
+    insertTests.run('multiline insert with newline after',
+      expected: ['
TeA
', '
B
', '
st
']
+    , Tandem.Delta.makeInsertDelta(5, 2, 'A\nB\n'))
+
+    insertTests.run('multiline insert with newline surrounding',
+      expected: ['
Te
', '
A
', '
B
', '
st
']
+    , Tandem.Delta.makeInsertDelta(5, 2, '\nA\nB\n'))
+
+    insertTests.run('multiline insert with formatted text',
+      expected: ['
Te
', '
A
', '
B
', '
st
']
+    , new Tandem.Delta(5, [new Tandem.RetainOp(0, 2), new Tandem.InsertOp('\n'), new Tandem.InsertOp('A', {bold:true}), new Tandem.InsertOp('\nB\n'), new Tandem.RetainOp(2, 5)]))
+  )
+
+
   describe('setDelta', ->
     deltaTest = new Scribe.Test.EditorTest(
       expected: '
Test
'


Minor cleanup.

Minor cleanup.

--- src/MappedCollection2.coffee
+++ src/MappedCollection2.coffee
@@ -103,12 +103,6 @@
   missingRule: (name) ->
     throw new Error "undefined rule “#{name}”"

-  # This option makes `set()` create missing rules when necessary
-  # instead of failing.
-  #
-  # TODO: should be replaced by a callback for flexibility.
-  createMissingRules: false
-
   constructor: ->
     # Items are stored here indexed by key.
     #


UI Copy: fixed "2 new comment"

UI Copy: fixed "2 new comment"

--- client/Social/Activity/views/comments/commentviewheader.coffee
+++ client/Social/Activity/views/comments/commentviewheader.coffee
@@ -110,7 +110,7 @@
         @setClass 'new'
         @allItemsLink.hide()
         @show()
-        @newItemsLink.updatePartial "#{@newCount} new comment..."
+        @newItemsLink.updatePartial "#{ KD.utils.formatPlural @newCount, 'new comment' }..."
         @newItemsLink.setClass('in')
     else
       @unsetClass 'new'


added initial version of install.coffee

added initial version of install.coffee

--- src/lib/install.coffee
+++ src/lib/install.coffee
@@ -0,0 +1,25 @@
+# [Template](http://neocotic.com/template)
+# (c) 2012 Alasdair Mercer
+# Freely distributable under the MIT license.
+# For all details and documentation:
+# <http://neocotic.com/template>
+
+#### Private variables
+
+# Names of the classes to be removed from the targeted elements.
+classes     = [
+                'chrome_install_button'
+                'primary'
+              ]
+# Extension ID of Template.
+extensionId = 'dcjnfaoifoefmnbhhlbppaebgnccfddf'
+# "Install" links to be modified.
+links       = document.querySelectorAll("a.#{classes[0]}[href$=#{extensionId}]")
+
+#### Functionality
+
+# Disable all "Install" links on the homepage for Template.
+for link in links
+  link.className += ' disabled'
+  link.innerText  = 'Installed'
+  link.className  = link.className.replace cls, '' for cls in classes


activity: fix deprecated method call

activity: fix deprecated method call

--- client/Social/Activity/views/comments/commentviewheader.coffee
+++ client/Social/Activity/views/comments/commentviewheader.coffee
@@ -48,8 +48,7 @@
     # If its our comments so it's not a new comment
     if @currentCount > 0 then @currentCount--

-    @updateNewCount()
-
+    @update()

   ownCommentDeleted: ->


Tests:Activity:Others: Unabled search activity test.

--- tests/src/activity/others.coffee
+++ tests/src/activity/others.coffee
@@ -8,19 +8,19 @@
 module.exports =

-  # searchActivity: (browser) ->
+  searchActivity: (browser) ->

-  #   post     = helpers.postActivity(browser)
-  #   selector = '[testpath=activity-list] [testpath=ActivityListItemView]:first-child'
-  #   word     = post.split(' ')[0]
+    post     = helpers.postActivity(browser)
+    selector = '[testpath=activity-list] [testpath=ActivityListItemView]:first-child'
+    word     = post.split(' ')[0]

-  #   browser
-  #     .setValue                 '.kdtabhandlecontainer .search-input', word + '\n'
-  #     .waitForElementVisible    '.kdtabpaneview.search', 25000
-  #     .waitForElementVisible    selector, 25000
-  #     .pause                    25000
-  #     .assert.containsText      selector, word # Assertion
-  #     .end()
+    browser
+      .setValue                 '.kdtabhandlecontainer .search-input', word + '\n'
+      .waitForElementVisible    '.kdtabpaneview.search', 25000
+      .waitForElementVisible    selector, 25000
+      .pause                    25000
+      .assert.containsText      selector, word # Assertion
+      .end()

   showMoreCommentLink: (browser) ->


Revert serial booster point notation

Revert serial booster point notation

c9063b6

--- js/script.coffee
+++ js/script.coffee
@@ -98,8 +98,8 @@
 			# CORE_SERIAL
 			body += "#{p.rocket.rocket_name} #{p.rocket.rocket_type}#{if p.rocket.first_stage.cores[0].landing_vehicle then " to #{p.rocket.first_stage.cores[0].landing_vehicle}" else ' EXP'}"
 			for c in p.rocket.first_stage.cores
-			  # body += "[#{c.core_serial}] "
-				body += ""
+			  body += "[#{c.core_serial}] "
+				# body += ""
 			body += "	"
 			# :LAUNCH_SITE_NAME
 			# #:FLIGHT_NUMBER :LOCAL_DATE
@@ -116,7 +116,8 @@
 			table.appendChild row
 	document.querySelector('#content').appendChild table
 	for core_id in core_reflown
-		loadJSON "{{ site.spacex_api }}?core_serial=#{core_id.split(".")[0]}&date=#{new_date}", cb_reflow
+		loadJSON "{{ site.spacex_api }}?core_serial=#{core_id}&date=#{new_date}", cb_reflow
+		# loadJSON "{{ site.spacex_api }}?core_serial=#{core_id.split(".")[0]}&date=#{new_date}", cb_reflow
 	for cap_id in cap_reflown
 		loadJSON "{{ site.spacex_api }}?cap_serial=#{cap_id}&date=#{new_date}", cb_reflow
 	return


remove unused dependency

remove unused dependency

--- lineman/app/app.coffee
+++ lineman/app/app.coffee
@@ -25,7 +25,7 @@
     snakeName = _.snakeCase(name);
     'generated/components/' + snakeName + '/' + snakeName + '.html';

-angular.module('loomioApp').controller 'AppController', ($scope,$document, $filter, $rootScope, $router, KeyEventService) ->
+angular.module('loomioApp').controller 'AppController', ($scope, $filter, $rootScope, $router, KeyEventService) ->
   $scope.currentComponent = 'nothing yet'

   $scope.$on 'currentComponent', ->


s/write/log

s/write/log

--- src/logger.coffee
+++ src/logger.coffee
@@ -12,14 +12,14 @@

     # Log a message at the debug level.
     debug : (message) ->
-        @write("DEBUG", message)
+        @log("DEBUG", message)

     # Log a message at the info level.
     info : (message) ->
-        @write("INFO", message)
+        @log("INFO", message)

     # Log the message at the given level.
-    write : (level, message) ->
+    log : (level, message) ->
         if console
             fields = [new Date().toString(), level, @module, message]
             console.log(fields.join(" | "))


mixpanel: fix turn on/off logic

mixpanel: fix turn on/off logic

--- client/Main/mixpanel.coffee
+++ client/Main/mixpanel.coffee
@@ -1,6 +1,6 @@
 if KD.config.logToExternal then do ->
   KD.getSingleton('mainController').on "AccountChanged", (account) ->
-    return  unless KD.isLoggedIn() or account or mixpanel
+    return  unless KD.isLoggedIn() and account and mixpanel

     account.fetchEmail (err, email)->
       console.log err  if err
@@ -23,9 +23,9 @@

 # Access control wrapper around mixpanel object.
 KD.mixpanel = (args...)->
-  return  unless mixpanel or not KD.config.logToExternal
+  return  unless mixpanel and KD.config.logToExternal
   mixpanel.track args...

 KD.mixpanel.alias = (args...)->
-  return  unless mixpanel or not KD.config.logToExternal
+  return  unless mixpanel and KD.config.logToExternal
   mixpanel.alias args...


tidy up

tidy up

--- client/code/view/admin_header.coffee
+++ client/code/view/admin_header.coffee
@@ -1,13 +1,12 @@
 class Cu.View.AdminHeader extends Backbone.View
   el: '#header'

-  initialize: (title) ->
-    @title = title
+  initialize: (@title) ->
     @render()

   render: ->
     @$el.empty()
     @$el.load '/tpl/admin_header', =>
-      @$el.find('h2 a').text(@title)
+      @$el.find('h2 a').text @title
       topAndTailDropdowns()


Fetch environment data before routing to new created workspace

--- client/ide/lib/idehelpers.coffee
+++ client/ide/lib/idehelpers.coffee
@@ -99,7 +99,8 @@
                 actions.createWorkspace machine, workspace
                 actions.hideAddWorkspaceView machine._id

-              handleRoute(machine, workspace)
+              dataProvider.fetch ->
+                handleRoute(machine, workspace)

   handleWorkspaceCreateError_: (eventObj, error) ->


Team: chronology fixed

Team: chronology fixed

--- client/landing/About/team.coffee
+++ client/landing/About/team.coffee
@@ -31,7 +31,7 @@
         username  : 'geraint'
         title     : 'Director of Infrastructure'
       ,
-        username  : 'leventyalcin'
+        username  : 'aleksey-m'
         title     : 'System Administrator'
       ,
         username  : 'ybrs'
@@ -43,14 +43,17 @@
         username  : 'szkl'
         title     : 'Software Engineer'
       ,
+        username  : 'burakcan'
+        title     : 'CSS Wizard'
+      ,
+        username  : 'leventyalcin'
+        title     : 'System Administrator'
+      ,
         username  : 'samet'
         title     : 'Software Engineer'
       ,
         username  : 'cenk6'
         title     : 'Software Engineer'
-      ,
-        username  : 'burakcan'
-        title     : 'CSS Wizard'
       ,
         username  : 'emred'
         title     : 'Designer'
@@ -60,12 +63,12 @@
       ,
         username  : 'pablostanley'
         title     : 'Illustrator'
-      ,
-        username  : 'leeolayvar'
-        title     : 'Community Manager'
-      ,
-        username  : 'stefanbc'
-        title     : 'Community Relations'
+      # ,
+      #   username  : 'leeolayvar'
+      #   title     : 'Community Manager'
+      # ,
+      #   username  : 'stefanbc'
+      #   title     : 'Community Manager'
       ,
         username  : 'dicle'
         title     : 'Business Analysist'
@@ -77,10 +80,7 @@
         title     : 'Finance Assistant'
       ,
         username  : 'nesli'
-        title     : 'Chef de cuisine'
-      ,
-        username  : 'aleksey-m'
-        title     : 'System Administrator'
+        title     : 'Chef de Cuisine'
       ]

     suspended     : []


Import result files, rename to Array

Import result files, rename to Array

--- client/lanes/models/Query.coffee
+++ client/lanes/models/Query.coffee
@@ -1,3 +1,8 @@
+##= require_self
+##= require ./query/Result
+##= require ./query/ArrayResult
+##= require ./query/CollectionResult
+
 class Field extends Lanes.Models.Base
     pubsub: false
     constructor: (attributes) ->
@@ -180,15 +185,15 @@
         @query = options.query
         @fields = options.query.fields

-class Lanes.Models.Query extends Lanes.Models.Base # needs to be Base so network events will be listened to
-    pubsub: false
+# needs to inherit from Base so network events will be listened to
+class Lanes.Models.Query extends Lanes.Models.Base
+    pubsub: false
+

     @mergedSyncOptions: (args...) ->
         _.merge {}, args..., (a, b) ->
             if _.isArray(a)
                 return a.concat(b)
-
-
     session:
         src:     'any'
         fields:  'collection'
@@ -201,6 +206,7 @@
         autoRetrieve: ['boolean', true, true]
         title: { type: 'string', default: 'Find Record' }
         sortField: 'object'
+        changeCount: {type: 'integer', default: 0}
         sortAscending: ['boolean', true, true]

     derived:
@@ -220,7 +226,7 @@
                 if @isCollection
                     new Lanes.Models.Query.CollectionResult(this)
                 else
-                    new Lanes.Models.Query.SyncedResult(this, pageSize: @pageSize)
+                    new Lanes.Models.Query.ArrayResult(this, pageSize: @pageSize)

     events:
         'change:sortField':     '_updateSort'


Allow backbone navigation from conversation index to user profile, in stead of letting the click be handled by the wholeElementClick

--- core/app/backbone/views/messages/conversation_item_view.coffee
+++ core/app/backbone/views/messages/conversation_item_view.coffee
@@ -4,6 +4,7 @@
   template: 'conversations/item'
   events:
     'click' : 'wholeElementClick'
+    'click .user-profile-link' : 'userProfileLinkClick'

   templateHelpers: =>
     url: @model.url()
@@ -13,3 +14,9 @@
     e.preventDefault()
     e.stopImmediatePropagation()
     Backbone.history.navigate url, true
+
+  userProfileLinkClick: (e) ->
+    console.info ('click"')
+    e.preventDefault()
+    e.stopImmediatePropagation()
+    Backbone.history.navigate @model.get('last_message').sender.username, true


add BSVE data exchange functionality

add BSVE data exchange functionality

--- client/postMessageHandler.coffee
+++ client/postMessageHandler.coffee
@@ -68,6 +68,20 @@
             console.log error
         )
     )
+  else if request.type == "eha.dataExchange"
+    if $("#suggestedIncidentsModal").length > 0
+      $("#suggestedIncidentsModal").modal('hide')
+    else
+      Modal.show 'suggestedIncidentsModal',
+        userEventId: null
+        showTable: true
+        acceptByDefault: true
+        article:
+          publishDate: new Date()
+          addedDate: new Date()
+          url: request.link
+          content: request.content
+
 window.addEventListener("message", postMessageHandler, false)
 # The timeout is used to wait for BSVE.init to be called in the parent frame.
 window.setTimeout ->


Fix formatting

Fix formatting

--- client/lib/ConnectedComponent.coffee
+++ client/lib/ConnectedComponent.coffee
@@ -10,22 +10,18 @@
 import awaitAll from '../lib/awaitAll'

 class ErrorBoundary extends React.Component
-    constructor:(props) ->
+    constructor: (props) ->
         super(props)
-        this.state = { error: null, errorInfo: null };
-
-
-    componentDidCatch:(error, errorInfo) ->
-        this.setState({
+        this.state = { error: null, errorInfo: null }
+
+    componentDidCatch: (error, errorInfo) ->
+        this.setState
             error: error,
             errorInfo: errorInfo
-        })
-
-
+
     render: () ->
         if this.state.errorInfo
             return
<FontAwesome name="exclamation-circle"/>

-
         return this.props.children;

 export default ConnectedComponent = (Component, options) ->


matrix

matrix

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -116,9 +116,7 @@
     @name deltaTransformPoint
     @methodOf Matrix#
     @see #transformPoint
-
-    @return A new point transformed by this matrix ignoring tx and ty.
-    @type Point
+    @return {Point} A new point transformed by this matrix ignoring tx and ty.
     ###
     deltaTransformPoint: (point) ->
       Point(


FSItem: provide a static method for getting kite from vmname

--- client/ide/fs/fsitem.coffee
+++ client/ide/fs/fsitem.coffee
@@ -8,7 +8,8 @@

   @create:({ path, type, vmName, treeController }, callback)->

-    kite = @getKite()
+    kite = FSItem.getKite {vmName}
+
     kite.vmOn().then ->

       plainPath = FSHelper.plainPath path
@@ -304,18 +305,21 @@
       @emit "fs.job.started"

   getKite: ->
+    FSItem.getKite {@vm, @vmName}
+
+  @getKite = ({vm, vmName})->
     if KD.useNewKites
       kontrol = KD.getSingleton 'kontrol'
       kontrol.getKite \
-        if @vm?
+        if vm?
         then {
           name              : 'oskite'
-          correlationName   : @vm.hostnameAlias
-          region            : @vm.region
+          correlationName   : vm.hostnameAlias
+          region            : vm.region
         }
         else {
           name              : 'oskite'
-          correlationName   : @vmName
+          correlationName   : vmName
         }
     else
-      KD.getSingleton('vmController').getKiteByVmName @vmName
+      KD.getSingleton('vmController').getKiteByVmName vmName


Reinit: Route to machine workspace properly after quit.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1556,9 +1556,10 @@
     @stopCollaborationSession()

     if destroy
-      kd.singletons.appManager.quit this
-      kd.utils.defer ->
-        kd.singletons.router.handleRoute '/IDE'
+      kd.singletons.appManager.quit this, =>
+        route = if @mountedMachine then "/IDE/#{@mountedMachine.slug}" else '/IDE'
+        kd.singletons.router.handleRoute route
+
       @once 'KDObjectWillBeDestroyed', @lazyBound 'emit', 'IDEDidQuit'


ide: editor rename button activated

ide: editor rename button activated

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1124,8 +1124,8 @@
     paneType = paneView?.getOptions().paneType
     tabView  = paneView?.parent

-    return  unless paneType is 'terminal'
-    return  unless paneType is 'editor'
+    return  unless paneType in ['terminal', 'editor']
+    # return  unless paneType is 'editor'

     tabView.tabHandle.setTitleEditMode yes


Main: include references removed for moved files to the core app

--- client/Main/includes.coffee
+++ client/Main/includes.coffee
@@ -16,15 +16,13 @@
   'libs/jquery-timeago.js'

   # --- Application ---
+  "mq.config.coffee",
   "utils.coffee"
   "KD.extend.coffee" # our extensions to KD global
-  "kodingrouter.coffee",
-  "mq.config.coffee",
   "log.config.coffee",
   "algoliaresult.coffee",

   # jview
-  "jview.coffee",
   "jcustomhtmlview.coffee",

   # mainapp controllers
@@ -33,7 +31,6 @@
   "socialapicontroller.coffee",
   "notificationcontroller.coffee",
   "linkcontroller.coffee",
-  "oauthcontroller.coffee",
   "widgetcontroller.coffee",
   "localsynccontroller.coffee",

@@ -43,15 +40,12 @@
   "onboarding/onboardingitemview.coffee",

   # COMMON VIEWS
-  "CommonViews/testimonialsquoteview.coffee",
-  "CommonViews/testimonialsview.coffee",
   "CommonViews/applicationview/applicationtabview.coffee",
   "CommonViews/applicationview/applicationtabhandleholder.coffee",
   "CommonViews/sharepopup.coffee",
   "CommonViews/sharelink.coffee",
   "CommonViews/linkviews/linkview.coffee",
   "CommonViews/linkviews/linkmenuitemview.coffee",
-  "CommonViews/linkviews/customlinkview.coffee",
   "CommonViews/linkviews/profilelinkview.coffee",
   "CommonViews/linkviews/profiletextview.coffee",
   "CommonViews/linkviews/taglinkview.coffee",
@@ -61,7 +55,6 @@
   "CommonViews/bidirectionalnavigation.coffee"
   "CommonViews/kodingswitch.coffee",
   "CommonViews/animatedmodalview.coffee",
-  "CommonViews/footerview.coffee",

   "CommonViews/tokenview.coffee",
   "CommonViews/suggestedtokenview.coffee",
@@ -161,8 +154,6 @@

   # Algolia-based autocomplete:
   "autocompletecontroller.coffee",
-
-  "navigation/topnavigation.coffee",

   "navigation/navigationlink.coffee",
   "navigation/navigationitem.coffee",
@@ -191,11 +182,6 @@
   "payment/existingaccountworkflow.coffee",

-  #maintabs
-
-  "maintabs/maintabview.coffee",
-  "maintabs/maintabpaneview.coffee",
-
   # global notifications
   "globalnotification.coffee",

@@ -222,16 +208,11 @@

   "maincontroller/groupdata.coffee",
   "maincontroller/mainviews/appsettingsmenubutton.coffee",
-  "maincontroller/mainviews/mainview.coffee",
-  "maincontroller/mainviewcontroller.coffee",
   "maincontroller/groupscontroller.coffee",
-  "maincontroller/maincontroller.coffee",
   "maincontroller/helpcontroller.coffee",

   # --- Applications ---
   "channels/kitechannel.coffee",
-  "ApplicationManager.coffee",
-  "AppController.coffee",
   "AppStorage.coffee",
   "localstorage.coffee",

@@ -241,7 +222,6 @@
   "extras/github/views/githubrepoitem.coffee",

   # Application Backend
-  "kodingappscontroller.coffee",
   "CommonViews/kodingappselectorforgithub.coffee",

   # CONTENT DISPLAY VIEWS
@@ -262,12 +242,6 @@
   # Virtualization CONTROLLER
   "VirtualizationController.coffee",
   "CommonViews/modalappslistitemview.coffee",
-
-  # these are generated dependencies
-  # do not delete they're ignored in git
-  # created in compile time.
-  "__generatedapps__.coffee",
-  "__generatedroutes__.coffee",

   "status.coffee",
   "main.coffee",
@@ -281,12 +255,9 @@

   # STYLES

-  "styl/appfn.styl",
-  "styl/resurrection.styl",
   "styl/resurrection.sidebar.styl",
   "styl/resurrection.account.dropdown.styl",
   "styl/resurrection.anims.styl",
-  "styl/resurrection.commons.styl",
   "styl/troubleshoot.styl",
   "styl/computeproviders.styl",


Stack-Editor: remove update check from editor instance

--- client/stack-editor/lib/index.coffee
+++ client/stack-editor/lib/index.coffee
@@ -168,38 +168,4 @@
     view    = new StackEditorView options, data
     view.on 'Cancel', -> kd.singletons.router.back()

-    confirmation = null
-
-    stackTemplate.on? 'update', =>
-
-      return  if confirmation
-      return  if view._stackSaveInAction
-
-      view.addSubView confirmation = new ContentModal
-        title: 'Stack Template is Updated'
-        cssClass: 'content-modal'
-        buttons:
-          cancel:
-            title: 'Cancel'
-            style: 'solid light-gray medium'
-            callback: ->
-              confirmation.destroy()
-              confirmation = null
-          ok:
-            title: 'OK'
-            style: 'solid green medium'
-            callback: =>
-              view.destroy()
-              delete @editors[stackTemplate._id]
-              @openEditor stackTemplate._id
-        content: '''
-           @_stackSaveInAction = yes
-    view.on 'StackSaveCompleted', -> @_stackSaveInAction = no
-
     return view


Joy of returning values from event handlers.

--- client/lib/annotator.coffee
+++ client/lib/annotator.coffee
@@ -62,7 +62,7 @@
     # Store the selected targets
     @selectedTargets = event.targets

-    return false unless @confirmSelection event
+    return unless @confirmSelection event

     time = new Date().valueOf()

@@ -76,4 +76,9 @@
     # TODO: Do something with annotation
     console.log annotation

-    true
+    return # Make sure CoffeeScript does not return anything
+
+  onFailedSelection: (event) =>
+    super
+
+    return # Make sure CoffeeScript does not return anything


Remove auto-loading screen

Remove auto-loading screen

Was a debugging thing and doesn't work if the role
lacks access.

--- client/skr/Extension.coffee
+++ client/skr/Extension.coffee
@@ -3,10 +3,3 @@
 class Skr.Extension extends Lanes.Extensions.Base

     identifier: "skr"
-
-    setBootstrapData: (data)->
-
-
-    onAvailable: ->
-        _.delay ->
-            Lanes.Screens.display_id("customer-maint")


Minor comment update.

Minor comment update.

--- src/scripts/namespace.coffee
+++ src/scripts/namespace.coffee
@@ -33,10 +33,10 @@
     # The current language. Must be a a 2 digit ISO_639-1 code.
     LANGUAGE: 'en'

-    # By default the a new paragraph `<p>` is created when the enter/return key
-    # is pressed, to insert a line-break `<br>` the shift key can be held down
-    # when pressing enter. This behaviour can be reversed by setting the
-    # preference to be for line breaks.
+    # By default a new paragraph `<p>` is created when the enter/return key is
+    # pressed, to insert a line-break `<br>` the shift key can be held down when
+    # pressing enter. This behaviour can be reversed by setting the preference
+    # to be for line-breaks.
     PREFER_LINE_BREAKS: false

     # The size (in pixels) of the corner region used to detect a resize event


Correctly handle dashes in street names.

--- src/models.coffee
+++ src/models.coffee
@@ -485,14 +485,17 @@
             slug = []
             add = (x) -> slug.push x

-            street = @get('street').getText('name').toLowerCase().replace(/\ /g, SEPARATOR)
+            streetName = @get('street').getText('name').toLowerCase()
+            streetName = streetName
+                .replace(SEPARATOR, SEPARATOR + SEPARATOR) # escape dashes by doubling them
+                .replace(/\ +/g, SEPARATOR)
             add @get('number')

             numberEnd = @get 'number_end'
             letter = @get 'letter'
             if numberEnd then add "#{SEPARATOR}#{numberEnd}"
             if letter then slug[slug.length-1] += SEPARATOR + letter
-            @slug = "#{municipality}/#{street}/#{slug.join(SEPARATOR)}"
+            @slug = "#{municipality}/#{streetName}/#{slug.join(SEPARATOR)}"
             @slug
         humanAddress: (opts)->
             street = @get 'street'
@@ -603,13 +606,22 @@
             instance

         @fromSlug: (municipality, streetName, numberPart) ->
-            SEPARATOR = /-/g
+            SEPARATOR = '-'
             numberParts = numberPart.split SEPARATOR
             number = numberParts[0]
             number = numberPart.replace /-.*$/, ''
-            street = new Street ({
-                name: streetName.replace(SEPARATOR, ' '),
-                municipality_name: municipality})
+            fn = (memo, value) =>
+                if value == ''
+                    # Double (escaped) dashes result in an empty
+                    # element.
+                    "#{memo}#{SEPARATOR}"
+                else if memo.endsWith SEPARATOR
+                    "#{memo}#{value}"
+                else
+                    "#{memo} #{value}"
+            street = new Street
+                name: _.reduce streetName.split(SEPARATOR), fn
+                municipality_name: municipality
             @fromPosition new Position
                 street: street
                 number: number


Update cardData.coffee

Update cardData.coffee

--- client/data/cardData.coffee
+++ client/data/cardData.coffee
@@ -13,19 +13,19 @@
 #  url:
 #    github: 'https://productgrunt.com'
 #,
-#  id: 'babytracker'
-#  title: 'Baby Tracker'
+#  id: 'safebaby'
+#  title: 'SafeBaby'
 #  img: 'svg'
 #  description: 'Check up on your baby from afar'
 #  url:
-#    github: 'https://github.com/grant/herder.co'
+#    github: 'http://challengepost.com/software/safebaby'
 #,
 #  id: 'herder'
 #  title: 'Herder'
 #  img: 'svg'
 #  description: 'Analytics for Uber drivers to maximize their revenue'
 #  url:
-#    github: 'https://github.com/grant/herder'
+#    github: 'https://github.com/grant/herder.co'
 #,
 #  id: 'awear'
 #  title: 'Awear'


Destroy view if there is one

Destroy view if there is one

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -876,7 +876,8 @@
     unless @fakeViewsDestroyed
       for ideView in @ideViews
         {tabView} = ideView
-        tabView.removePane tabView.getActivePane()
+        if pane = tabView.getActivePane()
+          tabView.removePane pane

       @fakeFinderView?.destroy()
       @fakeViewsDestroyed = yes


Add a field id_room in user's model.

Add a field id_room in user's model.

--- src/models/user.coffee
+++ src/models/user.coffee
@@ -26,6 +26,9 @@
     password:
         type    : String
         length  : 255
+    id_room:
+        type    : String
+        length  : 255
     id_acl  :
         type    : Number
     id_role :


Do not show language versions in submitForm


set title

set title

--- client/skr/Extension.coffee
+++ client/skr/Extension.coffee
@@ -23,6 +23,9 @@
             else
                 console.log "Unable to find model for #{type}"

+    title: ->
+        "Stockor"
+
     rootComponent: (viewport) ->
         Lanes.Workspace.Layout


0.4.4 Beta

0.4.4 Beta

Signed-off-by: Byron Ruth <e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98@devel.io>

--- jsonpatch.coffee
+++ jsonpatch.coffee
@@ -1,4 +1,4 @@
-# jsonpatch.js 0.4.3
+# jsonpatch.js 0.4.4
 # (c) 2011-2012 Byron Ruth
 # jsonpatch may be freely distributed under the BSD license


* Handle the target element being removed.

--- vendor/assets/javascripts/balloons.js.coffee
+++ vendor/assets/javascripts/balloons.js.coffee
@@ -19,6 +19,8 @@
         @close()
         if options['close']
           options['close'](@targetElement)
+      @targetElement.on 'remove', =>
+        @close() # Remove the balloon if the target goes away.

     evaluateTarget: ->
       # See if the target is a descendent of a fixed element.


Corrected a function to used fat arrow syntax. Cleanup.

--- src/pubsub.coffee
+++ src/pubsub.coffee
@@ -32,6 +32,7 @@
       logger.info "Options based to new Pub/Sub WebSocket:\n
           #{JSON.stringify(@options, null, 2)}"

+    @handlers = {}
     @sock = null
     @pingerRef = null
     @recordCount = 0
@@ -99,7 +100,10 @@
         reject new error.PubSubError message, null

   # Subscribe to a channel.
-  subscribe: (channel) ->
+  subscribe: (channel, handler) ->
+    if typeof handler == 'function'
+      @handlers[channel] = handler
+
     new P (resolve, reject) =>
       if @sock?
         seq = @sequence
@@ -127,8 +131,14 @@

     .then (response) -> response.channels

+    .catch (error) =>
+      logger.error "Error subscribing to channel '#{channel}'", error
+      delete @handlers[channel]
+
   # Unsubscribe from a channel.
   unsubscribe: (channel) ->
+    delete @handlers[channel]
+
     new P (resolve, reject) =>
       if @sock?
         seq = @sequence
@@ -158,6 +168,8 @@

   # Unsubscribe from a channel.
   unsubscribeAll: ->
+    @handlers = {}
+
     new P (resolve, reject) =>
       if @sock?
         seq = @sequence
@@ -225,7 +237,7 @@
         @pingerRef = null

     @unsubscribeAll()
-    .finally ->
+    .finally =>
       if @sock?
         try
           @sock.close()
@@ -344,14 +356,22 @@
                     response.message, null, record.code,
                     record.details, record
                   ))
-              else if message.id?
-                @emit 'message', record
+              else if record.action == 'msg'
+                {id, action, chan, msg} = message
+
+                @handlers[chan]?(chan, msg, id)
+
+                setImmediate =>
+                  @emit 'message',
+                    channel: chan
+                    message: msg
+                    id: id
               else
                 message = 'Valid, but un-handled response type.'
                 logger.error "#{message}"
                 @emit 'error', new PubSubError("#{message}: #{rec}")

-          # WebSoket connection failure
+          # WebSocket connection failure
           @sock.once 'connectFailed', (error) =>
             @emit 'connectFailed', error
             logger.error "Failed to connect to Pub/Sub WebSocket"


testUtil: assert method-specific goog.require

while acorn.util.test as a whole does not require acorn.shells.Shell,
the describeShellModule method does and should assert that it has been
included by anyone calling it

--- coffee/src/util/test.coffee
+++ coffee/src/util/test.coffee
@@ -5,6 +5,9 @@
 acorn.util.test.describeShellModule = (Module, tests) =>
   derives = athena.lib.util.derives
   Shell = acorn.shells.Shell
+
+  acorn.util.assert Shell, 'goog.require \'acorn.shells.Shell\' before ' +
+    'calling acorn.util.test.describeShellModule'

   describeView = athena.lib.util.test.describeView


Highlight code in comments

Highlight code in comments

--- coffee/views/comment.coffee
+++ coffee/views/comment.coffee
@@ -1,9 +1,10 @@
 define [
   "jquery"
+  "highlight"
   "views/base/refresh_date"
   "lib/utils"
   "templates/comment"
-], ($, RefreshDateView, utils, template) ->
+], ($, hljs, RefreshDateView, utils, template) ->
   "use strict"

   class CommentView extends RefreshDateView
@@ -24,12 +25,14 @@

     afterRender: ->
       super
-      # Listen events only on first div (because of nesting)
       @firstDiv = @$el.children(0)
       # At first we must undelegate current events on current $el
       @undelegateEvents()
+      # Listen events only on first div (because of nesting)
       @$el = @firstDiv
       @delegateEvents()
+      @$("pre code").each (i, e) ->
+        hljs.highlightBlock e

     toggleMark: (e) ->
       e.preventDefault()


Recursively assign the individual paths of an inflection so that we can do nested scope inflections.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -181,9 +181,11 @@
   # Returns an object map using the component's scope inflections.
   locals: (models = @view.models) =>
     result = {}
-    result[key] = models[inverse] for key, inverse of @inflections
+
+    for key, inverse of @inflections
+      result[key] = (result[key] or models)[path] for path in inverse.split '.'
+
     result[key] ?= model for key, model of models
-
     result

   # Intercepts `Rivets.Binding::update` to be called on `@componentView` with a


participant: Less noisy debug logging

participant: Less noisy debug logging

--- src/participant.coffee
+++ src/participant.coffee
@@ -39,7 +39,7 @@
   # Normally only used directly for Source type participants
   # For Transform or Sink type, is called on data from input queue
   send: (inport, data) ->
-    debug 'got msg from send', inport, data
+    debug 'got msg from send()', inport
     @func inport, data, (outport, err, data) =>
       if not err
         @onResult outport, data, () ->
@@ -62,7 +62,7 @@
       return callback null if not def.queue

       callFunc = (msg) =>
-        debug 'got msg from queue', msg.data
+        debug 'got msg from queue', def.queue
         @func def.id, msg.data, (outport, err, data) =>
           return @messaging.nackMessage msg if err
           @onResult outport, data, (err) =>


Reorder expectations in trinary test suite

This matches the jasmine conventions, and makes error messages less
confusing.

--- trinary/trinary_test.spec.coffee
+++ trinary/trinary_test.spec.coffee
@@ -3,29 +3,28 @@
 describe 'Trinary', ->

   it '1 is decimal 1', ->
-    expect(1).toEqual new Trinary('1').toDecimal()
+    expect(new Trinary('1').toDecimal()).toEqual 1

   xit '2 is decimal 2', ->
-    expect(2).toEqual new Trinary('2').toDecimal()
+    expect(new Trinary('2').toDecimal()).toEqual 2

   xit '10 is decimal 3', ->
-    expect(3).toEqual new Trinary('10').toDecimal()
+    expect(new Trinary('10').toDecimal()).toEqual 3

   xit '11 is decimal 4', ->
-    expect(4).toEqual new Trinary('11').toDecimal()
+    expect(new Trinary('11').toDecimal()).toEqual 4

   xit '100 is decimal 9', ->
-    expect(9).toEqual new Trinary('100').toDecimal()
+    expect(new Trinary('100').toDecimal()).toEqual 9

   xit '112 is decimal 14', ->
-    expect(14).toEqual new Trinary('112').toDecimal()
+    expect(new Trinary('112').toDecimal()).toEqual 14

   xit '222 is 26', ->
-    expect(26).toEqual new Trinary('222').toDecimal()
+    expect(new Trinary('222').toDecimal()).toEqual 26

   xit '1122000120 is 32091', ->
-    expect(32091).toEqual new Trinary('1122000120').toDecimal()
+    expect(new Trinary('1122000120').toDecimal()).toEqual 32091

   xit 'invalid trinary is decimal 0', ->
-    expect(0).toEqual new Trinary('carrot').toDecimal()
-
+    expect(new Trinary('carrot').toDecimal()).toEqual 0


Fetch sources when runtime connection is ready

--- components/GetSource.coffee
+++ components/GetSource.coffee
@@ -6,6 +6,7 @@
   icon: 'code'
   constructor: ->
     @sources = {}
+    @pending = []
     @runtime = null

     @inPorts = new noflo.InPorts
@@ -31,6 +32,9 @@
     @unsubscribe @runtime if @runtime
     runtime.on 'component', @handleMessage
     @runtime = runtime
+    while @pending.length
+      task = @pending.shift()
+      @doAsync task.name, task.callback

   unsubscribe: (runtime) ->
     @sources = {}
@@ -43,7 +47,10 @@

   doAsync: (name, callback) ->
     unless @runtime
-      return callback new Error 'No runtime available'
+      @pending.push
+        name: name
+        callback: callback
+      return
     @runtime.sendComponent 'getsource',
       name: name


don't destroy navigator... there's got to be a better way!

--- components/geo/test/index.coffee
+++ components/geo/test/index.coffee
@@ -30,7 +30,7 @@
       _.isNull(Backbone.sync.args[0][2].headers).should.be.ok()

     it 'uses the browser geolocation API when high accuracy is requested', ->
-      benv.expose navigator: geolocation: getCurrentPosition: sinon.stub()
+      navigator.geolocation = getCurrentPosition: sinon.stub()
       Geo.locate(accuracy: 'high')
       navigator.geolocation.getCurrentPosition.called.should.be.ok()


Calendar offset is incorrect

Calendar offset is incorrect

--- client/calendar/calendar.coffee
+++ client/calendar/calendar.coffee
@@ -19,7 +19,7 @@
     startDay -= 7
   if dayOfWeek == localeFirstDay
     startDay = daysInLastMonth - 6
-  curDate = moment(lastMonth).day(startDay)
+  curDate = moment(lastMonth).date(startDay)

   i = 0
   col = 0


webapp/lib-update: dealing with newly introduced global window object in render_react_static

--- src/smc-webapp/render.coffee
+++ src/smc-webapp/render.coffee
@@ -1,16 +1,21 @@
 # Enable transparent server-side requiring of cjsx files.
 require('node-cjsx').transform()

-# The strategy here is the following:
-# 1. render the .template file into the webapp-lib folder
-# 2. during the "webpack" step, copy over the policies/*.html files
-# to the target directory (see CopyFilesToTargetPlugin)
+# The overall procedure is the following:
+# 1. render the component (rendering step is defined in the billing.cjsx file) into the webapp-lib folder
+# 2. during the "webpack" step, this component is included into a full html page via includes
+# look into policies/pricing.html, there is <%= require('html?conservativeCollapse!./_static_pricing_page.html') %>
+
+# there is a global window object, which is undefined in node.js' world -- we mock it and hope for the best.
+global['window'] = {}

 # Code for static server-side rendering of the subscription options.
+# note, that we use renderToStaticMarkup, not renderToString
+# (see https://facebook.github.io/react/docs/top-level-api.html#reactdomserver.rendertostaticmarkup)
 exports.render_subscriptions = ->
     ReactDOMServer = require('react-dom/server')
     billing = require('./billing.cjsx')
     fs = require('fs')
-    html = ReactDOMServer.renderToString(billing.render_static_pricing_page())
+    html = ReactDOMServer.renderToStaticMarkup(billing.render_static_pricing_page())
     filename = '../webapp-lib/policies/_static_pricing_page.html'
     fs.writeFileSync(filename, html)


Add .table class to inserted tables

Add .table class to inserted tables

--- app/assets/javascripts/effective_ckeditor_text_area/initialize.js.coffee
+++ app/assets/javascripts/effective_ckeditor_text_area/initialize.js.coffee
@@ -80,7 +80,12 @@

   options[k] = v for k, v of (textarea.data('input-js-options') || {})

-  CKEDITOR.replace(textarea.attr('id'), options)
+  ckeditor = CKEDITOR.replace(textarea.attr('id'), options)
+
+  ckeditor.on 'insertElement', (event) ->
+    element = $(event.data.$)
+    if element.is('table')
+      element.removeAttr('style').addClass('table')

 $ -> initialize()
 $(document).on 'page:change', -> initialize()


AppController: Create dummy editor if vm is turned off.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -293,6 +293,7 @@
           machineLabel = machine.slug or machine.label
           splashs      = IDE.splashMarkups

+          @fakeEditor       = @ideViews.first.createEditor()
           @fakeTabView      = @activeTabView
           @fakeTerminalView = new KDCustomHTMLView partial: splashs.getTerminal nickname
           @fakeTerminalPane = @fakeTabView.parent.createPane_ @fakeTerminalView, { name: 'Terminal' }


AppController: Code styling.

AppController: Code styling.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -135,12 +135,14 @@

   bindRouteHandler: ->
+
     {router, mainView} = KD.singletons

     router.on 'RouteInfoHandled', (routeInfo) =>
       if routeInfo.path.indexOf('/IDE') is -1
         if mainView.isSidebarCollapsed
           mainView.toggleSidebar()
+

   bindCollapseEvents: ->

@@ -159,17 +161,23 @@
     baseSplit = panel.layout.getSplitViewByName 'BaseSplit'
     baseSplit.resizer.on 'dblclick', @bound 'toggleSidebar'

+
   setActiveTabView: (tabView) ->
+
     return  if tabView is @activeTabView
     @setActivePaneFocus off
     @activeTabView = tabView
     @setActivePaneFocus on

+
   setActivePaneFocus: (state) ->
+
     return  unless pane = @getActivePaneView()
     KD.utils.defer -> pane.setFocus? state

+
   splitTabView: (type = 'vertical', ideViewOptions) ->
+
     ideView        = @activeTabView.parent
     ideParent      = ideView.parent
     newIDEView     = new IDE.IDEView ideViewOptions
@@ -193,7 +201,9 @@

     splitView.on 'ResizeDidStop', KD.utils.throttle 500, @bound 'doResize'

+
   mergeSplitView: ->
+
     panel     = @activeTabView.parent.parent
     splitView = panel.parent
     {parent}  = splitView
@@ -214,7 +224,9 @@

     splitView.merge()

+
   handleSplitMerge: (views, container, parentSplitView, panelIndexInParent) ->
+
     ideView = new IDE.IDEView createNewEditor: no
     panes   = []

@@ -239,16 +251,24 @@
       parentSplitView.options.views[panelIndexInParent] = ideView
       parentSplitView.panels[panelIndexInParent]        = ideView.parent

+
   openFile: (file, contents, callback = noop, emitChange) ->
+
     @activeTabView.emit 'FileNeedsToBeOpened', file, contents, callback, emitChange

+
   openMachineTerminal: (machineData) ->
+
     @activeTabView.emit 'MachineTerminalRequested', machineData

+
   openMachineWebPage: (machineData) ->
+
     @activeTabView.emit 'MachineWebPageRequested', machineData

+
   mountMachine: (machineData) ->
+
     panel        = @workspace.getView()
     filesPane    = panel.getPaneByName 'filesPane'
     {credential} = machineData.jMachine
@@ -260,12 +280,16 @@

     filesPane.emit 'MachineMountRequested', machineData, rootPath

+
   unmountMachine: (machineData) ->
+
     panel        = @workspace.getView()
     filesPane    = panel.getPaneByName 'filesPane'
     filesPane.emit 'MachineUnmountRequested', machineData

+
   createInitialView: ->
+
     KD.utils.defer =>
       @splitTabView 'horizontal', createNewEditor: no
       @getMountedMachine (err, machine) =>
@@ -288,7 +312,9 @@
           @createNewTerminal machine
           @setActiveTabView @ideViews.first.tabView

+
   getMountedMachine: (callback = noop) ->
+
     KD.getSingleton('computeController').fetchMachines (err, machines) =>
       if err
         KD.showError "Couldn't fetch your VMs"
@@ -299,7 +325,9 @@

         callback null, @mountedMachine

+
   mountMachineByMachineUId: (machineUId) ->
+
     computeController = KD.getSingleton 'computeController'
     container         = @getView()

@@ -358,7 +386,9 @@

         callback()

+
   createMachineStateModal: (options = {}) ->
+
     { state, container, machineItem, initial } = options
     modalOptions = { state, container, initial }
     @machineStateModal = new EnvironmentsMachineStateModal modalOptions, machineItem
@@ -366,7 +396,9 @@
     @machineStateModal.once 'KDObjectWillBeDestroyed', => @machineStateModal = null
     @machineStateModal.once 'IDEBecameReady',          => @handleIDEBecameReady machineItem

+
   collapseSidebar: ->
+
     panel        = @workspace.getView()
     splitView    = panel.layout.getSplitViewByName 'BaseSplit'
     floatedPanel = splitView.panels.first
@@ -408,7 +440,9 @@
     #     splitView.setFloatingPanel 0, 39
     #     tabView.showPaneByName 'Dummy'

+
   expandSidebar: ->
+
     panel        = @workspace.getView()
     splitView    = panel.layout.getSplitViewByName 'BaseSplit'
     floatedPanel = splitView.panels.first
@@ -427,13 +461,19 @@
     # @getView().unsetClass 'sidebar-collapsed'
     # @isSidebarCollapsed = no

+
   toggleSidebar: ->
+
     if @isSidebarCollapsed then @expandSidebar() else @collapseSidebar()

+
   splitVertically: ->
+
     @splitTabView 'vertical'

+
   splitHorizontally: ->
+
     @splitTabView 'horizontal'

   createNewFile: do ->
@@ -446,7 +486,9 @@

       @openFile file, contents

+
   createNewTerminal: (machine, path, session, joinUser) ->
+
     machine = null  unless machine instanceof Machine

     if @workspaceData
@@ -457,29 +499,39 @@

     @activeTabView.emit 'TerminalPaneRequested', machine, path, session, joinUser

+
   createNewBrowser: (url) ->
+
     url = ''  unless typeof url is 'string'

     @activeTabView.emit 'PreviewPaneRequested', url

+
   createNewDrawing: (paneHash) ->
+
     paneHash = null unless typeof paneHash is 'string'

     @activeTabView.emit 'DrawingPaneRequested', paneHash

+
   goToLeftTab: ->
+
     index = @activeTabView.getActivePaneIndex()
     return if index is 0

     @activeTabView.showPaneByIndex index - 1

+
   goToRightTab: ->
+
     index = @activeTabView.getActivePaneIndex()
     return if index is @activeTabView.length - 1

     @activeTabView.showPaneByIndex index + 1

+
   goToTabNumber: (keyEvent) ->
+
     keyEvent.preventDefault()
     keyEvent.stopPropagation()

@@ -488,13 +540,19 @@

     @activeTabView.showPaneByIndex requiredIndex

+
   goToLine: ->
+
     @activeTabView.emit 'GoToLineRequested'

+
   closeTab: ->
+
     @activeTabView.removePane @activeTabView.getActivePane()

+
   registerIDEView: (ideView) ->
+
     @ideViews.push ideView

     ideView.on 'PaneRemoved', (pane) =>
@@ -510,7 +568,9 @@
     ideView.on 'ChangeHappened', (change) =>
       @syncChange change

+
   registerPane: (pane) ->
+
     {view} = pane
     unless view?.hash?
       return warn 'view.hash not found, returning'
@@ -521,7 +581,9 @@
     view.on 'ChangeHappened', (change) =>
       @syncChange change

+
   forEachSubViewInIDEViews_: (callback = noop, paneType) ->
+
     if typeof callback is 'string'
       [paneType, callback] = [callback, paneType]

@@ -534,7 +596,9 @@
         else
           callback view

+
   updateSettings: (component, key, value) ->
+
     # TODO: Refactor this method by passing component type to helper method.
     Class  = if component is 'editor' then IDE.EditorPane else IDE.TerminalPane
     method = "set#{key.capitalize()}"
@@ -546,19 +610,29 @@
         else
           view.webtermView.updateSettings()

+
   showShortcutsView: ->
+
     @activeTabView.emit 'ShortcutsViewRequested'

+
   getActivePaneView: ->
+
     return @activeTabView?.getActivePane()?.getSubViews().first

+
   saveFile: ->
+
     @getActivePaneView().emit 'SaveRequested'

+
   saveAs: ->
+
     @getActivePaneView().aceView.ace.requestSaveAs()

+
   saveAllFiles: ->
+
     @forEachSubViewInIDEViews_ 'editor', (editorPane) ->
       {ace} = editorPane.aceView
       ace.once 'FileContentSynced', ->
@@ -566,7 +640,9 @@

       editorPane.emit 'SaveRequested'

+
   previewFile: ->
+
     view   = @getActivePaneView()
     {file} = view.getOptions()
     return unless file
@@ -579,7 +655,9 @@
     else
       @notify 'File needs to be under ~/Web folder to preview.', 'error'

+
   updateStatusBar: (component, data) ->
+
     {status} = @statusBar

     text = if component is 'editor'
@@ -600,7 +678,9 @@

     status.updatePartial text

+
   showStatusBarMenu: (ideView, button) ->
+
     paneView = @getActivePaneView()
     paneType = paneView?.getOptions().paneType or null
     delegate = button
@@ -617,13 +697,17 @@
         syntaxSelector.on 'SelectionMade', (value) =>
           ace.setSyntax value

+
   showFileFinder: ->
+
     return @fileFinder.input.setFocus()  if @fileFinder

     @fileFinder = new IDE.FileFinder
     @fileFinder.once 'KDObjectWillBeDestroyed', => @fileFinder = null

+
   showContentSearch: ->
+
     return @contentSearch.findInput.setFocus()  if @contentSearch

     @contentSearch = new IDE.ContentSearch
@@ -631,38 +715,54 @@
     @contentSearch.once 'ViewNeedsToBeShown', (view) =>
       @activeTabView.emit 'ViewNeedsToBeShown', view

+
   createStatusBar: (splitViewPanel) ->
+
     splitViewPanel.addSubView @statusBar = new IDE.StatusBar

+
   createFindAndReplaceView: (splitViewPanel) ->
+
     splitViewPanel.addSubView @findAndReplaceView = new AceFindAndReplaceView
     @findAndReplaceView.hide()
     @findAndReplaceView.on 'FindAndReplaceViewClosed', =>
       @getActivePaneView().aceView?.ace.focus()
       @isFindAndReplaceViewVisible = no

+
   showFindReplaceView: (withReplaceMode) ->
+
     view = @findAndReplaceView
     @setFindAndReplaceViewDelegate()
     @isFindAndReplaceViewVisible = yes
     view.setViewHeight withReplaceMode
     view.setTextIntoFindInput '' # FIXME: Set selected text if exists

+
   hideFindAndReplaceView: ->
+
     @findAndReplaceView.close no

+
   setFindAndReplaceViewDelegate: ->
+
     @findAndReplaceView.setDelegate @getActivePaneView()?.aceView or null

+
   showFindAndReplaceViewIfNecessary: ->
+
     if @isFindAndReplaceViewVisible
       @showFindReplaceView @findAndReplaceView.mode is 'replace'

+
   handleFileDeleted: (file) ->
+
     for ideView in @ideViews
       ideView.tabView.emit 'TabNeedsToBeClosed', file

+
   handleIDEBecameReady: (machine) ->
+
     {finderController} = @finderPane
     if @workspaceData
       finderController.updateMachineRoot @mountedMachine.uid, @workspaceData.rootPath
@@ -679,10 +779,14 @@
       @setActiveTabView @ideViews.first.tabView
       @fakeViewsDestroyed = yes

+
   toggleFullscreenIDEView: ->
+
     @activeTabView.parent.toggleFullscreen()

+
   doResize: ->
+
     @forEachSubViewInIDEViews_ (pane) ->
       {paneType} = pane.options
       switch paneType
@@ -698,11 +802,13 @@
             ace.editor.resize()

   notify: (title, cssClass = 'success', type = 'mini', duration = 4000) ->
+
     return unless title
     new KDNotificationView { title, cssClass, type, duration }

   loadCollaborationFile: (fileId) ->
+
     return unless fileId

     @rtm.getFile fileId
@@ -752,6 +858,7 @@

   registerParticipantSessionId: ->
+
     collaborators = @rtm.getCollaborators()

     for collaborator in collaborators when collaborator.isMe
@@ -764,6 +871,7 @@

   addParticipant: (account) ->
+
     {hash, nickname} = account.profile

     @participants.push { nickname, hash }
@@ -774,6 +882,7 @@

   createWorkspaceSnapshot: ->
+
     panes = {}

     @forEachSubViewInIDEViews_ (pane) ->
@@ -837,6 +946,7 @@

   watchParticipant: (targetParticipant) ->
+
     # TODO: Add presence check before watching user.
     target   = targetParticipant.nickname
     nickname = KD.nick()
@@ -855,6 +965,7 @@

   listenChangeEvents: ->
+
     @rtm.bindRealtimeListeners @changes, 'list'
     @rtm.bindRealtimeListeners @broadcastMessages, 'list'

@@ -876,6 +987,7 @@

   handleChange: (change) ->
+
     {context, origin, type} = change
     myWatchMap = @rtm.getFromModel "#{KD.nick()}WatchMap"

@@ -912,6 +1024,7 @@

   getPaneByChange: (change) ->
+
     return unless change.context

     targetPane = null
@@ -929,6 +1042,7 @@

   createPaneFromChange: (change) ->
+
     {context} = change
     return unless context

@@ -951,6 +1065,7 @@

   handleParticipantAction: (actionType, changeData) ->
+
     KD.utils.wait 2000, =>
       participants  = @participants.asArray()
       {sessionId}   = changeData.collaborator


Incoporate review enhancement.

Incoporate review enhancement.

--- src/coffee/csvmapping.coffee
+++ src/coffee/csvmapping.coffee
@@ -12,7 +12,7 @@
         @_mapOrder(order, mappings)

       data = _.flatten rows, true
-      Q @toCSV(header, data)
+      @toCSV(header, data)

   _mapOrder: (order, mappings) ->
     rows = []
@@ -55,7 +55,7 @@

   # TODO: Move method below to sphere-node-utils
   formatPrice = (price) ->
-    if price? and price.value?
+    if price?.value?
       countryPart = ''
       if price.country?
         countryPart = "#{price.country}-"


fix bonus moves calculation if custom board size was specified in query string

--- src/coffeescript/game.coffee
+++ src/coffeescript/game.coffee
@@ -323,10 +323,10 @@
     constructor: () ->
         super

-        qs = (new QueryString).get('size')
-
-        boardSize = if cfg.customBoardSize and qs
-        then qs else cfg.defaultBoardSize
+        qsSize = parseInt((new QueryString).get('size'))
+
+        boardSize = if cfg.customBoardSize and qsSize
+        then qsSize else cfg.defaultBoardSize

         @renderManager = new RenderManager boardSize, @


Wait for Atom's warm-up before initial auto-update

--- lib/auto-update-packages.coffee
+++ lib/auto-update-packages.coffee
@@ -9,6 +9,7 @@
 minute = (m) -> second(m * 60)
 hour   = (h) -> minute(h * 60)

+WARMUP_WAIT = second(10)
 AUTO_UPDATE_CHECK_INTERVAL = minute(15)
 AUTO_UPDATE_BLOCK_DURATION = hour(6)

@@ -17,15 +18,24 @@
     atom.workspaceView.command 'auto-update-packages:update-now', =>
       @updatePackages(false)

+    setTimeout =>
+      @enableAutoUpdate()
+    , WARMUP_WAIT
+
+  deactivate: ->
+    @disableAutoUpdate()
+    atom.workspaceView.off 'auto-update-packages:update-now'
+
+  enableAutoUpdate: ->
     @updatePackagesIfAutoUpdateBlockIsExpired()

     @autoUpdateCheck = setInterval =>
       @updatePackagesIfAutoUpdateBlockIsExpired()
     , AUTO_UPDATE_CHECK_INTERVAL

-  deactivate: ->
-    clearInterval(@autoUpdateCheck)
-    atom.workspaceView.off 'auto-update-packages:update-now'
+  disableAutoUpdate: ->
+    clearInterval(@autoUpdateCheck) if @autoUpdateCheck
+    @autoUpdateCheck = null

   updatePackagesIfAutoUpdateBlockIsExpired: ->
     lastUpdateTime = @loadLastUpdateTime() || 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -136,7 +136,7 @@

 @name startsWith
 @methodOf String#
-@param {String} str The string to check
+@param {String} str The string to check.

 @returns {Boolean} True if this string starts with the given string, false otherwise.
 ###


small refactor

small refactor

--- app/assets/javascripts/app.coffee
+++ app/assets/javascripts/app.coffee
@@ -38,7 +38,38 @@
   # Document ready
   # ------------------------------------------------------------------------------------------------------------------
   $document.ready ->
-    # load ace editor
+    # init ace editor
+    vm.init_ace_editor()
+
+    # sync scroll
+    scrollSyncor.syncScroll(vm.ace_editor, $('#md_html_div'))
+
+    noteManager.init_current_note()
+
+    # get note list from local
+    noteManager.reload_local_note_list()
+
+    # get note list from remote
+    noteManager.fetch_note_list()
+
+    # get notebook list from remote
+    noteManager.fetch_notebook_list()
+
+    # take effect the settings
+    vm.set_keyboard_handler(vm.current_keyboard_handler)
+    vm.set_show_gutter(vm.current_show_gutter)
+    vm.set_ace_theme(vm.current_ace_theme)
+
+    # reset app status
+    vm.reset_status()
+
+    # all ready
+    vm.all_ready = true
+
+  # ------------------------------------------------------------------------------------------------------------------
+  # ace editor init
+  # ------------------------------------------------------------------------------------------------------------------
+  vm.init_ace_editor = ->
     $window.ace.config.set('basePath', '/javascripts/ace')
     vm.ace_editor = $window.ace.edit("md_editor_div")
     vm.ace_editor.renderer.setShowGutter(false)
@@ -48,28 +79,6 @@
     vm.ace_editor.setTheme('ace/theme/tomorrow_night_eighties')
     vm.ace_editor.on 'change', vm.editor_content_changed
     vm.ace_editor.focus()
-
-    # sync scroll
-    scrollSyncor.syncScroll(vm.ace_editor, $('#md_html_div'))
-
-    noteManager.init_current_note()
-
-    # get note list
-    noteManager.fetch_note_list()
-
-    # get notebook list
-    noteManager.fetch_notebook_list()
-
-    # take effect the settings
-    vm.set_keyboard_handler(vm.current_keyboard_handler)
-    vm.set_show_gutter(vm.current_show_gutter)
-    vm.set_ace_theme(vm.current_ace_theme)
-
-    # reset app status
-    vm.reset_status()
-
-    # all ready
-    vm.all_ready = true

   # ------------------------------------------------------------------------------------------------------------------
   # ace editor event handlers
@@ -838,7 +847,7 @@
   # ------------------------------------------------------------
   init_current_note: () =>
     previous_note_guid = localStorageService.get(@SETTINGS_KEY.CURRENT_NOTE_GUID)
-    if previous_note_guid? == false
+    if not previous_note_guid?
       previous_note_guid = "INVALID_GUID"
     p = @find_note_by_guid(previous_note_guid).then (note) =>
       if note?
@@ -887,13 +896,7 @@
               @note_load_finished(true, note.guid, null)
               console.log('loading note ' + note.guid + ' finished')
               # updating note list
-              @get_all_notes().then(
-                (notes) =>
-                  console.log('updating note lists')
-                  @_set_note_list(notes)
-                (error) =>
-                  notifier.error('get_all_notes() failed in load_note(): ' + error)
-              )
+              @reload_local_note_list()
             (error) =>
               notifier.error('load note ' + guid + ' failed: ' + JSON.stringify(error))
               @note_load_finished(false, guid, new Error('load note ' + guid + ' failed: ' + JSON.stringify(error)))


removed verbose debug logging

removed verbose debug logging

--- routes/api/v2/docs.coffee
+++ routes/api/v2/docs.coffee
@@ -2,7 +2,6 @@
   if Meteor.isServer
     aglio = require('aglio')
     blueprintDoc = Assets.getText 'API.apib'
-    console.log 'wawiewa', process.env.ROOT_URL[..1]
     url = "#{process.env.ROOT_URL}#{if process.env.ROOT_URL[process.env.ROOT_URL.length-1..-1] isnt '/' then '/' else ''}"
     blueprintDoc = blueprintDoc.replace 'http://localhost:3000/', url
     rendered = ''


:bug: Removes color markers from display markers when destroyed

--- lib/color-buffer-element.coffee
+++ lib/color-buffer-element.coffee
@@ -140,7 +140,9 @@
       view = @unusedMarkers.shift()
     else
       view = new ColorMarkerElement
-      view.onDidRelease ({marker}) => @releaseMarkerView(marker)
+      view.onDidRelease ({marker}) =>
+        @displayedMarkers.splice(@displayedMarkers.indexOf(marker), 1)
+        @releaseMarkerView(marker)
       @shadowRoot.appendChild view

     view.setModel(marker)


Make it more coffee like

Make it more coffee like

--- client/Framework/components/autocomplete/KDAutoCompleteController.coffee
+++ client/Framework/components/autocomplete/KDAutoCompleteController.coffee
@@ -373,7 +373,7 @@
     {nothingFoundItemClass} = @getOptions()
     view = new nothingFoundItemClass
       delegate: @dropdown.getListView()
-      userInput: if suggestion then suggestion else @getView().getValue()
+      userInput: suggestion or @getView().getValue()

   showNoDataFound: ->
     noItemFoundView = @getNoItemFoundView()


Workspace: Added amIHost method to check user joined a session or not.

--- client/landing/app/CommonViews/workspace/panel/collaborativepanel.coffee
+++ client/landing/app/CommonViews/workspace/panel/collaborativepanel.coffee
@@ -20,10 +20,11 @@
     PaneClass              = @getPaneClass paneOptions.type
     paneOptions.delegate   = @
     paneOptions.sessionKey = @getOptions().sessionKeys[@panes.length]  if @getOptions().sessionKeys
-    isJoinedASession       = !!paneOptions.sessionKey
+    isJoinedASession       = !!paneOptions.sessionKey and not @amIHost paneOptions.sessionKey

-    if isJoinedASession and paneOptions.type is "terminal"
-      PaneClass = CollaborativeClientTerminalPane
+    if isJoinedASession then log "#{KD.nick()} is joined a session"
+    else log "#{KD.nick()} created a session"
+

     pane = new PaneClass paneOptions

@@ -31,6 +32,10 @@
     @panes.push pane
     @emit "NewPaneCreated", pane

+  amIHost: (sessionKey) ->
+    return  no unless sessionKey
+    [sessionOwner] = sessionKey.split ":"
+    return sessionOwner == KD.nick()

 CollaborativePanel::EditorPaneClass   = CollaborativeEditorPane
 CollaborativePanel::TerminalPaneClass = CollaborativeTerminalPane


Remove text related to deprecated APIs being removed in V1.0.

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -16,11 +16,6 @@
         @div class: 'padded deprecation-overview', =>
           @div class: 'pull-right btn-group', =>
             @button class: 'btn btn-primary check-for-update', 'Check for Updates'
-
-          @div class: 'text native-key-bindings', tabindex: -1, =>
-            @span class: 'icon icon-question'
-            @span 'Deprecated APIs will be removed when Atom 1.0 is released in June. Please update your packages. '
-            @a class: 'link', outlet: 'openBlogPost', 'Learn more\u2026'

         @div class: 'panel-heading', =>
           @span "Deprecated calls"


Fix comment

Fix comment

--- lib/documents/annotation.coffee
+++ lib/documents/annotation.coffee
@@ -18,7 +18,7 @@
   #   tag:
   #     _id: label's tag id
   #     name: label's tag name (ISO 639-1 dictionary)
-  #     slug: labels' tag slug
+  #     slug: label's tag slug (ISO 639-1 dictionary)
   #   upvoters: list of
   #     _id: upvoter's person id
   #   downvoters: list of


Temporary disabling the wiki sanitizing

Temporary disabling the wiki sanitizing

--- app/coffee/directives/wiki.coffee
+++ app/coffee/directives/wiki.coffee
@@ -81,8 +81,6 @@
         scope.$watch attrs.gmRenderMarkdown, ->
             data = scope.$eval(attrs.gmRenderMarkdown)
             if data != undefined
-                data = $sanitize(data)
-
                 tree = markdown.parse(data.replace("\r", ""))
                 for item in tree[1..tree.length]
                     parseMarkdownLinks(scope, item)


Fix PandaPub to work with webpack

Fix PandaPub to work with webpack

Fixes: CNVS-35543

You can’t require dynamic unknown urls like
this in webpack. It needs to statically analyze
the things you are going to require at build time

Test plan:
sorry I don’t actually know how to set up pandapub locally so
we’ll have to ask someone that does how to set it up

* once you have panda pub set up, using webpack, go to a
  page that uses it and make sure it works

Change-Id: Icd2849fd9b212ebde543dfbeb5fbd3b3f36dbcbb
Reviewed-on: https://gerrit.instructure.com/104754
Reviewed-by: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Tested-by: Jenkins
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/PandaPub.coffee
+++ app/coffeescripts/PandaPub.coffee
@@ -63,7 +63,7 @@
       if @faye then cb(@faye)

       unless @faye
-        require [ INST.pandaPubSettings.push_url + '/client.js' ], =>
+        $.getScript INST.pandaPubSettings.push_url + '/client.js', =>
           @faye = new window.Faye.Client INST.pandaPubSettings.push_url
           @faye.addExtension @authExtension()
           cb @faye


attempt to fix simulations

attempt to fix simulations

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -357,6 +357,7 @@

   onSubjectSelect: (e, subject) =>
     # console.log 'onSubjectSelect(): '
+    console.log("selecting subject")
     @subject = subject
     @classification = new Classification {subject}
     @loadSubjectData()
@@ -391,8 +392,9 @@

     # read json data
     $.getJSON jsonFile, (data) =>
-      if data.metadata.known_transits
-        @known_transits = data.metadata.known_transits
+
+      if @subject.metadata.synthetic_id?
+        @calcKnownTransits(data)

       @canvasGraph?.marks.destroyAll()
       @marksContainer.appendChild(@canvas)
@@ -420,6 +422,36 @@
     # @el.find('#finished-marking').fadeIn()
     # @el.find('#finished-feedback').fadeIn()

+  calcKnownTransits:(data)=>
+    first_event = data.metadata.first_trans_event
+    no_points = data.x.length
+    console.log "data range ", data.x[no_points-1]-data.x[0], " planet peroid ", data.metadata.planet_period, "no points " ,  no_points*1.0
+
+    start = parseInt(first_event.split(":")[0])
+    end   = parseInt(first_event.split(":")[1])
+    period = data.metadata.planet_period
+    startX = data.x[start]
+    endX   = data.x[end]
+    console.log "data ", data.x, start, end
+    console.log "startx ", startX, " end x ", endX
+    dur = endX - startX
+    mid = (endX + startX)/2.0
+
+    in_range = true
+    i= 0
+
+
+    @known_transits = []
+
+    # while in_range
+    #   if (startX + (i*period)) > data.x[no_points-1]
+    #     in_range = false
+    #   else
+    #     @known_transits.push([startX + (i * period) - data.x[0] , endX + (i * period) - data.x[0] ])
+    #     i+=1
+
+
+
   insertMetadata: ->
     # ukirt data
     @ra      = @subject.coords[0]
@@ -568,8 +600,11 @@

     if @sim_count%2 == 0
       Subject.group = "5417014b3ae7400bda000002"
+
       Subject.fetch 1, (subjects) =>
         console.log "got sim subjects ", subjects
+        @onSubjectSelect(null, subjects[0])
+
     else
       Subject.group = "5417014a3ae7400bda000001"
       @Subject.next()
@@ -698,12 +733,14 @@

   displayKnownTransits: ->
     return unless @simulationsPresent()
-
+    console.log "on display", @known_transitss
     @hideMarkingButtons()
     @continueButton.show()
+    start_time = Subject.current.selected_light_curve.start_time

     for transit in @known_transits
-      @canvasGraph.highlightCurve(transit[0],transit[1])
+      console.log "hilighting", transit[0] , " to ", transit[1]
+      @canvasGraph.highlightCurve(transit[0], transit[1])

     @course.showPrompt()
     @course.prompt_el.find('#course-yes-container').hide()


Refresh all feedback messages on display

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -334,6 +334,10 @@
         firstStep: 'dashboard'
         steps: TutorialDashboard
         parent: @el[0]
+
+      tutorial.el.on 'start-tutorial enter-tutorial-step', ->
+        translate.refresh tutorial.el.get 0
+
       tutorial.start()

     # Prompt Talk message
@@ -343,6 +347,10 @@
         firstStep: 'talk'
         steps: TutorialTalk
         parent: @el[0]
+
+      tutorial.el.on 'start-tutorial enter-tutorial-step', ->
+        translate.refresh tutorial.el.get 0
+
       tutorial.start()

   onNoMoreSubjects: ->
@@ -698,6 +706,9 @@
         @tutorial = if nAnnotations > 0 then @createDudMissedFeedback(e) else @createDudFoundFeedback(e)

       # Start the tutorial
+      @tutorial.el.on 'start-tutorial enter-tutorial-step', =>
+        translate.refresh @tutorial.el.get 0
+
       @tutorial?.start()

     else


grab href

grab href

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -115,7 +115,7 @@
     @noTransitsButton.attr 'disabled', true

     $(".links a").on "click",(el)=>
-      clickEvent = { event: 'top-bar-pressed', link: $(el).attr("href"), timestamp: (new Date).toUTCString() }
+      clickEvent = { event: 'top-bar-pressed', link: $(el.currentTarget).attr("href"), timestamp: (new Date).toUTCString() }
       @recordedClickEvents.push clickEvent


codemirror: restore viewport and cursor if changes when refreshing codemirror (I observed a change).

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -1727,7 +1727,13 @@
             cm_wrapper.css
                 height : height
                 width  : width
-            cm.refresh()
+            scroll = cm.getScrollInfo(); pos = cm.getCursor()
+            cm.refresh()  # NOTE: unfortunately, this can break the cursor location and scrollTo -- I've seen it happen.
+            scroll_after = cm.getScrollInfo(); pos_after = cm.getCursor()
+            if scroll.left != scroll_after.left or scroll.top != scroll_after.top or pos.line != pos_after.line or pos.ch != pos_after.ch
+                console.log("WARNING: codemirror refresh lost pos -- RESETTING position")
+                cm.scrollTo(scroll.left, scroll.top)
+                cm.setCursor(pos)

         @emit('show', height)


Use attachments to render preview of uploads and use relative paths

--- packages/rocketchat-ui/lib/fileUpload.coffee
+++ packages/rocketchat-ui/lib/fileUpload.coffee
@@ -86,15 +86,19 @@

 						onComplete: (file) ->
 							self = this
+							url = file.url.replace(Meteor.absoluteUrl(), '/')
 							Meteor.call 'sendMessage', {
 								_id: Random.id()
 								rid: roomId
-								msg: """
-									File Uploaded: *#{file.name}*
-									#{file.url}
-								"""
+								msg: ""
 								file:
 									_id: file._id
+								groupable: false
+								attachments: [{
+									image_url: url
+									title: "File Uploaded: #{file.name}"
+									title_link: url
+								}]
 							}, ->
 								Meteor.setTimeout ->
 									uploading = Session.get 'uploading'


bugfix

bugfix

--- app/helper/parameterHelper.coffee
+++ app/helper/parameterHelper.coffee
@@ -35,7 +35,7 @@
   #   *imagePath* path to the input image
   #   *md5* md5 hash of the input image
   #   *req* the request
-  getReservedParamValue: (parameter, process,md5, req) ->
+  getReservedParamValue: (parameter, process, req) ->

     switch parameter
       when 'matlabPath'
@@ -79,7 +79,7 @@
       if checkReservedParameters parameter
         #check if highlighter
         if parameter is 'highlighter'
-          params[neededParameters[parameter]] = this.getHighlighterParamValues(process.neededParameters[parameter], process.inputHighlighters.segments)
+          params[process.neededParameters[parameter]] = this.getHighlighterParamValues(process.neededParameters[parameter], process.inputHighlighters.segments)
         else
           data[parameter] = this.getReservedParamValue(parameter, process, req)
       else


Working snake-to-apple collisions, apple spawning, game pausing (server only atm)

Signed-off-by: Kalman Olah <aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d@kalmanolah.net>

--- src/server/game.coffee
+++ src/server/game.coffee
@@ -26,6 +26,17 @@
     loopIterationDelayMs: 2

+# ###
+# Game helpers.
+# ###
+Game.helpers =
+    ###
+    Helper function for generating a random integer from a range.
+    ###
+    randomIntBetween: (min, max) ->
+        Math.floor (Math.random() * (max - min + 1) + min)
+
+
 class Game.State

     ###
@@ -127,7 +138,15 @@

     Only modify with `setState` to avoid breakage.
     ###
-    state: Game.State.STOPPED
+    state: null
+
+    ###
+    {@inheritDoc}
+    ###
+    constructor: () ->
+        super
+
+        @state = Game.State.STOPPED

     ###
     Start the instance.
@@ -155,13 +174,44 @@

     @type array<integer, integer, integer>
     ###
-    size: [0, 0, 0]
+    size: null
+
+    ###
+    World, or whatever you want to call it. A 3D point container with
+    information regarding which entity occupies what coordinate
+    in 3D space.
+    ###
+    world: null

     ###
     {@inheritDoc}
     ###
-    constructor: (@size) ->
+    constructor: (@size = [12, 12, 6]) ->
         super
+
+        @world = {}
+
+        # Initialize world
+        for x in [0 .. (@size[0] - 1)]
+            @world[x] = {}
+
+            for y in [0 .. (@size[1] - 1)]
+                @world[x][y] = {}
+
+                for z in [0 .. (@size[2] - 1)]
+                    @world[x][y][z] = null
+
+    ###
+    Get a random (unoccupied) coordinate in this level.
+    ###
+    getRandomCoordinate: () ->
+        # @TODO Make the random coordinate not so random
+        # and ensure the returned coordinate is not actually being occupied
+            [
+                Game.helpers.randomIntBetween 0, (@size[0] - 1)
+                Game.helpers.randomIntBetween 0, (@size[1] - 1)
+                Game.helpers.randomIntBetween 0, (@size[2] - 1)
+            ]

     ###
     Get state as a plain object.
@@ -188,21 +238,21 @@

     Only modify with `setState` to avoid breakage.
     ###
-    state: Game.State.STOPPED
+    state: null

     ###
     Current clients.

     Only modify with `addClient` and `removeClient` to avoid breakage.
     ###
-    clients: {}
+    clients: null

     ###
     Current entities.

     Only modify with `addEntity` and `removeEntity` to avoid breakage.
     ###
-    entities: {}
+    entities: null

     ###
     The current room level.
@@ -214,7 +264,7 @@

     Only modify with `setDifficulty` to avoid breakage.
     ###
-    difficulty: 1
+    difficulty: null

     ###
     Constructor.
@@ -222,7 +272,21 @@
     constructor: (@id, @name) ->
         super

-        @level = new Game.Level [32, 32, 16]
+        @level = new Game.Level [12, 12, 6]
+        @entities = {}
+        @clients = {}
+        @difficulty = 1
+        @state = Game.State.ACTIVE
+        @spawnApple()
+
+    ###
+    Spawn an apply in the level attached to this room.
+    ###
+    spawnApple: () ->
+        entity = new Game.AppleEntity
+        entity.position = @level.getRandomCoordinate()
+        @level.world[entity.position[0]][entity.position[1]][entity.position[2]] = entity
+        @addEntity entity

     ###
     Set the primary difficulty.
@@ -310,6 +374,7 @@
             difficulty: @difficulty
             level: @level.getAsObject()
             entities: (v.getAsObject() for k, v of @entities)
+            state: @state

 class Game.Client extends Game.Loggable
@@ -367,21 +432,31 @@

     @type array<integer, integer, integer>
     ###
-    position: [0, 0, 0]
+    position: null

     ###
     Positional history.

     @type array<array<integer, integer, integer>>
     ###
-    positionHistory: []
+    positionHistory: null

     ###
     Directional vector.

     @type array<integer, integer, integer>
     ###
-    direction: [0, 0, 0]
+    direction: null
+
+    ###
+    {@inheritDoc}
+    ###
+    constructor: () ->
+        super
+
+        @position = [0, 0, 0]
+        @positionHistory = []
+        @direction = [0, 0, 0]

     ###
     Get state as a plain object.
@@ -401,6 +476,13 @@
     ###

+class Game.AppleEntity extends Game.Entity
+
+    ###
+    Apple entity.
+    ###
+
+
 class Game.EventManager extends Game.StartStoppable

     ###
@@ -411,12 +493,21 @@
     ###
     Event queue.
     ###
-    events: []
+    events: null

     ###
     Event handlers.
     ###
-    handlers: {}
+    handlers: null
+
+    ###
+    {@inheritDoc}
+    ###
+    constructor: () ->
+        super
+
+        @events = []
+        @handlers = {}

     ###
     {@inheritDoc}
@@ -490,7 +581,7 @@
         # For each room, increment the current server tick counter
         # Once we reach the required amount of server ticks for a room
         # tick, we reset the counter.
-        for id, room of Game.rooms
+        for id, room of Game.rooms when room.state == Game.State.ACTIVE
             room.tickCounter ?= 0
             room.tickCounter += 1

@@ -504,9 +595,15 @@
     onRoomTick: (event, room) ->
         # Calculate latest positions for all entities
         for id, entity of room.entities
+            # No need to perform calculations for stuff which isn't moving
+            # @TODO Determine whether performance would be increased if we use array sum != 0 instead
+            if (entity.direction.indexOf 1) == -1 && (entity.direction.indexOf -1) == -1
+                continue
+
             oldPosition = entity.position[..]
             position = entity.position
             direction = entity.direction
+            moveOk = true

             # Increment x, y and z positioning by direction
             # vector value
@@ -521,7 +618,34 @@
                     position[i] = 0

             entity.positionHistory.unshift oldPosition
-            entity.positionHistory.pop()
+            oldestPosition = entity.positionHistory.pop()
+
+            # If there is already an entity at the entity's latest position,
+            # we have a collision on our hands
+            target = room.level.world[position[0]][position[1]][position[2]]
+            if target
+                # A collision with an apple entity means we gain a "point",
+                # whereas anything else means we've lost
+                if target.constructor.name == Game.AppleEntity.name
+                    # Replace apple
+                    room.removeEntity target
+                    room.spawnApple()
+
+                    # Increase the size of our player
+                    entity.positionHistory.push oldestPosition
+                    oldestPosition = null
+                else
+                    # @TODO Lose a life here, or lose the game
+                    # Fail movement
+                    moveOk = false
+
+            if moveOk
+                # Place the entity at its newest position
+                room.level.world[position[0]][position[1]][position[2]] = entity
+
+                # Remove the entity from its oldest position if required
+                if oldestPosition
+                    room.level.world[oldestPosition[0]][oldestPosition[1]][oldestPosition[2]] = null

         # Generate status update
         status = room.getAsObject()
@@ -601,6 +725,8 @@
         if client
             if type == 'setDirection'
                 client.entity.direction = parameters.direction
+            else if type == 'setState'
+                room.setState Game.State[parameters.state]

 ###


engine#add

engine#add

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -66,11 +66,13 @@

   flash: (options={}) ->
     self.camera(options.camera).flash(options)
-
+
   objects: ->
     I.currentState.objects()
+
   setState: (newState) ->
     requestedState = newState
+
   shake: (options={}) ->
     self.camera(options.camera).shake(options)
   saveState: ->


show root breadcrumb when browsing root

show root breadcrumb when browsing root

--- frontend/app/js/browse-ctrl.coffee
+++ frontend/app/js/browse-ctrl.coffee
@@ -12,7 +12,6 @@
       $scope.breadcrumbs = ctrl.breadcrumbs(uri)

   ctrl.breadcrumbs = (uri) ->
-    return [] if uri == "/"
     parts = uri.split("/")
     { label: part, path: parts[0..i].join("/") } for part, i in parts


bubbles: moved year caption to the top left corner

--- frontend/bubbles/bubbles-aiddata.coffee
+++ frontend/bubbles/bubbles-aiddata.coffee
@@ -667,9 +667,9 @@
     svg.append("text")
       .attr("id", "yearText")
       .attr("font-size", bubblesChartWidth/15)
-      .attr("x", bubblesChartWidth - 20)
+      .attr("x", 20) #bubblesChartWidth - 20)
       .attr("y", 100)
-      .attr("text-anchor", "end")
+      .attr("text-anchor", "start")
         .text(state.selMagnAttr())

     setSelDateTo = (date, noAnim) ->