Move volatile log parsing result usage to deeper scope

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -38,9 +38,9 @@
     proc = builder.run args, (statusCode) =>
       @destroyProgressIndicator()
       result = builder.parseLogFile(rootFilePath)
-      @pdfFile = result.outputFilePath
       switch statusCode
         when 0
+          @pdfFile = result.outputFilePath
           @moveResult(result, rootFilePath) if @shouldMoveResult()
           @showResult(result)
         when 127 then @showError \


SSE improvement

SSE improvement

--- src/rpc.coffee
+++ src/rpc.coffee
@@ -17,7 +17,7 @@
     log "#{msg.id} in", msg
     if msg.method is cnf.sse # SSE Support
       if typeof @local[cnf.sse] is 'function' # channel opens, cnf.sse is called
-        _return msg, rep:uid = "r-#{Number new Date()}", res
+        _return msg, rep:uid = "r-#{cnf.random()}", res
         @local[cnf.sse] new Remote(@local, msg), uid
       else _return msg, err:"error: no _remoteReady method for channel #{msg.args[0]}", res
     else if msg.method is cnf.srv
@@ -46,6 +46,7 @@
   constructor: (classes, @timeOut = cnf.sessionTimeOut) -> # list of classes that the server can instantiate
     for Class of classes when typeof classes[Class] is 'function'
       @["def #{Class}"] = Class:classes[Class], sessions:[] # 'def Class' to allow Class = process
+    @["def #{cnf.p2p}"] = Class:require('./p2p'), sessions:[]

   process: (Class, msg, res) ->
     uid = getSession msg # get session ID
@@ -80,7 +81,6 @@
   server = new classServer classes, options.timeOut
   app.use json
   app.use "/#{encodeURIComponent cnf.p2p}", (req, res) -> server.process cnf.p2p, req.body, res
-  # TODO uriEncode Class to prevent funny class names
   ( (Class) ->
     log "listening on class #{Class}"
     app.use "/#{encodeURIComponent Class}", (req, res) -> server.process Class, req.body, res
@@ -92,6 +92,8 @@

 class Remote
   constructor: (local, msg) ->
+    local[sseChannel] = new ChannelQ() # add a queue to the remote object
+    @_sseChannel = Channel.channels[msg.args[0]] # set _sseChannel so that it can be closed
     count = 0; uid = getSession msg # send message with same session ID

     ( (method) => @[method] = => send @_sseChannel, local[sseChannel], method:method, args:[].slice.call(arguments), id:"#{uid}-s#{++count}"
@@ -105,22 +107,23 @@
 class Channel
   @channels:[]
   constructor: (req, @resp, next) ->
-    Channel.channels[@uid = "c-#{Number new Date()}"] = @
+    Channel.channels[@uid = "c-#{cnf.random()}"] = @
     @resp.statusCode = 200
     @resp.setHeader 'Content-Type', 'text/event-stream'
     @resp.setHeader 'Cache-Control', 'no-cache'
     @resp.setHeader 'Connection', 'keep-alive'
     @resp.setHeader 'Access-Control-Allow-Origin', '*'
+    log "SSE out #{@uid}", msg = uid:@uid
+    @resp.write "event: #{cnf.tag}\ndata: #{JSON.stringify msg}\n\n"
+    next() if next
     req.on 'close', =>
       log 'SSE', @uid, 'closed'
       delete Channel.channels[@uid]
       @closed = true
-    @send uid:@uid, id:'SSE'
-    next() if next

   send: (msg) ->
     log "#{msg.id} out #{@uid}", msg
-    @resp.write "event: #{cnf.tag}\ndata: #{JSON.stringify msg}\n\n"
+    @resp.write "event: #{cnf.tag}/#{@uid}\ndata: #{JSON.stringify msg}\n\n"

 class ChannelQ
   constructor: -> @queue = []


[neo4j] added fetchActivitiesByTimestamp method

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -93,15 +93,35 @@
 authenticationFailed = (res, err)->
   res.send "forbidden! (reason: #{err?.message or "no session!"})", 403

-Graph = require "./graph/graph"
-GraphDecorator = require "../../../utils/graph/graph_decorator"
-
-app.get "/-/cache/latest", (req, res)->
+
+_fetchActivitiesByTimestamp = (req, res)->
+  Graph = require "./graph/graph"
+  GraphDecorator = require "./graph/graph_decorator"
+
   graph = new Graph neo4j
-  graph.fetchAll (err, respond)->
+  #calculate the current and next timestamp
+  timestamp = req.params?.timestamp
+  if not timestamp
+    timestamp = new Date
+
+  startDate = new Date timestamp
+  #20*60*1000 = 1200000
+  endDate = new Date(startDate.getTime() + 1200000);
+
+  graph.fetchAll startDate.toISOString(), endDate.toISOString(), (err, respond)->
     # res.send respond
     GraphDecorator.decorateToCacheObject respond, (decorated)->
       res.send decorated
+
+app.get "/-/cache/latest", (req, res)->
+  _fetchActivitiesByTimestamp req, res
+
+app.get "/-/cache/before/:timestamp", (req, res)->
+  console.log "dfasdf"
+  console.log  req.params
+  console.log "d12341324"
+
+  _fetchActivitiesByTimestamp req, res

 app.get "/-/cache/apps", (req, res)->
   graph = new Graph neo4j
@@ -127,11 +147,12 @@
     # console.log "latest: #{Date.now() - startTime} msecs!"
     return res.send if cache then cache.data else {}

-app.get "/-/cache/before/:timestamp", (req, res)->
+app.get "/-/oldcache/before/:timestamp", (req, res)->
   {JActivityCache} = koding.models
   JActivityCache.before req.params.timestamp, (err, cache)->
     if err then console.warn err
     res.send if cache then cache.data else {}
+

 app.get "/-/imageProxy", (req, res)->
   if req.query.url


Main: hide reconnected modal

Main: hide reconnected modal

--- client/Main/main.coffee
+++ client/Main/main.coffee
@@ -37,11 +37,12 @@
     clearTimeout modalTimerId
     modalTimerId = null

-    modalSize  = currentModalSize or options.modalSize
-    notifyUser = options.notifyUser
+    # hide reconnected modal
+    # modalSize  = currentModalSize or options.modalSize
+    # notifyUser = options.notifyUser

-    if notifyUser or currentModal
-      currentModal = showModal modalSize, state
+    # if notifyUser or currentModal
+    #   currentModal = showModal modalSize, state

   status.on 'disconnected', (options={})->
     reason     = options.reason     or= "unknown"


updated atom config

updated atom config

--- atom/config.cson
+++ atom/config.cson
@@ -1,5 +1,10 @@
 "*":
   core:
     telemetryConsent: "limited"
+  editor:
+    showIndentGuide: true
+    softWrap: true
   "exception-reporting":
     userId: "23b098d2-0be9-4130-941f-408e814f6fc5"
+  welcome:
+    showOnStartup: false


Update config.cson for Atom

Update config.cson for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -6,14 +6,6 @@
       "vendor"
     ]
     disabledPackages: [
-      "center-line"
-      "open-in-atom"
-      "terminal"
-      "merge-conflicts"
-      "terminal-panel"
-      "git-projects"
-      "outdoor-theme"
-      "error-status"
     ]
     destroyEmptyPanes: false
   editor:


Remove DPSIR attributes as they are not applicable on this branch

--- server/test/units/theme.coffee
+++ server/test/units/theme.coffee
@@ -183,11 +183,13 @@
     indicatorAttributes = [{
       title: "I'm an indicator of theme 1"
       theme: themes[0]._id
-      dpsir: pressure: true
+      indicatorDefinition:
+        unit: "$"
     }, {
       title: "I'm also an indicator of theme 1"
       theme: themes[0]._id
-      dpsir: state: true
+      indicatorDefinition:
+        unit: "#"
     }]

     helpers.createIndicatorModels(
@@ -195,7 +197,7 @@
     )
   ).then( (indicators)->
     Q.nfcall(
-      Theme.getIndicatorsByTheme, theThemes[0]._id, {"dpsir.state": true}
+      Theme.getIndicatorsByTheme, theThemes[0]._id, {"indicatorDefinition.unit": "#"}
     )
   ).then( (indicators) ->
     try


project status display -- fix cases on startup

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -1008,15 +1008,17 @@
                     usage = @container.find(".project-disk_usage")

                     @container.find(".project-location").text(status.host)
+                    if status.btrfs?
+                        usage.find(".salvus-userused-projects").text(Math.ceil(status.btrfs))
+                    if status.memory?
+                        usage.find(".salvus-project-settings-memory-used").text(Math.round(status.memory.rss/1000000*100)/100)
                     if status.quotas?
                         usage.find(".salvus-userquota-projects").text(status.quotas.disk_quota)
-                        usage.find(".salvus-userused-projects").text(Math.ceil(status.btrfs))
                         usage.find(".salvus-project-settings-cores").text(status.quotas.cores)
                         if status.quotas.cores == 1
                             usage.find(".salvus-project-settings-cores-plural").hide()
                         else
                             usage.find(".salvus-project-settings-cores-plural").show()
-                        usage.find(".salvus-project-settings-memory-used").text(Math.round(status.memory.rss/1000000*100)/100)
                         usage.find(".salvus-project-settings-memory").text(Math.round(status.quotas.memory/1000*100)/100)
                         mintime = Math.round(status.quotas.mintime/3600)
                         if mintime > 10000


Fix event names

Fix event names

--- lib/keybinding-resolver-view.coffee
+++ lib/keybinding-resolver-view.coffee
@@ -33,14 +33,14 @@

   attach: ->
     atom.workspaceView.prependToBottom(this)
-    @subscribe atom.keymap, "key-binding-triggered", (usedKeyBinding, unusedKeyBindings) =>
       console.log "ok"
+    @subscribe atom.keymap, "key-binding-matched", (usedKeyBinding, unusedKeyBindings) =>
       @update(usedKeyBinding, unusedKeyBindings)

-    @subscribe atom.keymap, "parital-key-bindings-triggered", (keystrokes, keyBindings) =>
+    @subscribe atom.keymap, "parital-key-bindings-matched", (keystrokes, keyBindings) =>
       @updatePartial(keystrokes, keyBindings)

-    @subscribe atom.keymap, "no-key-binding-triggered", (keystrokes) =>
+    @subscribe atom.keymap, "no-key-binding-matched", (keystrokes) =>
       @keystroke.html $$ -> @span class: 'keystroke', "#{keystrokes}"
       @commands.empty()


:guardsman: Add guard when creating binding without a minimap

Should prevent errors reported in #17

--- lib/minimap-git-diff-binding.coffee
+++ lib/minimap-git-diff-binding.coffee
@@ -7,10 +7,14 @@
   active: false

   constructor: (@minimap) ->
-    @editor = @minimap.getTextEditor()
     @decorations = {}
     @markers = null
     @subscriptions = new CompositeDisposable
+
+    unless @minimap?
+      return console.warn 'minimap-git-diff binding created without a minimap'
+
+    @editor = @minimap.getTextEditor()

     @subscriptions.add @editor.getBuffer().onDidStopChanging @updateDiffs
     @subscriptions.add @minimap.onDidDestroy @destroy


Workspace: Added a helper method to get active tab's content in tabbed editor.

--- client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -30,6 +30,9 @@
             @createEditorInstance file, null, data.sessionKey

     @workspaceRef.onDisconnect().remove()  if @workspace.amIHost()
+
+  getActivePaneContent: ->
+    return @tabView.getActivePane().getSubviews()[0].getValue()

   createEditorTabs: ->
     @tabHandleContainer = new ApplicationTabHandleHolder


neo4j: add config option in runtime to use neo4, cache; remove neo4: enabled flag

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -293,8 +293,13 @@

   fetchCachedActivity:(options = {}, callback)->

+    urlPrefix = "cache"
+
+    if KD.config.useNeo4j
+      urlPrefix = "neo4j"
+
     $.ajax
-      url     : "/-/cache/#{options.slug or 'latest'}"
+      url     : "/-/#{urlPrefix}/#{options.slug or 'latest'}"
       cache   : no
       error   : (err)->   callback? err
       success : (cache)=>


Add BEAST mitigation config

Add BEAST mitigation config

--- lib/proxy.coffee
+++ lib/proxy.coffee
@@ -24,6 +24,9 @@
     options.https = {
       key: config.https.key
       cert: config.https.cert
+      # mitigate BEAST
+      honorCipherOrder: true
+      ciphers: "ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH"
     }

   proxyServer = httpProxy.createServer(options)


grey bg

grey bg

--- Framework/Views/iOS/Controls/SearchResultsTableMoreRow_Framework_iOS.coffee
+++ Framework/Views/iOS/Controls/SearchResultsTableMoreRow_Framework_iOS.coffee
@@ -3,7 +3,7 @@
     @settings = root._.extend({}, options)

     @moreRow = Ti.UI.createTableViewRow({
-      backgroundColor: "#FFF"
+      backgroundColor: "#DDD"
       selectionStyle: Ti.UI.iPhone.TableViewCellSelectionStyle.NONE
       height: 50
       type: "loadMoreRow"
@@ -23,7 +23,7 @@
     @loadMoreWrap = Ti.UI.createView({
       height: Ti.UI.FILL
       width: Ti.UI.FILL
-      backgroundColor: "#FFF"
+      backgroundColor: "#DDD"
     })
     @loadMoreButton = root.app.create("Button", {
       text: "Load More"


have generated navigation use ember

have generated navigation use ember

--- packages/tower-generator/server/helpers.coffee
+++ packages/tower-generator/server/helpers.coffee
@@ -40,8 +40,11 @@

   navigation: (key, path) ->
     pattern = /div *class: *'nav-collapse' *, *->\s+ul *class: *'nav', *-> */
-    content = """\n    navItem t('links.#{key}'), #{path}
+    content = """\n    li ->
+\ \ \ \ \ \ a '{{action index#{@model.className} href=true}}', t('links.#{key}')
 """
+#    content = """\n    navItem t('links.#{key}'), #{path}
+#"""

     @inRoot =>
       @injectIntoFile "app/views/shared/_navigation.coffee", content, after: pattern, duplicate: false


Add `hlint`

Add `hlint`

--- .atom/config.cson
+++ .atom/config.cson
@@ -4,10 +4,11 @@
       "ide-haskell-repl"
       "exception-reporting"
       "metrics"
+      "linter-hlint"
     ]
     telemetryConsent: "no"
   editor:
-    fontSize: 5
+    fontSize: 6
     showInvisibles: true
   "exception-reporting":
     userId: ""
@@ -20,6 +21,7 @@
     pscIdeServerExe: "/home/jk/.psvm/current/bin/psc-ide-server"
     pursExe: "/home/jk/.psvm/current/bin/purs"
     useCombinedExe: false
+  "linter-hlint": {}
   "linter-ui-default":
     panelHeight: 69
     showPanel: true


align defaults, use ? instead of the type assertion, default to null

--- src/wow.coffee
+++ src/wow.coffee
@@ -101,18 +101,18 @@

 class @WOW
   defaults:
-    boxClass:     'wow'
-    animateClass: 'animated'
-    offset:       0
-    mobile:       true
-    live:         true
-    callback:     null
-    scrollContainer: undefined
+    boxClass:        'wow'
+    animateClass:    'animated'
+    offset:          0
+    mobile:          true
+    live:            true
+    callback:        null
+    scrollContainer: null

   constructor: (options = {}) ->
     @scrolled = true
     @config   = @util().extend(options, @defaults)
-    if typeof options.scrollContainer is 'string'
+    if options.scrollContainer?
       @config.scrollContainer = document.querySelector(options.scrollContainer)
     # Map of elements to animation names:
     @animationNameCache = new WeakMap()


add event to be listened; when user is selecting the file, when uploading is in progress

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -96,6 +96,8 @@
       return

     bindEventHandlers: ->
+        self.$input.trigger 'attachinary:fileselection'
+
       @$input.bind 'fileuploadsend', (event, data) =>
         @$input.addClass 'uploading'
         @$wrapper.addClass 'uploading' if @$wrapper?
@@ -136,6 +138,7 @@

       @$input.bind 'fileuploadprogressall', (e, data) =>
         progress = parseInt(data.loaded / data.total * 100, 10)
+        @$input.trigger 'attachinary:uploadprogress', [progress]
         if @config.disableWith && @config.indicateProgress
           @$submit.val "[#{progress}%] #{@config.disableWith}"


in calculating the instance state also take into account the auxiliary containers (network, ssh)

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -7,12 +7,12 @@
 allWithState =  (services, states...) ->
   _.reduce services, ((memo, service) -> memo and service.state in states), true
 allMeetDesiredState = (services) ->
-  _.reduce services, ((memo, service) -> memo and service.state in service.desiredState), true
+  _.reduce services, ((memo, service) -> memo and service.state in (service.desiredState or ['running'])), true

 runningIsDesired = (doc, services) ->
   if allMeetDesiredState services
     'running'
-  else if atLeastOneWithState services, 'starting'
+  else if atLeastOneWithState services, 'starting', 'running'
     'starting'

 stoppedIsDesired = (doc, services) ->
@@ -27,10 +27,14 @@

 determineState = (doc, stateF) ->
+
   services = _.values doc.services
-  overallState = stateF doc, services
+  auxServices = services.map (s) -> _.values s.aux
+  allServices = _.union services, _.flatten auxServices
+  overallState = stateF doc, allServices
   overallState = 'failing' unless overallState
   Instances.update doc._id, $set: state: overallState
+  console.log 'determineState::update'

 f = false

@@ -42,7 +46,7 @@
       status: 'Created'

 Instances.find({state: 'removed', desiredState: 'stopped'}, fields: {_id: 1}).observe
-  added: (doc, olddoc) -> Instances.remove doc._id
+  added: (doc, olddoc) -> console.log 'Instance.remove'; Instances.remove doc._id

 Instances.find({desiredState: 'running'}, fields: {services: 1}).observe
   changed: (doc, oldDoc) -> determineState doc, runningIsDesired
@@ -50,9 +54,6 @@
 Instances.find({desiredState: 'stopped'}, fields: {services: 1}).observe
   changed: (doc, oldDoc) -> determineState doc, stoppedIsDesired

-setStateDescription = (instanceName, stateDescription) ->
-  Instances.upsert {name: instanceName}, $set:
-    'state.description': stateDescription

 f = true

@@ -124,7 +125,7 @@
         updateDoc["services.#{service}.properties"] = properties
         updateDoc.status = "Starting #{service}" if mappedState is 'starting'
         updateDoc.status = "Stopping #{service}" if mappedState is 'stopping'
-
+        console.log 'updateServiceState::upsert'
         Instances.upsert {name: name}, $set: updateDoc
       else
         updateDoc = name: name
@@ -134,6 +135,7 @@
         service = labels['bigboat.service.name']
         updateDoc["services.#{service}.aux.#{type}"] = auxUpdateDoc
         Instances.update {name: name}, $set: updateDoc
+        console.log 'updateServiceState::update'

   #
   # Updates the internal state based on image pulls


Remove refresh button as refresh is automatic now

--- src/smc-webapp/editor_git.cjsx
+++ src/smc-webapp/editor_git.cjsx
@@ -568,10 +568,6 @@

Git Repository at {@props.git_repo_root}

                 <Space/> <Space/>
-                <Button onClick={=>@props.actions.set_tab(@props.tab)}>
-                    Refresh
-                </Button>
-                <Space/> <Space/>
                 <DropdownButton title={'Switch branch from '+@props.current_branch} id='switch_branches'>
                     <MenuItem eventKey="{file}" onSelect={(e)=>@props.actions.setState(show_create_branch_modal:true)}>Create a branch and reset to upstream master</MenuItem>
                     {@render_branches()}


Fixed the separator issue in the service_call

--- main/static/src/script/common/service.coffee
+++ main/static/src/script/common/service.coffee
@@ -9,9 +9,10 @@
   params = params || {}
   for k, v of params
     delete params[k] if not v?
+  separator = if url.search('\\?') >= 0 then '&' else '?'
   $.ajax
     type: method
-    url: "#{url}?#{$.param params}"
+    url: "#{url}#{separator}#{$.param params}"
     contentType: 'application/json'
     accepts: 'application/json'
     dataType: 'json'


change file position of planet

change file position of planet

--- mechanics/003-twilight-zone/simulator.coffee
+++ mechanics/003-twilight-zone/simulator.coffee
@@ -11,7 +11,7 @@
     'cs!/wahlque/physics/units/au'
     'cs!/wahlque/universe/starA'
     'cs!/wahlque/universe/starB'
-    'cs!/wahlque/universe/planet'
+    'cs!/wahlque/universe/planet/planet'
 ], (exports, solver, vec3, b3, au, a, b, p) ->
     handle = 0
     time = 0


FIX: Ensure task definition id is passed to alignment create API

--- src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.coffee
+++ src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.coffee
@@ -68,7 +68,7 @@
     $scope.alignment = data = {
       unit_id: $scope.unit.id
       learning_outcome_id: $scope.ilo.id
-      task_definition_id: $scope.task.id
+      task_definition_id: $scope.task.definition.id
       rating: $scope.alignment.rating
       description: null
     }


remove trash

remove trash

--- lib/carte/client/views/slideshow.cjsx
+++ lib/carte/client/views/slideshow.cjsx
@@ -1,6 +1,5 @@
 # @cjsx React.DOM
 $ = require('jquery')
-Backbone = require('backbone')
 React = require('react')
 CardCollection = require('../models/cards')
 config = require('../config')


Use userId instead of Meteor.userId in Messages model

--- packages/rocketchat-lib/server/models/Messages.coffee
+++ packages/rocketchat-lib/server/models/Messages.coffee
@@ -331,7 +331,7 @@
 		else
 			update =
 				$pull:
-					starred: { _id: Meteor.userId() }
+					starred: { _id: userId }

 		return @update query, update


cleaned and commented the lexer (again) interpolate_string() continues to shrink

--- lexer.coffee
+++ lexer.coffee
@@ -110,40 +110,54 @@
 # pushing some extra smarts into the Lexer.
 exports.Lexer: class Lexer

-  # Scan by attempting to match tokens one at a time. Slow and steady.
+  # **tokenize** is the Lexer's main method. Scan by attempting to match tokens
+  # one at a time, using a regular expression anchored at the start of the
+  # remaining code, or a custom recursive token-matching method
+  # (for interpolations). When the next token has been recorded, we move forward
+  # within the code past the token, and begin again.
+  #
+  # Each tokenizing method is responsible for incrementing `@i` by the number of
+  # characters it has consumed. `@i` can be thought of as our finger on the page
+  # of source.
   tokenize: (code, options) ->
     o        : options or {}
     @code    : code         # The remainder of the source code.
     @i       : 0            # Current character position we're parsing.
     @line    : o.line or 0  # The current line.
-    @indent  : 0            # The current indent level.
-    @indents : []           # The stack of all indent levels we are currently within.
-    @tokens  : []           # Collection of all parsed tokens in the form ['TOKEN_TYPE', value, line]
+    @indent  : 0            # The current indentation level.
+    @indents : []           # The stack of all current indentation levels.
+    @tokens  : []           # Stream of parsed tokens in the form ['TYPE', value, line]
     while @i < @code.length
       @chunk: @code.slice(@i)
       @extract_next_token()
     @close_indentation()
-    return @tokens if o.rewrite is no
+    return @tokens if o.rewrite is off
     (new Rewriter()).rewrite @tokens

   # At every position, run through this list of attempted matches,
-  # short-circuiting if any of them succeed.
+  # short-circuiting if any of them succeed. Their order determines precedence:
+  # `@literal_token` is the fallback catch-all.
   extract_next_token: ->
     return if @identifier_token()
     return if @number_token()
     return if @heredoc_token()
-    return if @string_token()
-    return if @js_token()
     return if @regex_token()
     return if @comment_token()
     return if @line_token()
     return if @whitespace_token()
+    return if @js_token()
+    return if @string_token()
     return    @literal_token()

   # Tokenizers
   # ----------

   # Matches identifying literals: variables, keywords, method names, etc.
+  # Check to ensure that JavaScript reserved words aren't being used as
+  # identifiers. Because CoffeeScript reserves a handful of keywords that are
+  # allowed in JavaScript, we're careful not to tag them as keywords when
+  # referenced as property names here, so you can still do `jQuery.is()` even
+  # though `is` means `===` otherwise.
   identifier_token: ->
     return false unless id: @match IDENTIFIER, 1
     @name_access_type()
@@ -163,17 +177,20 @@
     @i += number.length
     true

-  # Matches strings, including multi-line strings.
+  # Matches strings, including multi-line strings. Ensures that quotation marks
+  # are balanced within the string's contents, and within nested interpolations.
   string_token: ->
+    return false unless starts(@chunk, '"') or starts(@chunk, "'")
     string: @balanced_token ['"', '"'], ['${', '}']
-    string: @balanced_token ["'", "'"] if string is false
+    string: @balanced_token ["'", "'"] unless string
     return false unless string
     @interpolate_string string.replace STRING_NEWLINES, " \\\n"
     @line += count string, "\n"
     @i += string.length
     true

-  # Matches heredocs, adjusting indentation to the correct level.
+  # Matches heredocs, adjusting indentation to the correct level, as heredocs
+  # preserve whitespace, but ignore indentation to the left.
   heredoc_token: ->
     return false unless match = @chunk.match(HEREDOC)
     doc: @sanitize_heredoc match[2] or match[4]
@@ -182,14 +199,17 @@
     @i += match[1].length
     true

-  # Matches interpolated JavaScript.
+  # Matches JavaScript interpolated directly into the source via backticks.
   js_token: ->
+    return false unless starts @chunk, '`'
     return false unless script: @balanced_token ['`', '`']
     @token 'JS', script.replace(JS_CLEANER, '')
     @i += script.length
     true

-  # Matches regular expression literals.
+  # Matches regular expression literals. Lexing regular expressions is difficult
+  # to distinguish from division, so we borrow some basic heuristics from
+  # JavaScript and Ruby.
   regex_token: ->
     return false unless regex: @match REGEX, 1
     return false if include NOT_REGEX, @tag()
@@ -197,38 +217,13 @@
     @i += regex.length
     true

-  # Matches a balanced group such as a single or double-quoted string. Pass in
-  # a series of delimiters, all of which must be balanced correctly within the
-  # string.
-  balanced_string: (str, delimited...) ->
-    levels: []
-    i: 0
-    while i < str.length
-      for pair in delimited
-        [open, close]: pair
-        if levels.length and starts str, '\\', i
-          i += 1
-          break
-        else if levels.length and starts(str, close, i) and levels[levels.length - 1] is pair
-          levels.pop()
-          i += close.length - 1
-          i += 1 unless levels.length
-          break
-        else if starts str, open, i
-          levels.push(pair)
-          i += open.length - 1
-          break
-      break unless levels.length
-      i += 1
-    throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}" if levels.length
-    return false if i is 0
-    return str.substring(0, i)
-
-  # Matches a balanced string within the token's contents.
+  # Matches a token in which which the passed delimiter pairs must be correctly
+  # balanced (ie. strings, JS literals).
   balanced_token: (delimited...) ->
     @balanced_string @chunk, delimited...

-  # Matches and conumes comments.
+  # Matches and conumes comments. We pass through comments into JavaScript,
+  # so they're treated as real tokens, like any other part of the language.
   comment_token: ->
     return false unless comment: @match COMMENT, 1
     @line += (comment.match(MULTILINER) or []).length
@@ -239,6 +234,15 @@
     true

   # Matches newlines, indents, and outdents, and determines which is which.
+  # If we can detect that the current line is continued onto the the next line,
+  # then the newline is suppressed:
+  #
+  #     elements
+  #       .each( ... )
+  #       .map( ... )
+  #
+  # Keeps track of the level of indentation, because a single outdent token
+  # can close multiple indents, so we need to know how far in we happen to be.
   line_token: ->
     return false unless indent: @match MULTI_DENT, 1
     @line += indent.match(MULTILINER).length
@@ -249,10 +253,10 @@
     no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and
       prev and (prev[0] isnt '.') and not @value().match(CODE))
     if size is @indent
-      return @suppress_newlines(indent) if no_newlines
+      return @suppress_newlines() if no_newlines
       return @newline_token(indent)
     else if size > @indent
-      return @suppress_newlines(indent) if no_newlines
+      return @suppress_newlines() if no_newlines
       diff: size - @indent
       @token 'INDENT', diff
       @indents.push diff
@@ -261,8 +265,8 @@
     @indent: size
     true

-  # Record an outdent token or tokens, if we happen to be moving back inwards
-  # past multiple recorded indents.
+  # Record an outdent token or multiple tokens, if we happen to be moving back
+  # inwards past several recorded indents.
   outdent_token: (move_out, no_newlines) ->
     while move_out > 0 and @indents.length
       last_indent: @indents.pop()
@@ -280,20 +284,22 @@
     @i += space.length
     true

-  # Generate a newline token. Multiple newlines get merged together.
+  # Generate a newline token. Consecutive newlines get merged together.
   newline_token: (newlines) ->
     @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
     true

   # Use a `\` at a line-ending to suppress the newline.
   # The slash is removed here once its job is done.
-  suppress_newlines: (newlines) ->
+  suppress_newlines: ->
     @tokens.pop() if @value() is "\\"
     true

   # We treat all other single characters as a token. Eg.: `( ) , . !`
   # Multi-character operators are also literal tokens, so that Jison can assign
-  # the proper order of operations.
+  # the proper order of operations. There are some symbols that we tag specially
+  # here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish
+  # parentheses that indicate a method call from regular parentheses, and so on.
   literal_token: ->
     match: @chunk.match(OPERATOR)
     value: match and match[1]
@@ -334,18 +340,17 @@
       else
         @tag 1, 'PROPERTY_ACCESS'

-  # Sanitize a heredoc by escaping double quotes and erasing all external
-  # indentation on the left-hand side.
+  # Sanitize a heredoc by escaping internal double quotes and erasing all
+  # external indentation on the left-hand side.
   sanitize_heredoc: (doc) ->
     indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
     doc.replace(new RegExp("^" +indent, 'gm'), '')
        .replace(MULTILINER, "\\n")
        .replace(/"/g, '\\"')

-  # A source of ambiguity in our grammar was parameter lists in function
-  # definitions (as opposed to argument lists in function calls). Tag
-  # parameter identifiers in order to avoid this. Also, parameter lists can
-  # make use of splats.
+  # A source of ambiguity in our grammar used to be parameter lists in function
+  # definitions versus argument lists in function calls. Walk backwards, tagging
+  # parameters specially in order to make things easier for the parser.
   tag_parameters: ->
     return if @tag() isnt ')'
     i: 0
@@ -363,64 +368,85 @@
   close_indentation: ->
     @outdent_token(@indent)

-  # Error for when you try to use a forbidden word in JavaScript as
+  # The error for when you try to use a forbidden word in JavaScript as
   # an identifier.
   identifier_error: (word) ->
     throw new Error "SyntaxError: Reserved word \"$word\" on line ${@line + 1}"

-  # Error for when you try to assign to a reserved word in JavaScript,
+  # The error for when you try to assign to a reserved word in JavaScript,
   # like "function" or "default".
   assignment_error: ->
     throw new Error "SyntaxError: Reserved word \"${@value()}\" on line ${@line + 1} can't be assigned"

+  # Matches a balanced group such as a single or double-quoted string. Pass in
+  # a series of delimiters, all of which must be nested correctly within the
+  # contents of the string. This method allows us to have strings within
+  # interpolations within strings etc...
+  balanced_string: (str, delimited...) ->
+    levels: []
+    i: 0
+    while i < str.length
+      for pair in delimited
+        [open, close]: pair
+        if levels.length and starts str, '\\', i
+          i += 1
+          break
+        else if levels.length and starts(str, close, i) and levels[levels.length - 1] is pair
+          levels.pop()
+          i += close.length - 1
+          i += 1 unless levels.length
+          break
+        else if starts str, open, i
+          levels.push(pair)
+          i += open.length - 1
+          break
+      break unless levels.length
+      i += 1
+    throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}" if levels.length
+    return false if i is 0
+    return str.substring(0, i)
+
   # Expand variables and expressions inside double-quoted strings using
-  # [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation).
+  # [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation)
+  # for substitution of bare variables as well as arbitrary expressions.
   #
   #     "Hello $name."
   #     "Hello ${name.capitalize()}."
   #
+  # If it encounters an interpolation, this method will recursively create a
+  # new Lexer, tokenize the interpolated contents, and merge them into the
+  # token stream.
   interpolate_string: (str) ->
     if str.length < 3 or not starts str, '"'
       @token 'STRING', str
     else
-      lexer:  new Lexer()
-      tokens: []
-      quote:  str.substring(0, 1)
-      i:      1
-      last_i: i
+      lexer:    new Lexer()
+      tokens:   []
+      quote:    str.substring(0, 1)
+      [i, pi]:  [1, 1]
       while i < str.length - 1
         if starts str, '\\', i
           i += 1
-        else
-          match: str.substring(i).match INTERPOLATION
-          if match
-            [group, interp]: match
-            interp: "this.${ interp.substring(1) }" if starts interp, '@'
-            tokens.push ['STRING', "$quote${ str.substring(last_i, i) }$quote"] if last_i < i
-            tokens.push ['IDENTIFIER', interp]
-            i += group.length - 1
-            last_i: i + 1
-          else
-            expression: @balanced_string str.substring(i), ['${', '}']
-            if expression and expression.length > 3
-              inner: expression.substring(2, expression.length - 1)
-              nested: lexer.tokenize "($inner)", {rewrite: no, line: @line}
-              nested.pop()
-              tokens.push ['STRING', "$quote${ str.substring(last_i, i) }$quote"] if last_i < i
-              tokens.push ['TOKENS', nested]
-              i += expression.length - 1
-              last_i: i + 1
+        else if match: str.substring(i).match INTERPOLATION
+          [group, interp]: match
+          interp: "this.${ interp.substring(1) }" if starts interp, '@'
+          tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
+          tokens.push ['IDENTIFIER', interp]
+          i += group.length - 1
+          pi: i + 1
+        else if (expr: @balanced_string str.substring(i), ['${', '}']) and expr.length > 3
+          inner: expr.substring(2, expr.length - 1)
+          nested: lexer.tokenize "($inner)", {rewrite: no, line: @line}
+          nested.pop()
+          tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
+          tokens.push ['TOKENS', nested]
+          i += expr.length - 1
+          pi: i + 1
         i += 1
-      tokens.push ['STRING', "$quote${ str.substring(last_i, i) }$quote"] if last_i < i and last_i < str.length - 1
-      if tokens.length > 1
-        for i in [tokens.length - 1..1]
-          [prev, tok]: [tokens[i - 1], tokens[i]]
-          if tok[0] is 'STRING' and prev[0] is 'STRING'
-            [prev, tok]: [prev[1].substring(1, prev[1].length - 1), tok[1].substring(1, tok[1].length - 1)]
-            tokens.splice i - 1, 2, ['STRING', "$quote$prev$tok$quote"]
+      tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i and pi < str.length - 1
       for each, i in tokens
         if each[0] is 'TOKENS'
-          @token nested[0], nested[1] for nested in each[1]
+          @tokens: @tokens.concat each[1]
         else
           @token each[0], each[1]
         @token '+', '+' if i < tokens.length - 1


can use separate url

can use separate url

--- lib/middleware/proxy_to_gravity.coffee
+++ lib/middleware/proxy_to_gravity.coffee
@@ -4,10 +4,10 @@
 # If not, simply pass it to the next middleware
 #

-{ API_URL } = require '../../config'
+{ PROXY_API_URL, API_URL } = require '../../config'
 httpProxy = require 'http-proxy'
 proxy = httpProxy.createProxyServer()
 express = require 'express'

 module.exports.api = (req, res) ->
-  proxy.web req, res, { target: API_URL }
+  proxy.web req, res, { target: PROXY_API_URL or API_URL }


cocalc: rephrasing help links to offered software solutions to be more general

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -217,9 +217,9 @@
 THIRD_PARTY =
     sagemath :
         icon : 'cc-icon-sagemath'
-        href : 'http://www.sagemath.org/help.html'
+        href : 'http://www.sagemath.org/'
         link : 'SageMath'
-        text : documentation, help, support and books
+        text : open-source mathematical software
     r :
         icon : 'cc-icon-r'
         href : 'https://cran.r-project.org/doc/manuals/r-release/R-intro.html'
@@ -233,7 +233,8 @@
                     Statsmodels,{' '}
                     Pandas,{' '}
                     SymPy,{' '}
-                    Scikit Learn and many more
+                    Scikit Learn,{' '}
+                    NLTK and many more

     julia :
         icon : 'cc-icon-julia'
@@ -253,12 +254,13 @@
     latex :
         icon : 'cc-icon-tex-file'
         href : 'https://en.wikibooks.org/wiki/LaTeX'
-        link : 'LaTeX introduction'
+        link : 'LaTeX'
+        text : 'high-quality typesetting program'
     linux :
         icon : 'linux'
         href : 'http://ryanstutorials.net/linuxtutorial/'
         link : 'GNU/Linux'
-        text : 'introduction tutorial'
+        text : 'operating system and utility toolbox'

 ABOUT_LINKS =


adding in sync before previewing so exercises are up to date

--- src/stores/exercise.coffee
+++ src/stores/exercise.coffee
@@ -19,10 +19,9 @@
     questions = _.map @_local[id].questions, (question) ->
       QuestionActions.sync(question.id)
       QuestionStore.get(question.id)
+    @_change(id, {questions})

-  save: (id) ->
-    @sync(id)
-    @_change(id, {questions})
+  save: (id) -> @sync(id)

   exports:
     getQuestions: (id) -> @_local[id].questions


Start replace child test

Start replace child test

--- src/test/test_world.coffee
+++ src/test/test_world.coffee
@@ -71,6 +71,15 @@
     t.equal result.length, 0
     result = world.find_children("root:0")
     t.equal result.length, 1
+    t.end()
+
+  test 'world can replace children', (t) ->
+    t.ok world.has_children(), "has some"
+    count = world._child_count()
+    t.ok result = world.find_child("root:0"), "child exists"
+    console.log "result", result
+    t.equal result.get('c'), 3, "c is 3"
+
     t.end()

   # TODO: Break this up into smaller tests somehow


remove unused line

remove unused line

--- public/fav-client.coffee
+++ public/fav-client.coffee
@@ -54,7 +54,6 @@
       el.append text.substr(pos, e.indices[0] - pos)
       pos = e.indices[1]
       sub = text.substr(e.indices[0], e.indices[1] - e.indices[0])
-      console.log e.expanded_url ? e.display_url ? e.url
       el.append switch e.type
         when "urls" then $("<a>").attr(class: "urls", target: "_blank", href: e.expanded_url ? e.url).text e.display_url ? e.url
         when "user_mentions" then $("<a>").attr(class: "user_mentions", href: "/#{encodeURIComponent e.screen_name}").text sub


change async to series

change async to series

no real gain from parallel, series might reduce the cpu load if it finds the doc early

--- services/web/app/coffee/Features/Project/ProjectRootDocManager.coffee
+++ services/web/app/coffee/Features/Project/ProjectRootDocManager.coffee
@@ -19,7 +19,7 @@
 							return cb(doc?._id)
 						else
 							return cb()
-			async.parallel jobs, (root_doc_id)->
+			async.series jobs, (root_doc_id)->
 				if root_doc_id?
 					ProjectEntityHandler.setRootDoc project_id, root_doc_id, callback
 				else


Fix a crash bug in ffox platform

Fix a crash bug in ffox platform

Signed-off-by: Manson Li <f2a598d7717cc6757bef28b84a9ab02e5efc6048@matchstick.tv>

--- lib/platform/PlatformFirefoxOS.coffee
+++ lib/platform/PlatformFirefoxOS.coffee
@@ -51,13 +51,13 @@
             @sockets.add socket
             emitter = new JsonMessageEmitter socket, (message) =>
                 @_didReceiveSystemMessage message
-            socket.on 'end', ->
+            socket.on 'end', =>
                 Log.d "PAL sock closed!"
                 @sockets.remove socket
                 emitter = null
-            socket.on 'error', (exception) ->
+            socket.on 'error', (exception) =>
                 Log.e "PAL socket error: #{exception}"
-            socket.on 'data', (data) ->
+            socket.on 'data', (data) =>
                 Log.d "pal socket data: #{data}"
         @palServer.listen Config.FFOS_PAL_SERVER_PORT


Callback now called after an unknown command


Improve MDE buttons

Improve MDE buttons

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -26,11 +26,26 @@
         italic: "_"
       insertTexts:
         link: ["[", "](#play-)"]
-      toolbar: ["bold", "italic", "strikethrough", "|",
-                "heading-1", "heading-2", "heading-3", "|",
+      toolbar: ["heading", "bold", "italic", "strikethrough", "|",
                 "unordered-list", "ordered-list", "code", "quote", "link", "|",
-                "preview", "side-by-side", "fullscreen"
-      ],
+                "preview", "side-by-side", "fullscreen", "|",
+                {
+                    name: 'guide'
+                    action: 'https://venganzasdelpasado.com.ar/articles/como-editar-posts'
+                    className: 'fa fa-info-circle',
+                    noDisable: true,
+                    title: 'Editor\'s Guide',
+                    default: true
+                },
+                {
+                    name: 'guide'
+                    action: 'https://venganzasdelpasado.com.ar/articles/markdown'
+                    className: 'fa fa-question-circle',
+                    noDisable: true,
+                    title: 'Markdown Guide',
+                    default: true
+                }
+              ]
     )

     mde.codemirror.on 'refresh', ->


Configure Monarch for Rails

Configure Monarch for Rails

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -21,3 +21,7 @@

 window.Models = {}
 window.Views = {}
+
+Monarch.snakeCase = true
+Monarch.resourceUrlRoot = '/sandbox'
+Monarch.resourceUrlSeparator = '_'


Remove blog post click handler

Remove blog post click handler


d3 bar chart: tweaks

d3 bar chart: tweaks

starting position, legend, etc

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -7,10 +7,12 @@

     can_be_shown_as_table: -> true

+    outer_height: -> 410
+
     draw: =>
       margins =
-        top: 40
-        bottom: 100
+        top: 20
+        bottom: 200
         left: 30
         right: 30

@@ -27,7 +29,7 @@
       legend_columns = 2
       legend_margin = @width / legend_columns
       legend = @svg.append('svg:g')
-        .attr("transform", "translate(0,200)")
+        .attr("transform", "translate(10,210)")
         .selectAll("svg.legend")
         .data(@model.series.models)
         .enter()
@@ -35,7 +37,7 @@
         .attr("class", "legend")
         .attr("transform", (d, i) ->
           x = legend_margin * (i % legend_columns)
-          y = Math.floor(i / legend_columns) * 20
+          y = Math.floor(i / legend_columns) * 15
           "translate(#{x}, #{y})")
         .attr("height", 30)
         .attr("width", 90)
@@ -64,7 +66,7 @@
         .attr('class', 'year')
         .text((d) -> d)
         .attr('x', (d) => @x(d) + 10)
-        .attr('y', 175)
+        .attr('y', 205)
         .attr('dx', 45)

       @y = d3.scale.linear().range([0, @height]).domain([0, 7])
@@ -75,8 +77,11 @@
         .enter().append('svg:rect')
         .attr('class', 'serie')
         .attr("width", @x.rangeBand() * 0.5)
-        .attr('x', 0)
-        .attr('y', 0)
+        .attr('x', (d, i) =>
+          year = if i == 0 then 'start_year' else 'end_year'
+          @x(App.settings.get(year)) + 10
+          )
+        .attr('y', @height)
         .style('fill', (d) => d.color)

       $('rect.serie').qtip
@@ -146,7 +151,6 @@
       @svg.selectAll('rect.serie')
         .data(stacked_data, (s) -> s.id)
         .transition()
-        .attr('x', (d) => @x(d.x) + 10)
         .attr('y', (d) => @height - @y(d.y0 + d.y))
         .attr('height', (d) => @y(d.y))
         .attr("data-tooltip", (d) =>


Fix strange effects on attachments drag and drop.

--- app/coffee/modules/common/attachments.coffee
+++ app/coffee/modules/common/attachments.coffee
@@ -207,8 +207,11 @@
         tdom.sortable({
             items: "div.single-attachment"
             handle: "a.settings.icon.icon-drag-v"
+            containment: ".attachments"
+            cursorAt: {right: 50}
             dropOnEmpty: true
-            axis: "y"
+            scroll: false
+            tolerance: "pointer"
             placeholder: "sortable-placeholder single-attachment"
         })


Force to use tipsy as tooltip engine for likeCount tooltip

--- client/app/Applications/Activity.kdapplication/views/activityactions.coffee
+++ client/app/Applications/Activity.kdapplication/views/activityactions.coffee
@@ -21,11 +21,12 @@
       tooltip     :
         title     : "
Coming Soon
"
         placement : "above"
-        offset    : 3

     @likeCount    = new ActivityLikeCount
       tooltip     :
         title     : ""
+        engine    : "tipsy" # We should force to use tipsy because
+                            # for now only tipsy supports tooltip updates
       attributes  :
         href      : "#"
         title     : "Click to view..."


Remove the disposables.

Remove the disposables.

There is an error when disposing disposables after the callbacks have been
called.

--- lib/suggestion-list-element.coffee
+++ lib/suggestion-list-element.coffee
@@ -107,10 +107,8 @@
   render: ->
     @selectedIndex = 0
     atom.views.pollAfterNextUpdate?()
-    @renderDisposables?.dispose()
-    @renderDisposables = new CompositeDisposable()
-    @renderDisposables.add atom.views.updateDocument @renderItems.bind(this)
-    @renderDisposables.add atom.views.readDocument @readUIPropsFromDOM.bind(this)
+    atom.views.updateDocument @renderItems.bind(this)
+    atom.views.readDocument @readUIPropsFromDOM.bind(this)

   addActiveClassToEditor: ->
     editorElement = atom.views.getView(atom.workspace.getActiveTextEditor())
@@ -134,10 +132,8 @@

   setSelectedIndex: (index) ->
     @selectedIndex = index
-    @renderDisposables?.dispose()
-    @renderDisposables = new CompositeDisposable()
-    @renderDisposables.add atom.views.updateDocument @renderSelectedItem.bind(this)
-    @renderDisposables.add atom.views.readDocument @readUIPropsFromDOM.bind(this)
+    atom.views.updateDocument @renderSelectedItem.bind(this)
+    atom.views.readDocument @readUIPropsFromDOM.bind(this)

   visibleItems: ->
     @model?.items?.slice(0, @maxItems)


GameObject.Effect

GameObject.Effect

--- test/game_object_effect.coffee
+++ test/game_object_effect.coffee
@@ -12,6 +12,7 @@
   player.update(1)
   player.update(1)

+  ok player.I.a
   ok fadedOut, "callback was called"

 module()


Simplify word manager to use Range + Anchor to automatically keep marker positions up-to-date

Re-implement highlighted word manager to be simpler

--- services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
@@ -13,18 +13,31 @@
 	class HighlightedWordManager
 		constructor: (@editor) ->
 			@reset()
-
+
 		reset: () ->
+			@highlights?.rows.forEach (highlight) =>
+				@editor.getSession().removeMarker(highlight.markerId)
 			@highlights = rows: []

 		addHighlight: (highlight) ->
 			unless highlight instanceof Highlight
 				highlight = new Highlight(highlight)
-			range = new Range(
-				highlight.row, highlight.column,
-				highlight.row, highlight.column + highlight.word.length
-			)
-			highlight.markerId = @editor.getSession().addMarker range, "spelling-highlight", 'text', false
+
+			session = @editor.getSession()
+			doc = session.getDocument()
+			# Set up Range that will automatically update it's positions when the
+			# document changes
+			range = new Range()
+			range.start = doc.createAnchor({
+				row: highlight.row,
+				column: highlight.column
+			})
+			range.end = doc.createAnchor({
+				row: highlight.row,
+				column: highlight.column + highlight.word.length
+			})
+
+			highlight.markerId = session.addMarker range, "spelling-highlight", 'text', false
 			@highlights.rows[highlight.row] ||= []
 			@highlights.rows[highlight.row].push highlight

@@ -34,114 +47,7 @@
 				if h == highlight
 					@highlights.rows[highlight.row].splice(i, 1)

-		removeWord: (word) ->
-			toRemove = []
-			for row in @highlights.rows
-				for highlight in (row || [])
-					if highlight.word == word
-						toRemove.push(highlight)
-			for highlight in toRemove
+		clearRow: (row) ->
+			row = @highlights.rows[row]
+			for highlight in (row || []).slice()
 				@removeHighlight highlight
-
-		moveHighlight: (highlight, position) ->
-			@removeHighlight highlight
-			highlight.row = position.row
-			highlight.column = position.column
-			@addHighlight highlight
-
-		clearRows: (from, to) ->
-			from ||= 0
-			to ||= @highlights.rows.length - 1
-			for row in @highlights.rows.slice(from, to + 1)
-				for highlight in (row || []).slice(0)
-					@removeHighlight highlight
-
-		insertRows: (offset, number) ->
-			# rows are inserted after offset. i.e. offset row is not modified
-			affectedHighlights = []
-			for row in @highlights.rows.slice(offset)
-				affectedHighlights.push(highlight) for highlight in (row || [])
-			for highlight in affectedHighlights
-				@moveHighlight highlight,
-					row: highlight.row + number
-					column: highlight.column
-
-		removeRows: (offset, number) ->
-			# offset is the first row to delete
-			affectedHighlights = []
-			for row in @highlights.rows.slice(offset)
-				affectedHighlights.push(highlight) for highlight in (row || [])
-			for highlight in affectedHighlights
-				if highlight.row >= offset + number
-					@moveHighlight highlight,
-						row: highlight.row - number
-						column: highlight.column
-				else
-					@removeHighlight highlight
-
-		findHighlightWithinRange: (range) ->
-			rows = @highlights.rows.slice(range.start.row, range.end.row + 1)
-			for row in rows
-				for highlight in (row || [])
-					if @_doesHighlightOverlapRange(highlight, range.start, range.end)
-						return highlight
-			return null
-
-		applyChange: (change) ->
-			start = change.start
-			end = change.end
-			if change.action == "insert"
-				if start.row != end.row
-					rowsAdded = end.row - start.row
-					@insertRows start.row + 1, rowsAdded
-				# make a copy since we're going to modify in place
-				oldHighlights = (@highlights.rows[start.row] || []).slice(0)
-				for highlight in oldHighlights
-					if highlight.column > start.column
-						# insertion was fully before this highlight
-						@moveHighlight highlight,
-							row: end.row
-							column: highlight.column + (end.column - start.column)
-					else if highlight.column + highlight.word.length >= start.column
-						# insertion was inside this highlight
-						@removeHighlight highlight
-
-			else if change.action == "remove"
-				if start.row == end.row
-					oldHighlights = (@highlights.rows[start.row] || []).slice(0)
-				else
-					rowsRemoved = end.row - start.row
-					oldHighlights =
-						(@highlights.rows[start.row] || []).concat(
-							(@highlights.rows[end.row] || [])
-						)
-					@removeRows start.row + 1, rowsRemoved
-
-				for highlight in oldHighlights
-					if @_doesHighlightOverlapRange highlight, start, end
-						@removeHighlight highlight
-					else if @_isHighlightAfterRange highlight, start, end
-						@moveHighlight highlight,
-							row: start.row
-							column: highlight.column - (end.column - start.column)
-
-		_doesHighlightOverlapRange: (highlight, start, end) ->
-			highlightIsAllBeforeRange =
-				highlight.row < start.row or
-				(highlight.row == start.row and highlight.column + highlight.word.length <= start.column)
-			highlightIsAllAfterRange =
-				highlight.row > end.row or
-				(highlight.row == end.row and highlight.column >= end.column)
-			!(highlightIsAllBeforeRange or highlightIsAllAfterRange)
-
-		_isHighlightAfterRange: (highlight, start, end) ->
-			return true if highlight.row > end.row
-			return false if highlight.row < end.row
-			highlight.column >= end.column
-
-
-
-
-
-
-


deployment description should contain a module id

--- spec/module/deploy.spec.coffee
+++ spec/module/deploy.spec.coffee
@@ -5,15 +5,21 @@

 skipWhen process.env.STEROIDS_TEST_RUN_MODE, "fast"

+file = (path) ->
+  readJson: -> JSON.parse fs.readFileSync path
+  exists: -> fs.existsSync path
+  clean: ->
+    if fs.existsSync path
+      fs.unlinkSync path
+
 describe "module", ->

   rightHereRightNow =>
     @testHelper = new TestHelper
     @testHelper.prepare()

-  deploymentDescriptionFilename = path.join(@testHelper.testAppPath, "config", "deployment.json")
-  readModuleDescription = ->
-    JSON.parse fs.readFileSync deploymentDescriptionFilename
+  deploymentDescriptionFile = file path.join(@testHelper.testAppPath, "config", "deployment.json")
+  deploymentDescriptionFile.clean()

   describe "deploy", =>

@@ -30,4 +36,8 @@
           cmd.done

         runs ->
-          expect(fs.existsSync deploymentDescriptionFilename).toBeTruthy()
+          expect(deploymentDescriptionFile.exists()).toBeTruthy()
+
+      describe "deployment description", ->
+        it "should have an identifier for the module", ->
+          expect(deploymentDescriptionFile.readJson().module.id).toBeTruthy()


test for making notification dismissable on click

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -187,9 +187,13 @@
           expect(atom.views.getView(atom.workspace.getActiveTextEditor())).toHaveFocus()

     describe "when an autoclose notification is added", ->
-      it "closes and removes the message after a given amount of time", ->
+      notification = null
+
+      beforeEach ->
         atom.notifications.addSuccess('A message')
         notification = notificationContainer.querySelector('atom-notification.success')
+
+      it "closes and removes the message after a given amount of time", ->
         expect(notification).not.toHaveClass 'remove'

         advanceClock(NotificationElement::visibilityDuration)
@@ -198,6 +202,18 @@

         advanceClock(NotificationElement::animationDuration)
         expect(notificationContainer.childNodes.length).toBe 0
+
+      describe "when the notification is clicked", ->
+        beforeEach ->
+          notification.click()
+
+        it "makes the notification dismissable", ->
+          expect(notification).toHaveClass 'has-close'
+
+          advanceClock(NotificationElement::visibilityDuration)
+          expect(notification).not.toHaveClass 'remove'
+
+

     describe "when the `description` option is used", ->
       it "displays the description text in the .description element", ->


add postinstall message explaining how to use bin/install-services

--- server-ce/Gruntfile.coffee
+++ server-ce/Gruntfile.coffee
@@ -87,9 +87,11 @@
 	grunt.registerTask 'install:all', "Download and set up all ShareLaTeX services",
 		[].concat(
 			("install:#{service.name}" for service in SERVICES)
-		)
+		).concat(['postinstall'])

 	grunt.registerTask 'install', 'install:all'
+	grunt.registerTask 'postinstall', 'Explain postinstall steps', () ->
+		Helpers.postinstallMessage @async()

 	grunt.registerTask 'update:all', "Checkout and update all ShareLaTeX services",
 		["check:make"].concat(
@@ -146,6 +148,17 @@
 			proc = spawn "git", ["checkout", service.version], stdio: "inherit", cwd: dir
 			proc.on "close", () ->
 				callback()
+
+		postinstallMessage: (callback = (error) ->) ->
+			grunt.log.write """
+			Services cloned:
+				#{service.name for service in SERVICES}
+			To install services run:
+				$ source bin/install-services
+			This will install the required node versions and run `npm install` for each service.
+			See https://github.com/sharelatex/sharelatex/pull/549 for more info.
+			"""
+			callback()

 		checkMake: (callback = (error) ->) ->
 			grunt.log.write "Checking make is installed... "


More debugging travis-ci

More debugging travis-ci

--- test/specs/uploads/read.coffee
+++ test/specs/uploads/read.coffee
@@ -1,16 +1,14 @@
 describe 'Uploads GET show', ->

   it 'should get a resized image by width', (done) ->
-    console.log @test_dir
     resized = fs.readFileSync("#{@test_dir}/support/images/image_w_100.jpg")
-    console.log resized
     buffers = []
     stream  = request.get('http://localhost:3001/uploads/images/image?w=100')
-    stream.on 'data', (data) ->
-      console.log 'buffers'
-      buffers.push(data)
+    stream.on 'data', (data) -> buffers.push(data)
     stream.on 'end', ->
+      console.log "========="
       buffer = Buffer.concat(buffers)
+      console.log buffer
       expect(buffer).to.deep.equal resized
       done()


remove debug

remove debug

--- scripts/pagerv2_commands.coffee
+++ scripts/pagerv2_commands.coffee
@@ -439,7 +439,6 @@
       pagerv2.dropOverride(res.envelope.user, who)
     .then (data) ->
       if data
-        console.log data
         res.send "Ok, #{res.envelope.user.name}! " +
                  "#{data.overrides[0].user.summary} override is cancelled."
       else


:fire: meaningless spec

:fire: meaningless spec

Failed in atom 1.11.1.
Comment out newPane.addItem(editor) line test pass.
But this is not related to vmp and this spec is for vim-mode which
define command on each text-editor instances but vmp isn’t, vmp just
define commands to scope-based only once. So no risk to duplicate
command registration.
Thus remove!

--- spec/vim-mode-plus-spec.coffee
+++ spec/vim-mode-plus-spec.coffee
@@ -33,18 +33,6 @@
         ensure 'i', mode: 'insert'
         expect(statusBarTile.textContent).toBe("I")

-    it "doesn't register duplicate command listeners for editors", ->
-      set
-        text: '12345'
-        cursorBuffer: [0, 0]
-
-      pane = atom.workspace.getActivePane()
-      newPane = pane.splitRight()
-      pane.removeItem(editor)
-      newPane.addItem(editor)
-
-      ensure 'l', cursorBuffer: [0, 1]
-
   describe ".deactivate", ->
     it "removes the vim classes from the editor", ->
       atom.packages.deactivatePackage(packageName)


use piping instead of writeFileSync to store cache file

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -238,23 +238,26 @@

   # if caching isn't turned off in options
   if useCache
-    console.log '\n  caching is ON\n'
     cacheFileName = step.fullInputPath + '.cached'
     # checks if files have changed or we don't have cached values
     compileChanges = checkFileChanges step, cacheFileName

     if compileChanges
+      # instead of writing the string result to the file after we have gathered
+      # it all, let's pipe the result into the cache file while we're gathering
+      # all the data for our string result
+      cacheFileStream = fs.createWriteStream cacheFileName,
+        flags:'w'
+        encoding:'utf8'
+      bundle.pipe cacheFileStream
+
       # call our wrapped function with the readable stream as its argument
       string = getString bundle

-      # write result to a cache file
-      fs.writeFileSync cacheFileName, string
-
     else # read the cached file instead
       string = fs.readFileSync cacheFileName, encoding:'utf8'

   else # call our wrapped function with the readable stream as its argument
-    console.log '\n  caching is off\n'
     string = getString bundle

   return string


Removed only having SVG Layer test enabled

--- test/tests/SVGLayerTest.coffee
+++ test/tests/SVGLayerTest.coffee
@@ -1,5 +1,5 @@

-describe.only "SVGLayer", ->
+describe "SVGLayer", ->
 	describe "gradients", ->
 		it "should generate unique gradient id per instance", ->
 			a = new SVGLayer


comparison tests

comparison tests

--- test/unit/builder_tests.coffee
+++ test/unit/builder_tests.coffee
@@ -326,7 +326,7 @@
   describe 'binary arithmetic', ->
     me = beforeEach -> me = unit.makeFunction 'main', i32, i32, f32, vectorOf(3, i32), vectorOf(3, f32), vectorOf(4, f32)

-    testBinary = (name, acceptsInts, acceptsFloats) ->
+    testBinary = (name, acceptsInts, acceptsFloats, returns) ->
       describe name, ->
         it 'expects two values', ->
           (-> me[name]()).should.throw /value/i
@@ -353,12 +353,12 @@
           it 'accepts ints', ->
             int = me.parameter 0
             result = me[name] int, int
-            result.type.toString().should.equal int.type.toString()
+            result.type.toString().should.equal if returns then returns.toString() else int.type.toString()

           it 'accepts int vectors', ->
             vec = me.parameter 2
             result = me[name] vec, vec
-            result.type.toString().should.equal vec.type.toString()
+            result.type.toString().should.equal if returns then returns.toString() else vec.type.toString()
         else
           it 'does not accept ints', ->
             int = me.parameter 0
@@ -372,12 +372,12 @@
           it 'accepts floats', ->
             flo = me.parameter 1
             result = me[name] flo, flo
-            result.type.toString().should.equal flo.type.toString()
+            result.type.toString().should.equal if returns then returns.toString() else flo.type.toString()

           it 'accepts float vectors', ->
             vec = me.parameter 3
             result = me[name] vec, vec
-            result.type.toString().should.equal vec.type.toString()
+            result.type.toString().should.equal if returns then returns.toString() else vec.type.toString()
         else
           it 'does not accept floats', ->
             float = me.parameter 1
@@ -422,3 +422,26 @@
     testBinary 'shl', yes, no
     testBinary 'lshr', yes, no
     testBinary 'ashr', yes, no
+
+    testBinary 'equal', yes, no, i1
+    testBinary 'notEqual', yes, no, i1
+    testBinary 'uGreaterThan', yes, no, i1
+    testBinary 'uAtLeast', yes, no, i1
+    testBinary 'uLessThan', yes, no, i1
+    testBinary 'uAtMost', yes, no, i1
+    testBinary 'sGreaterThan', yes, no, i1
+    testBinary 'sAtLeast', yes, no, i1
+    testBinary 'sLessThan', yes, no, i1
+    testBinary 'sAtMost', yes, no, i1
+    testBinary 'foEqual', no, yes, i1
+    testBinary 'foNotEqual', no, yes, i1
+    testBinary 'foGreaterThan', no, yes, i1
+    testBinary 'foAtLeast', no, yes, i1
+    testBinary 'foLessThan', no, yes, i1
+    testBinary 'foAtMost', no, yes, i1
+    testBinary 'fuEqual', no, yes, i1
+    testBinary 'fuNotEqual', no, yes, i1
+    testBinary 'fuGreaterThan', no, yes, i1
+    testBinary 'fuAtLeast', no, yes, i1
+    testBinary 'fuLessThan', no, yes, i1
+    testBinary 'fuAtMost', no, yes, i1


Added publish and call methods

Added publish and call methods

--- server/bookings/bookings.coffee
+++ server/bookings/bookings.coffee
@@ -1,2 +1,19 @@
-Meteor.publish 'bookings', =>
-  @Bookings.find({})
+Bookings = @Bookings
+Meteor.publish 'bookings', ->
+  Bookings.find({})
+
+Meteor.publish 'next_bookings', ->
+  date = moment.utc().startOf('day').toISOSString()
+  Bookings.find({$gte: date: date}, {sort: {date: -1}, limit: 10})
+
+
+Meteor.methods
+  bookings_on: (day)->
+    date = moment.utc(day).startOf('day').toDate()
+    Bookings.find({
+      $eq: {
+        date: date
+      }
+    }, {
+      fields: {'_id':1, date: 1, color: 1, owner: 1}
+    }).fetch()


Fix crash on application startup, refs #149

--- server/controllers/album.coffee
+++ server/controllers/album.coffee
@@ -21,6 +21,8 @@
         (cb) -> Album.listWithThumbs cb
         (cb) -> cozydb.api.getCozyLocale cb
     ], (err, results) ->
+        return next err if err
+
         [albums, locale] = results
         visible = []
         async.each albums, (album, callback) =>


Fixed a point which prevents loading page when error occures.

--- server/lib/crawler/index.coffee
+++ server/lib/crawler/index.coffee
@@ -101,15 +101,15 @@
         activityContent = {
           fullName : fullName
           hash : hash
-          title : if model.title then model.title else model.body or ""
-          body : if model.body  then model.body  else ""
+          title : if model?.title? then model.title else model.body or ""
+          body : if model?.body?  then model.body  else ""
           codeSnippet : htmlEncode codeSnippet
-          createdAt : formatDate(model.data?.meta?.createdAt)
+          createdAt : formatDate(model?.data?.meta?.createdAt)
           numberOfComments : teaser.repliesCount or 0
           numberOfLikes : model?.data?.meta?.likes or 0
           comments : comments
           tags : tags
-          type : model.bongo_?.constructorName
+          type : model?.bongo_?.constructorName
         }
         content = activity {activityContent, section, models}
         callback null, content
@@ -145,12 +145,11 @@
             model = models.first if models and Array.isArray models
             return res.send 404, error_404() unless model

-
             if typeof model.fetchRelativeComments is "function"
               model?.fetchRelativeComments? limit:3, after:"", (error, comments)=>
                 queue = [0..comments.length].map (index)=>=>
                   comment = comments[index]
-                  if comment?.data
+                  if comment?.data?
                     # Get comments authors, put comment info into commentSummaries
                     decorateComment JAccount, comment, (error, commentSummary)=>
                       queue.next() if error
@@ -160,16 +159,13 @@
                       queue.next()
                   else queue.next()
                 queue.push =>
-                  createActivityContent JAccount, models, \
-                    queue.commentSummaries, section, (error, content)=>
-                      queue.next() if error
-                      return res.send 200, content
+                  createActivityContent JAccount, models, queue.commentSummaries, section, (error, content)=>
+                    queue.next() if error
+                    return res.send 200, content
                 daisy queue
             else
-              createActivityContent JAccount, models, \
-                {}, section, (error, content)=>
-                  queue.next() if error
-                  return res.send 200, content
+              createActivityContent JAccount, models, {}, section, (error, content)=>
+                return res.send 200, content
       else
         return res.send 404, error_404("No section is given.")
     else


Add shortcut to makai folder

Add shortcut to makai folder

Right click 'open cache folder' button.

--- views/app.cjsx
+++ views/app.cjsx
@@ -227,6 +227,14 @@
       openItem path.join(window.EXROOT, dir)
     catch e
       toggleModal '打开缓存目录', '打开失败，可能没有创建文件夹的权限'
+   handleOpenMakaiFolder: ->
+    dir = 'cache/kcs/resources/swf/ships'
+    dir = 'MyCache/kcs/resources/swf/ships' if process.platform == 'darwin'
+    try
+      fs.ensureDirSync path.join(window.EXROOT, dir)
+      openItem path.join(window.EXROOT, dir)
+    catch e
+      toggleModal '打开魔改目录', '打开失败，可能没有创建文件夹的权限'
   handleOpenScreenshotFolder: ->
     d = if process.platform == 'darwin' then path.join(path.homedir(), 'Pictures', 'Poi') else path.join(global.EXROOT, 'screenshots')
     try
@@ -234,6 +242,7 @@
       openItem d
     catch e
       toggleModal '打开截图目录', '打开失败，可能没有创建文件夹的权限'
+
   handleSetMuted: ->
     muted = !@state.muted
     config.set 'poi.content.muted', muted
@@ -271,7 +280,7 @@
         <Button onClick={@handleOpenCacheFolder} bsSize='small'><FontAwesome name='bolt' /></Button>
       </OverlayTrigger>
       <OverlayTrigger placement='left' overlay={<Tooltip>截图目录</Tooltip>}>
-        <Button onClick={@handleOpenScreenshotFolder} bsSize='small'><FontAwesome name='photo' /></Button>
+        <Button onClick={@handleOpenScreenshotFolder} onContextMenu={@handleOpenMakaiFolder} bsSize='small'><FontAwesome name='photo' /></Button>
       </OverlayTrigger>
       <OverlayTrigger placement='left' overlay={<Tooltip>自动适配页面</Tooltip>}>
         <Button onClick={@handleJustifyLayout} bsSize='small'><FontAwesome name='arrows-alt' /></Button>


node: update tokbox endpoints with new config

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -141,15 +141,15 @@

   { channelId } = req.body

+
   return res.status(400).send { err: 'Channel ID is required.'      }  unless channelId
-
   return res.status(200).send { sessionId: videoSessions[channelId] }  if videoSessions[channelId]

-  { API_KEY, API_SECRET } = KONFIG.tokbox
+  { apiKey, apiSecret } = KONFIG.tokbox

   OpenTok = require 'opentok'

-  opentok = new OpenTok API_KEY, API_SECRET
+  opentok = new OpenTok apiKey, apiSecret

   opentok.createSession (err, session) ->

@@ -165,11 +165,11 @@
   return res.status(400).send { err: "Session ID is required." } unless sessionId
   return res.status(400).send { err: "Role is required"        } unless role

-  { API_KEY, API_SECRET } = KONFIG.tokbox
+  { apiKey, apiSecret } = KONFIG.tokbox

   OpenTok = require 'opentok'

-  opentok = new OpenTok API_KEY, API_SECRET
+  opentok = new OpenTok apiKey, apiSecret

   token = opentok.generateToken sessionId, { role }


add button active state

add button active state

--- views/app.cjsx
+++ views/app.cjsx
@@ -297,13 +297,13 @@
         <Button onClick={@handleJustifyLayout} bsSize='small'><FontAwesome name='arrows-alt' /></Button>
       </OverlayTrigger>
       <OverlayTrigger placement='left' overlay={<Tooltip>{if @state.alwaysOnTop then __ 'Dont always on top' else __ 'Always on top'}</Tooltip>}>
-        <Button onClick={@handleSetAlwaysOnTop} bsSize='small'><FontAwesome name={if @state.alwaysOnTop then 'arrow-down' else 'arrow-up'} /></Button>
+        <Button onClick={@handleSetAlwaysOnTop} bsSize='small' className={if @state.alwaysOnTop then 'active' else ''}><FontAwesome name={if @state.alwaysOnTop then 'arrow-down' else 'arrow-up'} /></Button>
       </OverlayTrigger>
       <OverlayTrigger placement='left' overlay={<Tooltip>{__ 'Take a screenshot'}</Tooltip>}>
         <Button onClick={@handleCapturePage} bsSize='small'><FontAwesome name='camera-retro' /></Button>
       </OverlayTrigger>
       <OverlayTrigger placement='left' overlay={<Tooltip>{if @state.muted then __ 'Volume off' else __ 'Volume on'}</Tooltip>}>
-        <Button onClick={@handleSetMuted} bsSize='small'><FontAwesome name={if @state.muted then 'volume-off' else 'volume-up'} /></Button>
+        <Button onClick={@handleSetMuted} bsSize='small' className={if @state.muted then 'active' else ''}><FontAwesome name={if @state.muted then 'volume-off' else 'volume-up'} /></Button>
       </OverlayTrigger>
     </div>


webserver: fix style problem

webserver: fix style problem

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -56,7 +56,7 @@
 # routes ordered as before no particular structure

 # temp endpoints @cihangir will reorganize these - SY
-app.post '/-/teams/validate-token'                  , require './handlers/checktoken'
+app.post '/-/teams/validate-token'               , require './handlers/checktoken'
 app.post '/-/teams/create'                       , require './handlers/createteam'
 # fetches last members of team
 app.all  '/-/teams/:name/members'                , require './handlers/getteammembers'


Add lock tab

Add lock tab

--- views/app.cjsx
+++ views/app.cjsx
@@ -41,6 +41,7 @@
 settings = require path.join(ROOT, 'views', 'components', 'settings')

 # Main tabbed area
+lockedTab = false
 ControlledTabArea = React.createClass
   getInitialState: ->
     key: 0
@@ -56,6 +57,11 @@
     window.addEventListener 'keydown', (e) =>
       if e.keyCode is 9
         e.preventDefault()
+        return if lockedTab and e.repeat
+        lockedTab = true
+        setTimeout ->
+          lockedTab = false
+        , 200
         if e.ctrlKey || e.metaKey
           @handleCtrlOrCmdTabKeyDown()
         else


Use React.creatRef

Use React.creatRef

--- resources/assets/coffee/react/profile-page/historical.coffee
+++ resources/assets/coffee/react/profile-page/historical.coffee
@@ -25,6 +25,9 @@
     super props

     @id = "users-show-historical-#{osu.uuid()}"
+    @monthlyPlaycountsChartArea = React.createRef()
+    @replaysWatchedCountsChartArea = React.createRef()
+
     @charts = {}

@@ -57,7 +60,7 @@

           div
             className: 'page-extra__chart'
-            ref: @setMonthlyPlaycountsChartArea
+            ref: @monthlyPlaycountsChartArea

       h3
@@ -117,7 +120,7 @@

           div
             className: 'page-extra__chart'
-            ref: @setReplaysWatchedCountsChartArea
+            ref: @replaysWatchedCountsChartArea

   chartUpdate: (attribute, area) =>
@@ -179,13 +182,13 @@
   monthlyPlaycountsChartUpdate: =>
     return if !@hasMonthlyPlaycounts()

-    @chartUpdate 'monthly_playcounts', @monthlyPlaycountsChartArea
+    @chartUpdate 'monthly_playcounts', @monthlyPlaycountsChartArea.current

   replaysWatchedCountsChartUpdate: =>
     return if !@hasReplaysWatchedCounts()

-    @chartUpdate 'replays_watched_counts', @replaysWatchedCountsChartArea
+    @chartUpdate 'replays_watched_counts', @replaysWatchedCountsChartArea.current

   updateTicks: (chart, data) =>
@@ -207,11 +210,3 @@
     for own _name, chart of @charts
       @updateTicks chart
       chart.resize()
-
-
-  setMonthlyPlaycountsChartArea: (ref) =>
-    @monthlyPlaycountsChartArea = ref
-
-
-  setReplaysWatchedCountsChartArea: (ref) =>
-    @replaysWatchedCountsChartArea = ref


Remove empty, unused method

Remove empty, unused method

--- app/assets/javascripts/rglossa/react/components/left/metadata_categories.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/left/metadata_categories.jsx.coffee
@@ -60,7 +60,6 @@
     metadataValueIds

-  metadataCategory: (category) ->

   render: ->
     `<div id="left-sidebar" className="span3 sidebar">


added missing currency symbols to subscription formatter

--- services/web/app/coffee/Features/Subscription/SubscriptionFormatters.coffee
+++ services/web/app/coffee/Features/Subscription/SubscriptionFormatters.coffee
@@ -6,7 +6,14 @@
 	EUR: "€"
 	USD: "$"
 	GBP: "£"
-
+	SEK: "kr"
+	CAD: "$"
+	NOK: "kr"
+	DKK: "kr"
+	AUD: "$"
+	NZD: "$"
+	CHF: "Fr"
+	SGD: "$"

 module.exports =


Workspace: Using forwardEvent to pass event to delegate.

--- client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -83,11 +83,8 @@
       content
     }

-    editor.on "OpenedAFile", (file, content) =>
-      @emit "OpenedAFile", file, content
-
-    editor.on "EditorDidSave", =>
-      @emit "EditorDidSave"
+    @forwardEvent editor, 'EditorDidSave'
+    @forwardEvent editor, 'OpenedAFile'

     pane.addSubView editor
     @editors.push editor


remove chia expect

remove chia expect

--- shared/specs/components/enroll/confirm-join-course.spec.cjsx
+++ shared/specs/components/enroll/confirm-join-course.spec.cjsx
@@ -1,4 +1,4 @@
-{Testing, expect, sinon, _, ReactTestUtils} = require 'shared/specs/helpers'
+{Testing, sinon, _, ReactTestUtils} = require 'shared/specs/helpers'

 React = require 'react'

@@ -82,7 +82,7 @@
         courseEnrollmentActions={@courseEnrollmentActions}
         courseEnrollmentStore={COURSE_ENROLLMENT_STORE_WITHOUT_CONFLICT}
         />)
-      expect(wrapper.find('.conflicts li')).to.be.empty
+        expect(wrapper.find('.conflicts li').length).toEqual(0)
       undefined

     it 'displays info about the course being joined', ->


Updated address-selection to use existing address on initializing

--- app/assets/javascripts/sprangular/directives/addressSelection.coffee
+++ app/assets/javascripts/sprangular/directives/addressSelection.coffee
@@ -15,7 +15,7 @@
         found = _.find addresses, (existing) ->
           existing.same($scope.address)

-        $scope.existingAddress = true if found
+        $scope.toggleExistingAddress() if found

     $scope.toggleExistingAddress = ->
       $scope.existingAddress = !$scope.existingAddress


Styled single update page comments

Styled single update page comments

--- server/lib/crawler/staticpages/activity.coffee
+++ server/lib/crawler/staticpages/activity.coffee
@@ -145,14 +145,42 @@
   userInteractionMeta = createUserInteractionMeta \
     activityContent.numberOfLikes, activityContent.numberOfComments

-  if activityContent?.comments?.length? > 0
-    comments         = (createCommentNode(comment) for comment in activityContent.comments)
-    commentsContent  = "
Comments:
"
-    commentsContent += "
"
-    commentsContent += comments.join("")
-    commentsContent += "
"
-  else
-    commentsContent = ""
+  # if activityContent?.comments?.length? > 0
+  #   comments         = (createCommentNode(comment) for comment in activityContent.comments)
+  #   commentsContent  = "
Comments:
"
+  #   commentsContent += "
"
+  #   commentsContent += comments.join("")
+  #   commentsContent += "
"
+  # else
+  #   commentsContent = ""
+
+  commentsList = ""
+  if activityContent?.comments
+    for comment in activityContent.comments
+      avatarUrl = "https://gravatar.com/avatar/#{comment.authorHash}?size=90&d=https%3A%2F%2Fapi.koding.com%2Fimages%2Fdefaultavatar%2Fdefault.avatar.40.png"
+      commentsList +=
+        """
+

+
+

+              #{comment.authorName}
+

#{comment.body}

+

+

+        """
+
+  commentsContent =
+    """
+

+

+

+

+              #{commentsList}
+

+

+

+

+    """

   tags = ""
   if activityContent?.tags?.length > 0
@@ -208,8 +236,8 @@

               #{createdAt}

+          #{commentsContent}

-      #{commentsContent}
     """
   return content


 Merge branch 'master.jobsEndpoint' into 'master'

Master.Jobs Endpoint

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -250,6 +250,17 @@
 app.get "/-/version", (req, res) ->
   res.jsonp(version:KONFIG.version)

+app.get "/-/jobs", (req, res) ->
+
+  request = require 'request'
+  options =
+    url   : 'https://api.lever.co/v0/postings/koding'
+    json  : yes
+
+  request options, (err, r, postings) ->
+    res.send 404 if err
+    res.json postings
+
 app.get "/sitemap:sitemapName", (req, res)->
   {JSitemap}       = koding.models


landing: team create/join steps hide button loader when it asks for two factor auth

--- client/landing/site.landing/coffee/team/forms/teamjointabform.coffee
+++ client/landing/site.landing/coffee/team/forms/teamjointabform.coffee
@@ -57,5 +57,6 @@

   showTwoFactor: ->

+    @button.hideLoader()
     @$('.two-factor').removeClass 'hidden'
     @tfcode.setFocus()


fixing environment duplication in autocomplete issue

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/EnvironmentManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/EnvironmentManager.coffee
@@ -10,6 +10,20 @@
 		"quote",
 		"center"
 	]
+
+	snippetNames = [
+		"array",
+		"figure",
+		"tabular",
+		"table",
+		"list",
+		"enumerate",
+		"itemize",
+		"frame",
+		"thebibliography"
+	]
+
+	environmentNames = snippetNames.concat environments

 	staticSnippets = for env in environments
 		{
@@ -123,10 +137,11 @@
 		result = []
 		iterations = 0
 		while match = re.exec(text)
-			result.push {name: match[1], whitespace: match[2]}
-			iterations += 1
-			if iterations >= 1000
-				return result
+			if match[1] not in environmentNames
+				result.push {name: match[1], whitespace: match[2]}
+				iterations += 1
+				if iterations >= 1000
+					return result
 		return result

 	class EnvironmentManager


Support delayed eventing in Security capability

--- lib/capabilities/security/SecurityRule.coffee
+++ lib/capabilities/security/SecurityRule.coffee
@@ -25,6 +25,7 @@
       @_server.adapters.onEventAtPath path, 'aspectData:change',
         (args...) => @trigger('connection:change', args...)

+    @listenTo this, 'mode:change', @cancelDelayTimeout
     @listenTo this, 'mode:change connection:change', @evaluate

   # Override me with a method that tests whether the rule is currently
@@ -43,21 +44,28 @@
     if !@isValid()
       @log 'warn', 'Unable to evaluate - rule not currently valid'
       return
-
     mode = @currentModeParameters()
     state = @state()
     if !state?
       @log 'debug', "State cannot be determined"
+      @cancelDelayTimeout()
       # XXX Do something real
     else if state and mode.eventImportance?
       if @ongoingEvent?
         if @ongoingEvent.get('importance') != mode.eventImportance
           # The ongoing event is of the wrong importance; replace it
           @endOngoingEvent()
-          @createOngoingEvent(mode.eventImportance)
-      else # There is no event, and there should be
-        @createOngoingEvent(mode.eventImportance)
+          @createOngoingEvent()
+      else # There is no event, and there should be (now or soon)
+        if mode.eventDelay?
+          if !@delayTimeout? # If delay has already begun, leave it be
+            @delayTimeout = setTimeout((=> @createOngoingEvent()),
+              mode.eventDelay * 1000)
+            @log 'debug', "Event will fire in #{mode.eventDelay}s."
+        else
+          @createOngoingEvent()
     else
+      @cancelDelayTimeout()
       if @ongoingEvent?
         @endOngoingEvent() # This event is no longer appropriate

@@ -75,9 +83,16 @@
       uniquePaths.push(path) unless _.find(uniquePaths, (p) -> _.isEqual(p, path))
     uniquePaths

+  cancelDelayTimeout: ->
+    if @delayTimeout?
+      @log 'debug', 'Delay timeout cancelled.'
+      clearTimeout(@delayTimeout)
+    delete @delayTimeout
+
   createOngoingEvent: (importance) ->
+    @cancelDelayTimeout()
     @ongoingEvent = @_parent.createEvent
-      importance: importance
+      importance: @currentModeParameters().eventImportance
       title: @buildMessage('presentTitle')

   endOngoingEvent: ->


fix upload of marking details

fix upload of marking details

--- src/app/units/partials/directives/unit-marking-submissions-directive.coffee
+++ src/app/units/partials/directives/unit-marking-submissions-directive.coffee
@@ -7,7 +7,7 @@
 .directive('submissionMarkingContext', ->
   restrict: 'E'
   templateUrl: 'units/partials/templates/submission-marking-context.tpl.html'
-  controller: ($scope, alertService, Task) ->
+  controller: ($scope, alertService, Task, csvResultService) ->
     $scope.zipMarkingFiles = { file: { name: 'Re-Zipped Marked Files', type: 'zip' } }
     $scope.csvMarkingFiles = { file: { name: 'Marks.csv', type: 'csv' } }
     $scope.uploadSubmissionUrl = $scope.readyToMarkSubmissionsUrl =
@@ -16,10 +16,7 @@
     $scope.isReady =
       csv: null
       zip: null
-    $scope.taskUploadResults = null
     $scope.onMarkingUploadSuccess = (response) ->
-      alertService.add("success", "Uploaded marked tasks.", 2000)
-      $scope.taskUploadResults = response
-    $scope.dismissResults = ->
-      $scope.taskUploadResults = null
+      csvResultService.show "Marking CSV and ZIP upload", response
+      $scope.unit.refreshStudents()
 )


Fix rendering of long previews, stop cutting off last line in short ones

--- services/web/public/coffee/ide/binary-files/controllers/BinaryFileController.coffee
+++ services/web/public/coffee/ide/binary-files/controllers/BinaryFileController.coffee
@@ -92,11 +92,9 @@
 					# show dots when payload is closs to cutoff
 					if data.length >= (TWO_MEGABYTES - 200)
 						$scope.textPreview.shouldShowDots = true
-					try
 						# remove last partial line
 						data = data.replace(/\n.*$/, '')
-					finally
-						$scope.textPreview.data = data
+					$scope.textPreview.data = data
 					$timeout(setHeight, 0)
 				.catch (error) ->
 					console.error(error)


unidentified types are not json

unidentified types are not json

--- src/engine/json-schema-core/3-5-primitive-types-extra.coffee
+++ src/engine/json-schema-core/3-5-primitive-types-extra.coffee
@@ -44,4 +44,4 @@
         return false
     return true

-  return true
+  return false


Rename root to workspace

Rename root to workspace

--- source/coffee/index.coffee
+++ source/coffee/index.coffee
@@ -12,7 +12,7 @@
     self = this

     self.items = ko.mapping.fromJS [
-      name: "Root"
+      name: "Workspace"
       nodes: []
       children: [
         name: "Unprovisioned"


Event.preventDefault in form elements

Event.preventDefault in form elements

--- source/quo.gestures.coffee
+++ source/quo.gestures.coffee
@@ -15,7 +15,7 @@
   _handlers       = {}
   _data           = null
   _originalEvent  = null
-  _disabled_tags  = ["input", "select", "textarea", "button"]
+  _disabled_tags  = ["input", "select", "textarea"]

   $$(document).ready ->
     environment = $$ document.body
@@ -33,7 +33,7 @@

   # Private methods
   _start = (ev) ->
-    return if ev.srcElement.tagName.toLowerCase() in _disabled_tags
+    return ev.stopPropagation() if ev.srcElement.tagName.toLowerCase() in _disabled_tags
     _started = true
     _originalEvent = ev or event
     _data = _getFingersData(ev)


Consider the original top and left when testing whether a draggable moved.

--- spec/draggable_spec.coffee
+++ spec/draggable_spec.coffee
@@ -115,6 +115,10 @@
     describe 'after having been dragged', ->

       beforeEach ->
+        for edge in ['top', 'left']
+          value = parseInt @$draggable.css(edge)
+          @["original#{edge.charAt(0).toUpperCase()}#{edge.slice(1)}"] = if SpecHelper.isNaN(value) then 0 else value
+
         # Drag the draggable a standard distance
         @$draggable.simulate 'drag',
           moves: 1
@@ -125,8 +129,8 @@
         expect(@$draggable).toHaveCss { position: 'relative' }

       it 'should find itself the drag distance from its original top', ->
-        expect(@$draggable).toHaveCss { top: "#{options.dragDistance}px" }
+        expect(@$draggable).toHaveCss { top: "#{@originalTop + options.dragDistance}px" }

       it 'should find itself the drag distance from its original left', ->
-        expect(@$draggable).toHaveCss { left: "#{options.dragDistance}px" }
+        expect(@$draggable).toHaveCss { left: "#{@originalLeft + options.dragDistance}px" }


add tests for the new feature

add tests for the new feature

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -1446,3 +1446,90 @@
       keydown '.'
       expect(editor.getText()).toBe "abcabc"
       expect(editor.getCursorScreenPosition()).toEqual [0, 5]
+
+  describe "the ctrl-a/ctrl-x keybindings", ->
+    beforeEach ->
+      editor.setText('123\nab45\ncd-67ef\nab-5\na-bcdef')
+      editor.setCursorBufferPosition [0, 0]
+      editor.addCursorAtBufferPosition [1, 0]
+      editor.addCursorAtBufferPosition [2, 0]
+      editor.addCursorAtBufferPosition [3, 3]
+      editor.addCursorAtBufferPosition [4, 0]
+
+    describe "increasing numbers", ->
+      it "increases the next number", ->
+        keydown('a', ctrl: true)
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]]
+        expect(editor.getText()).toBe '124\nab46\ncd-66ef\nab-4\na-bcdef'
+
+      it "repeats with .", ->
+        keydown 'a', ctrl: true
+        keydown '.'
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]]
+        expect(editor.getText()).toBe '125\nab47\ncd-65ef\nab-3\na-bcdef'
+
+      it "can have a count", ->
+        keydown '5'
+        keydown 'a', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 2], [4, 0]]
+        expect(editor.getText()).toBe '128\nab50\ncd-62ef\nab0\na-bcdef'
+
+      it "can make a negative number positive, change number of digits", ->
+        keydown '9'
+        keydown '9'
+        keydown 'a', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 4], [2, 3], [3, 3], [4, 0]]
+        expect(editor.getText()).toBe '222\nab144\ncd32ef\nab94\na-bcdef'
+
+      it "does nothing when cursor is after the number", ->
+        editor.setCursorBufferPosition [2, 5]
+        keydown 'a', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[2, 5]]
+        expect(editor.getText()).toBe '123\nab45\ncd-67ef\nab-5\na-bcdef'
+
+      it "does nothing on an empty line", ->
+        editor.setText('\n')
+        editor.setCursorBufferPosition [0, 0]
+        editor.addCursorAtBufferPosition [1, 0]
+        keydown 'a', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 0], [1, 0]]
+        expect(editor.getText()).toBe '\n'
+
+    describe "decreasing numbers", ->
+      it "decreases the next number", ->
+        keydown('x', ctrl: true)
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]]
+        expect(editor.getText()).toBe '122\nab44\ncd-68ef\nab-6\na-bcdef'
+
+      it "repeats with .", ->
+        keydown 'x', ctrl: true
+        keydown '.'
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]]
+        expect(editor.getText()).toBe '121\nab43\ncd-69ef\nab-7\na-bcdef'
+
+      it "can have a count", ->
+        keydown '5'
+        keydown 'x', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 4], [4, 0]]
+        expect(editor.getText()).toBe '118\nab40\ncd-72ef\nab-10\na-bcdef'
+
+      it "can make a positive number negative, change number of digits", ->
+        keydown '9'
+        keydown '9'
+        keydown 'x', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 1], [1, 4], [2, 5], [3, 5], [4, 0]]
+        expect(editor.getText()).toBe '24\nab-54\ncd-166ef\nab-104\na-bcdef'
+
+      it "does nothing when cursor is after the number", ->
+        editor.setCursorBufferPosition [2, 5]
+        keydown 'x', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[2, 5]]
+        expect(editor.getText()).toBe '123\nab45\ncd-67ef\nab-5\na-bcdef'
+
+      it "does nothing on an empty line", ->
+        editor.setText('\n')
+        editor.setCursorBufferPosition [0, 0]
+        editor.addCursorAtBufferPosition [1, 0]
+        keydown 'x', ctrl: true
+        expect(editor.getCursorBufferPositions()).toEqual [[0, 0], [1, 0]]
+        expect(editor.getText()).toBe '\n'


Call atom.show() in spec bootstrap

Call atom.show() in spec bootstrap

--- spec/spec-bootstrap.coffee
+++ spec/spec-bootstrap.coffee
@@ -1,4 +1,5 @@
 require 'atom'
+atom.show()
 {runSpecSuite} = require 'jasmine-helper'

 document.title = "Spec Suite"


fixed bug with setting font after init

fixed bug with setting font after init

--- src/core/methods/attach.coffee
+++ src/core/methods/attach.coffee
@@ -83,7 +83,7 @@
       starting = true

       checkValue = (o, a, m, v) ->
-        if a of o[m]
+        if validObject(o[m]) and a of o[m]
           if validObject(o[m][a])
             if o[m][a].process
               o[m][a].value = o[m][a].process(v)


Use error handler for exceptions when deleting sequences

--- app/assets/javascripts/farmbot_app/controllers/sequence_controller.js.coffee
+++ app/assets/javascripts/farmbot_app/controllers/sequence_controller.js.coffee
@@ -67,7 +67,7 @@
   $scope.deleteStep = (index) ->
     Data
       .destroy('step', $scope.sequence.steps[index]._id)
-      .catch((e) -> console.error e)
+      .catch(nope)

 # The sequence controller supports the WYSIWYG sequence editor.
 angular.module('FarmBot').controller "SequenceController", [


Use multiple subdomains for map view

Use multiple subdomains for map view

--- app/assets/javascripts/modules/map/leaflet-plugins/gibs_tile_layer.js.coffee
+++ app/assets/javascripts/modules/map/leaflet-plugins/gibs_tile_layer.js.coffee
@@ -2,7 +2,7 @@

 ns.GibsTileLayer = do (L, ProjectionSwitchingLayer = ns.ProjectionSwitchingLayer) ->

-  gibsUrl = 'https://map1c.vis.earthdata.nasa.gov/wmts-{endpoint}/wmts.cgi?{timeparam}SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER={product}&STYLE=&TILEMATRIXSET={projection}_{resolution}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image%2F{format}{extra}'
+  gibsUrl = 'https://map1{s}.vis.earthdata.nasa.gov/wmts-{endpoint}/wmts.cgi?{timeparam}SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER={product}&STYLE=&TILEMATRIXSET={projection}_{resolution}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image%2F{format}{extra}'

   parent = ProjectionSwitchingLayer.prototype


Added paging controls and sort order constants.

--- src/CarvoyantClient.coffee
+++ src/CarvoyantClient.coffee
@@ -12,8 +12,12 @@
   ENGINE_SPEED: "GEN_RPM"
   FUEL_LEVEL: "GEN_FUELLEVEL"
   FUEL_RATE: "GEN_FUELRATE"
-  ENGINE_TEMPERATURE: "GEN_ENGINE_COOLANT_TEMP"
+  ENGINE_TEMP: "GEN_ENGINE_COOLANT_TEMP"
   MAX_SPEED: "GEN_SPEED"
+
+sortOrder =
+  ASCENDING: "asc"
+  DESCENDING: "desc"

 class CarvoyantRequest
   constructor: (@apiKey, @securityToken, @method, @uri) ->
@@ -38,6 +42,14 @@

   sortOrder: (sortOrder) ->
     @query["sortOrder"] = sortOrder
+    this
+
+  searchLimit: (limit) ->
+    @query["searchLimit"] = limit
+    this
+
+  searchOffset: (offset) ->
+    @query["searchOffset"] = offset
     this

   exec: (cb) ->
@@ -81,3 +93,4 @@

 module.exports = CarvoyantClient
 module.exports.dataKeys = dataKeys
+module.exports.sortOrder = sortOrder


No fat arrow needed

No fat arrow needed

--- js-library/src/js/models/fact.coffee
+++ js-library/src/js/models/fact.coffee
@@ -31,7 +31,7 @@

   # This may look like some magic, but here we expose the Fact.blur/focus/click
   # methods
-  createEventHandlers: (events) =>
+  createEventHandlers: (events) ->
     @createEventHandler event_handle for event_handle in events

   createEventHandler: (event_handle) =>


fix missing 'translations' parameter

fix missing 'translations' parameter

--- jsapp/xlform/src/view.choices.coffee
+++ jsapp/xlform/src/view.choices.coffee
@@ -20,9 +20,10 @@
       if cardText.find('.card__buttons__multioptions.js-expand-multioptions').length is 0
         cardText.prepend $.parseHTML($viewTemplates.row.expandChoiceList())
       @$el.html (@ul = $("<ul>", class: @ulClasses))
+      _ts = @model.getSurvey().translations
       if @row.get("type").get("rowType").specifyChoice
         for option, i in @model.options.models
-          new OptionView(model: option, cl: @model).render().$el.appendTo @ul
+          new OptionView(model: option, cl: @model, translations: _ts).render().$el.appendTo @ul
         if i == 0
           while i < 2
             @addEmptyOption("Option #{++i}")


listened events in a more elegant way

listened events in a more elegant way

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -38,10 +38,9 @@

     @_state = 'public'

-    @getListView().on "ItemIsBeingDestroyed", ()=>
-      @addNoItemFoundWidget()
-    @getListView().on "ItemWasAdded", ()=>
-      @removeNoItemFoundWidget()
+    @getListView().on "ItemIsBeingDestroyed", @bound "addNoItemFoundWidget"
+
+    @getListView().on "ItemWasAdded", @bound "removeNoItemFoundWidget"

     @scrollView.on 'scroll', (event) =>
       if event.delegateTarget.scrollTop > 0
@@ -53,13 +52,13 @@
         @activityHeader.unsetClass "scrolling-up-outset"
         @activityHeader.liveUpdateButton.setValue on

-  removeNoItemFoundWidget:()->
+  removeNoItemFoundWidget:->
     if @getItemCount() is 1
       {noItemFoundWidget, noMoreItemFoundWidget} = @getOptions()
       @scrollView.addSubView noMoreItemFoundWidget if noMoreItemFoundWidget
       noItemFoundWidget.destroy() if noItemFoundWidget

-  addNoItemFoundWidget:()->
+  addNoItemFoundWidget:->
     if @getItemCount() is 0
       {noItemFoundWidget, noMoreItemFoundWidget} = @getOptions()
       noMoreItemFoundWidget.destroy() if noMoreItemFoundWidget


More module name sanity checking

More module name sanity checking

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -30,6 +30,8 @@
     return unless definition.noflo

     prefix = @getModulePrefix definition.name
+    if moduleName[0] is '/'
+      moduleName.substr 1
     if definition.noflo.components
       for name, cPath of definition.noflo.components
         @registerComponent prefix, name, "/#{moduleName}/#{cPath}"


use group rounds collection

use group rounds collection

--- src/node_modules/group-rounds/group-rounds-controller.coffee
+++ src/node_modules/group-rounds/group-rounds-controller.coffee
@@ -7,6 +7,7 @@

   $scope.isAdmin = group.isAdmin(AuthService.getCurrentUser())
   $scope.group = group
+  rounds = group.rounds

   handleError = (err) ->
     if (err and err != "backdrop click")
@@ -20,8 +21,10 @@
         group: group
       )
     ).result.then (round) ->
+
       # TODO add new round in proper sort order
-      group.rounds.models.splice(0, 0, round)
+      rounds.add(round, at: 0)
+
     , handleError

   $scope.editRound = (round, index) ->
@@ -31,5 +34,8 @@
         group: group
       )
     ).result.then (round) ->
-      group.rounds.models[index] = round
+
+      rounds.at(index).set(round.serialize())
+      # TODO rounds.sort()
+
     , handleError