Remove already moved data

Remove already moved data

--- client/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/app/Applications/Groups.kdapplication/AppController.coffee
@@ -410,261 +410,7 @@
           title: 'Group was updated!'
           duration: 1000

-  showGroupSubmissionView:->
-
-    getGroupType = ->
-      modal.modalTabs.forms["Select group type"].inputs.type.getValue()
-
-    getPrivacyDefault = ->
-      switch getGroupType()
-        when 'educational'  then 'by-request'
-        when 'company'      then 'by-invite'
-        when 'project'      then 'public'
-        when 'custom'       then 'public'
-
-    getVisibilityDefault = ->
-      switch getGroupType()
-        when 'educational'  then 'visible'
-        when 'company'      then 'hidden'
-        when 'project'      then 'visible'
-        when 'custom'       then 'visible'
-
-    applyDefaults =->
-      {Privacy,Visibility} = modal.modalTabs.forms["General Settings"].inputs
-      Privacy.setValue getPrivacyDefault()
-      Visibility.setValue getVisibilityDefault()
-
-    modalOptions =
-      title                          : 'Create a new group'
-      height                         : 'auto'
-      cssClass                       : "group-admin-modal compose-message-modal admin-kdmodal"
-      width                          : 684
-      overlay                        : yes
-      tabs                           :
-        navigable                    : no
-        goToNextFormOnSubmit         : yes
-        hideHandleContainer          : yes
-        callback                     : (formData)=>
-          KD.track "Groups", "CreateNewGroup"
-          _createGroupHandler.call @, formData, (err) =>
-            modal.modalTabs.forms["VM Settings"].buttons["Create Group"].hideLoader()
-            unless err
-              modal.destroy()
-        forms                        :
-          # "Select group type"        :
-          #   title                    : 'Group type'
-          #   buttons                  :
-          #     "Next"                 :
-          #       style                : "modal-clean-gray"
-          #       type                 : "submit"
-          #       callback             : -> applyDefaults()
-          #   fields                   :
-          #     "type"                 :
-          #       name                 : "type"
-          #       itemClass            : GroupCreationSelector
-          #       defaultValue         : "project"
-          #       cssClass             : "group-type"
-          #       radios               : [
-          #         { title : "University/School", value : "educational", callback: -> log "1"}
-          #         { title : "Company",           value : "company", callback: -> log "2"}
-          #         { title : "Project",           value : "project", callback: -> log "3"}
-          #         { title : "Other",             value : "custom", callback: -> log "4"}
-          #       ]
-          #       change               : -> log @getValue()
-
-          "General Settings"         :
-            title                    : 'Create a group'
-            callback                 : ->
-              form = modal.modalTabs.forms["General Settings"]
-              unless form.inputs["Group VM"].getValue()
-                modal.modalTabs.removePaneByName "VM Settings"
-                modal.modalTabs.fireFinalCallback()
-            buttons                  :
-              "Next"                 :
-                style                : "modal-clean-gray"
-                type                 : "submit"
-                loader               :
-                  color              : "#444444"
-                  diameter           : 12
-              "Back"                 :
-                style                : "modal-cancel"
-                callback             : ->
-                  form = modal.modalTabs.forms["Select group type"]
-                  form.buttons.Next.hideLoader()
-                  modal.modalTabs.showPreviousPane()
-            fields                   :
-              "Title"                :
-                label                : "Title"
-                name                 : "title"
-                validate             :
-                  event              : "blur"
-                  rules              :
-                    required         : yes
-                    minLength        : 4
-                keydown              : (pubInst, event)->
-                  @utils.defer =>
-                    makeSlug()
-                placeholder          : 'Please enter your group title...'
-              "HiddenSlug"           :
-                name                 : "slug"
-                type                 : "hidden"
-                cssClass             : "hidden"
-              "Slug"                 :
-                label                : "Address"
-                partial              : "#{location.protocol}//#{location.host}/"
-                itemClass            : KDCustomHTMLView
-              "Description"          :
-                label                : "Description"
-                type                 : "textarea"
-                name                 : "body"
-                defaultValue         : ""
-                placeholder          : "Please enter a description for your group here..."
-              "Privacy"              :
-                label                : "Privacy/Visibility"
-                itemClass            : KDSelectBox
-                type                 : "select"
-                name                 : "privacy"
-                defaultValue         : "public"
-                selectOptions        :
-                  Public             : [
-                    { title : "Anyone can join",    value : "public" }
-                  ]
-                  Private            : [
-                    { title : "By invitation",       value : "by-invite" }
-                    { title : "By access request",   value : "by-request" }
-                    { title : "In same domain",      value : "same-domain" }
-                  ]
-                nextElement          :
-                  "Visibility"       :
-                    itemClass        : KDSelectBox
-                    type             : "select"
-                    name             : "visibility"
-                    defaultValue     : "visible"
-                    cssClass         : "visibility"
-                    selectOptions    : [
-                      { title : "Visible in group listings",    value : "visible" }
-                      { title : "Hidden in group listings",     value : "hidden" }
-                    ]
-              "Group VM"             :
-                label                : "Create virtual machines for the group"
-                itemClass            : KDOnOffSwitch
-                name                 : "group-vm"
-                defaultValue         : yes
-                callback             : (state)->
-                  form = modal.modalTabs.forms["General Settings"]
-                  form.buttons.Next.setTitle unless state then "Create Group" \
-                                                          else "Next"
-          "VM Settings"              :
-            title                    : 'VM Settings'
-            buttons                  :
-              "Create Group"         :
-                style                : "modal-clean-gray"
-                type                 : "submit"
-                loader               :
-                  color              : "#444444"
-                  diameter           : 12
-              "Back"                 :
-                style                : "modal-cancel"
-                callback             : ->
-                  modal.modalTabs.showPreviousPane()
-                  form = modal.modalTabs.forms["General Settings"]
-                  form.buttons.Next.hideLoader()
-            fields                   :
-              "VM Host"               :
-                label                : "Host Machine"
-                itemClass            : KDSelectBox
-                type                 : "select"
-                name                 : "vm-host"
-                defaultValue         : "1"
-                selectOptions        : [
-                  { title : "2GHz, 4GB RAM, 20GB Disk",      value : "1" }
-                  { title : "4GHz, 4GB RAM, 40GB Disk",      value : "2" }
-                  { title : "8GHz, 8GB RAM, 60GB Disk",      value : "3" }
-                  { title : "8GHz, 16GB RAM, 60GB Disk",     value : "4" }
-                  { title : "16GHz, 32GB RAM, 100GB Disk",   value : "5" }
-                ]
-              "Users per VM"         :
-                label                : "VM's per host"
-                itemClass            : KDSelectBox
-                name                 : "vm-users"
-                defaultValue         : "25"
-                selectOptions        : [
-                  { title : "5",     value : "5" }
-                  { title : "10",     value : "10" }
-                  { title : "25",     value : "25" }
-                  { title : "50",     value : "50" }
-                  { title : "100",     value : "100" }
-                ]
-              "userQuota"            :
-                label                : "Users can create FREE additional VM's up to"
-                itemClass            : KDSelectBox
-                name                 : "vm-additional-free-amount"
-                defaultValue         : "1"
-                selectOptions        : [
-                  { title : "0",            value : "0" }
-                  { title : "1",            value : "1" }
-                  { title : "2",            value : "2" }
-                  { title : "3",            value : "3" }
-                  { title : "5",            value : "5" }
-                  { title : "10",           value : "10" }
-                  { title : "25",           value : "25" }
-                  { title : "Unlimited",    value : "unlimited" }
-                ]
-              "additionalVMs"        :
-                label                : "Users can buy additional VM's for"
-                itemClass            : KDSelectBox
-                name                 : "vm-additional-price"
-                defaultValue         : "5"
-                selectOptions        : [
-                  { title : "Free",     value : "0" }
-                  { title : "$ 1",      value : "1" }
-                  { title : "$ 5",      value : "5" }
-                  { title : "$ 10",     value : "10" }
-                  { title : "$ 25",     value : "25" }
-                  { title : "$ 50",     value : "50" }
-                  { title : "$ 100",    value : "100" }
-                  { title : "Custom...",value : "custom" }
-                ]
-                change                : =>
-                  {additionalVMs, additionalVmCustom} = modal.modalTabs.forms["VM Settings"].inputs
-                  if additionalVMs.getValue() is 'custom'
-                  then additionalVmCustom.show()
-                  else additionalVmCustom.hide()
-                nextElement           :
-                  "additionalVmCustom":
-                    name              : "vm-additional-price-custom"
-                    cssClass          : 'hidden'
-                    placeholder       : '42'
-              "membershipFee"        :
-                label                : "Users should pay a monthly fee of"
-                itemClass            : KDSelectBox
-                name                 : "vm-membership-fee"
-                defaultValue         : "5"
-                selectOptions        : [
-                  { title : "Free",     value : "0" }
-                  { title : "$ 1",      value : "1" }
-                  { title : "$ 5",      value : "5" }
-                  { title : "$ 10",     value : "10" }
-                  { title : "$ 25",     value : "25" }
-                  { title : "$ 50",     value : "50" }
-                  { title : "$ 100",    value : "100" }
-                  { title : "Custom...",value : "custom" }
-                ]
-                change                : =>
-                  {membershipFee, membershipFeeCustom} = modal.modalTabs.forms["VM Settings"].inputs
-                  if membershipFee.getValue() is 'custom'
-                  then membershipFeeCustom.show()
-                  else membershipFeeCustom.hide()
-                nextElement             :
-                  "membershipFeeCustom" :
-                    name                : "vm-membership-fee-custom"
-                    cssClass            : 'hidden'
-                    placeholder         : '42'
-
-    modal = new GroupCreationModal #modalOptions
-    # form = modal.modalTabs.forms["General Settings"]
-    # form.on "FormValidationFailed", ->
-    #   form.buttons.Next.hideLoader()
+  showGroupSubmissionView:-> new GroupCreationModal

   handleError =(err, buttons)->
     unless buttons


Refactor.

Refactor.

--- app/client/js/songs/song.coffee
+++ app/client/js/songs/song.coffee
@@ -79,12 +79,11 @@
     @return {Array.<app.ValidationError>}
   ###
   validate: ->
-    errors = ['name', 'artist', 'lyrics']
+    ['name', 'artist', 'lyrics']
       .filter (name) => !@[name].trim()
       .map (name) =>
         @MSG_PLEASE_FILL_OUT = goog.getMsg 'Please fill out {$name}.', name: name
         new app.ValidationError name, @MSG_PLEASE_FILL_OUT
-    errors

   ###*
     Compute urls after set.


log out if the command running is being used

--- app/coffee/CommandRunner.coffee
+++ app/coffee/CommandRunner.coffee
@@ -1,5 +1,7 @@
 spawn = require("child_process").spawn
 logger = require "logger-sharelatex"
+
+logger.info "using standard command runner"

 module.exports = CommandRunner =
 	run: (project_id, command, directory, image, timeout, callback = (error) ->) ->


stashing changes for now

stashing changes for now

--- src/flux/media.coffee
+++ src/flux/media.coffee
@@ -5,19 +5,20 @@

 mediaLinkRegex = /<a\s+(?:[^>]*?\s+)?href="(#[^"]*)"/g
 mediaDOMRegex = (idString) ->
-  new RegExp('<[figure|table][^>]*id="+' + idString + '+".*?>(.*?)+[<\/figure|table]>', 'g')
+  new RegExp('<[figure|table][^>]*id="+' + idString + '+".*?>+[\s\S]+[<\/figure|table]>', 'g')

-# builder = new htmlparser.HtmlBuilder()
-window.htmlparser = htmlparser
+# # builder = new htmlparser.HtmlBuilder()
+# window.htmlparser = htmlparser

-parseHandler = new htmlparser.DefaultHandler (error, dom) ->
-  id = htmlparser.DomUtils.getElementById("fs-id1167066794530", dom)
-  # idDOM = builder.write(id)
-  console.info(id)
-  # console.info(idDOM)
+# parseHandler = new htmlparser.DefaultHandler (error, dom) ->
+# #  id = htmlparser.DomUtils.getElementById("fs-id1167066794530", dom)
+#   id = htmlparser.DomUtils.getElementById("Figure_03_01_Car", dom)
+#   # idDOM = builder.write(id)
+#   console.info(id)
+#   # console.info(idDOM)

-parser = new htmlparser.Parser(parseHandler)
+# parser = new htmlparser.Parser(parseHandler)

 MediaConfig =

@@ -25,14 +26,17 @@

   parse: (htmlString) ->
-    parser.parseComplete(htmlString)
+    # parser.parseComplete(htmlString)
+    console.info(htmlString)
     mediaLinks = htmlString.match(mediaLinkRegex)
     _.each(mediaLinks, (mediaLink) ->
+      console.info(mediaLink)
       link = _.last(mediaLink.split(' href="#'))
+      link = link.replace('"', '')
       console.info(link)
       console.info(mediaDOMRegex(link))
-      mediaDOM = htmlString.match(mediaDOMRegex(link))
-      console.info(mediaDOM)
+      # mediaDOM = htmlString.match(mediaDOMRegex(link))
+      # console.info(mediaDOM)
     )

   _get: (id) ->


fix firefox scrolling until I can get a fix merged into list-view

--- app/components/eui-popup.coffee
+++ app/components/eui-popup.coffee
@@ -134,6 +134,12 @@
   # List View

   listView: Ember.ListView.extend
+    css:
+      position: 'relative'
+      overflow: 'auto'
+      '-webkit-overflow-scrolling': 'touch'
+      'overflow-scrolling': 'touch'
+
     classNames: ['eui-options']
     height: Ember.computed.alias 'controller.listHeight'
     rowHeight: Ember.computed.alias 'controller.listRowHeight'


Add ::backdrop support

Add ::backdrop support

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -223,8 +223,15 @@
           browsers: browsers

 # Fullscreen selector
-feature require('caniuse-db/features-json/fullscreen'), (browsers) ->
+fullscreen = require('caniuse-db/features-json/fullscreen')
+
+feature fullscreen, (browsers) ->
   prefix ':fullscreen',
+          selector: true,
+          browsers: browsers
+
+feature fullscreen, match: /x(\s#2|$)/, (browsers) ->
+  prefix '::backdrop',
           selector: true,
           browsers: browsers


clean up test

clean up test

--- components/follow_button/test/view.coffee
+++ components/follow_button/test/view.coffee
@@ -1,9 +1,6 @@
-sd = require('sharify').data
 benv = require 'benv'
 Backbone = require 'backbone'
-moment = require 'moment'
 sinon = require 'sinon'
-path = require 'path'
 mediator = require '../../../lib/mediator.coffee'
 Profile = require '../../../models/profile.coffee'
 { Following, FollowButton } = require '../index.coffee'
@@ -13,7 +10,6 @@

   before (done) ->
     benv.setup =>
-      sd.API_URL = 'localhost:3003'
       benv.expose { $: benv.require 'jquery' }
       sinon.stub Backbone, 'sync'
       @mediator = sinon.spy mediator, 'trigger'


Fixed class generation

Fixed class generation

--- lib/class-complete.coffee
+++ lib/class-complete.coffee
@@ -44,6 +44,6 @@

         buffer += "\n"
         buffer += "\treturn #{className};"
-        buffer += "\n)());"
+        buffer += "\n}());"

         editor.insertText buffer


Add an option to not focus if requested, no api break here.

--- components/tree/treeviewcontroller.coffee
+++ components/tree/treeviewcontroller.coffee
@@ -397,10 +397,10 @@
         listController.deselectAllItems()
     @selectedNodes = []

-  selectNode:(nodeView, event)->
+  selectNode:(nodeView, event, setFocus = yes)->

     return unless nodeView
-    @setFocusState()
+    if setFocus then @setFocusState()
     @listControllers[@getNodePId nodeView.getData()].selectItem nodeView, event

   deselectNode:(nodeView, event)->


@markijbema likes this better

@markijbema likes this better

--- core/app/backbone/views/buttons/react_sliding_share_button.coffee
+++ core/app/backbone/views/buttons/react_sliding_share_button.coffee
@@ -38,6 +38,9 @@

     "https://twitter.com/intent/tweet?url=#{url}&text=#{text}&related=factlink"

+  componentDidMount: -> updateTwitter()
+  componentDidUpdate: -> updateTwitter()
+
   render: ->
     _a [
       'button-twitter'
@@ -47,9 +50,6 @@
       target: '_blank' # for if twitter is not yet loaded
     ],
       _i ["icon-twitter"]
-
-  componentDidMount: -> updateTwitter()
-  componentDidUpdate: -> updateTwitter()

 window.ReactSlidingShareButton = React.createBackboneClass


Only try to get from brain if brain exists

--- src/hubot-mute.coffee
+++ src/hubot-mute.coffee
@@ -23,8 +23,8 @@
 mute_explain = {}

 module.exports = (robot) ->
-  mute_channels = robot.brain.get('mute_channels') or []
-  mute_all = robot.brain.get('mute_all') or false
+  robot.brain && mute_channels = robot.brain.get('mute_channels') or []
+  robot.brain && mute_all = robot.brain.get('mute_all') or false

   robot.respond /mute list$/i, (msg) ->
     msg.finish()


fixing inability to delete tags from show entry.

--- app/coffee/tag.coffee
+++ app/coffee/tag.coffee
@@ -90,7 +90,7 @@
     @tagViewClass = tagViewClass
     @addAllCurrentTags()

-    @$container.find('.tag .close').live "click", (event)=>
+    @$container.find('.tag .close-16').live "click", (event)=>
       @removeTag($(event.currentTarget).parent().data('id'))

     #@$container.delegate 'div', 'click', (event)=>
@@ -235,7 +235,7 @@
   entryId: -> $('#showEntry').data('id')
   setId: (id)-> @id = id
   create: ->
-    deferred = $.post "/tags", { entry_id: @entryId(), what_name: @name }, (data)->
+    deferred = $.post "/tags", { entryIdntry_id: @entryId(), what_name: @name }, (data)->
       @id = data.what_id
     return deferred.promise()


Fix in app date display for meeting tiems

--- angular/core/services/time_service.coffee
+++ angular/core/services/time_service.coffee
@@ -2,7 +2,7 @@
   new class TimeService

     displayDate: (m, zone) =>
-      if moment._f == 'YYYY-MM-DD'
+      if m._f == 'YYYY-MM-DD'
         m.format("D MMMM#{@sameYear(m)}")
       else
         @inTimeZone(m, zone).format("D MMMM#{@sameYear(m)} - h:mma")
@@ -10,9 +10,6 @@
     isoDate: (m, zone) =>
       @inTimeZone(m, zone).toISOString()

-    hasTime: (m) ->
-      m._f != 'YYYY-MM-DD'
-
     inTimeZone: (m, zone) =>
       m.tz(zone || AppConfig.timeZone)


More comprehensive tests

More comprehensive tests

--- test/test.coffee
+++ test/test.coffee
@@ -5,10 +5,18 @@
 takeapeek = require ".."

 before ->
+    # Delete the files if they exist
+    if fs.existsSync("test/testfiles")
+        fs.unlinkSync "test/testfiles/file.txt"
+        fs.unlinkSync "test/testfiles/file with space.html"
+        fs.unlinkSync "test/testfiles/.hiddenfile"
+        fs.rmdirSync "test/testfiles"
+
     # Make some test files
     fs.mkdirSync "test/testfiles"
     fs.writeFileSync "test/testfiles/file.txt", "Hello World!"
     fs.writeFileSync "test/testfiles/file with space.html", "Hello There!!!"
+    fs.writeFileSync "test/testfiles/.hiddenfile", "I am a stealthy ninja"

 describe "basic", ->
     server = new takeapeek
@@ -32,17 +40,127 @@
             res.should.have.status 200
             done()

+    it "should not serve hidden files", (done) ->
+        request.get "http://localhost:3141/.hiddenfile", (err, res, body) ->
+            res.should.have.status 404
+            done()
+
     it "should 404 for non existent files", (done) ->
         request.get "http://localhost:3141/non_existent_file.txt", (err, res, body) ->
             res.should.have.status 404
             done()

-
     after ->
         server.close()
+
+describe "options", ->
+    describe "directory", ->
+        server = new takeapeek
+            directory: "./test"
+            port: 3141
+            quiet: true
+
+        before ->
+            # Start a server
+            server.listen()
+
+        it "should serve the correct directory", (done) ->
+            request.get "http://localhost:3141/mocha.opts", (err, res, body) ->
+                body.should.equal fs.readFileSync("./test/mocha.opts").toString()
+                res.should.have.status 200
+                done()
+
+        after ->
+            server.close()
+
+    describe "index", ->
+        server = new takeapeek
+            directory: "./test/testfiles"
+            port: 3141
+            quiet: true
+            index: true
+
+        before ->
+            # Start a server
+            server.listen()
+
+        it "should serve directory indexes", (done) ->
+            request.get "http://localhost:3141/", (err, res, body) ->
+                res.should.have.header "content-type", "text/html"
+                res.should.have.status 200
+                done()
+
+        it "should 404 for non existent folders", (done) ->
+            request.get "http://localhost:3141/non_existent_folder", (err, res, body) ->
+                res.should.have.status 404
+                done()
+
+        after ->
+            server.close()
+
+    describe "dotfiles", ->
+        server = new takeapeek
+            directory: "./test/testfiles"
+            port: 3141
+            quiet: true
+            dotfiles: true
+
+        before ->
+            # Start a server
+            server.listen()
+
+        it "should serve hidden files", (done) ->
+            request.get "http://localhost:3141/.hiddenfile", (err, res, body) ->
+                body.should.equal "I am a stealthy ninja"
+                res.should.have.status 200
+                done()
+
+        after ->
+            server.close()
+
+    describe "port", ->
+        server = new takeapeek
+            directory: "./test/testfiles"
+            port: 10023
+            quiet: true
+
+        before ->
+            # Start a server
+            server.listen()
+
+        it "should serve on a different port", (done) ->
+            request.get "http://localhost:10023/file.txt", (err, res, body) ->
+                body.should.equal "Hello World!"
+                res.should.have.status 200
+                done()
+
+        after ->
+            server.close()
+
+    describe "content-text", ->
+        options =
+            directory: "./test/testfiles"
+            port: 3141
+            quiet: true
+        options["content-text"] = true
+        server = new takeapeek options
+
+        before ->
+            # Start a server
+            server.listen()
+
+        it "should serve files as 'content-type: text/plain'", (done) ->
+            request.get "http://localhost:3141/file with space.html", (err, res, body) ->
+                res.should.have.header "content-type", "text/plain"
+                res.should.have.status 200
+                done()
+
+        after ->
+            server.close()

 after ->
     # Delete the test files
     fs.unlinkSync "test/testfiles/file.txt"
     fs.unlinkSync "test/testfiles/file with space.html"
+    fs.unlinkSync "test/testfiles/.hiddenfile"
     fs.rmdirSync "test/testfiles"


Session is a coffeescript file

Session is a coffeescript file

--- scripts/views/workspace/menu/auth.coffee
+++ scripts/views/workspace/menu/auth.coffee
@@ -3,7 +3,7 @@
   'underscore'
   'backbone'
   'marionette'
-  'session'
+  'cs!session'
   'cs!collections/content'
   'hbs!templates/workspace/menu/auth'
   'bootstrapTooltip'


cleanup[]

cleanup[]

--- api/apps/articles/test/integration.coffee
+++ api/apps/articles/test/integration.coffee
@@ -62,7 +62,6 @@
       request
         .get("http://localhost:5000/articles?published=false")
       .end (err, res) ->
-          console.log "MOOO", res.body
           res.body.message.should.containEql 'published=true'
           done()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/text.coffee
+++ test/text.coffee
@@ -3,7 +3,7 @@
 test "text moves up vertically", ->
   text = Text()

-
+  equals text

 test "text fades out", ->
   ;


remove angular ngTouch from angularInit

remove angular ngTouch from angularInit

--- app/assets/javascripts/angularInit.coffee
+++ app/assets/javascripts/angularInit.coffee
@@ -4,5 +4,4 @@
 #= require humanize-duration
 #= require angular-timer
 #= require angular-animate
-#= require angular-touch
 #= require angular-bootstrap


CoffeeScript cleanup

CoffeeScript cleanup

--- app/assets/javascripts/app/reports.coffee
+++ app/assets/javascripts/app/reports.coffee
@@ -28,7 +28,7 @@
       active: window.activeTab
       beforeActivate: (event, ui) ->
         # if there was an old error message, fade it out now
-        $('#error-msg').fadeOut();
+        $('#error-msg').fadeOut()

         self.update_href ui.newTab
         true
@@ -41,7 +41,7 @@
         ui.jqXHR.error (xhr, status, error) ->
           # don't show error message if it's because of user aborting ajax request
           if status != 'abort'
-            $('#error-msg').html('Sorry, but the tab could not load. Please try again soon.').show();
+            $('#error-msg').html('Sorry, but the tab could not load. Please try again soon.').show()

       load: (event, ui) ->
         self.fix_bad_dates(ui.panel)
@@ -78,8 +78,8 @@

   update_export_urls: ->
     url = @tab_url(@current_tab())
-    $('#export').attr('href', url + '&export_id=report&format=csv');
-    $('#export-all').attr('href', url + '&export_id=report_data&format=csv');
+    $('#export').attr('href', url + '&export_id=report&format=csv')
+    $('#export-all').attr('href', url + '&export_id=report_data&format=csv')

 $ ->
   window.report = new TabbableReports($('#refresh-form'))


Fix bouygues telecom konnector (urls and date parsing)

--- server/konnectors/bouyguestelecom.coffee
+++ server/konnectors/bouyguestelecom.coffee
@@ -88,7 +88,7 @@
 logIn = (requiredFields, bills, data, next) ->

     loginUrl = 'https://www.mon-compte.bouyguestelecom.fr/cas/login'
-    billUrl = "http://www.bouyguestelecom.fr/mon-compte/mes-factures/historique"
+    billUrl = "https://www.bouyguestelecom.fr/parcours/mes-factures/historique"
     userAgent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:36.0) ' + \
                 'Gecko/20100101 Firefox/36.0'

@@ -146,9 +146,12 @@

 # Procedure to extract bill data from the page.
 parsePage = (requiredFields, bills, data, next) ->
-    baseDlUrl = "http://www.bouyguestelecom.fr"
-    baseDlUrl += "/mon-compte/facture/download/index"
+    baseDlUrl = "https://www.bouyguestelecom.fr"
+    baseDlUrl += "/parcours/facture/download/index"
     bills.fetched = []
+
+    # Set moment locale for the date parsing
+    moment.locale('fr')

     # Load page to make it browseable easily.
     $ = cheerio.load data.html


hack to get the server working

hack to get the server working

--- app/assets/javascripts/projects.js.coffee
+++ app/assets/javascripts/projects.js.coffee
@@ -4,7 +4,7 @@

 do_poll = ->
-    $.post('/ajax/queue_status')
+    $.post('/beta/ajax/queue_status')
         .done( (data)->
             console.log('done: '+data.project_list))
         .fail( ->


minor formatting

minor formatting

--- app/assets/jax/controllers/wire.js.coffee
+++ app/assets/jax/controllers/wire.js.coffee
@@ -1,9 +1,11 @@
 # Attempts to implement single-pass wire frame (http://cgg-journal.com/2008-2/06/index.html) in WebGL.
 Jax.Controller.create "wire",
   index: ->
-    tpmesh = new Jax.Mesh.Teapot material: new Jax.Material.Wire
+    tpmesh = new Jax.Mesh.Teapot
+      material: new Jax.Material.Wire

     @point = @world.addLight new Jax.Light.Point
+      shadows: false
       attenuation:
         constant: 0
         linear: 0
@@ -14,8 +16,10 @@
         specular: [1, 0, 0, 1]
       position: [-20, 0, 0]
       direction: [20, 0, -25]
-
-    @world.addLight new Jax.Light.Directional direction: [-1, -1, -1]
+
+    @world.addLight new Jax.Light.Directional
+      shadows: false
+      direction: [-1, -1, -1]
     @world.addObject new Jax.Framerate ema: no
     @world.addObject new Jax.Model
       position: [0, 0, -3]


api 测试更新

api 测试更新

--- tests/api.coffee
+++ tests/api.coffee
@@ -27,23 +27,15 @@
         vfwebqq
     }

-
-    # log 'psessionid',psessionid
-    # log 'client_id',client_id
-    # log 'ptwebqq',ptwebqq
-    # log 'uin',uin
-    # log 'vfwebqq',vfwebqq
+

-    # api.long_poll client_id , psessionid , (ret)->
-    #     log ret
+    api.get_friend_list uin, ptwebqq, vfwebqq, (ret,e)->
+        log 'friend',ret
+        log ''

-    # api.get_friend_list uin, ptwebqq, vfwebqq, (ret,e)->
-    #     log 'friend',ret
-    #     log ''
-
-    # api.get_group_list vfwebqq, (ret , e)->
-    #     log 'group',ret
-    #     log ''
+    api.get_group_list vfwebqq, (ret , e)->
+        log 'group',ret
+        log ''

     # api.get_group_member 86784314, vfwebqq , (ret,e)->
@@ -53,11 +45,11 @@
     # api.send_msg_2buddy 2440652742, "你好啊" , auth_opts, (ret,e)->
     #     log "buddy send ret:",ret

-    api.send_msg_2group 2559225925, "系统提示：hahha" , auth_opts, (ret,e)->
-        log "group send ret:",ret
+    # api.send_msg_2group 2559225925, "系统提示：hahha" , auth_opts, (ret,e)->
+    #     log "group send ret:",ret

-    log "长轮训"
-    api.long_poll auth_opts , (ret)->
-        log ret
+    # log "长轮训"
+    # api.long_poll auth_opts , (ret)->
+    #     log ret

 test_api()


Properly detect if user is logged in for broadcasts

--- assets/scripts/app/controllers/flash.coffee
+++ assets/scripts/app/controllers/flash.coffee
@@ -17,8 +17,8 @@
   ).property('broadcasts.isLoaded', 'broadcasts.length')

   broadcasts: (->
-    if @get('currentUser') then Travis.Broadcast.find() else Ember.A()
-  ).property('currentUser')
+    if @get('currentUser.id') then Travis.Broadcast.find() else Ember.A()
+  ).property('currentUser.id')

   loadFlashes: (msgs) ->
     for msg in msgs


Add character to SpriteCommandSchema say (#302)

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -92,6 +92,12 @@
       ogg: c.shortString(title: 'OGG', format: 'sound-file')
       preload: {title: 'Preload', description: 'Whether to load this sound file before the level can begin (typically for the first dialogue of a level).', type: 'boolean' }
     responses: c.array {title: 'Buttons', description: 'An array of buttons to include with the dialogue, with which the user can respond.'}, ResponseSchema
+    character: c.shortString(
+      title: 'Character'
+      description: 'The character portrait to use for the say message. Not setting this, or choosing the default, will use the Vega character.'
+      enum: ['vega', 'blank', 'capella']
+      default: 'vega'
+    )
     i18n: {type: 'object', format: 'i18n', props: ['blurb', 'text', 'sound'], description: 'Help translate this message'}
   move: c.object {title: 'Move', description: 'Tell the Thang to move.', required: ['target'], default: {target: {}, duration: 500}},
     target: _.extend _.cloneDeep(PointSchema), {title: 'Target', description: 'Target point to which the Thang will move.', default: {x: 20, y: 20}}


Notify when trying to wip something outside the repo.

--- lib/git-wip.coffee
+++ lib/git-wip.coffee
@@ -73,7 +73,13 @@
       cdPath = directory = directories[0]
       if path? and @isFilePath(path)
         re = new RegExp directory
-        return unless re.test path
+        unless re.test path
+          messageFileNotInRepo = "The file that was saved is not part of this
+            project's git repository. Therefore no WIP checkpoint was saved.
+            If you're sure this file is part of the current project's repo,
+            make sure you don't have multiple top-level folders open."
+          @notifyUser messageFileNotInRepo, "error"
+          return
         command += " \"Atom autosave: #{path}\" --editor -- #{path}"
       else
         command+= " \"Atom autosave\" --editor --untracked "


Fixed tabs

Fixed tabs

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -2547,7 +2547,7 @@

 		# Content Type
 		contentType = document.get('contentTypeRendered') or document.get('contentType')
-    res.setHeader('Content-Type', contentType);
+		res.setHeader('Content-Type', contentType);

 		# Send
 		dynamic = document.get('dynamic')


Removing a bogus comment

Removing a bogus comment

--- src/lib/engine.coffee
+++ src/lib/engine.coffee
@@ -1,28 +1,4 @@
 # Resolver of the repository selectors
-
-# library interface:
-#
-#   gitwalk 'github:user/repo@branch', gitwalk.proc.files '.*'
-#     ((file_path, done) ->
-#     ),
-#     ((err) ->
-#     )
-#
-#   gitwalk 'github:user/repo@branch', (gitwalk.proc.shell 'ls -l .',
-#       (err) ->
-#     )
-#
-#   gitwalk 'github:user/repo@branch', (err, repo, calback) ->
-#     ((err) ->
-#     )
-#
-
-# terminal ui:
-#
-#  gitwalk 'github:user/repo@branch' files '.*' 'grep exp #{file}'
-#  gitwalk 'github:user/repo@branch' commits '.*' 'echo #{msg} #{author} #{sha}'
-#  gitwalk 'github:user/repo@branch' shell 'ls -l #{repo}'
-#

 fs = require 'fs'
 path = require 'path'


Use the callback url to redirect the user

--- app/controllers/session-controller.coffee
+++ app/controllers/session-controller.coffee
@@ -1,5 +1,6 @@
 EmailPasswordController = require './email-password-controller'
 debug = require('debug')('meshblu-email-password-authenticator:sessions-controller')
+url = require 'url'

 class SessionController
   constructor: (uuid, meshblu) ->
@@ -7,10 +8,16 @@

   create: (request, response) =>
     debug "Got request: #{JSON.stringify(request.body)}"
-    {email,password}  = request.body
+    {email,password,callbackUrl}  = request.body

     @emailPasswordController.getToken email, password, (error, device) =>
       return response.status(401).send error.message if error?
-      response.status(201).send token: device.token
+
+      uriParams = url.parse callbackUrl
+      uriParams.query ?= {}
+      uriParams.query.uuid = device.uuid
+      uriParams.query.token = device.token
+
+      response.redirect url.format uriParams

 module.exports = SessionController


fixed a small bug with unfocusing regions

--- app/assets/javascripts/carmenta/carmenta_editor.js.coffee
+++ app/assets/javascripts/carmenta/carmenta_editor.js.coffee
@@ -80,9 +80,9 @@
     Carmenta.bind 'focus:frame', => @iframe.focus()
     Carmenta.bind 'focus:window', => setTimeout((=> @focusableElement.focus()), 10)

-    @document.mousedown ->
+    @document.mousedown (event) ->
       Carmenta.trigger('hide:dialogs')
-      Carmenta.trigger('unfocus:regions')
+      Carmenta.trigger('unfocus:regions') unless $(event.target).closest('.carmenta-region').get(0) == Carmenta.region.element.get(0)

     $(window).resize => @resize()
     window.onbeforeunload = Carmenta.beforeUnload


Added `@data` to postrenders.

Added `@data` to postrenders.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -246,7 +246,7 @@
             # Apply postrender before sending response.
             res.render args[0], args[1], (err, str) ->
               jsdom.env html: str, src: [jquery], done: (err, window) ->
-                rendered = postrenders[args[1].postrender].apply({}, [$, window])
+                rendered = postrenders[args[1].postrender].apply({data: ctx.data}, [$, window])

                 doctype = (window.document.doctype or '') + "\n"
                 res.send doctype + window.document.documentElement.outerHTML


fix in_running_context to set scope.ctx

fix in_running_context to set scope.ctx

--- app/context.coffee
+++ app/context.coffee
@@ -218,13 +218,17 @@
   add_component ctx, nested_context.component
   apply_to nested_context, fn, args

-apply_to = (ctx, fn, args) ->
-  previous_context = ctx.scope.ctx
-  ctx.scope.ctx = ctx
+
+splice_ctx = (ctx, target_ctx, fn, args) ->
+  previous_context = target_ctx.scope.ctx
+  target_ctx.scope.ctx = ctx
   try
     fn.apply ctx, args
   finally
-    ctx.scope.ctx = previous_context
+    target_ctx.scope.ctx = previous_context
+
+apply_to = (ctx, fn, args) ->
+  splice_ctx ctx, ctx, fn, args

 value = (value) -> _lead_context_fn_value: value

@@ -250,7 +254,7 @@

 in_running_context = (ctx, fn, args) ->
   throw new Error 'no active running context. did you call an async function without keeping the context?' unless running_context_binding?
-  apply_to running_context_binding, fn, args
+  splice_ctx running_context_binding, ctx, fn, args

 # TODO this is an awful name
 context_run_context_prototype =


add downsampling for band series

add downsampling for band series

--- modules/plot/main/coffee/Utils.coffee
+++ modules/plot/main/coffee/Utils.coffee
@@ -147,20 +147,26 @@

 #calculate the average point in a data point array
 dataAverage = (array) ->
-  x = 0
-  y = 0
   length = array.length
-  for d in array
-    x += d.x
-    y += d.y
-  x: x/length
-  y: y/length
+  if array[0].y != undefined
+    sum = array.reduce((a,b) -> {x: a.x+b.x, y: a.y+b.y})
+    x: sum.x/length
+    y: sum.y/length
+  else
+    sum = array.reduce((a,b) -> {x: a.x+b.x, y1: a.y1+b.y1, y2: a.y2+b.y2})
+    x: sum.x/length
+    y1: sum.y1/length
+    y2: sum.y2/length

 #find the data point in an array with the largest triangle forming with two selected points
 maxTriangle = (data1, array, data2) ->
   maxArea = -1
   for d in array
-    area = Math.abs((data1.x-data2.x)*(d.y-data1.y)-(data2.y-data1.y)*(data1.x-d.x))
+    if d.y != undefined
+      area = Math.abs((data1.x-data2.x)*(d.y-data1.y)-(data2.y-data1.y)*(data1.x-d.x))
+    else
+      area = Math.abs((data1.x-data2.x)*(Math.abs(d.y1-d.y2)-Math.abs(data1.y2-data1.y1)) -
+          (Math.abs(data2.y2-data2.y1)-Math.abs(data1.y2-data1.y1))*(data1.x-d.x))
     if area > maxArea
       maxArea = area
       data = d


Hides explore (home search) box on map drag

--- app/assets/javascripts/modules/maps/interactive.js.coffee
+++ app/assets/javascripts/modules/maps/interactive.js.coffee
@@ -7,6 +7,7 @@

     listen: ->
       @map.on('click', @handleMapClick)
+      @map.on('dragstart', @handleMapDrag)

     addMarker: (coords, protected_area) =>
       if @currentMarker?
@@ -30,6 +31,9 @@
           @addMarker(coords, data[0])
       )

+    handleMapDrag: =>
+      $('.explore').fadeOut()
+
     linkTo: (pa) ->
       "#{pa.name}"


Defer breakage of Concept Map if we don't have esper.

--- app/views/artisans/LevelConceptMap.coffee
+++ app/views/artisans/LevelConceptMap.coffee
@@ -7,7 +7,8 @@
 CocoCollection = require 'collections/CocoCollection'
 Campaigns = require 'collections/Campaigns'
 Levels = require 'collections/Levels'
-parser = new esper().realm.parser
+unless typeof esper is 'undefined'
+  parser = new esper().realm.parser

 module.exports = class LevelConceptMap extends RootView
   template: template


Tell Aloha where to get ghbook plugins.

Tell Aloha where to get ghbook plugins.

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -36,6 +36,9 @@
       map:
         '*':
           'semanticblock/semanticblock-plugin': 'semanticblock/lib/semanticblock-plugin'
+
+    bundles:
+      ghbook: '../../../../scripts/aloha'

     plugins:
       # All the plugins we use in Aloha


Remove checks for oldIE

Remove checks for oldIE

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -21,12 +21,12 @@

     sidebar: {disabled:true}

-    #requireConfig:
-    #  paths:
+    requireConfig:
+      paths:
         # Override location of jquery-ui and use our own. Because
         # jquery-ui and bootstrap conflict in a few cases (buttons,
         # tooltip) our copy has those removed.
-        #jqueryui: '../../oerpub/js/jquery-ui-1.9.0.custom-aloha'
+        jqueryui: '../../oerpub/js/jquery-ui-1.9.0.custom-aloha'

     errorhandling: true
     plugins:


Fix consistency in code

Fix consistency in code

--- app/initialize.coffee
+++ app/initialize.coffee
@@ -1,7 +1,9 @@
 initialize = ->

   # Add Davy promises if available and we are using Exoskeleton
-  if Backbone.Deferred and Davy? then Backbone.Deferred = -> new Davy
+  if Backbone.Deferred and Davy?
+    Backbone.Deferred = ->
+      new Davy

   # Set up Rivets if available
   rivets?.adapters[':'] =


wip: get posting to validator.nu working

--- app/assets/javascripts/hctbox/html_validator.js.coffee
+++ app/assets/javascripts/hctbox/html_validator.js.coffee
@@ -9,25 +9,33 @@
     $(document).on 'click', @selector, @onClick

   onClick: =>
-    data = new FormData()
+    data = document.documentElement.outerHTML
+    console.info data

-      fragment: document.documentElement.outerHTML
-      prefill: '0'
-      doctype: 'HTML5'
-      prefill_doctype: 'html401'
-      group:   '0'
-      ss:      '1'
-      st:      '1'
-      outline: '1'
-      No200:   '1'
-      verbose: '1'
+    $.ajax
+      url: 'https://validator.nu/?out=json&showsource=yes'
+      data: data
+      type: 'POST'
+      contentType: 'text/html'
+      success: @onValidatingMarkup
+      error: @onError
+      beforeSend: @beforeSend

-    $.post 'http://validator.w3.org/check', data, @onValidatingMarkup, 'multipart/form-data;'
     false

   onValidatingMarkup: (data)=>
+    alert(10)
     console.info data
     $('body').append(data)
+
+  onError: (xhr, status, error)->
+    console.info xhr
+    console.info status
+    console.info error
+
+  beforeSend: (xhr, settings)->
+    console.info(xhr)
+    true

 # create an instance of html validator


Default unstack to true (instead of false). This fixes non-stacked graphs.

--- app/assets/javascripts/pgdatagraph/datagraph.js.coffee
+++ app/assets/javascripts/pgdatagraph/datagraph.js.coffee
@@ -14,7 +14,7 @@
     series: {}
     dateFormat: "M d, yy"
     renderer: "line"
-    unstack: no
+    unstack: true
     detailSmoothing: 10
     overviewSmoothing: 10
     yAxisTickFormat: (y) -> y


Support default value for getArgumentValue in statecharts

--- app/assets/javascripts/rglossa/react/statechart.coffee
+++ app/assets/javascripts/rglossa/react/statechart.coffee
@@ -30,13 +30,13 @@
   # Searches for a state argument (i.e. data stored in the args property on a state)
   # that matches the given name, beginning at the current state and working upwards
   # towards the root state.
-  getArgumentValue: (argname) ->
+  getArgumentValue: (argname, defaultValue = null) ->
     state = @currentState
     while state
       value = state.args?[argname]
       return value if value?
       state = state.parent
-    return null
+    return defaultValue

   # Checks whether the current state path contains the given subpath, which
   # should be a string of one or more state names separated by a slash


Remove task/phase relationship code.

Remove task/phase relationship code.

--- app/assets/javascripts/services/event_stream.js.coffee
+++ app/assets/javascripts/services/event_stream.js.coffee
@@ -50,21 +50,9 @@

   createOrUpdateTask: (action, esData) ->
     taskId = esData.task.id
-    if oldTask = @store.findTask(taskId)
-      oldPhase = oldTask.get('phase')
     @store.pushPayload('task', esData)
     task = @store.findTask(taskId)
-    phase = task.get("phase")
-    if action == 'created'
-      # This is an ember bug.  A task's phase needs to be notified that the other side of
-      # the hasMany relationship has changed via set.  Simply loading the updated task into the store
-      # won't trigger the relationship update.
-      phase.get('tasks').addObject(task)
-    if action == 'updated' && phase != oldPhase
-      phase.get('tasks').addObject(task)
-      oldPhase.get('tasks').removeObject(oldTask)
-      task.set('phase', phase)
-
+    # didLoad updates the task's thumbnail
     task.triggerLater('didLoad')

   eventStreamActions:


Remove unused stuff.

Remove unused stuff.

[ci skip]

--- app/assets/javascripts/services/event_stream.js.coffee
+++ app/assets/javascripts/services/event_stream.js.coffee
@@ -89,11 +89,8 @@
           task.triggerLater('didDelete')

     meta: (modelName, id) ->
-      @set('inFlightRecord', [modelName, id])
       Ember.run =>
         if model = @store.getById(modelName, id)
-          model.reload().then =>
-            @set('inFlightRecord', null)
+          model.reload()
         else
-          @store.find(modelName, id).then =>
-            @set('inFlightRecord', null)
+          @store.find(modelName, id)


add the alert body into log

add the alert body into log

--- lib/pagerv2.coffee
+++ lib/pagerv2.coffee
@@ -705,6 +705,7 @@
         result.push(alert.body.details.pd_description)
       else
         @robot.logger.warning 'no details found in the alerts'
+	@robot.logger.warning alert.body
     return result


newfiles: fix message when you delete a single item

fixes: CNVS-17021 CNVS-17027

test plan:
delete multiple items.
it should say: %{count} items deleted successfully.
delete a single item.
it should say: %{name} deleted successfully.

Change-Id: I53d5d4dbec69babd74899e0cb00248b6cc7baedc
Reviewed-on: https://gerrit.instructure.com/44707
Reviewed-by: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/react_files/utils/deleteStuff.coffee
+++ app/coffeescripts/react_files/utils/deleteStuff.coffee
@@ -15,7 +15,7 @@
         name: filesAndFolders[0]?.displayName()
       })
     else
-      I18n.t('confirm_delete', {
+      I18n.t({
         one: "Are you sure you want to delete %{name}?",
         other: "Are you sure you want to delete these %{count} items?"
       }, {
@@ -34,9 +34,15 @@
           reason = try
             $.parseJSON(response.responseText)?.message

-          $.flashError I18n.t 'delete_error', 'Error deleting %{name}: %{reason}',
+          $.flashError I18n.t 'Error deleting %{name}: %{reason}',
             name: item.displayName()
             reason: reason

     $.when(promises...).then ->
-      $.flashMessage(I18n.t('deleted_items_successfully', '%{count} items deleted successfully', {count: filesAndFolders.length}))
+      $.flashMessage(I18n.t({
+        one: '%{name} deleted successfully.'
+        other: '%{count} items deleted successfully.'
+      }, {
+        count: filesAndFolders.length
+        name: filesAndFolders[0]?.displayName()
+      }))


implementing cell, quad borders

implementing cell, quad borders

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -43,16 +43,22 @@
 @_new_description = ( S ) ->
   R =
     '~isa':     'MKTS/TABLE/description'
-    # grid:       { width: 4, height: 4, }
-    # ### default unit for width, height: ###
-    # u:
-    #   width:    '10mm'
-    #   height:   '10mm'
-    cells:              []
+    cellquads:          {}
+    cellborders:        {}
     quadwidths:         null
     quadheights:        null
     joint_coordinates:  null
-    cellgrid:           false
+    quadgrid:           false
+    #.......................................................................................................
+    styles:
+      sThin:              'thin'
+      sThick:             'thick'
+      sDotted:            'dotted'
+      sDashed:            'dashed'
+      sRed:               'red'
+      sBlack:             'black'
+      sQuadgrid:          'sRed,sDotted,sThin'
+    #.......................................................................................................
     default:
       gridwidth:  4
       gridheight: 4
@@ -112,31 +118,50 @@
   return null

 #-----------------------------------------------------------------------------------------------------------
-@cell = ( me, text ) ->
-  unless ( type = CND.type_of text ) is 'text'
-    throw new Error "(MKTS/TABLE 9791) need a text for mkts-table/cell, got a #{type}"
+@cellquads = ( me, text ) ->
+  unless ( type = CND.type_of text ) is 'text'
+    throw new Error "(MKTS/TABLE 9791) need a text for mkts-table/cellquads, got a #{type}"
   #.........................................................................................................
   @_ensure_gridwidth  me
   @_ensure_gridheight me
   @_ensure_unitvector me
-  cell = @_parse_range_quadref me, text
-  if cell.right > me.gridwidth
+  d           = @_parse_range_quadref me, text
+  designation = d.tl.toUpperCase()
+  if d.right > me.gridwidth
     throw new Error "(MKTS/TABLE 1274) cell exceeds grid width: #{rpr text}"
-  if cell.bottom > me.gridheight
+  if d.bottom > me.gridheight
     throw new Error "(MKTS/TABLE 6069) cell exceeds grid height: #{rpr text}"
-  me.cells.push cell
-  #.........................................................................................................
-  return null
-
-#-----------------------------------------------------------------------------------------------------------
-@cellgrid = ( me, text ) ->
+  if me.cellquads[ designation ]?
+    throw new Error "(MKTS/TABLE 6069) unable to redefine cell #{designation}: #{rpr text}"
+  #.........................................................................................................
+  me.cellquads[ designation ] = d
+  return null
+
+#-----------------------------------------------------------------------------------------------------------
+@cellborder = ( me, text ) ->
+  unless ( type = CND.type_of text ) is 'text'
+    throw new Error "(MKTS/TABLE 9791) need a text for mkts-table/cellborder, got a #{type}"
+  #.........................................................................................................
+  d                 = @_parse_cellborder me, text
+  if d.side is '*'
+    for side in [ 'left', 'right', 'top', 'bottom', ]
+      target            = me.cellborders[ d.cellref ]?= {}
+      target[ side ]    = d.style
+  else
+    target            = me.cellborders[ d.cellref ]?= {}
+    target[ d.side ]  = d.style
+  #.........................................................................................................
+  return null
+
+#-----------------------------------------------------------------------------------------------------------
+@quadgrid = ( me, text ) ->
   unless ( type = CND.type_of text ) is 'text'
     throw new Error "(MKTS/TABLE 9791) need a text for mkts-table/cell, got a #{type}"
   #.........................................................................................................
   switch text
-    when 'true'   then me.cellgrid = true
-    when 'false'  then me.cellgrid = false
-    else throw new Error "(MKTS/TABLE 9791) expected 'true' or 'false' for mkts-table/cellgrid, got a #{rpr text}"
+    when 'true'   then me.quadgrid = true
+    when 'false'  then me.quadgrid = false
+    else throw new Error "(MKTS/TABLE 9791) expected 'true' or 'false' for mkts-table/quadgrid, got a #{rpr text}"
   #.........................................................................................................
   return null

@@ -198,6 +223,16 @@
   [ _, x, y, ] = match
   return { x, y, }

+#-----------------------------------------------------------------------------------------------------------
+### TAINT use proper parsing tool ###
+@_parse_cellborder = ( me, cellborder ) ->
+  unless ( type = CND.type_of cellborder ) is 'text'
+    throw new Error "(MKTS/TABLE 3975) expected a text for cellborder, got a #{rpr type}"
+  unless ( match = cellborder.match /^\s*([A-Z]{1,3}[-0-9.]{1,4})-(left|right|top|bottom|\*)\s*:\s*(.+)$/ )?
+    throw new Error "(MKTS/TABLE 2658) expected a cellborder like 'a1-left:sDashed,sThick', got #{rpr cellborder}"
+  [ _, cellref, side, style, ] = match
+  return { cellref, side, style, }
+

 #===========================================================================================================
 # EVENT GENERATORS
@@ -205,13 +240,15 @@
 @_walk_events = ( me ) ->
   #.........................................................................................................
   yield from @_walk_opening_events                      me
+  yield from @_walk_style_events                        me
   yield from @_walk_cellspacing_events                  me
   yield from @_walk_column_and_row_coordinates_events   me
   yield from @_walk_joint_coordinates_events            me
   yield from @_walk_quad_sides_events                   me
   yield from @_walk_quad_coordinates_events             me
   yield from @_walk_debugging_events                    me
-  yield from @_walk_cellgrid_events                     me
+  yield from @_walk_quadgrid_events                     me
+  yield from @_walk_borders_events                      me
   yield from @_walk_closing_events                      me
   #.........................................................................................................
   # ### dump description for debugging ###
@@ -224,7 +261,7 @@
   # yield [ ')', 'code', [],                       ( copy me.meta ), ]
   # yield [ 'tex', '\\par{}', ]
   #.........................................................................................................
-  return null
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_opening_events = ( me ) ->
@@ -240,14 +277,20 @@
   yield [ 'tex', "\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = round ]%\n", ]
   yield [ 'tex', "\\tikzset{ x = #{me.unitheight} };%\n", ]
   yield [ 'tex', "\\tikzset{ y = #{me.unitheight} };%\n", ]
-  return null
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_closing_events = ( me ) ->
   yield [ 'tex', "\\end{tikzpicture}%\n", ]
   yield [ 'tex', "\\end{minipage}}}%\n", ]
   yield [ 'tex', "\\par% End of MKTS Table ====================================================================================\n\n", ]
-  return null
+  yield return
+
+#-----------------------------------------------------------------------------------------------------------
+@_walk_style_events = ( me ) ->
+  for key, value of me.styles
+    yield [ 'tex', "\\tikzset{#{key}/.style={#{value}}}%\n", ]
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_cellspacing_events = ( me ) ->
@@ -255,7 +298,7 @@
   return null unless me.cellspacing?
   yield [ 'tex', "\\coordinate (horizontal spacing)  at ( #{me.cellspacing.x}, 0 );%\n", ]
   yield [ 'tex', "\\coordinate (vertical spacing)    at ( 0, #{me.cellspacing.y} );%\n", ]
-  return null
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_column_and_row_coordinates_events = ( me ) ->
@@ -264,14 +307,14 @@
   y_position  = 0
   #.........................................................................................................
   for [ col_letter, col_nr, ] from @_walk_column_letters_and_numbers me, 'long'
-    yield [ 'tex', "\\coordinate (c#{col_letter}) at ( #{x_position}, 0 );%\n", ]
+    yield [ 'tex', "\\coordinate (col_#{col_letter}) at ( #{x_position}, 0 );%\n", ]
     x_position += me.quadwidths[ col_nr ]
   #.........................................................................................................
   for row_nr from @_walk_row_numbers me, 'long'
-    yield [ 'tex', "\\coordinate (r#{row_nr}) at ( 0, #{y_position} );%\n", ]
+    yield [ 'tex', "\\coordinate (row_#{row_nr}) at ( 0, #{y_position} );%\n", ]
     y_position += me.quadheights[ row_nr ]
   #.........................................................................................................
-  return null
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_joint_coordinates_events = ( me ) ->
@@ -283,23 +326,23 @@
   for [ col_letter, col_nr, ] from @_walk_column_letters_and_numbers me, 'long'
     for row_nr from @_walk_row_numbers me, 'long'
       joint = "#{col_letter}#{row_nr}"
-      yield [ 'tex', "\\coordinate (joint #{joint}) at ($ (c#{col_letter}) + (r#{row_nr}) $);%\n", ]
-  #.........................................................................................................
-  return null
+      yield [ 'tex', "\\coordinate (joint_#{joint}) at ($ (col_#{col_letter}) + (row_#{row_nr}) $);%\n", ]
+  #.........................................................................................................
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_quad_sides_events = ( me ) ->
   @_ensure_joint_coordinates  me
   #.........................................................................................................
   for [ col_letter, col_nr, ] from @_walk_column_letters_and_numbers me, 'long'
-    yield [ 'tex', "\\coordinate (c#{col_letter} W) at ($ (c#{col_letter}) - (horizontal spacing) $);%\n", ]
-    yield [ 'tex', "\\coordinate (c#{col_letter} E) at ($ (c#{col_letter}) + (horizontal spacing) $);%\n", ]
+    yield [ 'tex', "\\coordinate (col_#{col_letter} W) at ($ (col_#{col_letter}) - (horizontal spacing) $);%\n", ]
+    yield [ 'tex', "\\coordinate (col_#{col_letter} E) at ($ (col_#{col_letter}) + (horizontal spacing) $);%\n", ]
   #.........................................................................................................
   for row_nr from @_walk_row_numbers me, 'long'
-    yield [ 'tex', "\\coordinate (r#{row_nr} N) at ($ (r#{row_nr}) - (vertical spacing) $);%\n", ]
-    yield [ 'tex', "\\coordinate (r#{row_nr} S) at ($ (r#{row_nr}) + (vertical spacing) $);%\n", ]
-  #.........................................................................................................
-  return null
+    yield [ 'tex', "\\coordinate (row_#{row_nr} N) at ($ (row_#{row_nr}) - (vertical spacing) $);%\n", ]
+    yield [ 'tex', "\\coordinate (row_#{row_nr} S) at ($ (row_#{row_nr}) + (vertical spacing) $);%\n", ]
+  #.........................................................................................................
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_quad_coordinates_events = ( me ) ->
@@ -313,16 +356,16 @@
     for row_nr_1 from @_walk_row_numbers me, 'short'
       row_nr_2  = row_nr_1 + 1
       quad = "#{col_letter_1}#{row_nr_1}"
-      yield [ 'tex', "\\coordinate (quad #{quad} top left)      at ($ (r#{row_nr_1} S) + (c#{col_letter_1} E) $);%\n", ]
-      yield [ 'tex', "\\coordinate (quad #{quad} top right)     at ($ (r#{row_nr_1} S) + (c#{col_letter_2} W) $);%\n", ]
-      yield [ 'tex', "\\coordinate (quad #{quad} bottom left)   at ($ (r#{row_nr_2} N) + (c#{col_letter_1} E) $);%\n", ]
-      yield [ 'tex', "\\coordinate (quad #{quad} bottom right)  at ($ (r#{row_nr_2} N) + (c#{col_letter_2} W) $);%\n", ]
-  #.........................................................................................................
-  return null
-
-#-----------------------------------------------------------------------------------------------------------
-@_walk_cellgrid_events = ( me ) ->
-  return null unless me.cellgrid
+      yield [ 'tex', "\\coordinate (quad_#{quad} top left)      at ($ (row_#{row_nr_1} S) + (col_#{col_letter_1} E) $);%\n", ]
+      yield [ 'tex', "\\coordinate (quad_#{quad} top right)     at ($ (row_#{row_nr_1} S) + (col_#{col_letter_2} W) $);%\n", ]
+      yield [ 'tex', "\\coordinate (quad_#{quad} bottom left)   at ($ (row_#{row_nr_2} N) + (col_#{col_letter_1} E) $);%\n", ]
+      yield [ 'tex', "\\coordinate (quad_#{quad} bottom right)  at ($ (row_#{row_nr_2} N) + (col_#{col_letter_2} W) $);%\n", ]
+  #.........................................................................................................
+  yield return
+
+#-----------------------------------------------------------------------------------------------------------
+@_walk_quadgrid_events = ( me ) ->
+  return null unless me.quadgrid
   #.........................................................................................................
   ### TAINT code duplication; use iterator ###
   for [ col_letter_1, col_nr_1, ] from @_walk_column_letters_and_numbers me, 'short'
@@ -332,12 +375,29 @@
     for row_nr_1 from @_walk_row_numbers me, 'short'
       row_nr_2  = row_nr_1 + 1
       quad = "#{col_letter_1}#{row_nr_1}"
-      yield [ 'tex', "\\draw[ red, line width = 0.2mm ] (quad #{quad} top    left)  -- (quad #{quad} top    right); % Quad a1 top\n",    ]
-      yield [ 'tex', "\\draw[ red, line width = 0.2mm ] (quad #{quad} top    left)  -- (quad #{quad} bottom left);  % Quad a1 left\n",   ]
-      yield [ 'tex', "\\draw[ red, line width = 0.2mm ] (quad #{quad} bottom left)  -- (quad #{quad} bottom right); % Quad a1 bottom\n", ]
-      yield [ 'tex', "\\draw[ red, line width = 0.2mm ] (quad #{quad} top    right) -- (quad #{quad} bottom right); % Quad a1 right\n",  ]
-  #.........................................................................................................
-  return null
+      yield [ 'tex', "\\draw[sQuadgrid] (quad_#{quad} top    left)  -- (quad_#{quad} top    right);%\n",  ]
+      yield [ 'tex', "\\draw[sQuadgrid] (quad_#{quad} top    left)  -- (quad_#{quad} bottom left);%\n",   ]
+      yield [ 'tex', "\\draw[sQuadgrid] (quad_#{quad} bottom left)  -- (quad_#{quad} bottom right);%\n",  ]
+      yield [ 'tex', "\\draw[sQuadgrid] (quad_#{quad} top    right) -- (quad_#{quad} bottom right);%\n",  ]
+  #.........................................................................................................
+  yield return
+
+#-----------------------------------------------------------------------------------------------------------
+@_walk_borders_events = ( me ) ->
+  #.........................................................................................................
+  for designation, cellquads of me.cellquads
+    continue unless ( cellborders = me.cellborders[ designation ] )?
+    for d from @_walk_cellquads_sides me, cellquads, '*'
+      continue unless ( borderstyle = cellborders[ d.side ] )?
+      switch d.side
+        when 'top', 'bottom'
+          yield [ 'tex', "\\draw[#{borderstyle}] (quad_#{d.quad} #{d.side} left) -- (quad_#{d.quad} #{d.side} right);\n", ]
+        when 'left', 'right'
+          yield [ 'tex', "\\draw[#{borderstyle}] (quad_#{d.quad} top #{d.side}) -- (quad_#{d.quad} bottom #{d.side});\n", ]
+        else
+          throw new Error "(MKTS/TABLE 2658) illegal value for side #{rpr d.side}"
+  #.........................................................................................................
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_debugging_events = ( me ) ->
@@ -347,10 +407,10 @@
   for [ col_letter, col_nr, ] from @_walk_column_letters_and_numbers me, 'long'
     for row_nr from @_walk_row_numbers me, 'long'
       joint = "#{col_letter}#{row_nr}"
-      yield [ 'tex', "\\node[ color = gray ] at ($(joint #{joint})+(2mm,2mm)$) {{\\mktsStyleCode{}#{joint}}}; ", ]
-      yield [ 'tex', "\\node[ color = gray, shape = circle, draw ] at (joint #{joint}) {};%\n", ]
-  #.........................................................................................................
-  return null
+      yield [ 'tex', "\\node[ color = gray ] at ($(joint_#{joint})+(2mm,2mm)$) {{\\mktsStyleCode{}#{joint}}}; ", ]
+      yield [ 'tex', "\\node[ color = gray, shape = circle, draw ] at (joint_#{joint}) {};%\n", ]
+  #.........................................................................................................
+  yield return

 #===========================================================================================================
@@ -405,12 +465,53 @@
     ### TAINT don't use EXCJSCC directly ###
     col_letter  = ( EXCJSCC.n2l col_nr ).toLowerCase()
     yield [ col_letter, col_nr, ]
+  yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_row_numbers = ( me, mode ) ->
   @_ensure_gridheight me
   delta = if mode is 'short' then 0 else 1
   yield row_nr for row_nr in [ 1 .. me.gridheight + delta ]
+  yield return
+
+#-----------------------------------------------------------------------------------------------------------
+@_walk_cellquads_sides = ( me, cellquads, side ) ->
+  switch side
+    when 'left'
+      row_nr_1    = cellquads.top
+      row_nr_2    = cellquads.bottom
+      col_nr_1    = cellquads.left
+      col_nr_2    = cellquads.left
+    when 'right'
+      row_nr_1    = cellquads.top
+      row_nr_2    = cellquads.bottom
+      col_nr_1    = cellquads.right
+      col_nr_2    = cellquads.right
+    when 'top'
+      row_nr_1    = cellquads.top
+      row_nr_2    = cellquads.top
+      col_nr_1    = cellquads.left
+      col_nr_2    = cellquads.right
+    when 'bottom'
+      row_nr_1    = cellquads.bottom
+      row_nr_2    = cellquads.bottom
+      col_nr_1    = cellquads.left
+      col_nr_2    = cellquads.right
+    when '*'
+      yield from @_walk_cellquads_sides me, cellquads, 'left'
+      yield from @_walk_cellquads_sides me, cellquads, 'right'
+      yield from @_walk_cellquads_sides me, cellquads, 'top'
+      yield from @_walk_cellquads_sides me, cellquads, 'bottom'
+      yield return
+    else
+      throw new Error "(MKTS/TABLE 2658) illegal argument for side #{rpr side}"
+  for row_nr in [ row_nr_1 .. row_nr_2 ]
+    for col_nr in [ col_nr_1 .. col_nr_2 ]
+      ### TAINT don't use EXCJSCC directly ###
+      col_letter  = ( EXCJSCC.n2l col_nr ).toLowerCase()
+      quad        = "#{col_letter}#{row_nr}"
+      yield { col_nr, row_nr, col_letter, quad, side, }
+  yield return

 #===========================================================================================================


make sure atom uses 'which' command to check for pros installation on unix systems

--- lib/proscli.coffee
+++ lib/proscli.coffee
@@ -79,7 +79,9 @@
       cb obj.code, mapResponse fmt, obj

     if cliVer != null and not force then respond cliVer
-    @execute cmd: ['where', 'pros'], nosb: nosb, cb: (c, o) =>
+    which = ['which', 'pros']
+    if navigator.platform is 'Win32' then which[0] = 'where'
+    @execute cmd: which, nosb: nosb, cb: (c, o) =>
       if c != 0
         # console.log o
         cliVer = {code: 2}


Add comment with scraper object structure

--- lib/scraper.coffee
+++ lib/scraper.coffee
@@ -1,5 +1,26 @@
 cheerio = require 'cheerio'
 request = require 'request'
+
+###
+The Scraper class is responsible for downloading and extracting information
+from StackOverflow pages. "scrapeURL" serves as the encompassing function
+which produces a data object given a url. The object follows this structure:
+
+{
+  question: "<question string>"
+  answers: [
+    {
+      author: "<author username>",
+      votes: <number of votes>,
+      accepted: <true|false>,
+      sections: [{
+        type: "<code|text>",
+        body: "<string contents of the section>"
+      }]
+    }
+  ]
+}
+###

 class Scraper
   scrapeURL: (soLink) ->


Links tags in sidebar to a search

Links tags in sidebar to a search

--- app/talk/init.cjsx
+++ app/talk/init.cjsx
@@ -10,6 +10,7 @@
 ProjectLinker = require './lib/project-linker'
 ROLES = require './lib/roles'
 auth = require '../api/auth'
+{Link} = require 'react-router'

 module?.exports = React.createClass
   displayName: 'TalkInit'
@@ -59,7 +60,7 @@
     <BoardPreview {...@props} key={i} data={data} />

   tag: (t, i) ->
-
#{t.name}

+    <Link key={i} query={query: t.name} to="talk-search">#{t.name}</Link>{' '}

   roleReadLabel: (data, i) ->
     <input type="radio" name="role-read" value={data}/>{data}


Analyze observable in real time, without explicit analyze

--- assets/javascripts/react/components/result_control.coffee
+++ assets/javascripts/react/components/result_control.coffee
@@ -27,14 +27,11 @@
   getInitialState: ->
     notifications: {}
     time: 0
-
-  componentWillUpdate: ->
-    @hideAnalyzeError() if @state.notifications.error
+    playing: false

   handleTimeChange: (time) ->
     if @state.time != time
-      NotificationStore.setVirtualTime(time, @state.notifications)
-      @setState time: time
+      @setState time: time, playing: false

   hideAnalyzeError: ->
     @showInErrorArea()
@@ -45,28 +42,26 @@
   showInErrorArea: (component)->
     React.render(component, @refs.analyzeError.getDOMNode())

-  analyze: ->
-    notifications = getNotifications(@props.observable)
-    if !notifications.error
-      @handleNewNotifications(notifications)
-    else
-      @showAnalyzeError()
-      @handleNewNotifications(notifications)
-
-  handleNewNotifications: (notifications) ->
-    NotificationStore.setVirtualTime(0, notifications)
-
-    @setState notifications: notifications
-    @setState time: 0
-
   componentWillReceiveProps: (nextProps) ->
     unless R.eq(@props.observable, nextProps.observable)
-      @setState notifications: {}
+      @setState notifications: getNotifications(nextProps.observable)
+
+  componentWillUpdate: (nextProps, nextState) ->
+    @hideAnalyzeError() if @state.notifications.error
+    @showAnalyzeError() if nextState.notifications.error
+
+    unless nextState.playing
+      # could optimize somewhat to filter out pointless time changes
+      NotificationStore.setVirtualTime(nextState.time, nextState.notifications)
+
+  componentDidMount: ->
+    # force componentWillUpdate
+    @setState notifications: getNotifications(@props.observable)

   play: ->
-    timeCallback = (time) =>
-      @setState time: time
-    NotificationStore.play @state.notifications, @state.time, timeCallback
+    playCallback = (time) =>
+      @setState time: time, playing: true
+    NotificationStore.play @state.notifications, @state.time, playCallback

   render: ->
     showReplayArea = R.keys(@state.notifications).length > 0 && !@state.notifications.error
@@ -77,9 +72,6 @@
     ]

     <ButtonToolbar>
-      <Button className="analyze" id="analyze" onClick={@analyze} bsStyle="success" style={marginBottom: '5px'}>
-      Analyze
-

-      </Button>
+

       { if showReplayArea then replayArea }
     </ButtonToolbar>


Use dev cobalt when testing as well as staging.

--- server/code/model/box.coffee
+++ server/code/model/box.coffee
@@ -168,7 +168,8 @@
     Math.floor(Math.random() * (max - min + 1)) + min

   @listServers: ->
-    if /staging/.test process.env.NODE_ENV
+    if /testing|staging/.test process.env.NODE_ENV
+      console.log process.env.CU_BOX_SERVER
       return [process.env.CU_BOX_SERVER]
     _.uniq (obj.boxServer for plan, obj of plans)


Prefer original package name on translations

--- def/ko/settings.cson
+++ def/ko/settings.cson
@@ -76,7 +76,7 @@
       {
         _id: 'core.allowPendingPaneItems'
         title: "패널 아이템 미리보기 허용"
-        desc: "트리뷰에서 파일을 한 번 클릭 했을 때, 아이템을 탭에 추가하지 않고 미리보기 형태로 이전 패널과 겹쳐 보여줍니다."
+        desc: "Tree View에서 파일을 한 번 클릭 했을 때, 아이템을 탭에 추가하지 않고 미리보기 형태로 이전 패널과 겹쳐 보여줍니다."
       }
       {
         _id: 'core.audioBeep'
@@ -338,7 +338,7 @@
       _template: "${text1} <a>${text2}</a> ${text3}"
     }
     title1: "UI 테마"
-    description1: "이 테마는 탭, 상태 막대, 트리뷰, 그리고 드롭다운 메뉴의 스타일을 지정합니다"
+    description1: "이 테마는 탭, 상태 막대, Tree View, 그리고 드롭다운 메뉴의 스타일을 지정합니다"
     title2: "문법 테마"
     description2: "이 테마는 에디터 안의 텍스트 스타일을 지정합니다"
   }


console: make render possibly more robust

--- salvus/page/console.coffee
+++ salvus/page/console.coffee
@@ -314,6 +314,8 @@
             @_ignore_mesg = false

     render: (data) =>
+        if not data?
+            return
         try
             @terminal.write(data)
             if @value == ""


default editor settings

default editor settings

--- salvus/page/r_account.cjsx
+++ salvus/page/r_account.cjsx
@@ -47,14 +47,18 @@
 # Register account actions
 flux.createActions('account', AccountActions)

+
 # Define account store
 class AccountStore extends Store
     constructor: (flux) ->
         super()
         ActionIds = flux.getActionIds('account')
         @register(ActionIds.setTo, @setTo)
-        @state =
-            user_type : if localStorage.remember_me? then 'signing_in' else 'public'  # default
+
+        # Use the database defaults for all account info until this gets set after they login
+        @state = misc.deep_copy(require('schema').SCHEMA.accounts.user_query.get.fields)
+        @state.user_type = if localStorage.remember_me? then 'signing_in' else 'public'  # default
+

     setTo: (payload) ->
         @setState(payload)


show DataStoreUsageMeter in the sidebar

show DataStoreUsageMeter in the sidebar

--- imports/ui/pages/StoragePage.cjsx
+++ imports/ui/pages/StoragePage.cjsx
@@ -1,7 +1,7 @@
 { Meteor }          = require 'meteor/meteor'
 React               = require 'react'

-{ Article, Box, Header, Heading, Search, Title, Box, List, ListItem, Split } = require 'grommet'
+{ Article, Box, Header, Heading, Search, Title, Box, Split, Sidebar, Paragraph } = require 'grommet'

 DataStoreUsageMeter = require '../viz/DataStoreUsageMeter.cjsx'
 DataBucketsList     = require '../lists/DataBucketsList.cjsx'
@@ -13,13 +13,23 @@
     ds = @props.dataStore
     splitFlex = if @props.selectedAppName then 'right' else 'left'

-

-
-        <Title responsive=true truncate=true>Storage Buckets</Title>
-        <Search onDOMChange={@userSearch} placeHolder='Search...' inline=true fill=true size='medium' />
-
-      <Box align='center' justify='center' direction='row'>
-        <DataStoreUsageMeter used={ds.used} free={ds.free} />
-      </Box>
-      <DataBucketsList buckets={@props.buckets} />
-

+    <Split flex='left' priority='left'>
+

+
+            <Title responsive=true truncate=true>Storage Buckets</Title>
+            <Search onDOMChange={@userSearch} placeHolder='Search...' inline=true fill=true size='medium' />
+
+          <DataBucketsList buckets={@props.buckets} />
+

+      <Sidebar size='medium' colorIndex='light-2' direction='column'>
+        <Box align='center' justify='center' direction='column'>
+
+            <Title>Data Store</Title>
+            <Paragraph align='center'>
+              Overall storage capacity for the storage buckets.
+            </Paragraph>
+
+          <DataStoreUsageMeter used={ds.used} free={ds.free} />
+        </Box>
+      </Sidebar>
+    </Split>


UI/slideshow getting some debuggging back

--- ui/slideshow/src/slideshow.coffee
+++ ui/slideshow/src/slideshow.coffee
@@ -187,7 +187,7 @@

   # calculates the end size of the whole block
   _end_size: (item)->
-    @__clone or= @clone().style(visibility: 'hidden').insertTo(@, 'before')
+    @__clone or= @clone().style(visibility: 'hidden').insertTo(@, 'after')
     @__clone.style(display: '')

     item = item.clone().style(display: 'block', position: 'relative')


Replace large comprehension with much simpler code; fix comment to reference maxSize instead of previous 10KB limit; only pop a history entry if the file size is actually bigger than maxSize; only add items to the history if they are different from the last item by storing the last line and loading it on startup.

--- repl.coffee
+++ repl.coffee
@@ -82,8 +82,9 @@

 # Store and load command history from a file
 addHistory = (repl, filename, maxSize) ->
+  lastLine = null
   try
-    # Get file info and at most 10KB of command history
+    # Get file info and at most maxSize of command history
     stat = fs.statSync filename
     size = Math.min maxSize, stat.size
     # Read last `size` bytes from the file
@@ -93,17 +94,19 @@
     # Set the history on the interpreter
     repl.rli.history = buffer.toString().split('\n').reverse()
     # If the history file was truncated we should pop off a potential partial line
-    repl.rli.history.pop() if size is maxSize
+    repl.rli.history.pop() if stat.size > maxSize
     # Shift off the final blank newline
     repl.rli.history.shift() if repl.rli.history[0] is ''
     repl.rli.historyIndex = -1
+    lastLine = repl.rli.history[0]

   fd = fs.openSync filename, 'a'

   repl.rli.addListener 'line', (code) ->
-    if code and code.length and code isnt '.history'
+    if code and code.length and code isnt '.history' and lastLine isnt code
       # Save the latest command in the file
       fs.write fd, "#{code}\n"
+    lastLine = code

   process.on 'exit', ->
     fs.closeSync fd
@@ -112,8 +115,7 @@
   repl.commands['.history'] =
     help: 'Show command history'
     action: ->
-      history = (repl.rli.history[k] for k in Object.keys(repl.rli.history)).reverse()
-      repl.outputStream.write "#{history.join '\n'}\n"
+      repl.outputStream.write "#{repl.rli.history[..].reverse().join '\n'}\n"
       repl.displayPrompt()

 module.exports =


Fix connecting routing

Fix connecting routing

--- app/src/routes.coffee
+++ app/src/routes.coffee
@@ -4,6 +4,8 @@
 ledger.router.pluggedWalletRoutesExceptions = [
   '/',
   '/onboarding/device/plug'
+  '/onboarding/device/connecting'
+  '/onboarding/device/forged'
 ]

 # routes declarations


give up on mermaid

give up on mermaid

--- lib/pandoc-wrapper.coffee
+++ lib/pandoc-wrapper.coffee
@@ -175,13 +175,14 @@

   return processCodes(codes, lines, rootDirectoryPath, callback)

-saveSvgAsPng = (svgElement, dest, cb)->
+saveSvgAsPng = (svgElement, dest, option={}, cb)->
   return cb(null) if !svgElement or svgElement.tagName.toLowerCase() != 'svg'

-  width = null #svgElement.offsetWidth
-  height = null #svgElement.offsetHeight
-
-  svgAsPngUri svgElement, {}, (data)->
+  if typeof(option) == 'function' and !cb
+    cb = option
+    option = {}
+
+  svgAsPngUri svgElement, option, (data)->
     base64Data = data.replace(/^data:image\/png;base64,/, "")
     fs.writeFile dest, base64Data, 'base64', (err)->
       cb(err)
@@ -208,20 +209,18 @@
               atom.notifications.addError 'mermaid error', detail: err

             if mermaidAPI.parse(content)
-              container = document.getElementsByClassName('markdown-preview-enhanced')[0]
               div = document.createElement('div')
               div.classList.add('mermaid')
               div.textContent = content
-              container.appendChild(div)
+              document.body.appendChild(div)

               mermaid.init null, div, ()->
                 svgElement = div.getElementsByTagName('svg')[0]
-                return cb(null, null) if not svgElement

                 dest = path.resolve(rootDirectoryPath, (Math.random().toString(36).substr(2, 9)) + '_mermaid.png')

-                saveSvgAsPng svgElement, dest, (error)->
-                  container.removeChild(div)
+                saveSvgAsPng svgElement, dest, {}, (error)->
+                  document.body.removeChild(div)
                   cb(null, {dest, start, end, content})
             else
               cb(null, null)
@@ -237,7 +236,11 @@

             dest = path.resolve(rootDirectoryPath, (Math.random().toString(36).substr(2, 9)) + '_viz.png')

-            saveSvgAsPng div.children[0], dest, (error)->
+            svgElement = div.children[0]
+            width = svgElement.getBBox().width
+            height = svgElement.getBBox().height
+
+            saveSvgAsPng svgElement, dest, {width, height}, (error)->
               cb(null, {dest, start, end, content})

@@ -256,7 +259,11 @@

               dest = path.resolve(rootDirectoryPath, (Math.random().toString(36).substr(2, 9)) + '_puml.png')

-              saveSvgAsPng div.children[0], dest, (error)->
+              svgElement = div.children[0]
+              width = svgElement.getBBox().width
+              height = svgElement.getBBox().height
+
+              saveSvgAsPng div.children[0], dest, {width, height}, (error)->
                 cb(null, {dest, start, end, content})

         asyncFunc = helper(start, end, content)


Fix in-reply-to header, refs #405

Fix in-reply-to header, refs #405

--- server/models/account.coffee
+++ server/models/account.coffee
@@ -601,6 +601,10 @@
     # Returns (callback) {Object} info, the nodemailer infos
     sendMessage: (message, callback) ->
         return callback null, messageId: 66 if @isTest()
+        # In NodeMailer, inReplyTo header should be a string, so we only keep
+        # the first message ID (if replying to more than one message, others
+        # ID will be in references header)
+        message.inReplyTo = message.inReplyTo.shift()
         options =
             port: @smtpPort
             host: @smtpServer


Fix date format for pull requests

Fix date format for pull requests

--- lib/util/lifecycle.coffee
+++ lib/util/lifecycle.coffee
@@ -13,7 +13,7 @@
 { Directory, File, BufferedProcess } = require 'atom'

 branchRegex = /origin\/feature\/(\d+)\/(\w+)\/(\d+)/
-TODAY_FORMAT = "MMM d YYYY - HH:mm"
+TODAY_FORMAT = "MMM D YYYY - HH:mm"
 FORBIDDEN_BRANCHES = ["master", "develop"]
 STATUS =
   'INIT': 0


removed troubleshooting code

removed troubleshooting code

--- lib/utils/retsHttp.coffee
+++ lib/utils/retsHttp.coffee
@@ -10,11 +10,6 @@

 callRetsMethod = (methodName, retsSession, queryOptions) ->
-  ###
-  Promise.resolve
-    body: require('fs').readFileSync("/Users/joe/work/realtymaps/tmp/dump_#{methodName}_#{queryOptions.offset||0}.xml")
-    response: {"headers":{"rets-version":"RETS/1.7.2","server":"nginx/1.6.0"}}
-  ###
   logger.debug("RETS #{methodName}", queryOptions)
   Promise.try () ->
     retsSession(qs: queryOptions)
@@ -31,7 +26,6 @@

 streamRetsMethod = (methodName, retsSession, queryOptions, failCallback) ->
-  #require('fs').createReadStream("/Users/joe/work/realtymaps/tmp/dump_#{methodName}_#{queryOptions.offset||0}.xml")
   logger.debug("RETS #{methodName} stream", queryOptions)
   done = false
   errorHandler = (error) ->


Update pushbullet.coffee

Update pushbullet.coffee

--- src/pushbullet.coffee
+++ src/pushbullet.coffee
@@ -13,8 +13,6 @@
 # Author:
 #   dualmoon

-##TODO:
-##make sure we have HUBOT_PUSHBULLET_API_KEY
 if not process.env.HUBOT_PUSHBULLET_API_KEY
   console.log "HUBOT_PUSHBULLET_API_KEY was not set! This plugin won't work!"
 else


rename reactive variable for clarity

rename reactive variable for clarity

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -41,12 +41,12 @@
   str.charAt(0).toUpperCase() + str.slice(1)
 camelToSnake = (str) -> str.replace(/\W+/g, '_').replace(/([a-z\d])([A-Z])/g, '$1-$2')

-@currentPage = new ReactiveVar()
-@currentMenuItem = new ReactiveVar()
+@currentSubCat = new ReactiveVar()
+@currentCat = new ReactiveVar()

 getCurrentSubCat = ->
   for group in bootstrap_magic_variables
-    if group.keyName is currentPage.get()
+    if group.keyName is currentSubCat.get()
       return group

 initColorPicker = (node) ->
@@ -73,11 +73,11 @@
   BootstrapMagic.start() if BootstrapMagic.start

 Template._bootstrap_magic.rendered = ->
-  currentMenuItem.set bootstrap_magic_variables[0].category
-  currentPage.set bootstrap_magic_variables[0].keyName
+  currentCat.set bootstrap_magic_variables[0].category
+  currentSubCat.set bootstrap_magic_variables[0].keyName
   @.autorun ->
-    currentPage.get()
-    currentMenuItem.get()
+    currentSubCat.get()
+    currentCat.get()
     Meteor.defer -> initColorPicker($('.color-picker-area'))

 Template._bootstrap_magic.helpers
@@ -89,14 +89,14 @@
   #   return bootstrap_magic_variables

   "categories" : ->  _.map _.groupBy(bootstrap_magic_variables, 'category'), (obj) ->  obj[0]
-  "subCategories" : ->  _.map _.where(bootstrap_magic_variables, { category: currentMenuItem.get() }), (obj) -> obj
+  "subCategories" : ->  _.map _.where(bootstrap_magic_variables, { category: currentCat.get() }), (obj) -> obj
   "previewTmpl" : -> Template["bootstrap_magic_preview_#{camelToSnake @keyName}"] || null
   "inputTmpl" : -> Template["bootstrap_magic_input_#{@type}"] || null
   "formattedCat" : -> formatCamel @category
   "typeIs" : (type) -> @type is type
   "currentSubCat" : getCurrentSubCat
-  "isSelectedCat" : -> @category is currentMenuItem.get()
-  "isSelectedSubCat" : ->  @keyName is currentPage.get()
+  "isSelectedCat" : -> @category is currentCat.get()
+  "isSelectedSubCat" : ->  @keyName is currentSubCat.get()

 Template._bootstrap_magic.events
   'change input.bootstrap-magic-input' : (e) ->
@@ -104,10 +104,10 @@
     BootstrapMagic.setOverride $input.attr('name'), $input.val()

   'click .menu-secondary-list' : ->
-    currentMenuItem.set(@category)
-    if (_.map _.where(bootstrap_magic_variables, { category: currentMenuItem.get() }), (obj) -> obj).length is 1
-      currentPage.set(_.where(bootstrap_magic_variables, { category: currentMenuItem.get() })[0].keyName)
+    currentCat.set(@category)
+    if (_.map _.where(bootstrap_magic_variables, { category: currentCat.get() }), (obj) -> obj).length is 1
+      currentSubCat.set(_.where(bootstrap_magic_variables, { category: currentCat.get() })[0].keyName)

-  'click .menu-tertiary-list' : -> currentPage.set(@keyName)
+  'click .menu-tertiary-list' : -> currentSubCat.set(@keyName)

   # 'click #reset' :-> do something


Updated Atom packages

Updated Atom packages

--- atom/packages.cson
+++ atom/packages.cson
@@ -5,11 +5,11 @@
   "docker"
   "file-icons"
   "file-types"
+  "file-watcher"
   "git-blame"
   "git-log"
   "git-plus"
   "git-time-machine"
-  "hex"
   "highlight-line"
   "highlight-selected"
   "intentions"
@@ -37,6 +37,5 @@
   "todo-show"
   "tool-bar"
   "tool-bar-main"
-  "toolbar-iconshortcuts"
   "tree-view-git-status"
 ]


Add busy-signal and intentions packages

Add busy-signal and intentions packages

To quash "Enabling packages linter and intentions that are needed
for linter-ui-default" info message.

--- atom/packages.cson
+++ atom/packages.cson
@@ -3,6 +3,7 @@
   "atom-alignment"
   "atom-beautify"
   "atom-transpose"
+  "busy-signal"
   "change-case"
   "color-picker"
   "docblockr"
@@ -10,6 +11,7 @@
   "emmet"
   "expand-region"
   "git-blame"
+  "intentions"
   "language-apache"
   "language-applescript"
   "language-awk"


Implemented issue #2395 - Search term is deleted after I opened a object/ticket.

--- app/assets/javascripts/app/controllers/navigation.coffee
+++ app/assets/javascripts/app/controllers/navigation.coffee
@@ -16,9 +16,8 @@
     'submit form.search-holder': 'preventDefault'
     'dblclick form.search-holder .icon-magnifier': 'openExtendedSearch'
     'focus #global-search': 'searchFocus'
-    'blur #global-search': 'searchBlur'
     'keyup #global-search': 'listNavigate'
-    'click .js-global-search-result': 'emptyAndClose'
+    'click .js-global-search-result': 'emptyAndCloseDelayed'
     'click .js-details-link': 'openExtendedSearch'
     'change .js-menu .js-switch input': 'switch'

@@ -162,7 +161,7 @@
   renderResult: (result = [], noChange) =>
     if noChange
       return
-
+
     @removePopovers()

     # remove result if not result exists
@@ -202,19 +201,11 @@
     $('#app').append @notificationWidget.el

   searchFocus: (e) =>
-    @query = '' # reset query cache
+    @clearDelay('emptyAndCloseDelayed')
+    @throttledSearch()
     App.PopoverProvidable.anyPopoversDestroy()
     @searchContainer.addClass('focused')
-
-  searchBlur: (e) =>
-    # delay to be able to click x
-    update = =>
-      query = @searchInput.val().trim()
-      if !query
-        @emptyAndClose()
-        return
-
-    @delay(update, 100, 'removeFocused')
+    @selectAll(e)

   listNavigate: (e) =>
     if e.keyCode is 27 # close on esc
@@ -231,9 +222,9 @@
       href = @$('.global-search-result .nav-tab.is-hover').attr('href')
       if href
         @navigate(href)
+        @emptyAndCloseDelayed()
       else
         @openExtendedSearch()
-      @emptyAndClose()
       return

     # on other keys, show result
@@ -277,18 +268,24 @@
       @scrollToIfNeeded(prev, false)

   emptyAndClose: =>
+    @andClose()
+    @andEmpty()
+
+  emptyAndCloseDelayed: =>
+    @andClose()
+    delay = =>
+      @andEmpty()
+    @delay(delay, 60000, 'emptyAndCloseDelayed')
+
+  andEmpty: =>
     @query = ''
     @searchInput.val('')
+
+  andClose: =>
     @searchContainer.removeClass('focused filled open no-match loading')
     @globalSearch.close()
     @delayedRemoveAnyPopover()
-
-  andClose: =>
-    @query = ''
     @searchInput.blur()
-    @searchContainer.removeClass('open no-match loading')
-    @globalSearch.close()
-    @delayedRemoveAnyPopover()

   search: =>
     query = @searchInput.val().trim()
@@ -301,9 +298,10 @@
     @query = query

     if @query == ''
-      @searchContainer.removeClass('open')
-      return
-
+      @searchContainer.removeClass('open loading')
+      return
+
+    @searchContainer.addClass('open')
     @globalSearch.search(query: @query)

   filterNavbar: (values, user, parent = null) ->
@@ -460,7 +458,7 @@
     if e
       e.preventDefault()
     query = @searchInput.val()
-    @searchInput.val('').blur()
+    @emptyAndClose()
     if query
       @navigate("#search/#{encodeURIComponent(query)}")
       return


SidebarMachineList: separate the header click handler

--- client/app/lib/activity/sidebar/sidebarmachinelist.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinelist.coffee
@@ -1,8 +1,9 @@
-kd = require 'kd'
-KDCustomHTMLView = kd.CustomHTMLView
-CustomLinkView = require 'app/customlinkview'
+kd                = require 'kd'
+KDCustomHTMLView  = kd.CustomHTMLView
+
+MoreVMsModal      = require 'app/activity/sidebar/morevmsmodal'
+CustomLinkView    = require 'app/customlinkview'
 SidebarMachineBox = require 'app/activity/sidebar/sidebarmachinebox'
-MoreVMsModal = require 'app/activity/sidebar/morevmsmodal'

 module.exports = class SidebarMachineList extends KDCustomHTMLView
@@ -31,7 +32,7 @@
       tagName  : 'h3'
       cssClass : 'sidebar-title'
       partial  : title
-      click    : => new MoreVMsModal {}, @getMachines()
+      click    : @bound 'headerClickHandler'

     if hasPlusIcon
       @header.addSubView new CustomLinkView
@@ -39,6 +40,10 @@
         title    : ' '

     @addSubView @header
+
+
+  headerClickHandler: ->
+    new MoreVMsModal {}, @getMachines()

   addMachineBoxes: (boxes) ->


Fix typo in documentation

Fix typo in documentation

--- src/rivescript.coffee
+++ src/rivescript.coffee
@@ -758,7 +758,7 @@
   #
   # Freeze the variable state of a user. This will clone and preserve the user's
   # entire variable state, so that it can be restored later with
-  # `thatUservars()`
+  # `thawUservars()`
   ##
   freezeUservars: (user) ->
     if @_users[user]?


fix warning and workaround for mini editors

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -44,6 +44,7 @@
       title: 'Preview'
       description: 'This field does nothing, it\'s just here to preview your cursor'
       type: 'string'
+      default: ''

   activate: ->
     @subs = new CompositeDisposable()
@@ -89,6 +90,7 @@
         editorPresenter.startBlinkingCursorsAfterDelay = do ->
           _.debounce(editorPresenter.startBlinkingCursors, editorPresenter.getCursorBlinkResumeDelay())
       else
+        atom.config.set 'block-cursor.secondaryColor', atom.config.get 'block-cursor.primaryColor'
         editorPresenter.startBlinkingCursorsAfterDelay = ->

   applyPulseDuration: (duration) ->


revert some stuff

revert some stuff

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -115,8 +115,6 @@
       @updateCursorStyleForScope scopedConfig, editors, scopeName

   updateCursorStyleForScope: (scopedConfig, editors, scopeName) =>
-    if scopedConfig isnt @globalConfig and equals scopedConfig, @globalConfig, scopeName
-      return
     scopedConfig = @prepareConfig scopedConfig
     @updateStylesheet scopeName, scopedConfig
     for editor in editors


Removes previous variables markers as they are no longer needed

--- lib/color-buffer.coffee
+++ lib/color-buffer.coffee
@@ -42,6 +42,9 @@

     @subscriptions.add atom.config.observe 'pigments.ignoredScopes', (@ignoredScopes=[]) =>
       @emitter.emit 'did-update-color-markers', {created: [], destroyed: []}
+
+    # Needed to clean the serialized markers from previous versions
+    @editor.findMarkers(type: 'pigments-variable').forEach (m) => m.destroy()

     if colorMarkers?
       @restoreMarkersState(colorMarkers)


only show upgrade button

only show upgrade button


Use email to login

Use email to login

--- www/login.coffee
+++ www/login.coffee
@@ -5,7 +5,7 @@
 		'@##@'+document.getElementById('password').value)),window.ajax('')"

 	login_form.innerHTML="
-		Username:
<input id=email type=username></input>

+		Email:
<input id=email type=email></input>

 		Password:
<input id=password type=password></input>

 		<input type=submit value=Login></input>"


support Knitr/Sweave files (.Rnw/.Snw)

support Knitr/Sweave files (.Rnw/.Snw)

--- lib/latexer-hook.coffee
+++ lib/latexer-hook.coffee
@@ -30,7 +30,7 @@
       @unsubscribeBuffer()
       return unless @editor?
       title = @editor?.getTitle()
-      return unless title? and title.match(/\.tex$/)
+      return unless title? and title.match(/\.tex|[rs]nw$/i)
       @buffer = @editor.getBuffer()
       @disposableBuffer = @buffer.onDidStopChanging => @editorHook()


atom apm install white-cursor

atom apm install white-cursor

--- .atom/packages.cson
+++ .atom/packages.cson
@@ -6,12 +6,17 @@
   'language-batch'
   'language-powershell'
   'parcel'
+  'white-cursor'
   'atom-dark-syntax'
   'atom-dark-ui'
   'atom-light-syntax'
   'atom-light-ui'
   'base16-tomorrow-dark-theme'
   'base16-tomorrow-light-theme'
+  'one-dark-ui'
+  'one-dark-syntax'
+  'one-light-syntax'
+  'one-light-ui'
   'solarized-dark-syntax'
   'solarized-light-syntax'
   'archive-view'
@@ -37,6 +42,7 @@
   'link'
   'markdown-preview'
   'metrics'
+  'notifications'
   'open-on-github'
   'package-generator'
   'release-notes'
@@ -54,6 +60,7 @@
   'whitespace'
   'wrap-guide'
   'language-c'
+  'language-clojure'
   'language-coffee-script'
   'language-css'
   'language-gfm'


:art: minor refactoring.

:art: minor refactoring.

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -4,10 +4,7 @@
 {BlockwiseSelect} = require './visual-blockwise'

 swrap = require './selection-wrapper'
-{
-  toggleClassByCondition, getNewTextRangeFromCheckpoint
-  moveCursorLeft
-} = require './utils'
+{toggleClassByCondition, getNewTextRangeFromCheckpoint, moveCursorLeft} = require './utils'

 supportedModes = ['normal', 'insert', 'visual', 'operator-pending']
 supportedSubModes = ['characterwise', 'linewise', 'blockwise', 'replace']


:bug: always need to clearBlockwiseSelections()

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -176,6 +176,6 @@
         unless @vimState.operationStack.isProcessing()
           for blockwiseSelection in @vimState.getBlockwiseSelections()
             blockwiseSelection.restoreCharacterwise()
-          @vimState.clearBlockwiseSelections()
+        @vimState.clearBlockwiseSelections()

 module.exports = ModeManager


[Trivial] Add missing # in string interpolation.

--- src/statuspage.coffee
+++ src/statuspage.coffee
@@ -109,7 +109,7 @@
           msg.send "There are currently #{broken_components.length} components in a degraded state"
         if broken_components.length > 0
           msg.send "\nBroken Components:\n-------------\n"
-          msg.send ("#{component.name}: {component.status.replace(/_/g, ' ')}" for component in broken_components).join("\n") + "\n"
+          msg.send ("#{component.name}: #{component.status.replace(/_/g, ' ')}" for component in broken_components).join("\n") + "\n"
         if working_components.length > 0 && process.env.HUBOT_STATUS_PAGE_SHOW_WORKING == '1'
           msg.send "\nWorking Components:\n-------------\n"
           msg.send ("#{component.name}" for component in working_components).join("\n") + "\n"


Adds grunt test and default aliases

Adds grunt test and default aliases

--- grunt/workflow.coffee
+++ grunt/workflow.coffee
@@ -1,4 +1,7 @@
 module.exports = (grunt) ->
+  grunt.registerTask 'default', 'help'
+  grunt.registerTask 'help', 'availabletasks'
+
   grunt.registerTask 'dev', [
     'test'
     'watch'


Use scopeDescriptor for better validation of strings

--- lib/python-tools.coffee
+++ lib/python-tools.coffee
@@ -88,14 +88,25 @@
     bufferPosition = editor.getCursorBufferPosition()
     line = editor.lineTextForBufferRow(bufferPosition.row)

+    scopeDescriptor = editor.scopeDescriptorForBufferPosition(bufferPosition)
+    scopes = scopeDescriptor.getScopesArray()
+    debugger;
+
+    if "string.quoted.single.single-line.python" in scopes
+      delimiter = '\''
+    else if  'string.quoted.double.single-line.python' in scopes
+      delimiter = '"'
+    else
+      return
+
     start = end = bufferPosition.column

-    while line[start] != '\'' and line[start] != '"'
+    while line[start] != delimiter
       start = start - 1
       if start < 0
         return

-    while line[end] != line[start]
+    while line[end] != delimiter
       end = end + 1
       if end == line.length
         return