collaboration: controller minor

collaboration: controller minor

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -78,21 +78,17 @@
     return @socialChannel?.id or @channelId or @workspaceData.channelId

-  unsetSocialChannel: ->
-
-    @channelId = @socialChannel = null
+  unsetSocialChannel: -> @channelId = @socialChannel = null

   deletePrivateMessage: (callback = kd.noop) ->
-
-    {channel}    = kd.getSingleton 'socialapi'
-    {JWorkspace} = remote.api

     socialHelpers.destroyChannel @socialChannel, (err) =>
       return callback err  if err

-      @unsetSocialChannel()
-      workspaceHelpers.detachSocialChannel @workspaceData, callback
+      workspaceHelpers.detachSocialChannel @workspaceData, (err) =>
+        return callback err  if err
+        @unsetSocialChannel()

   # FIXME: This method is called more than once. It should cache the result and


deleting BaseCollection::options.meta

deleting BaseCollection::options.meta

--- shared/base/collection.coffee
+++ shared/base/collection.coffee
@@ -22,7 +22,9 @@
     # Add 'meta' property to store the parts of the response
     # that aren't part of the jsonKey.
     @meta = {}
-    _.extend(@meta, @options.meta) if _.isObject(@options.meta)
+    if _.isObject(@options.meta)
+      _.extend(@meta, @options.meta)
+      delete @options.meta

   # Idempotent parse
   parse: (resp) ->


Revert "Closes #1001. Throw cannot be directly used as an expression with parens, but can appear as a statement in an expression."

This reverts commit 96b22a16eb583af6df02385a9f1b44f235511b7e.

--- nodes.coffee
+++ nodes.coffee
@@ -49,7 +49,7 @@
   # Statements converted into expressions via closure-wrapping share a scope
   # object with their parent closure, to preserve the expected lexical scope.
   compileClosure: (o) ->
-    if @jumps() or this instanceof Throw
+    if @jumps()
       throw SyntaxError 'cannot use a pure statement in an expression.'
     o.sharedScope = yes
     Closure.wrap(this).compileNode o


Fixes #3053 - error for mismatched own/for-in without an index.

--- nodes.coffee
+++ nodes.coffee
@@ -1846,7 +1846,7 @@
     @pattern = @name instanceof Value
     @index.error 'indexes do not apply to range loops' if @range and @index
     @name.error 'cannot pattern match over range loops' if @range and @pattern
-    @index.error 'cannot use own with for-in' if @own and not @object
+    @name.error 'cannot use own with for-in' if @own and not @object
     @returns = false

   children: ['body', 'source', 'guard', 'step']


Tests: Added unlikePost test.

Tests: Added unlikePost test.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -34,6 +34,22 @@
       .click                 selector
       .waitForElementVisible likeElement, 10000
       .assert.containsText   likeElement, user.username + ' liked this.'
+      .end()
+
+
+  unlikePost: (browser) ->
+
+    user = helpers.beginTest(browser)
+    helpers.postActivity(browser, no)
+    selector    = activitySelector + ' [testpath=activity-like-link]'
+    likeElement = activitySelector + ' .like-summary'
+
+    browser
+      .waitForElementVisible    selector, 10000
+      .click                    selector
+      .waitForElementVisible    selector, 10000
+      .click                    selector
+      .waitForElementNotVisible likeElement, 10000
       .end()


convoluted code for displaying links...

convoluted code for displaying links...

--- assets/js/view.coffee
+++ assets/js/view.coffee
@@ -46,21 +46,70 @@
     info.text = x
     line.push info

+  # collect set of words, { word: word, start: start, end: end }
+  words = []
+  word = ''
+  word_start = 0
+
+  lineData.push ' ' # to make end condition easier
+  for char, i in lineData
+    if char == '\n' or char == ' '
+      if i != word_start
+        words.push {
+          word: word
+          start: word_start
+          end: i - 1
+        }
+      word_start = i + 1
+      word = ''
+    else
+      word += char
+
+  urlRegex = /^https?:\/\/[^\s]+\.[^\s]+$/
+  url_words = words.filter (w) ->
+    return urlRegex.test w.word
+
+  for url_word in url_words
+    line[url_word.start].url_start = url_word.word
+    line[url_word.end].url_end = url_word.word
+    # console.log 'url word', url_word
+
   do onto.empty

   acc = ''
   style = defaultStyle
+  url_onto = null
+
   for x in line
     mystyle = defaultStyle
     if x.cursor
       mystyle = 'cursor'
     if x.highlighted
       mystyle = 'highlight'
+
+    if x.url_start
+      if acc.length
+        onto.append $('<span>').html(acc).addClass(style)
+        acc = ''
+      url_onto = $('<a>').attr('href', x.url_start)
+
     if mystyle != style
-      onto.append $('<span>').html(acc).addClass(style)
+      if acc.length
+        if url_onto
+          url_onto.append $('<span>').html(acc).addClass(style)
+        else
+          onto.append $('<span>').html(acc).addClass(style)
+        acc = ''
       style = mystyle
+
+    acc += x.text
+
+    if x.url_end
+      url_onto.append $('<span>').html(acc).addClass(style)
       acc = ''
-    acc += x.text
+
+      onto.append url_onto
+      url_onto = null

   if acc.length
     onto.append $('<span>').html(acc).addClass(style)


Add template msg sending logic

Add template msg sending logic

--- src/line.coffee
+++ src/line.coffee
@@ -69,6 +69,7 @@
         # @robot.logger.debug 'msgObj instanceof SendSticker'
         # @robot.logger.debug msgObj instanceof SendSticker
         if msgObj and msgObj.type
+            # text, sticker, image. audio, video, location, imagemap(X), template
             obj = {
                 "type": msgObj.type
             }
@@ -82,6 +83,8 @@
             obj.previewImageUrl = msgObj.previewImageUrl if msgObj.previewImageUrl?
             obj.duration = msgObj.duration if msgObj.duration?
             obj.text = msgObj.text if msgObj.text?
+            obj.altText = msgObj.altText if msgObj.altText?
+            obj.template = @getTemplate(msgObj.template) if msgObj.template?
             return obj;
         else if typeof msgObj is 'string'
             return {
@@ -89,18 +92,32 @@
                 "text": msgObj
             }

-    # forTesting: (fn) ->
-    #     @reply = fn
-    # _formatTextObj: (token, msg) ->
-    #     return {
-    #         "replyToken": token,
-    #         "messages":[
-    #             {
-    #                 "type": "text",
-    #                 "text": msg
-    #             }
-    #         ]
-    #     }
+    getTemplate: (template) ->
+        # buttons, confirm, carousel
+        obj = {
+            type: template.type
+        }
+        obj.thumbnailImageUrl = msgObj.thumbnailImageUrl if msgObj.thumbnailImageUrl?
+        obj.title = msgObj.title if msgObj.title?
+        obj.text = msgObj.text if msgObj.text?
+        obj.actions = @getActions(msgObj.actions) if msgObj.actions?
+        return obj
+
+    getActions: (actionAry) ->
+        ary = []
+        ary.push @getAction(action) for action in actionAry
+        return ary
+
+    getAction: (action) ->
+        # postback, message, uri
+        obj = {
+            type: action.type
+        }
+        obj.label = msgObj.label if msgObj.label?
+        obj.data = msgObj.data if msgObj.data?
+        obj.text = msgObj.text if msgObj.text?
+        obj.uri = msgObj.uri if msgObj.uri?
+        return obj

     run: ->
         self = @


Use XML parser rather than jQuery to build nodes, so Opera doesn't uppercase element names.

--- web/lib/coffeescripts/session.coffee
+++ web/lib/coffeescripts/session.coffee
@@ -20,7 +20,7 @@
           callback true
           this.findRoster =>
             this.notify('roster')
-            @xmpp.send $('<presence/>').get 0
+            @xmpp.send this.xml '<presence/>'
             this.findCards()

   disconnect: -> @xmpp.disconnect()
@@ -96,12 +96,12 @@
     $('item', node).map(-> new Contact this ).get()

   findRoster: (callback) ->
-    node = $("""
+    node = this.xml """
       <iq id='#{this.uniqueId()}' type="get">
         <query xmlns="jabber:iq:roster"/>
       </iq>
-    """)
-    this.sendIQ node.get(0), (result) =>
+    """
+    this.sendIQ node, (result) =>
       contacts = this.parseRoster(result)
       @roster[contact.jid] = contact for contact in contacts
       callback()
@@ -116,40 +116,40 @@
     @xmpp.send node

   sendPresence: (away, status) ->
-    node = $ '<presence/>'
+    node = $ this.xml '<presence/>'
     if away
-      node.append $('<show>xa</show>')
-      node.append $('<status/>').text status if status != 'Away'
+      node.append $(this.xml '<show>xa</show>')
+      node.append $(this.xml '<status/>').text status if status != 'Away'
     else
-      node.append $('<status/>').text status if status != 'Available'
-    @xmpp.send node.get 0
+      node.append $(this.xml '<status/>').text status if status != 'Available'
+    @xmpp.send node

   sendIQ: (node, callback) ->
     @xmpp.sendIQ node, callback, callback, 5000

   updateContact: (contact, add) ->
-    node = $("""
+    node = this.xml """
       <iq id="#{this.uniqueId()}" type="set">
         <query xmlns="jabber:iq:roster">
           <item name="" jid="#{contact.jid}"/>
         </query>
       </iq>
-    """)
+    """
     $('item', node).attr 'name', contact.name
     for group in contact.groups
-      $('item', node).append $('<group></group>').text group
-    @xmpp.send node.get 0
+      $('item', node).append $(this.xml '<group></group>').text group
+    @xmpp.send node
     this.sendSubscribe(contact.jid) if add

   removeContact: (jid) ->
-    node = $("""
+    node = this.xml """
       <iq id="#{this.uniqueId()}" type="set">
         <query xmlns="jabber:iq:roster">
           <item jid="#{jid}" subscription="remove"/>
         </query>
       </iq>
-    """)
-    @xmpp.send node.get 0
+    """
+    @xmpp.send node

   sendSubscribe: (jid) ->
     @xmpp.send this.presence jid, 'subscribe'
@@ -161,12 +161,12 @@
     @xmpp.send this.presence jid, 'unsubscribed'

   presence: (to, type) ->
-    $("""
+    this.xml """
       <presence
         id="#{this.uniqueId()}"
         to="#{to}"
         type="#{type}"/>
-    """).get 0
+    """

   handleIq: (node) ->
     node = $(node)


Lengthen the time for requirejs to wait for downloads

--- src/main.coffee
+++ src/main.coffee
@@ -1,4 +1,6 @@
 require.config
+	waitSeconds: 20
+
 	paths:
 		jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min'
 		jqm:    'http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min'


removes extra ;

removes extra ;

--- src/main.coffee
+++ src/main.coffee
@@ -60,14 +60,14 @@
     return;

 # No more minimizing to tray, just close it
-global.forceClose = false;
+global.forceClose = false
 quit = ->
-    global.forceClose = true;
+    global.forceClose = true
     app.quit()
     return

 app.on 'before-quit', ->
-    global.forceClose = true;
+    global.forceClose = true
     return

 # For OSX show window main window if we've hidden it.
@@ -79,7 +79,8 @@
     mainWindow.loadURL 'file://' + __dirname + '/ui/index.html'
     # Only show window when it has some content
     mainWindow.once 'ready-to-show', () ->
-        mainWindow.show()
+        if !global.windowHideWhileCred? || global.windowHideWhileCred != true
+            mainWindow.show()
     # short hand

 toggleWindowVisible = ->
@@ -141,6 +142,7 @@

         loginWindow.on 'closed', quit

+        global.windowHideWhileCred = true
         mainWindow.hide()
         loginWindow.focus()
         # reinstate app window when login finishes


implement demo-style minimal functionality to get rid of unwarranted complexities (WIP)


bugfix: sliders constantly updating

bugfix: sliders constantly updating

--- src/main.coffee
+++ src/main.coffee
@@ -50,6 +50,8 @@
   code = textarea.getValue()

   return if !renderOutdated
+
+  renderOutdated = false

   md = markdown(code)
   outputarea.find('.output').html(md)


Remove github emoji script

Remove github emoji script

--- scripts/github.coffee
+++ scripts/github.coffee
@@ -1,42 +0,0 @@
-# Description:
-#   github の API をたたく
-#
-# Commands:
-#   hubot github emoji yum #-> https://assets-cdn.github.com/images/icons/emoji/yum.png?v5
-
-fs = require "fs"
-
-cachefile = "cache/github-emojis.json"
-
-module.exports = (robot) ->
-  robot.respond /(?:github )?emoji(?: me)? (.*)/i, (msg) ->
-    emojiMe msg, (url) ->
-      msg.send url
-
-  robot.respond /github clean/i, (msg) ->
-    clearEmojiCache msg
-
-emojiMe = (msg, cb) ->
-  emoji_arr = msg.match[1]
-    .replace(/^[ ]*/, "").replace(/[ ]*$/, "")
-    .split(/\s+/)
-  if fs.existsSync(cachefile)
-    body = fs.readFile cachefile, (err, data) ->
-      emojis = JSON.parse(data)
-      for emoji in emoji_arr
-        emoji_url = emojis[emoji]
-        cb emoji_url || "ないよ"
-
-  else
-    msg.http('https://api.github.com/emojis')
-      .get() (err, res, body) ->
-        fs.writeFile cachefile, body
-        emojis = JSON.parse(body)
-        for emoji in emoji_arr
-          emoji_url = emojis[emoji]
-          cb emoji_url || "ないよ"
-
-
-clearEmojiCache = (msg) ->
-  fs.unlinkSync cachefile
-  msg.send "Clear GitHub emoji cache!"


Create new arrays.

Create new arrays.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -111,8 +111,8 @@
             pointcount = msg.match[1]

             # Currently only seems to get results from in-chat users at time of command
-            savednames = robot.brain.get "people.username"
-            savedpoints = robot.brain.get "people.username.points"
+            savednames = [robot.brain.get "people.username"]
+            savedpoints = [robot.brain.get "people.username.points"]

             score = ["#{savednames} has #{savedpoints[savednames]}"]


Support single string activationEvents

Support single string activationEvents

--- package.cson
+++ package.cson
@@ -1,3 +1,3 @@
 'main': './lib/command-palette-view'
 'description': 'Find and run available commands.'
-'activationEvents': ['command-palette:toggle']
+'activationEvents': 'command-palette:toggle'


When a url doesn't match any defined route, router will fall to the
default behavior 'controller/action', getting these two as the two first
parameters of the url.

In case it can build a Controller for it, and the render method find the
appropriate View, Model and Template, the screen will get rendered at
body. More studies must to be done around it to better fit the common
scenarios.

--- www/src/theoricus/core/router.coffee
+++ www/src/theoricus/core/router.coffee
@@ -46,7 +46,8 @@
   @param [String] el
   ###
   map:( route, to, at, el )->
-    @routes.push new Route route, to, at, el, @
+    @routes.push route = new Route route, to, at, el, @
+    return route

   route:( state )->

@@ -70,9 +71,21 @@
       # fallback to root url in case user enter the '/'
       url = @Routes.root if url == "/"

+      # search in all defined routes
       for route in @routes
         if route.test url
           return @on_change route, url
+
+      # if none is found, tries to render based on default
+      # controller/action settings
+      [controller_name, action_name] = (url.replace /^\//m, '').split '/'
+
+      @the.factory.controller controller_name, (controller)=>
+        if controller?
+          route = @map url, "#{controller_name}/#{action_name}", null, 'body'
+          return @on_change route, url
+        else
+          console.error 'Route not found for ' + url

     @trigger = true


Fixed #425 removing newly added rows in Dynamic inlines

--- wzk/ui/inlineform/DynamicForm.coffee
+++ wzk/ui/inlineform/DynamicForm.coffee
@@ -1,8 +1,12 @@
+goog.provide 'wzk.ui.inlineform.DynamicForm'
+
+goog.require 'goog.dom.classlist'
+goog.require 'goog.events'
+goog.require 'goog.events.EventType'
 goog.require 'goog.string'
+
 goog.require 'wzk.ui.inlineform.FieldExpert'
 goog.require 'wzk.ui.inlineform.RowBuilder'
-goog.require 'goog.events'
-goog.require 'goog.events.EventType'
 goog.require 'wzk.ui.inlineform.ConfigHandler'

@@ -66,10 +70,14 @@
   ###
   handleRowDelete: (e) =>
     el = (`/** @type {Element} */`) e.target
-    for fileInput in @dom.all wzk.ui.inlineform.DynamicForm.SELECTORS.FILE_INPUT, el
-      @dom.removeNode fileInput
-    goog.style.setElementShown el, false
-    @removeDutyFromHiddenInputs el
+
+    if goog.dom.classlist.contains(el, 'empty')
+      @dom.removeNode(el)
+    else
+      for fileInput in @dom.all(wzk.ui.inlineform.DynamicForm.SELECTORS.FILE_INPUT, el)
+        @dom.removeNode(fileInput)
+      goog.style.setElementShown(el, false)
+      @removeDutyFromHiddenInputs(el)

   ###*
     @protected


Fix display of series identifier (module). Remove leftover debug line.

--- client/room_list/room_list.coffee
+++ client/room_list/room_list.coffee
@@ -24,7 +24,6 @@
       metadata.createdDisplay = created.format "HH:mm"
       serverNow = Session.get 'serverTime'
       duration = serverNow - created.unix()
-      console.log duration
       duration = 0 if duration < 0
       durationMoment = moment.unix(duration)
       durationH = durationMoment.hour()
@@ -45,6 +44,8 @@
         else
           "#{durationM} minutes"
       metadata.durationDisplay = if durationHString then "#{durationHString} #{durationMString}" else "#{durationMString}"
+      if metadata.series_identifier
+        metadata.series_identifier = metadata.series_identifier.replace '__', ':'
     metadata

 Template.rec.rendered = ->


Adding dirs handling in the cleanup routine

--- tests/unit/hooks.coffee
+++ tests/unit/hooks.coffee
@@ -7,6 +7,7 @@
 afterEach ->
   mods = [
     '../../lib/utils/plugins'
+    '../../lib/utils/config'
     '../../lib/core/compiler'
     '../../lib/core/file'
     '../../lib/core/files'
@@ -24,17 +25,28 @@

 # keeps fixtures always unchanged, reverting changes made by tests
-backup = []
+backup_files = []
+backup_dirs = []
+
 before ->
-  for file in fsu.find path.join(__dirname, '..', 'fixtures'), /.+/
-    backup.push path: file, raw: fs.readFileSync(file).toString()
+  for file in fsu.find path.join(__dirname, '..', 'fixtures'), /.+/, true
+    if fs.lstatSync(file).isDirectory()
+      backup_dirs.push file
+    else
+      backup_files.push path: file, raw: fs.readFileSync(file).toString()

 after clean = ->
   # roll back all fiels to its original state
-  for file in backup
+  for file in backup_files
     fs.writeFileSync file.path, file.raw
+
+  # removing created dirs
+  for dir in fsu.find path.join(__dirname, '..', 'fixtures'), /.+/, true
+    continue unless fs.existsSync dir
+    if fs.lstatSync(dir).isDirectory() and dir not in backup_dirs
+      fsu.rm_rf dir

   # remove also newly crated files
   for file in fsu.find path.join(__dirname, '..', 'fixtures'), /.+/
-    unless _.find backup, {path:file}
+    unless _.find backup_files, {path:file}
       fs.unlinkSync file


blur connect log

blur connect log

--- components/connect/client/connect_view.coffee
+++ components/connect/client/connect_view.coffee
@@ -22,6 +22,7 @@
     super

   clear: (e)->
+    console.log 'should clear'
     e.stopPropagation()
     e.preventDefault()
     @$el.html ""


Error message links gain rel="noreferrer" and target="_blank"

--- client/ErrorBoundary.coffee
+++ client/ErrorBoundary.coffee
@@ -13,7 +13,7 @@

           {@state.error.toString()}

-
Please report this bug along with what caused it and the error messages in the developer console.

+
Please report this bug along with what caused it and the error messages and trace in the developer console.

     else
       @props.children


Default options to empty object

Default options to empty object

--- src/rule.coffee
+++ src/rule.coffee
@@ -3,7 +3,7 @@

 module.exports =
 class Rule
-  constructor: (@grammar, @registry, {@scopeName, patterns, @endPattern}) ->
+  constructor: (@grammar, @registry, {@scopeName, patterns, @endPattern}={}) ->
     patterns ?= []
     @patterns = patterns.map (pattern) => @grammar.createPattern(pattern)
     if @endPattern and not @endPattern.hasBackReferences


Client: Remove use of window.load

Client: Remove use of window.load

--- client/app/js/client.coffee
+++ client/app/js/client.coffee
@@ -46,8 +46,7 @@
       collection: PokeBattle.userList
     )
     PokeBattle.chatView.render()
-    $(window).load ->
-      PokeBattle.initializeAuth()
+    PokeBattle.initializeAuth()

   'list chatroom': (socket, users) ->
     PokeBattle.userList.reset(users)


IDE: Change initial load of IDE to have only one terminal full-screen

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -569,8 +569,6 @@
           # After that, get participant's snapshot from collaboration data and build workspace.
           @silent = yes  if @isInSession and not @amIHost and not @mountedMachine.isPermanent()

-          @splitTabView { type: 'horizontal', dontSave: yes }
-
           @addInitialViews()

@@ -1293,8 +1291,7 @@

   addInitialViews: ->

-    @ideViews.first.createEditor()  unless @isNewRegister
-    @ideViews.last.createTerminal { machine: @mountedMachine }
+    @ideViews.first.createTerminal { machine: @mountedMachine }
     @setActiveTabView @ideViews.first.tabView
     @initialViewsReady = yes


Remove fake views as first thing once machine is ready

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -950,6 +950,10 @@

   handleIDEBecameReady: (machine) ->

+    unless @fakeViewsDestroyed
+      @removeFakeViews()
+      @fakeViewsDestroyed = yes
+
     {finderController} = @finderPane
     if @workspaceData
       finderController.updateMachineRoot @mountedMachine.uid, @workspaceData.rootPath
@@ -959,10 +963,6 @@
     machine.getBaseKite().fetchTerminalSessions()

     @fetchSnapshot (snapshot) =>
-
-      unless @fakeViewsDestroyed
-        @removeFakeViews()
-        @fakeViewsDestroyed = yes

       if snapshot
         @resurrectLocalSnapshot snapshot  unless @isLocalSnapshotRestored


IDE: Move layout drawing to mount machine.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -78,9 +78,8 @@

     $('body').addClass 'dark' # for theming

-    appView   = @getView()
-    workspace = @workspace = new IDEWorkspace { layoutOptions }
-    @ideViews = []
+    @workspace = new IDEWorkspace { layoutOptions }
+    @ideViews  = []

     # todo:
     # - following two should be abstracted out into a separate api
@@ -90,29 +89,7 @@
     {windowController} = kd.singletons
     windowController.addFocusListener @bound 'setActivePaneFocus'

-    workspace.once 'ready', =>
-      panel = workspace.getView()
-      appView.addSubView panel
-
-      panel.once 'viewAppended', =>
-        ideView = panel.getPaneByName 'editorPane'
-        @setActiveTabView ideView.tabView
-        @registerIDEView  ideView
-
-        splitViewPanel = ideView.parent.parent
-        @createStatusBar splitViewPanel
-        @createFindAndReplaceView splitViewPanel
-
-        appView.emit 'KeyViewIsSet'
-
-        @createInitialView()
-        @bindCollapseEvents()
-
-        {@finderPane, @settingsPane} = @workspace.panel.getPaneByName 'filesPane'
-
-        @bindRouteHandler()
-        @initiateAutoSave()
-        @emit 'ready'
+    @workspace.once 'ready', -> @getView().addSubView @workspace.getView()

     kd.singletons.appManager.on 'AppIsBeingShown', (app) =>

@@ -128,6 +105,31 @@
       @resizeActiveTerminalPane()

     @localStorageController = kd.getSingleton('localStorageController').storage 'IDE'
+
+
+  prepareIDE: ->
+
+    panel     = @workspace.getView()
+    appView   = @getView()
+    ideView   = panel.getPaneByName 'editorPane'
+
+    @setActiveTabView ideView.tabView
+    @registerIDEView  ideView
+
+    splitViewPanel = ideView.parent.parent
+    @createStatusBar splitViewPanel
+    @createFindAndReplaceView splitViewPanel
+
+    appView.emit 'KeyViewIsSet'
+
+    @createInitialView()
+    @bindCollapseEvents()
+
+    {@finderPane, @settingsPane} = @workspace.panel.getPaneByName 'filesPane'
+
+    @bindRouteHandler()
+    @initiateAutoSave()
+    @emit 'ready'

   bindRouteHandler: ->
@@ -382,6 +384,8 @@
         machineItem = new Machine machine: machineItem

       @mountedMachine = machineItem
+
+      @prepareIDE()

       callback = =>


Do JS setup immediately when possible

Do JS setup immediately when possible

--- cms/static/coffee/src/main.coffee
+++ cms/static/coffee/src/main.coffee
@@ -2,17 +2,16 @@
         "js/views/feedback_notification",
         "coffee/src/ajax_prefix", "jquery.cookie"],
 (domReady, $, str, Backbone, gettext, NotificationView) ->
-  AjaxPrefix.addAjaxPrefix jQuery, ->
-    $("meta[name='path_prefix']").attr('content')
+  main = ->
+    AjaxPrefix.addAjaxPrefix jQuery, ->
+      $("meta[name='path_prefix']").attr('content')

-  window.CMS = window.CMS or {}
-  CMS.URL = CMS.URL or {}
-  window.onTouchBasedDevice = ->
-    navigator.userAgent.match /iPhone|iPod|iPad/i
+    window.CMS = window.CMS or {}
+    CMS.URL = CMS.URL or {}
+    window.onTouchBasedDevice = ->
+      navigator.userAgent.match /iPhone|iPod|iPad/i

-  _.extend CMS, Backbone.Events
-
-  main = ->
+    _.extend CMS, Backbone.Events
     Backbone.emulateHTTP = true

     $.ajaxSetup
@@ -48,8 +47,9 @@
         data: JSON.stringify(data)
         success: callback

-    if onTouchBasedDevice()
-      $('body').addClass 'touch-based-device'
+    domReady ->
+      if onTouchBasedDevice()
+        $('body').addClass 'touch-based-device'

-  domReady(main)
+  main()
   return main


[replays] do not error if no battle id

[replays] do not error if no battle id

--- server/replays.coffee
+++ server/replays.coffee
@@ -26,9 +26,12 @@
     .select('battle_id')
     .then (battleIds) ->
       battleIds = _.pluck(battleIds, 'battle_id')
-      database.Battle
-      .where('battle_id', 'in', battleIds)
-      .fetchAll(columns: ['battle_id', 'players', 'name', 'format', 'created_at'])
+      if battleIds.length > 0
+        database.Battle
+        .where('battle_id', 'in', battleIds)
+        .fetchAll(columns: ['battle_id', 'players', 'name', 'format', 'created_at'])
+      else
+        []
     .then (replays) ->
       res.render('replays/index', bodyClass: 'no-sidebar', replays: replays)
     .catch (err) ->


when setting a username, return whether this was needed, or whether the name stayed the same

--- core/app/backbone/collections/channels.coffee
+++ core/app/backbone/collections/channels.coffee
@@ -33,6 +33,9 @@
     if not @_username? or @_username != name
       @reset([])
       @_username = name
+      return true
+    else
+      return false

   shouldReload: ->
     not (typeof localStorage is "object" and localStorage isnt null and localStorage["reload"] is "false")


Fixed incorrect comment about return value in Expression.coffee

--- coffeequate/src/Expression.coffee
+++ coffeequate/src/Expression.coffee
@@ -43,7 +43,7 @@
 		#
 		# @param variable [String] The variable to solve for.
 		# @param equivalencies [Object] Optional. A map of variable labels to a list of equivalent variable labels.
-		# @return [Expression] A solved Expression representing the variable.
+		# @return [Array<Expression>] An array of Expressions representing the variable for each solution.
 		solve: (variable, equivalencies={}) ->
 			(new Expression(solution) for solution in @expr.solve(variable, equivalencies))


adding <code> tag to this keyword

adding <code> tag to this keyword

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -166,7 +166,7 @@
 ###*
 Call the given iterator once for each element in the array,
 passing in the element as the first argument, the index of
-the element as the second argument, and this array as the
+the element as the second argument, and this array as the
 third argument.

@@ -190,7 +190,7 @@
 @param {Function} iterator Function to be called once for
 each element in the array.
 @param {Object} [context] Optional context parameter to be
-used as `this` when calling the iterator function.
+used as this when calling the iterator function.

 @type Array
 @returns this to enable method chaining.
@@ -207,7 +207,7 @@
 ###*
 Call the given iterator once for each element in the array,
 passing in the element as the first argument, the index of
-the element as the second argument, and this array as the
+the element as the second argument, and this array as the
 third argument.

@@ -222,7 +222,7 @@
 @param {Function} iterator Function to be called once for
 each element in the array.
 @param {Object} [context] Optional context parameter to be
-used as `this` when calling the iterator function.
+used as this when calling the iterator function.
 @type Array
 @returns An array of the results of the iterator function
 being called on the original array elements.


Don’t register defaultTarget for keymaps in constructor after all

We’re clearing the views on reset, so this view will end up being
different between test runs. Also, we didn’t previously always create
the workspace view in many cases, and it makes sense to apply that here.

We may want to get the default target for the keymap via a function so
we can defer view creation until one is needed.

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -195,7 +195,6 @@
     @registerDefaultOpeners()
     @registerDefaultDeserializers()
     @registerDefaultViewProviders()
-    @registerDefaultTargetForKeymaps()

     @installUncaughtErrorHandler()
     @installWindowEventHandler()
@@ -608,6 +607,8 @@
     @disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
     @listenForUpdates()

+    @registerDefaultTargetForKeymaps()
+
     @packages.loadPackages()

     @document.body.appendChild(@views.getView(@workspace))


allow to create files in the tarball from strings

--- src/wrap.coffee
+++ src/wrap.coffee
@@ -96,7 +96,13 @@
         file = path.resolve(dirname, file)
         stream = new BufferStream disabled:yes # no splitting needed
         stream.path = entry.file
-        stream.props = size:opts.size ? fs.statSync(file).size
+        unless opts.size?
+            if path.existsSync(file)
+                opts.size = fs.statSync(file).size
+            else if typeof content is 'string'
+                opts.size = new Buffer(content).length
+            @emit('error', "no size for #{file}") unless opts.size?
+        stream.props = size:opts.size
         @_push (done) ->
             @emit('append', stream)
             @tarball.append(stream, done)


This is more portable.

This is more portable.

--- dashboard/assets/javascripts/routes.js.coffee
+++ dashboard/assets/javascripts/routes.js.coffee
@@ -23,7 +23,7 @@
   requestedUrl: (params) ->
     locationHref = window.location.href

-    if locationHref.endsWith('/') && !params['url'].endsWith('/')
+    if (locationHref.lastIndexOf('/') == locationHref.length - 1) && !(params['url'].endsWith('/'))
       "#{params['url']}/"
     else
       params['url']


cleanup on builtin.sequence to use the new functions

--- src/builtin/sequence.coffee
+++ src/builtin/sequence.coffee
@@ -13,38 +13,38 @@
     new Int @value.length

   __min__: ->
-    new Int @value.reduce (a, b) ->
-      return a if a <= b else b
+    @value.reduce (a, b) ->
+      a if a <= b else b

   __max__: ->
-    new Int @value.reduce (a, b) ->
-      return a if a >= b else b
+    @value.reduce (a, b) ->
+      a if a >= b else b

   __mul__: (operand) ->
     if Int in operand.__class__().__bases__()
       newValue = new type(@)()
       for i in [0...operand.value]
         newValue.__iadd__(@value)
-      return newValue
+      newValue
     else
       super operand

   __slice__: (start, end, step)->
     if not step?
-      return new type(@)(@value.slice(start, end))
+      new (type(@)) @value.slice start, end
     else
       # **To-do**: implementation for `step > 1`
       # Push this up to named argument step=1
-
-  # **To-do**: The above `__contains__` uses a fallback definition of
-  # `Array.prototype.indexOf`.  Any way to trigger this?
-  index: (operand) ->
-    return new Int(Array.prototype.indexOf(@value, operand.value))
-
+
   count: (operand) ->
     total = 0
     for element in @value
       if element.__eq__(operand).value is true
         total += 1

-    return new Int(total)+    new Int total
+
+  # **To-do**: The above `__contains__` uses a fallback definition of
+  # `Array.prototype.indexOf`.  Any way to trigger this?
+  index: (operand) ->
+    new Int Array.prototype.indexOf @value, operand.value


A better way to disable viewer and editor.

--- client/lib/annotator.coffee
+++ client/lib/annotator.coffee
@@ -16,6 +16,11 @@
     delete @options.noScan

   _setupViewer: =>
+    # Overridden and disabled
+
+    @ # For chaining
+
+  _setupEditor: =>
     # Overridden and disabled

   _setupDynamicStyle: =>


Add - summary report method.

Add - summary report method.

--- src/coffee/streaming.coffee
+++ src/coffee/streaming.coffee
@@ -10,6 +10,20 @@
     @apiClient = new ApiClient @logger, options
     @matcher = new Matcher @logger, @apiClient, options
     @actionsToIgnore = options.actionsToIgnore
+    @_resetSummary()
+
+  _resetSummary: =>
+    @_summary =
+      updated: 0
+      created: 0
+
+  summaryReport: =>
+    if @_summary.created is 0 and @_summary.updated is 0
+      message = 'Summary: nothing to do, everything is fine'
+    else
+      message = "Summary: there were #{@_summary.created + @_summary.updated} imported categories " +
+        "(#{@_summary.created} were new and #{@_summary.updated} were updates)"
+    message

   processStream: (chunk, cb) ->
     @_processBatches(chunk)
@@ -19,7 +33,7 @@
     .catch (err) =>
       @logger.error err

-  _processBatches: (categories) ->
+  _processBatches: (categories) =>
     @logger.info "Processing '#{_.size categories}' categor#{if _.size(categories) is 1 then 'y' else 'ies'}"
     batchedList = _.batchList categories, 100
     Promise.map batchedList, (categoryList) =>
@@ -39,6 +53,9 @@
                     # remember id of created category for faster parent match
                     if result.body
                       @matcher.addMapping result.body
+                      switch result.statusCode
+                        when 200 then @_summary.updated++
+                        when 201 then @_summary.created++
                     else
                       @logger.warn result
                     Promise.resolve result


Remove obsolete shim for atom.workspace.getCenter()

Now that the latest stable version of Atom includes atom/atom#13977, we
no longer need this shim.

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -18,9 +18,6 @@
   else
     fs.unlinkSync(_path)

-# TODO: Remove this after atom/atom#13977 lands in favor of unguarded `getCenter()` calls
-getCenter = -> atom.workspace.getCenter?() ? atom.workspace
-
 getOrScheduleUpdatePromise = ->
   new Promise((resolve) -> etch.getScheduler().updateDocument(resolve))

@@ -801,7 +798,7 @@

   describe "opening a path into a split", ->
     it "opens the path by splitting the active editor left", ->
-      expect(getCenter().getPanes().length).toBe 1
+      expect(atom.workspace.getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -813,18 +810,18 @@
         atom.commands.dispatch bufferView.element, 'pane:split-left'

       waitsFor ->
-        getCenter().getPanes().length is 2
+        atom.workspace.getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [leftPane, rightPane] = getCenter().getPanes()
+        [leftPane, rightPane] = atom.workspace.getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe leftPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)

     it "opens the path by splitting the active editor right", ->
-      expect(getCenter().getPanes().length).toBe 1
+      expect(atom.workspace.getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -836,18 +833,18 @@
         atom.commands.dispatch bufferView.element, 'pane:split-right'

       waitsFor ->
-        getCenter().getPanes().length is 2
+        atom.workspace.getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [leftPane, rightPane] = getCenter().getPanes()
+        [leftPane, rightPane] = atom.workspace.getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe rightPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)

     it "opens the path by splitting the active editor up", ->
-      expect(getCenter().getPanes().length).toBe 1
+      expect(atom.workspace.getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -859,18 +856,18 @@
         atom.commands.dispatch bufferView.element, 'pane:split-up'

       waitsFor ->
-        getCenter().getPanes().length is 2
+        atom.workspace.getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [topPane, bottomPane] = getCenter().getPanes()
+        [topPane, bottomPane] = atom.workspace.getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe topPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)

     it "opens the path by splitting the active editor down", ->
-      expect(getCenter().getPanes().length).toBe 1
+      expect(atom.workspace.getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -882,13 +879,13 @@
         atom.commands.dispatch bufferView.element, 'pane:split-down'

       waitsFor ->
-        getCenter().getPanes().length is 2
+        atom.workspace.getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [topPane, bottomPane] = getCenter().getPanes()
+        [topPane, bottomPane] = atom.workspace.getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe bottomPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)


Resetting frozen events upon resetting the context

--- framer/Context.coffee
+++ framer/Context.coffee
@@ -73,6 +73,7 @@
 		@_createDOMEventManager()
 		@_createRootElement()

+		@resetFrozenEvents()
 		@resetLayers()
 		@resetAnimations()
 		@resetTimers()
@@ -148,6 +149,8 @@
 		return unless @_animations
 		@_animations.map (animation) -> animation.stop(true)

+	resetFrozenEvents: ->
+		delete @_frozenEvents

 	# Timers
 	@define "timers", get: -> _.clone(@_timers)
@@ -237,7 +240,7 @@
 				for listener in listeners
 					layer.on(eventName, listener)

-		delete @_frozenEvents
+		@resetFrozenEvents()

 	##############################################################


updating to sync with cache-render

updating to sync with cache-render

--- client/routes/router.coffee
+++ client/routes/router.coffee
@@ -21,6 +21,7 @@
 		this.layout('loginLayout')
 		this.render('loginForm')
 	else
+		Session.set 'openedRoom', null
 		this.next()

 Router.onBeforeAction ->
@@ -74,8 +75,9 @@
 		if not ChatRoom.find(@params._id).count()
 			Router.go 'home'

-		Session.set('openedRoom', this.params._id)
-		Session.set('showUserInfo', null)
+		Session.set 'openedRoom', @params._id
+		Meteor.call 'readMessages', @params._id
+		KonchatNotification.removeRoomNotification @params._id

 		this.next()


Remove id "card-element" as well

Remove id "card-element" as well

--- app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee
+++ app/assets/javascripts/admin/payments/directives/stripe_elements.js.coffee
@@ -1,9 +1,9 @@
 angular.module('admin.payments').directive "stripeElements", ($injector, AdminStripeElements) ->
   restrict: 'E'
-  template: "\
-

\
+  template: "
\
+

\

\
-             "
+
"
   scope:
     selected: "="


Update MCAdmin.Uploader.autoInit

Update MCAdmin.Uploader.autoInit

--- app/assets/javascripts/martian_components/components_admin/uploader.coffee
+++ app/assets/javascripts/martian_components/components_admin/uploader.coffee
@@ -1,6 +1,13 @@
 class @MCAdmin.Uploader
   @autoInit: ->
-    $('.fileinput-button input[type="file"]').each (i, el) => new @($(el))
+    init = =>
+      $('.fileinput-button input[type="file"]').each (i, el) => new @($(el))
+
+    $(document).on 'ajaxComplete', (ev, xhr) =>
+      return false unless $(xhr.responseJSON?.component_form).length
+      init()
+
+    init()

   constructor: (@input) ->
     @form             = @input.parents('form')


EDSC-359 clear datepicker selections when pressing dropdown clear button

--- app/assets/javascripts/modules/temporal/temporal_range_selection.js.coffee
+++ app/assets/javascripts/modules/temporal/temporal_range_selection.js.coffee
@@ -83,9 +83,7 @@
       validateTemporalInputs(root)
       $input.trigger('change')

-    # TODO: clear button in the dropdown needs to trigger clear button for the datepicker
     # TODO set the end time to 23:59:59 if the user doesn't enter a time
-    # TODO recurring temporal
     root.find('.temporal-range-picker').datepicker
       format: "yyyy-mm-dd"
       startDate: "1960-01-01"
@@ -202,6 +200,8 @@

   $(document).on 'click', '.temporal-filter .temporal-clear', ->
     validateTemporalInputs($(this).closest('.temporal-filter'))
+    # Clear datepicker selection
+    $('.temporal-range-picker').datepicker('update', '')

   # safe global stuff
   $(document).on 'click', '.xdsoft_today_button, button.xdsoft_prev, button.xdsoft_next', ->


Fix tests

Fix tests

--- app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee
+++ app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee
@@ -38,7 +38,14 @@
             controller = $controller

     it "toggle table options", () ->
-        epicTableCtrl = controller "EpicsTableCtrl"
+        data = {
+            project: {
+                my_permissions: [
+                    'modify_epic'
+                ]
+            }
+        }
+        epicTableCtrl = controller "EpicsTableCtrl", null, data
         epicTableCtrl.displayOptions = true
         epicTableCtrl.toggleEpicTableOptions()
         expect(epicTableCtrl.displayOptions).to.be.false


Simplify API api

Simplify API api

--- server/api.coffee
+++ server/api.coffee
@@ -25,13 +25,11 @@
 # * url
 # * params
 # * cache
-get = (options) ->
-  {url, params, cache} = options
-  url = buildUrl url, params
-  console.log url
-  if cache? && (cached = cache.get url)?
-    return Q cached
+get = (url, params, cache) ->
   Q.Promise (resolve, reject) ->
+    url = buildUrl url, params
+    if cache? && (cached = cache.get url)?
+      return resolve cached
     request url: url, json: true, (err, response, data) ->
       if err?
         reject err
@@ -47,7 +45,4 @@
     @cache = new Cache options

   get: (params) ->
-    get
-      url: @url
-      params: extend {}, @params, params
-      cache: @cache
+    get @url, (extend {}, @params, params), @cache


Remove basicAuth from express (added to nginx instead)

--- server/app.coffee
+++ server/app.coffee
@@ -21,7 +21,6 @@
   @db = mongoose.connect process.env.MONGO_URI or 'mongodb://localhost/'
   @models = require('./models')(@db)

-  @use express.basicAuth 'u2', 'b1tr4t3d'
   @use express.favicon()
   @use express.logger 'dev'
   @use express.json()


Remove pointless comment

Remove pointless comment

--- resources/assets/coffee/_classes/forum.coffee
+++ resources/assets/coffee/_classes/forum.coffee
@@ -54,7 +54,6 @@

   setTotalPosts: (n) =>
     @_totalPostsDiv[0].setAttribute('data-total-count', n)
-    # document.getElementsByClassName('js-forum__total-count')[0].textContent = n
     $('.js-forum__total-count').text(n)


Team: changed Alice's title

Team: changed Alice's title

--- client/About/team.coffee
+++ client/About/team.coffee
@@ -3,7 +3,6 @@
   team            :

     active        : [
-
         username  : 'devrim'
         title     : 'CEO & Co-Founder'
       ,
@@ -65,7 +64,7 @@
         title     : 'VP of Engineering'
       ,
         username  : 'alice'
-        title     : 'Chef de Cuisine'
+        title     : 'Senior Food Engineer'
       ]

     # suspended     : []


syntax improvements

syntax improvements

--- maskew.coffee
+++ maskew.coffee
@@ -2,34 +2,33 @@
 # Skew the shapes of DOM elements
 # Dan Motzenbecker
 # http://oxism.com
-# 0.1.3
+# 0.1.4
 # MIT License

 {cos, sin, PI, abs, round} = Math

-rad = (deg) -> deg * PI / 180
+rad       = (deg) -> deg * PI / 180

 getMetric = (style, key) -> parseInt style[key], 10

 transform = (y, angle) -> "translate3d(0, #{ y }px, 0) rotate3d(0, 0, 1, #{ angle }deg)"

-testProp = (prop) ->
+testProp  = (prop) ->
   return prop if testEl.style[prop]?
-  capProp = prop.charAt(0).toUpperCase() + prop.slice 1
   for prefix in prefixList
-    return key if testEl.style[(key = prefix + capProp)]?
+    return key if testEl.style[(key = prefix + prop.charAt(0).toUpperCase() + prop.slice 1)]?
   false
+

 hasSupport = true
 testEl     = document.createElement 'div'
 prefixList = ['Webkit', 'Moz', 'ms']

-
-css =
-  transform:      'transform'
-  origin:         'transformOrigin'
-  transformStyle: 'transformStyle'
+css = new ->
+  @[key] = key for key in 'transform transformOrigin transformStyle'.split ' '
+  @
+

 for key, value of css
   css[key] = testProp value
@@ -70,10 +69,10 @@
       @_outerMask.style.display = elStyle.display

     @_innerMask = @_outerMask.cloneNode false
-    @_innerMask.style[css.origin] = 'bottom left'
+    @_innerMask.style[css.transformOrigin] = 'bottom left'

     @_holder = @_outerMask.cloneNode false
-    @_holder.style[css.origin] = 'inherit'
+    @_holder.style[css.transformOrigin] = 'inherit'

     for side in ['Top', 'Right', 'Bottom', 'Left'] then do (key = 'margin' + side) =>
       @_outerMask.style[key] = elStyle[key]


let the user know whether the permissions are updated or not

--- client/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
+++ client/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
@@ -114,6 +114,12 @@
             log 'updated permissions',err,res
             @buttons["Save"].hideLoader()
             # TODO: do something with this callback
+            unless err
+              new KDNotificationView
+                title : "Group permissions have been updated."
+            else
+              new KDNotificationView # should change to KD.showError when merged
+                title : "An error occured while updating permissions."

     options.fields or= optionizePermissions roles, permissionSet
     super options,data


webterm: Added control codes \e[I and \e[Z (tab cursor movements).

--- client/app/Applications/WebTerm.kdapplication/src/ControlCodeReader.coffee
+++ client/app/Applications/WebTerm.kdapplication/src/ControlCodeReader.coffee
@@ -207,6 +207,7 @@
           "D": (params) -> terminal.cursor.move -(params[0] ? 1), 0
           "G": (params) -> terminal.cursor.moveTo (params[0] ? 1) - 1, terminal.cursor.y
           "H": (params) -> terminal.cursor.moveTo (params[1] ? 1) - 1, getOrigin() + (params[0] ? 1) - 1
+          "I": (params) -> terminal.cursor.moveTo (Math.floor(terminal.cursor.x / 8) + (params[0] ? 1)) * 8, terminal.cursor.y unless params[0] == 0
           "J": switchParameter 0
             0: ->
               terminal.writeEmptyText terminal.sizeX - terminal.cursor.x
@@ -225,6 +226,7 @@
           "S": (params) -> terminal.screenBuffer.scroll (params[0] ? 1)
           "T": (params) -> terminal.screenBuffer.scroll -(params[0] ? 1)
           "X": (params) -> terminal.writeEmptyText params[0] ? 1
+          "Z": (params) -> terminal.cursor.moveTo (Math.ceil(terminal.cursor.x / 8) - (params[0] ? 1)) * 8, terminal.cursor.y unless params[0] == 0
           "c": switchRawParameter 0
             0:   -> terminal.server.controlSequence "\x1B[>?1;2c"
             ">": -> terminal.server.controlSequence "\x1B[>0;261;0c"


prevent passport failure redirects from going to view that no longer exists

--- routes/index.coffee
+++ routes/index.coffee
@@ -26,7 +26,7 @@
   scope: ['email', 'user_location']
 )
 router.get '/auth/facebook/callback', passport.authenticate('facebook',
-  failureRedirect: '/login'
+  failureRedirect: '/'
 ), (req, res) ->
   res.redirect req.session.returnTo or '/'

@@ -35,14 +35,14 @@
   scope: 'profile email'
 )
 router.get '/auth/google/callback', passport.authenticate('google',
-  failureRedirect: '/login'
+  failureRedirect: '/'
 ), (req, res) ->
   res.redirect req.session.returnTo or '/'

 # twitter OAuth
 router.get '/auth/twitter', passport.authenticate('twitter')
 router.get '/auth/twitter/callback', passport.authenticate('twitter',
-  failureRedirect: '/login'
+  failureRedirect: '/'
 ), (req, res) ->
   res.redirect req.session.returnTo or '/'


Camera distance formatting tweak.

Camera distance formatting tweak.

--- views/nearby.coffee
+++ views/nearby.coffee
@@ -16,7 +16,7 @@
                 li class: 'ui-li ui-li-static ui-body-c', 'data-theme': 'c', ->
                     h3 class: 'ui-li-heading', '${obj.name}'
                     p class: 'ui-li-desc', ->
-                        strong '${dis.toFixed(2)} miles away'
+                        '${dis.toFixed(2)} miles away'
                         # TODO: Display formatted time here.  See
                         # prod/../nearby.coffee
                     # Paragraph avoids jQM adding a -has-thumb class to the


I should add better integration test for the board...

--- app/assets/javascripts/board.coffee
+++ app/assets/javascripts/board.coffee
@@ -528,7 +528,16 @@
   player.board.draw_fake_stone dot
   window.pendding_move = ->
     player.board.on_dot_click(dot)
-    move[player.board.color_in_turn.toUpperCase()] = dot.name
+    if dot.owner != 'e'
+      move[dot.owner.toUpperCase()] = dot.name
+    else
+      turn = player.board.color_in_turn
+      n = null
+      if turn is 'w'
+        n = 'B'
+      else
+        n = 'W'
+      move[n] = dot.name
     player.parser.update_game move

     if black_player is current_player


Fix issue where scroll content was smaller than scrollcomponent

--- framer/Components/ScrollComponent.coffee
+++ framer/Components/ScrollComponent.coffee
@@ -91,7 +91,10 @@
 		# Calculates the size of the content. By default this returns the total
 		# size of all the content layers based on width and height. You can override
 		# this for example to take scaling into account.
-		@content.contentFrame()
+		frame = @content.contentFrame()
+		frame.width = @width if frame.width < @width
+		frame.height = @height if frame.height < @height
+		return frame

 	setContentLayer: (layer) ->

@@ -119,7 +122,7 @@
 		contentFrame.y += @_contentInset.top
 		@content.frame = contentFrame

-		constraintsFrame = @content.contentFrame()
+		constraintsFrame = @calculateContentSize()
 		constraintsFrame =
 			x: -constraintsFrame.width  + @width - @_contentInset.right
 			y: -constraintsFrame.height + @height - @_contentInset.bottom


Return when calling callback

Return when calling callback

--- app/coffee/ConversionManager.coffee
+++ app/coffee/ConversionManager.coffee
@@ -61,7 +61,7 @@

 				if !lastCompressedUpdate?
 					# No saved versions, no raw updates, nothing to do
-					callback()
+					return callback()

 				compressedUpdates = [lastCompressedUpdate]
 				for rawUpdate in rawUpdates


increase lock timeouts for archiving

increase lock timeouts for archiving

--- app/coffee/DocArchiveManager.coffee
+++ app/coffee/DocArchiveManager.coffee
@@ -6,6 +6,11 @@
 _ = require "underscore"
 async = require "async"
 settings = require("settings-sharelatex")
+
+# increase lock timeouts because archiving can be slow
+LockManager.LOCK_TEST_INTERVAL = 500 # 500ms between each test of the lock
+LockManager.MAX_LOCK_WAIT_TIME = 30000 # 30s maximum time to spend trying to get the lock
+LockManager.LOCK_TTL = 30 # seconds

 module.exports = DocArchiveManager =


default joined_at to empty string

default joined_at to empty string

--- coach/src/user/model.coffee
+++ coach/src/user/model.coffee
@@ -59,9 +59,7 @@
   getCourse: (collectionUUID) ->
     _.chain(@courses)
       .where( ecosystem_book_uuid: collectionUUID )
-      .sortBy( (course) ->
-        course.getRole().joined_at
-      )
+      .sortBy( (course) -> course.getRole()?.joined_at or '' )
       .last()
       .value()


Make header blank bg

Make header blank bg

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -25,7 +25,7 @@

 COMMAND_SIZE = my.button.size * 0.8
 COMMAND_SPACE = COMMAND_SIZE + my.margin
-COMMAND_BORDER = '2px dotted grey'
+COMMAND_BORDER = "2px solid #{my.color.row}"
 COMMAND_SELECTED = '2px solid goldenrod'
 COMMAND_AUTHORITY = {
   height: COMMAND_SIZE
@@ -77,7 +77,10 @@
     program.put '_AUTHORITY', render_row # TODO: Fix width
     program.authority = render_command
     program.map_children (command) ->
-     command.put '_AUTHORITY', render_command
+     command.put '_AUTHORITY', render_command
+     if command.label() == program.label()
+       command.put 'fill', my.color.row
+       command.put 'border', ""

 set_current = (world, current) ->
   world.put('current', current) if current?


fix commandRunner error to match dockerRunner

--- services/clsi/app/coffee/CommandRunner.coffee
+++ services/clsi/app/coffee/CommandRunner.coffee
@@ -27,8 +27,8 @@
 				err = new Error("terminated")
 				err.terminated = true
 				return callback(err)
-			else if code
-				err = new Error("exit")
+			else if code is 1 # exit status from chktex
+				err = new Error("exited")
 				err.code = code
 				return callback(err)
 			else


Rewrote Kernel.load() to delete the existing instance from the Node.js require cache.

--- cms/System/Kernel.coffee
+++ cms/System/Kernel.coffee
@@ -42,8 +42,9 @@
   hook: (hookName, callback) ->
     throw new NotImplementedError "Kernel.hook - Not implemented."

-  load: (module) ->
-    require("../" + module)
+  load: (mname) ->
+    delete require.cache[require.resolve mname]
+    return require mname

   getSiteName: () ->
     "A CoffeeNode CMS site"


disable contorl { and } for tab switching using the keyboard -- somewhat broken, and very annoying on European keyboards, which have a different mapping

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -544,7 +544,8 @@
             #console.log("editor keyup")
             @project_page.display_tab("project-file-listing")
             return false
-        else if ev.ctrlKey
+        else if window.tab_switching and ev.ctrlKey  # note: window.tab_switching is for testing or if anybody complains (unlikely)
+            # this functionality (1) seems broken, and (2) is \ on a german keyboard
             #console.log("mod ", ev.keyCode)
             if ev.keyCode == 219    # [{
                 @switch_tab(-1)


Get rid of `do` syntax in coffeescript

Get rid of `do` syntax in coffeescript

--- rembrant.coffee
+++ rembrant.coffee
@@ -1,5 +1,5 @@
 # Rembrant - photo gallery software
-# (c) 2011 - Honza Pokorny - All rights reserved
+# (c) 2012 - Honza Pokorny - All rights reserved
 # Freely available under the terms of the BSD license
 # https://github.com/honza/rembrant

@@ -34,7 +34,7 @@
   constructor: (filename) ->
     if not path.existsSync filename
       console.log "Path doesn't exist, creating..."
-      do @createLibrary
+      @createLibrary()
       return

     @library = fs.readFileSync filename, 'utf-8'
@@ -85,13 +85,13 @@

     @library.photos = photos

-    do @ensureExif
-    # do @makeThumbs
+    @ensureExif()
+    @makeThumbs()

   ensureExif: ->
     async.concatSeries @library.photos, exif.readExifImage, (err, list) =>
       console.log 'done'
-      do @save
+      @save()

   scan: ->
     files = fs.readdirSync @library.source
@@ -131,8 +131,7 @@
     template = fs.readFileSync __dirname + "/views/single.html", "utf-8"
     for photo in @library.photos
       html = eco.render template, photo: photo
-      fs.writeFileSync
-      @finalizeFile html, "build/#{do photo.getHtmlName}"
+      @finalizeFile html, "build/#{photo.getHtmlName()}"

   finalizeFile: (content, filename) ->
     rendered = eco.render baseTemplate, content: content
@@ -143,9 +142,9 @@
       console.log 'done'

   export: ->
-    do @generateIndex
-    do @generateAlbums
-    do @generatePages
+    @generateIndex
+    @generateAlbums
+    @generatePages

 program = require 'commander'
@@ -157,13 +156,13 @@
 program.option '-t, --thumbs'
 program.parse process.argv

-r = new Rembrant 'library.json'
+rembrant = new Rembrant 'library.json'

 if program.thumbs
-  do r.makeThumbs
+  rembrant.makeThumbs()

 if program.export
-  do r.export
+  rembrant.export()

 if program.import
-  do r.importPhotos
+  rembrant.importPhotos()


Add support for new inline code format

Add support for new inline code format

--- grammars/weave_md.cson
+++ grammars/weave_md.cson
@@ -1,11 +1,11 @@
 'name': 'Weave.jl markdown'
-scopeName: 'source.weave.md'
+'scopeName' : 'source.weave.md'
 'fileTypes': [
   'jmd'
   'jmdw'
   'mdw'
 ]
-patterns: [
+'patterns': [
     {
       'include' : 'source.weave.noweb'
     }
@@ -35,6 +35,20 @@
     ]
     }
     {
+      'begin' : '^(!)\\s(.*)'
+      'beginCaptures':
+        '1' :
+            'name' : 'markup.bold.weave.md'
+        '2':
+          'contentName': 'source.embedded.julia'
+          'patterns' : [
+                  {
+                    'include': 'source.julia'
+                  }
+                ]
+      'end' : '$'
+    }
+    {
       'include': 'source.gfm'
     }
     {


Add products and prepaids to User schema for consistency with codecombat

--- app/schemas/models/user.ozar.coffee
+++ app/schemas/models/user.ozar.coffee
@@ -358,6 +358,7 @@
       endDate: c.stringDate()
       type: { type: ['string', 'null'] }
       includedCourseIDs: { type: ['array', 'null'], description: 'courseIDs that this prepaid includes access to', items: c.objectId() }
+      migrated: {type: 'boolean'}
     }
   }
   enrollmentRequestSent: { type: 'boolean', description: 'deprecated' }
@@ -418,6 +419,35 @@
         additionalProperties: FeatureRecipientSchema

   archived: c.date {description: 'Marks this record for automatic online archiving to cold storage by our cloud database.'}
+  products: c.array {title: 'Products purchased or used by this user'},
+    c.object { required: ['product', 'startDate', 'recipient', 'paymentService', 'paymentDetails'],  additionalProperties: true },
+      # ensure we can add additionalProperties
+      product: { type: 'string', enum: ['course', 'basic_subscription', 'pd', 'ai-league', 'online-classes'], decription: 'The "name" field for the product purchased' }  # And/or the ID of the Product in the database, if we make a Product for each thing we can buy?
+
+      prepaid: c.objectId(links: [ {rel: 'db', href: '/db/prepaid/{($)}'} ])  # required for type: “course” for legacy compatibility, optional for other types
+      productOptions:
+        oneOf: [
+          includedCourseIDs: {type: ['array', 'null']}
+        ]
+      startDate: c.date()
+      endDate: c.date()  # TODO: optional indication of no end date (lasts forever) - or do we just leave unset?
+      purchaser: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ]) # in case of gifts
+      recipient: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
+      purchaserDesc:
+        detailType: enum: ['email', 'phone']
+        detail: c.shortString(description: 'We may have a purchaser with no account, in which case only this email/phone/... will be set')
+      paymentService: { enum: ['stripe', 'testing', 'free', 'api', 'external', 'paypal']}  # Removed 'ios', could perhaps remove 'paypal', could differentiate 'external' further
+      paymentDetails:
+        c.object {additionalProperties: true},
+          purchaseDate: c.date()  # TODO: separate payment date and invoice date (esp. online classes)?
+          amount: { type: 'integer', description: 'Payment in cents on US server and in RMB cents on the China server' }
+          # Do we need something about autorenewal / frequency here?
+          oneOf: [
+            { stripeCustomerId: { type: 'string' }, subscriptionId: { type: 'string' }, paymentSession: c.objectId(links: [ {rel: 'extra', href: '/db/payment.session/{($)}'} ]) }  # TODO: other various Stripe-specific options
+            { paypalCustomerId: { type: 'string' } }  # TODO: various PayPal-specific options, if we keep PayPal
+            { staffCreator: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ]) }  # any other external payment source options?
+            # ... etc. for each possible payment service ...
+          ]

 c.extendBasicProperties UserSchema, 'user'


fix bug

fix bug

--- src/coffee/smart-complete.coffee
+++ src/coffee/smart-complete.coffee
@@ -70,7 +70,7 @@
             completorPos.left = if sepPos.top isnt completorPos.top then @inputor.caret('position', 0).left else Math.max(sepPos.left, parseInt(@inputor.css('paddingLeft'), 10))
             @completorWrap.css
               top:  (if @type is 'input' then inputorPos.top + @inputor.outerHeight() else completorPos.top + completorPos.height + inputorPos.top + 8) + 'px'
-              left: Math.min(completorPos.left + inputorPos.left, inputorPos.left + @inputor.outerWidth() - @width - 2)  + 'px'
+              left: Math.max(inputorPos.left,  Math.min(completorPos.left + inputorPos.left, inputorPos.left + @inputor.width() + parseInt(@inputor.css('paddingLeft')) + parseInt(@inputor.css('paddingRight')) - @width - 2))  + 'px'
             if @lastSearchStr isnt sv
               scope.results = []
               scope.$apply()