Basic type error display

Basic type error display

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -5868,6 +5868,7 @@
 exports.syntaxedExpHtml = syntaxedExpHtml
 exports.syntaxedType = syntaxedType
 exports.prettyPrint = prettyPrint
+exports.plainPrettyPrint = plainPrettyPrint

 # exports.compileModule = (source) ->
 #   """


Working type based completion

Working type based completion

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -626,6 +626,8 @@
         lambda = (fn_ extraParams, sortedCall)
         compiled = macroCompile ctx, lambda
         retrieve call, lambda
+        # TODO: massive hack, erase inserted scope, will have to figure out how to fix this better
+        ctx.savedScopes[ctx.savedScopes.length - 1].definitions = newMap()
         compiled
       else
         compiled =
@@ -3283,7 +3285,7 @@
 filterSet =
 filterMap = (fn, set) ->
   initialized = newMap()
-  for key, val of set.values when fn key
+  for key, val of set.values when fn key, val
     addToMap initialized, key, val
   initialized

@@ -3425,7 +3427,7 @@
   substituionFail "could not unify #{(safePrintType t1)}, #{(safePrintType t2)}"

 bindVariable = (variable, type) ->
-  if type instanceof TypeVariable and variable.name is type.name
+  if type.TypeVariable and variable.name is type.name
     emptySubstitution()
   else if inSet (findFree type), variable.name
     substituionFail "occurs check failed for #{variable.name}"
@@ -3566,15 +3568,15 @@
   vars: map.values

 findFree = (type) ->
-  if type instanceof TypeVariable
+  if type.TypeVariable
     newMapWith type.name, type.kind
-  else if type instanceof TypeApp
+  else if type.TypeApp
     concatMaps (findFree type.op), (findFree type.arg)
-  else if type instanceof Constrained
+  else if type.Constrained
     concatMaps (findFreeInList type.constraints), (findFree type.type)
-  else if type instanceof ClassConstraint
+  else if type.ClassConstraint
     findFree type.types
-  else if type instanceof Types
+  else if type.Types
     findFreeInList type.types
   else
     newMap()
@@ -3583,7 +3585,7 @@
   concatMaps (map findFree, list)...

 freshInstance = (ctx, type) ->
-  throw new Error "not a forall in freshInstance #{safePrintType type}" unless type instanceof ForAll
+  throw new Error "not a forall in freshInstance #{safePrintType type}" unless type and type.ForAll
   freshes = map ((kind) -> ctx.freshTypeVariable kind), type.kinds
   (substitute freshes, type).type

@@ -3599,30 +3601,31 @@
   all (map isNormalizedConstraintArgument, constraint.types.types)

 isNormalizedConstraintArgument = (type) ->
-  if type instanceof TypeVariable
-    yes
-  else if type instanceof TypeConstr
-    no
-  else if type instanceof TypeApp
-    isNormalizedConstraintArgument type.op.type
+  if type
+    if type.TypeVariable
+      yes
+    else if type.TypeConstr
+      no
+    else if type.TypeApp
+      isNormalizedConstraintArgument type.op.type

 typeEq = (a, b) ->
-  if a instanceof TypeVariable and b instanceof TypeVariable or
-      a instanceof TypeConstr and b instanceof TypeConstr
+  if a.TypeVariable and b.TypeVariable or
+      a.TypeConstr and b.TypeConstr
     a.name is b.name
-  else if a instanceof QuantifiedVar and b instanceof QuantifiedVar
+  else if a.QuantifiedVar and b.QuantifiedVar
     a.var is b.var
-  else if a instanceof TypeApp and b instanceof TypeApp
+  else if a.TypeApp and b.TypeApp
     (typeEq a.op, b.op) and (typeEq a.arg, b.arg)
-  else if a instanceof ForAll and b instanceof ForAll
+  else if a.ForAll and b.ForAll
     typeEq a.type, b.type
-  else if a instanceof Constrained and b instanceof Constrained
+  else if a.Constrained and b.Constrained
     (all zipWith typeEq, a.constraints, b.constraints) and
       (typeEq a.type, b.type)
-  else if a instanceof ClassConstraint and b instanceof ClassConstraint
+  else if a.ClassConstraint and b.ClassConstraint
     a.className is b.className and typeEq a.types, b.types
-  else if a instanceof Types and b instanceof Types
+  else if a.Types and b.Types
     all zipWith typeEq, a.types, b.types
   else
     no
@@ -3691,14 +3694,15 @@
     applyKindFn (applyKindFn fn, arg), args...

 isConstructor = (type) ->
-  type instanceof TypeApp
+  type.TypeApp

 kind = (type) ->
   if type.kind
     type.kind
-  else if type instanceof TypeApp
+  else if type.TypeApp
     (kind type.op).to
   else
+    console.log "invalid", type
     throw new Error "Invalid type in kind"

 kindsEq = (k1, k2) ->
@@ -3812,7 +3816,7 @@
     throw new Error "Unrecognized type in printType"

 collectArgs = (type) ->
-  if type instanceof TypeApp
+  if type.TypeApp
     op = collectArgs type.op
     arg = collectArgs type.arg
     if (Array.isArray op) and (Array.isArray arg) and
@@ -4051,10 +4055,10 @@
     toInject = collectRequiresFor moduleName
     ctx = injectedContext toInject
     {js, ast} = compileCtxAstToJs topLevel, ctx, (astFromSource "(#{source})", -1, -1)
+    (finalizeTypes ctx, ast)
     replaceOrAddToMap compiledModules, moduleName,
       declared: (subtractContexts ctx, (injectedContext toInject)) # must recompute because ctx is mutated
       js: js
-    (finalizeTypes ctx, ast)
     js: js
     ast: ast
     types: typeEnumaration ctx
@@ -4152,10 +4156,10 @@

 findMatchingDefinitionsOnType = (type, definitions) ->
   ctx = new Context
-  console.log type, type.type.TypeConstr
+  validDefinitions = filterMap ((name, def) -> def.type?), definitions # TODO: filter before
   typesUnify = (def) ->
     not isFailed mostGeneralUnifier (freshInstance ctx, def.type).type, type.type
-  [typed, notTyped] = partitionMap typesUnify, definitions
+  [typed, notTyped] = partitionMap typesUnify, validDefinitions
   # allDefs = join (setToArray typed), (setToArray notTyped)
   allDefs = concatMaps typed, notTyped # TODO: don't use object key ordering for ordering
   values mapMap (__ prettyPrint, _type), allDefs
@@ -4228,6 +4232,9 @@
     if expression.tea
       expression.tea = substitute ctx.substitution, expression.tea
     return
+  for scope in ctx.savedScopes when scope?
+    for name, def of values scope.definitions when def.type
+      def.type = substitute ctx.substitution, def.type
   return

 # end of API


Check for already defined constraint dictionaries in references

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2385,11 +2385,12 @@
 #       ^.___ not necessarily, we could have a tuple of functions or similar
 irDefinitionTranslate = (ctx, {type, expression, id}) ->
   finalType = substitute ctx.substitution, type
-  reducedConstraints =
+  allConstraints = (addConstraintsFrom ctx, {id, type}, finalType).constraints
+  reducedConstraints =
     # if declaredType = ctx.typeForId id
     #   constraintsFromCanonicalType ctx, declaredType, finalType
     # else
-      reduceConstraints ctx, (addConstraintsFrom ctx, {id, type}, finalType).constraints
+      reduceConstraints ctx, allConstraints
   ctx.updateClassParams()
   # TODO: what about the class dictionaries order?
   counter = {}
@@ -2455,7 +2456,8 @@
     translateIr ctx, (irMethod type, name)
   else
     finalType = addConstraintsFrom ctx, {id, type}, substitute ctx.substitution, type
-    classParams = dictsForConstraint ctx, finalType.constraints
+    additionalConstraints = filter ((c) -> not isAlreadyParametrized ctx, c), finalType.constraints
+    classParams = dictsForConstraint ctx, additionalConstraints
     params = map validIdentifier, arity
     if classParams.length > 0
       (irFunctionTranslate ctx,
@@ -5458,12 +5460,12 @@
   concat-map (fn [what over]
     (concat (map what over)))

-  concat-with (fn [with what]
-    (fold join-with empty what)
-    join-with (fn [x joined]
-      (concat {joined with x})))
-  """
-  "(size (concat-map (fn [x] {}) {1 2 3}))", 0
+  concat-suffix (fn [suffix what]
+    (fold join-suffix empty what)
+    join-suffix (fn [x joined]
+      (concat {joined suffix x})))
+  """
+  """(size (concat-suffix {1} (concat-map (fn [x] {{1}}) {1 2 3})))""", 6

   """recursive overloaded functions"""
   """


Fixed #46

Fixed #46

--- t9n/fr.coffee
+++ t9n/fr.coffee
@@ -42,11 +42,11 @@
   with: "avec"

-info:
-  emailSent: "Email envoyé"
-  emailVerified: "Email verifié"
-  passwordChanged: "Mot de passe modifié"
-  passwordReset: "Mot de passe réinitialisé"
+  info:
+    emailSent: "Email envoyé"
+    emailVerified: "Email verifié"
+    passwordChanged: "Mot de passe modifié"
+    passwordReset: "Mot de passe réinitialisé"

   error:


adding vendors do toaster config file

adding vendors do toaster config file

--- toaster.coffee
+++ toaster.coffee
@@ -8,5 +8,11 @@
 toast 'www/src'
 	alias: 'theoricus'
 	expose: 'window'
+	vendors: [
+		"www/vendors/history.adapter.native.js",
+		"www/vendors/history.js",
+		"www/vendors/jade.runtime.js",
+		"www/vendors/jquery.js"
+	]
 	release: 'lib/theoricus.js'
 	debug: 'lib/theoricus.debug.js'


Fix events url variable

Fix events url variable

--- lib/assets/javascripts/_request_manager.js.coffee
+++ lib/assets/javascripts/_request_manager.js.coffee
@@ -38,13 +38,13 @@
           $target.html(data)

           self._title(xhr.getResponseHeader('X-Wiselinks-Title'))
-          self._done($target, status, state.url, data)
+          self._done($target, status, state, data)
     ).fail(
       (xhr, status, error) ->
-        self._fail($target, status, state.url, error)
+        self._fail($target, status, state, error)
     ).always(
       (data_or_xhr, status, xhr_or_error)->
-        self._always($target, status, state.url)
+        self._always($target, status, state)
     )

   _assets_changed: (assets_digest) ->


Tweak process doc syntax

Tweak process doc syntax

--- src/nack/process.coffee
+++ src/nack/process.coffee
@@ -6,12 +6,6 @@
 {EventEmitter} = require 'events'
 {BufferedReadStream, BufferedLineStream} = require './buffered'

-# Generates a random sock path.
-tmpSock = ->
-  pid  = process.pid
-  rand = Math.floor Math.random() * 10000000000
-  "/tmp/nack." + pid + "." + rand + ".sock"
-
 # **Process** manages a single Ruby worker process.
 #
 # A Process requires a path to a rackup config file _(config.ru)_.
@@ -26,32 +20,44 @@
 #
 # A Process has 5 states:
 #
-#   `null`: Not started or dead
-#   `spawning`: Is booting and can't accept a connection yet
-#   `ready`: Is ready to accept a connection and handle a request
-#   `busy`: Is currently handling a request, not ready
-#   `quitting`: Was set a kill signal and is shutting down
+# > `null`: Not started or dead
+# >
+# > `spawning`: Is booting and can't accept a connection yet
+# >
+# > `ready`: Is ready to accept a connection and handle a request
+# >
+# > `busy`: Is currently handling a request, not ready
+# >
+# > `quitting`: Was set a kill signal and is shutting down
 #
 # Anytime a process changes states, an event is fired with the new
 # state name. When the process becomes `ready`, a `ready` is fired.
 #
 # Other events:
 #
-#   Event: `error`
-#   function (exception) { }
-#   Emitted when an error occurs.
-#
-#   Event: `spawn`
-#   function (exception) { }
-#   Emitted when the process moves from `spawning` to `ready` state
-#
-#   Event: `exit`
-#   function () { }
-#   Emitted when the Process terminates.
-#
-#   Event: `idle`
-#   function () { }
-#   Emitted when the Process times out because of inactivity.
+# > **Event: 'error'**
+# >
+# > `function (exception) { }`
+# >
+# > Emitted when an error occurs.
+# >
+# > **Event: 'spawn'**
+# >
+# > `function (exception) { }`
+# >
+# > Emitted when the process moves from `spawning` to `ready` state.
+# >
+# > **Event: 'exit'**
+# >
+# > `function () { }`
+# >
+# > Emitted when the Process terminates.
+# >
+# > **Event: 'idle'**
+# >
+# > `function () { }`
+# >
+# > Emitted when the Process times out because of inactivity.
 #
 exports.Process = class Process extends EventEmitter
   constructor: (@config, options) ->
@@ -81,7 +87,8 @@
     else
       exec 'which nackup', (error, stdout, stderr) =>
         if error
-          # Throw an exception if `nackup` isn't in the PATH
+          # Throw an exception if `nackup` isn't in the `PATH`.
+          #
           # Probably need to `gem install nack` or fix shitty rubygems
           callback new Error "Couldn't find `nackup` in PATH"
         else
@@ -96,7 +103,7 @@
     @changeState 'spawning'

     @getNackupPath (err, nackup) =>
-      # Bubble error from getNackupPath
+      # Bubble error from `getNackupPath`
       return @emit 'error', err if err

       # Generate a random sock path
@@ -225,3 +232,9 @@
 # Public API for creating a **Process**
 exports.createProcess = (args...) ->
   new Process args...
+
+# Generates a random sock path.
+tmpSock = ->
+  pid  = process.pid
+  rand = Math.floor Math.random() * 10000000000
+  "/tmp/nack." + pid + "." + rand + ".sock"


Environments: Fix clone stack button for new created stacks.

--- client/Environments/views/environmentsmainscene.coffee
+++ client/Environments/views/environmentsmainscene.coffee
@@ -86,6 +86,7 @@
       modal.destroy()

       stackView = new StackView { stack} , @environmentData
+      @forwardEvent stackView, "CloneStackRequested"
       @_stacks.push @addSubView stackView
       @highlightStack stackView


Added cache buster to stylesheet links for app.

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -602,7 +602,7 @@
     $("head .app-#{__utils.slugify name}").remove()
     stylesheets.forEach (sheet)->
       if devMode
-        urlToStyle = "https://#{KD.whoami().profile.nickname}.koding.com/.applications/#{__utils.slugify name}/#{__utils.stripTags sheet}"
+        urlToStyle = "https://#{KD.whoami().profile.nickname}.koding.com/.applications/#{__utils.slugify name}/#{__utils.stripTags sheet}?#{Date.now()}"
         $('head').append "<link class='app-#{__utils.slugify name}' rel='stylesheet' href='#{urlToStyle}'>"
       else
         if /(http)|(:\/\/)/.test sheet


:racehorse: Cache realpaths in PathScanner

--- src/path-scanner.coffee
+++ src/path-scanner.coffee
@@ -48,6 +48,7 @@
   #   * `includeHidden` {Boolean} default false; true includes hidden files
   constructor: (@rootPath, @options={}) ->
     @asyncCallsInProgress = 0
+    @realPathCache = {}
     @rootPath = path.resolve(@rootPath)
     @rootPathLength = @rootPath.length
     @pathFilter = new PathFilter(@rootPath, @options)
@@ -111,7 +112,7 @@
   isInternalSymlink: (filePath) ->
     realPath = null
     try
-      realPath = fs.realpathSync(filePath)
+      realPath = fs.realpathSync(filePath, @realPathCache)
     catch error
       ; # ignore
     realPath?.search(@rootPath) is 0


Allow mailto: links

Allow mailto: links

--- src/plugins/link.coffee
+++ src/plugins/link.coffee
@@ -53,7 +53,7 @@
           document.execCommand "unlink", null, ""
         else
           # link does not have ://, add http:// as default protocol
-          if !((new RegExp(/:\/\//)).test link)
+          if !((new RegExp(/:\/\//)).test link) && !((new RegExp(/^mailto:/)).test link)
             link = 'http://' + link
           if widget.lastSelection.startContainer.parentNode.href is undefined
             document.execCommand "createLink", null, link


AppManager: async loading mechanism improved

- removed old code
- ::tell now works with not-yet-loaded apps

--- client/Main/ApplicationManager.coffee
+++ client/Main/ApplicationManager.coffee
@@ -1,5 +1,3 @@
-KD.config.allowedApps = ['account', 'topics', 'terminal', 'ace', 'activity']
-
 class ApplicationManager extends KDObject

   ###
@@ -10,10 +8,6 @@
   ###

   manifestsFetched = no
-
-  @checkAppAvailability = (name='')->
-    return (name.toLowerCase() in KD.config.allowedApps) and\
-           (name not in Object.keys KD.appClasses)

   constructor:->

@@ -49,6 +43,9 @@
   #     missingRoute = appController.getOption('initialRoute') or route
   #     router.handleRoute missingRoute, { suppressListeners : yes, entryPoint }

+  checkAppAvailability : (name='')->
+    return KD.config.apps[name] and (name not in Object.keys KD.appClasses)
+

   open: do ->

@@ -74,14 +71,6 @@
       appParams            = options.params or {}
       defaultCallback      = createOrShow.bind this, appOptions, appParams, callback
       kodingAppsController = KD.getSingleton("kodingAppsController")
-
-      log 'AppManager: opening an app', name
-      if ApplicationManager.checkAppAvailability name
-        log 'AppManager: couldn\'t find', name
-        return KodingAppsController.putAppScript name, (err)=>
-          log 'AppManager: loaded', name
-          return warn err  if err
-          KD.utils.defer => @open name, options, callback

       # If app has a preCondition then first check condition in it
       # if it returns true then continue, otherwise call failure
@@ -214,6 +203,15 @@
         @emit "AppCouldntBeCreated", appInstance
         @utils.defer => @quitByName appOptions.name, yes
         return no
+
+    log 'AppManager: opening an app', name
+    if @checkAppAvailability name
+      log 'AppManager: couldn\'t find', name
+      return KodingAppsController.putAppScript name, (err)=>
+        log 'AppManager: loaded', name
+        return warn err  if err
+        KD.utils.defer => @create name, params, callback
+

     @utils.defer =>
       return @emit "AppCouldntBeCreated"  unless appInstance


Appmanager: return to avoid double callbacks

--- client/Main/ApplicationManager.coffee
+++ client/Main/ApplicationManager.coffee
@@ -197,7 +197,8 @@
       @emit "AppCreated", appInstance

       if appOptions.thirdParty
-        KD.getSingleton("kodingAppsController").putAppResources appInstance, callback
+        return KD.getSingleton("kodingAppsController").putAppResources appInstance, callback
+
       callback? appInstance

   show:(name, appParams, callback)->


Fix broken symlinks catch condition.

Fix broken symlinks catch condition.

--- client/app/MainApp/fs/fshelper.coffee
+++ client/app/MainApp/fs/fshelper.coffee
@@ -49,7 +49,7 @@
         [itemCount] = lines.splice(0,3)
         parentPath = parentPaths[0]
       for line in lines when line
-        unless line[0] is 'l' # broken symlinks has l type
+        unless line[0] in ['l', '?'] # broken symlinks has l type and it can be ? in someway
           unless systemFilesRegExp.test line
             data.push FSHelper.createFile parseFile parentPath, line
     data


shortcuts/ctrl: inherit from events.EventEmitter (see: https://github.com/tetsuo/kd/pull/15#issuecomment-93021712)

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -15,7 +15,7 @@

 module.exports =

-class ShortcutsController extends kd.Controller
+class ShortcutsController extends events.EventEmitter

   klass = this

@@ -43,7 +43,7 @@

     @_flushBuffer()

-    super options, data
+    super()

   # Prepares buffer for the next batch of changes.


Remove email field from cobalt user model

--- client/code/view/admin_content.coffee
+++ client/code/view/admin_content.coffee
@@ -21,7 +21,6 @@
       dataType: 'json'
       data:
         apikey: window.user.real.apiKey
-        email: profile.email[0]
         newApikey: profile.apikey
       success: (cobaltProfile) =>
         callback cobaltProfile


Rename urlHackObj -> hackUrlObj.

Rename urlHackObj -> hackUrlObj.

--- src/qr-generator.coffee
+++ src/qr-generator.coffee
@@ -30,8 +30,8 @@
       return

     urlObj = makeUrlObj data
-    urlHackObj = adapterHack urlObj, robot.adapterName
-    msg.send url.format urlHackObj
+    hackUrlObj = adapterHack urlObj, robot.adapterName
+    msg.send url.format hackUrlObj

 makeUrlObj = (data) ->
   urlObj = url.parse baseUrl


Changed if statements within _onTouchEnd - previously they were strictly checking for GESTURE.angle_difference !== 0 and GESTURE.distance_difference !== 0 but because capture rotation and capture pinch return undefined if they are below the predetermined threshold it meant that any double touch that wasn't a pinch or a zoom (like a double tap) was still firing these events: 'pinch', 'pinchIn' 'rotate', 'rotateLeft' So just changed them to check for if angle_difference and distance_difference are truthy so a negative or positive number will pass through but undefined will not. Also added in a 'twoTap' event for when you do a double finger tap as I couldn't find any support for this anywhere. I think the coffee script is correct but not sure as I usually just do pure JS. Also couldn't find any instructions for if a build was required before committing (assume that's what Grunt is for?) but you can at least see the changes here and decide if they're worthwhile. :)

--- src/quo.gestures.coffee
+++ src/quo.gestures.coffee
@@ -8,7 +8,7 @@
     TOUCH_TIMEOUT = undefined
     HOLD_DELAY = 650
     GESTURES = ["touch",
-                "tap", "singleTap", "doubleTap", "hold",
+                "tap", "singleTap", "twoTap" "doubleTap", "hold",
                 "swipe", "swiping", "swipeLeft", "swipeRight", "swipeUp", "swipeDown",
                 "rotate", "rotating", "rotateLeft", "rotateRight",
                 "pinch", "pinching", "pinchIn", "pinchOut",
@@ -105,12 +105,12 @@
                     ), 100)
         else
             anyevent = false
-            if GESTURE.angle_difference isnt 0
+            if GESTURE.angle_difference
                 _trigger "rotate", angle: GESTURE.angle_difference
                 rotation_direction = if GESTURE.angle_difference > 0 then "rotateRight" else "rotateLeft"
                 _trigger rotation_direction, angle: GESTURE.angle_difference
                 anyevent = true
-            if GESTURE.distance_difference isnt 0
+            if GESTURE.distance_difference
                 _trigger "pinch", angle: GESTURE.distance_difference
                 pinch_direction = if GESTURE.distance_difference > 0 then "pinchOut" else "pinchIn"
                 _trigger pinch_direction, distance: GESTURE.distance_difference
@@ -120,6 +120,9 @@
                     _trigger "drag"
                     drag_direction = _swipeDirection(FIRST_TOUCH[0].x, CURRENT_TOUCH[0].x, FIRST_TOUCH[0].y, CURRENT_TOUCH[0].y)
                     _trigger "drag" + drag_direction
+                    anyevent = true
+            if not anyevent
+                _trigger "twoTap"
             _cleanGesture()
         EVENT = undefined


add subcollection convenience method and docs

--- client/lanes/models/Collection.coffee
+++ client/lanes/models/Collection.coffee
@@ -10,6 +10,9 @@
             break if found != -1
         found

+    # convenience method to create a new subCollection
+    subcollection: (options)->
+        new Lanes.Models.SubCollection(this, options)

     destroyAll: (options={})->
         success = options.success
@@ -21,7 +24,6 @@
                 success.apply(@,arguments) if success
         })
         Lanes.Models.Sync.perform('delete', this, options)
-
 }

 class ModelsCollection
@@ -31,56 +33,66 @@
         @errors=[]
         Lanes.Vendor.Ampersand.Collection.apply(this, arguments)

+    # convenience method to instantiate a collection
+    # then call fetch on it with the options provided
     @fetch: (options)->
         collection = new this
         collection.fetch(options)
         collection
-
+
+    # Fetch the a set of models for the collection, replacing all
+    # current models whith them when the call completes
     fetch: (options={})->
-        @_isLoaded = true
-        Lanes.Models.Sync.wrapRequest(this,options)
-        return this.sync('read', this, options)
+        handlers = Lanes.Models.Sync.wrapRequest(this,options)
+        if options.force || !@_isLoaded
+            this.sync('read', this, options)
+        else
+            handlers.resolvePromise( record: this, reply: {} )
+        handlers.promise
+
+    # Call the callback function when the current fetch succeeds
+    # If the collection is not currently being loaded,
+    # the callback is immediatly invoked
+    whenLoaded: (cb)->
+        if this.requestInProgress
+            cb(this)
+        else
+            this._loaded_callbacks ||= []
+            this._loaded_callbacks.push(cb)
+        this

     # Sets the attribute data from a server respose
     setFromServer: (data, options)->
+        @_isLoaded = true
         method = if options.reset then 'reset' else 'set'
         this[method](data, options)
-        this.trigger('sync', this, data, options)
+        if this._loaded_callbacks
+            cb(this) for cb in this._loaded_callbacks
+            delete this._loaded_callbacks

-    isLoaded:->
-        @_isLoaded
+    isLoaded:-> @_isLoaded

-    ensureLoaded: ( callback )->
-        if ! @_isLoaded && ! this.length
-            this.fetch({ success: callback })
-        else if callback
-            callback()
-
+    # true if any models have unsaved data
     isDirty: ->
         for model in @models
             return true if model.isDirty()
         false

-    parse:(resp)->
-        resp['data']
-
-    viewJSON: (options)->
-        this.map( (model) ->
-            model.viewJSON(options)
-        )
-
-    url: ->
-        @model::urlRoot()
+    url: -> @model::urlRoot()

     sync: Lanes.Models.Sync.perform

     save: (options)->
         Lanes.Models.Sync.perform('update', this, options)

+    # returns data to save to server.  If options.saveAll is true,
+    # all attributes from all models data is returned.
+    # Otherwise only unsaved attributes are returned.
     dataForSave: (options)->
         unsaved = []
         for model in @models
-            unsaved.push( model.unsavedModels() ) if model.isDirty()
+            if options.saveAll || model.isDirty()
+                unsaved.push( model.dataForSave(options) )
         unsaved

     _prepareModel: (attrs, options={})->


Barcode task: better logic for automatically selecting active tool

--- overrides.coffee
+++ overrides.coffee
@@ -80,6 +80,12 @@
       if page.decisionTree.currentTask.key is 'barcode'
         task = page.decisionTree.currentTask
         task.check task.choices[1]
+
+    ms.on 'marking-surface:remove-tool', (tool) ->
+      if page.decisionTree.currentTask.key is 'barcode'
+        task = page.decisionTree.currentTask
+        choice = (choice for choice in task.choices when choice.value == tool.mark.value)[0]
+        task.check choice

     page.on page.LOAD_SUBJECT, (e, subject)->
       ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -26,7 +26,7 @@
       engine.setState levelState

   ###*
-  Execute a callback after a number of steps have passed.
+  E

   engine.delay 5, ->


adding objects by class name only

adding objects by class name only

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -62,5 +62,6 @@
   @name reloadLevel
   @methodOf Engine#
   ###
-  re: ->
+  restartLevel: ->
     loadLevel I.currentLevelName
+  reloadLevel: ->


content-type header value is case-insensitive

--- src/hit-validator.coffee
+++ src/hit-validator.coffee
@@ -31,7 +31,14 @@
     transformedHeaders = {}

     for key, value of headers
-      transformedHeaders[key.toLowerCase()] = value
+      key = key.toLowerCase()
+      #ref:
+      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
+      # http://www.w3.org/Protocols/rfc1341/4_Content-Type.html
+      if key == 'content-type'
+        transformedHeaders[key] = value.toLowerCase()
+      else
+        transformedHeaders[key] = value

     return transformedHeaders


added nice variable names for csx tag attribute regex matches

--- csx.coffee
+++ csx.coffee
@@ -128,34 +128,36 @@
   rewriteAttributes: (attributesText) ->
     astBranchNode CSX_ATTRIBUTES, null, do ->
       while attrMatches = TAG_ATTRIBUTES.exec attributesText
-        unless attrMatches[1] # has attribute
+        [ attrNameValText, attrName, doubleQuotedVal,
+          singleQuotedVal, csEscVal, bareVal ] = attrMatches
+        unless attrName # has attribute
           throwSyntaxError \
-            "Invalid attribute #{attrMatches[0]} in #{attributesText}",
+            "Invalid attribute #{attrNameValText} in #{attributesText}",
             first_line: @chunkLine, first_column: @chunkColumn

-        if attrMatches[2] # "value"
-          astBranchNode(CSX_ATTR_PAIR, null, [
-            astLeafNode(CSX_ATTR_KEY, "\"#{attrMatches[1]}\"")
-            astLeafNode(CSX_ATTR_VAL, "\"#{attrMatches[2]}\"")
-          ])
-        else if attrMatches[3] # 'value'
-          astBranchNode(CSX_ATTR_PAIR, null, [
-            astLeafNode(CSX_ATTR_KEY, "\"#{attrMatches[1]}\"")
-            astLeafNode(CSX_ATTR_VAL, "'#{attrMatches[3]}'")
-          ])
-        else if attrMatches[4] # {value}
-          astBranchNode(CSX_ATTR_PAIR, null, [
-            astLeafNode(CSX_ATTR_KEY, "\"#{attrMatches[1]}\"")
-            astBranchNode(CSX_ESC, null, [astLeafNode(CS, attrMatches[4])])
-          ])
-        else if attrMatches[5] # value
-          astBranchNode(CSX_ATTR_PAIR, null, [
-            astLeafNode(CSX_ATTR_KEY, "\"#{attrMatches[1]}\"")
-            astLeafNode(CSX_ATTR_VAL, "\"#{attrMatches[5]}\"")
+        if doubleQuotedVal # "value"
+          astBranchNode(CSX_ATTR_PAIR, null, [
+            astLeafNode(CSX_ATTR_KEY, "\"#{attrName}\"")
+            astLeafNode(CSX_ATTR_VAL, "\"#{doubleQuotedVal}\"")
+          ])
+        else if singleQuotedVal # 'value'
+          astBranchNode(CSX_ATTR_PAIR, null, [
+            astLeafNode(CSX_ATTR_KEY, "\"#{attrName}\"")
+            astLeafNode(CSX_ATTR_VAL, "'#{singleQuotedVal}'")
+          ])
+        else if csEscVal # {value}
+          astBranchNode(CSX_ATTR_PAIR, null, [
+            astLeafNode(CSX_ATTR_KEY, "\"#{attrName}\"")
+            astBranchNode(CSX_ESC, null, [astLeafNode(CS, csEscVal)])
+          ])
+        else if bareVal # value
+          astBranchNode(CSX_ATTR_PAIR, null, [
+            astLeafNode(CSX_ATTR_KEY, "\"#{attrName}\"")
+            astLeafNode(CSX_ATTR_VAL, "\"#{bareVal}\"")
           ])
         else
           astBranchNode(CSX_ATTR_PAIR, null, [
-            astLeafNode(CSX_ATTR_KEY, "\"#{attrMatches[1]}\"")
+            astLeafNode(CSX_ATTR_KEY, "\"#{attrName}\"")
             astLeafNode(CSX_ATTR_VAL, 'true')
           ])


Removed not needed debug log.

Removed not needed debug log.

--- app/assets/javascripts/app/lib/app_post/task_manager.js.coffee
+++ app/assets/javascripts/app/lib/app_post/task_manager.js.coffee
@@ -122,8 +122,7 @@

     # create new task if not exists
     task = @get( key )
-    console.log('TASKBAR RESET', @allTasks)
-    console.log('add', key, callback, params, to_not_show, task)
+    #console.log('add', key, callback, params, to_not_show, task)
     if !task
       task = new App.Taskbar
       task.load(
@@ -336,7 +335,6 @@

     # check if update is still in process
     if @tasksToUpdate[ task.key ] is 'inProgress'
-      console.log( 'DESTROY', task, @tasksToUpdate[ task.key ], task.key)
       @delay(
         => @taskDestroy(task)
         800


Add loading flags

Add loading flags

--- app/assets/javascripts/common/components/programList.js.coffee
+++ app/assets/javascripts/common/components/programList.js.coffee
@@ -87,9 +87,11 @@
         eventHub.$emit "flash:show", data.responseJSON.message, "danger"

     load: ->
+      @isLoading = true
       @programs = @initPrograms(@pageObject.programs)
       @hasNext = @programs.length > 0
       @user = @pageObject.user
+      @isLoading = false
       @$nextTick ->
         vueLazyLoad.refresh()


add times

add times

--- app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
+++ app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
@@ -3,28 +3,65 @@
 #= require react-input-autosize
 #= require react-select
 #= require underscore
+#= require moment

 TimeTable = React.createClass
-
-  render: ->
-    days = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"]
-
+  createTimeArray: ->
+    startMoment = moment()
+    startMoment.set "hours", 13
+    startMoment.set "minutes", 30
+    endMoment = moment startMoment
+    endMoment.set "hours", 19
+    endMoment.set "minutes", 0
+
+    timeMoment = moment startMoment
+
+    while endMoment.diff(timeMoment) >=0
+      label = timeMoment.format "HH:mm"
+      key = label
+      timeMoment.add 30, "minutes"
+      {key, label}
+  getMentorsFor: (day, time) ->
+    @props.mentors
+  render: ->
+    days = [
+      (key: "mon", label: "Montag")
+      (key: "tue", label: "Dienstag")
+      (key: "wed", label: "Mittwoch")
+      (key: "thu", label: "Donnerstag")
+      (key: "fri", label: "Freitag")
+    ]
+    times = @createTimeArray()

-
+
         {
-          {day}	 for day in days
+          {day.label}	 for day in days
         }

-
-
+      {
+        for time, i in times
+

+            {time.label}
+            {
+              for day in days
+
+                {
+                  for id, mentor of @getMentorsFor day, time
+                    {mentor.name}
+
+                }
+
+            }
+

+
+      }

-

@@ -54,7 +91,7 @@

Selected {_.size selectedMentors} mentors of {_.size @props.mentors}
       <button
         onClick=@selectAll
-        class="btn btn-default">Select All ({_.size @props.mentors})
+        className="btn btn-default">Select All ({_.size @props.mentors})
       </button>

       <MentorsForDisplayingFilter
@@ -65,7 +102,7 @@

       <TimeTable
         kid=@props.kid
-        mentors=@props.mentors
+        mentors=selectedMentors
       />


don't automatically reigset as delegate

don't automatically reigset as delegate

--- app/js/services/wallet.coffee
+++ app/js/services/wallet.coffee
@@ -164,6 +164,7 @@
           response.result

     wallet_account_register: (account_name, pay_from_account, public_data, as_delegate) ->
+        as_delegate = if as_delegate == undefined then 255 else as_delegate
         @rpc.request('wallet_account_register', [account_name, pay_from_account, public_data, as_delegate]).then (response) =>
           response.result


Fix demo of the second lobule

Fix demo of the second lobule


fix merge conflicts

fix merge conflicts

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -43,7 +43,7 @@

Only the assigned collaborators can view a private project. Anyone with the URL can access a public project.

-

+

         <input type="radio" name="live" value={true} data-json-value={true} checked={not @props.project.live} disabled={@state.setting.live} onChange={@set.bind this, 'live', false} />


Add an isRendered method to base view

Add an isRendered method to base view

--- app/scripts/views/base.coffee
+++ app/scripts/views/base.coffee
@@ -27,3 +27,5 @@
         @trigger.bind(this, "out")
       this

+    isRendered: ->
+      not @$el.is ":empty"


switch matrix from array to wrapping object with own proto

--- matrixSpec.coffee
+++ matrixSpec.coffee
@@ -1,36 +1,93 @@
-m = require 'math/matrix'
+M = require 'math/matrix'

 describe 'Matrix module', ->

+  A = null
+  A2 = null
+
+  beforeEach ->
+
+    A  = M [ 1,  3,  5
+             2,  4,  6 ], 3
+    A2 = M [ 1,  9, 25
+             4, 16, 36 ], 3
+
+  describe 'Matrix constructor', ->
+
+    it 'should be able to create a new empty matrix', ->
+
+      B = M []
+      expect(B.array).toEqual []
+      expect(B.height).toBe 0
+      expect(B.width).toBe 0
+
+    it 'should be able to create a new square matrix without width param', ->
+
+      B = M [ 1, 2, 3
+              4, 5, 6
+              7, 8, 9 ]
+      expect(B.height).toBe 3
+      expect(B.width).toBe 3
+
+    it 'should be able to create a new rectangular matrix', ->
+
+      B = M [ 1, 2, 3
+              4, 5, 6 ], 3
+      expect(B.height).toBe 2
+      expect(B.width).toBe 3
+
+  describe 'clone() function', ->
+
+    it 'should be able to clone a matrix', ->
+
+      B = A.clone()
+      expect(B).not.toBe A
+      expect(B).toEqual A
+
+    it 'should be able to clone to an existing matrix', ->
+
+      B = A.clone(A2)
+      expect(B).not.toBe A
+      expect(B).toBe A2
+      expect(B).toEqual A
+
   describe 'map() function', ->

+    it 'should be able to map to a new matrix', ->
+
+      B = A.clone()
+      C = B.map((x) -> x*x)
+      expect(C).not.toBe B # in place ?
+      # adapted correctly ?
+      expect(C).toEqual A2
+
     it 'should be able to map a matrix in place', ->
-      A = [[1, 2], [3, 4], [5, 6]]
-      B = m.map ((x) -> x*x), A
-      expect(B).toBe A # in place ?
-      expect(B).toEqual [[1, 4], [9, 16], [25, 36]] # adapted correctly ?
+      B = A.clone()
+      C = B.map ((x) -> x*x), B
+      expect(C).toBe B # A returned ?
+      expect(C).toEqual A2 # B adapted correctly ?

-    it 'should be able to map to another matrix', ->
-      A = [[1, 2], [3, 4], [5, 6]]
-      B = [[], [], []]
-      C = m.map ((x) -> x-1), A, B
-      expect(C).toBe B # B returned ?
-      expect(A).toEqual [[1, 2], [3, 4], [5, 6]] # A not changed ?
-      expect(B).toEqual [[0, 1], [2, 3], [4, 5]] # B adapted correctly ?
+    it 'should be able to map a matrix to a different matrix', ->
+      B = A.clone()
+      C = A.clone()
+      D = B.map ((x) -> x*x), C
+      expect(B).toEqual A # B adapted correctly ?
+      expect(D).toBe C # A returned ?
+      expect(D).toEqual A2 # B adapted correctly ?

   describe 'times() function', ->

     it 'should be able to multiply a matrix in place', ->
       A = [[1, 2]]
-      B = m.times(3, A)
+      B = M.times(3, A)
       expect(B).toBe A # in place ?
       expect(B).toEqual [[3, 6]] # adapted correctly ?

     it 'should be able to multiply to another matrix', ->
       A = [[1, 2]]
       B = [[]]
-      C = m.times(3, A, B)
+      C = M.times(3, A, B)
       expect(C).toBe B # B returned ?
       expect(A).toEqual [[1, 2]] # A not changed ?
       expect(B).toEqual [[3, 6]] # B adapted correctly ?
@@ -39,22 +96,22 @@
   describe 'height() function', ->

     it 'should give correct height for an empty matrix', ->
-      expect(m.height([])).toEqual 0
+      expect(M.height([])).toEqual 0

     it 'should fail for empty 2 dim matrix', ->
-      expect(-> m.height([[]])).toThrow()
+      expect(-> M.height([[]])).toThrow()

     it 'should give correct height for a rectangular matrix', ->
-      expect(m.height([[1, 2], [3, 4], [5, 6]])).toEqual 2
+      expect(M.height([[1, 2], [3, 4], [5, 6]])).toEqual 2

   describe 'width() function', ->

     it 'should give correct width for an empty matrix', ->
-      expect(m.width([])).toEqual 0
+      expect(M.width([])).toEqual 0

     it 'should fail for empty 2 dim matrix', ->
-      expect(-> m.width([[]])).toThrow()
+      expect(-> M.width([[]])).toThrow()

     it 'should give correct width for a rectangular matrix', ->
-      expect(m.width([[1, 2], [3, 4], [5, 6]])).toEqual 3
+      expect(M.width([[1, 2], [3, 4], [5, 6]])).toEqual 3


add identity matrix function

add identity matrix function

--- matrixSpec.coffee
+++ matrixSpec.coffee
@@ -53,6 +53,7 @@

       expect(-> M [ 1, 2, 3 ]).toThrow()

+
   describe 'get() function', ->

     it 'gets matrix elements', ->
@@ -62,6 +63,7 @@
       expect(A.get(1, 0)).toBe 2
       expect(A.get(1, 2)).toBe 6

+
   describe 'set() function', ->

     it 'sets matrix elements', ->
@@ -71,6 +73,7 @@
       expect(A.get(1, 2)).toBe 6
       expect(A.set(1, 2, 7).get(1, 2)).toBe 7

+
   describe 'isSameSize() function', ->

     it 'should return true on same sized matrices', ->
@@ -112,6 +115,7 @@
       expect(B).not.toBe A
       expect(B).toBe A2
       expect(B).toEqual A
+

   describe 'map() function', ->

@@ -183,6 +187,7 @@
       expect(C).toBe B
       expect(B2).toEqual A2

+
   describe 'add() function', ->

     it 'adds two matrices to a new one', ->
@@ -204,6 +209,7 @@
       expect(C).not.toBe B # not in place?
       expect(B).toEqual A2_A # source unchanged ?

+
   describe 'minus() function', ->

     it 'subtrats two matrices to a new matrix', ->
@@ -227,6 +233,7 @@
                             5, 6 ], 2 # expected result?
       expect(C).not.toBe B # not in place?
       expect(B).toEqual A # source unchanged ?
+

   describe 'mult() function', ->

@@ -244,7 +251,18 @@
       expect(A2).toEqual A # source unchanged ?
       expect(B2).toEqual B # source unchanged ?

+
   describe 'toString() function', ->

     it 'should return the matrix as string', ->
       expect(A.toString()).toBe '1 3 5\n2 4 6'
+
+
+  describe 'I()', ->
+
+    it 'creates a new identity matrix', ->
+      expect(M.I(3)).toEqual M [ 1, 0, 0
+                                 0, 1, 0
+                                 0, 0, 1 ]
+      expect(M.I(3, 2)).toEqual M [ 1, 0, 0
+                                    0, 1, 0 ], 3


init: restrict to coffee conf

init: restrict to coffee conf

--- lib/commands/init.coffee
+++ lib/commands/init.coffee
@@ -42,7 +42,7 @@
     content: """
     #!/bin/bash
     cd `dirname "${BASH_SOURCE}"`/..
-    ./node_modules/.bin/ryba -c ./conf $@
+    ./node_modules/.bin/ryba -c ./conf/*.coffee $@
     """
     mode: 0o0755
     eof: true


Minor javascript refactoring on landing page

--- views/landing.coffee
+++ views/landing.coffee
@@ -11,8 +11,6 @@
         $ ->
         navigator.geolocation.getCurrentPosition (position) ->
           console.log position
-          $.post '/set_location',
-            location: position.coords,
-            (data) ->
-              console.log data
-              $('a.btn').before "

Look! Your latitude is #{data.latitude}, and your longitude is #{data.longitude}!

How handy is that?

"
+          $.post '/set_location', location: position.coords, (data) ->
+            console.log data
+            $('a.btn').before "

Look! Your latitude is #{data.latitude}, and your longitude is #{data.longitude}!

How handy is that?

"


Resetting view model only when binding

Resetting view model only when binding

--- tubular.coffee
+++ tubular.coffee
@@ -47,17 +47,14 @@
       newValue = getter()
       if newValue isnt value
         value = newValue
-        runTemplate value, viewInstance, subTemplate
+        runTemplate value, {}, createNotifier(), viewInstance, subTemplate

-    runTemplate value, viewInstance, subTemplate
+    runTemplate value, {}, createNotifier(), viewInstance, subTemplate

     # return a handle to be able to unwatch
     { clear: clear }

-  runTemplate = (model, viewPrototype, template, preInitMap) ->
-    viewModel = {}
-    viewModelNotify = createNotifier()
-
+  runTemplate = (model, viewModel, viewModelNotify, viewPrototype, template, preInitMap) ->
     bindVariable = (name, subTemplate) ->
       bindOnChange (-> viewModel[name]), viewModelNotify, viewInstance, subTemplate

@@ -80,7 +77,7 @@
     viewInstance =
       fork: (map, subTemplate) ->
         # create clean sub-view model and initialize it with given values
-        runTemplate model, viewInstance, subTemplate, map
+        runTemplate model, viewModel, viewModelNotify, viewInstance, subTemplate, map

       get: () ->
         # @todo type-check for primitives for true immutability
@@ -121,4 +118,4 @@

     undefined # prevent stray output

-  runTemplate rootModel, Object.prototype, rootTemplate
+  runTemplate rootModel, {}, createNotifier(), Object.prototype, rootTemplate


Fixed bug that changes the active tab when other tabs are closed. Also small change to masking files.

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -30,7 +30,7 @@
 template_project_collab        = templates.find(".project-collab")
 template_project_linked        = templates.find(".project-linked")

-################################O##################
+##################################################
 # Initialize the modal project management dialogs
 ##################################################
 delete_path_dialog = $("#project-delete-path-dialog")
@@ -1531,13 +1531,14 @@
                     for index in masked_file_bad_index
                         filename = listing.files[index].name
                         ext = filename_extension(filename)
-                        name = filename.slice(0,filename.length - ext.length - 1)
+                        name = filename.slice(0, filename.length - ext.length - 1)
                         compare_name = name

                         # TODO: other unusual cases may need to be added here
                         if ext == 'gz'
-                            if name.indexOf(".synctex") != -1
-                                compare_name = name.slice(0,name.indexOf(".synctex"))
+                            second_extension = 'synctex'
+                            if filename_extension(name) == second_extension
+                                compare_name = name.slice(0, name.length - second_extension.length - 1)
                             else
                                 continue


strange typo

strange typo

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -959,7 +959,7 @@
                         if status.settings.network
                             @container.find(".salvus-network-blocked").hide()
                         else
-                            @container.find(".salvus-network-blocked").hide()()
+                            @container.find(".salvus-network-blocked").hide()

                     usage.show()


pm2 restart

pm2 restart

--- modules/admin/index.coffee
+++ modules/admin/index.coffee
@@ -44,7 +44,7 @@

   restartFunc: (msg)=>
     msg.channel.sendMessage 'FocaBot is restarting...'
-    .then ()-> process.exit() # Let's hope PM2 restarts it :)
+    .then ()-> childProcess.exec('pm2 restart focaBot')

   updateFunc: (msg,args,d,bot)=>
       @pullFunc msg,args,bot


Scroll to correct target (with offset etc)

--- resources/assets/coffee/react/profile-page/extra.coffee
+++ resources/assets/coffee/react/profile-page/extra.coffee
@@ -44,6 +44,8 @@

   _modeScan: =>
+    return if @_scrolling
+
     pages = document.getElementsByClassName('js-profile-page-extra--scrollspy')
     return unless pages.length

@@ -65,8 +67,12 @@

   _modeSwitch: (_e, mode) =>
+    @_scrolling = true
     $.scrollTo "##{mode}", 500,
-      onAfter: => @setState mode: mode
+      onAfter: =>
+        @setState mode: mode
+        setTimeout (=> @_scrolling = false), 100
+      offset: @refs.tabs.getBoundingClientRect().height * -1

   _removeListeners: ->
@@ -96,6 +102,7 @@
       div
         className: 'profile-extra-tabs js-sticky-header'
         'data-sticky-header-target': 'profile-extra-tabs'
+        ref: 'tabs'
         div
           className: tabsContainerClasses
           div className: 'osu-layout__row',


More compact way of rendering things

More compact way of rendering things

--- resources/assets/coffee/react/profile-page/links.coffee
+++ resources/assets/coffee/react/profile-page/links.coffee
@@ -73,18 +73,8 @@
         @renderPlaystyle()
         @renderPostCount()
       ]
-      [
-        @renderLink 'location'
-        @renderLink 'interests'
-        @renderLink 'occupation'
-      ]
-      [
-        @renderLink 'twitter'
-        @renderLink 'discord'
-        @renderLink 'skype'
-        @renderLink 'lastfm'
-        @renderLink 'website'
-      ]
+      ['location', 'interests', 'occupation'].map @renderLink
+      ['twitter', 'discord', 'skype', 'lastfm', 'website'].map @renderLink
     ]

     div className: bn,


Add CAPTCHA tab to admin settings UI

Add CAPTCHA tab to admin settings UI

--- picoCTF-web/web/coffee/management-settings.coffee
+++ picoCTF-web/web/coffee/management-settings.coffee
@@ -89,12 +89,10 @@
   propTypes:
     refresh: React.PropTypes.func.isRequired
     emailSettings: React.PropTypes.object.isRequired
-    loggingSettings: React.PropTypes.object.isRequired
     emailFilterSettings: React.PropTypes.array.isRequired

   getInitialState: ->
     settings = @props.emailSettings
-    $.extend settings, @props.loggingSettings
     settings.email_filter = @props.emailFilterSettings
     settings

@@ -341,6 +339,77 @@
       </Row>
     </Well>

+CaptchaTab = React.createClass
+  propTypes:
+    refresh: React.PropTypes.func.isRequired
+    captchaSettings: React.PropTypes.object.isRequired
+
+  getInitialState: ->
+    settings = @props.captchaSettings
+
+  toggleEnableCaptcha: ->
+    @setState update @state,
+      $set:
+        enable_captcha: !@state.enable_captcha
+
+  updateCaptchaURL: (e) ->
+    @setState update @state,
+      $set:
+        captcha_url: e.target.value
+
+  updateRecaptchaPublicKey: (e) ->
+    @setState update @state,
+      $set:
+        reCAPTCHA_public_key: e.target.value
+
+  updateRecaptchaPrivateKey: (e) ->
+    @setState update @state,
+      $set:
+        reCAPTCHA_private_key: e.target.value
+
+  pushUpdates: (makeChange) ->
+    pushData =
+      captcha:
+        enable_captcha: @state.enable_captcha
+        captcha_url: @state.captcha_url
+        reCAPTCHA_public_key: @state.reCAPTCHA_public_key
+        reCAPTCHA_private_key: @state.reCAPTCHA_private_key
+
+    if typeof(makeChange) == "function"
+      pushData = makeChange pushData
+
+    apiCall "PATCH", "/api/v1/settings", pushData
+    .success ((data) ->
+      apiNotify {"status": 1, "message": "Settings updated successfully"}
+      @props.refresh()
+    ).bind(this)
+    .error (jqXHR) ->
+      apiNotify {"status": 0, "message": jqXHR.responseJSON.message}
+
+  render: ->
+    enableCaptchaDescription = "Require users to solve a CAPTCHA when registering an account. Does not apply to invited or batch-registered accounts"
+    captchaURLDescription = "URL to request a CAPTCHA"
+    reCaptchaPublicKeyDescription = "Public key for the reCAPTCHA API"
+    reCaptchaPrivateKeyDescription = "Private key for the reCAPTCHA API"
+
+    <Well>
+      <Row>
+        <Col sm={8}>
+          <BooleanEntry name="Enable CAPTCHA" value={@state.enable_captcha} onChange=@toggleEnableCaptcha description={enableCaptchaDescription}/>
+          <TextEntry name="CAPTCHA URL" type="text" value={@state.captcha_url} onChange=@updateCaptchaURL description={captchaURLDescription}/>
+          <TextEntry name="reCAPTCHA Public Key" type="textarea" value={@state.reCAPTCHA_public_key} onChange=@updateRecaptchaPublicKey description={reCaptchaPublicKeyDescription}/>
+          <TextEntry name="reCAPTCHA Private Key" type="textarea" value={@state.reCAPTCHA_private_key} onChange=@updateRecaptchaPrivateKey description={reCaptchaPrivateKeyDescription}/>
+          <Row>
+            <div className="text-center">
+              <ButtonToolbar>
+                <Button onClick={@pushUpdates}>Update</Button>
+              </ButtonToolbar>
+            </div>
+          </Row>
+        </Col>
+      </Row>
+    </Well>
+

 SettingsTab = React.createClass
   getInitialState: ->
@@ -367,6 +436,11 @@
         default_stepping: 1
         steps: ""
         limit_added_range: false
+      captcha:
+        enable_captcha: false
+        captcha_url: ""
+        reCAPTCHA_public_key: ""
+        reCAPTCHA_private_key: ""

     tabKey: "general"

@@ -408,8 +482,11 @@
           </TabPane>

           <TabPane eventKey='email' tab='Email'>
-            <EmailTab refresh={@refresh} emailSettings={@state.settings.email} emailFilterSettings={@state.settings.email_filter}
-              loggingSettings={@state.settings.logging} key={Math.random()}/>
+            <EmailTab refresh={@refresh} emailSettings={@state.settings.email} emailFilterSettings={@state.settings.email_filter} key={Math.random()}/>
+          </TabPane>
+
+          <TabPane eventKey='captcha' tab='CAPTCHA'>
+            <CaptchaTab refresh={@refresh} captchaSettings={@state.settings.captcha} key={Math.random()}/>
           </TabPane>
         </TabbedArea>
       </Grid>


added null check on user when checking permissions

--- services/web/app/coffee/managers/SecurityManager.coffee
+++ services/web/app/coffee/managers/SecurityManager.coffee
@@ -89,7 +89,9 @@

 	requestIsOwner : (req, res, next)->
 		getRequestUserAndProject req, res, {}, (err, user, project)->
-			if userIsOwner user, project || user.isAdmin
+			if !user?
+				return res.redirect('/restricted')
+			else if userIsOwner user, project || user.isAdmin
 				next()
 			else
 				logger.log user_id: user?._id, email: user?.email, "user is not owner of project redirecting to restricted page"


#44 cache rebuild after /game posting implemented

--- src/server/controllers/game.coffee
+++ src/server/controllers/game.coffee
@@ -1,4 +1,5 @@
 modelBuilder = require('../models/BuildModels')
+rebuildCache = require('../models/RebuildCache')

 module.exports = (app) ->
     app.post('/game', (req, res) ->
@@ -10,6 +11,13 @@
           response.push("Дата: #{models.Game.date}")
           response.push("Ведущий: #{models.Game.referee}")
           response.push("Победа: #{models.Game.result}")
-          res.status(200).send(response)
+          rebuildCache((err, top10)->
+            if err
+              res.status(500).send(["error occured: #{err}"])
+            else
+              response.push("Cache rebuilded")
+              console.log(models.Players)
+              res.status(200).send(response)
+          )
       )
     )


Use DisplayMarkerLayer for Results

Use DisplayMarkerLayer for Results

--- lib/editor/result.coffee
+++ lib/editor/result.coffee
@@ -32,7 +32,8 @@

 metrics = throttle metrics, 60*60*1000

-resultEditorRegistry = {}
+resultEditorRegistry = new Set
+layers = {}

 module.exports =
 class Result
@@ -134,7 +135,7 @@
         resultEditorRegistry[ed.id] = true
         # create new editor specific result animation method
         listener = debounce((->
-          res = ed.findMarkers().filter((m) -> m.result?).map((m) -> m.result)
+          res = layers[ed.id].getMarkers().filter((m) -> m.result?).map((m) -> m.result)
           # reads
           rect = null
           fastdom.measure ->
@@ -148,7 +149,9 @@
         window.requestAnimationFrame listener

   initMarker: ->
-    @marker = @editor.markBufferRange @lineRange(@start, @end)
+    if not layers.hasOwnProperty @editor.id
+      layers[@editor.id] = @editor.addMarkerLayer()
+    @marker = layers[@editor.id].markBufferRange @lineRange(@start, @end)
     @marker.result = @
     @decorateMarker()
     @disposables.add @marker.onDidChange (e) => @checkMarker e
@@ -218,7 +221,7 @@
   @all: -> # TODO: scope selector
     results = []
     for item in atom.workspace.getPaneItems() when atom.workspace.isTextEditor item
-      item.findMarkers().filter((m) -> m.result?).forEach (m) ->
+      layers[item.id]?.getMarkers().forEach (m) ->
         results.push m.result
     results

@@ -228,18 +231,18 @@
       result.invalidate()

   @forLines: (ed, start, end, type = 'any') ->
-    ed.findMarkers().filter((m) -> m.result? &&
-                                   m.getBufferRange().intersectsRowRange(start, end) &&
-                                  (m.result.type == type || type == 'any'))
-                    .map((m) -> m.result)
+    layers[ed.id]?.findMarkers(intersectsBufferRowRange: [start, end])
+                  .filter((m) -> (m.result.type == type || type == 'any'))
+                  .map((m) -> m.result)

   @removeLines: (ed, start, end, type = 'any') ->
     rs = @forLines(ed, start, end, type)
+    return unless rs?
     rs.map (r) -> r.remove()
     rs.length > 0

   @removeAll: (ed = atom.workspace.getActiveTextEditor()) ->
-    ed?.findMarkers().filter((m) -> m.result?).map((m) -> m.result.remove())
+    layers[ed.id]?.getMarkers().map((m) -> m.result.remove())

   @removeCurrent: (e) ->
     if (ed = atom.workspace.getActiveTextEditor())
@@ -252,6 +255,7 @@
     ed = atom.workspace.getActiveTextEditor()
     for sel in ed.getSelections()
       rs = @forLines ed, sel.getHeadBufferPosition().row, sel.getTailBufferPosition().row
+      continue unless rs?
       rs.map (r) -> r.toggleView()

   # Commands


Prioritize close matches in install search results

--- lib/install-panel.coffee
+++ lib/install-panel.coffee
@@ -125,7 +125,7 @@
       null

   performSearch: ->
-    if query = @searchEditorView.getText().trim()
+    if query = @searchEditorView.getText().trim().toLowerCase()
       @performSearchForQuery(query)

   performSearchForQuery: (query) ->
@@ -170,6 +170,8 @@
       .then (packages=[]) =>
         @highlightExactMatch(@resultsContainer, query, packages)
       .then (packages=[]) =>
+        @addCloseMatches(@resultsContainer, query, packages)
+      .then (packages=[]) =>
         @addPackageViews(@resultsContainer, packages)
       .catch (error) =>
         @searchMessage.hide()
@@ -189,8 +191,18 @@

     packages

+  addCloseMatches: (container, query, packages) ->
+    matches = _.filter(packages, (pkg) -> pkg.name.indexOf(query) >= 0)
+
+    for pack in matches
+      packageCard = @getPackageCardView(pack)
+      @addPackageCardView(container, packageCard)
+      packages.splice(packages.indexOf(pack), 1)
+
+    packages
+
   addPackageViews: (container, packages) ->
-    for pack, index in packages
+    for pack in packages
       packageCard = @getPackageCardView(pack)
       @addPackageCardView(container, packageCard)


Fix access notifications when changing from empty

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -551,7 +551,7 @@
             authors =
               for author in msg[key]
                 author: author
-                diff: if old? and author not in old[key] then '+' else ''
+                diff: if old? and author not in (old[key] ? []) then '+' else ''
             for author in old?[key] ? []
               if author not in msg[key]
                 authors.push


Comments

Comments

--- lib/path-searcher.coffee
+++ lib/path-searcher.coffee
@@ -60,7 +60,7 @@
       else
         # TODO: I want to break this into a function, but it needs to return the
         # new text and an offset, or an offset and a length. I am worried about
-        # speed and creating a bunch of arrays just for returning from this
+        # speed and creating a bunch of arrays just for returning from the
         # proposed function.

         # Find the line context around the match
@@ -76,7 +76,7 @@
           lineTextEndOffset = lineTextLength - 1
           lineTextOffset = lineTextEndOffset - @maxLineLength

-        # We dont want the line to break a word, so find the boundaries
+        # We dont want the line to break a word, so expand to the word boundaries
         lineTextOffset = @findWordBreak(line, lineTextOffset, -1)
         lineTextEndOffset = @findWordBreak(line, lineTextEndOffset, 1) + 1


:lipstick: Add button to exit resolver dialog

--- lib/resolver-view.coffee
+++ lib/resolver-view.coffee
@@ -13,6 +13,8 @@
         @div class: 'block text-info', =>
           @span outlet: 'actionText', 'Save and stage'
           @text ' this file for commit?'
+      @div class: 'pull-left', =>
+        @button class: 'btn btn-primary', click: 'dismiss', 'Maybe Later'
       @div class: 'pull-right', =>
         @button class: 'btn btn-primary', click: 'resolve', 'Mark Resolved'


clean comments

clean comments

--- scripts/cron_events.coffee
+++ scripts/cron_events.coffee
@@ -52,24 +52,6 @@
         cron.addJob name, period, eventName, tz, options, (so) ->
           res.send so.message
         res.finish()
-
-  # #   hubot cron <name> at <date> <event> [<tz>]
-  # robot.respond new RegExp(
-  #   'cron ([^ ]+) (?:at|on) (.*)' +
-  #   '(?: in ([^ ]+))?' +
-  #   '(?: is ([-_a-zA-Z0-9\.]+))?' +
-  #   '(?: with ([-_a-zA-Z0-9]+=.+)+)? *$'
-  #   ), (res) ->
-  #     withPermission res, ->
-  #       name = res.match[1]
-  #       at = res.match[2]
-  #       tz = res.match[3]
-  #       eventName = res.match[4]
-  #       args = cron._extractKeys res.match[5]
-  #       options = res.match[5]
-  #       cron.addJob name, period, eventName, tz, options, (so) ->
-  #         res.send so.message
-  #       res.finish()

   #   hubot cron start <name>
   robot.respond /cron (?:start|resume) ([^ ]+)$/, (res) ->


social: remove unnecessary constant

social: remove unnecessary constant

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -1,6 +1,4 @@
 class SocialApiController extends KDController
-
-  SCREEN_MAP_SIZE = 5

   constructor: (options = {}, data) ->


Client: Canned text is always one line.

Client: Canned text is always one line.

Add newlines on a case-by-case basis.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -429,11 +429,8 @@
       when 'MOVE_MISS', 'JUMP_KICK_MISS', 'MOVE_FAIL', 'IMMUNITY'
         @addSummary(cannedText)
         setTimeout(done, 500)
-      when 'GOT_HIT', 'CRITICAL_HIT', 'SUPER_EFFECTIVE', 'NOT_VERY_EFFECTIVE'
+      else
         @addSummary(cannedText)
-        done()
-      else
-        @addSummary(cannedText, newline: true)
         done()

   getCannedText: (cannedTextName, args) =>


Add more comments on getConversation

Add more comments on getConversation

--- client/app/stores/message_store.coffee
+++ client/app/stores/message_store.coffee
@@ -581,18 +581,22 @@
     * @return {List}
     ###
     getConversation: (param={}) ->
+        # Update global context
         if param.conversationID?
             @setConversation param.conversationID

+        # If no specific action is precized
+        # return all contextual conversations
         unless _.isFunction param.transform
             return _conversationMemoize

         messageID = param.messageID or @getCurrentID()
         conversationID = param.conversationID or @getByID(messageID)?.get 'conversationID'
-
         messages = _messagesWithInFlights()

-        # Remove selected messages
+        # In this case, we just want
+        # next/previous message from a selection
+        # then remove selected messages from the list
         if param.conversationIDs
             conversationID = param.conversationIDs[0]
             messages = messages


Updated atom config

Updated atom config

--- atom/atom.symlink/config.cson
+++ atom/atom.symlink/config.cson
@@ -1,7 +1,7 @@
 'exception-reporting':
   'userId': '5392f155-a911-a1c0-cdd2-604cd7bf4ef0'
 'release-notes':
-  'viewedVersion': '0.81.0'
+  'viewedVersion': '0.84.0'
 'welcome':
   'showOnStartup': false
 'metrics':
@@ -22,6 +22,4 @@
     '.svn'
     '.DS_Store'
   ]
-  'disabledPackages': [
-    'language-rspec'
-  ]
+  'disabledPackages': []


Improve Gulp dependency tree (introduce sequence)

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -45,13 +45,13 @@
         'bower_components/select2/select2-spinner.gif'
       ]
   dest:
+    app: 'build/'
     css: 'build/public/css'
     js: 'build/public/js'
     img: 'build/public/img'
-    app: 'build/'
 }

-gulp.task 'app-coffee', ['app-lint'], (cb) ->
+gulp.task 'app-coffee', ['clean', 'app-lint'], (cb) ->
   gulp.src(paths.src.app.coffee, base: '.')
   .pipe(sourcemaps.init(sourceRoot: '../'))
   .pipe(coffee(bare: true).on('error', gutil.log))
@@ -59,28 +59,28 @@
   .pipe(gulp.dest(paths.dest.app))
   cb()

-gulp.task 'templates', ->
+gulp.task 'templates', ['clean'], ->
   gulp.src(paths.src.app.jade, base: '.')
   .pipe(gulp.dest(paths.dest.app))

-gulp.task 'assets-coffee', ['assets-lint'], ->
+gulp.task 'assets-coffee', ['clean', 'assets-lint'], ->
   gulp.src(paths.src.assets.coffee)
   .pipe(coffee().on('error', gutil.log))
   .pipe(uglify())
   .pipe(concat('default.js'))
   .pipe(gulp.dest(paths.dest.js))

-gulp.task 'js', ->
+gulp.task 'js', ['clean'], ->
   gulp.src(paths.src.assets.js)
   .pipe(uglify())
   .pipe(concat('lib.js'))
   .pipe(gulp.dest(paths.dest.js))

-gulp.task 'img', ->
+gulp.task 'img', ['clean'], ->
   gulp.src(paths.src.assets.img)
   .pipe(gulp.dest(paths.dest.img))

-gulp.task 'less', ->
+gulp.task 'less', ['clean'], ->
   gulp.src(paths.src.assets.less)
   .pipe(sourcemaps.init())
   .pipe(less(paths: paths.bower)).on('error', gutil.log)
@@ -89,7 +89,7 @@
   .pipe(sourcemaps.write())
   .pipe(gulp.dest(paths.dest.css))

-gulp.task 'css', ->
+gulp.task 'css', ['clean'], ->
   gulp.src(paths.src.assets.css)
   .pipe(urlAdjuster(prepend: '/img/'))
   .pipe(prefix())
@@ -147,15 +147,15 @@
 gulp.task 'seed', shell.task 'node_modules/.bin/coffee ./config/seed.coffee'

 gulp.task 'watch', ->
+  gulp.watch(paths.src.app.coffee, ['app-coffee'])
+  gulp.watch(paths.src.app.jade, ['templates'])
+  gulp.watch(paths.src.assets.coffee, ['assets-coffee'])
   gulp.watch(paths.src.assets.less, ['less'])
-  gulp.watch(paths.src.assets.coffee, ['assets-coffee'])
-  gulp.watch(paths.src.app.coffee, ['app-coffee'])
-  gulp.watch(paths.src.app.coffee, ['templates'])

-gulp.task 'clean', ->
-  del('build')
+gulp.task 'clean', (cb) ->
+  del('build', cb)

-commonTasks = ['clean', 'css', 'js', 'less', 'img', 'assets-coffee']
+commonTasks = ['css', 'js', 'less', 'img', 'assets-coffee']

 gulp.task 'build', commonTasks.concat ['app-coffee', 'templates']


hub: updating robots.txt after soft releasing /share

--- src/smc-hub/hub_http_server.coffee
+++ src/smc-hub/hub_http_server.coffee
@@ -142,11 +142,8 @@
         res.header('Cache-Control', 'private, no-cache, must-revalidate')
         res.write('''
                   User-agent: *
+                  Allow: /share
                   Disallow: /projects/*
-                  Allow: /*/raw/*.html
-                  Allow: /*/raw/*.md
-                  Allow: /*/raw/*.tex
-                  Allow: /*/raw/*.pdf
                   Disallow: /*/raw/
                   Disallow: /*/port/
                   Disallow: /haproxy


Social: added christian into admins

Social: added christian into admins

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -700,7 +700,7 @@

   dummyAdmins = [ 'sinan', 'devrim', 'gokmen', 'fatihacet', 'arslan',
                   'sent-hil', 'cihangirsavas', 'leeolayvar', 'stefanbc',
-                  'szkl', 'nitin', 'usirin', 'kodinglearn' ] # kodinglearn is nitin's impersonation account
+                  'szkl', 'nitin', 'usirin', 'kodinglearn', 'mulvad' ] # kodinglearn is nitin's impersonation account

   isEmailVerified: (callback) ->


Update dummy admin list

Update dummy admin list

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -751,7 +751,7 @@
         @update ($set: 'counts.twitterFollowers': followerCount), ->

-  dummyAdmins = [ "sinan", "devrim", "gokmen", "chris", "fatihacet", "arslan",
+  dummyAdmins = [ "sinan", "devrim", "gokmen", "fatihacet", "arslan",
                   "sent-hil", "cihangirsavas", "leeolayvar", "stefanbc",
                   "szkl", "canthefason", "nitin", "igungor", "usirin"]


Add {{admin_url}} handlebars helper

Add {{admin_url}} handlebars helper

We might not be mounted at the root. We need a way to reference routes
relative to the admin root.

--- backend/app/assets/javascripts/spree/backend/handlebars_extensions.coffee
+++ backend/app/assets/javascripts/spree/backend/handlebars_extensions.coffee
@@ -7,3 +7,5 @@
     console.error "No translation found for #{key}."
     key

+Handlebars.registerHelper "admin_url", ->
+  Spree.pathFor("admin")


Implement wheals' suggestion to parse {...} to hyperlinks

--- src/public/lookup.coffee
+++ src/public/lookup.coffee
@@ -54,6 +54,10 @@
                     for v in val
                        r += '
' + _.escape(v) + '
'
                     r += '
'
+
+                    # Parse {...} to links
+                    r = r.replace /{(.+)}/g, "$1"
+
                     return r
             )
         )


add assertions to an encryption test

add assertions to an encryption test

This failed sometimes when --batch mode aborted for lack of tracking
info. Not sure why it ever succeeded.

--- test/files/3_enc_dec.iced
+++ test/files/3_enc_dec.iced
@@ -32,7 +32,7 @@
   T.no_error err
   cb err

-exports.u0_encrypt_for_u1 = (T,cb) ->
+exports.alice_encrypt_for_bob = (T,cb) ->
   args = [ "encrypt", bob.username ]
   await alice.keybase { args , stdin : msg, quiet : true } , defer err, out
   T.no_error err
@@ -57,7 +57,7 @@
   cb()

 exports.alice_encrypt_for_charlie_with_sig = (T,cb) ->
-  args = [ "encrypt", "--batch", "--sign" ].concat [ charlie.username ]
+  args = [ "encrypt", "--batch", "--sign" ].concat(charlie.assertions()).concat [ charlie.username ]
   await alice.keybase { args, stdin : msg, quiet : true }, defer err, out
   T.no_error err
   T.assert out, "got back a PGP message"


FIxed incorrect class name use

FIxed incorrect class name use

--- src/Jme/MainBundle/Resources/coffee/RemoveDialog.coffee
+++ src/Jme/MainBundle/Resources/coffee/RemoveDialog.coffee
@@ -9,7 +9,7 @@

     createDialog: ->
         if($(@id).length == 0)
-            $dialog = $('

').attr({id: @id, title: ExposeTranslation.get('item.remove')})
+            $dialog = $('

').attr({id: @id, title: Translator.get('item.remove')})
             $('body').append($dialog);
         else
             $dialog = $(@id);
@@ -24,13 +24,13 @@

             buttons: [
                 {
-                    text: ExposeTranslation.get("yes"),
+                    text: Translator.get("yes"),
                     click: ->
                         window.location.href = $(ff).attr "href"
                         $(this).dialog("close")
                 },
                 {
-                    text: ExposeTranslation.get("no"),
+                    text: Translator.get("no"),
                     click: ->
                         $(this).dialog("close")
                 }


The server now returns structed error msgs - use them.

--- src/scripts/controllers/dapiControllers.coffee
+++ src/scripts/controllers/dapiControllers.coffee
@@ -324,10 +324,7 @@
         )
       .error((data, status, headers) =>
         @uploading = false
-        if status == 406
-          @add_error('No location data found in that file...')
-        else
-          @add_error('Upload failed: ' + data)
+        @add_error(data.message)
       )

     @isMine = () =>


removing trailing whitespace in base.coffee

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -3,7 +3,7 @@
   _ = require('underscore')
   Backbone = require('backbone')
   settings = require('settings')
-  socialMedia = require('cs!helpers/socialmedia.coffee')
+  socialMedia = require('cs!helpers/socialmedia.coffee')

   dispose = (obj) ->
     delete obj.parent


Better SearchBarView specs; covers new state

--- components/search_bar/test/view.coffee
+++ components/search_bar/test/view.coffee
@@ -20,26 +20,15 @@
       Backbone.$ = $

       benv.render resolve(__dirname, '../template.jade'), {}, =>
-        @indicateLoadingSpy     = sinon.spy SearchBarView::, 'indicateLoading'
-        @concealLoadingSpy      = sinon.spy SearchBarView::, 'concealLoading'
-        @selectResultSpy        = sinon.spy SearchBarView::, 'selectResult'
-        @displaySuggestionsSpy  = sinon.spy SearchBarView::, 'displaySuggestions'
-        @hideSuggestionsSpy     = sinon.spy SearchBarView::, 'hideSuggestions'
-
-        @$input             = $('#main-layout-search-bar-input')
-        @$input.typeahead   = sinon.stub()
+        @$input = $('#main-layout-search-bar-input')
+        @$input.typeahead = sinon.stub()

         @view = new SearchBarView
-          el     : $('#main-layout-search-bar-container')
-          $input : @$input
+          el: $('#main-layout-search-bar-container')
+          $input: @$input
         done()

   afterEach ->
-    @indicateLoadingSpy.restore()
-    @concealLoadingSpy.restore()
-    @selectResultSpy.restore()
-    @displaySuggestionsSpy.restore()
-    @hideSuggestionsSpy.restore()
     benv.teardown()

   describe '#initialize', ->
@@ -71,18 +60,61 @@
       @view.selectResult({}, model)
       window.location.should.equal model.get 'location'

-  it 'responds to search:start with #indicateLoading', ->
-    @view.trigger 'search:start'
-    @indicateLoadingSpy.called.should.be.ok
+  describe '#indicateLoading', ->
+    beforeEach ->
+      @view.trigger 'search:start'

-  it 'responds to search:complete with #concealLoading', ->
-    @view.trigger 'search:complete'
-    @concealLoadingSpy.called.should.be.ok
+    it 'triggers the loading state of the component', ->
+      @view.$el.attr('class').should.include 'is-loading'

-  it 'responds to search:opened with #displaySuggestions', ->
-    @view.trigger 'search:opened'
-    @displaySuggestionsSpy.called.should.be.ok
+    it 'restores the feedback to the original state', ->
+      @view.$el.html().should.include 'Search Artists, Artworks, Galleries, Museums, Categories'

-  it 'responds to search:closed with #hideSuggestions', ->
-    @view.trigger 'search:closed'
-    @hideSuggestionsSpy.called.should.be.ok
+  describe '#concealLoading', ->
+    it 'removes the loading state', ->
+      @view.trigger 'search:start'
+      @view.$el.attr('class').should.include 'is-loading'
+      @view.trigger 'search:complete'
+      @view.$el.attr('class').should.not.include 'is-loading'
+
+  describe '#displaySuggestions', ->
+    it 'displays the feedback when the input is empty', ->
+      @view.$('.autocomplete-feedback').text ''
+      _.isEmpty(@view.$('input').text()).should.be.true
+      @view.trigger 'search:opened'
+      @view.$el.html().should.include 'Search Artists, Artworks, Galleries, Museums, Categories'
+      @view.$el.attr('class').should.include 'is-open'
+
+    it 'does not display the feedback when the input has text', ->
+      @view.$('input').val 'Foo Bar'
+      @view.trigger 'search:opened'
+      _.isEmpty(@view.$el.attr('class')).should.be.true
+
+  describe '#hideSuggestions', ->
+    it 'removes the open state', ->
+      @view.trigger 'search:opened'
+      @view.$el.attr('class').should.include 'is-open'
+      @view.trigger 'search:closed'
+      _.isEmpty(@view.$el.attr('class')).should.be.true
+
+  describe '#displayFeedback', ->
+    it 'does not render a message when there are results', ->
+      @view.search.results.add(fabricate 'artist')
+      @view.search.results.length.should.equal 1
+      @view.trigger 'search:complete'
+      @view.$el.html().should.not.include 'No results found'
+      _.isEmpty(@view.$el.attr 'class').should.be.true
+
+    it 'renders a message when there are no results', ->
+      @view.search.results.length.should.equal 0
+      @view.trigger 'search:complete'
+      @view.$el.html().should.include 'No results found'
+      @view.$el.attr('class').should.include 'is-no-results'
+
+  describe '#feedbackString', ->
+    it 'uses the mode if there is one available', ->
+      @view.mode = 'artists'
+      @view.feedbackString().should.equal 'Search for Artists…'
+
+    it 'falls back to the default string', ->
+      @view.feedbackString().should.equal 'Search Artists, Artworks, Galleries, Museums, Categories…'


Update coffee file to yocto warrior

Update coffee file to yocto warrior

Changelog-entry: Update edison.coffee file to yocto warrior
Signed-off-by: Vicentiu Galanopulo <316f1858a504e73da779fe12f7a460349852883d@balena.io>

--- edison.coffee
+++ edison.coffee
@@ -62,7 +62,7 @@
 		machine: 'edison'
 		image: 'resin-image'
 		fstype: 'zip'
-		version: 'yocto-sumo'
+		version: 'yocto-warrior'
 		deployArtifact: 'resin-edison'
 		archive: true
 		compressed: true


now adding location headers when the response definition inclues a 201

--- src/configuration/preprocessors/responses.coffee
+++ src/configuration/preprocessors/responses.coffee
@@ -7,7 +7,7 @@
 # below adds these responses based on the enumerated response codes in the API
 # description.

-allowedHeaders = "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control,ETag,Last-Modified"
+allowedHeaders = "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control,ETag,Last-Modified,Location"

 Responses = (description) ->
   # Array of possible responses generated by the Lambda integration


update asana script to show task names.

update asana script to show task names.

--- src/scripts/asana.coffee
+++ src/scripts/asana.coffee
@@ -6,21 +6,22 @@
 #
 # Configuration:
 #   HUBOT_ASANA_API_KEY - find this in Account Settings -> API
-#
+#
 #   HUBOT_ASANA_WORKSPACE_ID - list all workspaces using
 #   curl -u <api_key>: https://app.asana.com/api/1.0/workspaces
-#   (note the colon after the api key)
-#
+#   (note the colon after the api key)
+#
 #   HUBOT_ASANA_PROJECT_ID - list all projects in the workspace using:
 #   curl -u <api_key>: https://app.asana.com/api/1.0/workspaces/<workspace id>/projects
 #
 # Commands:
 #   todo: @name? <task directive> - public message starting with todo: will add task, optional @name to assign task
 #   hubot todo users - Message the bot directly to list all available users in the workspace
-#
+#
 # Author:
 #   idpro
 #   abh1nav
+#   rajiv

 url  = 'https://app.asana.com/api/1.0'

@@ -87,6 +88,14 @@
     else
       addTask msg, taskName, '/tasks', params, false

+# show task title
+  robot.hear /https:\/\/app\.asana\.com\/(\d+)\/(\d+)\/(\d+)/, (msg) ->
+    taskId = msg.match[3]
+    getRequest msg, "/tasks/#{taskId}", (err, res, body) ->
+      response = JSON.parse body
+      name = response.data.name
+      msg.send "#{taskId}: #{name}"
+
 # List all Users
   robot.respond /(todo users)/i, (msg) ->
     getRequest msg, "/workspaces/#{workspace}/users", (err, res, body) ->


Set ArticleList as default route.

Set ArticleList as default route.

--- src/scripts/index.coffee
+++ src/scripts/index.coffee
@@ -12,13 +12,13 @@
 	render: ->

-
<Link to="blog">Blog</Link>

+
<Link to="app">Home</Link>

 			<RouteHandler/>

 routes = <Route name="app" path="/" handler={ App }>
-	<Route name="blog" path="/blog" handler={ ArticleList }/>
+	<DefaultRoute handler={ ArticleList }/>
 </Route>

 Router.run routes, Router.HistoryLocation, (Handler) ->


Fixed test for LIMIT with OFFSET to match modified implementation of query

--- test/pg_query_spec.coffee
+++ test/pg_query_spec.coffee
@@ -51,7 +51,7 @@

     it "adds a LIMIT with SKIP if specified", ->
       query = db.products.find(5).limit(10,1)
-      query.sql.should.equal("SELECT * FROM products \nWHERE \"id\" = 5 \nLIMIT(10,1)")
+      query.sql.should.equal("SELECT * FROM products \nWHERE \"id\" = 5 \nLIMIT 10 OFFSET 1")

     it "adds an ORDER if specified", ->
       query = db.products.find(5).order("name")
@@ -171,4 +171,4 @@
         should.exist result

     it "tables should have an each method", ->
-      should.exist db.products.each+      should.exist db.products.each


test: fix the unit tests

test: fix the unit tests

--- test/reporter.spec.coffee
+++ test/reporter.spec.coffee
@@ -43,6 +43,7 @@
     isDefined: (v) -> helper.isDefined v
     merge: (v...) -> helper.merge v...
     mkdirIfNotExists: mockMkdir
+    normalizeWinPath: (path) -> helper.normalizeWinPath path

   mocks =
     fs: mockFs
@@ -108,9 +109,10 @@

     beforeEach ->
       rootConfig =
+        basePath: '/base'
         coverageReporter: dir: 'path/to/coverage/'
       emitter = new events.EventEmitter
-      reporter = new m.CoverageReporter rootConfig, emitter, mockHelper, mockLogger
+      reporter = new m.CoverageReporter rootConfig, mockHelper, mockLogger
       browsers = new Collection emitter
       # fake user agent only for testing
       # cf. helper.browserFullNameToShort
@@ -124,7 +126,7 @@

     it 'has no pending file writings', ->
       done = sinon.spy()
-      emitter.emit 'exit', done
+      reporter.onExit done
       expect(done).to.have.been.called

     it 'has no coverage', ->
@@ -146,7 +148,7 @@
       expect(mockAdd).to.have.been.calledWith result.coverage
       expect(mockMkdir).to.have.been.called
       args = mockMkdir.lastCall.args
-      expect(args[0]).to.deep.equal path.resolve(rootConfig.coverageReporter.dir)
+      expect(args[0]).to.deep.equal path.resolve('/base', rootConfig.coverageReporter.dir)
       args[1]()
       expect(mockFs.writeFile).to.have.been.calledWith
       args2 = mockFs.writeFile.lastCall.args
@@ -156,8 +158,8 @@
       reporter.onRunComplete browsers
       expect(mockMkdir).to.have.been.calledTwice
       dir = rootConfig.coverageReporter.dir
-      expect(mockMkdir.getCall(0).args[0]).to.deep.equal path.resolve(dir, fakeChrome.name)
-      expect(mockMkdir.getCall(1).args[0]).to.deep.equal path.resolve(dir, fakeOpera.name)
+      expect(mockMkdir.getCall(0).args[0]).to.deep.equal path.resolve('/base', dir, fakeChrome.name)
+      expect(mockMkdir.getCall(1).args[0]).to.deep.equal path.resolve('/base', dir, fakeOpera.name)
       mockMkdir.getCall(0).args[1]()
       expect(mockReportCreate).to.have.been.called
       expect(mockWriteReport).to.have.been.called


Add error handling for stat server request

--- server/startup/cron.coffee
+++ server/startup/cron.coffee
@@ -10,8 +10,11 @@
 	statistics = RocketChat.statistics.save()
 	statistics.host = Meteor.absoluteUrl()
 	if RocketChat.settings.get 'Statistics_reporting'
-		HTTP.post 'https://rocket.chat/stats',
-			data: statistics
+		try
+			HTTP.post 'https://rocket.chat/stats',
+				data: statistics
+		catch e
+			logger.warn('Failed to send usage report')
 	return

 Meteor.startup ->


Hide email column.  Teacher can't view that data

--- src/components/course-administration/period-roster.cjsx
+++ src/components/course-administration/period-roster.cjsx
@@ -17,7 +17,6 @@

       {student.full_name}
       {student.id}
-      {student.email}

         <ResetPasswordLink courseId={@props.courseId} student={student} />
         <ChangePeriodLink courseId={@props.courseId} student={student} />
@@ -44,7 +43,6 @@

             Name
             Tutor ID
-            Email
             Actions


Add 9 level table

Add 9 level table

--- server/cron/downloadMaterials.coffee
+++ server/cron/downloadMaterials.coffee
@@ -521,7 +521,7 @@
             zaoch: "Нижегородские школьники",
             notnnov: "Остальные школьники"
             stud: "Студенты и старше"
-        tables = ["1А,1Б", "1В,1Г", "2", "3", "4", "5", "6", "7", "8", "main", "reg", "byWeek"]
+        tables = ["1А,1Б", "1В,1Г", "2", "3", "4", "5", "6", "7", "8", "9", "main", "reg", "byWeek"]
         materials = []
         trees = []
         globalHeaderMaterial = new Material


embedly: create md5 with width/height

embedly: create md5 with width/height

--- server/lib/server/image_cache.coffee
+++ server/lib/server/image_cache.coffee
@@ -40,7 +40,9 @@
   # replace nonalphanumeric characters
   ext = ext.replace(/\W+/g, "")

-  md5      = crypto.createHash("md5").update(noExt).digest("hex")
+  for i in [noExt, width, height]
+    md5 = crypto.createHash("md5").update(i).digest("hex")
+
   filename = "#{imagePath}/#{md5}.#{ext}"

   serveFile = (filename, res)->


Improve title generation

Improve title generation

--- src/javascripts/includes/state_controller.coffee
+++ src/javascripts/includes/state_controller.coffee
@@ -6,12 +6,18 @@
     @stateChanged()

   state: ->
-    HashUtil.decode(location.hash.replace('#', ''))
+    state = HashUtil.decode(location.hash.replace('#', ''))
+
+    if state.length > 0
+      state

   setState: (state) ->
     location.hash = HashUtil.encode(state)

   stateChanged: ->
-    @titleElement.text("#{@baseTitle} | #{@state()}")
+    @updateTitle()
     @callback @state()

+  updateTitle: ->
+    @titleElement.text [@baseTitle, @state()].filter((i) -> !!i).join(' | ')
+


Added back changes that was accidentally got reverted

https://github.com/poooi/poi/commit/e10b11ce521dba1d74325ba2ca9d3195cb5d415e#commitcomment-15081298

--- views/app.cjsx
+++ views/app.cjsx
@@ -566,15 +566,21 @@
   """
 # F5 & Ctrl+F5 & Alt+F5
 window.addEventListener 'keydown', (e) ->
-  if process.platform == 'darwin' and e.keyCode is 82 and e.metaKey
-    if e.shiftKey # cmd + shift + r
-      $('kan-game webview').reloadIgnoringCache()
-    else if e.altKey # cmd + alt + r
-      refreshFlash()
-    else # cmd + r
-      # Catched by menu
-      # $('kan-game webview').reload()
-      false
+  if process.platform == 'darwin'
+    if e.keyCode is 91 or e.keyCode is 93
+      # When the game (flash) is on focus, it catches all keypress events
+      # Blur the webview when any Cmd key is pressed,
+      # so the OS shortcuts will always work
+      remote.getCurrentWindow().blurWebView()
+    else if e.keyCode is 82 and e.metaKey
+      if e.shiftKey # cmd + shift + r
+        $('kan-game webview').reloadIgnoringCache()
+      else if e.altKey # cmd + alt + r
+        refreshFlash()
+      else # cmd + r
+        # Catched by menu
+        # $('kan-game webview').reload()
+        false
   else if e.keyCode is 116
     if e.ctrlKey # ctrl + f5
       $('kan-game webview').reloadIgnoringCache()


clicking off the us will hide connections and recruit nodes

--- source/javascripts/county-map.coffee
+++ source/javascripts/county-map.coffee
@@ -163,6 +163,11 @@

     recruitNodes.exit().remove()
     selectedSchool = school
+
+    map.on 'click', ->
+      if d3.event.target.tagName is 'svg'
+        connections.remove()
+        recruitNodes.remove()

   # Set the fill domain based on the total number of recruits
   env.fill.domain [0.2, d3.max(env.counties.features, (d) -> d.properties.total)]


Own rendering when deleting selection that spans blocks. Fixes #360

Avoids a contenteditable bug in Firefox that could result in duplicated <trix-editor>s. Tracked down and reported here: https://bugzilla.mozilla.org/show_bug.cgi?id=1332924.

Somewhat related: https://github.com/basecamp/trix/pull/338

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -126,16 +126,19 @@
     @insertText(text)

   deleteInDirection: (direction) ->
+    locationRange = @getLocationRange()
     range = @getSelectedRange()
     selectionIsCollapsed = rangeIsCollapsed(range)
-    block = @getBlock()
-
-    if selectionIsCollapsed and direction is "backward"
-      {offset} = @document.locationFromPosition(range[0])
-      deletingIntoPreviousBlock = offset is 0
+
+    if selectionIsCollapsed
+      deletingIntoPreviousBlock = direction is "backward" and locationRange[0].offset is 0
+    else
+      selectionSpansBlocks = locationRange[0].index isnt locationRange[1].index

     if deletingIntoPreviousBlock
       if @canDecreaseBlockAttributeLevel()
+        block = @getBlock()
+
         if block.isListItem()
           @decreaseListLevel()
         else
@@ -155,7 +158,7 @@
     else
       @setDocument(@document.removeTextAtRange(range))
       @setSelection(range[0])
-      false if deletingIntoPreviousBlock
+      false if deletingIntoPreviousBlock or selectionSpansBlocks

   moveTextFromRange: (range) ->
     [position] = @getSelectedRange()


AdminModal: Fix issues, sync it with master

--- client/Main/sidebar/modals/adminmodal.coffee
+++ client/Main/sidebar/modals/adminmodal.coffee
@@ -228,9 +228,16 @@
       submitValuesAsText  : yes
       dataSource          : (args, callback)=>
         {inputValue} = args
-        blacklist = (data.getId() for data in @userController.getSelectedItemData())
-        KD.remote.api.JAccount.byRelevance inputValue, {blacklist}, (err, accounts)=>
-          callback accounts
+        if /^@/.test inputValue
+          query = 'profile.nickname': inputValue.replace /^@/, ''
+          KD.remote.api.JAccount.one query, (err, account)=>
+            if not account
+              @userController.showNoDataFound()
+            else
+              callback [account]
+        else
+          KD.remote.api.JAccount.byRelevance inputValue, {}, (err, accounts)->
+            callback accounts

     @userController.on "ItemListChanged", =>
       accounts = @userController.getSelectedItemData()
@@ -263,3 +270,21 @@
   initIntroductionTab: ->
     parentView = @modalTabs.forms["Introduction"]
     parentView.addSubView new IntroductionAdmin { parentView }
+
+class MemberAutoCompleteItemView extends KDAutoCompleteListItemView
+  constructor:(options, data)->
+    options.cssClass = "clearfix member-suggestion-item"
+    super options, data
+
+    userInput = options.userInput or @getDelegate().userInput
+
+    @addSubView @profileLink = \
+      new AutoCompleteProfileTextView {userInput, shouldShowNick: yes}, data
+
+  viewAppended:-> JView::viewAppended.call this
+
+class MemberAutoCompletedItemView extends KDAutoCompletedItem
+
+  viewAppended:->
+    @addSubView @profileText = new AutoCompleteProfileTextView {}, @getData()
+    JView::viewAppended.call this


EditorPane: Remove separate line widget dom elements.

--- client/ide/workspace/panes/editorpane.coffee
+++ client/ide/workspace/panes/editorpane.coffee
@@ -145,9 +145,9 @@
     lineHeight     = renderer.lineHeight
     charWidth      = renderer.characterWidth
     color          = KD.utils.getColorFromString username
-    widgetStyle    = "border-bottom:2px dotted #{color};height:#{lineHeight}px;margin-top:-#{lineHeight + 2}px;"
-    lineCssClass   = 'ace-line-widget'
-    cursorCssClass = "ace-participant-cursor ace-cursor-#{KD.nick()}"
+    widgetStyle    = "border-bottom:2px dotted #{color};height:#{lineHeight}px;margin-top:-#{lineHeight+2}px;"
+    lineCssClass   = "ace-line-widget ace-line-widget-#{KD.nick()}"
+    cursorCssClass = 'ace-participant-cursor'
     cursorStyle    = "background-color:#{color};height:#{lineHeight}px;margin-left:#{charWidth*col+3}px"
     lineWidgetHTML = """
       <div class='#{lineCssClass}' style='#{widgetStyle}'>
@@ -158,6 +158,8 @@

     if oldWidget
       widgetManager.removeLineWidget oldWidget
+
+    $(".ace-line-widget-#{KD.nick()}").remove()

     lineWidgetOptions =
       row        : row


Add integration test coverage for reusing windows w/ the same dir

--- spec/integration/startup-spec.coffee
+++ spec/integration/startup-spec.coffee
@@ -50,30 +50,49 @@
       driver.wait ->
         driver.getTitle().then (title) -> title.indexOf("Atom") >= 0

+  startAnotherAtom = (args=[]) ->
+    spawnSync(AtomPath, args.concat([
+      "--dev",
+      "--safe",
+      "--socket-path=#{SocketPath}"
+    ]))
+
   describe "when given the name of a file that doesn't exist", ->
+    tempFilePath = null
+
     beforeEach ->
+      tempFilePath = path.join(tempDirPath, "an-existing-file")
+      fs.writeFileSync(tempFilePath, "This was already here.")
       startAtom([path.join(tempDirPath, "new-file")])

     it "opens a new window with an empty text editor", ->
       waitsForPromise ->
+        driver.getAllWindowHandles().then (handles) ->
+          expect(handles.length).toBe 1
+        driver.executeScript(-> atom.workspace.getActivePane().getItems().length).then (length) ->
+          expect(length).toBe 1
         driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
           expect(text).toBe("")
+        driver.findElement(By.tagName("atom-text-editor")).sendKeys("Hello world!")
+        driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
+          expect(text).toBe "Hello world!"

-        driver.findElement(By.tagName("atom-text-editor")).sendKeys("Hello world!")
+      # Opening another existing file in the same directory reuses the window,
+      # and opens a new tab for the file.
+      waitsForPromise ->
+        startAnotherAtom([tempFilePath])
+        driver.wait ->
+          driver.executeScript(-> atom.workspace.getActivePane().getItems().length).then (length) ->
+            length is 2
+        driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
+          expect(text).toBe "This was already here."

-        driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
-          expect(text).toBe("Hello world!")
-
-  describe "when given the name of a file that exists", ->
-    beforeEach ->
-      existingFilePath = path.join(tempDirPath, "existing-file")
-      fs.writeFileSync(existingFilePath, "This was already here.")
-      startAtom([existingFilePath])
-
-    it "opens a new window with a text editor for the given file", ->
+      # Opening a different directory creates a new window.
       waitsForPromise ->
-        driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
-          expect(text).toBe("This was already here.")
+        startAnotherAtom([temp.mkdirSync("another-empty-dir")])
+        driver.wait ->
+          driver.getAllWindowHandles().then (handles) ->
+            handles.length is 2

   describe "when given the name of a directory that exists", ->
     beforeEach ->


one detail

one detail

--- src/smc-webapp/3d.coffee
+++ src/smc-webapp/3d.coffee
@@ -506,8 +506,8 @@
                         when 5
                             push_face4_with_color(v...)
                         else
-                            console.log("WARNING: rendering colored face with #{v.length} vertices not implemented")
-                            push_face4_with_color(v...)
+                            console.log("WARNING: rendering colored face with #{v.length - 1} vertices not implemented")
+                            push_face4_with_color(v[0], v[1], v[2], v[3], v[-1])   # might as well render most of the face...
             else
                 for v in faces
                     switch v.length


Remove annoying CoffeeScript asset

Remove annoying CoffeeScript asset

Now we should be able to test the engine in the `test/dummy` directory
by running `rails s` as usual.

This asset was added by Action Cable. We don't want to make all
stakeholders of Alexandrie install the asset handler for CoffeeScript,
since we rarely use it anyway.

Such is the life of the bleeding edge ¯\_(ツ)_/¯

Signed-off-by: Emil Henry <ae80d870eb40a8fd7c256c0ec3faf2ba511ba134@plaimi.net>

--- test/dummy/app/assets/javascripts/cable.coffee
+++ test/dummy/app/assets/javascripts/cable.coffee
@@ -1,11 +0,0 @@
-# Action Cable provides the framework to deal with WebSockets in Rails.
-# You can generate new channels where WebSocket features live using the rails generate channel command.
-#
-# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb).
-#
-#= require action_cable
-#= require_self
-#= require_tree ./channels
-#
-# @App ||= {}
-# App.cable = ActionCable.createConsumer()


fix signature interpretation

fix signature interpretation

--- src/renderer/components/doc-signatures-component.coffee
+++ src/renderer/components/doc-signatures-component.coffee
@@ -15,7 +15,7 @@
           elm.push (
           c.signatures[0].parameters?.forEach (prm, i) ->
             elm.push {prm.name}
-            if prm.defaultValue?
+            if prm.defaultValue? || prm.flags.isOptional == true
               elm.push {'?'}
             elm.push :
             elm.push {prm.type.name}


If fullname isn't null, return fullname (id). If it is null, return only id.

--- src/scripts/modules/media/tabs/metadata/metadata.coffee
+++ src/scripts/modules/media/tabs/metadata/metadata.coffee
@@ -83,6 +83,7 @@
           return licensors

+
       '.maintainers > input':
         value: 'publishers'
         type: 'select2'
@@ -99,11 +100,11 @@
       roles = @getProperty(options.role)
       collection.add(roles)

-      userRoles = _.map roles, (item) ->
+      userRoles = _.map roles, (item) =>
         if typeof item is 'string'
           return users[item]
         else
-          user = {id: item.id, text: "#{item.fullname} (#{item.id})" or item.id}
+          user = {id: item.id, text: @displayName(item.fullname, item.id)}
           users["#{user.id}"] = user
           return user

@@ -124,14 +125,22 @@
           data: (term, page) ->
             q: term # search term

-          results: (data, page) -> # parse the results into the format expected by Select2.
+          results: (data, page) => # parse the results into the format expected by Select2.
             collection.add(data.users)
             return {
-              results: _.map data.users, (item) ->
-                user = {id: item.id, text: "#{item.fullname} (#{item.id})" or item.id}
+              results: _.map data.users, (item) =>
+                user = {id: item.id, text: @displayName(item.fullname, item.id)}
                 users["#{user.id}"] = user
                 return user
             }
+
+
+    displayName: (fullname, id) ->
+      if fullname isnt null
+        return "#{fullname} (#{id})"
+      else
+        return id
+

     initialize: () ->


Ignore content script requests in options page.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -115,6 +115,9 @@
     disableVimium: disableVimium

   chrome.extension.onRequest.addListener (request, sender, sendResponse) ->
+    # in the options page, we will receive requests from both content and background scripts. ignore those
+    # from the former.
+    return unless sender.tab?.url.startsWith 'chrome-extension://'
     sendResponse requestHandlers[request.name](request, sender)
     # Ensure the sendResponse callback is freed.
     false


Add null check for "@input"

Add null check for "@input"

--- client/app/lib/providers/managed/addmanagedmachinemodal.coffee
+++ client/app/lib/providers/managed/addmanagedmachinemodal.coffee
@@ -147,7 +147,7 @@

   destroy: ->

-    @input.unsetTooltip()
+    @input?.unsetTooltip()

     super


myteam/teamsettings: member will not see remove and upload logo buttons

--- client/home/lib/myteam/components/hometeamsettings/view.coffee
+++ client/home/lib/myteam/components/hometeamsettings/view.coffee
@@ -18,19 +18,29 @@

         <TeamLogo team={@props.team} logopath={@props.logopath} callback={@bound 'onClickLogo'}/>

-          <GenericButton className={'custom-link-view primary'} title={'UPLOAD LOGO'} callback={@props.onClickLogo} />
-          <GenericButton className={'custom-link-view remove'} title={'REMOVE'} callback={@props.onRemoveLogo}/>
+          <GenericButtons canEdit={@props.canEdit} clickLogo={@props.onClickLogo} removeLogo={@props.onRemoveLogo} />
           <input ref={(input) => @input = input} accept='image/*' className='kdinput file' type='file' onChange={@props.onUploadInput} />

       <form>

-          <TeamName title={@props.team.get 'title'} teamName={@props.teamName} callback={@props.onTeamNameChanged} />
+          <TeamName canEdit={@props.canEdit} title={@props.team.get 'title'} teamName={@props.teamName} callback={@props.onTeamNameChanged} />
           <TeamDomain slug={@props.team.get 'slug'} />

         <ActionBar canEdit={@props.canEdit} callback={@props.onUpdate} onLeaveTeam={@props.onLeaveTeam}/>
       </form>

+
+
+GenericButtons = ({ canEdit, clickLogo, removeLogo }) ->
+
+  if canEdit
+

+      <GenericButton className={'custom-link-view primary'} title={'UPLOAD LOGO'} callback={clickLogo} />
+      <GenericButton className={'custom-link-view remove'} title={'REMOVE'} callback={removeLogo} />
+

+  else
+

 ActionBar = ({ canEdit, callback, onLeaveTeam }) ->