Updated dataobject snippet

Updated dataobject snippet

--- completions.cson
+++ completions.cson
@@ -307,18 +307,41 @@
   'prefix': 'classdataobject'
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
-  'body': """
-  /**
-   * ${3:Description}
-   *
-   * @package silverstripe
-   * @subpackage ${4:mysite}
-   */
-  class ${1} extends ${2:DataObject}
-  {
-  \t${0}
-  }
-  """
+  'definitions': {
+    'comment': """
+    /**
+     * ${3:Description}
+     *
+     * @package silverstripe
+     * @subpackage ${4:mysite}
+     */
+    """
+  }
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'body': """
+      use SilverStripe\\\\ORM\\\\DataObject;
+
+      {{comment}}
+      class ${1} extends ${2:DataObject}
+      {
+      \t${0}
+      }
+      """
+    }
+    {
+      'maxVersion': '4.0'
+      'body': """
+      {{comment}}
+      class ${1} extends ${2:DataObject}
+      {
+      \t${0}
+      }
+      """
+    }
+  ]
+

 'Class Model Admin Silverstripe':
   'prefix': 'classmodeladmin'


Added session and director namespaces

Added session and director namespaces

--- completions.cson
+++ completions.cson
@@ -2767,26 +2767,86 @@
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
   'body': "Session::set(\'${1}\', ${2})${0}"
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'useNamespace': [
+        'SilverStripe\\Control\\Session'
+      ]
+    }
+    {
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+    }
+  ]
 'Get session value':
   'prefix': 'sessionget'
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
   'body': "Session::get(\'${1}\')${0}"
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'useNamespace': [
+        'SilverStripe\\Control\\Session'
+      ]
+    }
+    {
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+    }
+  ]
 'Get all sessions':
   'prefix': 'sessiongetall'
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
   'body': "Session::get_all(\'${1}\')${0}"
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'useNamespace': [
+        'SilverStripe\\Control\\Session'
+      ]
+    }
+    {
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+    }
+  ]
 'Clear session value':
   'prefix': 'sessionclear'
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
   'body': "Session::clear(\'${1}\')${0}"
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'useNamespace': [
+        'SilverStripe\\Control\\Session'
+      ]
+    }
+    {
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+    }
+  ]
 'Clear all sessions':
   'prefix': 'sessionclearall'
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
   'body': "Session::clear_all(\'${1}\')${0}"
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'useNamespace': [
+        'SilverStripe\\Control\\Session'
+      ]
+    }
+    {
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+    }
+  ]

 # Director
 'Get the root URL':
@@ -2799,6 +2859,16 @@
     }
     {
       'scope': '.text.html.php'
+      'minVersion': '4.0'
+      'body': "Director::baseURL()"
+      'useNamespace': [
+        'SilverStripe\\Control\\Director'
+      ]
+    }
+    {
+      'scope': '.text.html.php'
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
       'body': "Director::baseURL()"
     }
   ]
@@ -2812,6 +2882,16 @@
     }
     {
       'scope': '.text.html.php'
+      'minVersion': '4.0'
+      'body': "Director::absoluteBaseURL()"
+      'useNamespace': [
+        'SilverStripe\\Control\\Director'
+      ]
+    }
+    {
+      'scope': '.text.html.php'
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
       'body': "Director::absoluteBaseURL()"
     }
   ]


cleaned up windows edits

cleaned up windows edits

--- component.coffee
+++ component.coffee
@@ -64,7 +64,6 @@
     if link
       orig = path.resolve process.cwd()
       process.chdir tgtDir
-      console.log "IN THERE"
       fs.symlinkSync join(orig, src), name, "dir"
       process.chdir orig
     else
@@ -96,8 +95,7 @@
 provide = (pth) ->
   if not exists pth
     _.reduce [""].concat(pth.split(pathSep)), (soFar, dir) ->
-      if not exists(join(soFar, dir))
-        fs.mkdirSync join(soFar, dir)
+      fs.mkdirSync join(soFar, dir) if not exists(join(soFar, dir))
       soFar += (if soFar then pathSep else "") + dir

 discoverTests = (dir) ->


Fixed broken test

Fixed broken test

--- test/Reporter.coffee
+++ test/Reporter.coffee
@@ -69,7 +69,7 @@
             rep.aggregate noErrors, 'some_file'
             expect(rep.warnCount).to.equal 0
             expect(rep.errCount).to.equal 0
-            expect(rep.log).to.equal ''
+            expect(rep.log).to.equal '\n'

     describe 'report', ->
         it 'should log the errors', ->


Fix for WinScriptHost

Fix for WinScriptHost

--- test/appveyor.coffee
+++ test/appveyor.coffee
@@ -6,7 +6,7 @@
     listen @
     @emit.apply @, arguments

-do intercept if api = process.env.APPVEYOR_API_URL
+do intercept if api = process?.env.APPVEYOR_API_URL

 post = (data, path)->
   url = require 'url'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/distance.coffee
+++ test/distance.coffee
@@ -14,6 +14,7 @@
     width: 10
     height: 10

-
+  enemy = GameObject
+

 module()


Fix newline insertion of code-block snippets

--- .config/snippets.cson
+++ .config/snippets.cson
@@ -112,4 +112,7 @@
 ".text.restructuredtext, .source.gfm.restructuredtext":
 	"Code block":
 		prefix: "pre"
-		body: ".. code-block::\n\t$1"
+		body: ".. code-block::\n\n\t$1"
+	"Code block (Alternate)":
+		prefix: "code"
+		body: ".. code-block::\n\n\t$1"


cache on for escaping optimized tests

cache on for escaping optimized tests

--- test/escaping.coffee
+++ test/escaping.coffee
@@ -16,11 +16,11 @@
   describe "h1 h(\")", ->
     it "should render
<script>alert('"owned" by c&a &copy;')</script>
", ->
       t = -> h1 h("")
-      cc.render(t, optimized: true).should.equal "
<script>alert('"owned" by c&a &copy;')</script>
"
+      cc.render(t, optimized: true, cache: on).should.equal "
<script>alert('"owned" by c&a &copy;')</script>
"

 describe 'AutoEscaping optimized', ->
   describe "h1 ", ->
     it "should render
<script>alert('"owned" by c&a &copy;')</script>
", ->
       t = -> h1 ""
-      cc.render(t, autoescape: yes, optimized: true).should.equal "
<script>alert('"owned" by c&a &copy;')</script>
"
+      cc.render(t, autoescape: yes, optimized: true, cache: on).should.equal "
<script>alert('"owned" by c&a &copy;')</script>
"


new test case, shows array asymmetry

new test case, shows array asymmetry

--- test/mongoose.coffee
+++ test/mongoose.coffee
@@ -275,6 +275,9 @@
     ,
       spec: tags: [String]
       expected: null # same as spec
+    ,
+      spec: tags: [{type: String}]
+      expected: tags: [String]
     ,
       spec: comments: [{ body: String }]
       expected: null # same as spec


fix teams api + codestyle

fix teams api + codestyle

--- models/team.coffee
+++ models/team.coffee
@@ -4,18 +4,17 @@

 # custom validation constraints
 checkUnique = (value, next) ->
-    User.find where: nickname: value
-        .then (user) ->
-            if user then return next 'This name already exists'
+    Team.find where: name: value
+        .then (team) ->
+            if team then return next 'This team name already exists'
             return next()
         .catch (err) ->
             return next err
-
+
 checkNotNull = (value, next) ->
     if value.length == 0
-        throw new Error('Atention valeur nulle')
-    else
-        next()
+        throw new Error 'Cette valeur ne peut être nulle'
+    else do next

 # def model
 Team = db.orm.define 'Team',
@@ -44,4 +43,4 @@
     EARTHLINGS: "EARTHLINGS"

 #export
-module.exports = Team+module.exports = Team


Updated configuration to add performance module

--- udefine/config.coffee
+++ udefine/config.coffee
@@ -1,3 +1,9 @@
+# Performance shim
+do (name = 'performance') ->
+  udefine.configure (root) ->
+    udefine.inject.add name
+
+# Animationframe
 do (names = ['requestAnimationFrame', 'cancelAnimationFrame']) ->
   udefine.configure (root) ->
     for n in names


Update config.cson

Update config.cson

--- Atom/config.cson
+++ Atom/config.cson
@@ -8,17 +8,20 @@
   editor:
     invisibles:
       {}
-    fontSize: 17
+    fontSize: 16
+    showIndentGuide: true
+    scrollPastEnd: true
   "atom-beautify":
     _analyticsUserId: ...
+    html_end_with_newline: true
+    html_max_preserve_newlines: 2
     js_max_preserve_newlines: 2
     js_jslint_happy: true
-    html_max_preserve_newlines: 2
     js_eval_code: true
     js_keep_array_indentation: true
     js_keep_function_indentation: true
     js_space_after_anon_function: true
+    js_end_with_newline: true
     language_js_beautify_on_save: true
-    js_end_with_newline: true
   "pigments":
     markerType: "dot"


Adding back sanitize

Adding back sanitize

--- app/assets/javascripts/darkswarm/darkswarm.js.coffee
+++ app/assets/javascripts/darkswarm/darkswarm.js.coffee
@@ -5,6 +5,7 @@
   'infinite-scroll',
   'angular-flash.service',
   'templates',
+  'ngSanitize',
   'backstretch']).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
   $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
   $httpProvider.defaults.headers.put['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')


change the order that events are bound (fixes a bug in saving and then the prompt when leaving the page)

--- app/assets/javascripts/mercury/page_editor.js.coffee
+++ app/assets/javascripts/mercury/page_editor.js.coffee
@@ -28,8 +28,8 @@
     @statusbar = new Mercury.Statusbar(jQuery.extend(true, {}, @options, @options.statusbarOptions))
     @resize()

+    @iframe.one 'load', => @bindEvents()
     @iframe.on 'load', => @initializeFrame()
-    @iframe.one 'load', => @bindEvents()
     @loadIframeSrc(null)


Add separator at the end of categories

Add separator at the end of categories

--- app/assets/javascripts/search_autocomplete.js.coffee
+++ app/assets/javascripts/search_autocomplete.js.coffee
@@ -85,10 +85,15 @@
         data = []

         # List results
+        firstCategory = true
         for suggestion in response

           # Add group header before list each group
           if lastCategory isnt suggestion.category
+            data.push 'divider' if !firstCategory
+
+            firstCategory = false if firstCategory
+
             data.push
               header: suggestion.category


Added `User.serialize()` function

Added `User.serialize()` function

--- app/assets/javascripts/sprangular/models/user.coffee
+++ app/assets/javascripts/sprangular/models/user.coffee
@@ -14,6 +14,10 @@

     @allowOneClick = @creditCards.length > 0 && @addresses.length > 0

+  serialize: ->
+    _.omit this, (value) ->
+      typeof(value) == 'object' || typeof(value) == 'function' || Array.isArray(value)
+
   _mergeAddressLists: ->
     addresses = []
     unique = (address) ->


Add `signature` and `expire` for `/group/` request

--- app/assets/javascripts/uploadcare/files/group.coffee
+++ app/assets/javascripts/uploadcare/files/group.coffee
@@ -115,6 +115,8 @@
         @__fileInfosDf.done (infos...) =>
           utils.jsonp "#{@settings.urlBase}/group/", 'POST',
             pub_key: @settings.publicKey
+            signature: @settings.secureSignature
+            expire: @settings.secureExpire
             files: for info in infos
               "/#{info.uuid}/#{info.cdnUrlModifiers or ''}"
           .fail (reason) =>


stash

stash

--- apps/editorial_features/components/eoy/client.coffee
+++ apps/editorial_features/components/eoy/client.coffee
@@ -40,6 +40,7 @@
   watchWindow: =>
     watchScrolling = _.throttle(@watchScrolling, 30)
     $(window).scroll =>
+      console.log 'scroll'
       if $(window).scrollTop() != @windowPosition
         watchScrolling()
     $(window).resize =>
@@ -220,4 +221,4 @@
     analyticsHooks.trigger 'scroll:sa-body'

 module.exports.init = ->
-  new EoyView+  new EoyView


Change messages and listen words

Change messages and listen words

--- scripts/circular.coffee
+++ scripts/circular.coffee
@@ -27,7 +27,7 @@
     ]
     ask_read: [
       "早速お読みください！読んだかどうか、後で確認しますね。"
-      "こりゃ必読だがや、みんな読んでおいてちょーでゃー！"
+      "こりゃ必読だがや、おみゃーさん、はよ読んでおいてちょーでゃーよ！"
       "Please read this circular in a few spare minutes."
     ]
     greet: [
@@ -44,22 +44,19 @@
     thank: [
       "ありがとうございます。"
       "Thanks for reading!!"
-      "読み終わっちゃったんだ。。。私のこと、忘れないでねッ （未練を残しつつも走り去る乙女）"
+      "ホントに読んだんだ。。。もう、お別れダネ。。私のこと、忘れないでねッ （未練を残しつつも走り去る乙女）"
     ]
     come_again: [
       "では後ほど声かけますね！"
       "ラピュタは滅びんよ、何度でもよみがえるさ"
       "I'll be back"
     ]
-    ask_again: [
-      "以上。読んだら「読んだ」って行ってくださいね。後で読むなら「待って」と。"
-      ":point_up: 面白かった？ [はい/いいえ]"
-      "Did you read yet? [yes/no]"
-    ]
     response_usage: [
-      "yes/no/what でお答えください！"
-      "I can accept only yes/no/what"
-      "返事はこう！「はい」「いいえ」「何ですか？」！"
+      "回覧板の詳細をDMに送っていますよ！\n・・・で、読みました？ [yes/no]"
+      "後で読むなら `後で` とお応えくださいね〜"
+      ":heart: slackbot って書いてあるところ、通知ありません？\n確認済みなら `yes` ください :smile:"
+      "yes/no でお答えください！"
+      "I can accept only yes/no"
     ]
     bye: [
       "私たちにさよならなんて必要ありませんよね。"
@@ -243,7 +240,7 @@

       Circular.onDialogue = true
       @send "@#{user} さん、" + messages.pick("greet") + "\n" +
-        "回覧板 No.#{@id} *#{@title}* は読まれましたか？ [yes/no/what]\n"
+        "回覧板 No.#{@id} *#{@title}* は読まれましたか？ [yes/no]"

       # timeout for non-response from user
       timeout_id = setTimeout =>
@@ -273,9 +270,6 @@
           callback.call()
           @end()
           Circular.onDialogue = false
-        else if /what|\?|？|何/i.test text
-          response = "回覧板の内容はこちらです。\n" + self.toString() + "\n\n" +
-            messages.pick("ask_again")
         else
           response = messages.pick("response_usage")


Explicitness.

Explicitness.

--- app/client/js/app.coffee
+++ app/client/js/app.coffee
@@ -13,13 +13,14 @@
   ###
   constructor: (element, dispatcher, routes, storage, reactApp, router) ->

+    routes.addToEste router, (route, params) ->
+      dispatcher.dispatch 'route-load', route: route, params: params
+
     dispatcher.register (action, payload) ->
-      if action == 'router-load'
+      if action == 'route-load'
         storage.load payload.route, payload.params
           .then -> routes.setActive payload.route, payload.params
           .thenCatch (reason) -> routes.trySetErrorRoute reason
           .then -> React.renderComponent reactApp.component(), element

-    routes.addToEste router, (route, params) ->
-      dispatcher.dispatch 'router-load', route: route, params: params
     router.start()


fix typo in iterator for cache object

fix typo in iterator for cache object

--- app/coffee/ASpell.coffee
+++ app/coffee/ASpell.coffee
@@ -35,7 +35,7 @@

 			# update the cache after processing all words, to avoid cache
 			# changing while we use it
-			for k, v in addToCache
+			for k, v of addToCache
 				cache.set(k, v)

 			logger.log hits: hits, total: words.length, hitrate: hits/words.length, "cache hit rate"


updated atom config

updated atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -64,8 +64,7 @@
   "linter-rust":
     cargoPath: "/usr/local/bin/cargo"
     rustcPath: "/usr/local/bin/rustc"
-  "linter-ui-default":
-    panelHeight: 75
+  "linter-ui-default": {}
   minimap: {}
   "release-notes":
     viewedVersion: "0.89.0"
@@ -75,8 +74,7 @@
       "source.gfm"
       "text.git-commit"
     ]
-  "tree-view":
-    hideVcsIgnoredFiles: true
+  "tree-view": {}
   "unity-ui":
     colorStatusIndicatorsInTreeView: true
   welcome:


update atom

update atom

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -16,10 +16,6 @@
       "vendor"
     ]
     telemetryConsent: "no"
-    themes: [
-      "unity-ui"
-      "glacier-syntax"
-    ]
   editor:
     fontFamily: "Menlo"
     fontSize: 15
@@ -41,3 +37,6 @@
     ]
   welcome:
     showOnStartup: false
+".js.jsx.react.source":
+  editor:
+    autoIndent: true


disable exception-reporting plugin

disable exception-reporting plugin

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -12,6 +12,7 @@
     projectHome: "~/Code/"
     disabledPackages: [
       "metrics"
+      "exception-reporting"
     ]
   welcome:
     showOnStartup: false


ops in `upload` function is now optional

--- client/functions.coffee
+++ client/functions.coffee
@@ -54,6 +54,10 @@
 					return callback and callback null,result

 	upload: (files, ops={}, callback) ->
+		if _.isFunction ops
+			callback = ops
+			ops = {}
+
 		_.each files, (file) ->
 			reader = new FileReader

@@ -64,53 +68,50 @@

 	_upload_file: (file, ops={}, callback) ->
 		Meteor.call "c.sign", ops, (error,result) ->
-			if not error
-				# Build form
-				form_data = new FormData()
-				_.each result.hidden_fields, (v,k) ->
-					form_data.append k,v
+			console.log error
+			console.log result
+			console.log callback

-				form_data.append "file",file
+			if error
+				return callback and callback error,null

-				# Create collection document ID
-				collection_id = Random.id()
+			# Build form
+			form_data = new FormData()
+			_.each result.hidden_fields, (v,k) ->
+				form_data.append k,v

-				# Send data
-				Cloudinary.xhr = new XMLHttpRequest()
+			form_data.append "file",file

-				Cloudinary.collection.insert
-					_id:collection_id
-					status:"uploading"
-					preview:file
+			# Create collection document ID
+			collection_id = Random.id()

-				Cloudinary.xhr.upload.addEventListener "progress", (event) ->
-						Cloudinary.collection.update _id:collection_id,
-							$set:
-								loaded:event.loaded
-								total:event.total
-								percent_uploaded: Math.floor ((event.loaded / event.total) * 100)
-					,false
+			# Send data
+			Cloudinary.xhr = new XMLHttpRequest()

-				Cloudinary.xhr.addEventListener "load", ->
-					if Cloudinary.xhr.status < 400
-						response = JSON.parse @response
-						Cloudinary.collection.upsert collection_id,
-							$set:
-								status:"complete"
-								percent_uploaded: 100
-								response: response
+			Cloudinary.collection.insert
+				_id:collection_id
+				status:"uploading"
+				preview:file

-						callback and callback null,response
-					else
-						response = JSON.parse @response
-						Cloudinary.collection.upsert collection_id,
-							$set:
-								status:"error"
-								response: response
+			Cloudinary.xhr.upload.addEventListener "progress", (event) ->
+					Cloudinary.collection.update _id:collection_id,
+						$set:
+							loaded:event.loaded
+							total:event.total
+							percent_uploaded: Math.floor ((event.loaded / event.total) * 100)
+				,false

-						callback and callback response,null
+			Cloudinary.xhr.addEventListener "load", ->
+				if Cloudinary.xhr.status < 400
+					response = JSON.parse @response
+					Cloudinary.collection.upsert collection_id,
+						$set:
+							status:"complete"
+							percent_uploaded: 100
+							response: response

-				Cloudinary.xhr.addEventListener "error", ->
+					callback and callback null,response
+				else
 					response = JSON.parse @response
 					Cloudinary.collection.upsert collection_id,
 						$set:
@@ -119,17 +120,23 @@

 					callback and callback response,null

-				Cloudinary.xhr.addEventListener "abort", ->
-					Cloudinary.collection.upsert collection_id,
-						$set:
-							status:"aborted"
+			Cloudinary.xhr.addEventListener "error", ->
+				response = JSON.parse @response
+				Cloudinary.collection.upsert collection_id,
+					$set:
+						status:"error"
+						response: response

-				Cloudinary.xhr.open "POST",result.form_attrs.action,true
+				callback and callback response,null

-				Cloudinary.xhr.send form_data
+			Cloudinary.xhr.addEventListener "abort", ->
+				Cloudinary.collection.upsert collection_id,
+					$set:
+						status:"aborted"

-			else
-				return callback and callback error,null
+			Cloudinary.xhr.open "POST",result.form_attrs.action,true
+
+			Cloudinary.xhr.send form_data

 # Define helpers


tweak PeaceMarker

tweak PeaceMarker

--- client/src/mymap.coffee
+++ client/src/mymap.coffee
@@ -2,8 +2,7 @@
 {div, p, a, button, span, input, label, h1} = React.DOM
 L = global.L or require('leaflet')
 L.Icon.Default.imagePath = '/styles/images'
-# {homegeojsonDir} = '/cozymap-leaflet/leaflet.homegeojson.js'
-# onMapClick = '/cozymap-leaflet/PeaceMarker.js'
+

 map = L.map 'map',
@@ -51,6 +50,7 @@
     shadowSize: [41, 41])

+# Module PeaceMarker start
 map.on 'dblclick', (event) ->

     marker = new L.marker event.latlng,
@@ -64,7 +64,11 @@
         markerzoom = map.getZoom()
         position = marker.getLatLng()
         popupContent = L.popup()
-            .setContent('beau gosse!')
+            .setContent(
+                'beau gosse!' + '<br>' +
+                '[Lat; Lng](zoom) = [' +
+                position.lat.toFixed(4) + '; ' + position.lng.toFixed(4) + ']
+                (' + markerzoom + ')')
         marker.setLatLng new L.LatLng(position.lat, position.lng),
             draggable: 'true'
         marker.bindPopup(popupContent).openPopup()
@@ -82,5 +86,4 @@
             draggable:'true'
             opacity: '0.65'
         marker.addTo map
-
-
+# Module PeaceMarker end


server: change content type to multipart form data

--- servers/lib/server/handlers/invitetoteambycsv.test.coffee
+++ servers/lib/server/handlers/invitetoteambycsv.test.coffee
@@ -47,7 +47,7 @@
     form = req.form()
     form.append 'file', testData, {
       filename: 'invitations.csv'
-      contentType: 'text/csv'
+      contentType: 'multipart/form-data'
     }

 validateInvitation = (data, callback) ->


Address possible race condition in Vomnibar initialization.

This addresses the potential race condition mentioned in this commit
record (3542db7b6c322d803c263db641ae0b02327447ca) and in #2033.

In non-top frames, wait until documentReady before sending the message
to initialise the Vomnibar.  This cannot be before preDomReady in the
main frame, right?

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -440,10 +440,12 @@
   if DomUtils.isTopFrame()
     Vomnibar.init()
   else
-    # Ignore requests from other frames (if we're not the top frame).
+    # Ignore requests from other frames (we're not the top frame).
     unless request?
-      # Tell the top frame to initialize the Vomnibar.
-      chrome.runtime.sendMessage handler: "sendMessageToFrames", message: name: "initializeTopFrame"
+      # Tell the top frame to initialize the Vomnibar.  We wait until "DOMContentLoaded" to ensure that the
+      # listener in the main/top frame (which are installed pre-DomReady) is already installed.
+      DomUtils.documentReady ->
+        chrome.runtime.sendMessage handler: "sendMessageToFrames", message: name: "initializeTopFrame"

 # Checks if Vimium should be enabled or not in this frame.  As a side effect, it also informs the background
 # page whether this frame has the focus, allowing the background page to track the active frame's URL.


Make the check for noreload option more coffeescripty.

--- scripts/gh-book/session.coffee
+++ scripts/gh-book/session.coffee
@@ -8,7 +8,7 @@
       # details, or if it is changed.
       @on 'change', (s, options) =>
         # Internal signal not to reload. Used by authenticate below.
-        return if options and options.noreload
+        return if options?.noreload

         # If any authentication info has changed then reload the client
         if not _.isEmpty _.pick @.changed, ['token', 'id', 'password']


Frontend bug fix: state.expanded won't exist on initial load...

So we can’t try to do indexOf on it.

--- src/App.coffee
+++ src/App.coffee
@@ -31,7 +31,7 @@
       .spread (folders, state) =>

         folderList = _.template('''
-          <div data-fullpath="<%- fullPath %>" class="<%- Object.keys(subfolders).length ? '' : 'leaf' %> <%- state.selected == fullPath ? 'selected' : '' %> <%- state.expanded.indexOf(fullPath) > -1 ? 'expanded' : '' %>">
+          <div data-fullpath="<%- fullPath %>" class="<%- Object.keys(subfolders).length ? '' : 'leaf' %> <%- state.selected == fullPath ? 'selected' : '' %> <%- (state.expanded || []).indexOf(fullPath) > -1 ? 'expanded' : '' %>">
             <% if (folderName) { %>
               <a href="#<%- fullPath %>" class="folder"><%- folderName %>
             <% } %>


oauth: use v3 github for getting emails

oauth: use v3 github for getting emails

--- server/lib/server/github_callback.coffee
+++ server/lib/server/github_callback.coffee
@@ -56,6 +56,8 @@
       resp["username"]  = login
       resp["profile"]   = userInfo

+      headers["Accept"] = "application/vnd.github.v3.full+json"
+
       # Some users don't have email in public profile, so we make 2nd call
       # to get them.
       if not email? or email is ""
@@ -74,9 +76,8 @@
     userEmailResp.on "data", (chunk) -> rawResp += chunk
     userEmailResp.on "end", ->
       emails = JSON.parse(rawResp)
-      emails = emails.filter (email)-> !/noreply.github/.test email
-
-      originalResp.email = emails[0]
+      for email in emails when email.verified and email.primary
+        originalResp.email = email

       saveOauthAndRenderPopup originalResp, res, clientId


Fixed key

Fixed key


Improves XUnit output

Improves XUnit output

--- src/x-unit-reporter.coffee
+++ src/x-unit-reporter.coffee
@@ -44,12 +44,12 @@
       attrs.message = test.message
       attrs.expected = JSON.stringify test.expected
       attrs.actual = JSON.stringify test.actual
-      appendLine(path, toTag('testcase', attrs, false, toTag('failure', attrs, false, cdata(test.errorMessage))))
+      appendLine(path, toTag('testcase', attrs, false, toTag('failure', attrs, false, cdata(test.message))))
     else
       appendLine(path, toTag('testcase', attrs, true) )

   cdata = (str) ->
-    return '<![CDATA[' + escape(str) + ']]>'
+    return '<![CDATA[' + str + ']]>'

   appendLine = (path, line) ->
     fs.appendFileSync(path, line + "\n")
@@ -59,7 +59,7 @@
     pairs = []
     tag = undefined
     for key of attrs
-      pairs.push key + "=\"" + escape(attrs[key]) + "\""
+      pairs.push key + "=\"" + attrs[key] + "\""
     tag = "<" + name + ((if pairs.length then " " + pairs.join(" ") else "")) + end
     tag += content + "</" + name + end  if content
     tag


Add dual-path-cyclists-and-pedestrians; fix no_trucks

--- dev/de.cson
+++ dev/de.cson
@@ -471,6 +471,16 @@
     { type: 'circle-separator', color: 'white' }
   ]

+'dual-path-cyclists-and-pedestrians':
+  category: 'mandatory'
+  name: 'bicycle/pedestrian dual track'
+  elements: [
+    { type: 'circle-bg', color: 'blue' }
+    { type: 'footpath', color: 'white', transform: '{right_half}' }
+    { type: 'bicycle', color: 'white', transform: '{left_half}' }
+    { type: 'circle-separator', color: 'white' }
+  ]
+
 'bicycles-only':
   category: 'mandatory'
   name: 'Bicycles only'
@@ -710,9 +720,9 @@
     { type: 'bus', color: 'black' , transform: '{fit_border_circle}'}
   ]

-no_trucks:
-  category: 'prohibitory'
-  name: 'no trucks'
+'no-heavy-goods-vehicles':
+  category: 'prohibitory'
+  name: 'no heavy goods vehicles'
   elements: [
     { type: 'circle-bg', color: 'white' }
     { type: 'circle-o', color: 'red' }


course: fix major confusing bug -- the invitation got sent, but only when the project was created.

--- salvus/page/course.coffee
+++ salvus/page/course.coffee
@@ -813,7 +813,10 @@
             if no_invite
                 return email_address
             else
-                return "#{email_address} (invited)"
+                if student.project_id?
+                    return "#{email_address} (invited)"
+                else
+                    return "#{email_address} (create project to invite student)"

     course_project_settings: (student_id) =>
         z = @db.select_one(where:{table:'settings'})


Add test

Add test

--- test/integration/events.spec.coffee
+++ test/integration/events.spec.coffee
@@ -24,6 +24,23 @@
       list = _.map(list, (item) -> path.resolve path.join dir, item)
       list.push path.resolve dir
       cb list
+
+describe 'symlink', ->
+  beforeEach ->
+    fs.ensureDirSync './test/fixtures'
+    fs.mkdirSync './test/fixtures/test'
+    fs.mkdirSync './test/fixtures/test/test1'
+    fs.symlinkSync './test/fixtures/test/test1', './test/fixtures/test/testsl'
+  afterEach ->
+    fs.unlinkSync './test/fixtures/test/testsl'
+    fs.deleteDirSync './test/fixtures'
+
+  it 'should watch 2 dirs', (done) ->
+    watcher = new Inotifyr 'test/fixtures', {recursive: yes, events: ['create', 'modify', 'delete', 'move']}
+    sinon.stub watcher, '_addRecursiveWatches'
+    watcher._watch 'test/fixtures/test', {recursive: yes, events: ['create', 'modify', 'delete', 'move']}
+    expect(watcher._addRecursiveWatches).to.have.been.calledOnce
+    done()

 describe 'Inotifyr Events', ->


fix ssh test

fix ssh test

--- test/integration/ssh_dataset.coffee
+++ test/integration/ssh_dataset.coffee
@@ -145,9 +145,11 @@

     context 'when I click the "SSH in" menu link', ->
       before (done) ->
-        browser.elementByCss '.dataset.tile .dropdown-toggle', (err, settingsLink) =>
-          settingsLink.click =>
-            wd40.click '.dataset.tile .git-ssh', done
+        wd40.elementByPartialLinkText 'Apricot', (err, tile) ->
+          tile.elementByCss '.dropdown-toggle', (err, settingsLink) ->
+            settingsLink.click ->
+              tile.elementByCss '.git-ssh', (err, link) ->
+                link.click done

       it 'a modal window appears', (done) =>
         wd40.getText '.modal', (err, text) =>


add way to get and set state of the terminal converter

--- js/libs/terminal.coffee
+++ js/libs/terminal.coffee
@@ -1,11 +1,21 @@
 CI.terminal =
-  ansiToHtmlConverter: (defaultColor) ->
-    trailing_raw = ""
-    trailing_out = ""
-    style =
+  ansiToHtmlConverter: (defaultColor, state={}) ->
+    default_state =
+      trailing_raw: ""
+      trailing_out: ""
       color: defaultColor
       italic: false
       bold: false
+
+    initial_state = _.extend(default_state, state)
+
+    trailing_raw = initial_state.trailing_raw
+    trailing_out = initial_state.trailing_out
+
+    style =
+      color: initial_state.color
+      italic: initial_state.italic
+      bold: initial_state.bold

       reset: () ->
         @color = defaultColor
@@ -50,6 +60,18 @@
             content
         else
           ""
+      currentState: () ->
+        color: @color
+        italic: @italic
+        bold: @bold
+
+
+    currentState: () ->
+      _.extend
+        trailing_raw: trailing_raw
+        trailing_out: trailing_out
+      , style.currentState()
+

     wrapDefaultColor: (content) ->
       if (not content?) or (content is "")


Add features: insert, replace, delete

Add features: insert, replace, delete

--- flow.coffee
+++ flow.coffee
@@ -30,9 +30,28 @@

   prepend: (slideHtml) ->
     @list.prepend(slideHtml)
+    @show(@slide)

   append: (slideHtml) ->
     @list.append(slideHtml)
+    @show(@slide)
+
+  insert: (position, slideHtml) ->
+    if position is 0
+      @list.find(@options.slidesSelector).eq(position).before(slideHtml)
+    else
+      @list.find(@options.slidesSelector).eq(position - 1).after(slideHtml)
+    @show(@slide)
+
+  replace: (position, slideHtml) ->
+    slide = if position is @slide.index() then position else @slide
+    @list.find(@options.slidesSelector).eq(position).before(slideHtml).remove()
+    @show(slide)
+
+  delete: (index) ->
+    if index is @slide.index()
+      @next()
+    @list.find(@options.slidesSelector).eq(index).remove()

   _size: ->
     @list.find(@options.slidesSelector).size()


#269 - Translate "All Tasks" and "Completed"

--- source/scripts/controllers/search.coffee
+++ source/scripts/controllers/search.coffee
@@ -2,6 +2,14 @@
 List  = require '../models/list'
 Task  = require '../models/task'
 event = require '../utils/event'
+Translate = require '../utils/translate'
+
+text = {}
+
+event.on 'load:language', ->
+  text = Translate
+    all: 'All Tasks'
+    completed: 'Completed'

 class Search

@@ -14,7 +22,7 @@
   searchAll: (query) =>

     List.trigger 'select:model',
-      name: 'All Tasks'
+      name: text.all
       id: 'search'
       type: 'active'
       permanent: yes
@@ -23,7 +31,7 @@
   searchCompleted: (query) =>

     List.trigger 'select:model',
-      name: 'Completed'
+      name: text.completed
       id: 'search'
       type: 'completed'
       permanent: yes


Fixed bug

Fixed bug

--- src/Log.coffee
+++ src/Log.coffee
@@ -23,7 +23,7 @@
     currentLevel ?= @level
     # Only logs if the level is less than or equal to the current level
     code = @_levels[level]
-    return typeof code != 'undefined' && code <= @_levels[@level]
+    return typeof code != 'undefined' && code <= @_levels[currentLevel]

   on: ->
     return if @_loggingOn


CUI.choice

CUI.choice

- new shorthand for "new ConfirmationChoice().open()"

--- src/elements/ConfirmationChoice/ConfirmationChoice.coffee
+++ src/elements/ConfirmationChoice/ConfirmationChoice.coffee
@@ -113,6 +113,8 @@
 		CUI.ConfirmationDialog::show.call(@)
 		return @__deferred.promise()

+CUI.choice = (opts=text: "CUI.ConfirmationChoice") ->
+	new CUI.ConfirmationChoice(opts).open()

 CUI.defaults.class.ConfirmationChoice = CUI.ConfirmationChoice


Updates the gem resource snippet

Updates the gem resource snippet

--- snippets/gem.cson
+++ snippets/gem.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe gem':
     'prefix': 'gem'
-    'body': "# http://inspec.io/docs/reference/resources/gem/\ndescribe gem('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/gem/\ndescribe gem('${1:name}') do\n  it { should${2:_not} be_installed }\n  its('version') { should eq '${3:0.33.0}' }${6}\nend"


[master] bug

[master] bug


Run end-to-end tests also on PhantomJS

Run end-to-end tests also on PhantomJS

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -371,11 +371,15 @@
           'spec/tests.html': ['spec/*.js']
     # BDD tests on browser
     mocha_phantomjs:
-      all:
+      unit:
         options:
           reporter: 'spec'
           urls: ['http://localhost:9999/spec/tests.html']
           failWithOutput: true
+      browser:
+        options:
+          reporter: 'spec'
+          urls: ['http://localhost:9999/spec/browser/tests.html']

     # BDD tests on browser
     'saucelabs-mocha':
@@ -413,7 +417,6 @@
   @loadNpmTasks 'grunt-gh-pages'

   # Grunt plugins used for testing
-  #@loadNpmTasks 'grunt-mocha-phantomjs'
   @loadNpmTasks 'grunt-contrib-watch'
   @loadNpmTasks 'grunt-contrib-coffee'
   @loadNpmTasks 'grunt-mocha-phantomjs'


commit message updated [ci skip]

commit message updated [ci skip]

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -36,7 +36,6 @@
           url: 'https://bootswatch.com/api/3.json'
           callback: (error, response, data) ->
             data = JSON.parse data
-            console.log data
             if error?
               grunt.log.error 'Something went wrong! Can\'t reach Bootswatch API'
             else
@@ -116,7 +115,7 @@
           execOptions:
             cwd: 'dist'
         command: 'git add . --all && ' +
-                 'git commit -m "Auto update - Build #$TRAVIS_BUILD_NUMBER [ci skip]"'
+                 'git commit -m "Auto update - Build #$TRAVIS_BUILD_NUMBER"'
       tagVersion:
         options:
           failOnError: false


Gruntfile: Add tests to build task.

Gruntfile: Add tests to build task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -149,6 +149,7 @@
       'coffee2closure'
       'stylus'
       'deps'
+      'karma'
       'builder'
     ]


Don't blow up on missing settings

Don't blow up on missing settings

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -88,7 +88,7 @@

 		shell:
 			migrate:
-				command: "./node_modules/east/bin/east migrate --adapter east-mongo --url #{settings.mongo.url}"
+				command: "./node_modules/east/bin/east migrate --adapter east-mongo --url #{settings?.mongo?.url}"

 		availabletasks:
 			tasks:


Bugfix Gruntfile

Bugfix Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -34,8 +34,12 @@
             lib:
                 files: "src/**/*.coffee"
         coffee:
-            bin:
-                files: "src/**/*.coffee"
+            lib:
+                expand: yes
+                cwd: "src/"
+                src: [ "**/*.coffee" ]
+                dest: "lib/"
+                ext: ".js"
                 options:
                     bare: yes
         nodeunit:


Revert Gruntfile.coffee

Revert Gruntfile.coffee

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

     shell:
       test:
-        command: 'node node_modules/.bin/jasmine-focused --coffee --captureExceptions --forceexit spec'
+        command: 'node node_modules/jasmine-focused/bin/jasmine-focused --captureExceptions --coffee spec'
         options:
           stdout: true
           stderr: true


Remove legacy appcache

Remove legacy appcache

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -97,11 +97,10 @@

 						# enable Angular's HTML5 mode
 						modRewrite = require 'connect-modrewrite'
-						middlewares.unshift(modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.appcache|\\.png$ /index.html [L]']));
+						middlewares.unshift(modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']));
 						return middlewares;

 					open: true
-					port: 0

 		# Copies directories and files from one location to another
 		copy:
@@ -237,20 +236,6 @@
 			dist:
 				src: ['.temp/scripts/*.js', '.temp/scripts/**/*.js']
 				dest: '.temp/scripts/scripts.js'
-
-		appcache:
-			options:
-				basePath: '.temp/'
-			all:
-				dest: '.temp/manifest.appcache'
-				cache:
-					patterns: [
-						'.temp/**/*'
-						'!.temp/web.config'
-					]
-				network: [
-					'https://angular-apt.azurewebsites.net/api/*'
-				].concat(cdnJs).concat(cdnCss).concat(mapFiles)

 		# Run tasks when monitored files change
 		watch:
@@ -370,7 +355,6 @@
 		'cacheBust'
 		'template:prod'
 		'includeSource'
-		'appcache'
 		'copy:prod'
 	]


Also bump bower.json

Also bump bower.json

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -99,7 +99,7 @@
         part: "patch"
         onBumped: ->
           grunt.task.run("regex-replace")
-      files: ["package.json", "component.json"]
+      files: ["package.json", "component.json", "bower.json"]

     watch:
       test:


renamed src

renamed src

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -15,7 +15,7 @@
     pkg: grunt.file.readJSON "package.json"

     coffeelint:
-      files: ["Gruntfile.coffee", "src/**/*.coffee", "test/**/*.coffee"]
+      files: ["Gruntfile.coffee", "tasks/**/*.coffee", "test/**/*.coffee"]
       options:
         max_line_length:
           value: 200


fix tasks

fix tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -60,7 +60,7 @@
   grunt.loadNpmTasks 'grunt-contrib-uglify'
   grunt.loadNpmTasks 'grunt-contrib-watch'

-  grunt.registerTask 'dist', ['coffee', 'uglify']
+  grunt.registerTask 'dist', ['test', 'coffee', 'uglify']

   grunt.registerTask "test", ['mochaTest:test']
-  grunt.registerTask "default", ['test', 'dist']+  grunt.registerTask "default", ['dist']


Bump and tag each release

Bump and tag each release

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -43,6 +43,7 @@
         options:
           branch: 'releases'
           remote: 'git@github.com:ufal/lindat-license-selector.git'
+          tag: pkg.version

     copy:
       pages:
@@ -83,7 +84,7 @@
         commit: true
         commitMessage: 'Release v%VERSION%'
         commitFiles: ['package.json', 'bower.json']
-        createTag: true
+        createTag: false
         tagName: 'v%VERSION%'
         tagMessage: 'Version %VERSION%'
         push: true
@@ -192,6 +193,7 @@
   ])

   grunt.registerTask('release', [
+    'bump',
     'pages',
     'default',
     'copy:release'


Update message format

Update message format

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -213,7 +213,12 @@
 					Either latexmk is not installed or is not in your PATH.

 					latexmk comes with TexLive 2013, and must be a version from 2013 or later.
-					This is a not a fatal error, but compiling will not work without latexmk
+					If you have already have TeXLive installed, then make sure it is
+					included in your PATH (example for 64-bit linux):
+
+						export PATH=$PATH:/usr/local/texlive/2014/bin/x86_64-linux/
+
+					This is a not a fatal error, but compiling will not work without latexmk.
 					"""
 					return callback(error)
 				else if error?
@@ -254,8 +259,7 @@

 						brew install aspell

-					This is not a fatal error, but the spell-checker will not work
-					without aspell
+					This is not a fatal error, but the spell-checker will not work without aspell
 					"""
 					return callback(error)
 				else if error?


Removed bump from publish grunt task

Removed bump from publish grunt task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -69,7 +69,7 @@
   grunt.loadNpmTasks 'grunt-dotnet-mspec'

   grunt.registerTask 'default', ['ci']
-  grunt.registerTask 'publish', ['banner','bump','nugetpush']
+  grunt.registerTask 'publish', ['banner','nugetpush']
   grunt.registerTask 'release', ['publish']
   grunt.registerTask 'deploy', ['publish']
   grunt.registerTask 'ci', ['banner','clean','assemblyinfo','nugetrestore','msbuild','mspec','nugetpack']


Lint as part of default task

Lint as part of default task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -31,5 +31,5 @@
   grunt.loadNpmTasks('grunt-shell')
   grunt.registerTask 'clean', -> require('rimraf').sync('lib')
   grunt.registerTask('lint', ['coffeelint'])
-  grunt.registerTask('test', ['coffee', 'shell:test'])
-  grunt.registerTask('default', ['coffee'])
+  grunt.registerTask('test', ['default', 'shell:test'])
+  grunt.registerTask('default', ['lint', 'coffee'])


Updated gh-pages action

Updated gh-pages action

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -43,8 +43,8 @@
       "gh-pages":
         command: [
           "rm -r gh-pages/*"
-          "cp demo.html gh-pages/"
           "cp -r build lib gh-pages"
+          "coffee source/cli.coffee demo.haml > gh-pages/index.html"
           "cd gh-pages"
           "git add ."
           "git ci -am 'updating pages'"


robot can follow program

robot can follow program

--- dwim.coffee
+++ dwim.coffee
@@ -60,10 +60,13 @@
       'hex'
       ])

-    @mapping.commands[0] = new MoveCommand(LEFT.theta)
-    @mapping.commands[1] = new MoveCommand(UP.theta)
-    #@mapping.commands[2] = new MoveCommand(DOWN.theta)
-    @mapping.commands[3] = new MoveCommand(RIGHT.theta)
+    @mapping.commands[0] = new MoveCommand(LEFT)
+    @mapping.commands[1] = new MoveCommand(UP)
+    #@mapping.commands[2] = new MoveCommand(DOWN)
+    @mapping.commands[3] = new MoveCommand(RIGHT)
+
+    @pc = 0
+    @execute()

     @startRenderer()
     @startInput()
@@ -108,38 +111,83 @@
         dir.theta
       )

+  execute: () ->
+    @program.active = @pc
+    @mapping.active = @program.instructions[@pc]
+
+    next_command = @mapping.mapActive()
+    console.log('active instr = ' + @mapping.active)
+
+    if next_command?
+      @allowed_move = next_command.movePoint((x: @botx, y: @boty))
+    else
+      @allowed_move = null
+
   moveBotTo: (x, y, dir) ->
+    console.log('want ' +x+','+y)
     # check borders
     if x < 0 or y < 0 or x >= @Wi or y >= @Hi
       return false

+    if @allowed_move?
+      console.log('allowed ' +@allowed_move.x+','+@allowed_move.y)
+      if @allowed_move.x != x or @allowed_move.y != y
+        return false
+    else
+      # need to do stuff here with filling in the mapping
+      true
+
     @botx = x
     @boty = y
     @botdir = dir

+    @pc +=1
+    @execute()
+
     return true

 class Program
   constructor: (@instructions) ->

-  render: (ctx) ->
+  render: (ctx, t = 0) ->
     cs = g.command_size

     ctx.save()
     g.setStyle(ctx, g.thick_lined_style)

-    ctx.translate(cs/2, cs/2)
+    if @active?
+      ctx.translate(cs/2, cs/2 - (@active+t) * cs)
+    else
+      ctx.translate(cs/2, cs/2)

     for instruction in @instructions
       g.renderShape(ctx, instruction, g.inner_command_size/2)
+
       ctx.translate(0, cs)

     ctx.restore()
+
+    if @active?
+      ctx.save()
+      g.setStyle(ctx, g.lined_style)
+      ctx.strokeRect(0, 0, cs, cs)
+      ctx.restore()
+

 class Mapping
   constructor: (@symbol_names) ->
     @commands = []
+
+  mapActive: () ->
+    if @active?
+      idx = @symbol_names.indexOf(@active)
+      if idx == -1
+        return null
+      else
+        return @commands[idx]
+    else
+      return null

   render: (ctx) ->
     ocs = g.outer_command_size
@@ -193,10 +241,13 @@
 class MoveCommand
   constructor: (@movedir) ->

+  movePoint: (pos) ->
+    return x: pos.x + @movedir.dx, y: pos.y + @movedir.dy
+
   render: (ctx) ->
     ctx.save()
     g.setStyle(ctx, g.lined_style)
-    g.renderArrow(ctx, @movedir, g.inner_command_size)
+    g.renderArrow(ctx, @movedir.theta, g.inner_command_size)
     ctx.restore()

 window.Dwim = Dwim


Ahsan and Richard responses

Ahsan and Richard responses

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -107,4 +107,13 @@
     msg.send "The offshore team shall know no fear! The offshore team will be triumphant! :ahsan: :sachin: :prem: :kostas: :andrew: :drmatt:"

   robot.respond /what is the purpose of offshore team/i, (msg)->
-    msg.send "To crush our bugs, see them driven before us, and to hear the lamentation of their women"+    msg.send "To crush our bugs, see them driven before us, and to hear the lamentation of their women"
+
+  robot.respond /Ahsan smash/i, (msg)->
+    msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/hulk.gif"
+
+  robot.respond /What would :richard: say/i, (msg)->
+    msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/richardResponse.gif"
+
+  robot.respond /What would Richard say/i, (msg)->
+    msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/richardResponse.gif"


replaced session.public.user unlink with delete on logout

--- modules/http/lib/sessions/Sessions.coffee
+++ modules/http/lib/sessions/Sessions.coffee
@@ -158,7 +158,7 @@

             if (sess = @_registry.get(sid))

-                sess.public.user = floyd.tools.objects.unlink sess.public.user
+                delete sess.public.user

             fn()


Major overhaul of the expression manipulation library

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -9,8 +9,8 @@

     class ExpressionManipulation
       initialize: (opts={})->
-        @comps = opts.component_source
-
+        @comps = opts.comps
+        @pos = opts.pos
       evaluate: (exp)->
         expression_evaluation.build(exp).resultingExpression()

@@ -19,12 +19,12 @@
         if result then result.value() else 0

     class Calculate extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         @evaluate(expression)
     class_mixer(Calculate)

     class Square extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         val = @value(expression)
         @comps.build_expression expression: [@comps.build_number(value: val*val)]
     class_mixer(Square)
@@ -36,15 +36,22 @@
         if last instanceof @comps.classes.number
           new_last = last.clone()
           new_last = new_last.futureAsDecimal()
-          expression.replaceLast(new_last)
+          new_exp = expression.replaceLast(new_last)
         else
           new_last = @comps.build_number(value: 0)
           new_last = new_last.futureAsDecimal()
-          expression.append(new_last)
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=> @onFinalExpression(expr))
+          new_exp = expression.append(new_last)
+        @pointer = @pos.build(new_exp.last().id())
+        new_exp
+
+      perform: (expression)->
+        resulting_expression =
+          _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+            performOrDefault((expr)=> @onFinalExpression(expr))
+
+        expression: resulting_expression
+        position: @pointer
+

     class_mixer(AppendDecimal)

@@ -52,23 +59,45 @@
       initialize: (opts={})->
         super
         @val = opts.value
-
-      doAppend: (expression)->
-        last = expression.last()
-        number_with_this_val = @comps.build_number(value: @val)
-
-        if last && last instanceof @comps.classes.number
-          new_last = last.concatenate(@val)
-          expression.replaceLast(new_last)
-        else if (last && last instanceof @comps.classes.exponentiation) or (last && !last.isOperator())
-          expression.append(@comps.build_multiplication()).
-            append(number_with_this_val)
-        else
-          expression.append(number_with_this_val)
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=> @doAppend(expr))
+      # doAppend: (expression)->
+      #   last = expression.last()
+      #   number_to_append = @comps.build_number(value: @val)
+
+      #   if last && last instanceof @comps.classes.number
+      #     new_last = last.concatenate(@val)
+      #     new_exp = expression.replaceLast(new_last)
+      #   else if (last && last instanceof @comps.classes.exponentiation) or (last && !last.isOperator())
+      #     new_exp = expression.append(@comps.build_multiplication()).
+      #       append(number_to_append)
+      #   else
+      #     new_exp = expression.append(number_to_append)
+
+      #   @pointer = @pos.build(position: new_exp.id())
+      #   new_exp
+
+      # @destructive
+      doAppendD: (expression, expression_position)->
+        number_to_append = @comps.build_number(value: @val)
+        last = expression.last()
+        if last
+          if last instanceof @comps.classes.number
+            expression.last().concatenateD(@val)
+          else if (last instanceof @comps.classes.exponentiation) or !last.isOperator()
+            expression.appendD(@comps.build_multiplication())
+            expression.appendD(number_to_append)
+          else
+            expression.appendD(number_to_append)
+        else
+          expression.appendD(number_to_append)
+
+      perform: (expression_position)->
+        result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
+          withComponent(expression_position, (component)=>
+            @doAppendD(component, expression_position)
+          ).value()
+
+        # would be same position, but a new expression
+        expression_position.clone(expression: result_exp)

     class_mixer(AppendNumber)

@@ -97,7 +126,7 @@
         else
           power.open()

-      invoke: (expression)->
+      perform: (expression)->
         last = expression.last()

         # in the first case, our base case comes
@@ -122,48 +151,71 @@

     class_mixer(ExponentiateLast)

+    class AppendExponentiation extends ExpressionManipulation
+      perform: (expression_position)->
+        base = @comps.build_expression()
+        power = @comps.build_expression()
+
+        result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
+          withComponent(expression_position, (component)=>
+
+            _ImplicitMultiplication.build(@comps).invokeD(component)
+            component.appendD(@comps.build_exponentiation(base: base, power: power))
+          ).value()
+
+        # would be same position, but a new expression
+        expression_position.clone(expression: result_exp)
+
+    class_mixer(AppendExponentiation)
+
     class AppendMultiplication extends ExpressionManipulation
       appendAction: (expression)->
-        _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_multiplication()
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=> @appendAction(expr))
+        new_exp = _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_multiplication()
+        @position = @pos.build(new_exp.last().id())
+        new_exp
+
+      perform: (expression, position)->
+        if expression.isEmpty()
+          expression: expression
+          position: position
+        else
+          expr = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+            performOrDefault((expr)=> @appendAction(expr))
+          expression: expr
+          position: @position

     class_mixer(AppendMultiplication)

     class AppendEquals extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         expression.append @comps.build_equals()
     class_mixer(AppendEquals)

     class AppendDivision extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expr)=>
+          performOrDefault((expr)=>
             _OverrideIfOperatorOrAppend.build(@comps, expr).with @comps.build_division())
     class_mixer(AppendDivision)

     class AppendAddition extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expression)->
+          performOrDefault((expression)->
             _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_addition())

     class_mixer(AppendAddition)

-
     class AppendSubtraction extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expression)=>
+          performOrDefault((expression)=>
             _OverrideIfOperatorOrAppend.build(@comps, expression).with @comps.build_subtraction())
-
     class_mixer(AppendSubtraction)

     class Negation extends ExpressionManipulation
-      invoke: (expression)->
+      perform: (expression)->
         last = expression.last()
         if last
             expression.replaceLast(last.negated())
@@ -172,18 +224,108 @@

     class_mixer(Negation)

-    class OpenSubExpression extends ExpressionManipulation
+    class AppendOpenSubExpression extends ExpressionManipulation
       action: (expression)->
+        new_exp = @comps.build_expression().open()
+        @pointer = @pos.build(position: new_exp.id())
         _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(@comps.build_expression().open())
-
-      invoke: (expression)->
-        _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invokeOrDefault((expression)=> @action(expression))
-
-
-    class_mixer(OpenSubExpression)
+          invoke(expression).
+          append(new_exp)
+
+      perform: (expression)->
+        exp = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+          performOrDefault((expression)=> @action(expression))
+        expression: exp
+        position: @pointer
+
+
+    class_mixer(AppendOpenSubExpression)
+
+    class CloseSubExpression extends ExpressionManipulation
+      perform: (expression)->
+        ret = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
+          perform((expression)-> expression.close())
+        ret
+    class_mixer(CloseSubExpression)
+
+    # TODO this needs to ahve final open sub expression application
+    class AppendPi extends ExpressionManipulation
+      perform: (expression)->
+        _ImplicitMultiplication.build(@comps).
+          invoke(expression).
+          append(@comps.build_pi())
+
+    class_mixer(AppendPi)
+
+    class AppendRoot extends ExpressionManipulation
+      initialize: (opts={})->
+        super
+        @degree = opts.degree
+
+      perform: (expression_position)->
+        degree =
+          if @degree
+            @comps.build_expression expression: [
+              @comps.build_number(value: @degree)
+            ]
+          else
+            @comps.build_expression()
+        radicand = @comps.build_expression(expression: [])
+        root = @comps.build_root(degree: degree, radicand: radicand)
+
+        result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
+          withComponent(expression_position, (component)=>
+            _ImplicitMultiplication.build(@comps).invokeD(component)
+            component.appendD(root)
+          ).value()
+
+        # would be same position, but a new expression
+        expression_position.clone(expression: result_exp)
+
+    class_mixer(AppendRoot)
+
+    class AppendVariable extends ExpressionManipulation
+      initialize: (opts={})->
+        super
+        @variable_name = opts.variable
+      perform: (expression)->
+        variable = @comps.build_variable(name: @variable_name)
+        _ImplicitMultiplication.build(@comps).
+          invoke(expression).
+          append(variable)
+
+
+    class_mixer(AppendVariable)
+
+    class SquareRoot extends ExpressionManipulation
+      perform: (expression)->
+        value = @value(expression)
+        root = Math.sqrt(parseFloat(value))
+        unless isNaN(root)
+          num = @comps.build_number value: "#{root}"
+          @comps.classes.expression.buildWithContent [num]
+        else
+          @comps.classes.expression.buildError()
+    class_mixer(SquareRoot)
+
+
+    class Reset extends ExpressionManipulation
+      perform:  (expression, position)->
+        empty_expression = @comps.build_expression(expression: [])
+        @pos.build(expression: empty_expression, position: empty_expression.id())
+    class_mixer(Reset)
+
+    class UpdatePosition extends ExpressionManipulation
+      initialize: (opts={})->
+        super
+        @new_position_element_id = opts.element_id
+        @new_position_element_type = opts.type
+
+      perform:  (expression_position)->
+        expression_position.clone(position: @new_position_element_id, type: @new_position_element_type)
+
+    class_mixer(UpdatePosition)
+

     class _FinalOpenSubExpressionApplication
       initialize: (opts={})->
@@ -203,7 +345,7 @@
         else # not closable, not handled, return
           expr

-      invoke: (@action)->
+      perform: (@action)->
         @findAndPerformAction(@expr)

       wasFound: -> @found
@@ -225,7 +367,7 @@
           expr.radicand()
         else false

-      invokeOrDefault: (@action)->
+      performOrDefault: (@action)->
         result = @findAndPerformAction(@expr)
         if @wasFound()
           result
@@ -235,68 +377,20 @@
     class_mixer(_FinalOpenSubExpressionApplication)

-    class CloseSubExpression extends ExpressionManipulation
-      invoke: (expression)->
-        ret = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-          invoke((expression)-> expression.close())
-        ret
-    class_mixer(CloseSubExpression)
-
-    # TODO this needs to ahve final open sub expression application
-    class AppendPi extends ExpressionManipulation
-      invoke: (expression)->
-        _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(@comps.build_pi())
-
-    class_mixer(AppendPi)
-
-    class AppendRoot extends ExpressionManipulation
-      initialize: (opts={})->
-        super
-        @degree = opts.degree
-      invoke: (expression)->
-        degree = @comps.build_expression expression: [
-            @comps.build_number(value: @degree)
-          ]
-        radicand = @comps.build_expression(expression: []).open()
-        root = @comps.build_root(degree: degree, radicand: radicand)
-
-        _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(root)
-    class_mixer(AppendRoot)
-
-    class AppendVariable extends ExpressionManipulation
-      initialize: (opts={})->
-        super
-        @variable_name = opts.variable
-      invoke: (expression)->
-        variable = @comps.build_variable(name: @variable_name)
-        _ImplicitMultiplication.build(@comps).
-          onNumeric(expression).
-          append(variable)
-
-
-    class_mixer(AppendVariable)
-
-    class SquareRoot extends ExpressionManipulation
-      invoke: (expression)->
-        value = @value(expression)
-        root = Math.sqrt(parseFloat(value))
-        unless isNaN(root)
-          num = @comps.build_number value: "#{root}"
-          @comps.classes.expression.buildWithContent [num]
-        else
-          @comps.classes.expression.buildError()
-    class_mixer(SquareRoot)
-
     class _ImplicitMultiplication
       initialize: (@comps)->
-      onNumeric: (expression)->
+      invoke: (expression)->
         last = expression.last()
         if last && (last.isNumber() || last instanceof @comps.classes.expression || last instanceof @comps.classes.pi)
           expression.append(@comps.build_multiplication())
+        else
+          expression
+
+      invokeD: (expression)->
+        last = expression.last()
+        if last && (last.isNumber() || last instanceof @comps.classes.expression || last instanceof @comps.classes.pi)
+          expression.appendD(@comps.build_multiplication())
+          expression
         else
           expression

@@ -328,34 +422,53 @@

     class_mixer(_TrailingOperatorHandling)

+    class _ExpressionManipulator
+      initialize: (@expr)->
+      clone: ->
+        @expr = @expr.clone()
+        @
+      withComponent: (position, fn)->
+        comp = ttm.lib.math.ExpressionTraversal.build(@expr).
+          findForID(position.position())
+        fn(comp)
+        @
+
+      value: -> @expr
+
+    class_mixer(_ExpressionManipulator)
+
     exports =
       calculate: Calculate
       square: Square
       append_decimal: AppendDecimal
       append_number: AppendNumber
       exponentiate_last: ExponentiateLast
+      append_exponentiation: AppendExponentiation
       append_multiplication: AppendMultiplication
       append_addition: AppendAddition
       append_equals: AppendEquals
       append_subtraction: AppendSubtraction
       negate_last: Negation
-      open_sub_expression: OpenSubExpression
+      append_open_sub_expression: AppendOpenSubExpression
       close_sub_expression: CloseSubExpression
       append_division: AppendDivision
       append_pi: AppendPi
+      update_position: UpdatePosition
       square_root: SquareRoot
       append_root: AppendRoot
       append_variable: AppendVariable
+      reset: Reset

     class ExpressionManipulationSource
-      initialize: (@comps)->
+      initialize: (@comps, @pos)->
       classes: exports
     ttm.class_mixer(ExpressionManipulationSource)

     for name, klass of exports
       build_klass = do (name, klass)->
         (opts={})->
-          opts.component_source = @comps
+          opts.comps = @comps
+          opts.pos = @pos
           klass.build(opts)
       ExpressionManipulationSource.prototype["build_#{name}"] = build_klass


added more images to 'im out'

added more images to 'im out'

--- scripts/im-out.coffee
+++ scripts/im-out.coffee
@@ -1,6 +1,7 @@
 images = [
   "http://i.imgur.com/XYfUN.gif",
-  "http://gifs.gifbin.com/062009/1245227615_colin_farrel.gif"
+  "http://gifs.gifbin.com/062009/1245227615_colin_farrel.gif",
+  "http://i.imgur.com/mQK1x.gif"
 ]

 module.exports = (robot) ->


expors => exports

expors => exports

--- src/chaplin/application.coffee
+++ src/chaplin/application.coffee
@@ -10,7 +10,7 @@
 # The application bootstrapper
 # ----------------------------

-module.expors = class Application
+module.exports = class Application

   # Borrow the static extend method from Backbone
   @extend = Backbone.Model.extend


fs.exists

fs.exists


Fix command name 'turtleme' => 'turtle me'

--- scripts/turtleme.coffee
+++ scripts/turtleme.coffee
@@ -2,13 +2,13 @@
 #   hubotに亀の写真を拾ってきてもらう
 #
 # Commands:
-#   hubot turtleme - Queries Google Images for turtle and returns a random top result.
+#   hubot turtle me - Queries Google Images for turtle and returns a random top result.

 # Hubotのスクリプトはモジュールとして記述し，
 # Hubot起動時にrequireされてexportした関数が呼び出されます

 module.exports = (robot) ->
-  robot.respond /turtleme/i, (msg) ->
+  robot.respond /turtle me/i, (msg) ->
     turtleMe msg, (url) ->
       msg.send url


Back to an hour. Points fixed.

Back to an hour. Points fixed.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -134,7 +134,7 @@
             # Winners (and then recall) is equal to all users
             # with points, and their points respectively.
             robot.brain.set 'winners', points
-    ), 60000
+    ), 3600000
     # 3600000 for an hour

     # Get a list of stored people and points


Override composite onAbort with a localized one

--- lib/BehaviorTree.coffee
+++ lib/BehaviorTree.coffee
@@ -49,8 +49,7 @@
     tree.parentOnBranch = choice.parentOnBranch or @parentOnBranch

     onAbort = choice.onAbort or @onAbort
-    onAbort = null if @directOnAbort
-    unless onAbort
+    if not onAbort or @directOnAbort
       onAbort = (rChoice, reason, value, branched) =>
         return if branched
         log.tree "#{@name or @id} Non-collection #{rChoice} aborted with %s", reason
@@ -58,6 +57,16 @@
           choice: rChoice
           reason: reason
           value: value
+      tree.directOnAbort = true
+    else
+      origOnAbort = onAbort
+      onAbort = (rChoice, reason, value, branched) ->
+        unless branched
+          tree.aborted.push
+            choice: rChoice
+            reason: reason
+            value: value
+        origOnAbort rChoice, reason, value, branched
       tree.directOnAbort = true

     tree.onAbort = onAbort


Test and fix issues, also make sure you can't issue a bounty larger than the number of cups you have.


Add vote

Add vote

--- scripts/yamada.coffee
+++ scripts/yamada.coffee
@@ -19,6 +19,7 @@
 #   yamabo ping [IPADDR]         -- Execute ping [IPADDR] from bot
 #   yamabo traceroute [IPADDR]   -- Execute traceroute [IPADDR] from bot
 #   yamabo whois [IPADDR]        -- Execute whois [IPADDR]
+#   yamabo vote [TITLE] [ITEM1],[ITEM2],[ITEM3] -- Create vote template
 #
 # Author:
 #   noralife
@@ -46,6 +47,7 @@
 yamabo ping [IPADDR]         -- Execute ping [IPADDR] from bot server
 yamabo traceroute [IPADDR]   -- Execute traceroute [IPADDR] from bot server
 yamabo whois [IPADDR]        -- Execute whois [IPADDR]
+yamabo vote [TITLE] [ITEM1],[ITEM2],[ITEM3] -- Create vote template
 ```
               '''

@@ -182,6 +184,59 @@
       if !error && response.statusCode == 200
         json.analyzed_text = decodeURI(json.analyzed_text)
         msg.send("```\n" + JSON.stringify(json, null, "\t") + "\n```")
+
+  addReaction = (name, ch, ts, callback) ->
+    options = {
+      url: 'https://slack.com/api/reactions.add'
+      qs: {
+        'token': process.env.HUBOT_SLACK_TOKEN
+        'name': name
+        'channel': ch
+        'timestamp': ts
+      }
+    }
+    request.post options, (err, res, body) ->
+      callback(JSON.parse(body))
+      if err? or res.statusCode isnt 200
+        robot.logger.error("Failed to add emoji reaction #{JSON.stringify(err)}")
+
+  postMessage = (msg, text, callback) ->
+    options = {
+      url: 'https://slack.com/api/chat.postMessage'
+      qs: {
+        'token': process.env.HUBOT_SLACK_TOKEN
+        'channel': msg.message.rawMessage.channel
+        'text': text
+        'username': 'yamabo'
+        'as_user': true
+      }
+    }
+    request.post options, (err, res, body) ->
+      callback(JSON.parse(body))
+      if err? or res.statusCode isnt 200
+        robot.logger.error("Failed to post comment #{JSON.stringify(err)}")
+
+  postMessages = (msg, texts) ->
+    text = texts.shift()
+    if text?
+      postMessage msg, text, (body) ->
+        addReactions(["+1", "scream"], body.channel, body.ts)
+        postMessages(msg, texts)
+
+  addReactions = (names, ch, ts) ->
+    name = names.shift()
+    if name?
+      addReaction name, ch, ts, (body) ->
+        addReactions names, ch, ts
+
+  robot.respond /vote (.*)/i, (msg) ->
+    params = msg.match[1].trim().split(" ")
+    title = params[0]
+    items = params.slice(1).join(" ").split(",")
+    msg.send "#{title}の投票するよ"
+    msg.send "集計するときは-1を忘れずに(yamaboを除く)"
+    msg.send "-----------------------------------------"
+    postMessages msg, items

   # cron
   new cron '00 00 7 * * *', () ->


Change description

Change description

--- scripts/yamada.coffee
+++ scripts/yamada.coffee
@@ -100,4 +100,4 @@
    robot.respond /kindle/i, (msg) ->
      cheerio-httpcli.fetch 'http://ebook-sale.info/', {}, (err, $, res)->
        book =  $('header > h1 > a').first().text()
-       msg.send "今日の格安Kindle本は#{book}よ。買うしかないっしょ。"
+       msg.send "今日のKindle日替わりセール本は#{book}よ。買うしかないっしょ。"


document a possible error case

document a possible error case

--- earl.coffee
+++ earl.coffee
@@ -112,7 +112,12 @@
   dom.HISTORY_IGNORANT_LINK = window.A
   history_aware_links_installed = true

-  # todo: we might need to call statebus load_component...
+  # This has to occur BEFORE statebus runs make_component on everything in window.dom. That happens
+  # after the initial compilation and evaluation of the coffeescript code. So you should call
+  # Earl.start_work should be called during the initial evaluation of the coffeescript if you want
+  # history aware links.
+  # TODO: Either find a way around this requirement, or detect a bad sitution and provide a good
+  #       error message.
   dom.A = ->
     props = @props
     if @props.href


Always return created view

Always return created view

--- src/packages/command-logger/lib/command-logger.coffee
+++ src/packages/command-logger/lib/command-logger.coffee
@@ -35,6 +35,7 @@
     {@eventLog}

   createView: ->
-    unless @commandLoggerView
+    unless @commandLoggerView?
       CommandLoggerView = require './command-logger-view'
       @commandLoggerView = new CommandLoggerView
+    @commandLoggerView


change del keyword to delete

change del keyword to delete

--- server/controllers/routes.coffee
+++ server/controllers/routes.coffee
@@ -20,10 +20,10 @@

     # Data management
     'data/': post: [
-                utils.checkPermissionsByBody
-                data.encryptPassword
-                data.create
-            ]
+            utils.checkPermissionsByBody
+            data.encryptPassword
+            data.create
+        ]
     'data/:id/':
         get: [
             utils.getDoc
@@ -44,7 +44,7 @@
             data.update
             utils.unlockRequest
         ]
-        del: [
+        delete: [
             utils.lockRequest
             utils.getDoc
             utils.checkPermissionsByDoc
@@ -78,7 +78,7 @@
             requests.definition
             utils.unlockRequest
         ]
-        del: [
+        delete: [
             utils.checkPermissionsByType
             utils.lockRequest
             requests.remove
@@ -106,7 +106,7 @@
     ]
     'data/:id/attachments/:name':
         get: [utils.getDoc, utils.checkPermissionsByDoc, attachments.get]
-        del: [
+        delete: [
             utils.lockRequest
             utils.getDoc
             utils.checkPermissionsByDoc
@@ -123,7 +123,7 @@
     ]
     'data/:id/binaries/:name':
         get: [utils.getDoc, utils.checkPermissionsByDoc, binaries.get]
-        del: [
+        delete: [
             utils.lockRequest
             utils.getDoc
             utils.checkPermissionsByDoc
@@ -137,7 +137,7 @@

     # Device management
     'device/': post: [utils.checkPermissionsFactory('device'), devices.create]
-    'device/:id/': del: [
+    'device/:id/': delete: [
         utils.checkPermissionsFactory('device')
         utils.lockRequest
         utils.getDoc
@@ -154,7 +154,7 @@
             indexer.index
             utils.unlockRequest
         ]
-        del: [
+        delete: [
             utils.lockRequest
             utils.getDoc
             utils.checkPermissionsByDoc
@@ -162,7 +162,7 @@
             utils.unlockRequest
         ]
     'data/search/:type': post: [utils.checkPermissionsByType, indexer.search]
-    'data/index/clear-all/': del: [
+    'data/index/clear-all/': delete: [
         utils.checkPermissionsFactory('all')
         indexer.removeAll
     ]
@@ -192,5 +192,5 @@
     'accounts/password/':
         post: [account.checkPermissions, account.initializeKeys]
         put: [account.checkPermissions, account.updateKeys]
-    'accounts/reset/': del: [account.checkPermissions, account.resetKeys]
-    'accounts/': del: [account.checkPermissions, account.deleteKeys]
+    'accounts/reset/': delete: [account.checkPermissions, account.resetKeys]
+    'accounts/': delete: [account.checkPermissions, account.deleteKeys]


test #6

test #6

--- lib/TouchGesture.coffee
+++ lib/TouchGesture.coffee
@@ -1,25 +0,0 @@
-# Guesture Side
-# fires events on gestures from left and right
-
-# Use Settings from package.json
-{settings} = require '../package.json'
-Gesture = require './gesture'
-
-module.exports =
-class TouchGesture
-  constructor: (options) ->
-    {@onThreeLeft,@element} = options
-
-    addEvent 'touchstart',@touchStart
-    addEvent 'touchmove',@touchMove
-    addEvent 'touchend',@touchEnd
-
-  touchStart: (event) ->
-    console.log event.touches
-    console.log "3Finger" if event.touches.length==3
-
-  addEvent: (name,callBack) ->
-    @element.addEventListener name, (e) => @fortifyEvent e,callBack
-
-  fortifyEvent: (event,callFunction) ->
-    callFunction event


update gulp to force tag update.

update gulp to force tag update.

--- gulp.coffee
+++ gulp.coffee
@@ -82,7 +82,7 @@

 gulp.task 'git-tag', (cb) ->
   {version} = GET_VERSION()
-  git.tag version, "Created tag for release #{version}", (cb)
+  git.tag version, "Created tag for release #{version}", {args : '-f'}, (cb)

 gulp.task 'git-push', (cb) ->
   git.push('origin', 'master', {args : '--tags'}, cb)


Fix: remove extra dependency.

Fix: remove extra dependency.

--- src/app.coffee
+++ src/app.coffee
@@ -74,7 +74,6 @@
 (
     Models,
     widgets,
-    views,
     p13n,
     MapView,
     landing_page,
@@ -413,7 +412,7 @@
                 deferred
             )($.Deferred())

-    app = new Backbone.Marionette.Application()
+    app = new Marionette.Application()

     app_models =
         services: new Models.ServiceList()


condition in file name

condition in file name

--- src/app.coffee
+++ src/app.coffee
@@ -79,7 +79,8 @@
     dest: process.cwd()
     date:
         year: new Date().getFullYear()
-    filters: ['**/*', '**/.*']
+    # filters: ['**/*', '**/.*']
+    filters: ['**/*']

 commander
     .version require('../package.json').version
@@ -182,15 +183,27 @@
             process.exit 0

     for file in context.files
+
+        target = file
+
+        test = /// \( ([+-])? ([a-z0-9]+) \) ///g
+        install = yes
+        while (m = test.exec file)?
+            if m[2] of context
+                install = install && context[m[2]] if m[1] == '+' || not m[1]?
+                install = install && !context[m[2]] if m[1] == '-'
+                target = target.replace m[0], ''
+
+        (bar.tick(); continue) unless install
+
         opt = []
-        target = file
         for s in pack.special || []
             if minimatch target, s[0], {dot:true, nocase:true}
                 opt = s[1].split ';'

         mark = /// \{ (\w+) \} ///g
         while (m = mark.exec file)?
-            target = target.replace '{' + m[1] + '}', context[m[1]]
+            target = target.replace '{' + m[1] + '}', context[m[1]] if m[1] of context

         for o in opt
             if o[..6] == 'rename:'


fix #8

fix #8

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -6,7 +6,7 @@
   config:
     cursorType:
       type: 'string'
-      default: '\u25AE - Block'
+      default: 'block'
       enum: [
         'block'
         'bordered-box'


No hipchat for analytics.log.event post errors

--- server/routes/db.coffee
+++ server/routes/db.coffee
@@ -43,10 +43,13 @@
       handler[req.route.method](req, res, parts[1..]...)
     catch error
       errorMessage = "Error trying db method #{req?.route?.method} route #{parts} from #{name}: #{error}"
+      # TODO: add user info to this log
       log.error(errorMessage)
       log.error(error)
       log.error(error.stack)
-      hipchat.sendTowerHipChatMessage errorMessage
+      # TODO: Generally ignore this error: error: Error trying db method get route analytics.log.event from undefined: Error: Cannot find module '../undefined'
+      unless "#{parts}" is 'analytics.log.event'
+        hipchat.sendTowerHipChatMessage errorMessage
       errors.notFound(res, "Route #{req?.path} not found.")

 getSchema = (req, res, moduleName) ->


Handle case where user has no SC uploads

--- server/methods.coffee
+++ server/methods.coffee
@@ -345,7 +345,7 @@
       logger.debug("Couldn't find user in database")
       []
     else
-      scProfile = user.profile.soundCloud
+      scUploads = user.profile.soundCloud?.uploads || []
       R.filter(((x) -> x?), R.map((upload) ->
         uploadUrl = "http://soundcloud.com/#{upload.path}"
         url = "http://soundcloud.com/oembed?format=json&url=#{uploadUrl}"
@@ -357,7 +357,7 @@

         logger.debug("Got content:", content)
         JSON.parse(content)
-      , scProfile.uploads))
+      , scUploads))
 })

 insertTrelloBoard = (data, user) ->


CellManager: Fix algorithm of getCurrentCell

* Search all decorators (do not assume the list of decorators is ordered
  by row number).

--- lib/cell-manager.coffee
+++ lib/cell-manager.coffee
@@ -38,10 +38,11 @@
             decorationRow = decoration.marker.getStartBufferPosition().row

             if decorationRow >= row
-                end = decorationRow
-                break
-
-            start = decorationRow + 1
+                if (decorationRow < end)
+                    end = decorationRow
+            else
+                if (decorationRow >= start)
+                    start = decorationRow + 1

         console.log 'CellManager: Cell [start, end]:', [start, end], 'row:', row
         return [start, end]


improving/minimizing the provided test cases for #1014

--- test/range_literals.coffee
+++ test/range_literals.coffee
@@ -76,27 +76,10 @@
   eq  99, up[len - 1]

 test "#1014 slices with arguments object", ->
-  useArg0AtEnd = ->
-    ary = -> [0..arguments[0]]
-    ary 9
-  arg0End = useArg0AtEnd()
-
-  useArg0AtStart = ->
-    ary = -> [arguments[0]..9]
-    ary 0
-  arg0Start = useArg0AtStart()
-
-  useArgs0And1 = ->
-    ary = -> [arguments[0]..arguments[1]]
-    ary 0,9
-  args0And1 = useArgs0And1()
-
-  useArg0FromOuter = ->
-    ary = -> [arguments[0]..9]
-    ary(arguments[0])
-  arg0FromOuter = useArg0FromOuter(0)
-
-  arrayEq arg0End       , shared
-  arrayEq arg0Start     , shared
-  arrayEq args0And1     , shared
-  arrayEq arg0FromOuter , shared
+  expected = [0..9]
+  argsAtStart = (-> [arguments[0]..9]) 0
+  arrayEq expected, argsAtStart
+  argsAtEnd = (-> [0..arguments[0]]) 9
+  arrayEq expected, argsAtEnd
+  argsAtBoth = (-> [arguments[0]..arguments[1]]) 0, 9
+  arrayEq expected, argsAtBoth


Smaller size for graphite data.

Smaller size for graphite data.


Little refactor: make renaming function a bit simpler

--- src/lambda.coffee
+++ src/lambda.coffee
@@ -125,7 +125,9 @@
       if (freeIn t.varName, s) and (freeIn x, t.body)
         # (λy.E)[x := S] = λy'.(E[y := y'][x := S])
         # TODO This is alpha-conversion. Multiple of them can happen inside the same beta-reduction.
-        substitute (renameAbstractionVar t, s), x, s
+        newVarName = renameVar t.varName, t.body, s
+        renamedBody = substitute t.body, t.varName, new Variable newVarName
+        new Abstraction newVarName, (substitute renamedBody, x, s)
       else
         # (λy.E)[x := S] = λy.(E[x := S])
         new Abstraction t.varName, (substitute t.body, x, s)
@@ -138,26 +140,22 @@
           "Macros cannot have free variables"
       t

-# Renames the variable of an abstraction to avoid naming conflicts when doing a
-# substitution.
-renameAbstractionVar = (abstraction, substitutionTerm) ->
-  {varName, body} = abstraction
+# Renames a variable to avoid naming conflicts when doing a substitution.
+renameVar = (oldName, t, s) ->
   # Split the name into base and number part.
-  base = varName.replace /\d+$/, ''
-  n = if m = varName.match /\d+$/ then parseInt m[0] else 0
-
-  until newName and isValid
+  base = oldName.replace /\d+$/, ''
+  n = if m = oldName.match /\d+$/ then parseInt m[0] else 0
+
+  loop
     newName = base + ++n
     isValid =
       # Avoid name collisions with substitution term.
-      not (freeIn newName, substitutionTerm) and
+      not (freeIn newName, s) and
       # Avoid name collisions with free variables in body.
-      not (freeIn newName, body) and
+      not (freeIn newName, t) and
       # Avoid name collisions with inner abstractions.
-      not (varRenameCollides body, varName, newName)
-
-  # A new abstraction with the new name and the body with the renamed var.
-  new Abstraction newName, (substitute body, varName, new Variable newName)
+      not (varRenameCollides t, oldName, newName)
+    return newName if isValid

 # Whether the variable x is free in the term t.
 freeIn = (x, t) ->


Remove F5 command

Remove F5 command

Too much use

--- keymaps/remote-ftp.cson
+++ keymaps/remote-ftp.cson
@@ -3,4 +3,3 @@
   'ctrl-alt-d': 'remote-ftp:disconnect'
   'ctrl-alt-c': 'remote-ftp:connect'
   'cmd-shift-u': 'remote-ftp:upload-selected'
-  'f5': 'remote-ftp:download-active'


changed default routing regexp to be restricted to /styles.css

--- modules/gui/platforms/node/lib/StylusContext.coffee
+++ modules/gui/platforms/node/lib/StylusContext.coffee
@@ -8,7 +8,7 @@
         configure: (config)->
             super new floyd.Config
                 data:
-                    route: '/styles.css'
+                    route: '^/styles.css'
                     file: '/public/styles.styl'

             , config


Create temp directory if it doesn't exist.

--- src/server.coffee
+++ src/server.coffee
@@ -12,15 +12,17 @@
 app.use busboy()

 app.post "#{baseurl}/radial", (req, res, next) ->
+	if not fs.existsSync 'tmp_uploads/'
+		fs.mkdirSync 'tmp_uploads/'
 	req.pipe req.busboy
 	req.busboy.on 'file', (fieldname, file, filename) ->
 		console.log "Uploading #{filename}"

-		fstream = fs.createWriteStream __dirname + '/uploads/' + filename
+		fstream = fs.createWriteStream __dirname + '/tmp_uploads/' + filename
 		file.pipe fstream
 		fstream.on 'close', () ->
 			console.log "Finished uploading #{filename}"
-			file = 'uploads/' + filename
+			file = 'tmp_uploads/' + filename
 			if file?
 				radar = new nx.NexradDecoder()
 				radar.setFileResource file
@@ -36,7 +38,7 @@
 				res.json
 					data: false

-			fs.remove "uploads/#{filename}", (err) ->
+			fs.remove "tmp_uploads/#{filename}", (err) ->
 				if err
 					console.error err
 				else


adapt scrollbar range

adapt scrollbar range

Former-commit-id: 93cc6ed2d9817441222df73a84765314f6c88a32 [formerly 8bba38e11a01aa45259139862dcc19a3ee33acb4]
Former-commit-id: 595c9e30e9726579e57efdcaf0181e3c28a2b455

--- js/msa/zoombar.coffee
+++ js/msa/zoombar.coffee
@@ -8,7 +8,7 @@
       zoomSlider.name = "points"

       min = 1 unless min?
-      max = 30 unless max?
+      max = Math.max 30,@msa.zoomer.level unless max?

       zoomSlider.min = min
       zoomSlider.max = max


Webserver: Generate a new guest account when fakeClient does not have a delegate

--- server/lib/server/client.coffee
+++ server/lib/server/client.coffee
@@ -1,3 +1,6 @@
+{argv} = require 'optimist'
+Object.defineProperty global, 'KONFIG',
+  value: require('koding-config-manager').load("main.#{argv.c}")
 bongo = require './bongo'

 handleError = (err, callback) ->
@@ -39,7 +42,28 @@
   if not clientId and req.pendingCookies?.clientId
     clientId = req.pendingCookies.clientId

-  generateFakeClient { clientId, groupName, section }, callback
+  generateFakeClient { clientId, groupName, section }, (err, fakeClient) ->
+    return callback err  if err?
+    {delegate} = fakeClient.connection
+    return callback null, fakeClient  if delegate?
+
+    bongo.models.JSession.fetchSession {}, (err, {session, account})->
+      return callback err  if err?
+      return callback {message: "account cannot be created"}  unless account? and session?
+      { maxAge, secure } = KONFIG.sessionCookie
+
+      # set cookie as pending cookie
+      req.pendingCookies or= {}
+      req.pendingCookies.clientId = session.clientId
+
+      res.cookie "clientId", session.clientId, { maxAge, secure }
+
+      fakeClient.connection.delegate  = account
+      fakeClient.connection.groupName = groupName
+      fakeClient.context.user         = session.username
+      fakeClient.context.group        = groupName
+
+      callback null, fakeClient

 generateFakeClient = ({ clientId, groupName, section }, callback) ->


this seems to be important

this seems to be important

--- src/fn.coffee
+++ src/fn.coffee
@@ -14,7 +14,7 @@
                     parent._browser.wrapped = no
                     $par = $par.not(':first') # rm placeholder span
                     $parpar?.splice($parpar.index($par), i+1, $par...)
-            else if parent._browser.insert is true
+            else if $par.parent().length > 0
                 $el.insertAfter($par[i])
         else
             $par.append($el)


* pass the cwd to test

* pass the cwd to test

--- test/index.coffee
+++ test/index.coffee
@@ -67,7 +67,7 @@
         done(err)
   describe '#_loadStatSync(options)', ->
     it 'should load a file stat object', ->
-      cwd = __dirname
+      cwd = @cwd || __dirname
       vPath = 'fixtures/folder'
       file = @File(cwd: cwd, path: vPath)
       stat = file._loadStatSync file.mergeTo()
@@ -78,14 +78,16 @@
     it 'should load contents of a path', ->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       contents = file.loadContentSync()
       contents.should.be.equal file.contents
       loadContentTest contents, contentsForLoad
     it 'should load stream contents of a path', (done)->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       contents = file.loadContentSync(buffer: false)
       contents.should.be.equal file.contents
       loadContentTest contents, contentsForLoad, false, done
@@ -93,7 +95,8 @@
     it 'should load contents of a path', (done)->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       file.loadContent (err, contents)->
         if not err
           contents.should.be.equal file.contents
@@ -102,7 +105,8 @@
     it 'should load stream contents of a path', (done)->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       file.loadContent buffer:false, (err, contents)->
         if not err
           contents.should.be.equal file.contents
@@ -112,7 +116,7 @@

   describe '#load', ->
     it 'should load a path(only file stat loaded)', (done)->
-      cwd = __dirname
+      cwd = @cwd || __dirname
       vPath = @contentPath
       file = @File(cwd: cwd, path: vPath)
       file.load (err, result)->
@@ -124,7 +128,8 @@
     it 'should load contents of a path', (done)->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       file.load read:true, (err, contents)->
         if not err
           contents.should.be.equal file.contents
@@ -133,7 +138,8 @@
     it 'should load stream contents of a path', (done)->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       file.load read:true,buffer:false, (err, contents)->
         if not err
           contents.should.be.equal file.contents
@@ -142,7 +148,7 @@
           done(err)
   describe '#loadSync', ->
     it 'should load a path(only file stat loaded)', ->
-      cwd = __dirname
+      cwd = @cwd || __dirname
       vPath = @contentPath
       file = @File(cwd: cwd, path: vPath)
       result = file.loadSync()
@@ -152,14 +158,16 @@
     it 'should load contents of a path', ->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       contents = file.loadSync(read:true)
       contents.should.be.equal file.contents
       loadContentTest contents, contentsForLoad
     it 'should load stream contents of a path', (done)->
       contentsForLoad = @content
       loadContentTest = @loadContentTest
-      file = @File cwd: __dirname, path: @contentPath
+      cwd = @cwd || __dirname
+      file = @File cwd: cwd, path: @contentPath
       contents = file.loadSync(read:true, buffer: false)
       contents.should.be.equal file.contents
       loadContentTest contents, contentsForLoad, false, done


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Give PackageCard options a default value in case it doesn't get passed

--- lib/package-card.coffee
+++ lib/package-card.coffee
@@ -55,7 +55,7 @@
                 @span class: 'disable-text', 'Disable'
               @button type: 'button', class: 'btn status-indicator', tabindex: -1, outlet: 'statusIndicator'

-  initialize: (@pack, @packageManager, options) ->
+  initialize: (@pack, @packageManager, options={}) ->
     @disposables = new CompositeDisposable()

     # It might be useful to either wrap @pack in a class that has a ::validate


check for all versions of garena

check for all versions of garena

fixes #63

--- lib/path_manager.coffee
+++ lib/path_manager.coffee
@@ -51,13 +51,17 @@
       done new Error('Path not found'), selected_path

   else
-    # Default install, Garena Check 2
+    garena_glob = glob.sync(path.join(selected_path, 'GameData/Apps/**/Game/Config/'))
+
+    # Default install, Garena Check 1
     if fs.existsSync(path.join(selected_path, 'lol.launcher.exe')) or fs.existsSync(path.join(selected_path, 'League of Legends.exe'))
       done null, selected_path, 'Config/Champions/'

-    # Garena Installation Check 1
-    else if fs.existsSync(path.join(selected_path + 'LoLLauncher.exe'))
-      done null, selected_path, 'GameData/Apps/LoL/Game/Config/Champions/'
+    # Garena Installation Check 2
+    else if fs.existsSync(path.join(selected_path, 'LoLLauncher.exe')) and garena_glob
+      garena_split = garena_glob[0].split('/')
+      garena_version = garena_split[garena_split.length - 4]
+      done null, selected_path, 'GameData/Apps/' + garena_version + '/Game/Config/Champions/'

     else
       done new Error('Path not found'), selected_path


Proper resolve.

Proper resolve.

--- lib/price-import.coffee
+++ lib/price-import.coffee
@@ -34,7 +34,7 @@
             switch r.statusCode
               when 201 then @_summary.created++
               when 200 then @_summary.updated++
-          Promise.resolve('huhu')
+          Promise.resolve(@_summary)
     ,{concurrency: 1}

   _extractUniqueSkus: (prices) ->


manage suspend/resume

manage suspend/resume

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -687,6 +687,42 @@
   force: { type: 'boolean', optional: true }
 }

+exports.suspend = ({id}) ->
+  try
+    VM = @getObject id, 'VM'
+  catch
+    @throw 'NO_SUCH_OBJECT'
+
+  xapi = @getXAPI VM
+
+  $wait xapi.call 'VM.suspend', VM.ref
+
+  return true
+exports.suspend.permission = 'admin'
+exports.suspend.params = {
+  id: { type: 'string' }
+}
+
+exports.resume = ({id, force}) ->
+  try
+    VM = @getObject id, 'VM'
+  catch
+    @throw 'NO_SUCH_OBJECT'
+
+  xapi = @getXAPI VM
+
+  if not force
+    force = true
+
+  $wait xapi.call 'VM.resume', VM.ref, false, force
+
+  return true
+exports.resume.permission = 'admin'
+exports.resume.params = {
+  id: { type: 'string' }
+  force: { type: 'boolean', optional: true }
+}
+
 # revert a snapshot to its parent VM
 exports.revert = ({id}) ->
   try


Fix web server main handler route method

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -117,7 +117,7 @@
 app.post '/-/gravatar'                           , require './handlers/gravatar'
 app.get  '/-/confirm'                            , require './handlers/confirm'
 app.get  '/-/loginwithtoken'                     , require './handlers/loginwithtoken'
-app.all  '/:name/:section?/:slug?'               , require './handlers/main.coffee'
+app.get  '/:name/:section?/:slug?'               , require './handlers/main.coffee'
 app.get  '*'                                     , require './handlers/root.coffee'

 # once bongo is ready we can start listening


onlyMetadata param for vm.export() is optional.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -676,7 +676,7 @@
 export_.params = {
   vm: { type: 'string' }
   compress: { type: 'boolean', optional: true }
-  onlyMetadata: { type: 'boolean', optional: false }
+  onlyMetadata: { type: 'boolean', optional: true }
 }

 export_.resolve = {


vm.create() can set the description.

vm.create() can set the description.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -30,6 +30,7 @@
 # TODO: Implement ACLs
 create = $coroutine ({
   installation
+  name_description
   name_label
   template
   VDIs
@@ -37,6 +38,7 @@
 }) ->
   vm = yield @getXAPI(template).createVm(template.id, {
     installRepository: installation && installation.repository,
+    nameDescription: name_description,
     nameLabel: name_label,
     vdis: VDIs,
     vifs: VIFs
@@ -56,8 +58,9 @@
     }
   }

-  # Name of the new VM.
+  # Name/description of the new VM.
   name_label: { type: 'string' }
+  name_description: { type: 'string', optional: true }

   # TODO: add the install repository!
   # VBD.insert/eject


Initial media-grid unit tests.

Initial media-grid unit tests.

--- tests/js/media-grid.coffee
+++ tests/js/media-grid.coffee
@@ -23,18 +23,32 @@

-

           """
           .mediaGrid().mediaGrid()

     test '#_updateMetrics', (assert) ->
       {$el, $items} = @mg
-      $el.css width: 600
-      $items.css marginRight: 10, width: 200, height: 200
-      @mg._updateMetrics()
+      $el.add($items).css boxSizing: 'border-box'
+      $el.css width: 620, padding: 10
+      $items.css marginRight: 10, padding: 9, borderWidth: 1, width: 200, height: 200
+      @mg._updateMetrics(on)
+      assert.strictEqual @mg.metrics.gutter, 10,
+        'It calculates gutter based on item margin sizes.'
+      assert.strictEqual @mg.metrics.itemWidth, 200,
+        "It uses the item's 'outerWidth' as item width."
       assert.strictEqual @mg.metrics.rowSize, 2,
         'It calculates row size based on item and wrap sizes.'
+      assert.strictEqual @mg.metrics.colSize, 2,
+        'It calculates column size based on row size.'
+
+      $el.css width: 630
+      @mg._updateMetrics(on)
+      assert.strictEqual @mg.metrics.rowSize, 3,
+        'It calculates row size based on item and wrap sizes.'
+      assert.strictEqual @mg.selectByClass('sample').length, 1,
+        'It cleans up after multiple hard updates.'
+      return

     QUnit.start()
     return


support for scraperwiki.tool.getURL

support for scraperwiki.tool.getURL

--- shared/code/scraperwiki.coffee
+++ shared/code/scraperwiki.coffee
@@ -231,7 +231,7 @@

 scraperwiki.tool.pushSql = scraperwiki.dataset.installTool
 scraperwiki.tool.rename = scraperwiki.dataset.name
-scraperwiki.tool.getUrl = scraperwiki.tool.redirect = scraperwiki.url
+scraperwiki.tool.getUrl = scraperwiki.tool.getURL = scraperwiki.tool.redirect = scraperwiki.url
 scraperwiki.sql = scraperwiki.dataset.sql
 scraperwiki.sql.meta = scraperwiki.dataset.sql.meta
 scraperwiki.exec = scraperwiki.tool.exec


add ti.api log/error/debug/warn snippets

--- snippets/tialloy.cson
+++ snippets/tialloy.cson
@@ -32,6 +32,18 @@
   'Alloy Create Widget':
     'prefix': 'ACW'
     'body': 'Alloy.createWidget(\'${1}\')'
+  'Ti.API.error':
+    'prefix': 'terror'
+    'body': 'Ti.API.error(${1});'
+  'Ti.API.debug':
+    'prefix': 'tdebug'
+    'body': 'Ti.API.debug(${1});'
+  'Ti.API.info':
+    'prefix': 'tinfo'
+    'body': 'Ti.API.info(${1});'
+  'Ti.API.warn':
+    'prefix': 'twarn'
+    'body': 'Ti.API.warn(${1});'

 # '.source.css.tss':
   # 'Ti: new Style':


Call the new method to disableStockPackages

--- lib/sublime-tabs.coffee
+++ lib/sublime-tabs.coffee
@@ -15,6 +15,7 @@

   activate: (@state) ->
     @forceSettings()
+    @disableStockPackages()

     # tabs
     @paneSubscription = atom.workspaceView.eachPaneView (paneView) =>


Re-export TextObject.

Re-export TextObject.

Which was making some test cases fail.

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -94,4 +94,4 @@

     [false]

-module.exports = {SelectInsideWord, SelectInsideQuotes, SelectInsideBrackets}
+module.exports = {TextObject, SelectInsideWord, SelectInsideQuotes, SelectInsideBrackets}