Add check to GameDevTrackView.beautify

Add check to GameDevTrackView.beautify

If for some reason the `val` in beautify is undefined or null, the view
will break when it tries to split the result. This makes sure the value
returned by beautify is a string.

--- app/views/play/level/GameDevTrackView.coffee
+++ app/views/play/level/GameDevTrackView.coffee
@@ -68,4 +68,4 @@
     if typeof val is 'number'
       round = Math.round(val)
       return round
-    return val
+    return val ? ''


Updating

Updating

--- packages.cson
+++ packages.cson
@@ -1,8 +1,6 @@
 packages: [
   "browser-plus"
-  "dark-bint-syntax"
   "file-icons"
-  "fusion-ui"
   "git-plus"
   "git-time-machine"
   "language-archlinux"
@@ -11,6 +9,8 @@
   "language-coffee-script"
   "language-csharp"
   "language-css"
+  "language-debian"
+  "language-gentoo"
   "language-gfm2"
   "language-git"
   "language-go"
@@ -22,6 +22,8 @@
   "language-json"
   "language-less"
   "language-liquid"
+  "language-lisp"
+  "language-lua"
   "language-make"
   "language-mustache"
   "language-objective-c"
@@ -40,57 +42,13 @@
   "language-source"
   "language-sql"
   "language-text"
-  "language-debian"
   "language-todo"
   "language-toml"
   "language-unix-shell"
   "language-xml"
   "language-yaml"
-  "language-gentoo"
-  "language-lua"
-  "language-lisp"
-  "language-rpm-spec"
   "markdown-writer"
-  "archive-view"
-  "autocomplete-atom-api"
-  "autocomplete-css"
-  "autocomplete-html"
-  "autocomplete-plus"
-  "autocomplete-snippets"
-  "autoflow"
-  "autosave"
-  "background-tips"
-  "bookmarks"
-  "bracket-matcher"
-  "command-palette"
-  "deprecation-cop"
-  "dev-live-reload"
-  "encoding-selector"
-  "find-and-replace"
-  "fuzzy-finder"
-  "git-diff"
-  "go-to-line"
-  "grammar-selector"
-  "image-view"
-  "incompatible-packages"
-  "keybinding-resolver"
-  "line-ending-selector"
-  "link"
-  "markdown-preview"
-  "notifications"
-  "open-on-github"
-  "package-generator"
-  "settings-view"
-  "snippets"
-  "status-bar"
-  "styleguide"
-  "tabs"
-  "timecop"
-  "tree-view"
-  "update-package-dependencies"
-  "welcome"
-  "whitespace"
-  "wrap-guide"
   "minimap"
   "pigments"
+  "terminal-fusion"
 ]


Fixed: typo in variable name

Fixed: typo in variable name

--- server/lib/report-manager.coffee
+++ server/lib/report-manager.coffee
@@ -11,7 +11,7 @@
     constructor: ->
         @client = new Client "http://localhost:9101/"
         unless process.env.NODE_ENV not in ["production", "test"]
-            clientDS.setBasicAuth process.env.NAME, process.env.TOKEN
+            @client.setBasicAuth process.env.NAME, process.env.TOKEN

     start: ->
         @prepareNextCheck()


add setBasicAuth for static app in getToken

--- server/models/application.coffee
+++ server/models/application.coffee
@@ -77,6 +77,7 @@

 # get token for static app
 Application.getToken = (id, callback) ->
+    dataClient.setBasicAuth 'home', getToken()
     dataClient.post "request/access/byApp/", key: id, (err, res, body) ->
         if err
             callback err


Remove erroneous 256 from user handler

Remove erroneous 256 from user handler

--- server/users/user_handler.coffee
+++ server/users/user_handler.coffee
@@ -173,7 +173,7 @@

   getById: (req, res, id) ->
     if Handler.isID(id) and req.user?._id.equals(id)
-      return @sendSuccess(res, @formatEntity(req, req.user, 256))
+      return @sendSuccess(res, @formatEntity(req, req.user))
     super(req, res, id)

   getByIDs: (req, res) ->


Add a test for --no-colors. fixes #137

Add a test for --no-colors. fixes #137

--- test/mocha-phantomjs.coffee
+++ test/mocha-phantomjs.coffee
@@ -342,6 +342,10 @@
         @runner done, ['-C', fileURL('mixed')], (code, stdout, stderr) ->
           expect(stdout).to.not.match /\u001b\[\d\dm/

+      it 'suppresses color output plural long form', (done) ->
+        @runner done, ['--no-colors', fileURL('mixed')], (code, stdout, stderr) ->
+          expect(stdout).to.not.match /\u001b\[\d\dm/
+
     describe 'path', ->

       it 'has used custom path', (done) ->


debug tests

debug tests

--- test/todolist.coffee
+++ test/todolist.coffee
@@ -61,12 +61,19 @@
                 response.statusCode.should.equal 200
                 todolists = JSON.parse body
                 todolists.rows.length.should.equal 3
-                todolists.rows[0].title.should.equal "Recipes"
-                todolists.rows[0].path.should.equal "/all/recipes"
-                todolists.rows[0].humanPath.should.equal "All,Recipes"
-                todolists.rows[1].title.should.equal "Dessert"
-                todolists.rows[1].path.should.equal "/all/recipes/dessert"
-                todolists.rows[1].humanPath.should.equal "All,Recipes,Dessert"
+
+                for list in todolists.rows
+                    if list.title == "Recipes"
+                        recipeList = list
+                    else if list.title == "Dessert"
+                        dessertList = list
+
+                should.exist recipeList
+                should.exist dessertList
+                recipeList.path.should.equal "/all/recipes"
+                recipeList.humanPath.should.equal "All,Recipes"
+                dessertList.path.should.equal "/all/recipes/dessert"
+                dessertList.humanPath.should.equal "All,Recipes,Dessert"
                 done()

         it "And it should have updated tree structure properly", (done) ->
@@ -96,12 +103,19 @@
                 response.statusCode.should.equal 200
                 todolists = JSON.parse body
                 todolists.rows.length.should.equal 5
-                todolists.rows[0].title.should.equal "Recipes"
-                todolists.rows[0].path.should.equal "/all/travel/recipes"
-                todolists.rows[0].humanPath.should.equal "All,Travel,Recipes"
-                todolists.rows[1].title.should.equal "Dessert"
-                todolists.rows[1].path.should.equal "/all/travel/recipes/dessert"
-                todolists.rows[1].humanPath.should.equal "All,Travel,Recipes,Dessert"
+
+                for list in todolists.rows
+                    if list.title == "Recipes"
+                        recipeList = list
+                    else if list.title == "Dessert"
+                        dessertList = list
+
+                should.exist recipeList
+                should.exist dessertList
+                recipeList.path.should.equal "/all/travel/recipes"
+                recipeList.humanPath.should.equal "All,Travel,Recipes"
+                dessertList.path.should.equal "/all/travel/recipes/dessert"
+                dessertList.humanPath.should.equal "All,Travel,Recipes,Dessert"
                 done()

         it "And it should have updated tree structure properly", (done) ->


add --set_volume and --get_volume option in theta command

--- bin/theta.coffee
+++ bin/theta.coffee
@@ -18,6 +18,8 @@
   ['--save [FILENAME]', 'save picture']
   ['--delete [Object ID]', 'delete a picture']
   ['--battery', 'check battery level']
+  ['--get_volume', 'get audio volume']
+  ['--set_volume [NUMBER]', 'set audio volume (0~100)']
 ]

 parser.on 'help', ->
@@ -31,6 +33,9 @@
     % theta --list
     % theta --id [object_id] --save out.jpg
     % theta --delete [object_id]
+    % theta --battery
+    % theta --get_volume
+    % theta --set_volume 20
     % DEBUG=* theta --capture  # print all debug messages
   """
   console.log parser.toString()
@@ -98,6 +103,30 @@
       console.log "BatteryLevel: #{res.dataPacket.array[0]}"
       theta.disconnect()

+parser.on 'get_volume', ->
+  theta.connect()
+  theta.once 'connect', ->
+    theta.getProperty 0x502C, (err, res) ->
+      if err
+        console.error err
+        return process.exit 1
+      console.log "AudioVolume: #{res.dataPacket.array[0]}"
+      theta.disconnect()
+
+parser.on 'set_volume', (opt, volume) ->
+  theta.connect()
+  theta.once 'connect', ->
+    theta.setProperty 0x502C, volume, (err, res) ->
+      if err
+        console.error err
+        return process.exit 1
+      theta.getProperty 0x502C, (err, res) ->
+        if err
+          console.error err
+          return process.exit 1
+        console.log "AudioVolume: #{res.dataPacket.array[0]}"
+        theta.disconnect()
+
 if process.argv.length < 3
   parser.on_switches.help.call()


Fix polyfills error handling

Fix polyfills error handling

--- test/utils/polyfills.coffee
+++ test/utils/polyfills.coffee
@@ -27,10 +27,10 @@
     faulty = ->
       document.write

-    faulty().should.throw
+    faulty().should.throw Error

   it 'should throw error on document.writeln', ->
     faulty = ->
       document.writeln

-    faulty().should.throw
+    faulty().should.throw Error


Remove extraneous console log line.

Remove extraneous console log line.

--- app/assets/javascripts/ui.js.coffee
+++ app/assets/javascripts/ui.js.coffee
@@ -41,7 +41,6 @@
       target_select = $ this
       param = target_select.data("autoset-param")
       new_value = source_select.find("option:selected").data(param)
-      console.info source_select, target_select, param, new_value
       target_select.val(new_value)

   # When a select box is changed find any dependent elements and


Promises, promises.

Promises, promises.

--- app/controllers/items/create.coffee
+++ app/controllers/items/create.coffee
@@ -8,24 +8,30 @@
       extension = model.extension
       blueprint = model.blueprint

+      item.set 'blueprint', blueprint
+
       # Looping over relationships so we can set the values from the form.
+      #TODO There has to be an easier way.
+      promises = []
       item.eachRelationship (field, relationship) =>
         field_key = "#{field}_id"
         if item.get field_key
           id = item.get field_key
           type_key = relationship.type.typeKey

-          @store.findById(type_key, id).then (result) ->
+          promise = @store.findById(type_key, id)
+          .then (result) ->
             item.set field, result
+          promises.push promise

-      item.set 'blueprint', blueprint
-      console.log item
-      # item.save()
-      # .then =>
-      #   #TODO For whatever reason, this does not load the model hook in the
-      #   # route, but I can't pass a string because there's two dynamic segments.
-      #   @transitionToRoute 'items',
-      #     extension: extension.id
-      #     blueprint_slug: blueprint.get 'slug'
+      RSVP.allSettled(promises).then ->
+        console.log item
+        # item.save().then =>
+        #   #TODO For whatever reason, this does not load the model hook in the
+        #   # route, but I can't pass a string because there's two dynamic
+        #   # segments.
+        #   @transitionToRoute 'items',
+        #     extension: extension.id
+        #     blueprint_slug: blueprint.get 'slug'

 `export default ItemsCreateController`


hotfix: redundant bracket in analytics coffeescript

--- app/assets/javascripts/analytics/presenter.coffee
+++ app/assets/javascripts/analytics/presenter.coffee
@@ -63,7 +63,7 @@
       @pageViewTime += 500

     if (!$('body').hasClass('de'))
-      @automatedTranslation = $('div').hasClass('Automated-translation'))
+      @automatedTranslation = $('div').hasClass('Automated-translation')

   onBeforeUnload: =>
     ga?('send', 'timing', 'PageView', 'total', @pageViewTime)


Empty section timeout test origin

Empty section timeout test origin

--- test/apiary-blueprint-parser-test.coffee
+++ test/apiary-blueprint-parser-test.coffee
@@ -56,6 +56,17 @@

 responseBlueprint = (props = {}) ->
   resourceBlueprint responses: [new Response(props)]
+
+# Create blueprint with default name and empty sections
+# named S1 to S<count>.
+multipleSectionsBlueprint = (count) ->
+  sections = [];
+  for i in [1..count] by 1
+    sections.push new Section name: "S#{i}"
+
+  new Blueprint
+    name:     "API"
+    sections: sections

 describe "Apiary blueprint parser", ->
   # ===== Rule Tests =====
@@ -513,6 +524,56 @@
       --
       --
     """
+
+  it "parses many empty sections without timeout", ->
+    # Since parser is blocking result to comparing timestamps
+    parseTimestamp = Date.now()
+    assert.parse """
+--- API ---
+
+-- S1 --
+
+-- S2 --
+
+-- S3 --
+
+-- S4 --
+
+-- S5 --
+
+-- S6 --
+
+-- S7 --
+
+-- S8 --
+
+-- S9 --
+
+-- S10 --
+
+-- S11 --
+
+-- S12 --
+
+-- S13 --
+
+-- S14 --
+
+-- S15 --
+
+-- S16 --
+
+-- S17 --
+
+-- S18 --
+
+-- S19 --
+
+-- S20 --
+
+    """, multipleSectionsBlueprint 20
+
+    assert Date.now() - parseTimestamp < 2000, "parsing not under 2000ms"

   # Canonical Resources is:
   #


Fixed issue #1014 - Creation of overview fails.

--- app/assets/javascripts/app/models/overview.coffee
+++ app/assets/javascripts/app/models/overview.coffee
@@ -5,8 +5,8 @@
   @configure_attributes = [
     { name: 'name',       display: 'Name',                tag: 'input',    type: 'text', limit: 100, 'null': false },
     { name: 'link',       display: 'Link',                readonly: 1 },
-    { name: 'role_ids',   display: 'Available for Role',  tag: 'column_select', multiple: true, nulloption: true, null: false, relation: 'Role', translate: true },
-    { name: 'user_ids',   display: 'Available for User',  tag: 'column_select', multiple: true, nulloption: false, null: true,  relation: 'User', sortBy: 'firstname' },
+    { name: 'role_ids',   display: 'Available for Role',  tag: 'column_select', multiple: true, null: false, relation: 'Role', translate: true },
+    { name: 'user_ids',   display: 'Available for User',  tag: 'column_select', multiple: true, null: true,  relation: 'User', sortBy: 'firstname' },
     { name: 'organization_shared', display: 'Only available for Users with shared Organization', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
     { name: 'condition',  display: 'Conditions for shown Tickets', tag: 'ticket_selector', null: false },
     { name: 'prio',       display: 'Prio',                readonly: 1 },


OX-129 bug fix

OX-129 bug fix

--- app/assets/javascripts/geometries/skeleton.coffee
+++ app/assets/javascripts/geometries/skeleton.coffee
@@ -171,7 +171,8 @@
         @activeNodeSphere.material.color.setHex(colorNormal)
     else
       sphere = @getSphereFromId(nodeID)
-      sphere.material.color.setHex(colorNormal)
+      if sphere?
+        sphere.material.color.setHex(colorNormal)
     @flycam.hasChanged = true

   setNodeRadius : (value) ->


Correct the coffeescript object

Correct the coffeescript object

--- test/integration/mediatorAPITests.coffee
+++ test/integration/mediatorAPITests.coffee
@@ -22,7 +22,7 @@
           type: 'http'
         }
       ]
-      defaultChannelConfig: [{
+      defaultChannelConfig: [
         name: "Save Encounter 1"
         urlPattern: "/encounters"
         type: 'http'
@@ -35,7 +35,7 @@
             type: 'http'
           }
         ]
-      }, {
+      ,
         name: "Save Encounter 2"
         urlPattern: "/encounters2"
         type: 'http'
@@ -48,7 +48,7 @@
             type: 'http'
           }
         ]
-      }]
+      ]

     mediator2 =
       urn: "urn:uuid:25ABAB99-23BF-4AAB-8832-7E07E4EA5902"


Added tests for $.ajax type and method settings

--- test/tb/lib/ti/ajax/settings_test.coffee
+++ test/tb/lib/ti/ajax/settings_test.coffee
@@ -571,6 +571,34 @@
       $.ajax '/test', settings

   describe 'timeout', ->
-  describe 'type', ->
+  describe 'type and method', ->
+
+    beforeEach ->
+
+      Ti.Network.HTTPClient.mocks.push
+        url: '/capture'
+        method: 'POST'
+        response: (data, xhr) ->
+          responseText: xhr?.headers['Content-Type']
+          contentType: 'text'
+
+    it 'should call the request using the specified type', (done) ->
+
+      settings =
+        type: 'POST'
+
+      $.ajax('/capture', settings).done -> done()
+
+    it 'should call the request using the specified method', (done) ->
+
+      settings =
+        method: 'POST'
+
+      $.ajax('/capture', settings).done -> done()
+
+    it 'should be "GET" by default', (done) ->
+
+      $.ajax('/test').done -> done()
+
   describe 'url', ->
   describe 'xhrFields', ->


Classroom: Implemented navigating to next chapter on classroom workpsace.

--- client/landing/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
+++ client/landing/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
@@ -2,11 +2,12 @@

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

-    panelOptions = data.config.panel
+    panelOptions    = data.config.panel
+    config          = @extendOptions panelOptions
+    config.delegate = options.delegate # TODO: fatihacet - it's a quick hack, we neeed to merge all other options.

     @addDefaultButtons panelOptions

-    config = @extendOptions panelOptions
     super config, data

   extendOptions: (options) ->
@@ -31,6 +32,10 @@
       cssClass   : "cupid-green join-button"
       callback   : (panel, workspace) =>
         workspace.showJoinModal()
+    ,
+      itemClass  : KDView
+      cssClass   : "chapters"
+      callback   : => @bound "showChapterList"

   validateChapter: (panel, workspace) ->
     {config}   = @getData()
@@ -43,18 +48,28 @@
         config.onFailed?  panel, workspace

   handleChapterSuccess: ->
-    new KDModalView
+    modal = new KDModalView
       overlay      : yes
-      title        : "Yay! Passed this chapter"
+      title        : "Yay! Passed this chapter."
       cssClass     : "modal-with-text"
       content      : "
You have been passed this chapter. You have 3 more to go! Keep up the good work.
"
       buttons      :
         Next       :
           title    : "Next Chapter"
           cssClass : "modal-clean-green"
-          callback : @bound "goToNextChapter"
+          callback : => @goToNextChapter modal

-  goToNextChapter: ->
-    @destroy()
-    @parent.addSubView new KDView
-      partial : "dhehah"+  goToNextChapter: (modal) ->
+    parent = @parent
+    modal.destroy()
+    parent.destroySubViews()
+
+    router = KD.getSingleton "router"
+    {course, chapter} = KD.utils.parseQuery router.getCurrentPath().split('?')[1]
+    {chapters}        = @getData().courseManifest
+
+    if chapters.length > chapter
+      router.handleQuery "?course=#{course}&chapter=#{++chapter}"
+
+  showChapterList: ->
+


Fixed typo

Fixed typo

--- src/Models/Projects.coffee
+++ src/Models/Projects.coffee
@@ -25,7 +25,7 @@
           @debug "Recurse Projects::all()"
           data = data.concat(retData)
           params.page++
-          @all params, cb
+          return @get "projects", params, cb
         else
           data = data.concat(retData)
           return fn data if fn


Properly require underscore in specs

Properly require underscore in specs

--- spec/collector-spec.coffee
+++ spec/collector-spec.coffee
@@ -1,4 +1,4 @@
-_ = require 'underscore'
+{_} = require 'atom'
 Collector = require '../lib/collector'

 describe "Collector", ->


feat(web-storage): implement basic local storage for saving Model

Closes #13.

--- app/scripts/controllers/draw.coffee
+++ app/scripts/controllers/draw.coffee
@@ -16,6 +16,8 @@
       stencilsOpen: true
       propertiesOpen: false
     }
+
+    $scope.fileName = "ExampleFileName"

     $scope.selectedStencil = false

@@ -102,6 +104,13 @@

     # Save the Model
     $scope.saveModel = ->
+
+        fileName = prompt("Please enter file name", $scope.fileName)
+
+        if not fileName?
+            return
+        $scope.fileName = fileName
+
         $stencils = $('.stencil', $scope.container)
         stencils = $.map($stencils, (el) ->
                 $stencil = $(el)
@@ -125,8 +134,18 @@
         }
         diagrams.push(diagram)
         console.log('model', model)
+
         modelStr = JSON.stringify(model, undefined, 4)
-        downloadData("model.sponge", modelStr, "application/json")
+        if (typeof(Storage) isnt "undefined")
+            # Code for localStorage/sessionStorage.
+            localStorage[fileName] = modelStr
+        else
+            # Sorry! No Web Storage support..
+            alert("Sorry! No Web Storage support with this browser...")
+
+        if confirm("Would you also like to download the #{fileName}.sponge file?")
+            downloadData("#{fileName}.sponge", modelStr, "application/json")
+

     jsPlumb.ready ->
       $scope.instance = instance = jsPlumb.getInstance(


release string when replaced in a struct or when an Array is released

--- tds.coffee
+++ tds.coffee
@@ -127,7 +127,10 @@
   views[x][3][y]++

  release = (x, y) ->
+  if x is 0 and y is 0
+   return
   views[x][3][y]--
+  return

  B = 8191 #  is 1<<13  -  1
  M = 536870909 # < 1<<29
@@ -249,7 +252,6 @@
 console.log p.get()
 ###

-#TODO release when structs are replaced.
 #TODO check memory leaks

 Struct = ->
@@ -340,6 +342,7 @@
       s.retain()
      else
       s = new StringClass v
+     StringAlloc.release @views[i][@pos*2], @views[i][@pos*2+1]
      @views[i][@pos*2] = s.x
      @views[i][@pos*2+1] = s.y
     else
@@ -350,6 +353,7 @@
        s.retain()
       else
        s = new StringClass v
+      StringAlloc.release @views[i][k1 + j*2], @views[i][k1 + j*2 + 1]
       @views[i][k1 + j*2] = s.x
       @views[i][k1 + j*2 + 1] = s.y
      else
@@ -360,6 +364,7 @@
         s.retain()
        else
         s = new StringClass v[j]
+       StringAlloc.release @views[i][k1 + j*2], @views[i][k1 + j*2 + 1]
        @views[i][k1 + j*2] = s.x
        @views[i][k1 + j*2 + 1] = s.y
    else
@@ -495,6 +500,16 @@

   getViews: -> views

+  release: ->
+   for t, i in struct.types
+    if t is Types.String
+     k1 = struct.lengths[i] * TypeArrays[t][1]
+     k2 = TypeArrays[t][1]
+     for j in [0...lengths[i]]
+      p = @pos * k1 + j * k2
+      StringAlloc.release @views[i][p], @views[i][p+1]
+   return
+
  new ArrayClass

@@ -560,6 +575,7 @@
       tarViews[j][i] = views[j][i]
     tarViews = null
     views = null
+    arrays[0].release()
     arrays[0] = lastArr
     i_lArrPos = size
     size = size << 1


Tests: Messages: PrivateMessage: Use helper.

--- client/test/lib/messages/privatemessage.coffee
+++ client/test/lib/messages/privatemessage.coffee
@@ -1,11 +1,21 @@
 helpers = require '../helpers/helpers.js'
 assert  = require 'assert'
+messagesHelpers = require '../helpers/messageshelpers.js'

 module.exports =

+
   startConversation: (browser) ->

+    messageUser =
+      userName  : 'devrim'
+      fullName  : 'Devrim Yasar'

     helpers.beginTest(browser)

+    messagesHelpers.startConversation(browser, messageUser)
+    browser.end()
+
+    helpers.beginTest(browser)
+


Spy on editor.save

Spy on editor.save

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -955,9 +955,14 @@
           expect(editor.isModified()).toBeTruthy()

   describe "::saveActivePaneItem()", ->
+    editor = null
+    beforeEach ->
+      waitsForPromise ->
+        atom.workspace.open('sample.js').then (o) -> editor = o
+
     describe "when there is an error", ->
       it "emits a warning notification when the file cannot be saved", ->
-        spyOn(Pane::, 'saveActiveItem').andCallFake ->
+        spyOn(editor, 'save').andCallFake ->
           throw new Error("'/some/file' is a directory")

         atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy()
@@ -966,7 +971,7 @@
         expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'

       it "emits a warning notification when the directory cannot be written to", ->
-        spyOn(Pane::, 'saveActiveItem').andCallFake ->
+        spyOn(editor, 'save').andCallFake ->
           throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'")

         atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy()
@@ -975,7 +980,7 @@
         expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'

       it "emits a warning notification when the user does not have permission", ->
-        spyOn(Pane::, 'saveActiveItem').andCallFake ->
+        spyOn(editor, 'save').andCallFake ->
           error = new Error("EACCES, permission denied '/Some/dir/and-a-file.js'")
           error.code = 'EACCES'
           error.path = '/Some/dir/and-a-file.js'
@@ -987,14 +992,14 @@
         expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'

       it "emits a warning notification when the operation is not permitted", ->
-        spyOn(Pane::, 'saveActiveItem').andCallFake ->
+        spyOn(editor, 'save').andCallFake ->
           error = new Error("EPERM, operation not permitted '/Some/dir/and-a-file.js'")
           error.code = 'EPERM'
           error.path = '/Some/dir/and-a-file.js'
           throw error

       it "emits a warning notification when the file is already open by another app", ->
-        spyOn(Pane::, 'saveActiveItem').andCallFake ->
+        spyOn(editor, 'save').andCallFake ->
           error = new Error("EBUSY, resource busy or locked '/Some/dir/and-a-file.js'")
           error.code = 'EBUSY'
           error.path = '/Some/dir/and-a-file.js'
@@ -1009,7 +1014,7 @@
         expect(notificaiton.getMessage()).toContain 'Unable to save'

       it "emits a warning notification when the file system is read-only", ->
-        spyOn(Pane::, 'saveActiveItem').andCallFake ->
+        spyOn(editor, 'save').andCallFake ->
           error = new Error("EROFS, read-only file system '/Some/dir/and-a-file.js'")
           error.code = 'EROFS'
           error.path = '/Some/dir/and-a-file.js'
@@ -1024,7 +1029,7 @@
         expect(notification.getMessage()).toContain 'Unable to save'

       it "emits a warning notification when the file cannot be saved", ->
-        spyOn(Pane::, 'saveActiveItem').andCallFake ->
+        spyOn(editor, 'save').andCallFake ->
           throw new Error("no one knows")

         save = -> atom.workspace.saveActivePaneItem()


disable spec until we rewrite component

disable spec until we rewrite component

--- coach/specs/course/enroll-or-login.spec.coffee
+++ coach/specs/course/enroll-or-login.spec.coffee
@@ -8,7 +8,7 @@

 describe 'EnrollOrLogin Component', ->

-  it 'renders actions', ->
+  xit 'renders actions', ->
     Testing.renderComponent( EnrollOrLogin, props: {} ).then ({dom}) ->
       expect(dom.textContent).to.include 'Sign up for Concept Coach'
       expect(dom.textContent).to.include 'No enrollment code? Contact your instructor'


renaming map_label_values as render_nested_attributes_template

--- app/assets/javascripts/admin/stores.js.coffee
+++ app/assets/javascripts/admin/stores.js.coffee
@@ -64,9 +64,7 @@
         unless gon.authenticated is false
             fetch_user_photos(render_user_photo_feed, { product_slug: data.slug })

-    # build fields_for instagram_tag, color and size
-    # i.e. size: small,medium,large
-    map_label_values = (name, $context) ->
+    render_nested_attribute_template = (name, $context) ->
         container = $('<div />').attr('class', 'hide')
         ($ ".label-#{name}").each (i, label) ->
             data =
@@ -338,7 +336,8 @@
                 ($ @).append(container)

                 # build nested attribute inputs for instagram_tag, color and size
-                map_label_values(input, ($ @)) for input in multiple_value_inputs
+                for input in multiple_value_inputs
+                    render_nested_attribute_template(input, ($ @))

                 verb = if @id is 'new_product' then 'created' else 'updated'


Fix usage of course url

Fix usage of course url

--- app/assets/javascripts/components/course.cjsx
+++ app/assets/javascripts/components/course.cjsx
@@ -109,7 +109,7 @@

Your course has been published! Students may enroll in the course by visiting the following URL:

-
{@state.course.url + "?enroll=" + @state.course.passcode}

+
{@_courseLinkParams() + "?enroll=" + @state.course.passcode}

@@ -128,16 +128,20 @@
       if @getCurrentUser().id?
         enroll_card = (

-            <svg className="close" tabIndex="0" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" style={{"fill":"currentcolor", "verticalAlign": "middle", "width":"32px", "height":"32px"}}><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"></path></g></svg>
+
+              <svg className="close" tabIndex="0" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" style={{"fill":"currentcolor", "verticalAlign": "middle", "width":"32px", "height":"32px"}}><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"></path></g></svg>
+

Join '{@state.course.title}'?

             Join
-            Cancel
+            Cancel

         )
       else
         enroll_card = (

-            <svg className="close" tabIndex="0" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" style={{"fill":"currentcolor", "verticalAlign": "middle", "width":"32px", "height":"32px"}}><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"></path></g></svg>
+
+              <svg className="close" tabIndex="0" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" style={{"fill":"currentcolor", "verticalAlign": "middle", "width":"32px", "height":"32px"}}><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"></path></g></svg>
+

Hello,

               You’ve been invited to join {@state.course.title}. To join the course, you need to log in with a Wikipedia account.
@@ -152,7 +156,9 @@
     else if @props.location.query.enrolled
       enroll_card = (

-          <svg className="close" tabIndex="0" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" style={{"fill":"currentcolor", "verticalAlign": "middle", "width":"32px", "height":"32px"}}><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"></path></g></svg>
+
+            <svg className="close" tabIndex="0" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" style={{"fill":"currentcolor", "verticalAlign": "middle", "width":"32px", "height":"32px"}}><g><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"></path></g></svg>
+

Welcome!

You’ve successfully joined {@state.course.title}.


Fix sort order

Fix sort order

--- SingularityUI/app/models/TaskHistory.coffee
+++ SingularityUI/app/models/TaskHistory.coffee
@@ -29,7 +29,6 @@
                 taskHistory.task.isStopped = true

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

         # Construct mesos logs link
         taskHistory.mesosMasterLogsLink = "http://#{ app.state.get('masterLogsDomain') }/#/slaves/#{ taskHistory.task.offer.slaveId.value }/browse?path=#{ taskHistory.directory }"


Clean console.log

Clean console.log

--- app/assets/javascripts/georgia/tags.js.coffee
+++ app/assets/javascripts/georgia/tags.js.coffee
@@ -18,7 +18,6 @@

   search: (query='') ->
     if query?
-      console.log query
       $.ajax(
         type: 'GET'
         url: '/api/tags/search'


Change hintMarkers to @hintMarkers.

Change hintMarkers to @hintMarkers.

hintMarkers was previously passed around (within the link-hints mode
class) from function to function.  With #2048 (global link hints), it is
better if this becomes @hintMarkers (that is, is available directly to
all methods within the class.

With #2048, we used bind to avoid having to do this - to keep the diff
clearer.  Here, we do it directly.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -125,9 +125,9 @@
       HUD.showForDuration "No links to select.", 2000
       return

-    hintMarkers = (@createMarkerFor desc for desc in hintDescriptors)
+    @hintMarkers = (@createMarkerFor desc for desc in hintDescriptors)
     @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints)
-    @markerMatcher.fillInMarkers hintMarkers
+    @markerMatcher.fillInMarkers @hintMarkers

     @hintMode = new Mode
       name: "hint/#{mode.name}"
@@ -137,8 +137,8 @@
       suppressTrailingKeyEvents: true
       exitOnEscape: true
       exitOnClick: true
-      keydown: @onKeyDownInMode.bind this, hintMarkers
-      keypress: @onKeyPressInMode.bind this, hintMarkers
+      keydown: @onKeyDownInMode.bind this
+      keypress: @onKeyPressInMode.bind this

     @hintMode.onExit (event) =>
       HintCoordinator.sendMessage "deactivate" if event?.type == "click" or (event?.type == "keydown" and
@@ -148,10 +148,9 @@

     # Note(philc): Append these markers as top level children instead of as child nodes to the link itself,
     # because some clickable elements cannot contain children, e.g. submit buttons.
-    @hintMarkerContainingDiv = DomUtils.addElementList hintMarkers,
+    @hintMarkerContainingDiv = DomUtils.addElementList @hintMarkers,
       id: "vimiumHintMarkerContainer", className: "vimiumReset"
-    @hideMarker hintMarker for hintMarker in hintMarkers when hintMarker.hintDescriptor.frameId != frameId
-    @updateKeyState = @updateKeyState.bind this, hintMarkers # TODO(smblott): This can be refactored out.
+    @hideMarker hintMarker for hintMarker in @hintMarkers when hintMarker.hintDescriptor.frameId != frameId

   setOpenLinkMode: (@mode, shouldPropagateToOtherFrames = true) ->
     @hintMode.setIndicator @mode.indicator if windowIsFocused()
@@ -179,7 +178,7 @@
       marker

   # Handles <Shift> and <Ctrl>.
-  onKeyDownInMode: (hintMarkers, event) ->
+  onKeyDownInMode: (event) ->
     return if event.repeat
     @keydownKeyChar = KeyboardUtils.getKeyChar(event).toLowerCase()

@@ -212,7 +211,7 @@

     else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
       if @markerMatcher.popKeyChar()
-        @updateVisibleMarkers hintMarkers
+        @updateVisibleMarkers()
       else
         # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and
         # knows not to restart hints mode.
@@ -224,7 +223,7 @@

     else if event.keyCode == keyCodes.tab
       @tabCount = previousTabCount + (if event.shiftKey then -1 else 1)
-      @updateVisibleMarkers hintMarkers, @tabCount
+      @updateVisibleMarkers @tabCount

     else
       return
@@ -233,31 +232,31 @@
     DomUtils.suppressEvent event

   # Handles normal input.
-  onKeyPressInMode: (hintMarkers, event) ->
+  onKeyPressInMode: (event) ->
     return if event.repeat

     keyChar = String.fromCharCode(event.charCode).toLowerCase()
     if keyChar
       @markerMatcher.pushKeyChar keyChar, @keydownKeyChar
-      @updateVisibleMarkers hintMarkers
+      @updateVisibleMarkers()

     # We've handled the event, so suppress it.
     DomUtils.suppressEvent event

-  updateVisibleMarkers: (hintMarkers, tabCount = 0) ->
+  updateVisibleMarkers: (tabCount = 0) ->
     {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher
     HintCoordinator.sendMessage "updateKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}

-  updateKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
+  updateKeyState: ({hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
     extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue}

-    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount
+    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints @hintMarkers, tabCount
     if linksMatched.length == 0
       @deactivateMode()
     else if linksMatched.length == 1
       @activateLink linksMatched[0], userMightOverType ? false
     else
-      @hideMarker marker for marker in hintMarkers
+      @hideMarker marker for marker in @hintMarkers
       @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched

   # When only one hint remains, activate it in the appropriate way.  The current frame may or may not contain


Keep subsidy rate values floats

Keep subsidy rate values floats

--- app/assets/javascripts/price_policy.js.coffee
+++ app/assets/javascripts/price_policy.js.coffee
@@ -42,7 +42,7 @@
     if isFiniteAndPositive(usageAdjustment) then usageAdjustment else 0

   setUsageSubsidy = (usageAdjustmentElement, usageSubsidy)->
-    $(usageAdjustmentElement).parents("tr").find("span.minimum_cost").data("usageSubsidy", usageSubsidy.toFixed(2))
+    $(usageAdjustmentElement).parents("tr").find("span.minimum_cost").data("usageSubsidy", usageSubsidy)

   refreshCosts = ->
     $(".master_minimum_cost").each (index, element)-> setInternalCost element


Better indicator message.

Better indicator message.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -67,7 +67,7 @@
     @suppressKeyboardEvents = new SuppressAllKeyboardEvents
       name: "link-hints/suppress-keyboard-events"
       singleton: "link-hints-mode"
-      indicator: "Launching hints..."
+      indicator: "Collecting hints..."
       exitOnEscape: true
     @onExit = [onExit]
     @sendMessage "prepareToActivateMode", modeIndex: availableModes.indexOf mode


disable custom select on touch devices

disable custom select on touch devices

--- app/assets/javascripts/reimagine2/base.coffee
+++ app/assets/javascripts/reimagine2/base.coffee
@@ -1,6 +1,14 @@
 class Reimagine2
   constructor: ->
+    @configure()
+
     unless $('html').hasClass('lt-ie9')
       $(document).foundation()

+  configure: ->
+    if Modernizr?.touch
+      # disable custom select on touch devices
+      # https://github.com/zurb/foundation/issues/2090#issuecomment-23055697
+      $('form.custom select').attr('data-customforms', 'disabled')
+
 @Reimagine2 = Reimagine2


Organize ghost-text-spec.

Organize ghost-text-spec.

--- spec/ghost-text-view-spec.coffee
+++ spec/ghost-text-view-spec.coffee
@@ -16,51 +16,89 @@
 {$} = require 'space-pen'
 HackyGhostView = require '../lib/hacky-ghost-text-view'

+# TODO: move this to fixtures.
+predictions = require './fixtures/predictions.coffee'
+
 describe "HackyGhostView", ->
-  [$editor, ghostView] = []
+  [editor, $editor, ghostView, pList] = []

   beforeEach ->
     atom.workspaceView = workspaceView = new WorkspaceView
     atom.workspaceView.attachToDom()
+
+    pList = new PredictionList

     waitsForPromise ->
       atom.packages.activatePackage('gamboge')

     runs ->
       atom.workspaceView.simulateDomAttachment()
-      $editor = atom.getActiveTextEditor().getView()
-      ghostView = new HackyGhostView($($editor))
+      editor = atom.getActiveTextEditor()
+      $editor = editor.getView()

   describe '::constructor()', ->
-    [pList] = []
-    beforeEach ->
-      pList = new PredictionList
-
     it 'subscribes to PredictionList events', ->
       spyOn pList, 'onDidChangePredictions'

+      new HackyGhostView(pList, $editor)
+
+      expect(plist.onDidChangePredictions.length).toBe 1
+
+
   describe '::setAt()', ->
-    it 'displays ghost text at the end of the line'
-    # Not implemented
+    beforeEach ->
+      ghostView = new HackyGhostView(pList, $editor)
+
+    it 'displays ghost text at the end of the line', ->
+      $editor.getModel().setText('if __name__ ')
+      pList.setPredictions predictions.varied
+
+      ghostView.setAt([0, 0])
+
+    # Not implemented: v0.2.0-prerelease.
     it 'displays ghost text in the middle of the line'
+
+    # TODO: should ghost-view set this, or should editor-spy?
+    it 'sets the .gamboge class on the editor', ->
+      expect($editor).toBeMatchedBy false
+

   describe '::removeAll()', ->
     beforeEach ->
-      $editor.addClass 'gamboge'
+      pList.setPredictions predictions.varied
+      ghostView.setAt([0,0])

-    it 'removes all ghost-view spans and any prediction text from the editor'
+    it 'removes all gamboge-ghost spans and any prediction text from the editor', ->
+      # Set the prediction.
+      expect($editor).toContain '.gamboge-ghost'
+
+      ghostView.removeAll()
+      expect($editor).not.toContain '.gamboge-ghost'
+      expect($editor).not.toContain '.gamboge-invisible'
+
     it 'removes the `gamboge` class from the editor', ->
+      expect($editor).toHaveClass 'gamboge'
+
       ghostView.removeAll()
-      expect($editor.hasClass 'gamboge').toBe false
+      expect($editor).not.toHaveClass 'gamboge'

-  describe 'when a prediction is active', ->
-    it 'wraps whitespace with span of class `gamboge-whitespace`'
-    it 'inserts the correct visible whitespace tokens'
-    # Do tests for special characters!
-    it 'adds the `gamboge` class to the editor'
-  describe 'when the PredictionList changes', ->
-    it 'displays the current prediction', ->
-    it 'keeps the `gamboge` class on the editor'
-  describe 'when the prediction is deactived', ->
-    it 'removes the `gamboge` class'
+  describe 'when PredictionList triggers an event', ->
+    beforeEach ->
+      ghostView = new HackyGhostView(pList, $editor)

+    describe 'when a prediction is active', ->
+      it 'wraps whitespace with span of class `gamboge-whitespace`', ->
+        expect($editor)
+
+      it 'inserts the correct visible whitespace tokens', ->
+
+      # Do tests for special characters!
+      it 'adds the `gamboge` class to the editor'
+
+    describe 'when the PredictionList changes', ->
+      it 'displays the current prediction', ->
+      it 'keeps the `gamboge` class on the editor'
+
+    describe 'when the prediction is deactived', ->
+      it 'removes the `gamboge` class'
+


refactor, removed unnecessary comment

refactor, removed unnecessary comment

--- lib/atom-ternjs-view.coffee
+++ lib/atom-ternjs-view.coffee
@@ -96,12 +96,10 @@
       @width(@list.outerWidth())

   startCompletion: (completions)  ->
-    @setItems completions
-    if !@hasParent()
-      # atom.workspaceView.append(this)
-      @editorView = atom.workspaceView.getActivePaneView().activeView
-      @editorView?.appendToLinesView(this)
-      @editor = @editorView?.getEditor()
-      @setPosition()
-      @handleEvents()
-      @focusFilterEditor()
+    @setItems(completions)
+    @editorView = atom.workspaceView.getActivePaneView().activeView
+    @editorView?.appendToLinesView(this)
+    @editor = @editorView?.getEditor()
+    @setPosition()
+    @handleEvents()
+    @focusFilterEditor()


remove link after delete url

remove link after delete url

--- lib/insert-link-view.coffee
+++ lib/insert-link-view.coffee
@@ -5,7 +5,6 @@
 fs = require "fs-plus"

 ### TODO
-- Able to remove existing link
 - Support link with id
 ###

@@ -36,7 +35,7 @@

   initialize: ->
     @fetchPosts()
-    @loadLinks()
+    @loadSavedLinks()
     @handleEvents()
     @on "core:confirm", => @onConfirm()
     @on "core:cancel", => @detach()
@@ -47,10 +46,10 @@

   onConfirm: ->
     text = @textEditor.getText()
+    url = @urlEditor.getText()
     title = @titleEditor.getText()
-    url = @urlEditor.getText()
-    @insertLinkToEditor(text, title, url)
-    @saveLink(text, title, url) if @saveCheckbox.prop("checked")
+    if url then @insertLink(text, title, url) else @removeLink(text)
+    @updateSavedLink(text, title, url)
     @detach()

   display: ->
@@ -60,6 +59,7 @@
     atom.workspaceView.append(this)
     @searchBox.hide()
     if @textEditor.getText()
+      @urlEditor.getEditor().selectAll()
       @urlEditor.focus()
     else
       @textEditor.focus()
@@ -76,10 +76,13 @@
     if utils.isLink(selection)
       link = utils.parseLink(selection)
       @setLink(link.text, link.url, link.title)
+      @saveCheckbox.prop("checked", true) unless @isChangedSavedLink(link)
     else if @getSavedLink(selection)
       link = @getSavedLink(selection)
       @setLink(selection, link.url, link.title)
       @saveCheckbox.prop("checked", true)
+    else
+      @setLink(selection, "", "")

   updateSearch: ->
     return unless @posts
@@ -93,30 +96,40 @@
     @titleEditor.setText(e.target.textContent)
     @urlEditor.setText(e.target.dataset.url)

-  insertLinkToEditor: (text, title, url) ->
+  insertLink: (text, title, url) ->
     if title
       @editor.insertText("[#{text}](#{url} '#{title}')")
     else
       @editor.insertText("[#{text}](#{url})")

-  getSavedLink: (text) ->
-    @links?[text.toLowerCase()]
+  removeLink: (text) ->
+    @editor.insertText(text)

   setLink: (text, url, title) ->
     @textEditor.setText(text)
     @urlEditor.setText(url)
     @titleEditor.setText(title)

-  saveLink: (text, title, url) ->
+  getSavedLink: (text) ->
+    @links?[text.toLowerCase()]
+
+  isChangedSavedLink: (link) ->
+    savedLink = @getSavedLink(link.text)
+    savedLink and (savedLink.title != link.title or savedLink.url != link.url)
+
+  updateSavedLink: (text, title, url) ->
     try
-      @links[text.toLowerCase()] = title: title, url: url
-      CSON.writeFileSync(@getLinkPath(), @links)
+      if @saveCheckbox.prop("checked")
+        @links[text.toLowerCase()] = title: title, url: url if url
+      else if not @isChangedSavedLink(text: text, title: title, url: url)
+        delete @links[text.toLowerCase()]
+      CSON.writeFileSync(@getSavedLinksPath(), @links)
     catch error
       console.log(error.message)

-  loadLinks: ->
+  loadSavedLinks: ->
     try
-      file = @getLinkPath()
+      file = @getSavedLinksPath()
       if fs.existsSync(file)
         @links = CSON.readFileSync(file)
       else
@@ -124,7 +137,7 @@
     catch error
       console.log(error.message)

-  getLinkPath: ->
+  getSavedLinksPath: ->
     atom.project.resolve(atom.config.get("md-writer.siteLinkPath"))

   fetchPosts: ->


List files that match *.coffee

List files that match *.coffee

--- quidoc.coffee
+++ quidoc.coffee
@@ -10,13 +10,16 @@
 fs = require "fs"
 path = require "path"

-# Walk the tree whose root is the given path, listing all files.
-ls = ( directory ) ->
-  console.log ""
+# Walk the tree whose root is the given path, listing all files. If a regex is
+# supplied, only names matching that expression are listed.
+ls = ( directory, regex ) ->
+  console?.log ""
   console?.log directory + ":"
   files = fs.readdirSync directory
   for file in files
-    console?.log file
+    match = regex is undefined or regex.test( file )
+    if match
+      console?.log file
     filePath = path.join directory, file
     stats = fs.statSync filePath
     if stats.isDirectory()
@@ -24,4 +27,4 @@

 args = process.argv.splice 2 # Ignore "node" and script name args
 root = if args[0]? then path.resolve args[0] else process.cwd()
-ls root
+ls root, /\.coffee$/


Reducing limits a little more

Reducing limits a little more

--- tests/www/run.coffee
+++ tests/www/run.coffee
@@ -7,7 +7,7 @@

 exports.test = ( name, conf, base_url, mark_as_passed )->

-  timeout = if name is 'local' then 5000 else 60000
+  timeout = if name is 'local' then 5000 else 15000

   describe name, ->


remove unused auto_ranging property

remove unused auto_ranging property

--- bokehjs/src/coffee/range/data_range1d.coffee
+++ bokehjs/src/coffee/range/data_range1d.coffee
@@ -19,9 +19,6 @@
   _set_end: (end) ->
     @set('_end', end)

-  _get_auto_ranging: () ->
-    return not get('_start') and not get('_end')
-
   initialize: (attrs, options) ->
     @register_property('start', @_get_start, true)
     @register_setter('start', @_set_start)
@@ -34,9 +31,6 @@
     @add_dependencies('end', this, [
       '_end', 'flipped', '_auto_end', 'range_padding', 'default_span'
     ])
-
-    @register_property('auto_ranging', @_get_auto_ranging, true)
-    @add_dependencies('start', this, ['_start', '_end'])

     if attrs.start?
       @set('start', attrs.start)


code: incomplete, not working

code: incomplete, not working

--- 3d/tubing.coffee
+++ 3d/tubing.coffee
@@ -44,6 +44,14 @@

     # x, y pairs of the tube cross-section
     @tubeShape = nGonVertices (options.tubeSides ? 3), (options.tubeRadius ? 0.1)
+
+    # Normals from the previous call
+    @prevNormals = new Float32Array @nCells*3
+    for i in [0...@nCells*3] by 3
+      @prevNormals[i] = 1
+      @prevNormals[i+1] = 0
+      @prevNormals[i+2] = 0
+    return

   #to make chunk of N segments, we need n+3 states.
   # before-first, first, ... , last, after-last.
@@ -107,6 +115,10 @@

     r = @tubeRadius

+    x_pn=@prevNormals[tubeIndex/2]
+    y_pn=@prevNormals[tubeIndex/2+1]
+    z_pn=@prevNormals[tubeIndex/2+2]
+
     #x0 = xys[0][i]
     #y0 = xys[0][i]
     dz = @stepZ
@@ -119,13 +131,33 @@
       x=xy[tubeIndex]
       y=xy[tubeIndex+1]

-      #vectr from the previous point to this
+      #(dx,dy,dz) is an approximate tangent
       dx = xyNext[tubeIndex] - xyPrev[tubeIndex]
       dy = xyNext[tubeIndex+1] - xyPrev[tubeIndex+1] #y-y0
       #dz = @stepZ
+      iqxyz2 = 1.0 / ( dx*dx+dy*dy+dz*dz)
+      iqxyz = 1.0 / Math.sqrt( iqxyz2 ) #normalizing K

       #x0 = x
       #y0 = y
+
+      #main normal is calculated by projecting the last normal
+      # pn X tangent:
+      tan = (x_pn*dx + y_pn*dy + z_pn*dz) * iqxyz2 #tangent component, scaled by iqxyz
+      xn1 = x_pn - tan * dx
+      yn1 = y_pn - tan * dy
+      zn1 = z_pn - tan * dz
+      #now normalize it
+      inorm1 = 1.0/Math.sqrt(xn1*xn1+yn1*yn1+zn1*zn1)
+      if isFinite inorm1
+        xn1 *= inorm1
+        yn1 *= inorm1
+        zn1 *= inorm1
+      else
+        xn1=1.0
+        yn1=0.0
+        zn1=0.0
+

       #compute normals
       # noraml of form
@@ -135,21 +167,20 @@
       # xn1 =   dx / sqrt(dx^2+dy^2)
       # yn1 = - dy / sqrt(dx^2+dy^2)

-      qxy = Math.sqrt( dx*dx+dy*dy)
+      #qxy = Math.sqrt( dx*dx+dy*dy)
       #zn1 = 0
-      if qxy < 1e-3
-        xn1 = 1.0
-        yn1 = 0.0
-      else
-        iqxy = 1 / qxy
-        xn1 = dy * iqxy
-        yn1 = -dx * iqxy
+      #if qxy < 1e-3
+      #  xn1 = 1.0
+      #  yn1 = 0.0
+      #else
+      #  iqxy = 1 / qxy
+      #  xn1 = dy * iqxy
+      #  yn1 = -dx * iqxy
       #now calculate the third vector, as a X-product of
-      # (xn1, yn1, 0) X (dx, dy, dz)
+      # (xn1, yn1, zn1) X (dx, dy, dz)
       # -dz*yn1*i +dz*xn1*j+ k*(dx*yn1-dy*xn1)
-      iqxyz = 1.0 / Math.sqrt( dx*dx+dy*dy+dz*dz)
-      xn2 = -dz * iqxyz * yn1
-      yn2 =  dz * iqxyz * xn1
+      xn2 = (dy*zn1-dz*yn1) * iqxyz
+      yn2 = (dz*xn1-dx*zn1) * iqxyz
       zn2 = (dx*yn1-dy*xn1) * iqxyz

       vindex = curV / 3 | 0
@@ -159,13 +190,20 @@
       for i in [0 ... shape.length] by 2
         vx = shape[i]
         vy = shape[i+1]
-        pushXYZ x+xn1*vx+xn2*vy, y+yn1*vx+yn2*vy, z+zn2*vy #+zn1*vx
-
+        pushXYZ x+xn1*vx+xn2*vy, y+yn1*vx+yn2*vy, z+zn2*vy+zn1*vx
+
+      x_pn = xn1
+      y_pn = yn1
+      z_pn = zn1
+
       if iz > 1 and (Math.abs(xyPrev[tubeIndex] - x)+Math.abs(xyPrev[tubeIndex+1] - y) < jumpTreshold)
         for j in [0...tubeEdges] by 1
           j1 = (j+1)%tubeEdges
           pushQuad vindex-tubeEdges+j, vindex+j, vindex-tubeEdges+j1, vindex+j1

+    @prevNormals[tubeIndex/2] = x_pn
+    @prevNormals[tubeIndex/2+1] = y_pn
+    @prevNormals[tubeIndex/2+2] = z_pn
     if curV isnt vs.length then throw new Error "Not all vertices filled"

     if curIx isnt ixs.length


Don't write content if there isn't any

Don't write content if there isn't any

--- src/runtimes/iframe.coffee
+++ src/runtimes/iframe.coffee
@@ -49,7 +49,7 @@
     body = @iframe.contentDocument.querySelector 'body'
     return unless body
     env = @graph.properties.environment
-    return unless env or env.content
+    return if !env or !env.content
     body.innerHTML = env.content

   disconnect: ->


OPCE6: remove useless console.log

OPCE6: remove useless console.log

--- test/rte-test.coffee
+++ test/rte-test.coffee
@@ -114,7 +114,6 @@
       { retain: 7, attributes: {bold: true } }]}
     rte1 = new Rte "Gandalf the Grey"
     rte1.delta delta
-    console.log rte1.getWord(0)

 describe 'Selection object should', ->
   sel = sel2 = rte = null


Remove extra default template call in test.

--- test/template.coffee
+++ test/template.coffee
@@ -51,7 +51,6 @@
       Roots.template.add(name: test_tpl_name, uri: test_tpl_path)
         .then(-> Roots.template.default(name: test_tpl_name))
         .then(-> Roots.template.remove(name: test_tpl_name))
-        .then(-> Roots.template.default(name: 'roots-base'))
         .should.be.fulfilled

   describe 'reset', ->


add todo-show to config.cson

add todo-show to config.cson

--- Atom/config.cson
+++ Atom/config.cson
@@ -37,3 +37,9 @@
     maxRecentDirectories: 10
   "pigments":
     markerType: "dot"
+  "todo-show":
+    sortBy: "File"
+    showInTable: [
+      "File"
+      "Text"
+    ]


GameObject.Effect

GameObject.Effect

--- test/tweening.coffee
+++ test/tweening.coffee
@@ -4,16 +4,15 @@
   o = GameObject
     x: 0

-
   targetValue = 10
   o.tween 10,
     x: targetValue
-
+
   11.times (i) ->
     o.update(1)
     o.trigger "afterUpdate", 1

-    equals o.I.x, Math.min(i + 1, 10)
+    equals o.I.x, Math.min(i + 1, targetValue)

 # Clear out the module
 module()


improvements for format

improvements for format

--- tools/strings.coffee
+++ tools/strings.coffee
@@ -17,8 +17,10 @@

     ##
     format: (format, parts...)->
+
+        ## some hacks to make sure the format string is really a string ;-)
         if typeof format is 'object'
-            format = floyd.tools.objects.serialize format
+            format = floyd.tools.objects.inspect format

         if parts.length == 1 && floyd.tools.objects.isArray parts[0]
             parts = parts[0]
@@ -26,12 +28,13 @@
         if parts.length
             for i in [0..parts.length-1]
                 if typeof parts[i] is 'object'
-                    parts[i] = floyd.tools.objects.serialize parts[i]
-
-            if format.match
-                match = format.match /%[^%\s]/g
-            else
+                    parts[i] = floyd.tools.objects.inspect parts[i]
+
+            if typeof format isnt 'string'
                 format = format.toString()
+
+            match = format.match /%[^%\s]/g
+
             size = if match then match.length else 0
             length = parts.length - size


ComputeProvider: carry generatedFrom data into JMachine

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -80,7 +80,7 @@

   , (client, options, callback)->

-    { provider, stack, label, provisioners, users } = options
+    { provider, stack, label, provisioners, users, generatedFrom } = options
     { r: { group, user, account } } = client

     provider.create client, options, (err, machineData)->
@@ -93,7 +93,7 @@

       JMachine.create {
         provider : provider.slug
-        label, meta, group, user
+        label, meta, group, user, generatedFrom
         users, credential, provisioners
       }, (err, machine)->

@@ -133,6 +133,9 @@
       # Reset it here if someone tries to put users
       # from client side request
       options.users = []
+
+      # Remove generatedFrom option if provided
+      delete options.generatedFrom

       @create client, options, callback


Teamwork: Fixes for system message checker method.

--- client/Teamwork/Views/teamworkchatpane.coffee
+++ client/Teamwork/Views/teamworkchatpane.coffee
@@ -57,9 +57,10 @@

     return originUser is KD.nick() and isSystemMessage

-  isSystemMessage: (message = "") ->
-    [key] = message.split " "
-    return replyHandlers[key] isnt undefined
+  isSystemMessage: (message) ->
+    return  unless message
+    splitted = message.split " "
+    return splitted.length is 2 and replyHandlers[splitted.first] isnt undefined

   runSystemMessageHandler: (message) ->
     splitted = message.split " "
@@ -80,12 +81,13 @@
       @botReply getMessage "validateKey", sessionKey
       @workspace.firebaseRef.child(sessionKey).once "value", (snapshot) =>
         if snapshot.val() is null or not snapshot.val().keys
-          @botReply message.noSession
+          @botReply messages.noSession
         else
           @botReply getMessage "joinSession", sessionKey
           @workspace.getDelegate().emit "JoinSessionRequested", sessionKey
     else
       # TODO: fatihacet - implement getting the sessionKey via username
+      @botReply messages.invalidKey

   replyForInvite: (usernames) ->
     # TODO: fatihacet - currently invite only first user
@@ -185,6 +187,7 @@
     validateKey   : "01001101 ... I am checking this session key, $0."
     joinSession   : "01001010 ... Joining session, $0"
     noSession     : "Sorry, looks like this session is closed by its host, I cannot get you in."
+    invalidKey    : "Sorry, looks like this session key is not valid anymore."
     help          : """
         If you type,
         "invite username" I can bring someone to your session,


No need to call this in constructor, it causes duplicate list of admin stuff

--- client/app/MainApp/sidebar/sidebarview.coffee
+++ client/app/MainApp/sidebar/sidebarview.coffee
@@ -67,7 +67,7 @@

     @adminNav = @adminNavController.getView()

-    @resetAdminNavController()
+    # @resetAdminNavController()

     @finderHeader = new KDCustomHTMLView
       tagName   : "h2"


Collaboration: Existence check

Collaboration: Existence check

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -745,7 +745,7 @@
     @rtm.once 'FileDeleted', =>
       @statusBar.emit 'CollaborationEnded'
       @stopChatSession()
-      @modal.destroy()
+      @modal?.destroy()

       if @amIHost
         @setMachineSharingStatus off, (err) =>


collaboration: assign rtm to panes on collab active

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -648,6 +648,9 @@
     @bindRealtimeEvents()
     @bindSocialChannelEvents()

+    # attach RTM instance to already in-screen panes.
+    @forEachSubViewInIDEViews_ @bound 'setRealtimeManager'
+

   transitionViewsToActive: ->


Fixed Blog Posts Categorization on List View

The previous code on fixed the issue on Chrome, but made the Firefox and Safari one broke.
This update fixed the issue on all three browsers.

--- assets/coffee/apps/post/list/list_view.coffee
+++ assets/coffee/apps/post/list/list_view.coffee
@@ -5,9 +5,9 @@
     tagName: "tr"

     attributes: ->
-      if @model.get("active") is 1 and @model.get("publish_date") > moment().format('YYYY-MM-DD HH:mm:ss')
+      if String(@model.get("active")) is 1 and @model.get("publish_date") > moment().format('YYYY-MM-DD HH:mm:ss')
         class: "post-item scheduled post-#{@model.id}"
-      else if @model.get("active") is 1
+      else if String(@model.get("active")) is "1"
         class: "post-item published post-#{@model.id}"
       else
         class: "post-item draft post-#{@model.id}"


Increase timeout for socket in onlogout handler

--- assets/js/intertwinkles/authentication.coffee
+++ assets/js/intertwinkles/authentication.coffee
@@ -72,7 +72,7 @@
   intertwinkles.users = null
   intertwinkles.groups = null
   if not intertwinkles.socket?
-    if count > 15
+    if count > 200
       return alert("Socket connection failed.")
     count ?= 0
     console.log "onlogout awaiting socket #{count}..."


Set them in the fuction? D:

Set them in the fuction? D:

--- avalonstar/assets/javascripts/notifier.coffee
+++ avalonstar/assets/javascripts/notifier.coffee
@@ -11,9 +11,7 @@

 # Sounds.
 soundDonation = new Audio('/static/audio/donation.ogg')
-soundDonation.volume = 0.25
 soundSubscription = new Audio('/static/audio/subscription.ogg')
-soundSubscription.volume = 0.25

 subscribed = (data, added) ->
   if not running
@@ -21,6 +19,7 @@
     console.log "#{data.username} has subscribed!"

     # Play the subscription beat!
+    soundSubscription.volume = 0.25
     soundSubscription.play()

     # Add the .visible class to .js-subscribed to kick off the animation set.
@@ -57,6 +56,7 @@
     console.log "#{data.nickname} has donated #{data.amount}!"

     # Play the donation beat!
+    soundDonation.volume = 0.25
     soundDonation.play()

     # Add the .visible class to .js-subscribed to kick off the animation set.


Troubleshoot: disable vmController troubleshoot checks

--- client/Main/troubleshoot/troubleshoot.coffee
+++ client/Main/troubleshoot/troubleshoot.coffee
@@ -67,10 +67,10 @@
           KD.toggleKiteStack()

     # register osKite
-    vc = KD.singleton "vmController"
-    @registerItem "osKite",
-      troubleshoot : vc.bound 'ping'
-      recover      : vc.bound 'ping'
+    # vc = KD.singleton "vmController"
+    # @registerItem "osKite",
+    #   troubleshoot : vc.bound 'ping'
+    #   recover      : vc.bound 'ping'

     # register bongo
     KD.remote.once "modelsReady", =>
@@ -89,13 +89,13 @@
       speedCheck   : no
       troubleshoot : checkVersion.bind this

-    vmChecker = new VMChecker
-    @registerItem "vm",
-      speedCheck   : no
-      troubleshoot : vmChecker.bound 'healthCheck'
-
-    @registerItem "terminal",
-      troubleshoot : vmChecker.bound 'terminalHealthCheck'
+    # vmChecker = new VMChecker
+    # @registerItem "vm",
+    #   speedCheck   : no
+    #   troubleshoot : vmChecker.bound 'healthCheck'
+
+    # @registerItem "terminal",
+    #   troubleshoot : vmChecker.bound 'terminalHealthCheck'

   registerConnections: ->
     #register connection


Added AppDidQuit event to ApplicationManager.

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -193,6 +193,7 @@
     appView = appInstance.getView?()
     appView?.once "KDObjectWillBeDestroyed", =>
       @unregister appInstance
+      appInstance.emit "AppDidQuit"


notification: listen 'social' events

notification: listen 'social' events

--- client/app/lib/notificationcontroller.coffee
+++ client/app/lib/notificationcontroller.coffee
@@ -56,6 +56,11 @@
         # TODO enable this with team product
         # event = unless context is getGroup().slug then "#{event}-off-context" else event
         # event is mainly -> ChannelUpdateHappened // do not delete this line. here for search purposes. - SY
+        @emit event, contents  if event
+
+      @notificationChannel.on 'social', (notification) =>
+        { contents, context, event } = notification
+
         @emit event, contents  if event


Collaboration: Simplify collaborative string event handling

--- client/ide/workspace/panes/editorpane.coffee
+++ client/ide/workspace/panes/editorpane.coffee
@@ -223,19 +223,19 @@

     @rtm.bindRealtimeListeners string, 'string'

-    @rtm.on 'TextInsertedIntoString', (changedString, change) =>
-
-      if changedString is string
-        return if @isChangedByMe change
-
-        @applyChange change
-
-    @rtm.on 'TextDeletedFromString', (changedString, change) =>
-
-      if changedString is string
-        return if @isChangedByMe change
-
-        @applyChange change
+    @rtm
+      .on 'TextInsertedIntoString', @bound 'handleCollaborativeStringEvent'
+      .on 'TextDeletedFromString', @bound 'handleCollaborativeStringEvent'
+
+
+  handleCollaborativeStringEvent: (changedString, change) ->
+
+    string = @rtm.getFromModel @getFile().path
+
+    return  unless changedString is string
+    return  if @isChangedByMe change
+
+    @applyChange change

   isChangedByMe: (change) ->


IDE: Get machine from computeController and mount it.

--- client/ide/workspace/panes/finderpane.coffee
+++ client/ide/workspace/panes/finderpane.coffee
@@ -4,16 +4,27 @@

     super options, data

-    appManager = KD.getSingleton 'appManager'
+    appManager  = KD.getSingleton 'appManager'
+    computeCtrl = KD.getSingleton 'computeController'
+    ideApp      = appManager.getFrontApp()

-    appManager.open 'Finder', (finderApp) =>
-      fc = @finderController = finderApp.create
-        addAppTitle   : no
-        treeItemClass : IDE.FinderItem
+    # TODO: 404 - Brain not found.
+    # It should be fixed with computeController.ready but there is a race condition
+    # there but I don't have enough brain to fix it right now.

-      @addSubView fc.getView()
-      @bindListeners()
-      fc.reset()
+    KD.utils.wait 2000, => # computeCtrl.ready =>
+      for machine in computeCtrl.machines when machine.uid is ideApp.mountedMachineUId
+        machineItem  = machine
+
+      appManager.open 'Finder', (finderApp) =>
+        fc = @finderController = finderApp.create
+          addAppTitle   : no
+          treeItemClass : IDE.FinderItem
+          machineToMount: machineItem
+
+        @addSubView fc.getView()
+        @bindListeners()
+        fc.reset()

   bindListeners: ->
     mgr = KD.getSingleton 'appManager'


Store factoids in hubot brain

Store factoids in hubot brain

* store in brain for persistence after restarts.
* refactor logic into Factoids class.

--- src/scripts/factoid.coffee
+++ src/scripts/factoid.coffee
@@ -1,17 +1,32 @@
-factoids = {}
+class Factoids
+  constructor: (@robot) ->
+    @robot.brain.on 'loaded', =>
+      @cache = @robot.brain.data.factoids
+      @cache = {} unless @cache
+
+  add: (key, val) ->
+    @cache[key] = val
+    @robot.brain.data.factoids = @cache
+    "OK."
+
+  get: (key) ->
+    if @cache[key]
+      @cache[key]
+    else
+      "No factoid for #{key}"
+

 module.exports = (robot) ->
-
+  factoids = new Factoids robot
+
   robot.hear /^~(.+) is (.+)/i, (msg) ->
-    factoids[msg.match[1]] = msg.match[2]
-    msg.reply "OK."
+    result = factoids.add msg.match[1], msg.match[2]
+    msg.reply result

   robot.hear /^~(.+)/i, (msg) ->
     key = msg.match[1]
     # don't return factoid if we're adding a factoid.
     if /is .+/.exec key
       return
-    if factoids[key]
-      msg.reply factoids[key]
-    else
-      msg.reply "No factoid for #{key}."
+    result = factoids.get key
+    msg.reply result


`daily harvest` command now takes a username as an argument.

--- src/scripts/harvest.coffee
+++ src/scripts/harvest.coffee
@@ -15,7 +15,7 @@
 #   hubot forget my harvest account <email> - Deletes your account credentials from Hubot's memory
 #   hubot start harvest at <project>: notes - TODO
 #   hubot finish harvest at <project> - TODO
-#   hubot daily harvest - Hubot responds with your entries for today
+#   hubot daily harvest [of <user>] - Hubot responds with your/a specific user's entries for today
 #
 # Author:
 #   Quintus
@@ -40,17 +40,30 @@
     msg.message.user.harvest_account = null
     msg.reply "Okay, I erased your credentials from my memory."

-  robot.respond /daily harvest/i, (msg) ->
-    msg.message.user.harvest_account.daily msg, (status, body) ->
+  # Retrieve your or a specific user's timesheet for today.
+  robot.respond /daily harvest( of (.+))?/i, (msg) ->
+    if msg.match[2]
+      user = robot.userForName(msg.match[2])
+      if user
+        unless user.harvest_account
+          msg.reply "I didn't crack #{user.name}'s Harvest credentials yet, but I'm working on it... Sorry for the inconvenience."
+          return
+      else
+        msg.reply "#{msg.match[2]}? Who's that?"
+        return
+    else
+      user = msg.message.user
+
+    user.harvest_account.daily msg, (status, body) ->
       if 200 <= status <= 299
-        msg.reply "Your entries for today, #{msg.message.user.name}:"
+        msg.reply "Your entries for today, #{user.name}:"
         for entry in body.day_entries
           if entry.ended_at == ""
             msg.reply "* #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [running since #{entry.started_at} (#{entry.hours}h)]"
           else
             msg.reply "* #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} - #{entry.ended_at} (#{entry.hours}h)]"
       else
-        msg.reply "Failed with status #{status}."
+        msg.reply "Request failed with status #{status}."

 # Class managing the Harvest account associated with
 # a user. Keeps track of the user's credentials and can


make both parts of SERVER come from same setting

--- src/scripts/session.coffee
+++ src/scripts/session.coffee
@@ -2,7 +2,7 @@
   Backbone = require('backbone')
   settings = require('settings')

-  SERVER = "#{location.protocol}//#{settings.cnxarchive.host}:#{settings.cnxauthoring.port}"
+  SERVER = "#{location.protocol}//#{settings.cnxauthoring.host}:#{settings.cnxauthoring.port}"
   INTERVAL = 1000 * 60 # 1 minute
   MINIMUM_INTERVAL = 1000 * 5 # 5 seconds


Refactory $watch in address-form

Refactory $watch in address-form

--- app/assets/javascripts/sprangular/directives/addressForm.coffee
+++ app/assets/javascripts/sprangular/directives/addressForm.coffee
@@ -30,7 +30,7 @@
         address.stateId = null
         address.state = null

-    $scope.$watch (-> $scope.address.stateId), (newStateId) ->
+    $scope.$watch 'address.stateId', (newStateId) ->
       return unless newStateId
       state = _.find($scope.selectedCountry.states, (state) -> state.id == newStateId)
       $scope.address.state = state


Fixed warning for existing discussions

Fixed warning for existing discussions

--- app/assets/javascripts/sugar/exchanges/new_discussion.js.coffee
+++ app/assets/javascripts/sugar/exchanges/new_discussion.js.coffee
@@ -20,7 +20,6 @@
             words = $.map(words, (word) ->
               word.replace /[!~\^=\$\*\[\]\{\}]/, ""
             )
-            words[words.length - 1] += "*"  if words[words.length - 1].match(/^[\w]{3}[^\*]*$/)
             query = words.join(" | ")
             $.getJSON "/discussions/search.json",
               query: query
@@ -29,14 +28,10 @@
               $(searchResults).removeClass "loading"
               if json.discussions.length > 0
                 output = "
Similar discussions found. Maybe you should check them out before posting?
"
-                discussion = null
-                a = 0

-                while a < 10
-                  if a < json.discussions.length
-                    discussion = json.discussions[a].discussion
-                    output += "" + discussion.title + " " + discussion.posts_count + " posts"
-                  a += 1
+                for discussion in json.discussions[0...10]
+                  output += "" + discussion.title + " " + discussion.posts_count + " posts"
+
                 output += "Show all " + (json.total_entries) + " results"  if json.total_entries > 10
                 $(searchResults).html(output).hide().slideDown "fast"
               else


adding logging for project watch on restart

--- scripts/jira-linkifier.coffee
+++ scripts/jira-linkifier.coffee
@@ -52,6 +52,7 @@

   projects = robot.brain.get('jira-projects') or []
   for project in projects
+    console.log("Watching project " + project + " after restart")
     watch(robot, project)

 watch = (robot, project) ->


MapLinkView Select fixed

MapLinkView Select fixed

--- src/main/webapp/lib/views/MapLinkView.coffee
+++ src/main/webapp/lib/views/MapLinkView.coffee
@@ -85,9 +85,10 @@
       @model.set_editor_show(true)
       evt.stop()
     )
-    @clickHandler = gme.addListener(@link, 'click', =>
-      $a.broker.trigger('map:clear_selected') # this could also go in the model?
-      @model.toggle_selected()
+    @clickHandler = gme.addListener(@link, 'click', =>
+      selected = @model.selected()
+      $a.broker.trigger('map:clear_selected')
+      @model.set_selected(!selected)
     )
     lambda = (evt) => @_contextMenu(evt)
     @rClickListener = gme.addListener(@link, 'rightclick', lambda)
@@ -207,7 +208,7 @@

   # This method toggles the selection of the link
   toggleSelected: () ->
-    if(@model.selected() is false)
+    if(@model.selected() is true)
       @linkSelect()
     else
       @clearSelected()


move replace:esteLibraryVersion task into run tasks

because we want to update este library version everytime before run app

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -118,17 +118,6 @@
       app:
         files:
           'client/app/build/app.css': 'client/app/css/app.css'
-
-    replace:
-      updateIndexEsteLibraryVersion:
-        src: 'server/app/views/index.jade'
-        overwrite: true
-        replacements: [
-          from: /\(v.+\)/g
-          to: ->
-            version = require('./bower_components/este-library/bower.json').version
-            "(v#{version})"
-        ]

     esteBuilder:
       options:
@@ -174,6 +163,17 @@

     # run tasks

+    replace:
+      esteLibraryVersion:
+        src: 'server/app/views/index.jade'
+        overwrite: true
+        replacements: [
+          from: /\(v.+\)/g
+          to: ->
+            version = require('./bower_components/este-library/bower.json').version
+            "(v#{version})"
+        ]
+
     env:
       development:
         NODE_ENV: 'development'
@@ -256,7 +256,6 @@
   grunt.loadNpmTasks 'grunt-contrib-clean'
   grunt.loadNpmTasks 'grunt-contrib-coffee'
   grunt.loadNpmTasks 'grunt-contrib-cssmin'
-  grunt.loadNpmTasks 'grunt-contrib-jshint'
   grunt.loadNpmTasks 'grunt-contrib-stylus'
   grunt.loadNpmTasks 'grunt-env'
   grunt.loadNpmTasks 'grunt-este'
@@ -278,13 +277,13 @@
     if grunt.option 'stage'
       tasks = tasks.concat [
         "cssmin:#{app}"
-        "replace:updateIndexEsteLibraryVersion"
         "esteBuilder:#{app}"
       ]
     grunt.task.run tasks

   grunt.registerTask 'run', 'Run stack.', (app = 'app') ->
     grunt.task.run [
+      "replace:esteLibraryVersion"
       if grunt.option 'stage' then 'env:stage' else 'env:development'
       "bgShell:#{app}"
       "esteWatch"


per file build for jade templates

per file build for jade templates

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -170,7 +170,6 @@
       js:
         options:
           livereload: true
-          spawn:      true
         files: [
           'lib/**/*.js',
           'blocks/**/*.js',
@@ -203,13 +202,11 @@
             ga:      'UA-XXXXX-X'
             metrika: 'XXXXXXX'
             isDevelopment: true
-        files: [{
-          expand: true       # Enable dynamic expansion.
-          cwd:    'jade'     # Src matches are relative to this path.
-          src:    '*.jade'   # Actual pattern(s) to match.
-          dest:   ''         # Destination path prefix.
-          ext:    '.html'    # Dest filepaths will have this extension.
-        }]
+        expand: true       # Enable dynamic expansion.
+        cwd:    'jade/'    # Src matches are relative to this path.
+        src:    '*.jade'   # Actual pattern(s) to match.
+        dest:   ''         # Destination path prefix.
+        ext:    '.html'    # Dest filepaths will have this extension.

       publish:
         options:
@@ -230,34 +227,34 @@
           'i-mixins/i-mixins__vendor.styl',
           'i-mixins/i-mixins__gradients.styl',
           'i-mixins/i-mixins__if-ie.styl',
-          'sprite_positions.styl',
+          'sprite_positions.styl'
         ]

       dev:
-        expand: true     # Enable dynamic expansion.
-        cwd:    'blocks' # Src matches are relative to this path.
+        expand: true
+        cwd:    'blocks/'
         src:    [
           'i-reset/i-reset.styl',
           'b-*/**/*.styl',
           '!i-*/'
         ]
-        dest:   'blocks' # Destination path prefix.
-        ext:    '.css'   # Dest filepaths will have this extension.
+        dest:   'blocks'
+        ext:    '.css'

       dev_ie:
         options:
           define:
             ie: true

-        expand: true     # Enable dynamic expansion.
-        cwd:    'blocks' # Src matches are relative to this path.
+        expand: true
+        cwd:    'blocks/'
         src: [
           'i-reset/i-reset.styl',
           'b-*/**/*.ie.styl',
           '!i-*/'
         ]
-        dest:   'blocks' # Destination path prefix.
-        ext:    '.ie.css'# Dest filepaths will have this extension.
+        dest:   'blocks'
+        ext:    '.ie.css'

       publish:
         options:
@@ -279,14 +276,16 @@
         path: 'http://localhost:8000/main.html';

-  `grunt.event.on('watch', function(action, filepath) {
-    if (grunt.file.isMatch( grunt.config('watch.stylus.files'), filepath) ) {
-      filepath = filepath.replace(/\\/g, '/'); // win slashes
-      filepath = filepath.replace( grunt.config('stylus.dev.cwd')+'/', '' );
+  @event.on 'watch', (action, filepath) ->
+    filepath = filepath.replace(/\\/g, '/'); # windows
+    if grunt.file.isMatch( grunt.config('watch.stylus.files'), filepath)
+      filepath = filepath.replace( grunt.config('stylus.dev.cwd'), '' );
       grunt.config( 'stylus.dev.src', filepath );
       grunt.config( 'stylus.dev_ie.src', filepath );
-    }
-  });`
+    if grunt.file.isMatch( grunt.config('watch.jade.files'), filepath)
+      filepath = filepath.replace( grunt.config('jade.dev.cwd'), '' );
+      grunt.config( 'jade.dev.src', filepath );
+

   @registerTask( 'default',    [ 'concat:js', 'stylus:dev', 'stylus:dev_ie', 'concat:css', 'concat:css_ie', 'jade:dev' ])
   @registerTask( 'livereload', [ 'default', 'connect', 'open', 'watch' ])


Build the index in the project directory.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -39,7 +39,7 @@
         pretty: true
       index:
         src: 'index.pug'
-        dest: 'public/html/index.html'
+        dest: 'index.html'
       partials:
         expand: true
         cwd: 'src/'


Add atBegin to watch in gruntfile

Add atBegin to watch in gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -8,6 +8,7 @@
     watch: {
       options: {
         livereload: true
+        atBegin: true
       }
       coffee: {
         files: ['src/*.coffee', 'examples/*.coffee'],


add missing featured-music files

add missing featured-music files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -53,7 +53,7 @@
         win: buildPlatforms.win
         linux32: buildPlatforms.linux32
         linux64: buildPlatforms.linux64
-      src: ['./css/**', './fonts/**', './images/**', './js/**', './l10n/**', './node_modules/**', '!./node_modules/grunt*/**', './index.html', './package.json']
+      src: ['./css/**', './fonts/**', './images/**', './js/**', './l10n/**', './node_modules/**', './featured-music/**', '!./node_modules/grunt*/**', './index.html', './package.json']

     copy:
       main:


try different android definition

try different android definition

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -140,9 +140,7 @@
             platform: "OS X 10.10"
             version: '8.0'
           ,
-            browserName: ''
-            platform: "Android"
-            version: '4.4'
+            browserName: "android"
           ]
           build: process.env.TRAVIS_JOB_ID
           testname: 'GSS browser tests'


Some speed for Gruntfile.

Some speed for Gruntfile.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -151,7 +151,19 @@

 	# These plugins provide necessary tasks.
-	require('load-grunt-tasks')(grunt)
+	if grunt.option('speedup')?
+		speedup = grunt.option('speedup')
+		if speedup is 'test'
+			grunt.loadNpmTasks 'grunt-contrib-clean'
+			grunt.loadNpmTasks 'grunt-contrib-nodeunit'
+			grunt.loadNpmTasks 'grunt-jscoverage'
+			grunt.loadNpmTasks 'grunt-coffee-coverage'
+		else
+			grunt.log.warn 'Unknown speedup value'
+	else
+		require('load-grunt-tasks')(grunt)
+
+	# Custom plugins.
 	grunt.loadTasks './grunt-custom-tasks/'

 	# Custom tasks.
@@ -169,7 +181,7 @@
 	grunt.registerTask 'test', testTask

 	# Default task.
-	grunt.registerTask 'default', ['check', 'build', 'docs', 'test']
+	grunt.registerTask 'default', []#['check', 'build', 'docs', 'test']

 	# And some additional CI stuff.
 	grunt.registerTask 'travis', ['default', 'jscoverage_write_lcov', 'coveralls']


Don't compile coffee

Don't compile coffee

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -31,14 +31,14 @@
           dest: './browser-builds/'
         ]
     coffee:
-      compile:
-        files: [
-          expand: true
-          cwd: './src/'
-          src: ['**/*.?(lit)coffee']
-          dest: './lib/'
-          rename: coffeeRename
-        ]
+      # compile:
+      #   files: [
+      #     expand: true
+      #     cwd: './src/'
+      #     src: ['**/*.?(lit)coffee']
+      #     dest: './lib/'
+      #     rename: coffeeRename
+      #   ]
       browsertests:
         files: [
           expand: true
@@ -107,7 +107,7 @@

   # Define tasks.
   grunt.registerTask 'build', ['build:node', 'build:standalone']
-  grunt.registerTask 'build:node', ['coffee']
+  grunt.registerTask 'build:node', []
   grunt.registerTask 'build:browsertests', ['coffee:browsertests']
   grunt.registerTask 'build:standalone', ['build:node', 'browserify', 'uglify:browserbuilds']
   grunt.registerTask 'default', ['build']


Revert "Only run set-development-version on mac"

This reverts commit 9a4db9d95db8b179397ec3b77ad76b03d1fe1545.

It's supported on Windows now.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -188,8 +188,4 @@
   grunt.registerTask('ci', ['update-atom-shell', 'build', 'set-development-version', 'lint', 'test'])
   grunt.registerTask('deploy', ['partial-clean', 'update-atom-shell', 'build', 'codesign'])
   grunt.registerTask('docs', ['markdown:guides', 'build-docs'])
-
-  defaultTasks = ['update-atom-shell', 'build']
-  defaultTasks.push('set-development-version') if process.platform is 'darwin'
-  defaultTasks.push('install')
-  grunt.registerTask('default', defaultTasks)
+  grunt.registerTask('default', ['update-atom-shell', 'build', 'set-development-version', 'install'])


browserify specs

browserify specs

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -65,27 +65,27 @@
 # --- Testing tasks ---
 taskManager.add 'test_core', [
   'base'
-  'browserify:firewallSpec'
+  'browserify:genericCoreFirewallSpec'
   'browserify:genericCoreFreedomModuleSpec'
-  'browserify:localInstanceSpec'
-  'browserify:remoteInstanceSpec'
-  'browserify:remoteConnectionSpec'
-  'browserify:remoteUserSpec'
-  'browserify:socialSpec'
-  'browserify:storageSpec'
+  'browserify:genericCoreLocalInstanceSpec'
+  'browserify:genericCoreRemoteInstanceSpec'
+  'browserify:genericCoreRemoteConnectionSpec'
+  'browserify:genericCoreRemoteUserSpec'
+  'browserify:genericCoreSocialSpec'
+  'browserify:genericCoreStorageSpec'
   #'jasmine:generic_core'
 ]

 taskManager.add 'test_ui', [
   'base'
-  'browserify:uiSpec'
-  'browserify:userSpec'
+  'browserify:genericUiUiSpec'
+  'browserify:genericUiUserSpec'
   #'jasmine:generic_ui'
 ]

 taskManager.add 'test_chrome', [
   'build_chrome'
-  'browserify:chromeConnectorSpec'
+  'browserify:chromeExtensionConnectorSpec'
   #'jasmine:chrome_extension'
   #'jasmine:chrome_app'
 ]
@@ -619,8 +619,8 @@
     browserify:
       chromeAppMain: Rule.browserify 'chrome/app/scripts/main.core-env'

-      genericCoreChromeConnector: Rule.browserifySpec 'chrome/extension/scripts/chrome_core/connector'
-      genericCoreChromeConnectorSpec: Rule.browserifySpec 'chrome/extension/scripts/chrome_core/connector'
+      chromeExtensionConnector: Rule.browserify 'chrome/extension/scripts/chrome_core/connector'
+      chromeExtensionConnectorSpec: Rule.browserifySpec 'chrome/extension/scripts/chrome_core/connector'
       genericCoreFirewall: Rule.browserify 'generic_core/firewall'
       genericCoreFirewallSpec: Rule.browserifySpec 'generic_core/firewall'
       genericCoreFreedomModule: Rule.browserify 'generic_core/freedom-module'


Fix: coveralls submission not working

Fix: coveralls submission not working

Signed-off-by: Christian Roth <396398d2f664a436582eed8a8cfb08dd7671c8d6@port17.de>

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -27,6 +27,7 @@
         options:
           reporter: 'spec'
           growl:    true
+          bail:     true
       options:
         recursive:  true
         compilers:  ['coffee:coffee-script']
@@ -48,7 +49,7 @@
   ]

   grunt.registerTask 'test', [
-    'clean:server', 'coffee:server', 'mochacov:test'
+    'clean:server', 'coffee:server', 'mochacov:test', 'mochacov:coverage'
   ]

   grunt.registerTask 'travis', [


fix firefox build

fix firefox build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -396,7 +396,7 @@
           # code.
           expand: true, cwd: 'build/compile-src/firefox/data'
           src: ['polymer/vulcanized.js', 'polymer/vulcanized.html']
-          dest: firefoxDevPath
+          dest: firefoxDevPath + 'data'
         } ]

     }  # copy
@@ -589,7 +589,7 @@
         options:
           inline: true
         files:
-          'build/compile-src/firefox/data/polymer/vulcanized-inline.html': 'build/compile-src/firefox/data/polymer/browser-elements.html'
+          'build/compile-src/firefox/data/polymer/vulcanized-inline.html': 'build/compile-src/firefox/data/polymer/root.html'
       firefoxCsp:
         options:
           csp: true


only coffeescript compilation in default task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -17,7 +17,7 @@
         "#{srcDir}/**/*.coffee"
         "#{testSrcDir}/**/*.coffee"
       ]
-      tasks: ["default"]
+      tasks: ["cs"]

     # Transcompile CoffeeScript to JavaScript files
     coffee:
@@ -85,3 +85,4 @@

   grunt.registerTask "travis", ["clean", "coffee", "mochacov", "copy", "browserify", "uglify"]

+  grunt.registerTask "cs", ["clean", "coffee"]


For testing set the 'portrait' screenOrientation

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -90,6 +90,7 @@
         minSdkVersion: -> 11
         permissions: ['ACCESS_NETWORK_STATE']
         androidApplicationName: 'com.phonegap.test_custom_app'
+        screenOrientation: 'portrait'

     # Before generating any new files, remove any previously-created files.
     clean:


Remove unused task

Remove unused task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -15,12 +15,6 @@
         ext: '.js'

     shell:
-      buildOnig:
-        command: 'coffee build-onig.coffee'
-        options:
-          stdout: true
-          stderr: true
-
       rebuild:
         command: 'node-gyp rebuild'
         options:


chore(cleanup): remove redundant files

chore(cleanup): remove redundant files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -26,7 +26,7 @@
       files:
         dest: 'hammer.js'
         src: 'src/main.js'
-
+
     uglify:
       min:
         options:
@@ -53,7 +53,7 @@

     jscs:
       src: [
-        'src/**/*.js'
+        'src/**/*.js'
       ]
       options:
         config: "./.jscsrc"


Don't choke on regex based paths.

Don't choke on regex based paths.

--- server/commons/perfmon.coffee
+++ server/commons/perfmon.coffee
@@ -22,7 +22,7 @@
     recordMetrics = ->
       diff = process.hrtime(time);
       ms = (diff[0] * 1000 + diff[1] / 1e6);
-      path = req.route?.path or '/*'
+      path = req.route?.path?.toString() or '/*'
       stat = req.method + "." + path.replace /[^A-Za-z0-9]+/g, '_'
       realClient.timing stat, ms
       name = req.user?._id


fix ajax load more scrolling for course stats

fixes #9854

the load more scrolling in the recently logged in user section of course
statistics wasn't working very well in chrome and firefox, because the
scrollContainer that listens for events was being initialized after the event
listeners were added.

test plan:
- in all browsers, in a course with > 30 users
- go to the course statistics page, users tab
- make sure scrolling down auto loads more users until all have been loaded.

Change-Id: I1e9d62b4cceb189caf647e686d8996e20f0b9da3
Reviewed-on: https://gerrit.instructure.com/12835
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/views/RecentStudents/RecentStudentCollectionView.coffee
+++ app/coffeescripts/views/RecentStudents/RecentStudentCollectionView.coffee
@@ -8,10 +8,10 @@
   class RecentStudentCollectionView extends PaginatedView

     initialize: (options) ->
-      super
       @collection.on 'add', @renderUser
       @collection.on 'reset', @render
       @paginationScrollContainer = @$el
+      super

     render: =>
       ret = super


re-enable squashing of changefeeds

re-enable squashing of changefeeds

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -3813,7 +3813,7 @@
                                             if err
                                                 cb(err)
                                             else
-                                                @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes(includeStates: false, squash:3).run (err, feed) =>
+                                                @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes(includeStates: false, squash:5).run (err, feed) =>
                                                     if err
                                                         e = misc.to_json(err)
                                                         if e.indexOf("Did you just reshard?") != -1
@@ -3847,7 +3847,7 @@
                                             if err
                                                 cb(err)
                                             else
-                                                @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes(includeStates: false, squash:3).run (err, feed) =>
+                                                @table('projects').getAll(opts.account_id, index:'users').pluck('users').changes(includeStates: false, squash:5).run (err, feed) =>
                                                     if err
                                                         e = misc.to_json(err)
                                                         if e.indexOf("Did you just reshard?") != -1
@@ -3981,8 +3981,8 @@
                         process.nextTick(f)

                     winston.debug("FEED -- setting up a feed with id #{changefeed_id}")
-                    #db_query_no_opts.changes(includeStates: false, squash:.2).run (err, feed) =>
-                    db_query_no_opts.changes(includeStates: false).run (err, feed) =>
+                    db_query_no_opts.changes(includeStates: false, squash:.5).run (err, feed) =>
+                    #db_query_no_opts.changes(includeStates: false).run (err, feed) =>
                         if err
                             e = to_json(err)
                             winston.debug("FEED -- error setting up #{e}")


assign unassigned projects to storage

assign unassigned projects to storage

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -4,8 +4,7 @@
 ###

 if process.env.USER != 'root'
-    console.warn("storage.coffee may only be used as root")
-    process.exit(1)
+    console.warn("WARNING: many functions in storage.coffee will not work if you aren't root!")

 {join}      = require('path')
 fs          = require('fs')
@@ -713,6 +712,46 @@
         process.exit(if err then 1 else 0)
     )

+# Probably soon we won't need this since projects will get storage
+# assigned right when they are created.
+exports.assign_storage_to_all_projects = (database, cb) ->
+    # Ensure that every project is assigned to some storage host.
+    dbg = (m) -> winston.debug("assign_storage_to_all_projects: #{m}")
+    dbg()
+    projects = hosts = undefined
+    async.series([
+        (cb) ->
+            dbg("get projects with no assigned storage")
+            database.table('projects').filter((row)->row.hasFields({storage:true}).not()).pluck('project_id').run (err, v) ->
+                dbg("get #{v?.length} projects")
+                projects = v; cb(err)
+        (cb) ->
+            database.table('storage_servers').pluck('host').run (err, v) ->
+                if err
+                    cb(err)
+                else
+                    dbg("got hosts: #{misc.to_json(v)}")
+                    hosts = (x.host for x in v)
+                    cb()
+        (cb) ->
+            n = 0
+            f = (project, cb) ->
+                n += 1
+                {project_id} = project
+                host = misc.random_choice(hosts)
+                dbg("#{n}/#{projects.length}: assigning #{project_id} to #{host}")
+                database.get_project_storage  # do a quick check that storage isn't defined -- maybe slightly avoid race condition (we are being lazy)
+                    project_id : project_id
+                    cb         : (err, storage) ->
+                        if err or storage?
+                            cb(err)
+                        else
+                            database.set_project_storage
+                                project_id : project_id
+                                host       : host
+                                cb         : cb
+            async.mapLimit(projects, 10, f, cb)
+    ], cb)

 exports.update_storage = () ->
     # This should be run from the command line.
@@ -768,6 +807,8 @@
                 cb    : (err, db) ->
                     database = db
                     cb(err)
+        (cb) ->
+            exports.assign_storage_to_all_projects(database, cb)
         (cb) ->
             exports.save_recent_projects
                 database : database


only show error in log if there is one

only show error in log if there is one

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -1297,7 +1297,8 @@
             else
                 delete storage_request.err
             update_db (err) ->
-                dbg("ERROR: failed to record finishing the storage request - #{err}")
+                if err
+                    dbg("ERROR: failed to record finishing the storage request - #{err}")
                 # Still, we are done so we set this in tasks, so we don't block on doing one later, once
                 # things time out in the database.
                 tasks[project.project_id] = false


clean r_help

clean r_help

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -123,87 +123,87 @@

 SUPPORT_LINKS =
-    frequently_asked_questions :
-        icon : 'question-circle'
-        href : 'https://github.com/sagemathinc/smc/wiki/Portal'
-        link : 'SageMathCloud documentation'
+    teaching :
+        icon : 'users'
+        #href : 'http://www.beezers.org/blog/bb/2015/09/grading-in-sagemathcloud/'
+        #href : 'http://sagemath.blogspot.com/2014/10/sagemathcloud-course-management.html'
+        href : 'https://mikecroucher.github.io/SMC_tutorial/'
+        link : Tutorial for anyone wanting to use SageMathCloud for teaching
     pricing :
         icon : 'money'
         href : PolicyPricingPageUrl
         link : 'Pricing and subscription options'
         commercial: true
+    frequently_asked_questions :
+        icon : 'question-circle'
+        href : 'https://github.com/sagemathinc/smc/wiki/Portal'
+        link : 'SageMathCloud documentation'
     # commented out since link doesn't work
     #getting_started :
     #    icon : 'play'
     #    href : '#help-page-getting-started'
     #    link : Getting started with <SiteName/>
-    teaching :
-        icon : 'users'
-        #href : 'http://www.beezers.org/blog/bb/2015/09/grading-in-sagemathcloud/'
-        #href : 'http://sagemath.blogspot.com/2014/10/sagemathcloud-course-management.html'
-        href : 'https://github.com/mikecroucher/SMC_tutorial/blob/master/README.md'
-        link : Teaching a course with SageMathCloud
     courses :
         icon : 'graduation-cap'
         href : 'https://github.com/sagemathinc/smc/wiki/Teaching'
-        link :  List of courses that use SageMathCloud
-    realtime_chat :
-        icon : 'comments-o'
-        href : 'https://gitter.im/sagemath/cloud'
-        link : 'Realtime chat and help'
+        link :  Courses using SageMathCloud
+    #realtime_chat :
+    #    icon : 'comments-o'
+    #    href : 'https://gitter.im/sagemath/cloud'
+    #    link : 'Realtime chat and help'
     # removed since none of us SMC devs use ask.sagemath these days
     #quick_question :
     #    icon : 'question-circle'
     #    href : 'http://ask.sagemath.org/questions/'
     #    link : 'Ask a quick question'
-    github :
-        icon : 'github-square'
-        href : 'https://github.com/sagemathinc/smc'
-        link : 'SageMathCloud source code'
-    github_issue_tracker :
-        icon : 'exclamation-circle'
-        href : 'https://github.com/sagemathinc/smc/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AI-bug%20sort%3Acreated-asc%20-label%3Ablocked'
-        link : 'Known bugs in SageMathCloud'
     support_mailing_list :
         icon : 'life-ring'
         href : 'https://groups.google.com/forum/?fromgroups#!forum/sage-cloud'
-        link : Support mailing list
-    developer_mailing_list :
-        icon : 'envelope-o'
-        href : 'https://groups.google.com/forum/?fromgroups#!forum/sage-cloud-devel'
-        link : Developer mailing list
+        link : Mailing list
+    #developer_mailing_list :
+    #    icon : 'envelope-o'
+    #    href : 'https://groups.google.com/forum/?fromgroups#!forum/sage-cloud-devel'
+    #    link : Developer mailing list
     sagemath_blog :
         icon : 'rss'
-        href : 'http://sagemath.blogspot.com/'
+        href : 'http://blog.sagemath.com/'
         link : Blog
     google_plus_smc :
         icon : 'google-plus-square'
         href : 'https://plus.google.com/117696122667171964473/posts'
         link : Google+
         text : 'updates'
-    google_plus :
-        icon : 'google-plus-square'
-        href : 'https://plus.google.com/115360165819500279592/posts'
-        link : 'Google+ William Stein'
-        text : 'development updates'
+    #google_plus :
+    #    icon : 'google-plus-square'
+    #    href : 'https://plus.google.com/115360165819500279592/posts'
+    #    link : 'Google+ William Stein'
+    #    text : 'development updates'
     twitter :
         icon : 'twitter-square'
-        href : 'https://twitter.com/wstein389'
+        href : 'https://twitter.com/sagemathcloud'
         link : 'Twitter'
         text : 'the Twitter feed'
-    general_sagemath :
-        icon : 'superscript'
-        href : 'http://www.sagemath.org/help.html'
-        link : 'General SageMath help and support pages'
+    github :
+        icon : 'github-square'
+        href : 'https://github.com/sagemathinc/smc'
+        link : 'Source code'
+    github_issue_tracker :
+        icon : 'exclamation-circle'
+        href : 'https://github.com/sagemathinc/smc/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AI-bug%20sort%3Acreated-asc%20-label%3Ablocked'
+        link : 'Bugs'
+    #general_sagemath :
+    #    icon : 'superscript'
+    #    href : 'http://www.sagemath.org/help.html'
+    #    link : 'SageMath help and support'
     chrome_app :
         icon      : 'google'
         href      : 'https://chrome.google.com/webstore/detail/the-sagemath-cloud/eocdndagganmilahaiclppjigemcinmb'
-        link      : 'Install the Chrome App'
+        link      : 'Chrome App'
         className : 'salvus-chrome-only'
     user_survey :
         icon      : 'pencil-square'
         href      : 'https://docs.google.com/forms/d/1Odku9JuqYOVUHF4p5CXZ_Fl-7SIM3ApYexabfTV1O2o/viewform?usp=send_form'
-        link      : 'SageMathCloud User Survey'
+        link      : 'User Survey'

 HelpPageSupportSection = rclass
     displayName : 'HelpPage-HelpPageSupportSection'
@@ -514,7 +514,7 @@

                 {<HelpPageAboutSection /> if require('./customize').commercial}

-                <HelpPageGettingStartedSection />
+                {# <HelpPageGettingStartedSection /> }
             </Col>
             <Col sm=1 md=2 xsHidden></Col>
             <Col xs=12 sm=12 md=12>