Set image dimensions after loading the File

--- src/trix/views/image_attachment_view.coffee
+++ src/trix/views/image_attachment_view.coffee
@@ -4,31 +4,40 @@
     @image = document.createElement("img")

   render: ->
-    @loadPreview() if @attachment.isPending()
+    @loadFile() if @attachment.isPending()
     @updateImageAttributes()
     @image

-  loadPreview: ->
+  loadFile: ->
     reader = new FileReader
     reader.onload = (event) =>
-      @image.setAttribute("src", event.target.result)
+      if @attachment.isPending()
+        @image.setAttribute("src", event.target.result)
+        @attachment.update(width: @image.offsetWidth, height: @image.offsetHeight)
     reader.readAsDataURL(@attachment.file)

-  attributeNames = "src width height class".split(" ")
+  attributeNames = "url width height class".split(" ")

   updateImageAttributes: ->
     attributes = {}
-    attributes[key] = value for key, value of @attachment.attributes
-    attributes.src = attributes.url
-    attributes.class = "pending-attachment" if @attachment.isPending()

     for key in attributeNames
-      if value = attributes[key]
+      attributes[key] = @attachment.attributes[key]
+
+    if attributes.url
+      attributes.src = attributes.url
+      delete attributes.url
+
+    if @attachment.isPending()
+      attributes.class = "pending-attachment"
+
+    for key, value of attributes
+      if value?
         @image.setAttribute(key, value)
       else
         @image.removeAttribute(key)

   # Attachment delegate

-  attachmentDidUpdate: (@attachment) ->
+  attachmentDidUpdate: ->
     @updateImageAttributes()


Add another priority integration test

Add another priority integration test

--- test/integration/e2eIntegrationTests.coffee
+++ test/integration/e2eIntegrationTests.coffee
@@ -1205,7 +1205,7 @@
     channel2 = new Channel
       name: "TEST DATA - Mock endpoint 2"
       urlPattern: "^/.*$"
-      priority: 2
+      priority: 3
       allow: [ "PoC" ]
       routes: [
             name: "test route"
@@ -1216,7 +1216,7 @@
     channel3 = new Channel
       name: "TEST DATA - Mock endpoint 3"
       urlPattern: "^/test/mock$"
-      priority: 1
+      priority: 2
       allow: [ "PoC" ]
       routes: [
             name: "test route"
@@ -1254,10 +1254,11 @@
       Channel.remove { name: "TEST DATA - Mock endpoint 1" }, ->
         Channel.remove { name: "TEST DATA - Mock endpoint 2" }, ->
           Channel.remove { name: "TEST DATA - Mock endpoint 3" }, ->
-            Client.remove { clientID: "testApp" }, ->
-              mockServer1.close ->
-                mockServer2.close ->
-                  done()
+            Channel.remove { name: "TEST DATA - Mock endpoint 4" }, ->
+              Client.remove { clientID: "testApp" }, ->
+                mockServer1.close ->
+                  mockServer2.close ->
+                    done()

     afterEach (done) ->
       server.stop ->
@@ -1288,3 +1289,29 @@
             else
               res.text.should.be.exactly 'target1' #should route to target1 via channel2
               done()
+
+    it "should deny access if multiple channels match but the top priority channel denys access", (done) ->
+      channel4 = new Channel
+        name: "TEST DATA - Mock endpoint 4"
+        urlPattern: "^/test/mock$"
+        priority: 1
+        allow: [ "something else" ]
+        routes: [
+              name: "test route"
+              host: "localhost"
+              port: 1234
+              primary: true
+            ]
+
+      channel4.save () ->
+
+        server.start httpPort: 5001, ->
+          request("http://localhost:5001")
+            .get("/test/mock")
+            .auth("testApp", "password")
+            .expect(401)
+            .end (err, res) ->
+              if err
+                done err
+              else
+                done()


check if selection is a link or not

check if selection is a link or not

--- plugins/linkimg.coffee
+++ plugins/linkimg.coffee
@@ -40,6 +40,17 @@
                     widget.lastSelection = widget.options.editable.getSelection()
                     dialog.dialog('open')

+                @element.bind "keyup paste change mouseup", (event) ->
+                    console.log(jQuery(event.target)[0].nodeName is "A")
+                    if jQuery(event.target)[0].nodeName is "A"
+                        button.attr "checked", true
+                        button.next().addClass "ui-state-active"
+                        button.button "refresh"
+                    else
+                        button.attr "checked", false
+                        button.next().removeClass "ui-state-active"
+                        button.button "refresh"
+
             if (@options.link)
                 buttonize "A"


catch up with sendMessage method changes - closes #14

--- src/rocketchat_driver.coffee
+++ src/rocketchat_driver.coffee
@@ -19,7 +19,7 @@

   sendMessage: (text, roomid) =>
     @logger.info "send message"
-    @asteroid.call('sendMessage', {message: text, rid: roomid})
+    @asteroid.call('sendMessage', {msg: text, rid: roomid})

   login: (username, password) =>
     @logger.info "login"


fix doc matching

fix doc matching

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -16,7 +16,7 @@
 #   # Incident Lifecycle
 #
 #   hubot pager incidents - return the current incidents
-#               sup
+#   hubot pager sup - return the current incidents
 #   hubot pager incident NNN - return the incident NNN
 #   hubot pager note <incident> <content> - add note to incident #<incident> with <content>
 #   hubot pager notes <incident> - show notes for incident #<incident>
@@ -74,7 +74,7 @@

     cmds = robot.helpCommands()
-    cmds = (cmd for cmd in cmds when cmd.match(/(pager me |who's on call)/))
+    cmds = (cmd for cmd in cmds when cmd.match(/(hubot pager|on call)/))
     msg.send cmds.join("\n")

   robot.respond /pager(?: me)? as (.*)$/i, (msg) ->


sidechat: terminal support

sidechat: terminal support

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -2808,29 +2808,29 @@
             salvus_client.new_session(mesg)

-    _get: () =>  # FUTURE ??
+    _get: =>  # FUTURE ??
         return @opts.session_uuid ? ''

     _set: (content) =>  # FUTURE ??

-    save: (cb) =>
+    save: =>
         # DO nothing -- a no-op for now
         # FUTURE: Add notion of history
         cb?()

-    focus: () =>
+    focus: =>
         @console?.focus()

-    blur: () =>
+    blur: =>
         @console?.blur()

     terminate_session: () =>

-    remove: () =>
+    remove: =>
         @element.salvus_console(false)
         super()

-    hide : () =>
+    hide: =>
         if @console?
             @element?.hide()
             @console.blur()
@@ -2845,7 +2845,7 @@
             if feature.isMobile.iOS()
                 ht = Math.floor(ht/2)
             e.height(ht)
-            @element.css(left:0, top:redux.getProjectStore(@project_id).get('editor_top_position'), position:'fixed')   # HACK: this is hack-ish; needs to be redone!
+            @element.css(left:0, top:redux.getProjectStore(@project_id).get('editor_top_position'))
             @console.focus(true)

 class Media extends FileEditor


Use cache on parallel bower install if possible

--- src/main/coffee/bower.coffee
+++ src/main/coffee/bower.coffee
@@ -5,6 +5,7 @@
   chalk = require 'bower/lib/node_modules/chalk'
   Q = require 'q'
   mout = require 'bower/lib/node_modules/mout'
+  semver = require 'bower/lib/node_modules/semver'
   common = require './common'
   Logger = require './logger'

@@ -16,6 +17,7 @@
   configs = packages.configs
   parallelize = process.argv[5] is 'true'
   logLevel = parseInt process.argv[6]
+  transitiveDependencies = {}

   log = new Logger logLevel, 'Bower'

@@ -61,30 +63,127 @@
     name = item.name
     version = item.version
     cacheName = item.cacheName
+    log.d "Check cache for #{name}##{version}"
     deferred = Q.defer()
     bower.commands.cache.list [cacheName], {}, {}
     .on 'error', (err) ->
       log.e "Checking cache failed: #{err}"
       deferred.reject()
     .on 'end', (r) ->
+      #log.d "checkCache: #{JSON.stringify r, null, '  '}" # FIXME
+      # get all caches and response.dependencies
+      # -> each -> semver.satisfies(ver)
+      # TODO Check caches for dependencies first, then
+      # add transitive dependencies to a global array.
+      # Then check that they all cached with satisfied version.
+      # Perhaps we might have to check their caches again.
+      if item.checkDependencies?
+        log.d "Retrieve transitive dependencies for #{name}##{version}"
+        if 'dependencies' in r.pkgMeta
+          log.d "#{name}##{version} has some dependencies"
+          for dep in Object.keys r.pkgMeta.dependencies
+            ver = r.pkgMeta.dependencies[dep]
+            log.d "#{name}##{version} depends on #{dep}#{ver}"
+            # dep should be jquery
+            # r.pkgMeta.dependencies[dep] should be >= 1.7
+            if dep in transitiveDependencies
+              transitiveDependencies[dep].push ver
+            else
+              transitiveDependencies[dep] = [ver]
       offline = false
       for e in r
         if e.pkgMeta.version is version
           offline = true
           break
+      log.d "Cache #{if offline then "found" else "not found"} for #{name}##{version}"
       deferred.resolve {name: name, version: version, cacheName: cacheName, offline: offline}
     deferred.promise

+  checkCacheForAllDependencies = ->
+    log.d "Check cache for all dependencies"
+    @.allCacheExists = true
+    that = @
+    deferred = Q.defer()
+
+    # all dependencies must have the exact version, including transitives.
+    # >,<,=,~,^ should not exist.
+    for dependency in dependencies
+      if dependency.version is "latest" or dependency.version.match /[><=~\^]/
+        log.d "Dependency #{dependency.name} requires online install"
+        deferred.resolve {offline: false}
+        return deferred.promise
+    log.d "Check cache status for top level #{dependencies.length} dependencies"
+    Q.all dependencies.map (dependency) ->
+      dep = mout.object.mixIn {}, dependency
+      dep.checkDependencies = true
+      checkCache dep
+    .then (results) ->
+      deferred2 = Q.defer()
+      log.d "Checked all top level #{results.length} dependencies cache"
+      for result in results
+        # result: {"state": "fulfilled", "values": {"name": "jquery"...
+        if result.state isnt "fulfilled"
+          log.d "Cache checking state for #{result.values.name}##{result.values.version} is not normal: #{result.state}"
+          deferred2.reject()
+          return deferred2.promise
+        log.d "Cache status for #{result.values.name}: #{if result.values.offline then "cached" else "not cached"}"
+        unless result.values.offline
+          log.d "Found dependency that requires online: #{if result.values.name? then result.values.name else "unknown"}"
+          that.allCacheExists = false
+      if that.allCacheExists
+        # Let's go to next step to check transitive dependencies' cache status
+        log.d "Top level dependencies are cached"
+        deferred2.resolve()
+      else
+        log.d "Some top level dependencies are not cached"
+        deferred2.reject()
+      deferred2.promise
+    .then ->
+      deferred3 = Q.defer()
+      nextTransitiveDeps = []
+      for dep in Object.keys transitiveDependencies
+        for ver in transitiveDependencies[dep]
+          if ver is "latest"
+            log.d "Found transitive dependency that requires online for resolving latest version: #{dep}"
+            deferred3.reject()
+            return deferred3.promise
+        for d in dependencies
+          if dep is d.name
+            found = true
+            for ver in transitiveDependencies[dep]
+              unless semver.satisfies d.version, ver
+                log.d "Found transitive dependency that does not satisfy semver: #{dep}"
+                deferred3.reject()
+                return deferred3.promise
+        unless found
+          # Should check cache again, but currently we give up here.
+          # We now assume that we must install online.
+          log.d "Found transitive dependency that is not specified on bower.dependencies, which will require online installation"
+          deferred3.reject()
+          return deferred3.promise
+      log.d "All dependencies are cached"
+      deferred3.resolve()
+      deferred3.promise
+    .catch (err) ->
+      log.d "Some processes are rejected"
+      that.allCacheExists = false
+    .done ->
+      log.d "Check cache for all dependencies: done: offline: #{that.allCacheExists}"
+      deferred.resolve {offline: that.allCacheExists}
+    deferred.promise
+
   install = (item) ->
-    isSerialInstall = item isnt undefined
+    isSerialInstall = item?.name?
     deferred = Q.defer()

+    # offline param might be passed even when it's parallel install
+    offline = item.offline
     if isSerialInstall
       name = item.name
       version = item.version
       cacheName = item.cacheName
-      offline = item.offline
       installedVersion = getInstalledVersion name
+      log.d "Serial install for #{name}##{version}"
       if installedVersion isnt ''
         # already installed
         if version isnt installedVersion
@@ -94,6 +193,7 @@
         return deferred.promise
       saveCurrentDependencyToJson name, version
     else
+      log.d "Parallel install: offline: #{offline}"
       saveBowerJson()

     cached = false
@@ -102,7 +202,9 @@
     installOptions = {}
     installConfigs = configs or {}
     installConfigs['offline'] = offline unless 'offline' in installConfigs
+    log.d "Install configs: #{JSON.stringify installConfigs}"
     installOptions[mout.string.camelCase k] = options[k] for k of options
+    log.d "Install options: #{JSON.stringify installOptions}"
     # If 1st arg is specified, they are marked as unresolvable, and resolutions have no effect.
     bower.commands.install [], installOptions, installConfigs
     .on 'log', (l) ->
@@ -152,7 +254,7 @@
       if isSerialInstall
         for key in Object.keys(installed)
           i = installed[key]
-          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}#{if offline then " (offline)" else ""}"
+          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}#{if installConfigs['offline'] then " (offline)" else ""}"
         hasDependencies = Object.keys(installed).length > 1
         if cached and validate and !hasDependencies
           # Installed with cache, but validation occurred
@@ -184,12 +286,13 @@
       else
         for key in Object.keys(installed)
           i = installed[key]
-          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}"
+          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}#{if installConfigs['offline'] then " (offline)" else ""}"
       deferred.resolve()
     deferred.promise

   installParallel = (dummy, cb) ->
-    Q.fcall install
+    Q.fcall checkCacheForAllDependencies
+    .then install
     .catch (error) ->
       log.e "Install failed: #{error.stack}" if error
       common.setExitCode 1


Unfold element's root node contents.

Unfold element's root node contents.

--- src/OVirtResponseHydrator.coffee
+++ src/OVirtResponseHydrator.coffee
@@ -109,6 +109,7 @@
 # + Detect whether element is a resource link (it has a resource href).
 # + Detect collection links.
 # - Detect resources.
+# + Unfold element's root node contents.
 #
 class OVirtResponseHydrator
   # Static properties


Write specs for collection links hydration.

--- src/OVirtResponseHydrator.coffee
+++ src/OVirtResponseHydrator.coffee
@@ -55,15 +55,46 @@
 #
 # ### Hydrate collections
 #
-# - Detect collection links.
-# - Detect search options.
-# - Instantiate collection objects.
-# - Detect special objects inks and put them into collection.
-# - Save results in collection property.
+# - Collection links
+#     - Detect whether specified node is a collection links.
+#     - Instantiate collection objects.
+#     - Save link to collection object in {#collections} property with `rel` as
+#       a key and `xpath` as a namespace.
+#     - Set node value to undefined.
+# - Search options
+#     - Detect search option.
+#     - Detect corresponding collection `rel`.
+#     - Save search options in {#searchOptions} with `rel` base as a key and
+#       `xpath` as a namespace.
+#     - Set node to undefined.
+# - Special objects
+#     - Detect special object.
+#     - Detect special object related collection `rel`.
+#     - Save a link to special object in {#specialObjects} with `rel` base as a
+#       key and `xpath` as a namespace.
+#     - Set node to undefined.
+# - Setup collections
+#     - Detect that a set of the links are added to current node.
+#     - Resolve collections namespace adding '/link' to current xpath.
+#     - Loop over {#searchOptions} of the namespace and setup corresponding
+#       collections.
+#     - Clean the applied namespace of the {#searchOptions search options}.
+#     - Loop over {#specialObjects} of the namespace and add links to special
+#       objects to corresponding collections.
+#     - Clean the applied namespace of the {#specialObjects special objects}
+# - Export collections (right after collections setup)
+#     - If `link` is array then remove undefined values from it.
+#     - Delete link if it is an epty array or is undefined.
+#     - Loop over current {#collections} namespace
+#     - Resolve collection name from `rel` key
+#     - If current node isn't a root one then save link to collection object in
+#       current node hash with collection name as a key.
+#     - Otherwise export collection to target node.
+#     - Clean current namespace of the {#collections} property.
 #
 # ### Hydrate resource links
 #
-# - Detect links to resources
+# - Detect link to resource.
 # - Instantiate resource objects in link mode.
 # - Save results in resource links property.
 #


Fixed check for missing article.

Fixed check for missing article.

--- src/scripts/views/article-full.coffee
+++ src/scripts/views/article-full.coffee
@@ -9,9 +9,9 @@
 { div, h1, header, p, b } = Elements

 module.exports = (props) ->
-  if !props.article
+  lang = props.lang
+  if !props.article || !props.article.title[lang].text
     return div()
-  lang = props.lang
   article = formatSelector props.article, lang
   { title, headline, summary, intro, body } = article
   pageTitle = props.title || title


Crappy optimization. This will get re-written soon.

--- src/Rickshaw.Metamorph.js.coffee
+++ src/Rickshaw.Metamorph.js.coffee
@@ -43,7 +43,9 @@
     else
       throw new Error "\"#{position}\" is not a valid metamorph inject position."

-    this.startMarkerElement().store( "rickshaw-controller", @controller )
+    unless @storedOnStartMarker
+      this.startMarkerElement().store( "rickshaw-metamorph", this )
+      @storedOnStartMarker = true
     return this

   _injectAfter: (element) ->
@@ -65,7 +67,9 @@
   # Set this Metamorph's inner HTML content.
   setHTML: (html) ->
     @_morph.html( html )
-    this.startMarkerElement().store( "rickshaw-controller", @controller )
+    unless @storedOnStartMarker
+      this.startMarkerElement().store( "rickshaw-metamorph", this )
+      @storedOnStartMarker = true

   # Metamorph markers
   # -----------------


activity: reformat and simplify `CommentListViewController` class definition

--- client/Social/Activity/views/comments/commentlistviewcontroller.coffee
+++ client/Social/Activity/views/comments/commentlistviewcontroller.coffee
@@ -2,33 +2,18 @@

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

+    options.viewOptions =
+      type              : "comments"
+      itemClass         : CommentListItemView
+      itemOptions       :
+        delegate        : this
+        activity        : data
+
     super options, data

-    @_hasBackgrounActivity = no
-    @startListeners()
+    {delegate} = options

-
-  loadView: (mainView) ->
-
-    { scrollView
-      startWithLazyLoader
-      noItemFoundWidget
-    } = @getOptions()
-
-    {windowController} = KD.singletons
-
-    if scrollView
-      mainView.addSubView @customScrollView or @scrollView
-      @scrollView.addSubView @getListView()
-      @showLazyLoader no  if startWithLazyLoader
-
-      @scrollView.on 'LazyLoadThresholdReached', @bound "showLazyLoader"
-
-    @putNoItemView()  if noItemFoundWidget
-
-    @instantiateListItems @getListView().getData()?.replies or []
-
-    windowController.on "ReceivedMouseUpElsewhere", @bound 'mouseUpHappened'
+    @forwardEvent delegate, "Mention"

   instantiateListItems: (items) ->
@@ -36,101 +21,21 @@
     super items.sort (a, b) -> a.meta.createdAt - b.meta.createdAt

-  startListeners: ->
+  loadView: (mainView) ->

-    listView = @getListView()
+    {scrollView, noItemFoundWidget} = @getOptions()

-    listView.on 'ItemWasAdded', (view, index) ->
+    @setScrollView mainView  if scrollView
+    @putNoItemView()  if noItemFoundWidget

-      view.on 'CommentIsDeleted', ->
-        listView.emit "CommentIsDeleted"
-
-    listView.on "AllCommentsLinkWasClicked", (commentHeader) =>
-
-      return if @_hasBackgrounActivity
-
-      # some problems when logged out server doesnt responds
-      @utils.wait 5000, -> listView.emit "BackgroundActivityFinished"
-
-      {meta} = listView.getData()
-
-      listView.emit "BackgroundActivityStarted"
-      @_hasBackgrounActivity = yes
-      @_removedBefore = no
-      @fetchRelativeComments 10, meta.createdAt
+    {replies} = @getData()
+    @instantiateListItems replies  if replies.length

-  fetchCommentsByRange: (from, to, callback) ->
+  setScrollView: (mainView) ->

-    [to, callback] = [callback, to] unless callback
+    mainView.addSubView @customScrollView or @scrollView
+    @scrollView.addSubView @getListView()
+    @showLazyLoader no  if @getOption "startWithLazyLoader"

-    query   = {from, to}
-    message = @getListView().getData()
-
-    message.commentsByRange query, (err, comments) =>
-
-      @getListView().emit "BackgroundActivityFinished"
-      callback err, comments
-
-
-  fetchAllComments: (skipCount = 3, callback = noop) ->
-
-    listView = @getListView()
-    listView.emit "BackgroundActivityStarted"
-
-    message = @getListView().getData()
-    message.restComments skipCount, (err, comments) ->
-      listView.emit "BackgroundActivityFinished"
-      listView.emit "AllCommentsWereAdded"
-      callback err, comments
-
-
-  fetchRelativeComments: (limit = 10, after, continuous = yes, sort = 1) ->
-
-    listView = @getListView()
-    message  = listView.getData()
-    message.fetchRelativeComments {limit, after, sort}, (err, comments) =>
-
-      if not @_removedBefore
-        @removeAllItems()
-        @_removedBefore = yes
-
-      @instantiateListItems comments[limit - 10...], yes
-
-      if comments.length is limit
-        startTime = comments[comments.length - 1].createdAt
-        @fetchRelativeComments ++limit, startTime, continuous, sort  if continuous
-      else
-        listView = @getListView()
-        listView.emit "BackgroundActivityFinished"
-        listView.emit "AllCommentsWereAdded"
-        @_hasBackgrounActivity = no
-
-
-  replaceAllComments: (comments) ->
-
-    @removeAllItems()
-    @instantiateListItems comments
-
-
-  reply: (body, callback = noop) ->
-
-    listView = @getListView()
-    activity = listView.getData()
-
-    listView.emit "BackgroundActivityStarted"
-
-    KD.singleton("appManager").tell "Activity", "reply", {activity, body}, (err, reply) =>
-
-      return KD.showError err  if err
-
-      if not KD.getSingleton('activityController').flags?.liveUpdates
-        listView.addItem reply
-        listView.emit "OwnCommentHasArrived"
-      else
-        listView.emit "OwnCommentWasSubmitted"
-      listView.emit "BackgroundActivityFinished"
-
-    KD.mixpanel "Comment activity, success"
-    KD.getSingleton("badgeController").checkBadge
-      property: "comments", relType: "commenter", source: "JNewStatusUpdate", targetSelf: 1
+    @scrollView.on 'LazyLoadThresholdReached', @bound "showLazyLoader"


Free plan warning update

Free plan warning update

--- client/Main/providers/machinestatemodal.coffee
+++ client/Main/providers/machinestatemodal.coffee
@@ -288,7 +288,7 @@
         if plan is "free"
           message = "
             Your VM was automatically turned off after 60 minutes
-            of inactivity as you are in Free plan."
+            of inactivity as you are on the free plan."
           upgradeMessage = """

               Upgrade to make your VMs always-on.


ProfileView: skill tags ux changed

ProfileView: skill tags ux changed

--- client/app/CommonViews/SkillTagFormView.coffee
+++ client/app/CommonViews/SkillTagFormView.coffee
@@ -6,36 +6,29 @@
     @memberData = data
     @memberData.skillTags or= []

-    @setListeners()
+  showForm: ->
+    return if @hasClass 'active'
+    @setClass 'active'
+    @focusFirstElement()

-  showForm: ->
-    @windowController = KD.getSingleton 'windowController'
-    @windowController.addLayer @
-    unless @hasClass 'active'
-      @setClass 'active'
-      @focusFirstElement()
-
-  setListeners: ->
-    @on 'ReceivedClickElsewhere', => @unsetClass 'active'

   viewAppended: ->
+
     super
+
+    @parent.on "EditingModeToggled", (state)=>
+      if state then @showForm() else @unsetClass 'active'
+
     @addSubView tagWrapper = new KDCustomHTMLView
       tagName     : "div"
       cssClass    : "form-actions-holder clearfix"
-      bind        : "mouseenter mouseleave"
-      mouseenter  : =>
-        unless @hasClass "active"
-          @label.updatePartial "Click to edit..."
-          @tip.hide()
-      mouseleave  : =>
-        @label.updatePartial "SKILLS"
-        @tip.show() if @memberData.skillTags.length is 0

     tagWrapper.addSubView @label = new KDLabelView
       cssClass    : "skilltagslabel"
       title       : "SKILLS"
-      click       : @bound 'showForm'
+      for         : "skillTagsInput"
+      click       : =>
+        @parent.setEditingMode on

     tagWrapper.addSubView @tip = new KDCustomHTMLView
       tagName     : "span"
@@ -56,6 +49,9 @@
       outputWrapper       : tagWrapper
       selectedItemsLimit  : 10
       form                : this
+      view                : new KDAutoComplete
+        placeholder       : 'Add a skill...'
+        name              : 'skillTagsInput'
       dataSource          : ({inputValue}, callback) =>
         blacklist = (data.getId() for data in @tagController.getSelectedItemData() when 'function' is typeof data.getId)
         @emit "AutoCompleteNeedsTagData", {inputValue,blacklist,callback}


The most important commit to get Apps running.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -287,6 +287,7 @@
           try
             # security please!
             do (appView)->
+              appScript = "var appView = KD.instances[\"#{appView.getId()}\"];\n\n"+appScript
               eval appScript
           catch e
             warn "App caused some problems:", e


Cleanup AppStorage for KodingApps even after failed fetchFromFS

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -91,6 +91,7 @@
     # require ["coffee-script"], (coffee)=>
     @kiteController.run "ls #{escapeFilePath path} -lpva", (err, response)=>
       if err
+        @putAppsToAppStorage {}
         warn err
         callback err
       else
@@ -162,8 +163,8 @@
     @constructor.manifests = {}
     KDApps = {}
     @fetchAppsFromFs (err, apps)=>
+      @emit "AppsRefreshed", apps
       if not err
-        @emit "AppsRefreshed", apps
         callback? err, apps
       else
         callback err


Sidebar: Fix broken "if" condition

Sidebar: Fix broken "if" condition

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -122,14 +122,11 @@

   renderStacks: ->

-    if @state.stacks.size and not @state.showNoStacksWidget
-
+    if @state.stacks.size
       <SidebarStackHeaderSection>
         {@divideStacks()}
       </SidebarStackHeaderSection>
-
-    else
-
+    else if @state.showNoStacksWidget
       <SidebarNoStacks />


Environment: Actions: deleteStack: quit from IDE just before destroy

Since we do have stack destroy flow in the backend which needs to be silenced for the client side, we are at this point quit from IDE

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -636,7 +636,7 @@

 deleteStack = ({ stackTemplateId, stack }) ->

-  { computeController, reactor } = kd.singletons
+  { computeController, appManager, router } = kd.singletons

   _stack = remote.revive stack.toJS()  if stack

@@ -648,11 +648,14 @@
   computeController.ui.askFor 'deleteStack', {}, (status) ->
     return  unless status.confirmed

-    computeController.destroyStack _stack, (err) ->
-      return  if showError err
-
-      kd.singletons.appManager.quitByName 'IDE', ->
-        kd.singletons.router.handleRoute '/IDE'
+    appManager.quitByName 'IDE', ->
+      computeController.destroyStack _stack, (err) ->
+        return  if showError err
+
+        computeController
+          .reset yes
+          .once 'RenderStacks', ->
+            router.handleRoute '/IDE'

 changeTemplateTitle = (id, value) ->


environment/flux: add unshare with all action

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -732,6 +732,21 @@
       .then -> loadMachineSharedUsers machineId
       .catch (err) ->
         showError err  unless err.message is 'user is not in the shared list.'
+
+
+unshareMachineWihAllUsers = (machineId) ->
+
+  machine = _kd.singletons.reactor.evaluate ['MachinesStore', machineId]
+
+  new Promise (resolve, reject) ->
+    queue = machine.get('sharedUsers').toJS().map (user) -> (next) ->
+      { nickname } = user.profile
+      unshareMachineWithUser(machineId, nickname)
+        .then -> next(null)
+        .catch (err) -> next(err)
+
+    async.series queue, (err) -> if err then reject() else resolve()
+

 module.exports = {
@@ -775,5 +790,6 @@
   loadMachineSharedUsers
   shareMachineWithUser
   unshareMachineWithUser
+  unshareMachineWihAllUsers
   disconnectMachine
 }


Adds ?

Adds ?

--- client/apps/edit/components/admin/index.coffee
+++ client/apps/edit/components/admin/index.coffee
@@ -167,7 +167,7 @@
         @article.save contributing_authors: _.pluck items, 'id'
       removed: (e, item, items) =>
         @article.save contributing_authors: _.without(_.pluck(items, 'id'),item.id)
-    if ids = @article.get('contributing_authors').length
+    if ids = @article.get('contributing_authors')?.length
       @authors = []
       async.each ids, (id, cb) =>
         request


Temporarily disable open recent files.

Temporarily disable open recent files.

--- client/ide/workspace/panes/settingspane.coffee
+++ client/ide/workspace/panes/settingspane.coffee
@@ -108,7 +108,6 @@

Font        {{> @fontSize}}

Theme       {{> @theme}}

Tab size    {{> @tabSize}}

-
Open Recent Files               {{> @openRecentFiles}}

     """

@@ -150,5 +149,6 @@

     # TODO: reimplement these settings
     #
Highlight selected word {{> @highlightWord}}

-    #
                      {{> @shortcuts}}

-    #
Syntax           {{> @syntax}}

+    #
             {{> @shortcuts}}

+    #
Syntax  {{> @syntax}}

+    #
Open Recent Files           {{> @openRecentFiles}}


Show guests correctly in LikeView tooltips

--- client/landing/app/CommonViews/LikeView.coffee
+++ client/landing/app/CommonViews/LikeView.coffee
@@ -55,24 +55,33 @@
       sort  : timestamp : -1
     , (err, likes) =>

-      peopleWhoLiked   = []
+      peopleWhoLiked = []
+      guestsWhoLiked = 0

       if likes

-        likes.forEach (item)=>
-          if peopleWhoLiked.length < 3
+        for item in likes
+          if item.type is 'unregistered'
+            guestsWhoLiked++
+          else
             {firstName, lastName} = item.profile
             peopleWhoLiked.push "" + firstName + " " + lastName + ""
-          else return

         sep = ', '
+
+        guestLikes =
+          switch guestsWhoLiked
+            when 0 then ""
+            when 1 then "and a guest"
+            when 2 then "and 2 guests"
+            when 3 then "3 guests"
+
         tooltip =
-          switch data.meta.likes
-            when 0 then ""
-            when 1 then "#{peopleWhoLiked[0]}"
-            when 2 then "#{peopleWhoLiked[0]} and #{peopleWhoLiked[1]}"
-            when 3 then "#{peopleWhoLiked[0]}#{sep}#{peopleWhoLiked[1]} and #{peopleWhoLiked[2]}"
-            else "#{peopleWhoLiked[0]}#{sep}#{peopleWhoLiked[1]}#{sep}#{peopleWhoLiked[2]} and #{data.meta.likes - 3} more.</stron>"
+          switch peopleWhoLiked.length
+            when 0 then "#{guestLikes}"
+            when 1 then "#{peopleWhoLiked[0]} #{guestLikes}"
+            when 2 then "#{peopleWhoLiked[0]} and #{peopleWhoLiked[1]} #{guestLikes}"
+            else "#{peopleWhoLiked[0]}#{sep}#{peopleWhoLiked[1]}#{sep}#{peopleWhoLiked[2]} and #{data.meta.likes - 3} more."

         @likeCount.getTooltip().update { title: tooltip }
         @_lastUpdatedCount = likes.length


Start codo inline documentation

Start codo inline documentation

--- app/models/Entity.coffee
+++ app/models/Entity.coffee
@@ -1,12 +1,23 @@
 Model = require 'models/base/model'
 mediator = require 'mediator'

-module.exports = class Entity extends Model
+# Base class for all entities
+#
+# @example How to subclass Entity
+#   module.exports = class VisibleEntity extends Entity
+#     onAction: (object): ->
+#
+
+module.exports = class Entity extends
+  # The entity's position
   position:
     x: 0
     y: 0

+  # decides what sprite to draw
   animationStep: 0
+  # what direction the entity looks
+  # can be of of 0,1,2,3
   viewDirection: 0


:wrench: Added styled-jsx as tagged template syntax for language-babel syntax

--- config/atom/config.cson
+++ config/atom/config.cson
@@ -88,6 +88,11 @@
   jumpy:
     fontSize: 0.95
     matchPattern: "([A-EG-Z]+([0-9a-eg-z])*)|[a-egz0-9]{2,}"
+  "language-babel":
+    autoIndentJSX: true
+    taggedTemplateGrammar: [
+      "\"(?<=<style jsx>{)|(?<=<style jsx global>{)\":source.css.styled"
+    ]
   "linter-eslint":
     fixOnSave: true
     lintHtmlFiles: true


Fixed syntax error in Xbox redeemer

Fixed syntax error in Xbox redeemer

--- bots/Xbox.coffee
+++ bots/Xbox.coffee
@@ -40,5 +40,5 @@
     )

 casper.run ->
-  @echo 'Finished redeeming code#{if ARGS[2].length == 1 then '' else 's'}.'
+  @echo "Finished redeeming code#{if ARGS[2].length == 1 then '' else 's'}."
   @exit 0


updated review

updated review

--- app/models/review.coffee
+++ app/models/review.coffee
@@ -15,10 +15,10 @@
     },
     {
       id: '2'
-      user: 'Eamon Mitchell'
+      user: 'Marissa Tomei'
       toilet: 11
       createdAt: new Date(2014, 4, 21)
-      text: '0403 569 031 for gdood time'
+      text: 'Scent of jasmine throughout while I was doing a number two. Would go again.'
     }
   ]


environment creation error handling

environment creation error handling

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -700,6 +700,8 @@

   removeOverlay:()->

+    return unless @$overlay
+
     @emit "OverlayWillBeRemoved"
     kallback = =>
       @$overlay.off "click.overlay"


Cleanup

Cleanup

--- test/default-network.coffee
+++ test/default-network.coffee
@@ -9,8 +9,6 @@
     before (done) ->
       defaultNetwork.collect (error, result) ->
         data = result
-        console.log data
-        console.log os.networkInterfaces()[Object.keys(data)[0]]
         done(error)
     it 'is an data object', ->
       expect(data).to.be.an.any('object')


Add custom tag names

Add custom tag names

--- modules/gui/tools/ck.coffee
+++ modules/gui/tools/ck.coffee
@@ -30,12 +30,15 @@

 TEMPLATES = {}

-module.exports = (code, cached=true)->
+module.exports = (code, config=true)->
+    if typeof cached is 'boolean'
+        config =
+            cached: config || config isnt false

     if typeof code is 'function'
         code = '('+code.toString()+').call(this);'

-    if !cached || !TEMPLATES[hash = floyd.tools.strings.hash code]
+    if !config.cached || !TEMPLATES[hash = floyd.tools.strings.hash code]

         ##
         html    = null
@@ -103,6 +106,14 @@
                 html += "<![endif]-->"
                 return

+        if config.tags
+            for tag in config.tags
+                compileTag tag, false # don't self close
+
+        if config.tagsSelfClosing
+            for tag in tagsSelfClosing
+                compileTag tag, true # self close
+
         for tag in tagsNormal
             compileTag tag, false # don't self close

@@ -138,7 +149,7 @@

         ##

-        if cached
+        if config.cached
             TEMPLATES[hash] = template

         return template


Fix Coderwall endpoint URL, change from http to https

--- src/scripts/coderwall.coffee
+++ src/scripts/coderwall.coffee
@@ -16,18 +16,18 @@
 module.exports = (robot) ->
   robot.respond /(coderwall)( me)? (.*)/i, (msg) ->
     user = msg.match[3]
-    msg.http("http://coderwall.com/"+user+".json")
+    msg.http("https://coderwall.com/"+user+".json")
       .get() (err, res, body) ->
         # If not response bad username
         if res.headers['content-length'] <= 1
           letter_s = if user.substr(-1)=='s' then '' else 's'
-          msg.send "Sorry I cannot find "+user+"'"+letter_s+" coderwall"
+          msg.send "Sorry I cannot find "+user+"'"+letter_s+" coderwall"
         # Else return the coder badges
-        else
+        else
           profile = JSON.parse(body)
           # Give an intro to the coderwall profile
           resp_str = profile.name + " from " + profile.location + " \n"
-          resp_str += "coderwall -> http://coderwall.com/"+user + "\n"
+          resp_str += "coderwall -> http://coderwall.com/"+user + "\n"
           resp_str += "github -> https://github.com/" + profile.accounts.github + "\n"
           resp_str += profile.username + " has " + profile.endorsements + " endorsements and " + profile.badges.length + " badges\n"
           # Iterate all badges and continue building string


Set 'User-Agent' header on translate calls

This is required for certain language translations

--- src/scripts/translate.coffee
+++ src/scripts/translate.coffee
@@ -83,6 +83,7 @@
         uptl: "en"
         text: term
       })
+      .header('User-Agent', 'Mozilla/5.0')
       .get() (err, res, body) ->
         data   = body
         if data.length > 4 && data[0] == '['


adds element check for error when starting with maximized window

--- src/ui/views/convhead.coffee
+++ src/ui/views/convhead.coffee
@@ -73,6 +73,7 @@
         toggleHidden document.getElementById('win-restore'), true

     toggleHidden = (element, hidden) ->
+        return unless element
         if hidden
             element.style.display = 'none'
         else


allow QrScan to recognize bitcoin address without prefix


webapp: improve error message display

webapp: improve error message display

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1724,7 +1724,7 @@
             path       : @filename
             cb         : (err, result) =>
                 if err
-                    alert_message(type:"error", message: "Error connecting to console server -- #{err}")
+                    alert_message(type:"error", message: "Error connecting to console server -- #{misc.to_safe_str(err)}")
                 else
                     # New session or connect to session
                     if result.content? and result.content.length < 36


feat: accounts comparison improvement

feat: accounts comparison improvement

- accounts-list now selects saved accounts more intellegently, depending on comparison mode state & type of uids given

--- src/components/widgets-settings/accounts-list/accounts-list.directive.coffee
+++ src/components/widgets-settings/accounts-list/accounts-list.directive.coffee
@@ -33,20 +33,36 @@

     if w.content? && !_.isEmpty(w.content.complete_list)
       w.remainingAccounts = angular.copy(w.content.complete_list)
-      # Impac! returns the list of all the accounts, and we want that:
-      # completeList + savedList = list of all accounts
+      # pre-fills saved accounts (or saved grouped accounts) into w.selectedAccounts
       if !_.isEmpty(w.metadata.accounts_list)
-        angular.forEach(w.metadata.accounts_list, (accUid) ->
+        for accUid in savedAccounts()
           acc = _.find(w.content.complete_list, (acc) ->
             acc.uid == accUid
           )
-          w.moveAccountToAnotherList(acc,w.remainingAccounts,w.selectedAccounts,false)
-        )
-      stashedAccounts = angular.copy(w.remainingAccounts)
+          w.moveAccountToAnotherList(acc, w.remainingAccounts, w.selectedAccounts, false)
       setting.isInitialized = true

   setting.toMetadata = ->
     return { accounts_list: _.map(w.selectedAccounts, ((acc) -> acc.uid)) } if setting.isInitialized
+
+  # Returns uids of saved accounts or groups, also converting saved groups into
+  # accounts or accounts into groups when switching between comparison mode.
+  savedAccounts = () ->
+    isComparisonMode = _.result( _.find(w.metadata.comparison_mode, 'id', 'compare_accounts'), 'value') || false
+    # Decontruct group uids into account uids
+    if !isComparisonMode && w.metadata.accounts_list[0].indexOf(':') >= 0
+      _.flatten _.map(w.metadata.accounts_list, (a) -> a.split(':'))
+    # Find first group uid matchable by account uids.
+    else if isComparisonMode
+      if w.metadata.accounts_list[0].indexOf(':') < 0
+        for savedAccount in w.metadata.accounts_list
+          account = _.find(w.remainingAccounts, (acc) -> acc.uid.indexOf(savedAccount) >= 0)
+          return [account.uid] if account
+      else
+        _.flatten _.map(w.metadata.accounts_list, (a) -> a.split(':'))
+    # Saved accounts
+    else
+      w.metadata.accounts_list

   w.settings.push(setting)


Added .toString() for parsing strings in weaker environments

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -78,7 +78,7 @@
 ###
 String::parse = () ->
   try
-    JSON.parse(this)
+    JSON.parse(this.toString())
   catch e
     this.toString()


Make interactive test running work with ‘test’ directory

In addition to ‘spec’. I hate the word ‘spec’.

--- src/workspace-element.coffee
+++ src/workspace-element.coffee
@@ -1,5 +1,6 @@
 {ipcRenderer} = require 'electron'
 path = require 'path'
+fs = require 'fs-plus'
 {Disposable, CompositeDisposable} = require 'event-kit'
 Grim = require 'grim'
 scrollbarStyle = require 'scrollbar-style'
@@ -122,6 +123,12 @@
       [projectPath] = @project.relativizePath(activePath)
     else
       [projectPath] = @project.getPaths()
-    ipcRenderer.send('run-package-specs', path.join(projectPath, 'spec')) if projectPath
+    if projectPath
+      specPath = path.join(projectPath, 'spec')
+      testPath = path.join(projectPath, 'test')
+      if not fs.existsSync(specPath) and fs.existsSync(testPath)
+        specPath = testPath
+
+      ipcRenderer.send('run-package-specs', specPath)

 module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype


added padding to stacked y axis

added padding to stacked y axis

--- src/viz/types/stacked.coffee
+++ src/viz/types/stacked.coffee
@@ -9,6 +9,10 @@
 stacked = (vars) ->

   graph vars
+
+  # Add padding to the top of the Y axis
+  domain = vars.y.scale.viz.domain()
+  vars.y.scale.viz.domain [domain[0]*1.025,domain[1]]

   data = nest vars


99○ should be a DOMAIN ERROR, according to the ISO standard.

--- src/vocabulary/circle.coffee
+++ src/vocabulary/circle.coffee
@@ -19,6 +19,8 @@
   # 1e¯10>∣.5-1○○÷6 <=> 1 # sin(pi/6) = .5
   #  9○3j4         <=> 3
   # 11○3j4         <=> 4
+  # 99○1           !!! DOMAIN ERROR
+  # 99○1j2         !!! DOMAIN ERROR
   dyad: (x, i) ->
     if typeof x is 'number'
       switch i
@@ -37,11 +39,11 @@
         when  5 then (Math.exp(2 * x) - 1) / 2 # sinh
         when  6 then (Math.exp(2 * x) + 1) / 2 # cosh
         when  7 then ex = Math.exp(2 * x); (ex - 1) / (ex + 1) # tanh
-        else throw Error 'Unknown circular or hyperbolic function ' + i
+        else throw DomainError 'Unknown circular or hyperbolic function ' + i
     else if x instanceof Complex
       switch i
         when  9 then x.re
         when 11 then x.im
-        else throw Error 'Unknown circular or hyperbolic function ' + i
+        else throw DomainError 'Unknown circular or hyperbolic function ' + i
     else
       throw DomainError()


Don't need to do this twice. Done in finishTransiton()

--- js/flight.coffee
+++ js/flight.coffee
@@ -95,7 +95,6 @@
       window.setTimeout =>
         @currentPanel.style.webkitTransition = "#{@speed} ease"
         @currentPanel.style.webkitTransform = "translateX(200%)"
-        @targetPanel.className += " visible"
       , 10

     else


new integration test

new integration test

--- test/integrationSpec.coffee
+++ test/integrationSpec.coffee
@@ -73,7 +73,7 @@
     #   'when collide with context methods names'
     #   'multiple describes (inner and parallel)'

-    it 'dependencies in one describe', ->
+    it 'dependencies in one describe part 1', ->
       context.describe 'test', ->
         collection.is 'something else'
         another.is collection + ' another'
@@ -83,6 +83,19 @@
           expect(another).toBeEqual 'something else another'

       JE.run(context)
+
+    it 'dependencies in one describe part 2', ->
+      context.describe 'test', ->
+        another   .is collection + ' another'
+        collection.is 'something else'
+
+        @it 'two', ->
+          expect(collection).toBeEqual 'something else'
+          expect(another).toBeEqual 'something else another'
+
+      JE.run(context)
+
+

 ## Planning DSL example:


stray log

stray log

--- test/lib/metaphysics.coffee
+++ test/lib/metaphysics.coffee
@@ -110,7 +110,6 @@
           }
         '
       .catch (err) ->
-        console.log err.data
         err.data.foo.should.equal 'bar'

   describe 'partial error', ->


Add Select Inside Brackets to Selection menu

--- menus/bracket-matcher.cson
+++ menus/bracket-matcher.cson
@@ -7,9 +7,15 @@
         { 'label': 'Go To Matching Bracket', 'command': 'bracket-matcher:go-to-matching-bracket' }
         { 'label': 'Select Inside Brackets', 'command': 'bracket-matcher:select-inside-brackets' }
         { 'label': 'Remove Brackets From Selection', 'command': 'bracket-matcher:remove-brackets-from-selection' }
-        { 'label': 'Close Current Tag', 'command': 'bracket-matcher:close-tag'}
-        { 'label': 'Remove Matching Brackets', 'command': 'bracket-matcher:remove-matching-brackets'}
+        { 'label': 'Close Current Tag', 'command': 'bracket-matcher:close-tag' }
+        { 'label': 'Remove Matching Brackets', 'command': 'bracket-matcher:remove-matching-brackets' }
       ]
+    ]
+  },
+  {
+    'label': 'Selection'
+    'submenu': [
+        { 'label': 'Select Inside Brackets', 'command': 'bracket-matcher:select-inside-brackets' }
     ]
   }
 ]


Set global class

Set global class

--- flow.coffee
+++ flow.coffee
@@ -1,4 +1,4 @@
-class Flow
+class window.Flow
   defaultOptions:
     slidesSelector: '> *'


Changing quantity of existing products

Changing quantity of existing products

--- app/assets/javascripts/admin/suppliers_code.js.coffee
+++ app/assets/javascripts/admin/suppliers_code.js.coffee
@@ -3,11 +3,15 @@
   new_row_template = Handlebars.compile($('#suppliers-new-product-row').html())
   input_field = $("<input type='text' value='' />")

-  $('table.index td.quantity').on 'click', ->
-    if ($(this).children('input').size() == 0)
-      field = input_field.clone()
-      field.val($(this).html())
-      $(this).html field
+  $(document)
+    .on 'click', 'table.index td.quantity', ->
+      if ($(this).children('input').size() == 0)
+        field = input_field.clone()
+        field.val($(this).html())
+        $(this).html field
+        $(this).find('input').focus()
+    .on 'blur', 'table.index td.quantity input', (el) ->
+      $(this).parent().html $(this).val()

   $(".variant_autocomplete").select2
     placeholder: "Select a variant"


map search widget: auto reload after hiding sidebar

--- app/assets/javascripts/application/widget_edit.coffee
+++ app/assets/javascripts/application/widget_edit.coffee
@@ -2,4 +2,7 @@
   console.log('initializer')
   oz = new OZ('oz_map_search')
   oz.api 'on_params_changed', (params) ->
-    $('#widget_map_search_api_params').val(JSON.stringify(params))+    $('#widget_map_search_api_params').val(JSON.stringify(params))
+
+  this.on 'change', 'input[type="checkbox"]', ->
+    $(this).parents('form:first').submit()


[Cartilage.View] Detect and log template load failures.

--- app/assets/javascripts/cartilage/views/view.js.coffee
+++ app/assets/javascripts/cartilage/views/view.js.coffee
@@ -9,7 +9,10 @@
   observers: []

   template: (options) ->
-    JST[_.underscore(@constructor.name)](options)
+    try
+      JST[_.underscore(@constructor.name)](options)
+    catch error
+      console.log "No template!"

   render: ->


cleanup

cleanup

--- client/app/Applications/Groups.kdapplication/Views/groupsinvitationrequestsview.coffee
+++ client/app/Applications/Groups.kdapplication/Views/groupsinvitationrequestsview.coffee
@@ -78,7 +78,6 @@
     (@["#{controllerName}Controller"] for controllerName in controllerNames)

   refresh:->
-    @requestListController.removeAllItems()

     @fetchSomeRequests @invitationTypeFilter, @statusFilter, (err, requests)=>
       if err then console.error err
@@ -99,7 +98,7 @@
         {pending, sent, resolved} = groupedRequests

         # clear out any items that may be there already:
-        @getControllers().forEach (controller)-> controller.removeAllItems()
+        @getControllers().forEach controller.bound 'removeAllItems'

         # populate the lists:
         @requestListController.instantiateListItems pending     if pending?


pullinMerge branch 'master' of github.com:zooniverse/scribeAPI

--- app/assets/javascripts/components/dynamic-router.cjsx
+++ app/assets/javascripts/components/dynamic-router.cjsx
@@ -16,9 +16,9 @@
   componentDidMount: ->
     $.getJSON '/project', (result) =>

-      @setState project:           result.project
-      @setState home_page_content: result.project.home_page_content
-      @setState pages:             result.project.pages
+      @setState project:           result
+      @setState home_page_content: result.home_page_content
+      @setState pages:             result.pages

       for workflow in @state.project.workflows
         @setState mark_tasks: workflow.tasks if workflow.key is 'mark'


When dragging node which is unselected, only that node should be dragged.

--- src/code/views/graph-view.coffee
+++ src/code/views/graph-view.coffee
@@ -140,10 +140,11 @@
     selectedNodes = @state.selectedNodes
     if (selectedNodes.length > 0)
       @handleEvent ->
-        for node in selectedNodes
-          GraphStore.store.moveNode node.key, leftDiff, topDiff
-        # when node is unselected, but we drag it, it should be treated as selected
-        (GraphStore.store.moveNode theNode.key, leftDiff, topDiff) unless theNode in selectedNodes
+        if theNode in selectedNodes
+          for node in selectedNodes
+            GraphStore.store.moveNode node.key, leftDiff, topDiff
+        else # when node is unselected, but we drag it, only it should be dragged
+          (GraphStore.store.moveNode theNode.key, leftDiff, topDiff)
     else
       # alert "leftDiff 2" + leftDiff
       @handleEvent ->


:art: removed non working parts

:art: removed non working parts

--- grammars/perl.cson
+++ grammars/perl.cson
@@ -800,35 +800,6 @@
       }
     ]
   }
-  # {
-  #   'begin': '(((<<) *"CSS"))(.*)\\n?'
-  #   'captures':
-  #     '1':
-  #       'name': 'punctuation.definition.string.perl'
-  #     '2':
-  #       'name': 'string.unquoted.heredoc.doublequote.perl'
-  #     '3':
-  #       'name': 'punctuation.definition.heredoc.perl'
-  #     '4':
-  #       'patterns': [
-  #         {
-  #           'include': '$self'
-  #         }
-  #       ]
-  #   'contentName': 'text.css.embedded.perl'
-  #   'end': '(^CSS$)'
-  #   'patterns': [
-  #     {
-  #       'include': '#escaped_char'
-  #     }
-  #     {
-  #       'include': '#variable'
-  #     }
-  #     {
-  #       'include': 'source.css'
-  #     }
-  #   ]
-  # }
   {
     'begin': '(((<<) *"JAVASCRIPT"))(.*)\\n?'
     'captures':
@@ -988,29 +959,6 @@
       }
     ]
   }
-  # {
-  #   'begin': '(((<<) *\'CSS\'))(.*)\\n?'
-  #   'captures':
-  #     '1':
-  #       'name': 'punctuation.definition.string.perl'
-  #     '2':
-  #       'name': 'string.unquoted.heredoc.quote.perl'
-  #     '3':
-  #       'name': 'punctuation.definition.heredoc.perl'
-  #     '4':
-  #       'patterns': [
-  #         {
-  #           'include': '$self'
-  #         }
-  #       ]
-  #   'contentName': 'text.css.embedded.perl'
-  #   'end': '(^CSS$)'
-  #   'patterns': [
-  #     {
-  #       'include': 'source.css'
-  #     }
-  #   ]
-  # }
   {
     'begin': '(((<<) *\'JAVASCRIPT\'))(.*)\\n?'
     'captures':


Use "#bold-first" rule for manpage synopsis titles

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -1165,15 +1165,7 @@
 				0: {name: "meta.function.end.synopsis.man.macro.gnu.roff"}
 				1: {name: "punctuation.definition.macro.gnu.roff"}
 				2: {name: "entity.function.name.gnu.roff"}
-			patterns: [{
-
-				# Display the command's name in bold
-				contentName: "markup.bold.roff"
-				begin: "\\G[ \t]*"
-				end:   "[ \t]*\\Z|(?=\\\\\")"
-				patterns: [include: "#escapes"]
-
-			},{
+			patterns: [include: "#bold-first", {

 				# .OP: Option description (GNU extension)
 				name: "meta.function.option-description.man.macro.gnu.roff"


Exclude terminator character from parametric-escape names

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -710,7 +710,7 @@

 			# "Parametric" escape sequences supported in Groff (GNU)
 			name: "constant.character.escape.function.check-identifier.gnu.roff"
-			begin: "((\\\\)[ABRsZ]((.)))"
+			begin: "((\\\\)[ABRsZ])((.))"
 			end:   "(\\4)|(?<!\\\\)(?=$)"
 			contentName: "string.other.roff"
 			patterns: [include: "#escapes"]


Update wording

Update wording

--- src/course/new-registration.cjsx
+++ src/course/new-registration.cjsx
@@ -58,12 +58,13 @@
   teacherMessage: ->

-        Welcome!  To see the student view,
+        Welcome!
+

+        To see the student view of your course in Concept Coach,
         enter an enrollment code from one of your sections.
-

-

-        You may want to create a test section to keep your
-        responses separate from your real students.
+

+        We suggest creating a test section for yourself so you can
+        separate your Concept Coach responses from those of your students.


fix random things with snippets

fix random things with snippets

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/top_hundred_snippets.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/top_hundred_snippets.coffee
@@ -1,32 +1,27 @@
 define -> [{
     "caption": "\\begin{}",
     "snippet": "\\begin{$1}",
-    "meta": "cmd",
+    "meta": "env",
     "score": 7.849662248028187
 }, {
     "caption": "\\begin{}[]",
     "snippet": "\\begin{$1}[$2]",
-    "meta": "cmd",
+    "meta": "env",
     "score": 7.849662248028187
 }, {
     "caption": "\\begin{}{}",
     "snippet": "\\begin{$1}{$2}",
-    "meta": "cmd",
+    "meta": "env",
     "score": 7.849662248028187
 }, {
     "caption": "\\end{}",
     "snippet": "\\end{$1}",
-    "meta": "cmd",
+    "meta": "env",
     "score": 7.847906405228455
-}, {
-    "caption": "\\usepackage{}",
-    "snippet": "\\usepackage{$1}",
-    "meta": "cmd",
-    "score": 5.427890758130527
 }, {
     "caption": "\\usepackage[]{}",
     "snippet": "\\usepackage[$1]{$2}",
-    "meta": "cmd",
+    "meta": "pkg",
     "score": 5.427890758130527
 }, {
     "caption": "\\item",


remove logging

remove logging

--- test/visualizer-test.coffee
+++ test/visualizer-test.coffee
@@ -24,7 +24,4 @@
     it 'should render', ->
       @visualizer.render()
       element = $('html')[0]
-      console.log element.style.background
       expect(element.style.background).toMatch(/rgb/)
-
-


Add mobile ready.

Add mobile ready.


Remove log

Remove log

--- SingularityUI/app/models/TaskHistory.coffee
+++ SingularityUI/app/models/TaskHistory.coffee
@@ -26,7 +26,6 @@
             taskUpdate.timestampHuman = utils.humanTimeAgo taskUpdate.timestamp

             if taskUpdate.statusUpdate in ['TASK_KILLED', 'TASK_FAILED', 'TASK_FINISHED']
-                log 'woot...', taskUpdate.statusUpdate
                 taskHistory.task.isStopped = true

         _.sortBy taskHistory.taskUpdates, (t) -> t.timestamp


[misc] test/unit: stub the correct redis settings

--- services/document-updater/test/unit/coffee/DispatchManager/DispatchManagerTests.coffee
+++ services/document-updater/test/unit/coffee/DispatchManager/DispatchManagerTests.coffee
@@ -13,7 +13,7 @@
 			"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }
 			"settings-sharelatex": @settings =
 				redis:
-					realtime: {}
+					documentupdater: {}
 			"redis-sharelatex": @redis = {}
 			"./RateLimitManager": {}
 			"./Errors": Errors


worldwide

worldwide

--- examples/reload-http-server/http-server.coffee
+++ examples/reload-http-server/http-server.coffee
@@ -1,6 +1,6 @@
 http = require 'http'

 server = http.createServer (req, res) ->
-  res.end "hello chi.js()\n"
+  res.end "hai\n"

 server.listen 2121, -> console.log "listening on localhost:2121"


修整下格式以及参数名称

修整下格式以及参数名称

--- core/model/Model.coffee
+++ core/model/Model.coffee
@@ -62,7 +62,6 @@
           throw err if err
           callback err,results

-
   update: (documents , opts = {w:1},callback = null) ->
     if _.isFunction opts
       callback = opts
@@ -73,8 +72,6 @@
       if callback
         results = @constructor.createModels doc
         callback err,results
-
-

   @find: (selector, opts = {}, callback = null) ->
     if _.isFunction selector


加入@update update

加入@update update

--- core/model/Model.coffee
+++ core/model/Model.coffee
@@ -6,10 +6,10 @@
 module.exports = class Model
   constructor: (@data) ->

-  @create : ->
+  @create: ->
     throw 'this function must be overrided'

-  @createModels : (docs)->
+  @createModels: (docs)->
     throw 'docs must be array' if not _.isArray docs
     results = []
     if docs.length is 1
@@ -18,21 +18,21 @@
       for doc in docs
         results.push @create doc
     results
-  @table : ->
+  @table: ->
     "#{@name.toLowerCase()}s"

   @collection: ->
     db.mongo.collection @table()

-  set : (key, value = null) ->
+  set: (key, value = null) ->
     if (_.isObject key) is 'object' then attrs = key else attrs[key] = value
     @data[k] = v for k, v of attrs
     return @

-  get : (attr) ->
+  get: (attr) ->
     @data[attr]

-  @insert : (data, callback = null) ->
+  @insert: (data, callback = null) ->
     @collection().insert data, {w:1}, (err, docs) =>
       throw err if err
       if callback
@@ -47,19 +47,43 @@
         else
           callback 'there is  more then 1 documents with the same id'

-  update : (newObj , opts = {},callback) ->
-
   remove: (callback = null)->
     @constructor.removeById @data._id,callback

-  @find : (data, opts = {}, callback = null) ->
-    if _.isFunction data
-      callback = data
-      data = {}
+  @update: (selector, documents,opts = {w: 1,multi: true},callback = null) ->
+    if _.isFunction opts
+      callback = opts
+      opts = {w: 1,multi: true}
+    throw 'arguments wrong' if not ((_.isObject selector) and (_.isObject documents))
+    @collection().update selector,documents,opts,(err,numberUpdated)=>
+      throw err if err
+      if callback
+        @find selector,(err,results)->
+          throw err if err
+          callback err,results
+
+
+  update: (documents , opts = {w:1},callback = null) ->
+    if _.isFunction opts
+      callback = opts
+      opts = {w: 1}
+    throw 'arguments wrong' if not _.isObject documents
+    @constructor.collection().update {_id: @data._id},documents,opts,(err,doc)=>
+      throw err if err
+      if callback
+        results = @constructor.createModels doc
+        callback err,results
+
+
+
+  @find: (selector, opts = {}, callback = null) ->
+    if _.isFunction selector
+      callback = selector
+      selector = {}
     else if _.isFunction opts
       callback = opts
       opts = {}
-    @collection().find(data, opts).toArray (err, docs)=>
+    @collection().find(selector, opts).toArray (err, docs)=>
       throw err if err
       if callback
         results = @createModels docs


17th Commit

17th Commit

--- protractor.conf.coffee
+++ protractor.conf.coffee
@@ -9,6 +9,8 @@
     SpecReporter = require 'jasmine-spec-reporter'
     jasmine.getEnv().addReporter new SpecReporter displayStacktrace: true
     browser.ignoreSynchronization = false
+    browser.driver.manage().window().maximize()
+    return browser.get('http://localhost:9876/')
   @framework: 'jasmine2'
   @jasmineNodeOpts:
     isVerbose: false


[client][app][activity][view] added block user menu

--- client/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
+++ client/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
@@ -115,11 +115,15 @@
         menu =
           'Mark User as Troll' :
             callback           : ->
-              mainController.markUserAsTroll data
+              mainController.blockUser data

       menu['Delete Post'] =
         callback : =>
           @confirmDeletePost data
+
+      menu['Block User'] =
+        callback : ->
+          mainController.openBlockUserModal data

       return menu


Add tests

Add tests

--- test/tests/ScrollComponentTest.coffee
+++ test/tests/ScrollComponentTest.coffee
@@ -34,55 +34,78 @@

 		copy = instance.copy()
 		copy.scrollHorizontal.should.be.false
-
-	describe "scolling with mousEvents", ->
-		it "should work", ->
-			scroll = new ScrollComponent size: 200
-			new Layer
-				width: 400
-				height: 400
-				parent: scroll.content
-			scroll.mouseWheelEnabled = true
-			scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
-			scroll.content.x.should.equal -75
-			scroll.content.y.should.equal -150
-
-		it "should respect scrollHorizontal = false", ->
-			scroll = new ScrollComponent size: 200
-			new Layer
-				width: 400
-				height: 400
-				parent: scroll.content
-			scroll.mouseWheelEnabled = true
-			scroll.scrollHorizontal = false
-			scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
-			scroll.content.x.should.equal 0
-			scroll.content.y.should.equal -150
-
-		it "should respect scrollVertial = false", ->
-			scroll = new ScrollComponent size: 200
-			new Layer
-				width: 400
-				height: 400
-				parent: scroll.content
-			scroll.mouseWheelEnabled = true
-			scroll.scrollVertical = false
-			scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
-			scroll.content.x.should.equal -75
-			scroll.content.y.should.equal 0
-
-		it "should fire move events", (done) ->
-			scroll = new ScrollComponent size: 200
-			new Layer
-				width: 400
-				height: 400
-				parent: scroll.content
-			scroll.mouseWheelEnabled = true
-			scroll.on Events.Move, (event) ->
-				event.x.should.equal -75
-				event.y.should.equal -150
-				done()
-			scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
+	describe "Events", ->
+		describe "scolling with mousEvents", ->
+			it "should work", ->
+				scroll = new ScrollComponent size: 200
+				new Layer
+					width: 400
+					height: 400
+					parent: scroll.content
+				scroll.mouseWheelEnabled = true
+				scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
+				scroll.content.x.should.equal -75
+				scroll.content.y.should.equal -150
+
+			it "should respect scrollHorizontal = false", ->
+				scroll = new ScrollComponent size: 200
+				new Layer
+					width: 400
+					height: 400
+					parent: scroll.content
+				scroll.mouseWheelEnabled = true
+				scroll.scrollHorizontal = false
+				scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
+				scroll.content.x.should.equal 0
+				scroll.content.y.should.equal -150
+
+			it "should respect scrollVertial = false", ->
+				scroll = new ScrollComponent size: 200
+				new Layer
+					width: 400
+					height: 400
+					parent: scroll.content
+				scroll.mouseWheelEnabled = true
+				scroll.scrollVertical = false
+				scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
+				scroll.content.x.should.equal -75
+				scroll.content.y.should.equal 0
+
+			it "should fire move events", (done) ->
+				scroll = new ScrollComponent size: 200
+				new Layer
+					width: 400
+					height: 400
+					parent: scroll.content
+				scroll.mouseWheelEnabled = true
+				scroll.on Events.Move, (event) ->
+					event.x.should.equal -75
+					event.y.should.equal -150
+					done()
+				scroll.emit(Events.MouseWheel, {wheelDeltaX: -75, wheelDeltaY: -150})
+
+		describe "PageComponent", ->
+			it "should fire scroll events", (done) ->
+				page = new PageComponent
+					width: 100
+					height: 100
+				page.addPage(new Layer)
+				page.addPage(new Layer)
+				page.once Events.Scroll, ->
+					done()
+				page.snapToNextPage()
+
+		it "should fire move events when moving a layer programmatically", (done) ->
+			scroll = new ScrollComponent
+				width: 20
+				height: 20
+			layer = new Layer parent: scroll.content
+			doneCalled = false
+			scroll.on Events.Move, ->
+				done() if not doneCalled
+				doneCalled = true
+			scroll.scrollToPoint({x: 50, y: 50})
+

 	describe "wrap", ->


fix: Remove debug logging

fix: Remove debug logging

--- app/web/caretaker-controlpanel/dialogs/caretaker-controlpanel-edit-widget-dialog.coffee
+++ app/web/caretaker-controlpanel/dialogs/caretaker-controlpanel-edit-widget-dialog.coffee
@@ -7,7 +7,6 @@
       title: @widget.title
       width: @widget.width
       height: @widget.height
-    console.log @initial
     @$.devicesNames.go()
     @validate()
     promise = new Promise ((resolve, reject) ->


Add key to Markdown loop

Add key to Markdown loop

--- docs/components/common/Markdown.cjsx
+++ docs/components/common/Markdown.cjsx
@@ -38,7 +38,7 @@
           if line.indexOf('|Code:') > -1
             place = /\|Code:(.+?)\|/.exec(line)[1]

-            codes[place]
+
{ codes[place] }

           else

 }


fix caching of suggestions

fix caching of suggestions

--- app/coffee/ASpell.coffee
+++ app/coffee/ASpell.coffee
@@ -13,8 +13,12 @@
 			suggestions = @getSuggestions(output)
 			results = []
 			for word, i in words
-				if suggestions[word]?
-					cache.set(language + ':' + word, suggestions[word])
+				key = language + ':' + word
+				cached = cache.get(key)
+				if cached?
+					results.push index: i, suggestions: cached
+				else if suggestions[word]?
+					cache.set(key, suggestions[word])
 					results.push index: i, suggestions: suggestions[word]
 			callback null, results

@@ -51,13 +55,13 @@

 		newWord = {}
 		for word in words
-			newWord[word] = true if !newWord[word] && !cache.get(language + ':' + word)?
+			newWord[word] = true if !newWord[word] && !cache.has(language + ':' + word)
 		words = Object.keys(newWord)

 		if words.length
 			WorkerPool.check(language, words, ASpell.ASPELL_TIMEOUT, callback)
 		else
-			callback null, []
+			callback null, ""

 module.exports = ASpell =
 	# The description of how to call aspell from another program can be found here:
@@ -69,4 +73,3 @@
 	ASPELL_TIMEOUT : 4000

 WorkerPool = new ASpellWorkerPool()
-


forgot to hide __sendBetaInvites

forgot to hide __sendBetaInvites

--- server/app/bongo/models/invitation.coffee
+++ server/app/bongo/models/invitation.coffee
@@ -13,7 +13,7 @@
     indexes         :
       code          : 'unique'
     sharedMethods   :
-      static        : ['byCode','__sendBetaInvites']#,,'__createBetaInvites']
+      static        : ['byCode']#,'__sendBetaInvites',,'__createBetaInvites']
     schema          :
       code          : String
       inviteeEmail  : String


Log an error instead of crashing when setting header twice

--- server/queues/scoring/scoringUtils.coffee
+++ server/queues/scoring/scoringUtils.coffee
@@ -23,9 +23,12 @@

 module.exports.sendResponseObject = (res, object) ->
-  res.setHeader('Content-Type', 'application/json')
-  res.send(object)
-  res.end()
+  try
+    res.setHeader('Content-Type', 'application/json')
+    res.send(object)
+    res.end()
+  catch error
+    log.error("ERROR in #{res.req.method} #{res.req.url}:\n" + error.stack)

 module.exports.formatSessionInformation = (session) ->
   heroID = if session.team is 'ogres' then 'hero-placeholder-1' else 'hero-placeholder'


Fix not necessary warn

Fix not necessary warn

--- client/app/Applications/Chat.kdapplication/Controllers/conversationlistcontroller.coffee
+++ client/app/Applications/Chat.kdapplication/Controllers/conversationlistcontroller.coffee
@@ -20,7 +20,7 @@
     chatController = KD.getSingleton 'chatController'
     {JChatConversation} = KD.remote.api
     JChatConversation.fetchSome {}, (err, conversations)=>
-      warn err  unless err?.message is 'Access denied'
+      warn err  if err and err.message isnt 'Access denied'
       conversations ?= []
       for conversation in conversations
         chatController.addConversationToChatPanel \


avatar upload mr fixes.

avatar upload mr fixes.

--- client/landing/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
+++ client/landing/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
@@ -212,7 +212,9 @@
                     height: 400
                 capture.on "snap", (data)-> [_, newAvatar] = data.split ','
                 capture.on "save", =>
-                  avatarSetter newAvatar, -> capture.hide()
+                  avatarSetter newAvatar, =>
+                    @bigAvatar.show()
+                    capture.hide()

         modal = new KDModalView modalOptions
         modal.addSubView @bigAvatar = new AvatarStaticView


Linter error

Linter error

--- tests/unit/models/patient-test.coffee
+++ tests/unit/models/patient-test.coffee
@@ -45,15 +45,16 @@
   patient = null
   Ember.run ->
     patient = store.createRecord('patient', {})
-    patient.get('name').pushObject(store.createRecord('human-name',   {
-        "use": "official",
-        "family": [
-          "Donald"
-        ],
-        "given": [
-          "Duck"
-        ]
-      }))
+    patient.get('name').pushObject(store.createRecord('human-name',
+    {
+      "use": "official",
+      "family": [
+        "Donald"
+      ],
+      "given": [
+        "Duck"
+      ]
+    }))
     equal patient.get('fullName'), 'Donald, Duck', 'Full name is correct'


Fix broken test (test condition didn't have verificationStatus properly set)

--- tests/unit/models/patient-test.coffee
+++ tests/unit/models/patient-test.coffee
@@ -125,6 +125,7 @@
     codeableConcept.get('coding').pushObject(coding)
     condition.set('code', codeableConcept)
     condition.set("onsetDate", "2012-10-03T08:00:00-04:00")
+    condition.set("verificationStatus", "confirmed")
     patient.get('conditions').pushObject(condition)

     condition = store.createRecord('condition', {})
@@ -138,6 +139,7 @@
     condition.set('code', codeableConcept)
     condition.set("onsetDate", "2012-10-03T08:00:00-04:00")
     condition.set("abatementDateTime", "2013-10-03T08:00:00-04:00")
+    condition.set("verificationStatus", "confirmed")
     patient.get('conditions').pushObject(condition)

     equal patient.get('conditions.length'), 2


Splitting config options between `local` and `sauce labs`

--- tests/www/tests/utils/browsers.coffee
+++ tests/www/tests/utils/browsers.coffee
@@ -1,8 +1,15 @@
-module.exports = ->
+exports['local'] = ->
+  return [
+    chrome_mountain
+    firefox_mountain
+    safari6_mountain
+  ]
+
+exports['sauce labs'] = ->
   return [
     # chrome
+    chrome_linux
     chrome_win8
-    chrome_linux
     chrome_mountain

     # firefox


Don't fetch external resources when templating

--- extension/palsu/utils.coffee
+++ extension/palsu/utils.coffee
@@ -2,6 +2,10 @@
 fs = require 'fs'
 jsdom = require 'jsdom'
 jQuery = require 'jquery'
+
+jsdom.defaultDocumentFeatures =
+  FetchExternalResources: false,
+  ProcessExternalResources: false

 exports.writeUser = (user, jQuery) ->
   jQuery('#account [property="foaf\\:nick"]').text user.displayName


Fix issue that player can move on tile beyond the map

stupid index wars, just let everything start with 1 -.-

--- app/models/Entity.coffee
+++ app/models/Entity.coffee
@@ -105,7 +105,7 @@
     numYTiles = @map.get 'numYTiles'

     @position.y++
-    @position.y = numYTiles if @position.y > numYTiles
+    @position.y = numYTiles - 1 if @position.y > numYTiles - 1

     # moveDirection and animationState
     @updateViewAndAnimation(2)
@@ -120,7 +120,7 @@
     numXTiles = @map.get 'numXTiles'

     @position.x++
-    @position.x = numXTiles if @position.x > numXTiles
+    @position.x = numXTiles - 1 if @position.x > numXTiles - 1

     # moveDirection and animationState
     @updateViewAndAnimation(1)


make upgrade link scroll to top of upgrade box

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -1019,6 +1019,10 @@
         if e.keyCode == 13 and @state.title_text != ''
             @create_project()

+    go_to_upgrade : (e) ->
+        e.preventDefault();
+        $('html, body').animate({ scrollTop: $('#upgrade_before_creation').offset().top }, 0)
+
     render_upgrade_before_create : ->
         subs = @props.customer?.subscriptions?.total_count ? 0
         console.log(subs)
@@ -1036,7 +1040,9 @@
             3 GB Memory, 5 GB Disk space, and half CPU share. You can share upgrades
             with any project you are a collaborator on.

+                {

 if subs == 0}
                 <BillingPageSimplifiedRedux redux={redux} />
+                {

 if subs > 0}
                 {@render_upgrades_adjustor() if subs > 0}

         </Col>
@@ -1100,7 +1106,7 @@
                 <Col sm=12 style={color:'#555'}>

                         A project is your own private computational workspace that you can
-                        share with others and upgrade.
+                        share with others and upgrade.

                 </Col>
             </Row>


Unsubscribe correctly in subscribeToCommand

--- src/stdlib/subscriber.coffee
+++ src/stdlib/subscriber.coffee
@@ -7,7 +7,7 @@
   subscribeToCommand: (view, eventName, callback) ->
     view.command eventName, callback
     @subscriptions ?= []
-    @subscriptions.push(cancel: -> view.off view, callback)
+    @subscriptions.push(cancel: -> view.off eventName, callback)

   unsubscribe: ->
     subscription.cancel() for subscription in @subscriptions ? []


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

   return target

-String::constantize
+String::constantize = ->
 return this.match(/\.([^\.]*)$/, '').last() || '';

 ###*


Optimize by removing letters that are not possible

--- finnishwordbasehelper.coffee
+++ finnishwordbasehelper.coffee
@@ -20,6 +20,32 @@
 .split("\n")
 .map((x) => x.trim())
 .filter((x) => x.length == 10)
+
+filterWordsByBoard = (words, board) ->
+    getCharacterCount = (word) =>
+        count = {}
+        for c in word
+            count[c] = 0 unless count[c]?
+            count[c] += 1
+        count
+
+    getBoardCharacterCount = (board) =>
+        count = {}
+        for x in [0..board.width-1]
+            for y in [0..board.height-1]
+                c = board.get([x, y])
+                count[c] = 0 unless count[c]?
+                count[c] += 1
+        count
+
+    boardCount = getBoardCharacterCount(board)
+    words.filter (word) =>
+        wordCount = getCharacterCount(word.toUpperCase())
+        for letter, count of wordCount
+            unless boardCount[letter] or boardCount[letter] < count
+                return false
+        return true
+

 class Tri
     MATCH_EXACT = 2
@@ -96,6 +122,12 @@
 console.log board[12]

+solver = new BoardSolver(board)
+console.log "Words", words.length
+words = filterWordsByBoard(words, solver)
+console.log "Words (after)", words.length
 tri = new Tri(words)
-solver = new BoardSolver(board)
+
+
+
 solver.solve(tri)


Change to post in channel not reply.

Change to post in channel not reply.

--- src/the-more-you-know.coffee
+++ src/the-more-you-know.coffee
@@ -1,4 +1,4 @@
 module.exports = (robot) ->
   robot.hear /the more you know/, (msg) ->
-    msg.reply "http://www.youtube.com/watch?v=v3rhQc666Sg"
+    msg.send "http://www.youtube.com/watch?v=v3rhQc666Sg"


fix image url for linked images attachments

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -220,6 +220,7 @@

 formatAttachment = (att) ->
+    console.log 'attachment', att if att.length > 0
     if att?[0]?.embed_item?.type_
         {href, thumb} = extractProtobufStyle(att)
     else if att?[0]?.embed_item?.type
@@ -253,6 +254,10 @@
     return unless k
     href = data?[k]?[5]
     thumb = data?[k]?[9]
+    if not thumb
+      href = data?[k]?[4]
+      thumb = data?[k]?[5]
+
     {href, thumb}

 extractObjectStyle = (att) ->


Correct overdrag

Correct overdrag

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -163,7 +163,7 @@
 			x: touchEvent.clientX - @_correctedLayerStartPoint.x
 			y: touchEvent.clientY - @_correctedLayerStartPoint.y

-		@_point = @layer.point
+		@_point = @_correctedLayerStartPoint
 		@_ignoreUpdateLayerPosition = false

 		@emit(Events.DragStart, event)


StackTemplate: notify group when updating group stack template

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -389,7 +389,7 @@
       notifyOptions =
         account : delegate
         group   : group.slug
-        target  : 'account'
+        target  : if @accessLevel is 'group' then 'group' else 'account'

       @updateAndNotify notifyOptions, query, (err) =>
         callback err, this


fix linux monitor

fix linux monitor

--- plugin/linux/monitor.coffee
+++ plugin/linux/monitor.coffee
@@ -13,7 +13,8 @@
 passwd_cache = {}

 exports.run = ->
-  #setInterval exports.monitoring, config.plugins.linux.monitor_cycle
+  exports.monitoring()
+  setInterval exports.monitoring, config.plugins.linux.monitor_cycle

 exports.loadPasswd = (callback) ->
   app.redis.get 'rp:passwd_cache', (err, passwd_cache) ->
@@ -73,7 +74,7 @@
   exports.loadPasswd ->
     exports.getProcessList (plist) ->
       plist = _.reject plist, (item) ->
-        return item[..3] == 'root'
+        return item.user == 'root'

       async.parallel
         cpu: (callback) ->
@@ -84,7 +85,7 @@

       , (err, result) ->
         app.redis.get REDIS_KEY, (err, resources_usage_list) ->
-          resources_usage_list = JSON.parse resources_usage_list
+          resources_usage_list = JSON.parse(resources_usage_list) ? []
           resources_usage_list.push result
           resources_usage_list = _.last resources_usage_list, ITEM_IN_RESOURCES_LIST

@@ -96,7 +97,7 @@
             if account_usage[account_name][type]
               account_usage[account_name][type] += value
             else
-              account_usage[account_name][type] += value
+              account_usage[account_name][type] = value

           for item in resources_usage_list
             for account_name, cpu_usage of item.cpu
@@ -109,13 +110,16 @@
             usage.memory = usage.memory / (resources_usage_list.length * config.plugins.linux.monitor_cycle * 1000)

           app.redis.setex REDIS_OVERVIEW, 60, JSON.stringify(account_usage), ->
-            async.each _.keys(account_usage), (account_name) ->
+            async.each _.keys(account_usage), (account_name, callback) ->
               mAccount.byUsername account_name, (err, account) ->
+                unless account
+                  return callback()
+
                 if account_usage[account_name].cpu > account.attribute.resources_limit.cpu
-                  child_process.exec "pkill -SIGKILL -u #{account_name}", ->
+                  child_process.exec "sudo pkill -SIGKILL -u #{account_name}", ->

                 if account_usage[account_name].memory > account.attribute.resources_limit.memory
-                  child_process.exec "pkill -SIGKILL -u #{account_name}", ->
+                  child_process.exec "sudo pkill -SIGKILL -u #{account_name}", ->

                 callback()
             , ->
@@ -151,7 +155,7 @@
   callback null, total_time

 exports.monitoringMemory = (plist, callback) ->
-  total_memory = 0
+  total_memory = {}

   addMemory = (account_name, menory) ->
     if total_memory[account_name]
@@ -160,6 +164,6 @@
       total_memory[account_name] = menory

   for item in plist
-    addMemory item.user, ((item.rss / 1024) * config.plugins.linux.monitor_cycle / 1000).toFixed()
+    addMemory item.user, parseInt ((item.rss / 1024) * config.plugins.linux.monitor_cycle / 1000).toFixed()

   callback null, total_memory


Set default GestureInputLongPressTime to iOS Default

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -1,6 +1,6 @@
 Utils = require "./Utils"

-GestureInputLongPressTime = 0.25
+GestureInputLongPressTime = 0.5
 GestureInputDoubleTapTime = 0.25
 GestureInputSwipeThreshold = 30
 GestureInputEdgeSwipeDistance = 30


fixed repo scanning; fixes #51

fixed repo scanning; fixes #51

--- app/assets/javascripts/views/admin/active_repositories_item_view.coffee
+++ app/assets/javascripts/views/admin/active_repositories_item_view.coffee
@@ -16,15 +16,29 @@
       <% }) %>

-      <a id="scan" href="/admin/repositories/<%= name %>/scan"> scan
-       remove
+      <a id="scan" href="/api/repos/<%= id %>/scan <%= name %>/scan"> scan
+       remove

   """)

   events:
     "click a#remove": "removeItem"
+    "click a#scan" : "scan"

+  ui :
+    "spinner" : ".fa-refresh"

   removeItem: ->

-    @model.destroy()+    @model.destroy()
+
+
+  scan : (evt) ->
+
+    evt.preventDefault()
+
+    $.get(evt.target.href).done(=>
+      @ui.spinner.removeClass("fa-spin")
+    )
+
+    @ui.spinner.addClass("fa-spin")


Add atom config

Add atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -6,7 +6,7 @@
     fontSize: 15
     softWrapAtPreferredLineLength: true
     softWrap: true
-    preferredLineLength: 120
+    preferredLineLength: 150
     showInvisibles: true
   core:
     ignoredNames: [
@@ -26,6 +26,17 @@
       "background-tips"
       "exception-reporting"
       "metrics"
+      "language-csharp"
+      "language-coffee-script"
+      "language-clojure"
+      "language-c"
+      "language-gfm"
+      "language-go"
+      "language-less"
+      "language-make"
+      "language-mustache"
+      "language-objective-c"
+      "language-toml"
     ]
   "spell-check":
     grammars: [
@@ -38,3 +49,21 @@
   "release-notes":
     viewedVersion: "0.89.0"
   emmet: {}
+".angular.html.text":
+  editor:
+    preferredLineLength: 150
+".clojure.source":
+  editor:
+    preferredLineLength: 150
+".c.source":
+  editor:
+    preferredLineLength: 150
+".cpp.source":
+  editor:
+    preferredLineLength: 150
+".perl.source":
+  editor:
+    preferredLineLength: 150
+".perl6.source":
+  editor:
+    preferredLineLength: 150


Fixed path argument for starting omni-sharp-server.

--- lib/omni-sharp-server/omni-sharp-server.coffee
+++ lib/omni-sharp-server/omni-sharp-server.coffee
@@ -72,7 +72,7 @@
             return console.error "error finding freeport: ", err

           @port = port
-          serverArguments = [ "-s", atom?.project?.path, "-p", port]
+          serverArguments = [ "-s", atom?.project?.getPaths()[0], "-p", port]

           if useMono
             serverArguments.unshift location


utils: add collaborative channel check function

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -942,6 +942,15 @@
         success     : ->
           callback null, "#{policy.req_url}/#{policy.upload_url}/#{name}"

+  getCollaborativeChannelPrefix: -> '___collaborativeSession.'
+
+  isChannelCollaborative: (channel) ->
+
+    return no  unless channel.purpose?
+
+    prefix = KD.utils.getCollaborativeChannelPrefix()
+    return channel.purpose.slice(0, prefix.length) is prefix
+

   ###*
   Decimal adjustment of a number


Collaboration: Log error occurred during IDE load

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -141,7 +141,9 @@

       catch e

+        console.error e
         return routeToLatestWorkspace()
+

   refreshWorkspaces = (callback) ->


Remove dependency on underscore from the keyboard manager

--- app/assets/javascripts/exo/keyboard_manager.js.coffee
+++ app/assets/javascripts/exo/keyboard_manager.js.coffee
@@ -1,5 +1,7 @@
 namespace 'Exo', (exports) ->
   keyboardManager = class exports.KeyboardManager
+
+    cid = 0

     _map = {
       8: 'backspace'
@@ -62,7 +64,7 @@

     constructor: (@el = null) ->
       @_responders = []
-      @cid = _.uniqueId("k")
+      @cid = "k" + cid++

       @el ||= document
       @$el = $(@el)


Fix indentation bug

Fix indentation bug

--- admin/static/coffee/namespaces/index.coffee
+++ admin/static/coffee/namespaces/index.coffee
@@ -109,19 +109,19 @@
                 need_to_increase = true
                 switch formdata.protocol
                     when "Memcached"
-                        formdata.port = 11211
+                        formdata.port = 11213
                     when "Redis"
                         formdata.port = 6379
                     when "Riak"
                         formdata.port = 8098
                     when "MongoDB"
                         formdata.port = 27017
-             if need_to_increase is true
-                used_ports = {}
-                for namespace in namespaces.models
-                    used_ports[namespace.get('port')] = true
-                    while formdata.port of used_ports
-                        formdata.port++
+                if need_to_increase is true
+                    used_ports = {}
+                    for namespace in namespaces.models
+                        used_ports[namespace.get('port')] = true
+                        while formdata.port of used_ports
+                            formdata.port += 123
             else if @isnt_integer(formdata.port)
                 input_error = true
                 template_error.port_isnt_integer = true


Prevent LaTeX processing in Markdown code blocks (fix #387)

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -549,8 +549,13 @@
     #text = replaceMathBlocks text, (block) ->
     #  block.replace /[\\`*{}\[\]()#+\-.!_]/g, '\\$&'
     #marked.Lexer.rules = {text: /^[^\n]+/} if title
+    ## First extract Markdown verbatim environments (backticks) to prevent
+    ## LaTeX processing on them (especially math mode, so $ is inert in code).
+    [text, ticks] = preprocessMarkdownTicks text
     ## Support what we can of LaTeX before doing Markdown conversion.
     [text, math] = latex2htmlLight text
+    ## Put Markdown verbatims back in.
+    text = putTicksBack text, ticks
     if title  ## use "single-line" version of Markdown
       text = markdownInline text
     else
@@ -695,9 +700,8 @@
   math = []
   i = 0
   text = replaceMathBlocks text, (block) ->
-    i += 1
-    math[i] = block
-    "MATH#{i}ENDMATH"
+    math.push block
+    "MATH#{i++}ENDMATH"
   [text, math]

 putMathBack = (tex, math) ->
@@ -705,6 +709,7 @@
   tex.replace /MATH(\d+)ENDMATH/g, (match, id) -> math[id].all

 postprocessKaTeX = (text, math, initialBold) ->
+  return text unless math.length
   macros = {}  ## shared across multiple math expressions within same body
   if initialBold
     weights = [boldWeight]
@@ -753,6 +758,32 @@
     else
       out

+preprocessMarkdownTicks = (text) ->
+  ticks = []
+  i = 0
+  ## See https://spec.commonmark.org/0.29/#code-spans and
+  ## https://spec.commonmark.org/0.29/#fenced-code-blocks for relevant specs.
+  ## Bad cases not handled by this regex:
+  ## * `foo followed by two newlines is treated as still opening a code span
+  ## * <a href="`"> is treated as opening a code span
+  text = text.replace ///
+    (^|[^\\`])  # backticks shouldn't be preceded by \escape or more backticks
+    (`+)        # one or more backticks to open
+    ([^`] |         # case 1: single nontick character inside
+     [^`][^]*?[^`]) # case 2: multiple characters inside, not bounded by ticks
+    (\2|$)      # matching number of backticks to close
+    ([^`]|$)    # not any additional `s afterward
+  ///g, (match, pre, left, mid, right, post) ->
+    ## Three or more backticks (fenced code blocks) are terminated by end of doc
+    return match unless right or left.length >= 3
+    ticks.push match[pre.length..]
+    "#{pre}TICK#{i++}ENDTICK#{post}"
+  [text, ticks]
+
+putTicksBack = (text, ticks) ->
+  return text unless ticks.length
+  text.replace /TICK(\d+)ENDTICK/g, (match, id) -> ticks[id]
+
 ## Search highlighting
 formatSearchHighlight = (isTitle, text) ->
   if Meteor.isClient and Router.current()?.route?.getName() == 'search' and


fixes JS exception in #1319

fixes JS exception in #1319


load properly

load properly

--- test/cron_events_test.coffee
+++ test/cron_events_test.coffee
@@ -105,6 +105,7 @@
         }
       }
       room.robot.brain.emit 'loaded'
+      room.robot.cron.loadAll()

       afterEach ->
         room.robot.brain.data.cron = { }
@@ -138,6 +139,7 @@
         }
       }
       room.robot.brain.emit 'loaded'
+      room.robot.cron.loadAll()

       afterEach ->
         room.robot.brain.data.cron = { }


Style formatting

Style formatting

--- test/unit/config.spec.coffee
+++ test/unit/config.spec.coffee
@@ -7,6 +7,7 @@
   mocks = m = e = null
   path = require('path')
   util = require('../../lib/util')
+
   resolveWinPath = (p) -> util.normalizeWinPath(path.resolve(p))

   normalizeConfigWithDefaults = (cfg) ->
@@ -183,9 +184,11 @@
       config = m.parseConfig '/home/config6.js', {}
       expect(config.reporters).toEqual ['junit']

+
     it 'should compile coffeescript config', ->
       config = e.parseConfig '/conf/coffee.coffee', {}
       expect(config.files).toEqual ['/conf/one.js', '/conf/two.js']
+

     it 'should set defaults with coffeescript', ->
       config = e.parseConfig '/conf/coffee.coffee', {}


Dumb typo

Dumb typo

--- static/js/maproulette.coffee
+++ static/js/maproulette.coffee
@@ -317,7 +317,7 @@
   challenge = currentChallenge.slug
   task_id = currentTask.id
   $.post "/c/#{challenge}/task/#{task_id}", payload, ->
-    setDelay 1 ->
+    setDelay 1, ->
       clearTask()
       getTask(challenge, near)


Use a stub to test Backbone.History#navigate calls Simplify changeURL options tests

--- test/spec/router_spec.coffee
+++ test/spec/router_spec.coffee
@@ -1,9 +1,10 @@
 define [
+  'backbone'
   'underscore'
   'chaplin/mediator'
   'chaplin/lib/router'
   'chaplin/lib/route'
-], (_, mediator, Router, Route) ->
+], (Backbone, _, mediator, Router, Route) ->
   'use strict'

   describe 'Router and Route', ->
@@ -32,7 +33,7 @@
       expect(Backbone.history).to.be.a Backbone.History

     it 'should not start the Backbone.History at once', ->
-      expect(Backbone.History.started).to.not.be.ok()
+      expect(Backbone.History.started).to.be false

     it 'should allow to start the Backbone.History', ->
       spy = sinon.spy(Backbone.history, 'start')
@@ -55,7 +56,7 @@
       spy = sinon.spy(Backbone.history, 'stop')
       expect(router.stopHistory).to.be.a 'function'
       router.stopHistory()
-      expect(Backbone.History.started).to.not.be.ok()
+      expect(Backbone.History.started).to.be false
       expect(spy).was.called()

     it 'should have a match method which returns a route', ->
@@ -252,70 +253,64 @@

     it 'should listen to the !router:route event', ->
       path = 'router-route-events'
-      sinon.spy(router, 'route')
-      spy = sinon.spy()
+      routeSpy = sinon.spy router, 'route'
+      callback = sinon.spy()
       router.match path, 'router#route'

-      mediator.publish '!router:route', path, spy
-      expect(router.route).was.calledWith path
-      expect(spy).was.calledWith true
+      mediator.publish '!router:route', path, callback
+      expect(routeSpy).was.calledWith path
+      expect(callback).was.calledWith true
       expect(route.controller).to.be 'router'
       expect(route.action).to.be 'route'

-      spy = sinon.spy()
-      mediator.publish '!router:route', 'different-path', spy
-      expect(spy).was.calledWith false
+      callback = sinon.spy()
+      mediator.publish '!router:route', 'different-path', callback
+      expect(callback).was.calledWith false
+
+      routeSpy.restore()

     it 'should listen to the !router:changeURL event', ->
       path = 'router-changeurl-events'
-      sinon.spy(router, 'changeURL')
+      changeURL = sinon.spy router, 'changeURL'

       mediator.publish '!router:changeURL', path
-      expect(router.changeURL).was.calledWith path
-
-    it 'should allow options to the passed through the !router:route event', ->
+      expect(changeURL).was.calledWith path
+
+      changeURL.restore()
+
+    it 'should pass the routing options through', ->
       path = 'router-route-events-options'
-      sinon.spy(router, 'route')
-      spy = sinon.spy()
+      routeSpy = sinon.spy router, 'route'
+      callback = sinon.spy()
       router.match path, 'router#route'

-      mediator.publish '!router:route', path, replace: true, spy
-      expect(router.route).was.calledWith path, replace: true, changeURL: true
-      expect(spy).was.calledWith true
+      expectedOptions = path: path, replace: true, changeURL: true
+      mediator.publish '!router:route', path, replace: true, callback
+      expect(routeSpy).was.calledWith path, expectedOptions
+      expect(callback).was.calledWith true
       expect(route.controller).to.be 'router'
       expect(route.action).to.be 'route'
-      expect(options).to.eql replace: true, changeURL: true
-
-    it 'should forward options passed through the !router:changeURL event', ->
-      path = 'router-changeurl-options-events'
-      sinon.spy(router, 'changeURL')
-
-      mediator.publish '!router:changeURL', path, replace: true
-      expect(router.changeURL).was.calledWith path,
-        replace: true
-        trigger: false
-
-      navigate = sinon.spy(Backbone.history, 'navigate')
-
-      Backbone.history.start()
-      mediator.publish '!router:changeURL', path
-      expect(Backbone.history.fragment).to.be path
+      expect(options).to.eql expectedOptions
+
+      routeSpy.restore()
+
+    it 'should forward changeURL routing options to Backbone', ->
+      path = 'router-changeurl-options'
+      changeURL = sinon.spy router, 'changeURL'
+      navigate = sinon.stub Backbone.history, 'navigate'
+
+      options = some: 'stuff'
+      mediator.publish '!router:changeURL', path, options
+      expect(navigate).was.calledWith path,
+        replace: false, trigger: false
+
+      options = replace: true, trigger: true, some: 'stuff'
+      mediator.publish '!router:changeURL', path, options
       expect(Backbone.history.navigate).was.calledWith path,
-        trigger: false
-
-      path = 'router-changeurl-replace-events'
-      mediator.publish '!router:changeURL', path
-      expect(Backbone.history.fragment).to.be path
-      expect(Backbone.history.navigate).was.calledWith path,
-        trigger: false
-
-      path = 'router-changeurl-replace-with-events'
-      mediator.publish '!router:changeURL', path, replace: true
-      expect(Backbone.history.fragment).to.be path
-      expect(Backbone.history.navigate).was.calledWith path,
-        trigger: false, replace: true
-
-      Backbone.history.stop()
+        replace: true, trigger: true
+
+      changeURL.restore()
+      navigate.restore()

     it 'should dispose itself correctly', ->
       expect(router.dispose).to.be.a 'function'


refs #7 add "static" option for manually closable, the growl message with option static will display for 3 years before it is dismissed

--- javascripts/jquery.growl.coffee
+++ javascripts/jquery.growl.coffee
@@ -37,6 +37,8 @@
     $("body:not(:has(#growls))").append '<div id="growls" />'

   constructor: (settings = {}) ->
+    if(settings.static)
+        settings.duration=99999999999;
     @settings = $.extend {}, Growl.settings, settings
     @$growls().attr 'class', @settings.location
     @render()


Client asserts about errors thrown by notifier

--- test/unit/client_test.coffee
+++ test/unit/client_test.coffee
@@ -146,10 +146,14 @@
       processor = { process: -> throw(new Error("Processor Error")) }
       getProcessor = -> processor
       client = new Client(getProcessor, getReporter)
-      client.captureException(exception)
+
+      run = -> client.captureException(exception)
+      expect(run).not.to.throw()

     it "ignores errors thrown by reporter", ->
       reporter = { report: -> throw(new Error("Reporter Error")) }
-      getReporter = -> processor
+      getReporter = -> reporter
       client = new Client(getProcessor, getReporter)
-      client.captureException(exception)+
+      run = -> client.captureException(exception)
+      expect(run).not.to.throw()


fix exec.

fix exec.

--- code/serv.coffee
+++ code/serv.coffee
@@ -191,9 +191,7 @@
   res.removeHeader 'Content-Type'
   cmd = req.body.cmd
   su = child_process.spawn "su", ["-", "-c", "#{cmd}", "#{req.params.boxname}"]
-  console.log data
   su.stdout.on 'data', (data) ->
-    console.log data
     res.write data
   su.stderr.on 'data', (data) ->
     res.write data


fixed cacheing issue

fixed cacheing issue

--- coffeeApp.coffee
+++ coffeeApp.coffee
@@ -46,7 +46,9 @@
 # Prod config
 app.configure 'production', ->
   app.use express.errorHandler()
-
+
+
+
 Twitter = new twit {
   consumer_key: process.env.consumer_key,
   consumer_secret: process.env.consumer_secret,
@@ -56,9 +58,12 @@

 # Temporary storage of tweets
 tweets = []
+
+eraseTweets = -> tweets = []
+
 # Clears cache of tweets
 setInterval eraseTweets, 5000
-eraseTweets = -> tweets = []
+
 # Returns tweets to client
 `getTweets = function() {return tweets;}`

@@ -92,13 +97,13 @@

 # Array of re-tweeted screen_names
 twitterUsernameArray = []
+
 # 350 per hour, 50 per min, we'll do 45 so we avoid hitting the limit
 limit = 1

-setInterval resetLimit, 30000
 resetLimit = -> limit = 1

-
+setInterval resetLimit, 30000

 retweet = (screen_name, tweetID) ->
   if limit is 0
@@ -123,4 +128,3 @@

 process.on 'uncaughtException', (err) ->
     console.log 'Uncaught Error!!! : ' + err
-